From 51befdbf876e2ec997183f6fbc3b18a69c5e0da0 Mon Sep 17 00:00:00 2001 From: fanyang Date: Sun, 12 Apr 2026 13:02:23 +0800 Subject: [PATCH] fix(faketcp): harden packet parsing against malformed frames (#2103) Discard malformed fake TCP frames instead of panicking so OpenWrt nodes can survive unexpected or truncated packets. Also emit the correct IPv6 ethertype and cover the parser with round-trip and truncation regression tests. --- easytier/src/tunnel/fake_tcp/packet.rs | 162 ++++++++++++++++++++++--- easytier/src/tunnel/fake_tcp/stack.rs | 15 ++- 2 files changed, 153 insertions(+), 24 deletions(-) diff --git a/easytier/src/tunnel/fake_tcp/packet.rs b/easytier/src/tunnel/fake_tcp/packet.rs index 0651fd70..41770e3b 100644 --- a/easytier/src/tunnel/fake_tcp/packet.rs +++ b/easytier/src/tunnel/fake_tcp/packet.rs @@ -80,7 +80,10 @@ pub fn build_tcp_packet( let mut ethernet = MutableEthernetPacket::new(&mut eth_buf).unwrap(); ethernet.set_destination(dst_mac); ethernet.set_source(src_mac); - ethernet.set_ethertype(EtherTypes::Ipv4); + ethernet.set_ethertype(match local_addr { + SocketAddr::V4(_) => EtherTypes::Ipv4, + SocketAddr::V6(_) => EtherTypes::Ipv6, + }); match (local_addr, remote_addr) { (SocketAddr::V4(local), SocketAddr::V4(remote)) => { @@ -129,31 +132,150 @@ pub fn build_tcp_packet( pub fn parse_ip_packet( buf: &Bytes, ) -> Option<(MacAddr, MacAddr, IPPacket<'_>, tcp::TcpPacket<'_>)> { - let eth = EthernetPacket::new(buf).unwrap(); + let eth = EthernetPacket::new(buf.as_ref())?; let src_mac = eth.get_source(); let dst_mac = eth.get_destination(); + let ethertype = eth.get_ethertype(); tracing::trace!("Parsing IP packet: {:?}", eth); - let buf = &buf[ETH_HDR_LEN..]; - if buf[0] >> 4 == 4 { - let v4 = ipv4::Ipv4Packet::new(buf).unwrap(); - if v4.get_next_level_protocol() != ip::IpNextHeaderProtocols::Tcp { - return None; - } + let ip_payload = &buf[ETH_HDR_LEN..]; - let tcp = tcp::TcpPacket::new(&buf[IPV4_HEADER_LEN..]).unwrap(); - Some((src_mac, dst_mac, IPPacket::V4(v4), tcp)) - } else if buf[0] >> 4 == 6 { - let v6 = ipv6::Ipv6Packet::new(buf).unwrap(); - if v6.get_next_header() != ip::IpNextHeaderProtocols::Tcp { - return None; - } + match ethertype { + EtherTypes::Ipv4 => { + let v4 = ipv4::Ipv4Packet::new(ip_payload)?; + if v4.get_next_level_protocol() != ip::IpNextHeaderProtocols::Tcp { + return None; + } - let tcp = tcp::TcpPacket::new(&buf[IPV6_HEADER_LEN..]).unwrap(); - Some((src_mac, dst_mac, IPPacket::V6(v6), tcp)) - } else { - tracing::trace!("Invalid IP version: {}", buf[0] >> 4); - None + let tcp_offset = usize::from(v4.get_header_length()) * 4; + if tcp_offset < IPV4_HEADER_LEN || tcp_offset > ip_payload.len() { + return None; + } + + let tcp = tcp::TcpPacket::new(&ip_payload[tcp_offset..])?; + Some((src_mac, dst_mac, IPPacket::V4(v4), tcp)) + } + EtherTypes::Ipv6 => { + let v6 = ipv6::Ipv6Packet::new(ip_payload)?; + if v6.get_next_header() != ip::IpNextHeaderProtocols::Tcp { + return None; + } + + let tcp = tcp::TcpPacket::new(&ip_payload[IPV6_HEADER_LEN..])?; + Some((src_mac, dst_mac, IPPacket::V6(v6), tcp)) + } + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pnet::packet::Packet as _; + + #[test] + fn parse_ipv4_packet_round_trip() { + let src_mac = MacAddr::new(0x02, 0, 0, 0, 0, 1); + let dst_mac = MacAddr::new(0x02, 0, 0, 0, 0, 2); + let local_addr: SocketAddr = "192.0.2.1:12345".parse().unwrap(); + let remote_addr: SocketAddr = "198.51.100.2:23456".parse().unwrap(); + let payload = b"hello fake tcp"; + + let packet = build_tcp_packet( + src_mac, + dst_mac, + local_addr, + remote_addr, + 10, + 20, + tcp::TcpFlags::ACK, + Some(payload), + ); + + let (parsed_src_mac, parsed_dst_mac, ip_packet, tcp_packet) = + parse_ip_packet(&packet).unwrap(); + + assert_eq!(parsed_src_mac, src_mac); + assert_eq!(parsed_dst_mac, dst_mac); + assert_eq!(ip_packet.get_source(), local_addr.ip()); + assert_eq!(ip_packet.get_destination(), remote_addr.ip()); + assert_eq!(tcp_packet.get_source(), local_addr.port()); + assert_eq!(tcp_packet.get_destination(), remote_addr.port()); + assert_eq!(tcp_packet.payload(), payload); + } + + #[test] + fn build_and_parse_ipv6_packet_round_trip() { + let src_mac = MacAddr::new(0x02, 0, 0, 0, 0, 3); + let dst_mac = MacAddr::new(0x02, 0, 0, 0, 0, 4); + let local_addr: SocketAddr = "[2001:db8::1]:12345".parse().unwrap(); + let remote_addr: SocketAddr = "[2001:db8::2]:23456".parse().unwrap(); + let payload = b"ipv6 payload"; + + let packet = build_tcp_packet( + src_mac, + dst_mac, + local_addr, + remote_addr, + 30, + 40, + tcp::TcpFlags::ACK, + Some(payload), + ); + + let ethernet = EthernetPacket::new(packet.as_ref()).unwrap(); + assert_eq!(ethernet.get_ethertype(), EtherTypes::Ipv6); + + let (parsed_src_mac, parsed_dst_mac, ip_packet, tcp_packet) = + parse_ip_packet(&packet).unwrap(); + + assert_eq!(parsed_src_mac, src_mac); + assert_eq!(parsed_dst_mac, dst_mac); + assert_eq!(ip_packet.get_source(), local_addr.ip()); + assert_eq!(ip_packet.get_destination(), remote_addr.ip()); + assert_eq!(tcp_packet.get_source(), local_addr.port()); + assert_eq!(tcp_packet.get_destination(), remote_addr.port()); + assert_eq!(tcp_packet.payload(), payload); + } + + #[test] + fn parse_rejects_short_ethernet_frame() { + let packet = Bytes::from_static(&[0u8; ETH_HDR_LEN - 1]); + assert!(parse_ip_packet(&packet).is_none()); + } + + #[test] + fn parse_rejects_truncated_ipv4_tcp_packet() { + let packet = build_tcp_packet( + MacAddr::new(0x02, 0, 0, 0, 0, 5), + MacAddr::new(0x02, 0, 0, 0, 0, 6), + "192.0.2.10:1111".parse().unwrap(), + "198.51.100.20:2222".parse().unwrap(), + 1, + 2, + tcp::TcpFlags::ACK, + None, + ); + let truncated = Bytes::copy_from_slice(&packet[..ETH_HDR_LEN + IPV4_HEADER_LEN + 10]); + + assert!(parse_ip_packet(&truncated).is_none()); + } + + #[test] + fn parse_rejects_truncated_ipv6_header() { + let packet = build_tcp_packet( + MacAddr::new(0x02, 0, 0, 0, 0, 7), + MacAddr::new(0x02, 0, 0, 0, 0, 8), + "[2001:db8::10]:1111".parse().unwrap(), + "[2001:db8::20]:2222".parse().unwrap(), + 1, + 2, + tcp::TcpFlags::ACK, + None, + ); + let truncated = Bytes::copy_from_slice(&packet[..ETH_HDR_LEN + IPV6_HEADER_LEN - 1]); + + assert!(parse_ip_packet(&truncated).is_none()); } } diff --git a/easytier/src/tunnel/fake_tcp/stack.rs b/easytier/src/tunnel/fake_tcp/stack.rs index 3173c383..b3c311f8 100644 --- a/easytier/src/tunnel/fake_tcp/stack.rs +++ b/easytier/src/tunnel/fake_tcp/stack.rs @@ -223,8 +223,12 @@ impl Socket { return None; }; - let (src_mac, dst_mac, _v4_packet, tcp_packet) = - parse_ip_packet(&raw_buf).unwrap(); + let Some((src_mac, dst_mac, _v4_packet, tcp_packet)) = + parse_ip_packet(&raw_buf) + else { + trace!("Dropping malformed fake tcp packet for established socket"); + continue; + }; tracing::trace!( "Socket received TCP packet from {}({:?}) to {}({:?}): {:?}", @@ -307,8 +311,11 @@ impl Socket { info!("Waiting for client SYN + ACK timed out"); return None; }; - let (src_mac, _dst_mac, _v4_packet, tcp_packet) = - parse_ip_packet(&buf).unwrap(); + let Some((src_mac, _dst_mac, _v4_packet, tcp_packet)) = parse_ip_packet(&buf) + else { + trace!("Dropping malformed fake tcp packet during handshake"); + continue; + }; if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 { tracing::trace!("Connection {} reset by peer", self);