Skip to content

CONNECT-IP server lacks overload limits and tenant isolation checks #991

Closed

Description

@ecv

The CONNECT-IP server from #954 has no binary or deployment yet. Before anything exposes it to the internet, it needs four fixes that did not block the review.

  1. Any client can open unlimited sessions and admission calls, because no limit sits ahead of admission.
  2. Unauthenticated clients get a probe packet before admission runs.
  3. One full tunnel device tears down other tenants' sessions.
  4. Routes are checked against each other but not against other tenants' ranges.

Lease fencing on the datapath is tracked in #976, and overload limits are a #970 criterion. Done when each item is fixed or the wrapper that exposes the server handles it. The detail is in the first comment.

Related to #954, #970, #976

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.

Integration disposition and acceptance

This issue is an exposure gate for #980/#981, with #984 supplying subsequent load qualification. #992 provides foundation gates and route arbitration; it does not automatically fix CONNECT-IP admission or queue behavior.

Activity

  1. ecv commented on Oct 22, 2026

    @ecv
    ContributorAuthor

    Detail, as of #954 at c6c48bd:

    1. server.go runs Admit with no per-connection, per-client or global cap, and Config sets no QUIC stream or connection limits. Each admitted session holds two 64KB buffers and up to 256 queued packets in TUNBackend. Add a bounded in-flight and session semaphore ahead of Admit, set explicit QUIC limits, and cap queued bytes per session.
    2. probeInnerMTU sends a 1.3KB datagram to any request with a well-formed bearer token, before Admit. Move it after admission.
    3. In tun.go, any unix.Write error, EAGAIN on a full nonblocking TUN included, ends the session, and all sessions share backend.mu. Count EAGAIN as a dropped packet instead.
    4. routeRanges checks routes against each other only, not against other tenants' /96s or the assigned prefix. Route authority belongs to the admission adapter, which does not exist yet.
    5. TUNBackend never checks lease state, so forwarding stops only when lease.Done() closes the session. Keep NewTUNBackend unexported until a fencing wrapper exists (Integrate connector ownership with Edge claims and independent expiry #976).
    6. Inbound source-address and destination-route checks live in connect-ip-go v0.6.0. Confirm the library enforces both in both directions for IPv4 and IPv6. TUN return traffic demultiplexes on the destination /96 only, so IPv4 packets never match a session.
  2. added
    tech-debtInternal quality/maintenance: unwind shortcuts, config drift, overdue upgrades, fork/dep hygiene
    on Oct 22, 2026
  3. added theissue type on Oct 28, 2026
  4. self-assigned this
    on Oct 28, 2026
  5. privateip commented on Oct 28, 2026

    @privateip
    Collaborator

    Verdict: valid for items 1 and 2. Checked against main at 15a1b5a.

    Item 2 reproduced. A test sent a CONNECT-IP request over HTTP/3 with an unknown bearer token. The server answered 401 and also sent the client a 1,281-byte probe datagram. ServeHTTP runs probeInnerMTU before Admit.

    Item 1 confirmed from source. Config sets no connection, stream, admission or session limit. ServeHTTP calls Admit for every request that carries a bearer token and a VPC header. Each TUNBackend session can queue 256 packets of up to 64 KB, with no byte cap.

    The other items are already handled on the path that serves the server today, which is the MASQUE adapter used by tests/edgeconformance/masqueprovider:

    1. packetSession.WritePacket counts EAGAIN as a drop and keeps the session. The shared lock in TUNBackend covers one nonblocking write.
    2. Routes come from the grant the MASQUE grant authority issues (Implement admission, credential expiry and access revocation #977). The client and provider choose none of them. Foundation prefix conflicts belong to Implement authorized prefix routing #998.
    3. The adapter's forwarding check fences every packet on the instance lease epoch and the grant deadline (Integrate connector ownership with Edge claims and independent expiry #976).
    4. Packet checks belong to Enforce delegated-prefix ownership and bidirectional traffic policy #979, which has an approved plan.

    HTTP/2 has no server yet. #981 must probe after admission and apply the same limits.

  6. privateip commented on Oct 28, 2026

    @privateip
    Collaborator

    Approved fix plan for items 1 and 2.

    Root cause. ServeHTTP in internal/edge/connectip/server.go probes the inner MTU before it calls Admit. Config sets no limit on connections, streams, admissions or sessions. TUNBackend queues up to 256 packets per session with no byte cap.

    Fix.

    1. ServeHTTP probes only after Admit succeeds and the lease checks pass, and before backend.Open. A failed probe answers 503 and releases the lease.

    2. Config.Limits adds four caps. A zero field takes its default.

      • MaxConnections, default 1024: QUIC connections over the cap are closed with H3_EXCESSIVE_LOAD.
      • MaxStreamsPerConn, default 16: sets QUIC MaxIncomingStreams.
      • MaxAdmissions, default 64: requests inside Admit at once.
      • MaxSessions, default 1024: admitted requests at once.

      Both request caps are taken before Admit without waiting. A request over either cap gets 503.

    3. Each TUNBackend session also caps its queue at 1 MiB and drops packets past it.

    Tests. A request with an unknown credential receives no datagram. Requests over the admission and session caps get 503 before Admit runs. A connection over the connection cap is closed. The queue byte cap has a unit test that does not need root.

    Risk. A client that opens more than 16 requests on one connection now waits for stream credit. These defaults are starting points, and #984 qualifies capacity. Items 3 to 6 need no code here, as the verdict comment explains.

  7. privateip commented on Oct 28, 2026

    @privateip
    Collaborator

    Change to the approved plan: the default limits now match the initial per-pod targets in the MASQUE contract, not the numbers in the plan.

    Limit Plan Now Contract row
    MaxConnections 1024 128 Unauthenticated transport
    MaxStreamsPerConn 16 2 Unauthenticated transport
    MaxAdmissions 64 16 Admission
    MaxSessions 1024 64 Active connectors (32) + replacement headroom (32)
    TUN queue per session 256 packets, 1 MiB 128 packets, 256 KiB Session buffers

    This change does not add the contract's per-source-IP cap, per-credential cap, handshake and admission deadlines, or the 32 MiB aggregate queue cap. #984 qualifies the numbers, and those limits stay with #980 and #984.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

tech-debtInternal quality/maintenance: unwind shortcuts, config drift, overdue upgrades, fork/dep hygiene

Type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions