use nom::{
bytes::complete::take,
number::complete::{be_u16, be_u8},
IResult,
};
pub const PROTO_UDP: u8 = 17;
pub const PROTO_TCP: u8 = 6;
pub const ETHERTYPE_IP: u16 = 0x0800;
pub const ETHERTYPE_8021Q: u16 = 0x8100;
pub const ETHERTYPE_8021AD: u16 = 0x88a8;
#[derive(Debug, Clone)]
pub struct IpHeader {
pub src: [u8; 4],
pub dst: [u8; 4],
pub protocol: u8,
pub ttl: u8,
}
impl IpHeader {
pub fn src_str(&self) -> String {
format!(
"{}.{}.{}.{}",
self.src[0], self.src[1], self.src[2], self.src[3]
)
}
pub fn dst_str(&self) -> String {
format!(
"{}.{}.{}.{}",
self.dst[0], self.dst[1], self.dst[2], self.dst[3]
)
}
}
pub fn strip_ethernet(input: &[u8]) -> Option<&[u8]> {
if input.len() < 14 {
return None;
}
let ethertype = u16::from_be_bytes([input[12], input[13]]);
match ethertype {
e if e == ETHERTYPE_IP => Some(&input[14..]),
e if e == ETHERTYPE_8021Q => {
if input.len() < 18 {
return None;
}
let inner_et = u16::from_be_bytes([input[16], input[17]]);
if inner_et == ETHERTYPE_IP {
Some(&input[18..])
} else {
None
}
}
e if e == ETHERTYPE_8021AD => {
if input.len() < 22 {
return None;
}
let mid_et = u16::from_be_bytes([input[16], input[17]]);
if mid_et != ETHERTYPE_8021Q {
return None;
}
let inner_et = u16::from_be_bytes([input[20], input[21]]);
if inner_et == ETHERTYPE_IP {
Some(&input[22..])
} else {
None
}
}
_ => None,
}
}
fn parse_ip_header(input: &[u8]) -> IResult<&[u8], (IpHeader, &[u8])> {
let (input, ver_ihl) = be_u8(input)?;
let ihl = ((ver_ihl & 0x0f) * 4) as usize;
if ihl < 20 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
let (input, _dscp_ecn) = be_u8(input)?;
let (input, total_len) = be_u16(input)?;
let (input, _ident) = be_u16(input)?;
let (input, _flags_frag) = be_u16(input)?;
let (input, ttl) = be_u8(input)?;
let (input, protocol) = be_u8(input)?;
let (input, _checksum) = be_u16(input)?;
let (input, src_raw) = take(4usize)(input)?;
let (input, dst_raw) = take(4usize)(input)?;
let src = [src_raw[0], src_raw[1], src_raw[2], src_raw[3]];
let dst = [dst_raw[0], dst_raw[1], dst_raw[2], dst_raw[3]];
let options_len = ihl - 20;
let (input, _) = take(options_len)(input)?;
let payload_len = (total_len as usize).saturating_sub(ihl);
let (input, payload) = take(payload_len)(input)?;
Ok((
input,
(
IpHeader {
src,
dst,
protocol,
ttl,
},
payload,
),
))
}
pub fn extract_ip(frame: &[u8]) -> Option<(IpHeader, &[u8])> {
let ip_data = strip_ethernet(frame)?;
parse_ip_header(ip_data).ok().map(|(_, result)| result)
}
pub fn extract_udp(payload: &[u8]) -> Option<(u16, u16, &[u8])> {
if payload.len() < 8 {
return None;
}
let src_port = u16::from_be_bytes([payload[0], payload[1]]);
let dst_port = u16::from_be_bytes([payload[2], payload[3]]);
let length = u16::from_be_bytes([payload[4], payload[5]]) as usize;
if length < 8 || length > payload.len() {
return None;
}
Some((src_port, dst_port, &payload[8..length]))
}
pub struct TcpSegment<'a> {
pub src_port: u16,
pub dst_port: u16,
pub payload: &'a [u8],
}
pub fn extract_tcp(payload: &[u8]) -> Option<TcpSegment<'_>> {
if payload.len() < 20 {
return None;
}
let src_port = u16::from_be_bytes([payload[0], payload[1]]);
let dst_port = u16::from_be_bytes([payload[2], payload[3]]);
let data_off = ((payload[12] >> 4) as usize) * 4;
if data_off < 20 || data_off > payload.len() {
return None;
}
let tcp_payload = &payload[data_off..];
Some(TcpSegment {
src_port,
dst_port,
payload: tcp_payload,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn build_ip_frame(ethertype_prefix: &[u8], protocol: u8, payload: &[u8]) -> Vec<u8> {
let mut frame = Vec::new();
frame.extend_from_slice(&[0u8; 12]); frame.extend_from_slice(ethertype_prefix);
let total_len = 20 + payload.len();
frame.push(0x45); frame.push(0); frame.extend_from_slice(&(total_len as u16).to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes()); frame.extend_from_slice(&0u16.to_be_bytes()); frame.push(64); frame.push(protocol);
frame.extend_from_slice(&0u16.to_be_bytes()); frame.extend_from_slice(&[192, 0, 2, 1]); frame.extend_from_slice(&[192, 0, 2, 2]); frame.extend_from_slice(payload);
frame
}
fn build_udp_payload(src_port: u16, dst_port: u16, data: &[u8]) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&src_port.to_be_bytes());
buf.extend_from_slice(&dst_port.to_be_bytes());
buf.extend_from_slice(&((8 + data.len()) as u16).to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(data);
buf
}
#[test]
fn strips_untagged_ethernet() {
let frame = build_ip_frame(ÐERTYPE_IP.to_be_bytes(), PROTO_UDP, &[1, 2, 3]);
let ip = strip_ethernet(&frame).unwrap();
assert_eq!(ip[9], PROTO_UDP); }
#[test]
fn strips_single_8021q_tag() {
let mut prefix = ETHERTYPE_8021Q.to_be_bytes().to_vec();
prefix.extend_from_slice(&0x0064u16.to_be_bytes()); prefix.extend_from_slice(ÐERTYPE_IP.to_be_bytes());
let frame = build_ip_frame(&prefix, PROTO_UDP, &[1, 2, 3]);
assert!(strip_ethernet(&frame).is_some());
}
#[test]
fn strips_double_tagged_qinq_frame() {
let mut prefix = ETHERTYPE_8021AD.to_be_bytes().to_vec();
prefix.extend_from_slice(&0x002Au16.to_be_bytes()); prefix.extend_from_slice(ÐERTYPE_8021Q.to_be_bytes());
prefix.extend_from_slice(&0x0064u16.to_be_bytes()); prefix.extend_from_slice(ÐERTYPE_IP.to_be_bytes());
let frame = build_ip_frame(&prefix, PROTO_UDP, &[1, 2, 3]);
assert!(strip_ethernet(&frame).is_some());
}
#[test]
fn rejects_non_ip_ethertype() {
let frame = build_ip_frame(&0x86DDu16.to_be_bytes(), PROTO_UDP, &[1, 2, 3]); assert!(strip_ethernet(&frame).is_none());
}
#[test]
fn extracts_ip_and_udp_end_to_end() {
let udp_data = b"hello";
let udp = build_udp_payload(1234, 53, udp_data);
let frame = build_ip_frame(ÐERTYPE_IP.to_be_bytes(), PROTO_UDP, &udp);
let (ip_header, ip_payload) = extract_ip(&frame).unwrap();
assert_eq!(ip_header.protocol, PROTO_UDP);
assert_eq!(ip_header.src_str(), "192.0.2.1");
assert_eq!(ip_header.dst_str(), "192.0.2.2");
let (src_port, dst_port, payload) = extract_udp(ip_payload).unwrap();
assert_eq!(src_port, 1234);
assert_eq!(dst_port, 53);
assert_eq!(payload, udp_data);
}
#[test]
fn extracts_tcp_skipping_options() {
let mut tcp = Vec::new();
tcp.extend_from_slice(&4321u16.to_be_bytes()); tcp.extend_from_slice(&80u16.to_be_bytes()); tcp.extend_from_slice(&0u32.to_be_bytes()); tcp.extend_from_slice(&0u32.to_be_bytes()); tcp.push(0x70); tcp.push(0x02); tcp.extend_from_slice(&0u16.to_be_bytes()); tcp.extend_from_slice(&0u16.to_be_bytes()); tcp.extend_from_slice(&0u16.to_be_bytes()); tcp.extend_from_slice(&[0u8; 8]); tcp.extend_from_slice(b"payload");
let seg = extract_tcp(&tcp).unwrap();
assert_eq!(seg.src_port, 4321);
assert_eq!(seg.dst_port, 80);
assert_eq!(seg.payload, b"payload");
}
#[test]
fn rejects_truncated_udp() {
assert!(extract_udp(&[0u8; 4]).is_none());
}
#[test]
fn rejects_truncated_tcp() {
assert!(extract_tcp(&[0u8; 10]).is_none());
}
}