学不会 TCP
? 没关系,让我们就自己造一个。
环境搭建 本文专注于 TCP
层的协议实现,因此对于 网卡驱动
OS
都不在本文的实现范围内,因此我们基于现已有pnet
进行开发。
main source code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 fn main () { let interface_name = env::args ().nth (1 ).unwrap (); let interface_names_match = |iface: &NetworkInterface| iface.name == interface_name; let interfaces = datalink::interfaces (); let interface = interfaces.into_iter () .filter (interface_names_match) .next () .unwrap (); let (mut tx, mut rx) = match datalink::channel (&interface, Default ::default ()) { Ok (Ethernet (tx, rx)) => (tx, rx), Ok (_) => panic! ("Unhandled channel type" ), Err (e) => panic! ("An error occurred when creating the datalink channel: {}" , e) }; loop { match rx.next () { Ok (packet) => { let packet = EthernetPacket::new (packet).unwrap (); println! ("Got packet {:?}" , packet); } Err (e) => { panic! ("An error occurred while reading: {}" , e); } } } }
这就是我们最初的模板环境的,源码下载
我们运行可得结果
1 2 3 4 $ cargo run -- lo0 Got packet EthernetPacket { destination : 00:00:00:00:00:00, source : 00:00:00:00:00:00, ethertype : EtherType(0), } Got packet EthernetPacket { destination : 00:00:00:00:00:00, source : 00:00:00:00:00:00, ethertype : EtherType(0), } Got packet EthernetPacket { destination : 00:00:00:00:00:00, source : 00:00:00:00:00:00, ethertype : EtherType(0), }
IP 协议 Internet Protocol
协议为上层协议提供了寻址的能力, ip
协议通过协议头部分的 IP Address
进行不同目标地址的区分,因为我们直接从 二层
上进行本次探索,因此对于 IP Route
的部分已经被 OS
完成,我们直接解析协议即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +------+ +-----+ +-----+ +-----+ |Telnet| | FTP | | TFTP| ... | ... | +------+ +-----+ +-----+ +-----+ | | | | +-----+ +-----+ +-----+ | TCP | | UDP | ... | ... | +-----+ +-----+ +-----+ | | | +--------------------------+----+ | Internet Protocol & ICMP | +--------------------------+----+ | +---------------------------+ | Local Network Protocol | +---------------------------+
IP
协议的构成如下
Internet Header Format hhttps://tools.ietf.org/html/rfc791#page-11 doc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
让我们开始 coding
吧,我们先来定义下数据结构。比 u8
还小的数据体我们都用 u8
描述。
定义格式 def 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 pub struct IPv4Header { pub version: u8 , pub ihl: u8 , pub toc: u8 , pub len: u16 , pub identification: u16 , pub flags: u8 , pub offset: u16 , pub ttl: u8 , pub protocol: u8 , pub checksum: u16 , pub source_address: u32 , pub destination_address: u32 , pub options_len: u8 , pub options_buffer: [u8 ; 40 ], } pub struct IPv4 { pub header: IPv4Header, pub data: Vec <u8 >, }
我们定义完成我们的 IP
协议格式之后,增加一个对于 Check TCP IP
的函数,并为之增加单元测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pub fn is_tcp_ip (packet: &[u8 ]) -> bool { if packet.len () < 10 { return false ; } let version = packet[0 ] >> 4 ; let protocol = packet[9 ]; return version == 4 && protocol == 6 ; } #[test] pub fn test_is_tcp_ip () { let hex_data = hex::decode ("45000034000040004006f023c0a80d1f783504a4df98e67dd861552917e0b7ae801007fff3ac00000101080a016dfbad36f9392c" ); assert! (is_tcp_ip (hex_data.unwrap ().as_slice ())); }
协议解析 这部分其实没有什么花头,按照规则解析即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 impl IPv4 { fn new (packet: &[u8 ]) -> IPv4 { let len = BigEndian::read_u16 (&packet[2 ..]); let ihl = packet[0 ] << 4 >> 4 ; return IPv4 { header: IPv4Header { version: packet[0 ] >> 4 , ihl, toc: packet[1 ], len, identification: BigEndian::read_u16 (&packet[4 ..]), flags: packet[6 ] >> 5 , offset: BigEndian::read_u16 (&packet[6 ..]) << 3 >> 3 , ttl: packet[8 ], protocol: packet[9 ], checksum: BigEndian::read_u16 (&packet[10 ..]), source_address: BigEndian::read_u32 (&packet[12 ..]), destination_address: BigEndian::read_u32 (&packet[16 ..]), options_len: 0 , options_buffer: vec! [], }, data: Vec ::from (&packet[(ihl as usize ) * 4 ..len as usize ]), }; } }
看看效果 修改我们的 main
函数,打印出更可读的部分内容
1 2 3 4 5 6 7 if packet.len () > payload_offset && ip::IPv4::is_tcp_ip (&packet[payload_offset..]) { let ipv4_packet = ip::IPv4::new (&packet[payload_offset..]); println! ("Got Tcp Ip Package From {} To {} Len {}" , ipv4_packet.header.source (), ipv4_packet.header.destination (), ipv4_packet.header.len); }
效果如下:
1 2 3 4 5 Got Tcp Ip Package From 127.0.0.1 To 127.0.0.1 Len 64 Got Tcp Ip Package From 127.0.0.1 To 127.0.0.1 Len 40 Got Tcp Ip Package From 127.0.0.1 To 127.0.0.1 Len 100 Got Tcp Ip Package From 127.0.0.1 To 127.0.0.1 Len 52 Got Tcp Ip Package From 127.0.0.1 To 127.0.0.1 Len 100
为了快速的跑完主线,对于 Options
和 分片
部分的逻辑暂时也没有涉及,因此对于 TCP
有自己的 MMS
的协商部分,一般不会使用到 IP
的分片逻辑。下一步我们就正式的进入我们的 TCP
代码部分吧。
TCP 协议 传输控制协议(Transmission Control Protocol
)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793
定义。
数据格式定义 和 IP
类似
TCP Header Format link 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data | |U|A|P|R|S|F| | | Offset| Reserved |R|C|S|S|Y|I| Window | | | |G|K|H|T|N|N| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
我们先来定义数据结构先
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 pub struct TCPHeader { pub source_port: u16 , pub destination_port: u16 , pub sequence_number: u32 , pub acknowledgment_number: u32 , pub offset: u8 , pub reserved: u8 , pub flags: u8 , pub windows: u16 , pub checksum: u16 , pub urgent_pointer: u16 , } pub struct TCP { pub header: TCPHeader, pub data: Vec <u8 >, }
解析数据 相同的味道和套路,再来一遍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 impl TCP { pub fn new (packet: &[u8 ]) -> TCP { let offset = packet[12 ] >> 4 ; return TCP { header: TCPHeader { source_port: BigEndian::read_u16 (packet), destination_port: BigEndian::read_u16 (&packet[2 ..]), sequence_number: BigEndian::read_u32 (&packet[4 ..]), acknowledgment_number: BigEndian::read_u32 (&packet[8 ..]), offset, reserved: (BigEndian::read_u16 (&packet[12 ..]) << 4 >> 10 ) as u8 , flags: packet[13 ] << 2 >> 2 , windows: BigEndian::read_u16 (&packet[14 ..]), checksum: BigEndian::read_u16 (&packet[17 ..]), urgent_pointer: BigEndian::read_u16 (&packet[21 ..]), }, data: Vec ::from (&packet[(offset as usize ) * 4 ..]), }; } }
记得补充单元测试
unit test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #[test] pub fn test_parse_tcp () { let hex_data = hex::decode ("e67ddf9817e0b77ed8615529801801f52b2300000101080a36f9392c016de81f263cdb224dcc9a4b4c2191ecb4c43c0a3daeb61233ee4af155a60c9dcac70fcebe0fca8964908c1c5f5073c50b0522eb" ); let tcp = TCP::new (hex_data.unwrap ().as_slice ()); assert_eq! (59005 , tcp.header.source_port); assert_eq! (57240 , tcp.header.destination_port); assert_eq! (400603006 , tcp.header.sequence_number); assert_eq! (3630257449 , tcp.header.acknowledgment_number); assert_eq! (501 , tcp.header.windows); assert! (tcp.header.is_psh ()); assert! (tcp.header.is_ack ()); assert! (!tcp.header.is_fin ()); assert! (!tcp.header.is_rst ()); assert! (!tcp.header.is_syn ()); assert! (!tcp.header.is_urg ()); let data = hex::decode ("263cdb224dcc9a4b4c2191ecb4c43c0a3daeb61233ee4af155a60c9dcac70fcebe0fca8964908c1c5f5073c50b0522eb" ); assert_eq! (data.unwrap ().as_slice (), tcp.data.as_slice ()); }
增加Flag辅助函数 我们知道 TCP
大部分的时候,我们都需要判断 Flags
的标记,一共 6
种,我们处理一下。
tcpheader flags 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 impl TCPHeader { pub fn is_urg (&self ) -> bool { self .flags & 0b100000 != 0 } pub fn is_ack (&self ) -> bool { self .flags & 0b010000 != 0 } pub fn is_psh (&self ) -> bool { self .flags & 0b001000 != 0 } pub fn is_rst (&self ) -> bool { self .flags & 0b000100 != 0 } pub fn is_syn (&self ) -> bool { self .flags & 0b000010 != 0 } pub fn is_fin (&self ) -> bool { self .flags & 0b000001 != 0 } }
源码地址
在来看看效果
1 2 3 4 5 6 7 8 9 10 if packet.len () > payload_offset && ip::IPv4::is_tcp_ip (&packet[payload_offset..]) { let ipv4_packet = ip::IPv4::new (&packet[payload_offset..]); let tcp_packet = tcp::TCP::new (ipv4_packet.data.as_slice ()); println! ("Got Tcp Ip Package From {}:{} To {}:{} Data Len {}" , ipv4_packet.header.source (), tcp_packet.header.source_port, ipv4_packet.header.destination (), tcp_packet.header.destination_port, tcp_packet.data.len ()); }
1 2 3 4 5 Got Tcp Ip Package From 127.0.0.1:1080 To 127.0.0.1:64533 Data Len 48 Got Tcp Ip Package From 127.0.0.1:1080 To 127.0.0.1:64588 Data Len 47 Got Tcp Ip Package From 127.0.0.1:64533 To 127.0.0.1:1080 Data Len 0 Got Tcp Ip Package From 127.0.0.1:64588 To 127.0.0.1:1080 Data Len 0 Got Tcp Ip Package From 127.0.0.1:64531 To 127.0.0.1:64532 Data Len 48
TCP 状态储存 我们知道 TCP/IP
协议来说,我们通过 (来源IP,来源端口,目的IP,目标端口) 这四组数据可以组成一个唯一的ID
,我们使用这个ID
作为我们储存的KEY
来储存我们的数据。
我们用一种比较简单的 u128
来表示我们的这个 ID
1 2 3 0 31 63 95 127 +-----------------------------------------------------------------------------------------------+ source address source port destination address destination port
计算ID 1 2 3 4 let mut unique : u128 = (packet.header.source_address as u128 ) << 96 ;unique = unique & ((tcp_packet.header.source_port as u128 ) << 64 ); unique = unique & ((packet.header.destination_address as u128 ) << 32 ); unique = unique & tcp_packet.header.destination_port as u128 ;
之后我们简单的使用一个 map
来储存我们的数据。
1 2 3 4 5 6 lazy_static! { static ref HASHMAP: HashMap<u128 , TCPCurState> = { let mut m = HashMap::new (); m }; }
TCP 状态转移 Map 先上一份PDF
三次握手 我们来实现左线关于 Server
端的 TCP
状态转移,不过因为我们并没有实现 Socket
,因此数据进入系统的第一步应该是通过 Listening Port
决定是否需要丢弃 Packet
,因此我们使用一个常量来模拟 Listening Port
。
1 const LISTENING_PORT: u16 = 10023 ;
那我们先来处理我们第一步需要处理的状态转换: LISTEN
-> SYN_RCVD
,这是我们建立 TCP
连接的第一步,从图上看我们知道,服务器接收到 syn
报文然后响应 syn,ack
报文即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 TCP A TCP B 1. CLOSED LISTEN 2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED 3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED 4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED 5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED Basic 3-Way Handshake for Connection Synchronization
参考