从零实现TCP/IP协议栈:AR →IP分片→TCP状态机→拥塞控制

从零实现TCP/IP协议栈:AR →IP分片→TCP状态机→拥塞控制

一、引言

TCP/IP是互联网的基石。本文将用Rust从零实现一个用户态TCP/IP协议栈(仅800行),涵盖:以太网帧解析、ARP地址解析、IP分片重组、TCP状态机(CLOSED→ESTABLISHED→CLOSE_WAIT)、滑动窗口流控、CUBIC拥塞控制,最终通过原始套接字实现HTTP请求。

二、TUN设备与原始套接字

rust 复制代码
use std::fs::File;
use std::os::fd::AsRawFd;
use std::io::{Read, Write};

struct TunDevice {
    file: File,
    mtu: usize,
}

impl TunDevice {
    fn create(name: &str, ip: &str) -> Self {
        // Linux TUN设备: /dev/net/tun
        let file = File::open("/dev/net/tun").unwrap();
        
        // ioctl: 创建TUN接口
        unsafe {
            let mut ifr: libc::ifreq = std::mem::zeroed();
            let name_bytes = name.as_bytes();
            ifr.ifr_name[..name_bytes.len()].copy_from_slice(name_bytes);
            ifr.ifr_flags = libc::IFF_TUN | libc::IFF_NO_PI;
            
            libc::ioctl(file.as_raw_fd(), libc::TUNSETIFF, &ifr);
        }
        
        // 配置IP: ip addr add 10.0.0.1/24 dev tun0
        std::process::Command::new("ip")
            .args(["addr", "add", ip, "dev", name])
            .output().unwrap();
        std::process::Command::new("ip")
            .args(["link", "set", name, "up"])
            .output().unwrap();
        
        TunDevice { file, mtu: 1500 }
    }
    
    fn read_packet(&mut self) -> Vec {
        let mut buf = vec![0u8; self.mtu];
        let n = self.file.read(&mut buf).unwrap();
        buf.truncate(n);
        buf
    }
    
    fn write_packet(&mut self, data: &[u8]) {
        self.file.write_all(data).unwrap();
    }
}

三、ARP地址解析

rust 复制代码
use std::collections::HashMap;
use std::net::Ipv4Addr;

const ARP_REQUEST: u16 = 1;
const ARP_REPLY: u16 = 2;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct MacAddr([u8; 6]);

struct ArpTable {
    entries: HashMap,
    pending: HashMap>>,  // 等待ARP回复的包
}

#[repr(C, packed)]
struct ArpPacket {
    htype: u16,  // 硬件类型 (1=Ethernet)
    ptype: u16,  // 协议类型 (0x0800=IPv4)
    hlen: u8,    // 硬件地址长度 (6)
    plen: u8,    // 协议地址长度 (4)
    oper: u16,   // 操作 (1=Request, 2=Reply)
    sha: [u8; 6], // 发送方MAC
    spa: [u8; 4], // 发送方IP
    tha: [u8; 6], // 目标MAC (全0=未知)
    tpa: [u8; 4], // 目标IP
}

impl ArpTable {
    fn new() -> Self {
        ArpTable { entries: HashMap::new(), pending: HashMap::new() }
    }
    
    fn handle_packet(&mut self, data: &[u8], our_mac: &MacAddr, our_ip: Ipv4Addr) -> Option> {
        if data.len() < std::mem::size_of::() { return None; }
        
        let arp = unsafe { &*(data.as_ptr() as *const ArpPacket) };
        let target_ip = Ipv4Addr::from(arp.tpa);
        
        if target_ip != our_ip { return None; }  // 不是发给我的
        
        match arp.oper {
            ARP_REQUEST => {
                self.entries.insert(Ipv4Addr::from(arp.spa), MacAddr(arp.sha));
                
                // 发送ARP Reply
                let mut reply = ArpPacket {
                    htype: 1, ptype: 0x0800, hlen: 6, plen: 4,
                    oper: ARP_REPLY,
                    sha: our_mac.0, spa: our_ip.octets(),
                    tha: arp.sha, tpa: arp.spa,
                };
                let bytes = unsafe {
                    std::slice::from_raw_parts(&reply as *const _ as *const u8,
                                               std::mem::size_of::())
                };
                Some(bytes.to_vec())
            }
            ARP_REPLY => {
                let ip = Ipv4Addr::from(arp.spa);
                self.entries.insert(ip, MacAddr(arp.sha));
                
                // 发送pending队列中等待的包
                if let Some(pending_packets) = self.pending.remove(&ip) {
                    // ... 发送pending包 ...
                }
                None
            }
            _ => None,
        }
    }
    
    fn lookup(&self, ip: Ipv4Addr) -> Option<&MacAddr> {
        self.entries.get(&ip)
    }
}

四、IP分片重组

rust 复制代码
use std::collections::HashMap;

#[repr(C, packed)]
struct Ipv4Header {
    version_ihl: u8,       // 4位版本+4位头部长度
    dscp_ecn: u8,
    total_length: u16,
    identification: u16,
    flags_fragment: u16,   // 3位Flags+13位Fragment Offset
    ttl: u8,
    protocol: u8,          // 6=TCP, 17=UDP
    header_checksum: u16,
    source_ip: [u8; 4],
    dest_ip: [u8; 4],
}

struct IpReassembly {
    fragments: HashMap<(Ipv4Addr, Ipv4Addr, u16), Vec>>>,  // (src, dst, id) → fragments
}

impl IpReassembly {
    fn add_fragment(&mut self, header: &Ipv4Header, data: &[u8]) -> Option> {
        let src = Ipv4Addr::from(header.source_ip);
        let dst = Ipv4Addr::from(header.dest_ip);
        let id = header.identification;
        let offset = (header.flags_fragment & 0x1FFF) as usize * 8;
        let more_fragments = (header.flags_fragment & 0x2000) != 0;
        
        let key = (src, dst, id);
        let fragments = self.fragments.entry(key).or_insert_with(Vec::new);
        
        // 确保容量足够
        let fragment_idx = offset / 8 + data.len() / 8 + 1;
        if fragments.len() < fragment_idx {
            fragments.resize(fragment_idx, None);
        }
        fragments[offset / 8] = Some(data.to_vec());
        
        if !more_fragments {
            // 最后一片到达 → 尝试重组
            if fragments.iter().all(|f| f.is_some()) {
                let mut reassembled = Vec::new();
                for frag in fragments.iter() {
                    reassembled.extend_from_slice(frag.as_ref().unwrap());
                }
                self.fragments.remove(&key);
                return Some(reassembled);
            }
        }
        
        None // 还没收齐
    }
}

五、TCP核心状态机

rust 复制代码
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TcpState {
    Closed,
    Listen,
    SynRcvd,
    SynSent,
    Established,
    FinWait1,
    FinWait2,
    CloseWait,
    LastAck,
    TimeWait,
    Closing,
}

#[repr(C, packed)]
struct TcpHeader {
    source_port: u16,
    dest_port: u16,
    seq_num: u32,
    ack_num: u32,
    data_offset_flags: u16,  // 4位偏移+6位保留+6位flag
    window: u16,
    checksum: u16,
    urgent_ptr: u16,
}

// TCP Flag位: 从低到高 FIN(0) SYN(1) RST(2) PSH(3) ACK(4) URG(5)
const TCP_FIN: u16 = 0x01;
const TCP_SYN: u16 = 0x02;
const TCP_RST: u16 = 0x04;
const TCP_PSH: u16 = 0x08;
const TCP_ACK: u16 = 0x10;

struct TcpConnection {
    state: TcpState,
    local_ip: Ipv4Addr, local_port: u16,
    remote_ip: Ipv4Addr, remote_port: u16,
    
    // 序列号
    send_next: u32,      // SND.NXT: 下一个要发送的seq
    send_unack: u32,     // SND.UNA: 最早未确认的seq
    send_window: u16,    // 对端通告的窗口
    receive_next: u32,   // RCV.NXT: 期望收到的下一个seq
    
    // 重传队列
    retransmit_queue: Vec<(u32, Vec)>,  // (seq, data)
    
    // 接收缓冲区 (乱序重组)
    receive_buffer: HashMap>,
    
    // RTT/RTO估算
    srtt: f64,    // 平滑往返时间
    rttvar: f64,  // RTT方差
    rto: f64,     // 重传超时
}

impl TcpConnection {
    fn default_rto() -> f64 { 1.0 }  // 初始RTO = 1秒
    
    fn handle_packet(&mut self, tcp_header: &TcpHeader, payload: &[u8]) -> Option> {
        let flags = tcp_header.data_offset_flags & 0x3F;
        
        match self.state {
            TcpState::Listen => {
                if flags & TCP_SYN != 0 && flags & TCP_ACK == 0 {
                    // 收到SYN → SYN_RCVD, 发送SYN+ACK
                    self.receive_next = tcp_header.seq_num.wrapping_add(1);
                    self.send_next = rand::random::();
                    self.state = TcpState::SynRcvd;
                    return Some(self.build_packet(TCP_SYN | TCP_ACK, None));
                }
            }
            TcpState::SynSent => {
                if flags & TCP_SYN != 0 && flags & TCP_ACK != 0 {
                    // 收到SYN+ACK → ESTABLISHED, 发送ACK
                    self.receive_next = tcp_header.seq_num.wrapping_add(1);
                    self.send_unack = tcp_header.ack_num;
                    self.state = TcpState::Established;
                    return Some(self.build_packet(TCP_ACK, None));
                }
            }
            TcpState::Established => {
                let seq = tcp_header.seq_num;
                
                // ★ 数据接收
                if !payload.is_empty() && seq == self.receive_next {
                    // 顺序到达
                    let data = payload.to_vec();
                    self.receive_next = seq.wrapping_add(payload.len() as u32);
                    
                    // 检查receive_buffer中的乱序数据
                    while let Some(buffered) = self.receive_buffer.remove(&self.receive_next) {
                        self.receive_next = self.receive_next.wrapping_add(buffered.len() as u32);
                    }
                    
                    // 发送ACK(确认收到的数据)
                    return Some(self.build_packet(TCP_ACK, None));
                } else if !payload.is_empty() && seq > self.receive_next {
                    // 乱序到达 → 缓存
                    self.receive_buffer.insert(seq, payload.to_vec());
                    // 发送重复ACK(触发快重传)
                    return Some(self.build_packet(TCP_ACK, None));
                }
                
                // FIN处理
                if flags & TCP_FIN != 0 {
                    self.receive_next = self.receive_next.wrapping_add(1);
                    self.state = TcpState::CloseWait;
                    return Some(self.build_packet(TCP_ACK | TCP_FIN, None));  // 被动关闭 → 发FIN+ACK
                }
            }
            TcpState::FinWait1 => {
                if flags & TCP_ACK != 0 {
                    if flags & TCP_FIN != 0 {
                        // 同时收到ACK+FIN → TIME_WAIT
                        self.receive_next = tcp_header.seq_num.wrapping_add(1);
                        self.send_unack = tcp_header.ack_num;
                        self.state = TcpState::TimeWait;
                        return Some(self.build_packet(TCP_ACK, None));
                    } else {
                        // 仅ACK → FIN_WAIT_2
                        self.send_unack = tcp_header.ack_num;
                        self.state = TcpState::FinWait2;
                    }
                }
            }
            _ => {}
        }
        None
    }
    
    fn build_packet(&self, flags: u16, data: Option<&[u8]>) -> Vec {
        let mut buf = vec![0u8; std::mem::size_of::()];
        let header = TcpHeader {
            source_port: self.local_port,
            dest_port: self.remote_port,
            seq_num: self.send_next,
            ack_num: self.receive_next,
            data_offset_flags: (5 << 12) | flags,  // 5×4=20字节头部
            window: 65535,
            checksum: 0,  // 先填0,后面计算
            urgent_ptr: 0,
        };
        
        unsafe {
            std::ptr::copy_nonoverlapping(
                &header as *const _ as *const u8,
                buf.as_mut_ptr(),
                std::mem::size_of::()
            );
        }
        
        if let Some(d) = data {
            buf.extend_from_slice(d);
        }
        
        buf
    }
}

六、拥塞控制

rust 复制代码
struct CubicCongestionControl {
    cwnd: f64,          // 拥塞窗口(字节)
    ssthresh: f64,     // 慢启动阈值
    w_max: f64,        // 上次丢包时的窗口(用于CUBIC计算)
    beta: f64,         // 乘法减小因子 (CUBIC: 0.7)
    c: f64,             // CUBIC参数 (默认0.4)
    tcp_friendliness: bool,
}

impl CubicCongestionControl {
    fn new() -> Self {
        CubicCongestionControl {
            cwnd: 1460.0 * 10.0,   // 初始窗口=10 MSS
            ssthresh: f64::MAX,     // 初始在慢启动
            w_max: 0.0, beta: 0.7, c: 0.4,
            tcp_friendliness: true,
        }
    }
    
