Skip to content

Enforce delegated-prefix ownership and bidirectional traffic policy #979

Closed

Description

@privateip

Parent: #970

Deliverable

Packet-path policy enforcement that confines a connector to its delegated /96 and authorized gVPC traffic in both directions.

Dependencies

#977, #978, foundation #999.

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

  • Integrate connector session policy with the existing gVPC policy boundary using the accepted internal interfaces.
  • Validate inner source addresses before admitting client traffic into the gVPC and enforce destination/protocol/port policy as applicable.
  • Apply return-direction policy before selecting and writing to a connector tunnel. Never use another tenant as a fallback for missing state.
  • Handle malformed IPv6 packets, extension headers, fragments and required control traffic with bounded parsing and explicit rules; policy updates must affect active sessions.

Acceptance criteria

  • Tests reject spoofed sources outside the /96, cross-connector impersonation, cross-tenant traffic and traffic denied in either direction.
  • Identical addresses in two tenant contexts never select the other tenant's route or tunnel.
  • Policy updates affect established sessions, and malformed or unevaluable packets cannot bypass enforcement.
  • Required CONNECT-IP control and ICMP behavior remains functional under the enforced policy; denial reasons are available to observability.

Additional integration acceptance

  • Consume Integrate gVPC traffic policy #999 attachment policy binding and updates; keep only connector/session-specific restrictions in MASQUE, and never bypass a closed foundation gate or missing/invalid policy.
  • Key packet demultiplexing by explicit VPC/attachment context plus connector prefix and session generation. A destination prefix alone is not a global tenant identity.
  • Validate IPv6 source ownership and authorized route/destination use in both directions, including malformed packets/fragments/extension headers and required control traffic; explicitly reject unsupported IPv4 rather than implying IPv4 support.
  • Prove denied packets arrive at neither workload nor other connector, including same-instance connectors, overlapping tenants and live policy updates. Verify the relied-on library checks rather than assuming them; cover Edge pod attachments: unchecked caller inputs and weak isolation tests #989/CONNECT-IP server lacks overload limits and tenant isolation checks #991 findings.

Scope follows #970: CONNECT-IP with HTTP/2 fallback; no public API development, plugin/portal integration, product-client development or production rollout.

Activity

  1. changed the title [-]MASQUE: enforce delegated-prefix ownership and bidirectional traffic policy[/-] [+]Enforce delegated-prefix ownership and bidirectional traffic policy[/+] on Oct 22, 2026
  2. added theissue type on Oct 22, 2026
  3. self-assigned this
    on Oct 28, 2026
  4. privateip commented on Oct 28, 2026

    @privateip
    CollaboratorAuthor

    Verdict: valid. Reproduced on main at 2dcdb3e.

    The CONNECT-IP proxy runs no packet checks of its own. It relies on connect-ip-go v0.6.0, which checks a packet's source against the assigned /96 and its destination against the advertised routes. The library does not apply the parsing profile in the MASQUE contract, and it counts no drops.

    A test in internal/edge/connectip sent raw datagrams over an admitted request with the test fixture's addresses. All three of these packets got through:

    1. A client packet with an atomic Fragment header reached the gVPC backend.
    2. A client ICMPv6 Destination Unreachable with a source outside the connector's /96 reached the gVPC. The library accepts any source on an ICMP error as long as the quoted packet could have crossed the tunnel, so a client can inject packets with a spoofed source.
    3. A gVPC packet with a Fragment header reached the client.

    Other findings:

    • Drops are reported only through the library's log.Printf, with no reason counter.
    • The library checks the upper-layer protocol, not each extension header. Nothing limits the header count or length, and a Routing header with segments left is evaluated against its next hop instead of its final destination.
    • The server accepts a grant whose routes include IPv4 prefixes and advertises them, which implies IPv4 support that the packet path does not have.
    • The TUN backend drops IPv4 and unmatched packets from the device without counting them.
  5. privateip commented on Oct 28, 2026

    @privateip
    CollaboratorAuthor

    Approved fix plan.

    Root cause. Server.proxySession in internal/edge/connectip/server.go moves packets between the CONNECT-IP stream and the backend with no checks of its own. connect-ip-go v0.6.0 accepts an ICMP error from any source, does not walk extension headers or refuse fragments, and logs drops instead of counting them.

    Fix.

    1. A session packet policy in internal/edge/connectip, built from the lease's /96 and routes:
      • Parsing: IPv6 only, with IPv4 refused under its own reason. The payload length must match exactly. At most 8 extension headers and 256 header bytes. Hop-by-Hop only first. Every Fragment header is refused, as is any Routing header with segments left, and any unknown extension header.
      • Toward the gVPC: the source must be in the connector's /96, ICMP errors included, and the destination must be in an advertised route.
      • Toward the client: the destination must be in the /96. The source must be in a route, unless the packet is an ICMPv6 error quoting a packet from this /96 to a route, which keeps Packet Too Big from gVPC routers working.
    2. proxySession applies it in both directions, and to the Packet Too Big the library composes for the client.
    3. galactic_connectip_packets_denied_total{direction,reason} counts every refusal, including IPv4 and unmatched packets the TUN backend reads from its device.
    4. A grant with an IPv4 route is refused, so IPv4 is never advertised.
    5. docs/edge/masque-provider.md documents the policy, its reasons and the metric.

    A grant refresh that changes the routes already ends the session, so MASQUE-side policy updates reach live sessions. gVPC traffic policy stays with the foundation (#999).

    Tests. A table test for every reason in both directions. The reproduction becomes a server test that fails on main: spoofed and cross-connector sources, fragments in both directions, the refusal counters, and the IPv4 route grant. A socketpair TUN test shows two VPCs holding the same /96 never receive each other's packets.

    Risk. Stricter than RFC 9484 for client ICMP errors: a client must send its errors from its own /96. Fragmented inner packets are refused, as the contract already states. HTTP/2 (#981) and the lab rows (#985) are out of scope.

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions