QUIC peer max_udp_payload_size getter returns 1200 when the transport parameter is absent (openssl-4.1.0-alpha1)
OpenSSL version
The issue is present in:
- OpenSSL 4.1.0-alpha1, tag
openssl-4.1.0-alpha1, commitf42705198e92b4b969f05c973fff405880c7e80e - OpenSSL master commit
3bdd4a655bd707f66a7ea620ffae88afc3e15fa7from 2026-09-15
OpenSSL was built from upstream source rather than supplied by the operating system.
Summary
After completion of a QUIC handshake, SSL_get_feature_peer_request_uint() with SSL_VALUE_QUIC_UDP_PAYLOAD_SIZE_MAX returns 1200 when the peer omitted the max_udp_payload_size transport parameter.
RFC 9000 Section 18.2 specifies that the default value of an absent max_udp_payload_size parameter is 65527:
https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2
Therefore, the effective peer value exposed by the OpenSSL API should be 65527 when the parameter is absent.
Reproduction
After completing the QUIC handshake, obtain the peer value with:
uint64_t peer_max_udp_payload_size = 0;
if (!SSL_get_feature_peer_request_uint(
conn,
SSL_VALUE_QUIC_UDP_PAYLOAD_SIZE_MAX,
&peer_max_udp_payload_size)) {
/* handle error */
}
printf("%llu\n",
(unsigned long long)peer_max_udp_payload_size);Connect using an unmodified h2load/ngtcp2 client:
h2load --alpn-list=h3 \
--connect-to=127.0.0.1:8445 \
-n 1 -c 1 \
https://localhost:8445/ngtcp2 initializes its local max_udp_payload_size transport parameter to the RFC default of 65527. Since this is the default, ngtcp2 omits the parameter from the encoded transport parameters.
OpenSSL reports:
1200If h2load is modified to set:
params.max_udp_payload_size = 1452;ngtcp2 explicitly encodes the non-default value, and OpenSSL correctly reports:
1452This indicates that explicitly transmitted values are parsed correctly, while the absent-parameter case uses the wrong default.
Current implementation
In ch_init(), OpenSSL initializes the transmit Maximum Datagram Payload Length to 1200 and copies the same value into rx_max_udp_payload_size:
While parsing peer transport parameters, rx_max_udp_payload_size is updated only when the parameter is present:
The peer-request getter returns this field directly:
The source already defines the RFC default separately:
#define QUIC_MAX_MAX_UDP_PAYLOAD_SIZE 65527
#define QUIC_DEFAULT_MAX_UDP_PAYLOAD_SIZE \
QUIC_MAX_MAX_UDP_PAYLOAD_SIZEThe current initialization therefore conflates OpenSSL's conservative 1200-byte transmit MDPL with the effective value of the peer's transport parameter.
Expected behavior
When the peer omits max_udp_payload_size, the peer-request getter should return 65527, the RFC-defined default.
This should not change OpenSSL's transmit behavior. The transmit MDPL may remain fixed at 1200.
A possible correction would be to initialize:
ch->rx_max_udp_payload_size =
QUIC_DEFAULT_MAX_UDP_PAYLOAD_SIZE;while continuing to initialize:
qtx_args.mdpl = QUIC_MIN_INITIAL_DGRAM_LEN;A regression test should complete a handshake in which the peer omits max_udp_payload_size and verify that the peer-request getter returns 65527.
Related context
PR #29664 originally stated that OpenSSL would dynamically use the full peer-advertised payload range:
https://github.com/openssl/openssl/pull/29664
During review, that transmit-path behavior was deliberately removed because OpenSSL does not yet perform the necessary path-MTU discovery:
https://github.com/openssl/openssl/pull/29664#discussion_r2852280016
The author subsequently confirmed that the functionality had been removed:
https://github.com/openssl/openssl/pull/29664#discussion_r2854789323
This report does not request restoration of dynamic transmit sizing. It concerns only the value exposed by the peer transport-parameter API when the parameter is absent.
Source: openssl/openssl