    fn on_ack(&mut self, bytes_acked: usize, rtt: f64) {
        if self.cwnd < self.ssthresh {
            // ★ 慢启动: 每收到一个ACK, cwnd += MSS
            self.cwnd += bytes_acked as f64;
        } else {
            // ★ CUBIC拥塞避免
            // cwnd = C*(t - K)^3 + w_max
            // 其中 K = (w_max * beta / C)^(1/3)
            let k = (self.w_max * self.beta / self.c).cbrt();
            let t = rtt;  // 简化: 用RTT作为时间度量
            self.cwnd = self.c * (t - k).powi(3) + self.w_max;
        }
    }
    
    fn on_loss(&mut self) {
        self.w_max = self.cwnd;  // 记录丢包点
        self.ssthresh = (self.cwnd * self.beta) as f64;  // 乘法减小
        self.cwnd = self.ssthresh;  // 窗口减半
    }
}

七、端到端HTTP请求

rust 复制代码
fn http_get_via_stack(host: &str, path: &str) -> String {
    // 1. DNS解析 (略,假设已知IP)
    let server_ip: Ipv4Addr = "93.184.216.34".parse().unwrap();
    
    // 2. ARP查询
    let server_mac = arp_table.lookup(server_ip)
        .or_else(|| { arp_table.send_request(server_ip); None })
        .unwrap();
    
    // 3. TCP三次握手
    tcp.state = TcpState::SynSent;
    tun.write_packet(&tcp.build_packet(TCP_SYN, None));
    let syn_ack = tun.read_packet();
    tcp.handle_packet(&parse_tcp(&syn_ack), &[]);
    tun.write_packet(&tcp.build_packet(TCP_ACK, None));
    
    // 4. HTTP GET
    tcp.state = TcpState::Established;
    let http_request = format!("GET {} HTTP/1.1\r\nHost: {}\r\n\r\n", path, host);
    tun.write_packet(&tcp.build_packet(TCP_PSH | TCP_ACK, Some(http_request.as_bytes())));
    
    // 5. 接收HTTP响应
    let mut response = String::new();
    loop {
        let packet = tun.read_packet();
        let (tcp_hdr, payload) = parse_tcp(&packet);
        tcp.handle_packet(&tcp_hdr, &payload);
        response.push_str(std::str::from_utf8(&payload).unwrap());
        if response.contains("\r\n\r\n") { break; }
    }
    
    // 6. 四次挥手(FIN_WAIT1→FIN_WAIT2→TIME_WAIT→CLOSED)
    tun.write_packet(&tcp.build_packet(TCP_FIN | TCP_ACK, None));
    let fin_ack = tun.read_packet(); tcp.handle_packet(&parse_tcp(&fin_ack), &[]);
    // TIME_WAIT: 等2MSL
    response
}

八、总结

从0实现的TCP/IP四大挑战:

  1. IP分片 --- MTU限制 + 识别标志位重组
  2. TCP状态机 --- 11种状态 + 标志位驱动的转换
  3. 滑动窗口 --- 流控 + 选择性确认(SACK)的乱序处理
  4. 拥塞控制 --- CUBIC的三次函数窗口调整 + TCP友好性

一个完整的用户态TCP/IP栈 ~800行代码,可处理真实网络流量。

相关推荐
paopaokaka_luck1 小时前
基于springboot3+vue3的乡村医生诊疗管理系统(AI助手、协同过滤算法、webSocket实时聊天、Echarts图形化分析)
前端·网络·人工智能·spring boot·websocket·网络协议·echarts
热心市民R先生1 小时前
IgH EtherCAT Master 1.5 全流程安装部署手册(Git 源码版 + 内核源码修改 + 实时内核适配)
网络·机器人
程序猿乐锅1 小时前
【计算机网络 | 第三章】数据链路层
网络·网络协议·计算机网络
zerwave2 小时前
Docker 学习:多容器网络互通——从 host 模式到自定义 bridge 网络
网络·学习·docker
大模型搬砖师2 小时前
在Kubernetes上部署企业AI网关:一份云原生参考
网络·人工智能·安全
为啥全要学3 小时前
fastapi websocket全双工通信
websocket·网络协议·fastapi
celiahul3 小时前
当搜索引擎变成“问答机器人”,你的网站该如何适配?
网络·人工智能·搜索引擎·内容运营·外贸推广
七月稻草人3 小时前
Grafana只能在内网看?配置HTTPS公网地址远程打开监控面板
网络协议·https·grafana
进击切图仔3 小时前
Ubuntu 挂载 XFS 磁盘并免密读写
linux·网络·ubuntu