Bug 1579049 - Part2: Expose proxy response error code in nsIProxiedChannel.idl r=mayhemer
authorKershaw Chang <kershaw@mozilla.com>
Thu, 21 Nov 2019 16:02:47 +0000
changeset 503393 56b7bbfba2265b0df382e3c2de2255f0bccc05c6
parent 503392 1e3186c185dab5c4231d8a23b5027a90c9927ee3
child 503394 3079e0857fda4a4411bb5ff862ad635f2300ac8d
push id36833
push userbtara@mozilla.com
push dateFri, 22 Nov 2019 21:40:53 +0000
treeherdermozilla-central@2c912e46295e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer
bugs1579049
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1579049 - Part2: Expose proxy response error code in nsIProxiedChannel.idl r=mayhemer Differential Revision: https://phabricator.services.mozilla.com/D51143
js/xpconnect/src/xpc.msg
netwerk/base/nsIProxiedChannel.idl
netwerk/protocol/ftp/FTPChannelChild.cpp
netwerk/protocol/ftp/nsFTPChannel.cpp
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpTransactionShell.h
netwerk/protocol/http/TunnelUtils.cpp
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
netwerk/test/unit/test_http1-proxy.js
netwerk/test/unit/test_http2-proxy.js
netwerk/test/unit/test_proxyconnect.js
--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -139,21 +139,28 @@ XPC_MSG_DEF(NS_ERROR_NO_CONTENT         
 XPC_MSG_DEF(NS_ERROR_IN_PROGRESS                    , "The requested action could not be completed while the object is busy")
 XPC_MSG_DEF(NS_ERROR_ALREADY_OPENED                 , "Channel is already open")
 XPC_MSG_DEF(NS_ERROR_INVALID_CONTENT_ENCODING       , "The content encoding of the source document is incorrect")
 XPC_MSG_DEF(NS_ERROR_CORRUPTED_CONTENT              , "Corrupted content received from server (potentially MIME type mismatch because of 'X-Content-Type-Options: nosniff')")
 XPC_MSG_DEF(NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY, "Couldn't extract first component from potentially corrupted header field")
 XPC_MSG_DEF(NS_ERROR_ALREADY_CONNECTED              , "The connection is already established")
 XPC_MSG_DEF(NS_ERROR_NOT_CONNECTED                  , "The connection does not exist")
 XPC_MSG_DEF(NS_ERROR_CONNECTION_REFUSED             , "The connection was refused")
-XPC_MSG_DEF(NS_ERROR_PROXY_CONNECTION_REFUSED       , "The connection to the proxy server was refused")
-XPC_MSG_DEF(NS_ERROR_PROXY_AUTHENTICATION_FAILED    , "The proxy requires authentication")
-XPC_MSG_DEF(NS_ERROR_PROXY_BAD_GATEWAY              , "The request failed on the proxy")
-XPC_MSG_DEF(NS_ERROR_PROXY_GATEWAY_TIMEOUT          , "The request timed out on the proxy")
-XPC_MSG_DEF(NS_ERROR_PROXY_TOO_MANY_REQUESTS        , "Sending too many requests to a proxy")
+
+/* Error codes return from the proxy */
+XPC_MSG_DEF(NS_ERROR_PROXY_CONNECTION_REFUSED           , "The connection to the proxy server was refused")
+XPC_MSG_DEF(NS_ERROR_PROXY_AUTHENTICATION_FAILED        , "The proxy requires authentication")
+XPC_MSG_DEF(NS_ERROR_PROXY_BAD_GATEWAY                  , "The request failed on the proxy")
+XPC_MSG_DEF(NS_ERROR_PROXY_GATEWAY_TIMEOUT              , "The request timed out on the proxy")
+XPC_MSG_DEF(NS_ERROR_PROXY_TOO_MANY_REQUESTS            , "Sending too many requests to a proxy")
+XPC_MSG_DEF(NS_ERROR_PROXY_VERSION_NOT_SUPPORTED        , "The proxy does not support the version of the HTTP request")
+XPC_MSG_DEF(NS_ERROR_PROXY_FORBIDDEN                    , "The user is banned from the proxy")
+XPC_MSG_DEF(NS_ERROR_PROXY_SERVICE_UNAVAILABLE          , "The proxy is not available")
+XPC_MSG_DEF(NS_ERROR_PROXY_UNAVAILABLE_FOR_LEGAL_REASONS, "The desired destination is unavailable for legal reasons")
+
 XPC_MSG_DEF(NS_ERROR_NET_TIMEOUT                    , "The connection has timed out")
 XPC_MSG_DEF(NS_ERROR_OFFLINE                        , "The requested action could not be completed in the offline state")
 XPC_MSG_DEF(NS_ERROR_PORT_ACCESS_NOT_ALLOWED        , "Establishing a connection to an unsafe or otherwise banned port was prohibited")
 XPC_MSG_DEF(NS_ERROR_NET_RESET                      , "The connection was established, but no data was ever received")
 XPC_MSG_DEF(NS_ERROR_NET_INTERRUPT                  , "The connection was established, but the data transfer was interrupted")
 XPC_MSG_DEF(NS_ERROR_NET_PARTIAL_TRANSFER           , "A transfer was only partially done when it completed")
 XPC_MSG_DEF(NS_ERROR_NOT_RESUMABLE                  , "This request is not resumable, but it was tried to resume it, or to request resume-specific data")
 XPC_MSG_DEF(NS_ERROR_ENTITY_CHANGED                 , "It was attempted to resume the request, but the entity has changed in the meantime")
--- a/netwerk/base/nsIProxiedChannel.idl
+++ b/netwerk/base/nsIProxiedChannel.idl
@@ -17,11 +17,20 @@ interface nsIProxiedChannel : nsISupport
 {
   /**
    * Gets the proxy info the channel was constructed with. null or a
    * proxyInfo with type "direct" mean no proxy.
    *
    * The returned proxy info must not be modified.
    */
   readonly attribute nsIProxyInfo proxyInfo;
+
+  /**
+   * The HTTP response code returned from the proxy to the CONNECT method.
+   * The response code is only available when we get the response from
+   * the proxy server, so this value is known in and after OnStartRequest.
+   *
+   * If CONNECT method is not used, httpProxyConnectResponseCode is always -1.
+   * After OnStartRequest, httpProxyConnectResponseCode is the real HTTP
+   * response code or 0 if we can't reach to the proxy.
+   */
+  readonly attribute int32_t httpProxyConnectResponseCode;
 };
-
-
--- a/netwerk/protocol/ftp/FTPChannelChild.cpp
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -106,16 +106,21 @@ NS_IMETHODIMP
 FTPChannelChild::GetEntityID(nsACString& aEntityID) {
   aEntityID = mEntityID;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FTPChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); }
 
+NS_IMETHODIMP FTPChannelChild::GetHttpProxyConnectResponseCode(
+    int32_t* aResponseCode) {
+  DROP_DEAD();
+}
+
 NS_IMETHODIMP
 FTPChannelChild::SetUploadStream(nsIInputStream* aStream,
                                  const nsACString& aContentType,
                                  int64_t aContentLength) {
   NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
   mUploadStream = aStream;
   // NOTE: contentLength is intentionally ignored here.
   return NS_OK;
--- a/netwerk/protocol/ftp/nsFTPChannel.cpp
+++ b/netwerk/protocol/ftp/nsFTPChannel.cpp
@@ -76,16 +76,21 @@ nsFtpChannel::GetEntityID(nsACString& en
 //-----------------------------------------------------------------------------
 NS_IMETHODIMP
 nsFtpChannel::GetProxyInfo(nsIProxyInfo** aProxyInfo) {
   nsCOMPtr<nsIProxyInfo> info = ProxyInfo();
   info.forget(aProxyInfo);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsFtpChannel::GetHttpProxyConnectResponseCode(
+    int32_t* aResponseCode) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 //-----------------------------------------------------------------------------
 
 nsresult nsFtpChannel::OpenContentStream(bool async, nsIInputStream** result,
                                          nsIChannel** channel) {
   if (!async) return NS_ERROR_NOT_IMPLEMENTED;
 
   SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
   RefPtr<nsFtpState> state = new nsFtpState();
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -3186,16 +3186,21 @@ HttpChannelChild::ClearClassFlags(uint32
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsIProxiedChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); }
 
+NS_IMETHODIMP HttpChannelChild::GetHttpProxyConnectResponseCode(
+    int32_t* aResponseCode) {
+  DROP_DEAD();
+}
+
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsIApplicationCacheContainer
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::GetApplicationCache(nsIApplicationCache** aApplicationCache) {
   NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
   return NS_OK;
--- a/netwerk/protocol/http/HttpTransactionShell.h
+++ b/netwerk/protocol/http/HttpTransactionShell.h
@@ -123,16 +123,17 @@ class HttpTransactionShell : public nsIS
   virtual bool HasStickyConnection() const = 0;
 
   virtual void SetH2WSConnRefTaken() = 0;
   virtual void SetTransactionObserver(TransactionObserver* arg) = 0;
   virtual void SetRequestContext(nsIRequestContext* aRequestContext) = 0;
   virtual void SetPushedStream(Http2PushedStreamWrapper* push) = 0;
 
   virtual bool ProxyConnectFailed() = 0;
+  virtual int32_t GetProxyConnectResponseCode() = 0;
   virtual bool ResolvedByTRR() = 0;
 
   virtual nsHttpTransaction* AsHttpTransaction() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransactionShell, HTTPTRANSACTIONSHELL_IID)
 
 #define NS_DECL_HTTPTRANSACTIONSHELL                                           \
@@ -171,14 +172,15 @@ NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransa
   virtual void SetDNSWasRefreshed() override;                                  \
   virtual void DontReuseConnection() override;                                 \
   virtual bool HasStickyConnection() const override;                           \
   virtual void SetH2WSConnRefTaken() override;                                 \
   virtual void SetTransactionObserver(TransactionObserver* arg) override;      \
   virtual void SetRequestContext(nsIRequestContext* aRequestContext) override; \
   virtual void SetPushedStream(Http2PushedStreamWrapper* push) override;       \
   virtual bool ProxyConnectFailed() override;                                  \
+  virtual int32_t GetProxyConnectResponseCode() override;                      \
   virtual bool ResolvedByTRR() override;                                       \
   virtual nsHttpTransaction* AsHttpTransaction() override;
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // HttpTransactionShell_h__
--- a/netwerk/protocol/http/TunnelUtils.cpp
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -1203,16 +1203,17 @@ void SpdyConnectTransaction::MapStreamTo
     mTunneledConn->SetInSpdyTunnel(true);
   }
 
   // make the originating transaction stick to the tunneled conn
   RefPtr<nsAHttpConnection> wrappedConn =
       gHttpHandler->ConnMgr()->MakeConnectionHandle(mTunneledConn);
   mDrivingTransaction->SetConnection(wrappedConn);
   mDrivingTransaction->MakeSticky();
+  mDrivingTransaction->OnProxyConnectComplete(httpResponseCode);
 
   if (!mIsWebsocket) {
     // jump the priority and start the dispatcher
     Unused << gHttpHandler->InitiateTransaction(
         mDrivingTransaction, nsISupportsPriority::PRIORITY_HIGHEST - 60);
     mDrivingTransaction = nullptr;
   }
 }
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -214,16 +214,18 @@ class nsAHttpTransaction : public nsSupp
   }
 
   virtual uint64_t TopLevelOuterContentWindowId() {
     MOZ_ASSERT(false);
     return 0;
   }
 
   virtual void SetFastOpenStatus(uint8_t aStatus) {}
+
+  virtual void OnProxyConnectComplete(int32_t aResponseCode) {}
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpTransaction, NS_AHTTPTRANSACTION_IID)
 
 #define NS_DECL_NSAHTTPTRANSACTION                                             \
   void SetConnection(nsAHttpConnection*) override;                             \
   nsAHttpConnection* Connection() override;                                    \
   void GetSecurityCallbacks(nsIInterfaceRequestor**) override;                 \
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -352,16 +352,17 @@ nsHttpChannel::nsHttpChannel()
       mOnTailUnblock(nullptr),
       mWarningReporter(nullptr),
       mIsReadingFromCache(false),
       mFirstResponseSource(RESPONSE_PENDING),
       mRaceCacheWithNetwork(false),
       mRaceDelay(0),
       mIgnoreCacheEntry(false),
       mRCWNLock("nsHttpChannel.mRCWNLock"),
+      mProxyConnectResponseCode(0),
       mDidReval(false) {
   LOG(("Creating nsHttpChannel [this=%p]\n", this));
   mChannelCreationTime = PR_Now();
   mChannelCreationTimestamp = TimeStamp::Now();
 }
 
 nsHttpChannel::~nsHttpChannel() {
   LOG(("Destroying nsHttpChannel [this=%p]\n", this));
@@ -1919,16 +1920,28 @@ nsresult nsHttpChannel::CallOnStartReque
       LOG(("offline cache is up to date, not updating"));
       CloseOfflineCacheEntry();
     }
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP nsHttpChannel::GetHttpProxyConnectResponseCode(
+    int32_t* aResponseCode) {
+  NS_ENSURE_ARG_POINTER(aResponseCode);
+
+  if (mConnectionInfo && mConnectionInfo->UsingConnect()) {
+    *aResponseCode = mProxyConnectResponseCode;
+  } else {
+    *aResponseCode = -1;
+  }
+  return NS_OK;
+}
+
 nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
   // Failure to set up a proxy tunnel via CONNECT means one of the following:
   // 1) Proxy wants authorization, or forbids.
   // 2) DNS at proxy couldn't resolve target URL.
   // 3) Proxy connection to target failed or timed out.
   // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
   //
   // Our current architecture would parse the proxy's response content with
@@ -7640,20 +7653,24 @@ nsHttpChannel::OnStartRequest(nsIRequest
   mAfterOnStartRequestBegun = true;
   if (mOnStartRequestTimestamp.IsNull()) {
     mOnStartRequestTimestamp = TimeStamp::Now();
   }
 
   Telemetry::Accumulate(Telemetry::HTTP_ONSTART_SUSPEND_TOTAL_TIME,
                         mSuspendTotalTime);
 
-  if (!mSecurityInfo && !mCachePump && mTransaction) {
-    // grab the security info from the connection object; the transaction
-    // is guaranteed to own a reference to the connection.
-    mSecurityInfo = mTransaction->SecurityInfo();
+  if (mTransaction) {
+    mProxyConnectResponseCode = mTransaction->GetProxyConnectResponseCode();
+
+    if (!mSecurityInfo && !mCachePump) {
+      // grab the security info from the connection object; the transaction
+      // is guaranteed to own a reference to the connection.
+      mSecurityInfo = mTransaction->SecurityInfo();
+    }
   }
 
   // don't enter this block if we're reading from the cache...
   if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
     // mTransactionPump doesn't hit OnInputStreamReady and call this until
     // all of the response headers have been acquired, so we can take ownership
     // of them from the transaction.
     mResponseHead = mTransaction->TakeResponseHead();
@@ -7753,17 +7770,20 @@ nsresult nsHttpChannel::ContinueOnStartR
   }
 
   // on proxy errors, try to failover
   if (mConnectionInfo->ProxyInfo() &&
       (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
        mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
        mStatus == NS_ERROR_NET_TIMEOUT)) {
     PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
-    if (NS_SUCCEEDED(ProxyFailover())) return NS_OK;
+    if (NS_SUCCEEDED(ProxyFailover())) {
+      mProxyConnectResponseCode = 0;
+      return NS_OK;
+    }
     PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
   }
 
   // Hack: ContinueOnStartRequest3 uses NS_OK to detect successful redirects,
   // so we distinguish this codepath (a non-redirect that's processing
   // normally) by passing in a bogus error code.
   return ContinueOnStartRequest3(NS_BINDING_FAILED);
 }
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -822,16 +822,20 @@ class nsHttpChannel final : public HttpB
   // OnCacheEntryCheck are no longer valid.
   bool mIgnoreCacheEntry;
   // Lock preventing OnCacheEntryCheck and SetupTransaction being called at
   // the same time.
   mozilla::Mutex mRCWNLock;
 
   TimeStamp mNavigationStartTimeStamp;
 
+  // We update the value of mProxyConnectResponseCode when OnStartRequest is
+  // called and reset the value when we switch to another failover proxy.
+  int32_t mProxyConnectResponseCode;
+
  protected:
   virtual void DoNotifyListenerCleanup() override;
 
   // Override ReleaseListeners() because mChannelClassifier only exists
   // in nsHttpChannel and it will be released in ReleaseListeners().
   virtual void ReleaseListeners() override;
 
   virtual void DoAsyncAbort(nsresult aStatus) override;
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -894,16 +894,25 @@ nsresult nsHttpConnection::Activate(nsAH
            this, static_cast<uint32_t>(rv)));
     }
   }
 
   if (mTLSFilter) {
     RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
     rv = mTLSFilter->SetProxiedTransaction(trans, baseTrans);
     NS_ENSURE_SUCCESS(rv, rv);
+    if (mTransaction->ConnectionInfo()->UsingConnect()) {
+      SpdyConnectTransaction* trans =
+          baseTrans ? baseTrans->QuerySpdyConnectTransaction() : nullptr;
+      if (trans && !trans->IsWebsocket()) {
+        // If we are here, the tunnel is already established. Let the
+        // transaction know that proxy connect is successful.
+        mTransaction->OnProxyConnectComplete(200);
+      }
+    }
     mTransaction = mTLSFilter;
   }
 
   trans->OnActivated();
 
   rv = OnOutputStreamReady(mSocketOut);
 
 failed_activation:
@@ -1001,16 +1010,22 @@ nsresult nsHttpConnection::AddTransactio
 
   nsHttpConnectionInfo* transCI = httpTransaction->ConnectionInfo();
 
   bool needTunnel = transCI->UsingHttpsProxy();
   needTunnel = needTunnel && !mTLSFilter;
   needTunnel = needTunnel && transCI->UsingConnect();
   needTunnel = needTunnel && httpTransaction->QueryHttpTransaction();
 
+  // Let the transaction know that the tunnel is already established and we
+  // don't need to setup the tunnel again.
+  if (transCI->UsingConnect() && mEverUsedSpdy && mTLSFilter) {
+    httpTransaction->OnProxyConnectComplete(200);
+  }
+
   bool isWebsocket = false;
   nsHttpTransaction* trans = httpTransaction->QueryHttpTransaction();
   if (trans) {
     isWebsocket = trans->IsWebsocketUpgrade();
     MOZ_ASSERT(!isWebsocket || !needTunnel, "Websocket and tunnel?!");
   }
 
   LOG(("nsHttpConnection::AddTransaction for %s%s",
@@ -1411,16 +1426,17 @@ nsresult nsHttpConnection::OnHeadersAvai
   if (mProxyConnectStream) {
     MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
                "SPDY NPN Complete while using proxy connect stream");
     mProxyConnectStream = nullptr;
     bool isHttps = mTransaction ? mTransaction->ConnectionInfo()->EndToEndSSL()
                                 : mConnInfo->EndToEndSSL();
     bool onlyConnect = mTransactionCaps & NS_HTTP_CONNECT_ONLY;
 
+    mTransaction->OnProxyConnectComplete(responseStatus);
     if (responseStatus == 200) {
       LOG(("proxy CONNECT succeeded! endtoendssl=%d onlyconnect=%d\n", isHttps,
            onlyConnect));
       // If we're only connecting, we don't need to reset the transaction
       // state. We need to upgrade the socket now without doing the actual
       // http request.
       if (!onlyConnect) {
         *reset = true;
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -126,17 +126,18 @@ nsHttpTransaction::nsHttpTransaction()
       mPassedRatePacing(false),
       mSynchronousRatePaceRequest(false),
       mClassOfService(0),
       mResolvedByTRR(false),
       m0RTTInProgress(false),
       mDoNotTryEarlyData(false),
       mEarlyDataDisposition(EARLY_NONE),
       mFastOpenStatus(TFO_NOT_TRIED),
-      mTrafficCategory(HttpTrafficCategory::eInvalid) {
+      mTrafficCategory(HttpTrafficCategory::eInvalid),
+      mProxyConnectResponseCode(0) {
   this->mSelfAddr.inet = {};
   this->mPeerAddr.inet = {};
   LOG(("Creating nsHttpTransaction @%p\n", this));
 
 #ifdef MOZ_VALGRIND
   memset(&mSelfAddr, 0, sizeof(NetAddr));
   memset(&mPeerAddr, 0, sizeof(NetAddr));
 #endif
@@ -2471,10 +2472,26 @@ bool nsHttpTransaction::IsWebsocketUpgra
 
 void nsHttpTransaction::SetH2WSTransaction(
     SpdyConnectTransaction* aH2WSTransaction) {
   MOZ_ASSERT(OnSocketThread());
 
   mH2WSTransaction = aH2WSTransaction;
 }
 
+void nsHttpTransaction::OnProxyConnectComplete(int32_t aResponseCode) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(mConnInfo->UsingConnect());
+
+  LOG(("nsHttpTransaction::OnProxyConnectComplete %p aResponseCode=%d", this,
+       aResponseCode));
+
+  MutexAutoLock lock(mLock);
+  mProxyConnectResponseCode = aResponseCode;
+}
+
+int32_t nsHttpTransaction::GetProxyConnectResponseCode() {
+  MutexAutoLock lock(mLock);
+  return mProxyConnectResponseCode;
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -132,16 +132,18 @@ class nsHttpTransaction final : public n
 
   void SetFastOpenStatus(uint8_t aStatus) override;
 
   void SetHttpTrailers(nsCString& aTrailers);
 
   bool IsWebsocketUpgrade();
   void SetH2WSTransaction(SpdyConnectTransaction*);
 
+  void OnProxyConnectComplete(int32_t aResponseCode) override;
+
  private:
   friend class DeleteHttpTransaction;
   virtual ~nsHttpTransaction();
 
   MOZ_MUST_USE nsresult Restart();
   char* LocateHttpStart(char* buf, uint32_t len, bool aAllowPartialMatch);
   MOZ_MUST_USE nsresult ParseLine(nsACString& line);
   MOZ_MUST_USE nsresult ParseLineSegment(char* seg, uint32_t len);
@@ -403,14 +405,15 @@ class nsHttpTransaction final : public n
 
   uint8_t mFastOpenStatus;
 
   // H2 websocket support
   RefPtr<SpdyConnectTransaction> mH2WSTransaction;
 
   HttpTrafficCategory mTrafficCategory;
   bool mThroughCaptivePortal;
+  int32_t mProxyConnectResponseCode;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // nsHttpTransaction_h__
--- a/netwerk/test/unit/test_http1-proxy.js
+++ b/netwerk/test/unit/test_http1-proxy.js
@@ -86,18 +86,20 @@ function make_channel(url) {
 function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL) {
   return new Promise(resolve => {
     channel.asyncOpen(
       new ChannelListener(
         (request, data) => {
           request.QueryInterface(Ci.nsIHttpChannel);
           const status = request.status;
           const http_code = status ? undefined : request.responseStatus;
-
-          resolve({ status, http_code, data });
+          request.QueryInterface(Ci.nsIProxiedChannel);
+          const proxy_connect_response_code =
+            request.httpProxyConnectResponseCode;
+          resolve({ status, http_code, data, proxy_connect_response_code });
         },
         null,
         flags
       )
     );
   });
 }
 
@@ -154,64 +156,72 @@ registerCleanupFunction(() => {
 // The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
 // code from the channel and not try to ask for any credentials.
 add_task(async function proxy_auth_failure() {
   const chan = make_channel(`https://407.example.com/`);
   const auth_prompt = { triggered: false };
   chan.notificationCallbacks = new AuthRequestor(
     () => new UnxpectedAuthPrompt2(auth_prompt)
   );
-  const { status, http_code } = await get_response(chan, CL_EXPECT_FAILURE);
+  const { status, http_code, proxy_connect_response_code } = await get_response(
+    chan,
+    CL_EXPECT_FAILURE
+  );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
+  Assert.equal(proxy_connect_response_code, 407);
   Assert.equal(http_code, undefined);
   Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
 });
 
 // 502 Bad gateway code returned by the proxy.
 add_task(async function proxy_bad_gateway_failure() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://502.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+  Assert.equal(proxy_connect_response_code, 502);
   Assert.equal(http_code, undefined);
 });
 
 // 504 Gateway timeout code returned by the proxy.
 add_task(async function proxy_gateway_timeout_failure() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://504.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
+  Assert.equal(proxy_connect_response_code, 504);
   Assert.equal(http_code, undefined);
 });
 
 // 404 Not Found means the proxy could not resolve the host.
 add_task(async function proxy_host_not_found_failure() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://404.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
+  Assert.equal(proxy_connect_response_code, 404);
   Assert.equal(http_code, undefined);
 });
 
 // 429 Too Many Requests means we sent too many requests.
 add_task(async function proxy_too_many_requests_failure() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://429.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS);
+  Assert.equal(proxy_connect_response_code, 429);
   Assert.equal(http_code, undefined);
 });
 
 add_task(async function shutdown() {
   await new Promise(resolve => {
     http_server.stop(resolve);
   });
 });
--- a/netwerk/test/unit/test_http2-proxy.js
+++ b/netwerk/test/unit/test_http2-proxy.js
@@ -101,18 +101,20 @@ function make_channel(url) {
 function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL) {
   return new Promise(resolve => {
     channel.asyncOpen(
       new ChannelListener(
         (request, data) => {
           request.QueryInterface(Ci.nsIHttpChannel);
           const status = request.status;
           const http_code = status ? undefined : request.responseStatus;
-
-          resolve({ status, http_code, data });
+          request.QueryInterface(Ci.nsIProxiedChannel);
+          const proxy_connect_response_code =
+            request.httpProxyConnectResponseCode;
+          resolve({ status, http_code, data, proxy_connect_response_code });
         },
         null,
         flags
       )
     );
   });
 }
 
@@ -334,20 +336,22 @@ add_task(async function proxy_success_on
   const foo = await get_response(
     make_channel(`https://foo.example.com/random-request-1`)
   );
   const alt1 = await get_response(
     make_channel(`https://alt1.example.com/random-request-2`)
   );
 
   Assert.equal(foo.status, Cr.NS_OK);
+  Assert.equal(foo.proxy_connect_response_code, 200);
   Assert.equal(foo.http_code, 200);
   Assert.ok(foo.data.match("random-request-1"));
   Assert.ok(foo.data.match("You Win!"));
   Assert.equal(alt1.status, Cr.NS_OK);
+  Assert.equal(alt1.proxy_connect_response_code, 200);
   Assert.equal(alt1.http_code, 200);
   Assert.ok(alt1.data.match("random-request-2"));
   Assert.ok(alt1.data.match("You Win!"));
   Assert.equal(
     await proxy_session_counter(),
     1,
     "Created just one session with the proxy"
   );
@@ -356,102 +360,111 @@ add_task(async function proxy_success_on
 // The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
 // code from the channel and not try to ask for any credentials.
 add_task(async function proxy_auth_failure() {
   const chan = make_channel(`https://407.example.com/`);
   const auth_prompt = { triggered: false };
   chan.notificationCallbacks = new AuthRequestor(
     () => new UnxpectedAuthPrompt2(auth_prompt)
   );
-  const { status, http_code } = await get_response(chan, CL_EXPECT_FAILURE);
+  const { status, http_code, proxy_connect_response_code } = await get_response(
+    chan,
+    CL_EXPECT_FAILURE
+  );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
+  Assert.equal(proxy_connect_response_code, 407);
   Assert.equal(http_code, undefined);
   Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
   Assert.equal(
     await proxy_session_counter(),
     1,
     "No new session created by 407"
   );
 });
 
 // 502 Bad gateway code returned by the proxy, still one session only, proper different code
 // from the channel.
 add_task(async function proxy_bad_gateway_failure() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://502.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+  Assert.equal(proxy_connect_response_code, 502);
   Assert.equal(http_code, undefined);
   Assert.equal(
     await proxy_session_counter(),
     1,
     "No new session created by 502 after 407"
   );
 });
 
 // Second 502 Bad gateway code returned by the proxy, still one session only with the proxy.
 add_task(async function proxy_bad_gateway_failure_two() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://502.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+  Assert.equal(proxy_connect_response_code, 502);
   Assert.equal(http_code, undefined);
   Assert.equal(
     await proxy_session_counter(),
     1,
     "No new session created by second 502"
   );
 });
 
 // 504 Gateway timeout code returned by the proxy, still one session only, proper different code
 // from the channel.
 add_task(async function proxy_gateway_timeout_failure() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://504.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
+  Assert.equal(proxy_connect_response_code, 504);
   Assert.equal(http_code, undefined);
   Assert.equal(
     await proxy_session_counter(),
     1,
     "No new session created by 504 after 502"
   );
 });
 
 // 404 Not Found means the proxy could not resolve the host.  As for other error responses
 // we still expect this not to close the existing session.
 add_task(async function proxy_host_not_found_failure() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://404.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
+  Assert.equal(proxy_connect_response_code, 404);
   Assert.equal(http_code, undefined);
   Assert.equal(
     await proxy_session_counter(),
     1,
     "No new session created by 404 after 504"
   );
 });
 
 add_task(async function proxy_too_many_requests_failure() {
-  const { status, http_code } = await get_response(
+  const { status, http_code, proxy_connect_response_code } = await get_response(
     make_channel(`https://429.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS);
+  Assert.equal(proxy_connect_response_code, 429);
   Assert.equal(http_code, undefined);
   Assert.equal(
     await proxy_session_counter(),
     1,
     "No new session created by 429 after 504"
   );
 });
 
@@ -461,18 +474,20 @@ add_task(async function proxy_success_st
     make_channel(`https://foo.example.com/random-request-1`)
   );
   const alt1 = await get_response(
     make_channel(`https://alt1.example.com/random-request-2`)
   );
 
   Assert.equal(foo.status, Cr.NS_OK);
   Assert.equal(foo.http_code, 200);
+  Assert.equal(foo.proxy_connect_response_code, 200);
   Assert.ok(foo.data.match("random-request-1"));
   Assert.equal(alt1.status, Cr.NS_OK);
+  Assert.equal(alt1.proxy_connect_response_code, 200);
   Assert.equal(alt1.http_code, 200);
   Assert.ok(alt1.data.match("random-request-2"));
   Assert.equal(
     await proxy_session_counter(),
     1,
     "No new session created after proxy error codes"
   );
 });
@@ -489,24 +504,27 @@ add_task(async function proxy_success_is
   const alt1 = await get_response(
     make_channel(`https://alt1.example.com/random-request-2`)
   );
   const lh = await get_response(
     make_channel(`https://localhost/random-request-3`)
   );
 
   Assert.equal(foo.status, Cr.NS_OK);
+  Assert.equal(foo.proxy_connect_response_code, 200);
   Assert.equal(foo.http_code, 200);
   Assert.ok(foo.data.match("random-request-1"));
   Assert.ok(foo.data.match("You Win!"));
   Assert.equal(alt1.status, Cr.NS_OK);
+  Assert.equal(alt1.proxy_connect_response_code, 200);
   Assert.equal(alt1.http_code, 200);
   Assert.ok(alt1.data.match("random-request-2"));
   Assert.ok(alt1.data.match("You Win!"));
   Assert.equal(lh.status, Cr.NS_OK);
+  Assert.equal(lh.proxy_connect_response_code, 200);
   Assert.equal(lh.http_code, 200);
   Assert.ok(lh.data.match("random-request-3"));
   Assert.ok(lh.data.match("You Win!"));
   Assert.equal(
     await proxy_session_counter(),
     2,
     "Just one new session seen after changing the isolation key"
   );
@@ -519,17 +537,19 @@ add_task(async function proxy_bad_gatewa
     CL_EXPECT_FAILURE
   );
   const failure2 = await get_response(
     make_channel(`https://502.example.com/`),
     CL_EXPECT_FAILURE
   );
 
   Assert.equal(failure1.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+  Assert.equal(failure1.proxy_connect_response_code, 502);
   Assert.equal(failure1.http_code, undefined);
   Assert.equal(failure2.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+  Assert.equal(failure2.proxy_connect_response_code, 502);
   Assert.equal(failure2.http_code, undefined);
   Assert.equal(
     await proxy_session_counter(),
     2,
     "No new session created by 502"
   );
 });
--- a/netwerk/test/unit/test_proxyconnect.js
+++ b/netwerk/test/unit/test_proxyconnect.js
@@ -61,30 +61,31 @@ var checkReadData = "";
 var threadManager;
 var socket;
 var streamIn;
 var streamOut;
 var accepted = false;
 var acceptedSocket;
 var state = STATE_NONE;
 var transportAvailable = false;
+var proxiedChannel;
 var listener = {
   expectedCode: -1, // uninitialized
 
   onStartRequest: function test_onStartR(request) {},
 
   onDataAvailable: function test_ODA() {
     do_throw("Should not get any data!");
   },
 
   onStopRequest: function test_onStopR(request, status) {
     if (state === STATE_COMPLETED) {
       Assert.equal(transportAvailable, false, "transport available not called");
       Assert.equal(status, 0x80004005, "error code matches");
-
+      Assert.equal(proxiedChannel.httpProxyConnectResponseCode, 200);
       nextTest();
       return;
     }
 
     Assert.equal(accepted, true, "socket accepted");
     accepted = false;
   },
 };
@@ -285,16 +286,17 @@ function createProxy() {
 
 function test_connectonly() {
   Services.prefs.setCharPref("network.proxy.ssl", "localhost");
   Services.prefs.setIntPref("network.proxy.ssl_port", socketserver_port);
   Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
   Services.prefs.setIntPref("network.proxy.type", 1);
 
   var chan = makeChan();
+  proxiedChannel = chan.QueryInterface(Ci.nsIProxiedChannel);
   chan.asyncOpen(listener);
 
   do_test_pending();
 }
 
 function test_connectonly_noproxy() {
   clearPrefs();
   var chan = makeChan();