← 返回作品展示

ProtoStack — 自研 QUIC 协议栈

RustQUICHTTP/3BBRv3

ProtoStack 是一个从 UDP 套接字到 HTTP/3 的完整 QUIC 协议实现,包含 0-RTT 握手、连接迁移、QPACK 头部压缩与 BBRv3 拥塞控制算法。项目深入网络协议栈底层,展示了高性能网络编程的深厚功底。

一、QUIC 协议架构

QUIC 基于 UDP 实现了类 TCP 的可靠传输,同时解决了 TCP 的队头阻塞问题。每个 Stream 独立传输,一个 Stream 的丢包不影响其他 Stream。QUIC 将 TLS 1.3 集成到握手过程中,实现 0-RTT 和 1-RTT 握手。

use std::net::UdpSocket;
use ring::aead;

pub struct QuicConnection {
    socket: UdpSocket,
    connection_id: ConnectionId,
    state: ConnectionState,
    streams: HashMap,
    crypto: QuicCrypto,
    congestion: Box,
    loss_detection: LossDetection,
}

pub struct QuicStream {
    id: StreamId,
    send_buffer: VecDeque,
    recv_buffer: BTreeMap>,
    send_offset: u64,
    recv_offset: u64,
    state: StreamState,
    flow_control: FlowController,
}

二、0-RTT 握手实现

QUIC 的 0-RTT 允许客户端在首次握手时就发送应用数据,前提是客户端已缓存服务器的静态配置。这需要 TLS 1.3 的 early_data 扩展和 QUIC 的地址验证令牌机制。

impl QuicConnection {
    pub fn connect_0rtt(&mut self, server_addr: SocketAddr,
        early_data: &[u8]) -> Result<()>
    {
        // 1. 使用缓存的服务器配置构建 ClientHello
        let cached_config = self.load_cached_config(server_addr)?;
        let client_hello = self.build_client_hello_0rtt(&cached_config);

        // 2. 发送 Initial 包 + 0-RTT 数据
        let initial_packet = self.build_initial_packet(&client_hello);
        let zero_rtt_packet = self.build_0rtt_packet(early_data,
            &cached_config.early_data_key);

        self.socket.send_to(&initial_packet, server_addr)?;
        self.socket.send_to(&zero_rtt_packet, server_addr)?;

        // 3. 等待服务器确认
        self.state = ConnectionState::WaitingForHandshake;
        Ok(())
    }

    fn build_0rtt_packet(&self, data: &[u8],
        key: &aead::LessSafeKey) -> Vec
    {
        let mut packet = QuicPacket::new(PacketType::ZeroRTT);
        let frame = CryptoFrame { offset: 0, data: data.to_vec() };
        packet.add_frame(QuicFrame::Crypto(frame));

        // 使用 early_data 密钥加密
        let nonce = self.generate_nonce();
        let encrypted = key.seal_in_place_append_tag(nonce,
            aead::Aad::empty(), &mut packet.payload)?;

        packet.build()
    }
}

三、连接迁移

QUIC 的连接由 Connection ID 标识,而非 IP+Port 四元组。这使得客户端在切换网络(WiFi→5G)时无需重新握手,只需用新地址发送携带相同 Connection ID 的数据包即可。

impl QuicConnection {
    pub fn migrate_connection(&mut self, new_addr: SocketAddr) -> Result<()> {
        // 1. 发送 PATH_CHALLENGE 帧验证新路径可达
        let challenge_data = rand::random::<[u8; 8]>();
        let frame = QuicFrame::PathChallenge {
            data: challenge_data,
        };
        self.send_frame(frame, new_addr)?;

        // 2. 等待 PATH_RESPONSE
        let response = self.wait_for_path_response(challenge_data,
            Duration::from_secs(3))?;

        // 3. 验证通过,切换路径
        if response.data == challenge_data {
            self.current_path = new_addr;
            self.path_validated = true;

            // 4. 在新路径上探测带宽
            self.congestion.on_path_change();
            self.probe_bandwidth(new_addr)?;
        }
        Ok(())
    }
}

四、BBRv3 拥塞控制

BBRv3 是 Google 开发的最新一代拥塞控制算法,通过持续探测带宽和 RTT 来找到最优发送速率,而非依赖丢包信号。相比 CUBIC,BBRv3 在高延迟、有损链路上吞吐量提升 10-100 倍。

pub struct BBRv3 {
    state: BBRState,
    max_bw: f64,           // 最大带宽估计
    min_rtt: Duration,     // 最小 RTT 估计
    pacing_rate: f64,      // 当前发送速率
    cwnd: u64,             // 拥塞窗口
    cycle: BBRCycle,
}

#[derive(PartialEq)]
enum BBRState {
    Startup,       // 指数增长探测带宽
    Drain,         // 排空队列
    ProbeBW,       // 周期性探测更多带宽
    ProbeRTT,      // 周期性探测最小 RTT
}

impl BBRv3 {
    pub fn on_ack(&mut self, packet: &AckedPacket) {
        let delivery_rate = packet.delivered / packet.interval.as_secs_f64();
        self.max_bw = self.max_bw.max(delivery_rate);
        self.min_rtt = self.min_rtt.min(packet.rtt);

        match self.state {
            BBRState::Startup => {
                self.pacing_rate = self.max_bw * 2.89;  // 2/ln(2)
                if self.pacing_rate_gain() < 1.0 {
                    self.state = BBRState::Drain;
                }
            }
            BBRState::ProbeBW => {
                self.cycle.advance();
                self.pacing_rate = self.max_bw * self.cycle.gain();
            }
            _ => {}
        }

        self.cwnd = (self.pacing_rate * self.min_rtt.as_secs_f64()
            * 2.0) as u64;
    }
}

五、QPACK 头部压缩

QPACK 是 HTTP/3 的头部压缩方案,基于 QCRAM 设计,解决了 HPACK 在 QUIC 中的队头阻塞问题。使用两个单向流分别传输编码器和解码器的表更新。

ProtoStack 是网络协议栈领域的深度实践,从传输层到应用层完整实现,展示了高性能网络编程的核心能力。

← 返回作品展示