Skip to content

Frame overhead does not account for the aead-epoch 8-byte packet id in calc_packet_id_size_dc #1074

Description

@raphaelthomas

Describe the bug
On any 2.7 tunnel with an AEAD data cipher (e.g., the default AES-256-GCM), the
aead-epoch data-channel format is negotiated automatically: each 2.7 peer
advertises IV_PROTO_DATA_EPOCH and the server enables it for AEAD ciphers.
The on-wire packet id is then 8 bytes, but calc_packet_id_size_dc() in
mtu.c still reports 4. The overhead is undercounted by 4 bytes, so mssfix N mtu emits an outer datagram of N + 4. Packets sized to the path or link MTU
exactly then overshoot it by 4 bytes and are dropped by any strict next hop,
leading to large TCP flows (TLS/SSH handshakes, bulk transfer) stalling.

To Reproduce

  • 2.7 on both ends, --proto udp, AEAD data cipher (AES-256-GCM), mssfix M mtu in effect (the default is M=1492).
  • Send a full-size TCP segment through the tunnel; capture the outer UDP
    datagram on the link.
  • Its IP length is M + 4 (see e.g. with tcpdump -ni IFACE -v)

Expected behavior
The outer UDP datagram should be <= M: with mssfix M mtu the overhead
calculation should reserve the true on-wire packet id size (8 bytes for
aead-epoch), so encapsulated packets never exceed the configured MTU.

Version information (please complete the following information):

  • OS: Linux From Scratch, kernel 6.18
  • OpenVPN version: 2.7.4 (reproduced); mtu.c unchanged in 2.7.5 / master
  • Both server and client run the same version.

Additional context
packet_id_write_epoch() writes an 8-byte id (uint64_t: 16-bit epoch +
48-bit counter), but calc_packet_id_size_dc() returns packet_id_size(false)
= 4 for any TLS+AEAD tunnel and never consults CO_EPOCH_DATA_KEY_FORMAT. So
the AEAD framing is computed as 4 + 4 + 16 = 24 while the wire is 4 + 8 + 16 = 28 (opcode+peerid | packet id | GCM tag).

Proposed fix: make calc_packet_id_size_dc() epoch-aware, with the epoch size
named packet_id_size_epoch() next to packet_id_size(). The gate is the
negotiated flag options->imported_protocol_flags & CO_EPOCH_DATA_KEY_FORMAT
rather than cipher_kt_mode_aead().

 // src/openvpn/mtu.c
 unsigned int
 calc_packet_id_size_dc(const struct options *options, const struct key_type *kt)
 {
     bool tlsmode = options->tls_server || options->tls_client;

+    if (tlsmode && (options->imported_protocol_flags & CO_EPOCH_DATA_KEY_FORMAT))
+    {
+        return packet_id_size_epoch();
+    }
+
     bool packet_id_long_form = !tlsmode || cipher_kt_mode_ofb_cfb(kt->cipher);

     return packet_id_size(packet_id_long_form);
 }
 // src/openvpn/packet_id.h - beside packet_id_size()
+static inline int
+packet_id_size_epoch(void)
+{
+    return sizeof(uint64_t);
+}

Workaround: reserve >= 4 bytes headroom below the true MTU in mssfix N mtu.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions