Skip to content

CONNECT-IP: Packet Too Big for an oversized HTTP/3 packet never reaches the gVPC sender #1255

Closed

Description

@privateip

What happens

Since #1248, the CONNECT-IP server sends HTTP/3 packets of a fixed 1350 bytes. An HTTP/3 tunnel then carries inner packets of about 1310 bytes, and a larger gVPC packet toward the connector should get an ICMPv6 Packet Too Big reporting 1280, sent back to the gVPC sender. docs/edge/masque-service.md (Packet size) describes this behavior.

In the containerlab gVPC lab on main at 32704f5, a gVPC workload that sends 1400-byte packets to an HTTP/3 connector gets no Packet Too Big. The packets are not delivered either. This happens on a full 1500-byte path, so the path is not the cause.

How it was seen

The MASQUE service lab suite's mtu-live-drop row runs the provider and grant authority from config/masque, with images built from 32704f5. A connector sends 1280-byte packets to a workload every 100 ms over HTTP/3. The workload sends 1280-byte and 1400-byte packets to the connector and captures ICMPv6 Packet Too Big on its own node. The row then lowers the provider pod's host-side veth MTU in steps.

Host-side veth MTU 1280-byte replies 1400-byte packets delivered to the connector Packet Too Big at the workload
1500 350 0 0
1420 to 1360 349 to 350 0 0
1340 to 1300 0 0 0

Below 1360, the tunnel stops carrying 1280-byte packets and stays open, which is #1247. This issue covers only the rows above that, where the path carries the fixed packet size and the server should answer oversized packets with a Packet Too Big.

Where it could be lost

None of these has been checked yet:

  1. The server does not build or send the Packet Too Big for packets from the TUN device. fix(connectip): Send Packet Too Big for oversized packets on HTTP/3 #1248's loopback test passes against an in-memory backend, not the TUN device.
  2. The Packet Too Big leaves the provider but is dropped on the way back: by the session packet policy, the TUN backend, the edge attachment, gVPC traffic policy, or the SRv6 path to the workload's node.
  3. The row's capture misses it. The capture has never seen a Packet Too Big in this lab, so it has no positive control.

Expected

An oversized gVPC packet toward an HTTP/3 connector draws a Packet Too Big reporting 1280 that reaches the sending workload, as the service guide says. The mtu-live-drop row should show it at every step from 1500 down to 1360.

Related

#1248 (fixed packet size), #1237 (closed by #1248), #1247 (path below the fixed packet size).

Activity

  1. self-assigned this
    on Oct 29, 2026
  2. added theissue type on Oct 29, 2026
  3. privateip commented on Oct 29, 2026

    @privateip
    CollaboratorAuthor

    Verdict: the Packet Too Big reaches the sender. The mtu-live-drop row can't see it, so the fix belongs in the row, not the datapath.

    Checked on main at 0558ab4, with a containerlab gVPC lab run of the MASQUE service suite whose provider and datapath match 32704f5.

    What the lab showed

    During the mtu-live-drop row:

    • The workload's capture recorded exactly one ICMPv6 Packet Too Big, reporting MTU 1280, from the connector's address. It arrived with the first oversized packet.
    • Afterwards the workload's kernel had a cached route toward the connector with mtu 1280 and a 600-second expiry. Its Icmp6InPktTooBigs counter was 1.

    So the server builds the error for a packet read from the TUN device, the packet policy and the TUN backend pass it, and the edge attachment, traffic policy and return path deliver it. Of the three places listed in the issue, the third is where it goes missing.

    Why the row reports 0

    1. Path MTU discovery (RFC 8201) caches the reported MTU. After one Packet Too Big, the sender stops putting oversized packets on the wire for that destination until the cache entry expires: its kernel either fragments them or refuses the send locally. A correct sender therefore gets one Packet Too Big per cache lifetime, not one per step. The row expects one in every step.
    2. That single Packet Too Big arrives with the first oversized ping, just after the ping starts. Each step's window opens 1 s after the step begins, so even a captured Packet Too Big falls outside step 0's window and is never counted.
    3. The row starts the capture and the ping one immediately after the other, without waiting for tcpdump to report that it is listening. The one message can arrive before the capture is ready, which matches the run in the issue, where nothing was captured at all.

    For the same reason, the "1400-byte packets delivered" column is 0 by design over HTTP/3. A fixed 1350-byte QUIC packet carries at most about 1310 inner bytes.

    #1247 (the steps below 1360) is unaffected.

  4. privateip commented on Oct 29, 2026

    @privateip
    CollaboratorAuthor

    Approved fix plan

    The fix is test-only, in the mtu-live-drop row of tests/edgeconformance/masque_service_rows_test.go:

    1. Start the workload's Packet Too Big capture and wait until tcpdump reports that it is listening before the oversized ping starts.
    2. At each step, after the MTU change, flush the workload's cached path MTU in its network namespace before that step's window opens. The next oversized packet then reaches the provider and draws a fresh Packet Too Big, so every step from 1500 down to 1360 can show one.
    3. Keep the failure condition: a step that carries 1280-byte replies but shows no Packet Too Big fails the row.

    Risk: this changes no runtime behavior. The assertions for the steps below the floor (#1247) are unchanged.

    Verification: task lint, task ci, and the MASQUE service suite's mtu-live-drop row in the containerlab lab.

  5. privateip commented on Oct 29, 2026

    @privateip
    CollaboratorAuthor

    Change to the plan: the row's ping also stops after the first Packet Too Big

    With the capture and cache-flush changes alone, the lab row still counted no Packet Too Big at any step. Captures inside the workload and the provider pod showed why: during the whole row, the workload sent exactly one oversized packet. The provider returned exactly one Packet Too Big for it, and the workload received it.

    The row runs iputils ping with -w, and with a deadline set, iputils ping exits at its first error. A three-namespace reproduction outside the lab (a sender, a router with a smaller next-hop MTU, and a receiver) confirms this:

    Ping Cache flushes Oversized packets sent Packet Too Big received
    -w 6 2 1 1
    timeout 6 0 1 1
    timeout 6 2 3 3

    So the row also bounds the ping with timeout instead of -w. With both changes, each step gets a fresh oversized packet and a fresh Packet Too Big. The fix is still test-only.

  6. added a commit that references this issue on Oct 30, 2026
    ba40632
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions