Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 10, 2025

Per RFC 9110 §9.3.6 and RFC 9112 §6.3, any 2xx status code indicates successful CONNECT tunnel establishment, not just 200 OK. The current implementation rejects valid 201/202/204 responses.

Changes:

  • HttpConnection.cs: Use IsSuccessStatusCode instead of exact == HttpStatusCode.OK check; reorder conditions to check CONNECT before NoContent to fix 204 handling
  • HttpConnectionPool.cs: Accept IsSuccessStatusCode for proxy tunnel establishment
  • Http2Stream.cs: Accept statusCode >= 200 && statusCode <= 299 for Extended CONNECT

Before:

else if (request.Method.IsConnect && response.StatusCode == HttpStatusCode.OK)

After:

if (request.Method.IsConnect && response.IsSuccessStatusCode)

Tests: Added theory tests validating 200/201/202/204 status codes for both HTTP/1.1 CONNECT and HTTP/2 Extended CONNECT.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • badhost
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.deps.json /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/testhost.dll --port 40175 --endpoint 127.0.0.1:040175 --role client --parentprocessid 42021 --datacollectionport 38705 --telemetryoptedin false grep (dns block)
  • does.not.exist.sorry
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.deps.json /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/Microsoft.DotNet.RemoteExecutor.dll System.Net.Http.Functional.Tests, Version=11.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 System.Net.Http.Functional.Tests.DiagnosticsTest &lt;SendAsync_ConnectionFailure_RecordsActivitiesWithCorrectErrorInfo&gt;g__RunTest|18_0 /tmp/4nnrhrc0.0rq 2.0 True dns sts/MultiInterfaceStreamContent.cs k/runtime/runtime/.dotnet/grep unnel.*proxy stParserTest.cs niStressTest.cs grep (dns block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.deps.json /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/Microsoft.DotNet.RemoteExecutor.dll System.Net.Http.Functional.Tests, Version=11.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 System.Net.Http.Functional.Tests.DiagnosticsTest &lt;SendAsync_ConnectionFailure_RecordsActivitiesWithCorrectErrorInfo&gt;g__RunTest|18_0 /tmp/50n1p3nb.fhg 1.1 False dns sts/HttpClientHa-d /usr/bin/grep unnel.*proxy ngeParserTest.csdiff sts/MultiInterfa--name-only grep (dns block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.deps.json /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/Microsoft.DotNet.RemoteExecutor.dll System.Net.Http.Functional.Tests, Version=11.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 System.Net.Http.Functional.Tests.DiagnosticsTest &lt;SendAsync_ConnectionFailure_RecordsActivitiesWithCorrectErrorInfo&gt;g__RunTest|18_0 /tmp/aiatuzah.jy5 1.1 True dns sts/HttpClientHa-d /home/REDACTED/wor168.63.129.16 unnel.*proxy ngeParserTest.csdiff sts/MultiInterfa--name-only grep (dns block)
  • nosuchhost.invalid
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.deps.json /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/testhost.dll --port 41445 --endpoint 127.0.0.1:041445 --role client --parentprocessid 38979 --datacollectionport 33849 --telemetryoptedin false grep (dns block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.deps.json /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/testhost.dll --port 40175 --endpoint 127.0.0.1:040175 --role client --parentprocessid 42021 --datacollectionport 38705 --telemetryoptedin false grep (dns block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.deps.json /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/Microsoft.DotNet.RemoteExecutor.dll System.Net.Http.Functional.Tests, Version=11.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 System.Net.Http.Functional.Tests.DiagnosticsTest&#43;&lt;&gt;c &lt;SendAsync_ExpectedDiagnosticExceptionLogging&gt;b__9_0 /tmp/fflb0jko.0nv 1.1 True |:protocol sts/FunctionalTe/low:false /usr/bin/grep unnel.*proxy derValueTest.cs sts/HttpClientHa--exclude-standard grep (dns block)
  • www.some.example
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/System.Net.Http.Functional.Tests.deps.json /home/REDACTED/work/runtime/runtime/artifacts/bin/System.Net.Http.Functional.Tests/Debug/net11.0-linux/testhost.dll --port 40175 --endpoint 127.0.0.1:040175 --role client --parentprocessid 42021 --datacollectionport 38705 --telemetryoptedin false grep (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>SocketsHttpHandler only checks for 200 OK instead of 2XX for CONNECT responses</issue_title>
<issue_description>https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.6-7

Any 2xx (Successful) response indicates that the sender (and all inbound proxies) will switch to tunnel mode immediately after the response header section

https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3-2.2

Any 2xx (Successful) response to a CONNECT request implies that the connection will become a tunnel immediately after the empty line that concludes the header fields. A client MUST ignore any Content-Length or Transfer-Encoding header fields received in such a message.

else if (request.Method.IsConnect && response.StatusCode == HttpStatusCode.OK)

if (statusCode == 200 && _response.RequestMessage!.IsExtendedConnectRequest)

Just from reading the RFC, not sure we've seen a proxy that uses a different 2XX response.</issue_description>

<agent_instructions>Please fix this bug in SocketsHttpHandler and ensure it has appropriate test coverage in the existing tests, ideally augmenting an existing test to also validate this, but if not adding a new test to an existing file, ideally as a theory to validate multiple response status codes.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…onses

- Updated HttpConnection.cs to use IsSuccessStatusCode for CONNECT responses
- Updated HttpConnectionPool.cs to accept any 2xx status code for proxy tunnel
- Updated Http2Stream.cs to accept 2xx status codes for Extended CONNECT
- Added theory tests to verify multiple 2xx status codes work correctly

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Fixed bug where 204 NoContent was checked before CONNECT, preventing CONNECT with 204 status from working properly. Moved CONNECT check before NoContent check.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix socketsHttpHandler to check for 2XX status in CONNECT responses Accept any 2xx status code for CONNECT responses per RFC 9110/9112 Dec 10, 2025
Copilot AI requested a review from stephentoub December 10, 2025 22:07
@stephentoub stephentoub marked this pull request as ready for review December 10, 2025 22:12
Copilot AI review requested due to automatic review settings December 10, 2025 22:12
Copilot AI requested a review from stephentoub December 10, 2025 22:13
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes RFC 9110/9112 compliance by accepting any 2xx status code for CONNECT tunnel establishment, not just 200 OK. The previous implementation incorrectly rejected valid 201/202/204 responses.

Key changes:

  • Updated HTTP/1.1 CONNECT handling to use IsSuccessStatusCode and reordered conditions to properly handle 204 NoContent
  • Updated proxy tunnel establishment to accept any 2xx response
  • Updated HTTP/2 Extended CONNECT to accept status codes in the 200-299 range

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
HttpConnection.cs Uses IsSuccessStatusCode instead of exact == HttpStatusCode.OK check; reorders conditions to check CONNECT before NoContent to correctly handle 204 responses
HttpConnectionPool.cs Changes proxy tunnel establishment to accept IsSuccessStatusCode instead of checking for exact 200 status
Http2Stream.cs Updates Extended CONNECT to accept statusCode >= 200 && statusCode <= 299 instead of exact 200 check
HttpClientHandlerTest.Connect.cs Converts test from [Fact] to [Theory] with inline data testing 200/201/202/204 status codes for HTTP/1.1 CONNECT
SocketsHttpHandlerTest.Http2ExtendedConnect.cs Adds new theory test validating 200/201/202/204 status codes for HTTP/2 Extended CONNECT

Copy link
Member

@wfurt wfurt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about whether supporting 204 is more or less correct here given that in https://www.rfc-editor.org/rfc/rfc9112.html#name-message-body-length it notes that the rules are ordered, with CONNECT coming after 204.

The length of a message body is determined by one of the following (in order of precedence):

  1. Any response to a HEAD request and any response with a 1xx (Informational), 204 (No Content), or 304 (Not Modified) status code is always terminated by the first empty line after the header fields, regardless of the header fields present in the message, and thus cannot contain a message body or trailer section.
  2. Any 2xx (Successful) response to a CONNECT request implies that the connection will become a tunnel immediately after the empty line that concludes the header fields. A client MUST ignore any Content-Length or Transfer-Encoding header fields received in such a message.

On the other hand, curl made a similar change where it allows any 2xx response, with an explicit test for 204. curl/curl#1859

The scenarios where it'd be problematic seem unrealistic enough that it likely doesn't matter though (e.g. reverse proxy that also proxied CONNECT requests, and a backend that accepted CONNECT requests and responded with a 204 but interpreted it as rejecting the tunnel).

@github-actions github-actions bot locked and limited conversation to collaborators Jan 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SocketsHttpHandler only checks for 200 OK instead of 2XX for CONNECT responses

4 participants