//! Bounded RFC 792 echo-request to echo-reply conversion for one Ethernet //! fixture. This is not yet a general IP stack or proof of an OS ping command. use crate::{net_ipv4, net_mac}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { WrongEthernet, InvalidSourceMac, Ipv4Header, UnsupportedIhl, WrongIpv4Length, Fragmented, WrongProtocol, WrongTarget, ExpiredTtl, WrongIcmpTypeCode, WrongIcmpChecksum, NonzeroPadding, } fn checksum(bytes: &[u8]) -> u16 { let mut sum = 0u32; let mut words = bytes.chunks_exact(2); for pair in &mut words { sum += u32::from(u16::from_be_bytes([pair[0], pair[1]])); } if let Some(&last) = words.remainder().first() { sum += u32::from(last) << 8; } while sum > 0xffff { sum = (sum & 0xffff) + (sum >> 16); } !(sum as u16) } /// Accept one 60-byte Ethernet frame with a 20-byte IPv4 header, 16-byte /// ICMP echo request and zero Ethernet padding. Recompute both checksums. pub fn build_echo_reply( request: &[u8; 60], own_mac: [u8; 6], own_ipv4: [u8; 4], ) -> Result<[u8; 60], Error> { if request[..6] != own_mac || request[12..14] != [0x08, 0x00] { return Err(Error::WrongEthernet); } let source_mac: [u8; 6] = request[6..12].try_into().unwrap(); if net_mac::validate(net_mac::VIRTIO_NET_F_MAC, source_mac).is_err() { return Err(Error::InvalidSourceMac); } let ip = &request[14..]; let geometry = net_ipv4::verify_header_checksum(ip).map_err(|_| Error::Ipv4Header)?; if geometry.header_bytes != 20 { return Err(Error::UnsupportedIhl); } if geometry.total_bytes != 36 { return Err(Error::WrongIpv4Length); } if ip[6..8] != [0, 0] { return Err(Error::Fragmented); } if ip[8] == 0 { return Err(Error::ExpiredTtl); } if ip[9] != 1 { return Err(Error::WrongProtocol); } if ip[16..20] != own_ipv4 { return Err(Error::WrongTarget); } if request[34..36] != [8, 0] { return Err(Error::WrongIcmpTypeCode); } if checksum(&request[34..50]) != 0 { return Err(Error::WrongIcmpChecksum); } if request[50..].iter().any(|byte| *byte != 0) { return Err(Error::NonzeroPadding); } let mut reply = *request; reply[..6].copy_from_slice(&source_mac); reply[6..12].copy_from_slice(&own_mac); reply[26..30].copy_from_slice(&own_ipv4); reply[30..34].copy_from_slice(&request[26..30]); reply[22] = 64; reply[24..26].fill(0); let ip_checksum = checksum(&reply[14..34]); reply[24..26].copy_from_slice(&ip_checksum.to_be_bytes()); reply[34] = 0; reply[36..38].fill(0); let icmp_checksum = checksum(&reply[34..50]); reply[36..38].copy_from_slice(&icmp_checksum.to_be_bytes()); Ok(reply) } #[cfg(test)] mod tests { use super::*; fn known() -> [u8; 60] { let mut frame = [0u8; 60]; frame[..14].copy_from_slice(&[ 0x52, 0x54, 0, 0x12, 0x34, 0x56, 0x02, 0, 0, 0, 0, 1, 0x08, 0, ]); frame[14..34].copy_from_slice(&[ 0x45, 0, 0, 36, 0, 7, 0, 0, 64, 1, 0, 0, 192, 0, 2, 1, 192, 0, 2, 2, ]); let ip_sum = checksum(&frame[14..34]); frame[24..26].copy_from_slice(&ip_sum.to_be_bytes()); frame[34..50].copy_from_slice(&[ 8, 0, 0, 0, 0x12, 0x34, 0, 1, b'T', b'U', b'R', b'K', b'O', b'S', b'!', b'?', ]); let icmp_sum = checksum(&frame[34..50]); frame[36..38].copy_from_slice(&icmp_sum.to_be_bytes()); frame } #[test] fn k437_echo_reply_swaps_addresses_and_preserves_identifier_sequence_payload() { let request = known(); let own = [0x52, 0x54, 0, 0x12, 0x34, 0x56]; let reply = build_echo_reply(&request, own, [192, 0, 2, 2]).unwrap(); assert_eq!(&reply[..6], &request[6..12]); assert_eq!(&reply[6..12], &own); assert_eq!(&reply[26..30], &[192, 0, 2, 2]); assert_eq!(&reply[30..34], &[192, 0, 2, 1]); assert_eq!(&reply[34..36], &[0, 0]); assert_eq!(&reply[38..50], &request[38..50]); assert_eq!(checksum(&reply[14..34]), 0); assert_eq!(checksum(&reply[34..50]), 0); assert_eq!(&reply[50..], &[0; 10]); } #[test] fn k437_echo_rejects_wrong_target_checksums_protocol_and_fragment() { let base = known(); let own = [0x52, 0x54, 0, 0x12, 0x34, 0x56]; let mut frame = base; frame[30] ^= 1; assert_eq!( build_echo_reply(&frame, own, [192, 0, 2, 2]), Err(Error::Ipv4Header) ); frame = base; frame[30] ^= 1; frame[24..26].fill(0); let sum = checksum(&frame[14..34]); frame[24..26].copy_from_slice(&sum.to_be_bytes()); assert_eq!( build_echo_reply(&frame, own, [192, 0, 2, 2]), Err(Error::WrongTarget) ); frame = base; frame[42] ^= 1; assert_eq!( build_echo_reply(&frame, own, [192, 0, 2, 2]), Err(Error::WrongIcmpChecksum) ); frame = base; frame[23] = 17; frame[24..26].fill(0); let sum = checksum(&frame[14..34]); frame[24..26].copy_from_slice(&sum.to_be_bytes()); assert_eq!( build_echo_reply(&frame, own, [192, 0, 2, 2]), Err(Error::WrongProtocol) ); frame = base; frame[21] = 1; frame[24..26].fill(0); let sum = checksum(&frame[14..34]); frame[24..26].copy_from_slice(&sum.to_be_bytes()); assert_eq!( build_echo_reply(&frame, own, [192, 0, 2, 2]), Err(Error::Fragmented) ); } }