Maximum UDP Payload Size in Tailcat: IPv4 and IPv6 Limits Explained

The maximum UDP payload size in Tailcat is 65,507 bytes for IPv4 and 65,527 bytes for IPv6, determined by standard Internet protocol limits rather than custom implementation constraints.

Tailcat is a UDP tunneling proxy built on the gvisor.dev/gvisor/pkg/tcpip netstack. When forwarding UDP traffic, it relies on the underlying network stack's protocol enforcement, meaning the maximum UDP payload size follows classical IP datagram boundaries without additional truncation or application-level caps.

How Tailcat Handles UDP Packet Sizes

Tailcat delegates all UDP packet construction to the gVisor netstack. The total IP packet length field is a 16-bit unsigned integer, capped at 65,535 bytes. After subtracting fixed header overhead, the remaining space defines the payload ceiling.

IPv4 UDP Payload Limit

IPv4 carries 20 bytes of header plus 8 bytes of UDP header:

Component Size
IPv4 header 20 bytes
UDP header 8 bytes
Maximum payload 65,507 bytes
// Sending the largest IPv4 UDP payload Tailcat can forward
payload := make([]byte, 65507) // 65,507 bytes — max IPv4 UDP payload
for i := range payload {
    payload[i] = byte(i)
}
conn, _ := net.DialUDP("udp4", nil, net.UDPAddr{
    IP:   net.ParseIP("127.0.0.1"),
    Port: 12345,
})
conn.Write(payload) // Tailcat forwards the complete datagram

IPv6 UDP Payload Limit

IPv6 uses a 40-byte header with the same 8-byte UDP header:

Component Size
IPv6 header 40 bytes
UDP header 8 bytes
Maximum payload 65,527 bytes
// IPv6 — maximum payload increases due to fixed 40-byte header
payload := make([]byte, 65527) // 65,527 bytes — max IPv6 UDP payload
conn, _ := net.DialUDP("udp6", nil, net.UDPAddr{
    IP:   net.ParseIP("::1"),
    Port: 12345,
})
conn.Write(payload) // Full IPv6 UDP packet forwarded by Tailcat

Source Code Implementation

The server implementation in main/tailcat.go initializes the netstack and establishes UDP handling through DialContextUDPWithBind. No custom size checks appear in the application code—the gVisor netstack enforces boundaries automatically.

Key files demonstrating this behavior:

Tests in these files construct UDP packets up to the protocol-defined maximums, confirming Tailcat forwards them without truncation.

Why No Smaller Limit Exists

Some tunneling implementations impose arbitrary ceilings (1,500 bytes for MTU alignment, or 8,192 bytes for buffer sizing). Tailcat avoids this by:

  1. Delegating to netstack — The gVisor tcpip package manages fragmentation and reassembly
  2. Virtual interface abstraction — Packets traverse an internal stack before wire transmission
  3. Test coverage — Explicit validation that 65,507/65,527-byte payloads traverse cleanly

The result: maximum UDP payload size in Tailcat equals the theoretical IP limit, not a constrained practical subset.

Summary

  • IPv4 maximum UDP payload: 65,507 bytes (65,535 − 20 − 8)
  • IPv6 maximum UDP payload: 65,527 bytes (65,535 − 40 − 8)
  • Tailcat imposes no additional limits beyond protocol-defined ceilings
  • Implementation in main/tailcat.go uses gvisor.dev/gvisor/pkg/tcpip for standard-conforming behavior
  • Tests in main/tailcat_test.go and main/cmd/tailcat/socks_test.go verify full-size packet forwarding

Frequently Asked Questions

What happens if I send a UDP payload larger than 65,507 bytes through Tailcat?

Packets exceeding the IP maximum transmission unit trigger fragmentation at lower network layers, or the sending kernel rejects them with EMSGSIZE. Tailcat receives only what the netstack permits; oversized sends fail before reaching the tunnel.

Does Tailcat support jumbo frames or Ethernet MTU adjustments?

Tailcat operates above the Ethernet layer via the gVisor netstack virtual interface. Jumbo frame handling depends on the underlying physical network between Tailcat instances, not the internal UDP payload limit.

Why is the IPv6 payload limit 20 bytes larger than IPv4?

IPv6 eliminates header options and checksum fields present in IPv4, using a fixed 40-byte base header versus IPv4's minimum 20 bytes. The 20-byte difference (40 − 20) transfers directly to additional payload capacity.

Where does Tailcat validate UDP packet sizes during forwarding?

Validation occurs implicitly through the gVisor netstack imported at gvisor.dev/gvisor/pkg/tcpip. The main/wire.go encoding routines respect the 16-bit length fields without additional application checks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →