use std::net::Ipv4Addr;
pub const ETH_HEADER_LEN: usize = 14;
const ARP_FRAME_MIN_LEN: usize = ETH_HEADER_LEN + 28;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EtherType {
Ipv4,
Arp,
Ipv6,
Unknown(u16),
}
impl EtherType {
#[must_use]
pub fn from_raw(raw: u16) -> Self {
match raw {
0x0800 => Self::Ipv4,
0x0806 => Self::Arp,
0x86DD => Self::Ipv6,
other => Self::Unknown(other),
}
}
#[must_use]
pub fn to_raw(self) -> u16 {
match self {
Self::Ipv4 => 0x0800,
Self::Arp => 0x0806,
Self::Ipv6 => 0x86DD,
Self::Unknown(v) => v,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct EthernetHeader {
pub dst_mac: [u8; 6],
pub src_mac: [u8; 6],
pub ethertype: EtherType,
}
impl EthernetHeader {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < ETH_HEADER_LEN {
return None;
}
let mut dst_mac = [0u8; 6];
let mut src_mac = [0u8; 6];
dst_mac.copy_from_slice(&data[0..6]);
src_mac.copy_from_slice(&data[6..12]);
let raw_type = u16::from_be_bytes([data[12], data[13]]);
Some(Self {
dst_mac,
src_mac,
ethertype: EtherType::from_raw(raw_type),
})
}
#[must_use]
pub fn to_bytes(&self) -> [u8; ETH_HEADER_LEN] {
let mut buf = [0u8; ETH_HEADER_LEN];
buf[0..6].copy_from_slice(&self.dst_mac);
buf[6..12].copy_from_slice(&self.src_mac);
buf[12..14].copy_from_slice(&self.ethertype.to_raw().to_be_bytes());
buf
}
}
#[must_use]
pub fn strip_ethernet_header(frame: &[u8]) -> &[u8] {
if frame.len() <= ETH_HEADER_LEN {
return &[];
}
&frame[ETH_HEADER_LEN..]
}
#[must_use]
pub fn prepend_ethernet_header(ip_packet: &[u8], dst_mac: [u8; 6], src_mac: [u8; 6]) -> Vec<u8> {
let hdr = EthernetHeader {
dst_mac,
src_mac,
ethertype: EtherType::Ipv4,
};
let mut frame = Vec::with_capacity(ETH_HEADER_LEN + ip_packet.len());
frame.extend_from_slice(&hdr.to_bytes());
frame.extend_from_slice(ip_packet);
frame
}
pub struct ArpResponder {
gateway_ip: Ipv4Addr,
gateway_mac: [u8; 6],
}
impl ArpResponder {
#[must_use]
pub fn new(gateway_ip: Ipv4Addr, gateway_mac: [u8; 6]) -> Self {
Self {
gateway_ip,
gateway_mac,
}
}
#[must_use]
pub fn handle_arp(&self, frame: &[u8]) -> Option<Vec<u8>> {
if frame.len() < ARP_FRAME_MIN_LEN {
return None;
}
let arp = &frame[ETH_HEADER_LEN..];
if u16::from_be_bytes([arp[0], arp[1]]) != 1
|| u16::from_be_bytes([arp[2], arp[3]]) != 0x0800
{
return None;
}
if arp[4] != 6 || arp[5] != 4 || u16::from_be_bytes([arp[6], arp[7]]) != 1 {
return None;
}
let target_ip = Ipv4Addr::new(arp[24], arp[25], arp[26], arp[27]);
if target_ip != self.gateway_ip {
return None;
}
let mut sender_mac = [0u8; 6];
sender_mac.copy_from_slice(&arp[8..14]);
let sender_ip_bytes: [u8; 4] = [arp[14], arp[15], arp[16], arp[17]];
let mut reply = Vec::with_capacity(ARP_FRAME_MIN_LEN);
reply.extend_from_slice(&sender_mac);
reply.extend_from_slice(&self.gateway_mac);
reply.extend_from_slice(&0x0806u16.to_be_bytes());
reply.extend_from_slice(&1u16.to_be_bytes()); reply.extend_from_slice(&0x0800u16.to_be_bytes()); reply.push(6); reply.push(4); reply.extend_from_slice(&2u16.to_be_bytes()); reply.extend_from_slice(&self.gateway_mac); reply.extend_from_slice(&self.gateway_ip.octets()); reply.extend_from_slice(&sender_mac); reply.extend_from_slice(&sender_ip_bytes);
Some(reply)
}
}
#[must_use]
pub fn build_udp_ip_ethernet(
src_ip: Ipv4Addr,
dst_ip: Ipv4Addr,
src_port: u16,
dst_port: u16,
payload: &[u8],
src_mac: [u8; 6],
dst_mac: [u8; 6],
) -> Vec<u8> {
let udp_len = 8 + payload.len();
let ip_total_len = 20 + udp_len;
let frame_len = ETH_HEADER_LEN + ip_total_len;
let mut frame = vec![0u8; frame_len];
frame[0..6].copy_from_slice(&dst_mac);
frame[6..12].copy_from_slice(&src_mac);
frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
let ip = &mut frame[ETH_HEADER_LEN..];
ip[0] = 0x45; ip[2..4].copy_from_slice(&(ip_total_len as u16).to_be_bytes());
ip[8] = 64; ip[9] = 17; ip[12..16].copy_from_slice(&src_ip.octets());
ip[16..20].copy_from_slice(&dst_ip.octets());
let ip_cksum = ipv4_header_checksum(&ip[..20]);
ip[10..12].copy_from_slice(&ip_cksum.to_be_bytes());
let udp_start = ETH_HEADER_LEN + 20;
frame[udp_start..udp_start + 2].copy_from_slice(&src_port.to_be_bytes());
frame[udp_start + 2..udp_start + 4].copy_from_slice(&dst_port.to_be_bytes());
frame[udp_start + 4..udp_start + 6].copy_from_slice(&(udp_len as u16).to_be_bytes());
frame[udp_start + 6..udp_start + 8].copy_from_slice(&[0, 0]);
frame[udp_start + 8..].copy_from_slice(payload);
let udp_cksum = udp_checksum(src_ip, dst_ip, &frame[udp_start..]);
frame[udp_start + 6..udp_start + 8].copy_from_slice(&udp_cksum.to_be_bytes());
frame
}
pub fn ipv4_header_checksum(header: &[u8]) -> u16 {
let mut sum: u32 = 0;
let mut i = 0;
while i + 1 < header.len() {
if i != 10 {
sum += u32::from(u16::from_be_bytes([header[i], header[i + 1]]));
}
i += 2;
}
while sum > 0xFFFF {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!sum as u16
}
fn udp_checksum(src_ip: Ipv4Addr, dst_ip: Ipv4Addr, udp_segment: &[u8]) -> u16 {
let mut sum: u32 = 0;
let src = src_ip.octets();
let dst = dst_ip.octets();
sum += u32::from(u16::from_be_bytes([src[0], src[1]]));
sum += u32::from(u16::from_be_bytes([src[2], src[3]]));
sum += u32::from(u16::from_be_bytes([dst[0], dst[1]]));
sum += u32::from(u16::from_be_bytes([dst[2], dst[3]]));
sum += 17u32; sum += udp_segment.len() as u32;
let mut i = 0;
while i + 1 < udp_segment.len() {
if i != 6 {
sum += u32::from(u16::from_be_bytes([udp_segment[i], udp_segment[i + 1]]));
}
i += 2;
}
if i < udp_segment.len() {
sum += u32::from(udp_segment[i]) << 8;
}
while sum > 0xFFFF {
sum = (sum & 0xFFFF) + (sum >> 16);
}
let result = !sum as u16;
if result == 0 { 0xFFFF } else { result }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ethertype_roundtrip() {
for raw in [0x0800u16, 0x0806, 0x86DD, 0x1234] {
assert_eq!(EtherType::from_raw(raw).to_raw(), raw);
}
}
#[test]
fn test_ethernet_header_parse_roundtrip() {
let hdr = EthernetHeader {
dst_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
src_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
ethertype: EtherType::Ipv4,
};
let bytes = hdr.to_bytes();
let parsed = EthernetHeader::parse(&bytes).unwrap();
assert_eq!(parsed.dst_mac, hdr.dst_mac);
assert_eq!(parsed.src_mac, hdr.src_mac);
assert_eq!(parsed.ethertype, hdr.ethertype);
}
#[test]
fn test_parse_too_short() {
assert!(EthernetHeader::parse(&[0; 13]).is_none());
assert!(EthernetHeader::parse(&[]).is_none());
}
#[test]
fn test_strip_ethernet_header() {
let mut frame = vec![0u8; 20];
frame[14] = 0xAB;
let payload = strip_ethernet_header(&frame);
assert_eq!(payload.len(), 6);
assert_eq!(payload[0], 0xAB);
assert!(strip_ethernet_header(&[0; 10]).is_empty());
}
#[test]
fn test_prepend_ethernet_header_roundtrip() {
let ip_data = [0x45, 0x00, 0x00, 0x28]; let dst = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
let src = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
let frame = prepend_ethernet_header(&ip_data, dst, src);
assert_eq!(frame.len(), ETH_HEADER_LEN + ip_data.len());
let hdr = EthernetHeader::parse(&frame).unwrap();
assert_eq!(hdr.dst_mac, dst);
assert_eq!(hdr.src_mac, src);
assert_eq!(hdr.ethertype, EtherType::Ipv4);
assert_eq!(strip_ethernet_header(&frame), &ip_data);
}
#[test]
fn test_arp_responder_reply() {
let gw_ip = Ipv4Addr::new(192, 168, 64, 1);
let gw_mac = [0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0x01];
let responder = ArpResponder::new(gw_ip, gw_mac);
let sender_mac = [0x02, 0x00, 0x00, 0x00, 0x00, 0x99];
let sender_ip = [192, 168, 64, 100];
let target_ip = [192, 168, 64, 1];
let mut frame = vec![0u8; ARP_FRAME_MIN_LEN];
frame[0..6].copy_from_slice(&[0xFF; 6]); frame[6..12].copy_from_slice(&sender_mac);
frame[12..14].copy_from_slice(&0x0806u16.to_be_bytes());
let arp = &mut frame[ETH_HEADER_LEN..];
arp[0..2].copy_from_slice(&1u16.to_be_bytes()); arp[2..4].copy_from_slice(&0x0800u16.to_be_bytes()); arp[4] = 6; arp[5] = 4; arp[6..8].copy_from_slice(&1u16.to_be_bytes()); arp[8..14].copy_from_slice(&sender_mac);
arp[14..18].copy_from_slice(&sender_ip);
arp[24..28].copy_from_slice(&target_ip);
let reply = responder.handle_arp(&frame).expect("Expected ARP reply");
assert_eq!(&reply[0..6], &sender_mac); assert_eq!(&reply[6..12], &gw_mac); assert_eq!(u16::from_be_bytes([reply[12], reply[13]]), 0x0806);
let rarp = &reply[ETH_HEADER_LEN..];
assert_eq!(u16::from_be_bytes([rarp[6], rarp[7]]), 2); assert_eq!(&rarp[8..14], &gw_mac); assert_eq!(&rarp[14..18], &target_ip); assert_eq!(&rarp[18..24], &sender_mac); assert_eq!(&rarp[24..28], &sender_ip); }
#[test]
fn test_arp_responder_ignores_wrong_target() {
let gw_ip = Ipv4Addr::new(192, 168, 64, 1);
let gw_mac = [0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0x01];
let responder = ArpResponder::new(gw_ip, gw_mac);
let mut frame = vec![0u8; ARP_FRAME_MIN_LEN];
frame[12..14].copy_from_slice(&0x0806u16.to_be_bytes());
let arp = &mut frame[ETH_HEADER_LEN..];
arp[0..2].copy_from_slice(&1u16.to_be_bytes());
arp[2..4].copy_from_slice(&0x0800u16.to_be_bytes());
arp[4] = 6;
arp[5] = 4;
arp[6..8].copy_from_slice(&1u16.to_be_bytes());
arp[24..28].copy_from_slice(&[192, 168, 64, 99]);
assert!(responder.handle_arp(&frame).is_none());
}
#[test]
fn test_build_udp_ip_ethernet_checksum() {
let src_ip = Ipv4Addr::new(192, 168, 64, 1);
let dst_ip = Ipv4Addr::new(192, 168, 64, 2);
let src_mac = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
let dst_mac = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
let payload = b"hello";
let frame = build_udp_ip_ethernet(src_ip, dst_ip, 1234, 5678, payload, src_mac, dst_mac);
let hdr = EthernetHeader::parse(&frame).unwrap();
assert_eq!(hdr.ethertype, EtherType::Ipv4);
let ip = &frame[ETH_HEADER_LEN..];
assert_eq!(ip[0], 0x45);
assert_eq!(ip[9], 17); let ip_total = u16::from_be_bytes([ip[2], ip[3]]) as usize;
assert_eq!(ip_total, 20 + 8 + payload.len());
let mut sum: u32 = 0;
for i in (0..20).step_by(2) {
sum += u32::from(u16::from_be_bytes([ip[i], ip[i + 1]]));
}
while sum > 0xFFFF {
sum = (sum & 0xFFFF) + (sum >> 16);
}
assert_eq!(sum as u16, 0xFFFF, "IP header checksum verification failed");
let udp = &frame[ETH_HEADER_LEN + 20..];
assert_eq!(u16::from_be_bytes([udp[0], udp[1]]), 1234);
assert_eq!(u16::from_be_bytes([udp[2], udp[3]]), 5678);
let udp_len = u16::from_be_bytes([udp[4], udp[5]]) as usize;
assert_eq!(udp_len, 8 + payload.len());
let udp_cksum = u16::from_be_bytes([udp[6], udp[7]]);
assert_ne!(udp_cksum, 0);
}
}