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.
This commit is contained in:
fanyang
2026-04-12 13:02:23 +08:00
committed by GitHub
parent 8311b11713
commit 51befdbf87
2 changed files with 153 additions and 24 deletions
+142 -20
View File
@@ -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());
}
}
+11 -4
View File
@@ -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);