quic_pmtu (quic v1.3.1)
View SourceQUIC Path MTU Discovery implementation.
Implements Datagram Packetization Layer Path MTU Discovery (DPLPMTUD) to dynamically discover the optimal packet size for a path.
State Machine
The PMTU discovery follows RFC 8899 states:
disabled- PMTU discovery not activebase- Using base MTU (1200), ready to probesearching- Binary search for optimal MTU in progresssearch_complete- Found optimal MTUerror- Black hole detected, fell back to base MTU
Binary Search Algorithm
The module uses binary search between 1200 and max_mtu, stopping when the search range is less than 10 bytes.
Summary
Functions
Cancel all pending timers.
Create a probe packet (PING + PADDING frames).
Get the current effective MTU.
Get the current generation.
Get the current state.
Check if PMTU discovery is enabled.
Create a new PMTU state with default settings.
Create a new PMTU state with options.
Initialize PMTU probing after connection is established.
Reset black hole counter on successful ACK.
Track packet loss for black hole detection.
Reset PMTU state on path change (connection migration).
Handle probe packet ACK.
Handle probe packet loss.
Record that a probe packet was sent.
Handle probe timeout.
Handle raise timer expiration for periodic re-probing.
Set the probe timeout timer.
Set the raise timer for periodic re-probing.
Check if we should send a probe packet.
Types
-type pmtu_opts() :: #{pmtu_enabled => boolean(), pmtu_max_mtu => pos_integer(), pmtu_probe_timeout => pos_integer(), pmtu_raise_interval => pos_integer()}.
Functions
-spec cancel_timers(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Cancel all pending timers.
-spec create_probe_packet(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}, pos_integer()) -> {pos_integer(), [term()]}.
Create a probe packet (PING + PADDING frames).
Creates a packet with PING frame and PADDING to reach the target size. The probe size is calculated using binary search.
-spec current_mtu(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> pos_integer().
Get the current effective MTU.
-spec get_generation(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> non_neg_integer().
Get the current generation.
-spec get_state(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> disabled | base | searching | search_complete | error.
Get the current state.
-spec is_enabled(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> boolean().
Check if PMTU discovery is enabled.
-spec new() -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Create a new PMTU state with default settings.
-spec new(pmtu_opts()) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Create a new PMTU state with options.
Options: - pmtu_enabled: Enable PMTU discovery (default: true) - pmtu_max_mtu: Maximum MTU to probe (default: 1500)
-spec on_connection_established(pos_integer() | undefined, #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Initialize PMTU probing after connection is established.
Called when the QUIC handshake completes. Uses the peer's max_udp_payload_size transport parameter to set the upper bound.
-spec on_packet_acked(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Reset black hole counter on successful ACK.
-spec on_packet_lost(pos_integer(), #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Track packet loss for black hole detection.
Only counts losses of packets near the current MTU size (within 100 bytes). Small packet losses are not indicative of MTU black holes.
-spec on_path_change(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Reset PMTU state on path change (connection migration).
RFC 8899 Section 5.3.3: PMTU should be reset on path change since the new path may have different MTU characteristics.
-spec on_probe_acked(non_neg_integer(), non_neg_integer(), #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Handle probe packet ACK.
Called when the probe packet is acknowledged. On success, updates search bounds and MTU using the loss array algorithm.
-spec on_probe_lost(non_neg_integer(), non_neg_integer(), #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Handle probe packet loss.
Called when the probe packet is detected as lost. Uses loss array algorithm: inserts loss into array and adjusts search.
-spec on_probe_sent(non_neg_integer(), #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> {non_neg_integer(), #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}}.
Record that a probe packet was sent.
-spec on_probe_timeout(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Handle probe timeout.
Called when the probe timer fires. Treats timeout as loss and uses loss array algorithm.
-spec on_raise_timer(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Handle raise timer expiration for periodic re-probing.
Unlike on_path_change, this keeps current_mtu and probes higher. Used for periodic attempts to increase MTU without dropping throughput.
-spec set_probe_timer(reference(), #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Set the probe timeout timer.
-spec set_raise_timer(reference(), #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> #pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}.
Set the raise timer for periodic re-probing.
-spec should_probe(#pmtu_state{state :: disabled | base | searching | search_complete | error, base_mtu :: pos_integer(), current_mtu :: pos_integer(), max_mtu :: pos_integer(), search_min :: pos_integer(), lost :: [pos_integer() | undefined], last_probe_lost :: boolean(), probe_size :: non_neg_integer(), probe_pn :: non_neg_integer() | undefined, generation :: non_neg_integer(), probe_timer :: reference() | undefined, raise_timer :: reference() | undefined, black_hole_count :: non_neg_integer(), black_hole_threshold :: pos_integer()}) -> boolean().
Check if we should send a probe packet.
Returns true if: - State is base (ready to start probing) - State is searching and no probe is in flight - State is error and raise timer expired