Skip to content

[QUIC] Implement Path MTU Discovery (PMTUD) #407

Description

@kcenon

Summary

Implement Path MTU Discovery (PMTUD) for QUIC to optimize packet sizes and avoid IP fragmentation.

Current State

  • Fixed max datagram size: 1200 bytes (QUIC minimum)
  • No PMTUD implementation
  • Suboptimal throughput on paths supporting larger MTUs

Current congestion controller:

// Default max datagram size (QUIC minimum guaranteed MTU)
static constexpr size_t kDefaultMaxDatagramSize = 1200;

RFC 9000 Section 14: Datagram Size

Requirements

  • QUIC implementations SHOULD use PMTUD
  • Minimum QUIC packet size: 1200 bytes
  • Maximum: Path MTU minus IP/UDP overhead
  • Must probe to discover larger MTU

Proposed Implementation

1. PMTUD Controller Class

class pmtud_controller {
public:
    struct config {
        size_t min_mtu = 1200;        // QUIC minimum
        size_t max_probe_mtu = 1500;  // Maximum to probe
        size_t probe_step = 32;       // Probe increment
        std::chrono::seconds probe_timeout{3};
        size_t max_probes = 3;        // Probes before giving up
    };
    
    explicit pmtud_controller(config cfg = {});
    
    // Get current MTU to use
    [[nodiscard]] auto current_mtu() const -> size_t;
    
    // Check if we should send a probe
    [[nodiscard]] auto should_probe() const -> bool;
    
    // Get probe packet size
    [[nodiscard]] auto probe_size() const -> size_t;
    
    // Called when probe packet is acknowledged
    void on_probe_acked(size_t probed_size);
    
    // Called when probe is lost
    void on_probe_lost(size_t probed_size);
    
    // Called when ICMP Packet Too Big received
    void on_packet_too_big(size_t reported_mtu);
    
    // Check if MTU search is complete
    [[nodiscard]] auto is_search_complete() const -> bool;
    
private:
    enum class state {
        searching,      // Actively probing for larger MTU
        stable,         // MTU found, using it
        confirmation,   // Periodically re-validating
    };
    
    config config_;
    state state_{state::searching};
    size_t current_mtu_{1200};
    size_t probing_mtu_{0};
    size_t probe_count_{0};
    std::chrono::steady_clock::time_point last_probe_;
};

2. DPLPMTUD Algorithm (RFC 8899)

// Datagram Packetization Layer PMTU Discovery
class dplpmtud {
public:
    // States per RFC 8899 Section 5.2
    enum class search_state {
        disabled,       // PMTUD disabled
        base,           // Using BASE_PLPMTU
        searching,      // Binary search for larger MTU
        search_complete,// Maximum MTU found
        error,          // PTB triggered reduction
    };
    
    void start_search();
    void process_probe_result(bool acked);
    void handle_ptb(size_t mtu);
    
private:
    // Binary search parameters
    size_t low_mtu_;   // Known good
    size_t high_mtu_;  // Target to probe
    size_t probe_count_{0};
};

3. Integration with Connection

class connection {
public:
    // Get current path MTU
    [[nodiscard]] auto path_mtu() const -> size_t;
    
private:
    // Generate MTU probe packets
    auto generate_mtu_probe() -> std::optional<std::vector<uint8_t>>;
    
    // Handle probe responses
    void on_mtu_probe_acked(size_t size);
    void on_mtu_probe_lost(size_t size);
    
    pmtud_controller pmtud_;
};

4. Probe Packet Generation

auto connection::generate_mtu_probe() -> std::optional<std::vector<uint8_t>> {
    if (!pmtud_.should_probe()) {
        return std::nullopt;
    }
    
    size_t probe_size = pmtud_.probe_size();
    
    // Generate PING frame with padding
    std::vector<uint8_t> packet;
    
    // Add PING frame (ack-eliciting)
    packet.push_back(static_cast<uint8_t>(frame_type::ping));
    
    // Add PADDING frames to reach probe size
    size_t padding_needed = probe_size - packet.size() - header_overhead;
    for (size_t i = 0; i < padding_needed; ++i) {
        packet.push_back(0x00);  // PADDING frame
    }
    
    return packet;
}

Platform Considerations

Linux

  • IP_PMTUDISC_PROBE socket option for probing
  • IP_MTU to get current path MTU

macOS/iOS

  • IP_DONTFRAG for IPv4
  • IPV6_DONTFRAG for IPv6

Windows

  • IP_DONTFRAGMENT socket option

Tasks

  • Implement pmtud_controller class
  • Implement DPLPMTUD algorithm
  • Add probe packet generation
  • Handle probe ACKs and losses
  • Integrate with congestion controller
  • Add platform-specific socket options
  • Handle ICMP Packet Too Big messages
  • Unit tests for PMTUD logic
  • Integration tests with varying MTUs
  • Document platform differences

Acceptance Criteria

  • MTU probing discovers larger path MTU
  • Falls back to 1200 on probe failure
  • Handles ICMP PTB correctly
  • Congestion controller uses discovered MTU
  • Works on Linux, macOS, Windows
  • No regressions in existing tests

Files to Create/Modify

  • New: include/kcenon/network/protocols/quic/pmtud_controller.h
  • New: src/protocols/quic/pmtud_controller.cpp
  • Modify: src/protocols/quic/connection.cpp
  • Modify: src/protocols/quic/congestion_controller.cpp
  • New: tests/test_quic_pmtud.cpp

Related

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions