Repository navigation
Description
Parent: #970
Deliverable
An HTTP/2 listener on TCP/443 that carries CONNECT-IP packets in capsules and uses the same service authority and gVPC packet path as HTTP/3.
Dependencies
Galactic Edge integration (#992)
Implement this milestone as a consumer of the Galactic Edge provider contract. #992 owns provider authorization, host attachment lifecycle, shared-state fencing and maintenance, independently enforced forwarding expiry, prefix conflict arbitration/publication, and gVPC traffic policy. MASQUE owns client authentication, connector /96 assignment and retention, session packet checks, transport behavior and its provider adapter. Use the foundation's versioned contract and existing provider helpers; do not introduce a second host-networking controller, lease store or BGP publisher. Any required foundation change must be tracked and qualified under #992 before this milestone claims integrated acceptance.
Work
- Implement Extended CONNECT negotiation and capsule transport over HTTP/2 with TLS using the selected stack.
- Reuse admission, prefix grants, policy and per-connector ownership from the HTTP/3 service; fallback must not create a second concurrent forwarding owner.
- Implement bounded incremental capsule parsing, stream backpressure and independent cancellation, including capsules split across reads and multiple capsules per read.
- Apply the agreed reconnect and address-retention behavior when a new HTTP/2 tunnel replaces a QUIC tunnel. Automatic product-client transport selection remains out of scope.
Acceptance criteria
- With UDP/443 blocked, an independent client exchanges bidirectional 1280-byte IPv6 packets with real gVPC workloads over TCP/443.
- Packet or negotiated-protocol evidence confirms HTTP/2 over TLS/TCP with no QUIC dependency.
- The same authorization, spoofing, tenant-isolation and expiry cases pass on both transports.
- Truncated, malformed and oversized capsule inputs are bounded; slow readers and cancellation cannot cause unbounded buffering or disrupt another connector.
- A controlled QUIC-to-HTTP/2 reconnect follows the documented continuity contract and never permits dual ownership.
Additional integration acceptance
- Route HTTP/2 through exactly the same foundation-backed authorization, packet backend and connector session generation as HTTP/3; fallback cannot acquire authority from a second store or bypass readiness.
- Test replacement both within the same provider instance and across nodes while the old QUIC path remains usable. Shared instance epochs alone do not prevent duplicate sessions.
- Apply CONNECT-IP server lacks overload limits and tenant isolation checks #991 pre-admission limits and authenticate before probes on TCP as well as UDP; HTTP/2 stream cancellation or slow readers must preserve other sessions and attachment users.
Scope follows #970: CONNECT-IP with HTTP/2 fallback; no public API development, plugin/portal integration, product-client development or production rollout.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
[-]MASQUE: implement HTTP/2 CONNECT-IP fallback over TLS[/-][+]Implement HTTP/2 CONNECT-IP fallback over TLS[/+]privateip commentedon Oct 28, 2026
Verdict: valid. No production HTTP/2 CONNECT-IP listener exists.
Checked against
mainat d902d55:connectip.Serveronly serves HTTP/3. Its only listener entry point isServe(net.PacketConn), anddoc.gocalls HTTP/2 fallback "a separate implementation".Server.ServeHTTPan Extended CONNECT shaped the waynet/http's HTTP/2 server delivers it (CONNECT,HTTP/2.0,:protocol: connect-ip, a valid bearer credential and VPC). The server answered 501 and opened no backend session.connect-ip-goparses only HTTP/3 requests, and its proxy needshttp3.HTTPStreamer.tests/connectipinterop(Prove CONNECT-IP interoperability over HTTP/3 and HTTP/2 #973). It has its own admission path, takes no CONNECT-IP server lacks overload limits and tenant isolation checks #991 limits, and has nopacketPolicy(Enforce delegated-prefix ownership and bidirectional traffic policy #979).docs/edge/connectip-interop.mdlists the six items still needed before a listener ships.Dependency: the HTTP/3 service from #980 (
cmd/masque-provider,service.Run,config/masque) is still an open stack. The HTTP/2 listener has to run inside that service, so the fix stacks on it.privateip commentedon Oct 28, 2026
Approved plan. Two PRs stacked on #980's stack (#1202 → #1203 → #1204), because the listener runs inside the
service.Runthat #1203 adds. Both PRs say "Related to #981". This issue stays open for the lab acceptance rows: real gVPC workloads with UDP/443 blocked, and replacement across nodes.Root cause.
connectip.Serverserves only HTTP/3. Its request path runs throughconnect-ip-go, which parses only HTTP/3 requests. The one HTTP/2 server is the #973 prototype intests/connectipinterop.PR 1,
internal/edge/connectip:ServeHTTPbecomes a transport parse followed by one shared path: admission, lease watch, backend, tunnel andproxySession. Both transports share the sameAdmission, the session, admission and per-credential caps, andpacketPolicy. HTTP/2 cannot get a second admission path or forwarding owner.packetPolicy. Each capsule write has a 5 s deadline, so a client that stops reading ends only its own session.Server.ServeTLS(net.Listener). HTTP/2 only, ALPNh2.MaxConnectionsandMaxConnectionsPerSource.Limitssets the stream, header and handshake limits.GODEBUGcontainshttp2xconnect=1.PR 2, service:
--listen-tcp(default:8443). Startup fails if Extended CONNECT is off, and/readyzneeds both listeners.GODEBUGis set in the image and the Deployment. The Service gets TCP/443.Tests:
RST_STREAMon one stream while another stream keeps forwarding.Risk. The stack merges after #980's stack. Extended CONNECT is process-wide, which only matters to masque-provider. The Service exposes mixed UDP and TCP ports.
privateip commentedon Oct 28, 2026
The fix is open as two drafts on top of #980's stack, in stack #1205:
connectip.Server, sharing one request path with HTTP/3.GODEBUG=http2xconnect=1, and moves the interop harness onto the production server.One change from the plan: the HTTP/2 tunnel clears its write deadline after each write. net/http resets an HTTP/2 stream whose write deadline passes even when no write is pending, so without this every session idle for 5 s would end. A regression test covers it.
task test:connectip-interoppassed all 33 tests against the pinned Xray client over both transports, including HTTP/2 with UDP/443 blocked. The acceptance rows that need real gVPC workloads and replacement across nodes are not run. This issue stays open for them.feat(masque): Serve the HTTP/2 CONNECT-IP fallback from masque-provider
feat(connectip): Serve CONNECT-IP over HTTP/2 with TLS
privateip commentedon Oct 29, 2026
Every acceptance criterion now has evidence, so I'm closing this. The HTTP/2 listener merged in #1206, and #1207 runs it in the service on TCP/443. #1228 adds an HTTP/2 transport to the lab probe, and #1240 adds the HTTP/2 rows to the containerlab lab suite. All of those rows passed.
h2. No UDP datagrams to the service port reached the provider pod, and the block dropped none during the run. An HTTP/3 control attempt, by contrast, had 2 dropped