Title: Cannot CONNECT http2-over-http2 in Node 20.12.0 · Issue #52344 · nodejs/node · GitHub
Open Graph Title: Cannot CONNECT http2-over-http2 in Node 20.12.0 · Issue #52344 · nodejs/node
X Title: Cannot CONNECT http2-over-http2 in Node 20.12.0 · Issue #52344 · nodejs/node
Description: Version v20.12.0 Platform Linux 6.5.0-21-generic #21-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 7 14:17:40 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux Subsystem http2 What steps will reproduce the bug? Test-suite ready demo: 'use strict'; const comm...
Open Graph Description: Version v20.12.0 Platform Linux 6.5.0-21-generic #21-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 7 14:17:40 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux Subsystem http2 What steps will reproduce the bug? Test-su...
X Description: Version v20.12.0 Platform Linux 6.5.0-21-generic #21-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 7 14:17:40 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux Subsystem http2 What steps will reproduce the bug? Test-su...
Opengraph URL: https://github.com/nodejs/node/issues/52344
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Cannot CONNECT http2-over-http2 in Node 20.12.0","articleBody":"### Version\n\nv20.12.0\n\n### Platform\n\nLinux 6.5.0-21-generic #21-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 7 14:17:40 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux\n\n### Subsystem\n\nhttp2\n\n### What steps will reproduce the bug?\n\nTest-suite ready demo:\r\n\r\n```javascript\r\n'use strict';\r\n\r\nconst common = require('../common');\r\nconst assert = require('assert');\r\nconst h2 = require('http2');\r\n\r\nconst server = h2.createServer();\r\n\r\nserver.listen(0, common.mustCall(function() {\r\n const proxyClient = h2.connect(`http://localhost:${server.address().port}`);\r\n\r\n const request = proxyClient.request({\r\n ':method': 'CONNECT',\r\n ':authority': 'example.com:80'\r\n });\r\n\r\n request.on('response', common.mustCall((connectResponse) => {\r\n assert.strictEqual(connectResponse[':status'], 200);\r\n\r\n const proxiedClient = h2.connect('http://example.com', {\r\n createConnection: () => request // Tunnel via first request stream\r\n });\r\n\r\n const proxiedRequest = proxiedClient.request();\r\n proxiedRequest.on('response', common.mustCall((proxiedResponse) => {\r\n assert.strictEqual(proxiedResponse[':status'], 204);\r\n\r\n proxiedClient.close();\r\n proxyClient.close();\r\n server.close();\r\n }));\r\n }));\r\n}));\r\n\r\nserver.once('connect', common.mustCall((req, res) => {\r\n assert.strictEqual(req.headers[':method'], 'CONNECT');\r\n res.writeHead(200); // Accept the CONNECT tunnel\r\n\r\n // Handle this stream as a new 'proxied' connection (pretend to forward\r\n // but actually just unwrap the tunnel ourselves):\r\n server.emit('connection', res.stream);\r\n}));\r\n\r\n// Handle the 'proxied' request itself:\r\nserver.once('request', common.mustCall((req, res) => {\r\n res.writeHead(204);\r\n res.end();\r\n}));\r\n```\n\n### How often does it reproduce? Is there a required condition?\n\nFails every time in Node v20.12.\r\n\r\nWorks every time in Node v20.11 and previous releases (for a few years, at least)\n\n### What is the expected behavior? Why is that the expected behavior?\n\nIt should be possible to make an HTTP/2 CONNECT request (opening a tunnel to a target server through an HTTP/2 proxy) and then make another HTTP/2 request to the target server.\n\n### What do you see instead?\n\nInstead, creating an HTTP/2 connection on top of an HTTP/2 tunnel fails with:\r\n\r\n```javascript\r\nError [ERR_HTTP2_SOCKET_BOUND]: The socket is already bound to an Http2Session\r\n at new Http2Session (node:internal/http2/core:1238:13)\r\n at new ServerHttp2Session (node:internal/http2/core:1640:5)\r\n at Http2Server.connectionListener (node:internal/http2/core:3102:19)\r\n at Http2Server.emit (node:events:518:28)\r\n at Http2Server.\u003canonymous> (/home/tim/Dropbox/programming/javascript/node/test/parallel/test-http2-client-proxy-over-http2.js:41:10)\r\n at Http2Server.\u003canonymous> (/home/tim/Dropbox/programming/javascript/node/test/common/index.js:473:15)\r\n at Object.onceWrapper (node:events:633:26)\r\n at Http2Server.emit (node:events:518:28)\r\n at Http2Server.onServerStream (node:internal/http2/compat:956:17)\r\n at Http2Server.emit (node:events:518:28) {\r\n code: 'ERR_HTTP2_SOCKET_BOUND'\r\n}\r\n```\n\n### Additional information\n\nI'm fairly sure I know the issue here: `kSession` is used in the HTTP/2 code as a single symbol with two meanings:\r\n\r\n* On sockets, marking them as being owned by an HTTP/2 session (i.e. there is a session currently running within this socket)\r\n* On HTTP/2 streams, providing access to the HTTP/2 session that contains them (i.e. this stream is running within that session)\r\n\r\nIn this case, these two meanings mix: the CONNECT stream should be within an HTTP/2 session (the initial connection) _and_ should act as a socket and contain an HTTP/2 session. That conflict is why this blows up.\r\n\r\nThis didn't happen in the past because the inner stream uses a JSStreamSocket to handle this, and kSession wasn't passed through that wrapper, so the JSS and the inner H2 stream had independent kSession values. That changed in https://github.com/nodejs/node/commit/c50524a9fc19cd2fe6e61bb7f8ad22685024842b#diff-8dd7127678f00bf090a030256bad7ae645834f7100e54154b4fc81c8cbe9c3fc, which (reasonably I think) exposed the 'real' kSession through that wrapper.\r\n\r\nI think the solution here is two split these two use cases, and track the 'socket-in-use-by' and 'stream-powered-by' state under separate keys, so a single H2 stream can have both. I'm going to keep digging and hopefully open a PR for this within a day or two, but if anybody has any context on that in the meantime and whether this is going to work or whether there's a good reason the two are the same, that would be very interesting.","author":{"url":"https://github.com/pimterry","@type":"Person","name":"pimterry"},"datePublished":"2024-04-03T11:50:38Z","url":"https://github.com/nodejs/node/issues/52344"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:7c2fed7a-a488-297b-fce5-fe47829deb1d |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | C7F0:2C3014:660F7D:846421:6AED2C1B |
| html-safe-nonce | 3c00819bb101092eaa56d2924e9ec0c264eb146c350776eb9eab4bd5b9e84131 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDN0YwOjJDMzAxNDo2NjBGN0Q6ODQ2NDIxOjZBRUQyQzFCIiwidmlzaXRvcl9pZCI6IjI4Njk2Mjc2NjU1NTMxMDc5OTUiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 65c17399f92b327c084c4c626aa934b1d11020426f20f00c3ce6656324520a8e |
| hovercard-subject-tag | issue:2222720722 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/52344/issue_layout |
| twitter:image | https://opengraph.githubassets.com/715e05b8740923d1bdcc237a5ec7a1af6ad91688cb4b94253f51e2b68795973b/nodejs/node/issues/52344 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/715e05b8740923d1bdcc237a5ec7a1af6ad91688cb4b94253f51e2b68795973b/nodejs/node/issues/52344 |
| og:image:alt | Version v20.12.0 Platform Linux 6.5.0-21-generic #21-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 7 14:17:40 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux Subsystem http2 What steps will reproduce the bug? Test-su... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | pimterry |
| hostname | github.com |
| expected-hostname | github.com |
| None | e51d0b7b5ab34b1688c05b54c3ed17b8658b2fa9bca973a9c740683ccb12cf4a |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 59ebb7b901cc154ecc179a44f266fcf69f343b30 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width