diff --git a/src/nat/error.rs b/src/nat/error.rs deleted file mode 100644 index d90751a..0000000 --- a/src/nat/error.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[derive(Debug, thiserror::Error)] -pub enum Nat64Error { - #[error(transparent)] - Table(#[from] super::table::TableError), - #[error(transparent)] - Tun(#[from] protomask_tun::Error), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - PacketHandling(#[from] crate::packet::error::PacketError), - #[error(transparent)] - PacketReceive(#[from] tokio::sync::broadcast::error::RecvError), - #[error(transparent)] - PacketSend(#[from] tokio::sync::mpsc::error::SendError>), -} diff --git a/src/nat/mod.rs b/src/nat/mod.rs deleted file mode 100644 index 930b363..0000000 --- a/src/nat/mod.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::{ - metrics::PACKET_COUNTER, - packet::{ - protocols::{ipv4::Ipv4Packet, ipv6::Ipv6Packet}, - xlat::ip::{translate_ipv4_to_ipv6, translate_ipv6_to_ipv4}, - }, -}; - -use self::{ - error::Nat64Error, - table::Nat64Table, - utils::{embed_address, extract_address, unwrap_log}, -}; -use ipnet::{Ipv4Net, Ipv6Net}; -use protomask_tun::TunDevice; -use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - time::Duration, -}; -use tokio::sync::broadcast; - -mod error; -mod table; -mod utils; - -pub struct Nat64 { - table: Nat64Table, - interface: TunDevice, - ipv6_nat_prefix: Ipv6Net, -} - -impl Nat64 { - /// Construct a new NAT64 instance - pub async fn new( - ipv6_nat_prefix: Ipv6Net, - ipv4_pool: Vec, - static_reservations: Vec<(Ipv6Addr, Ipv4Addr)>, - reservation_duration: Duration, - ) -> Result { - // Bring up the interface - let mut interface = TunDevice::new("nat64i%d").await?; - - // Add the NAT64 prefix as a route - interface.add_route(ipv6_nat_prefix.into()).await?; - - // Add the IPv4 pool prefixes as routes - for ipv4_prefix in &ipv4_pool { - interface.add_route((*ipv4_prefix).into()).await?; - } - - // Build the table and insert any static reservations - let mut table = Nat64Table::new(ipv4_pool, reservation_duration); - for (v6, v4) in static_reservations { - table.add_infinite_reservation(v6, v4)?; - } - - Ok(Self { - table, - interface, - ipv6_nat_prefix, - }) - } - - /// Block and process all packets - pub async fn run(&mut self) -> Result<(), Nat64Error> { - // Get an rx/tx pair for the interface - let (tx, mut rx) = self.interface.spawn_worker().await; - - // Process packets in a loop - loop { - // Try to read a packet - match rx.recv().await { - Ok(packet) => { - // Clone the TX so the worker can respond with data - let tx = tx.clone(); - - // Separate logic is needed for handling IPv4 vs IPv6 packets, so a check must be done here - match packet[0] >> 4 { - 4 => { - // Parse the packet - let packet: Ipv4Packet> = packet.try_into()?; - - // Drop packets that aren't destined for a destination the table knows about - if !self.table.contains(&IpAddr::V4(packet.destination_address)) { - PACKET_COUNTER.with_label_values(&["ipv4", "dropped"]).inc(); - continue; - } - - // Get the new source and dest addresses - let new_source = - embed_address(packet.source_address, self.ipv6_nat_prefix); - let new_destination = - self.table.get_reverse(packet.destination_address)?; - - // Mark the packet as accepted - PACKET_COUNTER - .with_label_values(&["ipv4", "accepted"]) - .inc(); - - // Spawn a task to process the packet - tokio::spawn(async move { - if let Some(output) = unwrap_log(translate_ipv4_to_ipv6( - packet, - new_source, - new_destination, - )) { - tx.send(output.into()).await.unwrap(); - PACKET_COUNTER.with_label_values(&["ipv6", "sent"]).inc(); - } - }); - } - 6 => { - // Parse the packet - let packet: Ipv6Packet> = packet.try_into()?; - - // Drop packets "coming from" the NAT64 prefix - if self.ipv6_nat_prefix.contains(&packet.source_address) { - log::warn!( - "Dropping packet \"from\" NAT64 prefix: {} -> {}", - packet.source_address, - packet.destination_address - ); - PACKET_COUNTER.with_label_values(&["ipv6", "dropped"]).inc(); - continue; - } - - // Get the new source and dest addresses - let new_source = - self.table.get_or_assign_ipv4(packet.source_address)?; - let new_destination = extract_address(packet.destination_address); - - // Drop packets destined for private IPv4 addresses - if new_destination.is_private() { - log::warn!( - "Dropping packet destined for private IPv4 address: {} -> {} ({})", - packet.source_address, - packet.destination_address, - new_destination - ); - PACKET_COUNTER.with_label_values(&["ipv6", "dropped"]).inc(); - continue; - } - - // Mark the packet as accepted - PACKET_COUNTER - .with_label_values(&["ipv6", "accepted"]) - .inc(); - - // Spawn a task to process the packet - tokio::spawn(async move { - if let Some(output) = unwrap_log(translate_ipv6_to_ipv4( - &packet, - new_source, - new_destination, - )) { - tx.send(output.into()).await.unwrap(); - PACKET_COUNTER.with_label_values(&["ipv4", "sent"]).inc(); - } - }); - } - n => { - log::warn!("Unknown IP version: {}", n); - } - } - Ok(()) - } - Err(error) => match error { - broadcast::error::RecvError::Lagged(count) => { - log::warn!("Translator running behind! Dropping {} packets", count); - Ok(()) - } - error @ broadcast::error::RecvError::Closed => Err(error), - }, - }?; - } - } -} diff --git a/src/nat/table.rs b/src/nat/table.rs deleted file mode 100644 index e7e3693..0000000 --- a/src/nat/table.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::{ - collections::HashMap, - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - time::{Duration, Instant}, -}; - -use bimap::BiHashMap; -use ipnet::Ipv4Net; - -use crate::metrics::{IPV4_POOL_RESERVED, IPV4_POOL_SIZE}; - -/// Possible errors thrown in the address reservation process -#[derive(Debug, thiserror::Error)] -pub enum TableError { - #[error("Address already reserved: {0}")] - AddressAlreadyReserved(IpAddr), - #[error("IPv4 address has no IPv6 mapping: {0}")] - NoIpv6Mapping(Ipv4Addr), - #[error("Address pool depleted")] - AddressPoolDepleted, -} - -/// A NAT address table -#[derive(Debug)] -pub struct Nat64Table { - /// All possible IPv4 addresses that can be used - ipv4_pool: Vec, - /// Current reservations - reservations: BiHashMap, - /// The timestamp of each reservation (used for pruning) - reservation_times: HashMap<(Ipv6Addr, Ipv4Addr), Option>, - /// The maximum amount of time to reserve an address pair for - reservation_timeout: Duration, -} - -impl Nat64Table { - /// Construct a new NAT64 table - /// - /// **Arguments:** - /// - `ipv4_pool`: The pool of IPv4 addresses to use in the mapping process - /// - `reservation_timeout`: The amount of time to reserve an address pair for - pub fn new(ipv4_pool: Vec, reservation_timeout: Duration) -> Self { - // Track the total pool size - let total_size: usize = ipv4_pool.iter().map(|net| net.hosts().count()).sum(); - IPV4_POOL_SIZE.set(total_size as i64); - - Self { - ipv4_pool, - reservations: BiHashMap::new(), - reservation_times: HashMap::new(), - reservation_timeout, - } - } - - /// Make a reservation for an IP address pair for eternity - pub fn add_infinite_reservation( - &mut self, - ipv6: Ipv6Addr, - ipv4: Ipv4Addr, - ) -> Result<(), TableError> { - // Check if either address is already reserved - self.prune(); - self.track_utilization(); - if self.reservations.contains_left(&ipv6) { - return Err(TableError::AddressAlreadyReserved(ipv6.into())); - } else if self.reservations.contains_right(&ipv4) { - return Err(TableError::AddressAlreadyReserved(ipv4.into())); - } - - // Add the reservation - self.reservations.insert(ipv6, ipv4); - self.reservation_times.insert((ipv6, ipv4), None); - log::info!("Added infinite reservation: {} -> {}", ipv6, ipv4); - Ok(()) - } - - /// Check if a given address exists in the table - pub fn contains(&self, address: &IpAddr) -> bool { - match address { - IpAddr::V4(ipv4) => self.reservations.contains_right(ipv4), - IpAddr::V6(ipv6) => self.reservations.contains_left(ipv6), - } - } - - /// Get or assign an IPv4 address for the given IPv6 address - pub fn get_or_assign_ipv4(&mut self, ipv6: Ipv6Addr) -> Result { - // Prune old reservations - self.prune(); - self.track_utilization(); - - // If the IPv6 address is already reserved, return the IPv4 address - if let Some(ipv4) = self.reservations.get_by_left(&ipv6) { - // Update the reservation time - self.reservation_times - .insert((ipv6, *ipv4), Some(Instant::now())); - - // Return the v4 address - return Ok(*ipv4); - } - - // Otherwise, try to assign a new IPv4 address - for ipv4_net in &self.ipv4_pool { - for ipv4 in ipv4_net.hosts() { - // Check if this address is available for use - if !self.reservations.contains_right(&ipv4) { - // Add the reservation - self.reservations.insert(ipv6, ipv4); - self.reservation_times - .insert((ipv6, ipv4), Some(Instant::now())); - log::info!("Assigned new reservation: {} -> {}", ipv6, ipv4); - return Ok(ipv4); - } - } - } - - // If we get here, we failed to find an available address - Err(TableError::AddressPoolDepleted) - } - - /// Try to find an IPv6 address for the given IPv4 address - pub fn get_reverse(&mut self, ipv4: Ipv4Addr) -> Result { - // Prune old reservations - self.prune(); - self.track_utilization(); - - // If the IPv4 address is already reserved, return the IPv6 address - if let Some(ipv6) = self.reservations.get_by_right(&ipv4) { - // Update the reservation time - self.reservation_times - .insert((*ipv6, ipv4), Some(Instant::now())); - - // Return the v6 address - return Ok(*ipv6); - } - - // Otherwise, there is no matching reservation - Err(TableError::NoIpv6Mapping(ipv4)) - } -} - -impl Nat64Table { - /// Prune old reservations - fn prune(&mut self) { - let now = Instant::now(); - - // Prune from the reservation map - self.reservations.retain(|v6, v4| { - if let Some(Some(time)) = self.reservation_times.get(&(*v6, *v4)) { - let keep = now - *time < self.reservation_timeout; - if !keep { - log::info!("Pruned reservation: {} -> {}", v6, v4); - } - keep - } else { - true - } - }); - - // Remove all times assigned to reservations that no longer exist - self.reservation_times.retain(|(v6, v4), _| { - self.reservations.contains_left(v6) && self.reservations.contains_right(v4) - }); - } - - fn track_utilization(&self) { - // Count static and dynamic in a single pass - let (total_dynamic_reservations, total_static_reservations) = self - .reservation_times - .iter() - .map(|((_v6, _v4), time)| match time { - Some(_) => (1, 0), - None => (0, 1), - }) - .fold((0, 0), |(a1, a2), (b1, b2)| (a1 + b1, a2 + b2)); - - // Track the values - IPV4_POOL_RESERVED - .with_label_values(&["dynamic"]) - .set(i64::from(total_dynamic_reservations)); - IPV4_POOL_RESERVED - .with_label_values(&["static"]) - .set(i64::from(total_static_reservations)); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_add_infinite_reservation() { - let mut table = Nat64Table::new( - vec![Ipv4Net::new(Ipv4Addr::new(192, 0, 2, 0), 24).unwrap()], - Duration::from_secs(60), - ); - - // Add a reservation - table - .add_infinite_reservation("2001:db8::1".parse().unwrap(), "192.0.2.1".parse().unwrap()) - .unwrap(); - - // Check that it worked - assert_eq!( - table - .reservations - .get_by_left(&"2001:db8::1".parse().unwrap()), - Some(&"192.0.2.1".parse().unwrap()) - ); - } -} diff --git a/src/nat/utils.rs b/src/nat/utils.rs deleted file mode 100644 index 44d1f18..0000000 --- a/src/nat/utils.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::net::{Ipv4Addr, Ipv6Addr}; - -use ipnet::Ipv6Net; - -use crate::packet::error::PacketError; - -/// Embed an IPv4 address in an IPv6 prefix -pub fn embed_address(ipv4_address: Ipv4Addr, ipv6_prefix: Ipv6Net) -> Ipv6Addr { - let v4_octets = ipv4_address.octets(); - let v6_octets = ipv6_prefix.addr().octets(); - Ipv6Addr::new( - u16::from_be_bytes([v6_octets[0], v6_octets[1]]), - u16::from_be_bytes([v6_octets[2], v6_octets[3]]), - u16::from_be_bytes([v6_octets[4], v6_octets[5]]), - u16::from_be_bytes([v6_octets[6], v6_octets[7]]), - u16::from_be_bytes([v6_octets[8], v6_octets[9]]), - u16::from_be_bytes([v6_octets[10], v6_octets[11]]), - u16::from_be_bytes([v4_octets[0], v4_octets[1]]), - u16::from_be_bytes([v4_octets[2], v4_octets[3]]), - ) -} - -/// Extract an IPv4 address from an IPv6 address -pub fn extract_address(ipv6_address: Ipv6Addr) -> Ipv4Addr { - let octets = ipv6_address.octets(); - Ipv4Addr::new(octets[12], octets[13], octets[14], octets[15]) -} - -/// Logs errors instead of crashing out of them -pub fn unwrap_log(result: Result) -> Option { - match result { - Ok(value) => Some(value), - Err(err) => match err { - PacketError::MismatchedAddressFamily(addr_a, addr_b) => { - log::error!( - "Mismatched address family between {} and {}", - addr_a, - addr_b - ); - None - } - PacketError::TooShort(len, data) => { - log::warn!("Received packet that's too short to parse. Length {}", len); - log::debug!("Short packet: {:?}", data); - None - } - PacketError::UnsupportedIcmpType(icmp_type) => { - log::warn!("Unsupported ICMP type {}", icmp_type); - None - } - PacketError::UnsupportedIcmpv6Type(icmp_type) => { - log::warn!("Unsupported ICMPv6 type {}", icmp_type); - None - } - }, - } -} diff --git a/src/packet/error.rs b/src/packet/error.rs deleted file mode 100644 index ec2ffe9..0000000 --- a/src/packet/error.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::net::IpAddr; - -#[derive(Debug, thiserror::Error)] -pub enum PacketError { - #[error("Mismatched source and destination address family: source={0:?}, destination={1:?}")] - MismatchedAddressFamily(IpAddr, IpAddr), - #[error("Packet too short: {0}")] - TooShort(usize, Vec), - #[error("Unsupported ICMP type: {0}")] - UnsupportedIcmpType(u8), - #[error("Unsupported ICMPv6 type: {0}")] - UnsupportedIcmpv6Type(u8), -} - diff --git a/src/packet/mod.rs b/src/packet/mod.rs deleted file mode 100644 index a40cbd5..0000000 --- a/src/packet/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Custom packet modification utilities -//! -//! pnet isn't quite what we need for this project, so the contents of this module wrap it to make it easier to use. - -pub mod error; -pub mod protocols; -pub mod xlat; diff --git a/src/packet/protocols/icmp.rs b/src/packet/protocols/icmp.rs deleted file mode 100644 index 53e4a2a..0000000 --- a/src/packet/protocols/icmp.rs +++ /dev/null @@ -1,106 +0,0 @@ -use pnet_packet::{ - icmp::{IcmpCode, IcmpType}, - Packet, -}; - -use crate::packet::error::PacketError; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct IcmpPacket { - pub icmp_type: IcmpType, - pub icmp_code: IcmpCode, - pub payload: T, -} - -impl IcmpPacket { - /// Construct a new `ICMP` packet - pub fn new(icmp_type: IcmpType, icmp_code: IcmpCode, payload: T) -> Self { - Self { - icmp_type, - icmp_code, - payload, - } - } -} - -impl TryFrom> for IcmpPacket -where - T: TryFrom, Error = PacketError>, -{ - type Error = PacketError; - - fn try_from(bytes: Vec) -> Result { - // Parse the packet - let packet = pnet_packet::icmp::IcmpPacket::new(&bytes) - .ok_or(PacketError::TooShort(bytes.len(), bytes.clone()))?; - - // Return the packet - Ok(Self { - icmp_type: packet.get_icmp_type(), - icmp_code: packet.get_icmp_code(), - payload: packet.payload().to_vec().try_into()?, - }) - } -} - -impl From> for Vec -where - T: Into>, -{ - fn from(packet: IcmpPacket) -> Self { - // Convert the payload into raw bytes - let payload: Vec = packet.payload.into(); - - // Allocate a mutable packet to write into - let total_length = - pnet_packet::icmp::MutableIcmpPacket::minimum_packet_size() + payload.len(); - let mut output = - pnet_packet::icmp::MutableIcmpPacket::owned(vec![0u8; total_length]).unwrap(); - - // Write the type and code - output.set_icmp_type(packet.icmp_type); - output.set_icmp_code(packet.icmp_code); - - // Write the payload - output.set_payload(&payload); - - // Calculate the checksum - output.set_checksum(0); - output.set_checksum(pnet_packet::icmp::checksum(&output.to_immutable())); - - // Return the raw bytes - output.packet().to_vec() - } -} - -#[cfg(test)] -mod tests { - use pnet_packet::icmp::IcmpTypes; - - use super::*; - - // Test packet construction - #[test] - #[rustfmt::skip] - fn test_packet_construction() { - // Make a new packet - let packet = IcmpPacket::new( - IcmpTypes::EchoRequest, - IcmpCode(0), - "Hello, world!".as_bytes().to_vec(), - ); - - // Convert to raw bytes - let packet_bytes: Vec = packet.into(); - - // Check the contents - assert!(packet_bytes.len() >= 4 + 13); - assert_eq!(packet_bytes[0], IcmpTypes::EchoRequest.0); - assert_eq!(packet_bytes[1], 0); - assert_eq!(u16::from_be_bytes([packet_bytes[2], packet_bytes[3]]), 0xb6b3); - assert_eq!( - &packet_bytes[4..], - "Hello, world!".as_bytes().to_vec().as_slice() - ); - } -} diff --git a/src/packet/protocols/icmpv6.rs b/src/packet/protocols/icmpv6.rs deleted file mode 100644 index d320e93..0000000 --- a/src/packet/protocols/icmpv6.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::net::Ipv6Addr; - -use pnet_packet::{ - icmpv6::{Icmpv6Code, Icmpv6Type}, - Packet, -}; - -use crate::packet::error::PacketError; - -use super::raw::RawBytes; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Icmpv6Packet { - pub source_address: Ipv6Addr, - pub destination_address: Ipv6Addr, - pub icmp_type: Icmpv6Type, - pub icmp_code: Icmpv6Code, - pub payload: T, -} - -impl Icmpv6Packet { - /// Construct a new `ICMPv6` packet - pub fn new( - source_address: Ipv6Addr, - destination_address: Ipv6Addr, - icmp_type: Icmpv6Type, - icmp_code: Icmpv6Code, - payload: T, - ) -> Self { - Self { - source_address, - destination_address, - icmp_type, - icmp_code, - payload, - } - } -} - -impl Icmpv6Packet -where - T: From>, -{ - /// Construct a new `ICMPv6` packet from raw bytes - #[allow(dead_code)] - pub fn new_from_bytes( - bytes: &[u8], - source_address: Ipv6Addr, - destination_address: Ipv6Addr, - ) -> Result { - // Parse the packet - let packet = pnet_packet::icmpv6::Icmpv6Packet::new(bytes) - .ok_or(PacketError::TooShort(bytes.len(), bytes.to_vec()))?; - - // Return the packet - Ok(Self { - source_address, - destination_address, - icmp_type: packet.get_icmpv6_type(), - icmp_code: packet.get_icmpv6_code(), - payload: packet.payload().to_vec().into(), - }) - } -} - -impl Icmpv6Packet { - /// Construct a new `ICMPv6` packet with a raw payload from raw bytes - pub fn new_from_bytes_raw_payload( - bytes: &[u8], - source_address: Ipv6Addr, - destination_address: Ipv6Addr, - ) -> Result { - // Parse the packet - let packet = pnet_packet::icmpv6::Icmpv6Packet::new(bytes) - .ok_or(PacketError::TooShort(bytes.len(), bytes.to_vec()))?; - - // Return the packet - Ok(Self { - source_address, - destination_address, - icmp_type: packet.get_icmpv6_type(), - icmp_code: packet.get_icmpv6_code(), - payload: RawBytes(packet.payload().to_vec()), - }) - } -} - -impl From> for Vec -where - T: Into>, -{ - fn from(packet: Icmpv6Packet) -> Self { - // Convert the payload into raw bytes - let payload: Vec = packet.payload.into(); - - // Allocate a mutable packet to write into - let total_length = - pnet_packet::icmpv6::MutableIcmpv6Packet::minimum_packet_size() + payload.len(); - let mut output = - pnet_packet::icmpv6::MutableIcmpv6Packet::owned(vec![0u8; total_length]).unwrap(); - - // Write the type and code - output.set_icmpv6_type(packet.icmp_type); - output.set_icmpv6_code(packet.icmp_code); - - // Write the payload - output.set_payload(&payload); - - // Calculate the checksum - output.set_checksum(0); - output.set_checksum(pnet_packet::icmpv6::checksum( - &output.to_immutable(), - &packet.source_address, - &packet.destination_address, - )); - - // Return the raw bytes - output.packet().to_vec() - } -} - -#[cfg(test)] -mod tests { - use pnet_packet::icmpv6::Icmpv6Types; - - use super::*; - - // Test packet construction - #[test] - #[rustfmt::skip] - fn test_packet_construction() { - // Make a new packet - let packet = Icmpv6Packet::new( - "2001:db8:1::1".parse().unwrap(), - "2001:db8:1::2".parse().unwrap(), - Icmpv6Types::EchoRequest, - Icmpv6Code(0), - "Hello, world!".as_bytes().to_vec(), - ); - - // Convert to raw bytes - let packet_bytes: Vec = packet.into(); - - // Check the contents - assert!(packet_bytes.len() >= 4 + 13); - assert_eq!(packet_bytes[0], Icmpv6Types::EchoRequest.0); - assert_eq!(packet_bytes[1], 0); - assert_eq!(u16::from_be_bytes([packet_bytes[2], packet_bytes[3]]), 0xe2f0); - assert_eq!( - &packet_bytes[4..], - "Hello, world!".as_bytes().to_vec().as_slice() - ); - } -} diff --git a/src/packet/protocols/ipv4.rs b/src/packet/protocols/ipv4.rs deleted file mode 100644 index 6b17f6b..0000000 --- a/src/packet/protocols/ipv4.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::net::Ipv4Addr; - -use pnet_packet::{ - ip::IpNextHeaderProtocol, - ipv4::{Ipv4Option, Ipv4OptionPacket}, - Packet, -}; - -use crate::packet::error::PacketError; - -#[derive(Debug, Clone)] -pub struct Ipv4Packet { - pub dscp: u8, - pub ecn: u8, - pub identification: u16, - pub flags: u8, - pub fragment_offset: u16, - pub ttl: u8, - pub protocol: IpNextHeaderProtocol, - pub source_address: Ipv4Addr, - pub destination_address: Ipv4Addr, - pub options: Vec, - pub payload: T, -} - -impl Ipv4Packet { - /// Construct a new IPv4 packet - #[allow(clippy::too_many_arguments)] - pub fn new( - dscp: u8, - ecn: u8, - identification: u16, - flags: u8, - fragment_offset: u16, - ttl: u8, - protocol: IpNextHeaderProtocol, - source_address: Ipv4Addr, - destination_address: Ipv4Addr, - options: Vec, - payload: T, - ) -> Self { - Self { - dscp, - ecn, - identification, - flags, - fragment_offset, - ttl, - protocol, - source_address, - destination_address, - options, - payload, - } - } - - #[allow(clippy::cast_possible_truncation)] - fn options_length_words(&self) -> u8 { - self.options - .iter() - .map(|option| Ipv4OptionPacket::packet_size(option) as u8) - .sum::() - / 4 - } -} - -impl TryFrom> for Ipv4Packet -where - T: From>, -{ - type Error = PacketError; - - fn try_from(bytes: Vec) -> Result { - // Parse the packet - let packet = pnet_packet::ipv4::Ipv4Packet::new(&bytes) - .ok_or(PacketError::TooShort(bytes.len(), bytes.clone()))?; - - // Return the packet - Ok(Self { - dscp: packet.get_dscp(), - ecn: packet.get_ecn(), - identification: packet.get_identification(), - flags: packet.get_flags(), - fragment_offset: packet.get_fragment_offset(), - ttl: packet.get_ttl(), - protocol: packet.get_next_level_protocol(), - source_address: packet.get_source(), - destination_address: packet.get_destination(), - options: packet.get_options(), - payload: packet.payload().to_vec().into(), - }) - } -} - -impl From> for Vec -where - T: Into> + Clone, -{ - fn from(packet: Ipv4Packet) -> Self { - // Convert the payload into raw bytes - let payload: Vec = packet.payload.clone().into(); - - // Build the packet - let total_length = 20 + (packet.options_length_words() as usize * 4) + payload.len(); - let mut output = - pnet_packet::ipv4::MutableIpv4Packet::owned(vec![0u8; total_length]).unwrap(); - - // Set the fields - output.set_version(4); - output.set_header_length(5 + packet.options_length_words()); - output.set_dscp(packet.dscp); - output.set_ecn(packet.ecn); - output.set_total_length(total_length.try_into().unwrap()); - output.set_identification(packet.identification); - output.set_flags(packet.flags); - output.set_fragment_offset(packet.fragment_offset); - output.set_ttl(packet.ttl); - output.set_next_level_protocol(packet.protocol); - output.set_source(packet.source_address); - output.set_destination(packet.destination_address); - output.set_options(&packet.options); - - // Set the payload - output.set_payload(&payload); - - // Calculate the checksum - output.set_checksum(0); - output.set_checksum(pnet_packet::ipv4::checksum(&output.to_immutable())); - - // Return the packet - output.to_immutable().packet().to_vec() - } -} diff --git a/src/packet/protocols/ipv6.rs b/src/packet/protocols/ipv6.rs deleted file mode 100644 index bd89377..0000000 --- a/src/packet/protocols/ipv6.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::net::Ipv6Addr; - -use pnet_packet::{ip::IpNextHeaderProtocol, Packet}; - -use crate::packet::error::PacketError; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Ipv6Packet { - pub traffic_class: u8, - pub flow_label: u32, - pub next_header: IpNextHeaderProtocol, - pub hop_limit: u8, - pub source_address: Ipv6Addr, - pub destination_address: Ipv6Addr, - pub payload: T, -} - -impl Ipv6Packet { - /// Construct a new IPv6 packet - pub fn new( - traffic_class: u8, - flow_label: u32, - next_header: IpNextHeaderProtocol, - hop_limit: u8, - source_address: Ipv6Addr, - destination_address: Ipv6Addr, - payload: T, - ) -> Self { - Self { - traffic_class, - flow_label, - next_header, - hop_limit, - source_address, - destination_address, - payload, - } - } -} - -impl TryFrom> for Ipv6Packet -where - T: From>, -{ - type Error = PacketError; - - fn try_from(bytes: Vec) -> Result { - // Parse the packet - let packet = pnet_packet::ipv6::Ipv6Packet::new(&bytes) - .ok_or(PacketError::TooShort(bytes.len(), bytes.clone()))?; - - // Return the packet - Ok(Self { - traffic_class: packet.get_traffic_class(), - flow_label: packet.get_flow_label(), - next_header: packet.get_next_header(), - hop_limit: packet.get_hop_limit(), - source_address: packet.get_source(), - destination_address: packet.get_destination(), - payload: packet.payload().to_vec().into(), - }) - } -} - -impl From> for Vec -where - T: Into>, -{ - fn from(packet: Ipv6Packet) -> Self { - // Convert the payload into raw bytes - let payload: Vec = packet.payload.into(); - - // Allocate a mutable packet to write into - let total_length = - pnet_packet::ipv6::MutableIpv6Packet::minimum_packet_size() + payload.len(); - let mut output = - pnet_packet::ipv6::MutableIpv6Packet::owned(vec![0u8; total_length]).unwrap(); - - // Write the header - output.set_version(6); - output.set_traffic_class(packet.traffic_class); - output.set_flow_label(packet.flow_label); - output.set_payload_length(u16::try_from(payload.len()).unwrap()); - output.set_next_header(packet.next_header); - output.set_hop_limit(packet.hop_limit); - output.set_source(packet.source_address); - output.set_destination(packet.destination_address); - - // Write the payload - output.set_payload(&payload); - - // Return the packet - output.to_immutable().packet().to_vec() - } -} diff --git a/src/packet/protocols/mod.rs b/src/packet/protocols/mod.rs deleted file mode 100644 index ae58976..0000000 --- a/src/packet/protocols/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod icmp; -pub mod icmpv6; -pub mod ipv4; -pub mod ipv6; -pub mod raw; -pub mod tcp; -pub mod udp; diff --git a/src/packet/protocols/raw.rs b/src/packet/protocols/raw.rs deleted file mode 100644 index 91e10e7..0000000 --- a/src/packet/protocols/raw.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::packet::error::PacketError; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct RawBytes(pub Vec); - -impl TryFrom> for RawBytes { - type Error = PacketError; - - fn try_from(bytes: Vec) -> Result { - Ok(Self(bytes)) - } -} - -impl From for Vec { - fn from(val: RawBytes) -> Self { - val.0 - } -} \ No newline at end of file diff --git a/src/packet/protocols/tcp.rs b/src/packet/protocols/tcp.rs deleted file mode 100644 index d1623e5..0000000 --- a/src/packet/protocols/tcp.rs +++ /dev/null @@ -1,246 +0,0 @@ -use std::net::{IpAddr, SocketAddr}; - -use pnet_packet::{ - tcp::{TcpOption, TcpOptionPacket}, - Packet, -}; - -use super::raw::RawBytes; -use crate::packet::error::PacketError; - -/// A TCP packet -#[derive(Debug, Clone)] -pub struct TcpPacket { - source: SocketAddr, - destination: SocketAddr, - pub sequence: u32, - pub ack_number: u32, - pub flags: u8, - pub window_size: u16, - pub urgent_pointer: u16, - pub options: Vec, - pub payload: T, -} - -impl TcpPacket { - /// Construct a new TCP packet - #[allow(clippy::too_many_arguments)] - pub fn new( - source: SocketAddr, - destination: SocketAddr, - sequence: u32, - ack_number: u32, - flags: u8, - window_size: u16, - urgent_pointer: u16, - options: Vec, - payload: T, - ) -> Result { - // Ensure the source and destination addresses are the same type - if source.is_ipv4() != destination.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - source.ip(), - destination.ip(), - )); - } - - // Build the packet - Ok(Self { - source, - destination, - sequence, - ack_number, - flags, - window_size, - urgent_pointer, - options, - payload, - }) - } - - // Set a new source - #[allow(dead_code)] - pub fn set_source(&mut self, source: SocketAddr) -> Result<(), PacketError> { - // Ensure the source and destination addresses are the same type - if source.is_ipv4() != self.destination.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - source.ip(), - self.destination.ip(), - )); - } - - // Set the source - self.source = source; - - Ok(()) - } - - // Set a new destination - #[allow(dead_code)] - pub fn set_destination(&mut self, destination: SocketAddr) -> Result<(), PacketError> { - // Ensure the source and destination addresses are the same type - if self.source.is_ipv4() != destination.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - self.source.ip(), - destination.ip(), - )); - } - - // Set the destination - self.destination = destination; - - Ok(()) - } - - /// Get the source - pub fn source(&self) -> SocketAddr { - self.source - } - - /// Get the destination - pub fn destination(&self) -> SocketAddr { - self.destination - } - - /// Get the length of the options in words - #[allow(clippy::cast_possible_truncation)] - fn options_length(&self) -> u8 { - self.options - .iter() - .map(|option| TcpOptionPacket::packet_size(option) as u8) - .sum::() - } -} - -impl TcpPacket -where - T: From>, -{ - /// Construct a new TCP packet from bytes - #[allow(dead_code)] - pub fn new_from_bytes( - bytes: &[u8], - source_address: IpAddr, - destination_address: IpAddr, - ) -> Result { - // Ensure the source and destination addresses are the same type - if source_address.is_ipv4() != destination_address.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - source_address, - destination_address, - )); - } - - // Parse the packet - let parsed = pnet_packet::tcp::TcpPacket::new(bytes) - .ok_or_else(|| PacketError::TooShort(bytes.len(), bytes.to_vec()))?; - - // Build the struct - Ok(Self { - source: SocketAddr::new(source_address, parsed.get_source()), - destination: SocketAddr::new(destination_address, parsed.get_destination()), - sequence: parsed.get_sequence(), - ack_number: parsed.get_acknowledgement(), - flags: parsed.get_flags(), - window_size: parsed.get_window(), - urgent_pointer: parsed.get_urgent_ptr(), - options: parsed.get_options().clone(), - payload: parsed.payload().to_vec().into(), - }) - } -} - -impl TcpPacket { - /// Construct a new TCP packet with a raw payload from bytes - pub fn new_from_bytes_raw_payload( - bytes: &[u8], - source_address: IpAddr, - destination_address: IpAddr, - ) -> Result { - // Ensure the source and destination addresses are the same type - if source_address.is_ipv4() != destination_address.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - source_address, - destination_address, - )); - } - - // Parse the packet - let parsed = pnet_packet::tcp::TcpPacket::new(bytes) - .ok_or_else(|| PacketError::TooShort(bytes.len(), bytes.to_vec()))?; - - // Build the struct - Ok(Self { - source: SocketAddr::new(source_address, parsed.get_source()), - destination: SocketAddr::new(destination_address, parsed.get_destination()), - sequence: parsed.get_sequence(), - ack_number: parsed.get_acknowledgement(), - flags: parsed.get_flags(), - window_size: parsed.get_window(), - urgent_pointer: parsed.get_urgent_ptr(), - options: parsed.get_options().clone(), - payload: RawBytes(parsed.payload().to_vec()), - }) - } -} - -impl From> for Vec -where - T: Into>, -{ - fn from(packet: TcpPacket) -> Self { - // Get the options length in words - let options_length = packet.options_length(); - - // Convert the payload into raw bytes - let payload: Vec = packet.payload.into(); - - // Allocate a mutable packet to write into - let total_length = pnet_packet::tcp::MutableTcpPacket::minimum_packet_size() - + options_length as usize - + payload.len(); - let mut output = - pnet_packet::tcp::MutableTcpPacket::owned(vec![0u8; total_length]).unwrap(); - - // Write the source and dest ports - output.set_source(packet.source.port()); - output.set_destination(packet.destination.port()); - - // Write the sequence and ack numbers - output.set_sequence(packet.sequence); - output.set_acknowledgement(packet.ack_number); - - // Write the offset - output.set_data_offset(5 + (options_length / 4)); - - // Write the options - output.set_options(&packet.options); - - // Write the flags - output.set_flags(packet.flags); - - // Write the window size - output.set_window(packet.window_size); - - // Write the urgent pointer - output.set_urgent_ptr(packet.urgent_pointer); - - // Write the payload - output.set_payload(&payload); - - // Calculate the checksum - output.set_checksum(0); - output.set_checksum(match (packet.source.ip(), packet.destination.ip()) { - (IpAddr::V4(source_ip), IpAddr::V4(destination_ip)) => { - pnet_packet::tcp::ipv4_checksum(&output.to_immutable(), &source_ip, &destination_ip) - } - (IpAddr::V6(source_ip), IpAddr::V6(destination_ip)) => { - pnet_packet::tcp::ipv6_checksum(&output.to_immutable(), &source_ip, &destination_ip) - } - _ => unreachable!(), - }); - - // Return the raw bytes - output.packet().to_vec() - } -} diff --git a/src/packet/protocols/udp.rs b/src/packet/protocols/udp.rs deleted file mode 100644 index 98b73dd..0000000 --- a/src/packet/protocols/udp.rs +++ /dev/null @@ -1,215 +0,0 @@ -use std::net::{IpAddr, SocketAddr}; - -use pnet_packet::Packet; - -use super::raw::RawBytes; -use crate::packet::error::PacketError; - -/// A UDP packet -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct UdpPacket { - source: SocketAddr, - destination: SocketAddr, - pub payload: T, -} - -impl UdpPacket { - /// Construct a new UDP packet - pub fn new( - source: SocketAddr, - destination: SocketAddr, - payload: T, - ) -> Result { - // Ensure the source and destination addresses are the same type - if source.is_ipv4() != destination.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - source.ip(), - destination.ip(), - )); - } - - // Build the packet - Ok(Self { - source, - destination, - payload, - }) - } - - // Set a new source - #[allow(dead_code)] - pub fn set_source(&mut self, source: SocketAddr) -> Result<(), PacketError> { - // Ensure the source and destination addresses are the same type - if source.is_ipv4() != self.destination.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - source.ip(), - self.destination.ip(), - )); - } - - // Set the source - self.source = source; - - Ok(()) - } - - // Set a new destination - #[allow(dead_code)] - pub fn set_destination(&mut self, destination: SocketAddr) -> Result<(), PacketError> { - // Ensure the source and destination addresses are the same type - if self.source.is_ipv4() != destination.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - self.source.ip(), - destination.ip(), - )); - } - - // Set the destination - self.destination = destination; - - Ok(()) - } - - /// Get the source - pub fn source(&self) -> SocketAddr { - self.source - } - - /// Get the destination - pub fn destination(&self) -> SocketAddr { - self.destination - } -} - -impl UdpPacket -where - T: From>, -{ - /// Construct a new UDP packet from bytes - #[allow(dead_code)] - pub fn new_from_bytes( - bytes: &[u8], - source_address: IpAddr, - destination_address: IpAddr, - ) -> Result { - // Ensure the source and destination addresses are the same type - if source_address.is_ipv4() != destination_address.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - source_address, - destination_address, - )); - } - - // Parse the packet - let parsed = pnet_packet::udp::UdpPacket::new(bytes) - .ok_or_else(|| PacketError::TooShort(bytes.len(), bytes.to_vec()))?; - - // Build the struct - Ok(Self { - source: SocketAddr::new(source_address, parsed.get_source()), - destination: SocketAddr::new(destination_address, parsed.get_destination()), - payload: parsed.payload().to_vec().into(), - }) - } -} - -impl UdpPacket { - /// Construct a new UDP packet with a raw payload from bytes - pub fn new_from_bytes_raw_payload( - bytes: &[u8], - source_address: IpAddr, - destination_address: IpAddr, - ) -> Result { - // Ensure the source and destination addresses are the same type - if source_address.is_ipv4() != destination_address.is_ipv4() { - return Err(PacketError::MismatchedAddressFamily( - source_address, - destination_address, - )); - } - - // Parse the packet - let parsed = pnet_packet::udp::UdpPacket::new(bytes) - .ok_or_else(|| PacketError::TooShort(bytes.len(), bytes.to_vec()))?; - - // Build the struct - Ok(Self { - source: SocketAddr::new(source_address, parsed.get_source()), - destination: SocketAddr::new(destination_address, parsed.get_destination()), - payload: RawBytes(parsed.payload().to_vec()), - }) - } -} - -impl From> for Vec -where - T: Into>, -{ - fn from(packet: UdpPacket) -> Self { - // Convert the payload into raw bytes - let payload: Vec = packet.payload.into(); - - // Allocate a mutable packet to write into - let total_length = - pnet_packet::udp::MutableUdpPacket::minimum_packet_size() + payload.len(); - let mut output = - pnet_packet::udp::MutableUdpPacket::owned(vec![0u8; total_length]).unwrap(); - - // Write the source and dest ports - output.set_source(packet.source.port()); - output.set_destination(packet.destination.port()); - - // Write the length - output.set_length(u16::try_from(total_length).unwrap()); - - // Write the payload - output.set_payload(&payload); - - // Calculate the checksum - output.set_checksum(0); - output.set_checksum(match (packet.source.ip(), packet.destination.ip()) { - (IpAddr::V4(source_ip), IpAddr::V4(destination_ip)) => { - pnet_packet::udp::ipv4_checksum(&output.to_immutable(), &source_ip, &destination_ip) - } - (IpAddr::V6(source_ip), IpAddr::V6(destination_ip)) => { - pnet_packet::udp::ipv6_checksum(&output.to_immutable(), &source_ip, &destination_ip) - } - _ => unreachable!(), - }); - - // Return the raw bytes - output.packet().to_vec() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // Test packet construction - #[test] - #[rustfmt::skip] - fn test_packet_construction() { - // Make a new packet - let packet = UdpPacket::new( - "192.0.2.1:1234".parse().unwrap(), - "192.0.2.2:5678".parse().unwrap(), - "Hello, world!".as_bytes().to_vec(), - ) - .unwrap(); - - // Convert to raw bytes - let packet_bytes: Vec = packet.into(); - - // Check the contents - assert!(packet_bytes.len() >= 8 + 13); - assert_eq!(u16::from_be_bytes([packet_bytes[0], packet_bytes[1]]), 1234); - assert_eq!(u16::from_be_bytes([packet_bytes[2], packet_bytes[3]]), 5678); - assert_eq!(u16::from_be_bytes([packet_bytes[4], packet_bytes[5]]), 8 + 13); - assert_eq!(u16::from_be_bytes([packet_bytes[6], packet_bytes[7]]), 0x1f74); - assert_eq!( - &packet_bytes[8..], - "Hello, world!".as_bytes().to_vec().as_slice() - ); - } -} diff --git a/src/packet/xlat/icmp/mod.rs b/src/packet/xlat/icmp/mod.rs deleted file mode 100644 index f0479b0..0000000 --- a/src/packet/xlat/icmp/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -#![allow(clippy::doc_markdown)] - -use std::net::{Ipv4Addr, Ipv6Addr}; - -use pnet_packet::{icmp::IcmpTypes, icmpv6::Icmpv6Types}; - -use crate::{ - metrics::ICMP_COUNTER, - packet::{ - error::PacketError, - protocols::{icmp::IcmpPacket, icmpv6::Icmpv6Packet, raw::RawBytes}, - }, -}; - -use super::ip::{translate_ipv4_to_ipv6, translate_ipv6_to_ipv4}; - -mod type_code; - -/// Translates an ICMP packet to an ICMPv6 packet -pub fn translate_icmp_to_icmpv6( - input: IcmpPacket, - new_source: Ipv6Addr, - new_destination: Ipv6Addr, -) -> Result, PacketError> { - ICMP_COUNTER - .with_label_values(&[ - "icmp", - &input.icmp_type.0.to_string(), - &input.icmp_code.0.to_string(), - ]) - .inc(); - - // Translate the type and code - let (icmpv6_type, icmpv6_code) = - type_code::translate_type_and_code_4_to_6(input.icmp_type, input.icmp_code)?; - - // Some ICMP types require special payload edits - let payload = match icmpv6_type { - Icmpv6Types::TimeExceeded => { - // In this case, the current payload looks like: 4bytes + Ipv4(Data) - // This needs to be translated to: 4bytes + Ipv6(Data) - let inner_payload = input.payload.0[4..].to_vec(); - - // Translate - let inner_payload = - translate_ipv4_to_ipv6(inner_payload.try_into()?, new_source, new_destination)?; - let inner_payload: Vec = inner_payload.into(); - - // Build the new payload - RawBytes({ - let mut buffer = Vec::with_capacity(4 + inner_payload.len()); - buffer.extend_from_slice(&input.payload.0[..4]); - buffer.extend_from_slice(&inner_payload); - buffer - }) - } - _ => input.payload, - }; - - // Build output packet - Ok(Icmpv6Packet::new( - new_source, - new_destination, - icmpv6_type, - icmpv6_code, - payload, - )) -} - -/// Translates an ICMPv6 packet to an ICMP packet -pub fn translate_icmpv6_to_icmp( - input: Icmpv6Packet, - new_source: Ipv4Addr, - new_destination: Ipv4Addr, -) -> Result, PacketError> { - ICMP_COUNTER - .with_label_values(&[ - "icmpv6", - &input.icmp_type.0.to_string(), - &input.icmp_code.0.to_string(), - ]) - .inc(); - - // Translate the type and code - let (icmp_type, icmp_code) = - type_code::translate_type_and_code_6_to_4(input.icmp_type, input.icmp_code)?; - - // Some ICMP types require special payload edits - let payload = match icmp_type { - IcmpTypes::TimeExceeded => { - // In this case, the current payload looks like: 4bytes + Ipv6(Data) - // This needs to be translated to: 4bytes + Ipv4(Data) - let inner_payload = input.payload.0[4..].to_vec(); - - // Translate - let inner_payload = - translate_ipv6_to_ipv4(&inner_payload.try_into()?, new_source, new_destination)?; - let inner_payload: Vec = inner_payload.into(); - - // Build the new payload - RawBytes({ - let mut buffer = Vec::with_capacity(4 + inner_payload.len()); - buffer.extend_from_slice(&input.payload.0[..4]); - buffer.extend_from_slice(&inner_payload); - buffer - }) - } - _ => input.payload, - }; - - // Build output packet - Ok(IcmpPacket::new(icmp_type, icmp_code, payload)) -} diff --git a/src/packet/xlat/icmp/type_code.rs b/src/packet/xlat/icmp/type_code.rs deleted file mode 100644 index 8aead6c..0000000 --- a/src/packet/xlat/icmp/type_code.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! Functions to map between ICMP and ICMPv6 types/codes - -#![allow(clippy::doc_markdown)] - -use pnet_packet::{ - icmp::{destination_unreachable, IcmpCode, IcmpType, IcmpTypes}, - icmpv6::{Icmpv6Code, Icmpv6Type, Icmpv6Types}, -}; - -use crate::packet::error::PacketError; - -/// Best effort translation from an ICMP type and code to an ICMPv6 type and code -#[allow(clippy::deprecated_cfg_attr)] -pub fn translate_type_and_code_4_to_6( - icmp_type: IcmpType, - icmp_code: IcmpCode, -) -> Result<(Icmpv6Type, Icmpv6Code), PacketError> { - match (icmp_type, icmp_code) { - // Echo Request - (IcmpTypes::EchoRequest, _) => Ok((Icmpv6Types::EchoRequest, Icmpv6Code(0))), - - // Echo Reply - (IcmpTypes::EchoReply, _) => Ok((Icmpv6Types::EchoReply, Icmpv6Code(0))), - - // Packet Too Big - ( - IcmpTypes::DestinationUnreachable, - destination_unreachable::IcmpCodes::FragmentationRequiredAndDFFlagSet, - ) => Ok((Icmpv6Types::PacketTooBig, Icmpv6Code(0))), - - // Destination Unreachable - (IcmpTypes::DestinationUnreachable, icmp_code) => Ok(( - Icmpv6Types::DestinationUnreachable, - #[cfg_attr(rustfmt, rustfmt_skip)] - #[allow(clippy::match_same_arms)] - Icmpv6Code(match icmp_code { - destination_unreachable::IcmpCodes::DestinationHostUnreachable => 3, - destination_unreachable::IcmpCodes::DestinationProtocolUnreachable => 4, - destination_unreachable::IcmpCodes::DestinationPortUnreachable => 4, - destination_unreachable::IcmpCodes::SourceRouteFailed => 5, - destination_unreachable::IcmpCodes::SourceHostIsolated => 2, - destination_unreachable::IcmpCodes::NetworkAdministrativelyProhibited => 1, - destination_unreachable::IcmpCodes::HostAdministrativelyProhibited => 1, - destination_unreachable::IcmpCodes::CommunicationAdministrativelyProhibited => 1, - - // Default to No Route to Destination - _ => 0, - }), - )), - - // Time Exceeded - (IcmpTypes::TimeExceeded, icmp_code) => { - Ok((Icmpv6Types::TimeExceeded, Icmpv6Code(icmp_code.0))) - } - - // Default unsupported - (icmp_type, _) => Err(PacketError::UnsupportedIcmpType(icmp_type.0)), - } -} - -/// Best effort translation from an ICMPv6 type and code to an ICMP type and code -#[allow(clippy::deprecated_cfg_attr)] -pub fn translate_type_and_code_6_to_4( - icmp_type: Icmpv6Type, - icmp_code: Icmpv6Code, -) -> Result<(IcmpType, IcmpCode), PacketError> { - match (icmp_type, icmp_code) { - // Echo Request - (Icmpv6Types::EchoRequest, _) => Ok((IcmpTypes::EchoRequest, IcmpCode(0))), - - // Echo Reply - (Icmpv6Types::EchoReply, _) => Ok((IcmpTypes::EchoReply, IcmpCode(0))), - - // Packet Too Big - (Icmpv6Types::PacketTooBig, _) => Ok(( - IcmpTypes::DestinationUnreachable, - destination_unreachable::IcmpCodes::FragmentationRequiredAndDFFlagSet, - )), - - // Destination Unreachable - (Icmpv6Types::DestinationUnreachable, icmp_code) => Ok(( - IcmpTypes::DestinationUnreachable, - #[cfg_attr(rustfmt, rustfmt_skip)] - #[allow(clippy::match_same_arms)] - match icmp_code.0 { - 1 => destination_unreachable::IcmpCodes::CommunicationAdministrativelyProhibited, - 2 => destination_unreachable::IcmpCodes::SourceHostIsolated, - 3 => destination_unreachable::IcmpCodes::DestinationHostUnreachable, - 4 => destination_unreachable::IcmpCodes::DestinationPortUnreachable, - 5 => destination_unreachable::IcmpCodes::SourceRouteFailed, - _ => destination_unreachable::IcmpCodes::DestinationNetworkUnreachable, - }, - )), - - // Time Exceeded - (Icmpv6Types::TimeExceeded, icmp_code) => { - Ok((IcmpTypes::TimeExceeded, IcmpCode(icmp_code.0))) - } - - // Default unsupported - (icmp_type, _) => Err(PacketError::UnsupportedIcmpv6Type(icmp_type.0)), - } -} diff --git a/src/packet/xlat/ip.rs b/src/packet/xlat/ip.rs deleted file mode 100644 index ae9804f..0000000 --- a/src/packet/xlat/ip.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - -use pnet_packet::ip::IpNextHeaderProtocols; - -use crate::{ - packet::protocols::{icmp::IcmpPacket, tcp::TcpPacket, udp::UdpPacket}, - packet::{ - error::PacketError, - protocols::{icmpv6::Icmpv6Packet, ipv4::Ipv4Packet, ipv6::Ipv6Packet, raw::RawBytes}, - }, -}; - -use super::{ - icmp::{translate_icmp_to_icmpv6, translate_icmpv6_to_icmp}, - tcp::{translate_tcp4_to_tcp6, translate_tcp6_to_tcp4}, - udp::{translate_udp4_to_udp6, translate_udp6_to_udp4}, -}; - -/// Translates an IPv4 packet to an IPv6 packet -pub fn translate_ipv4_to_ipv6( - input: Ipv4Packet>, - new_source: Ipv6Addr, - new_destination: Ipv6Addr, -) -> Result>, PacketError> { - // Perform recursive translation to determine the new payload - let new_payload = match input.protocol { - IpNextHeaderProtocols::Icmp => { - let icmp_input: IcmpPacket = input.payload.try_into()?; - translate_icmp_to_icmpv6(icmp_input, new_source, new_destination)?.into() - } - IpNextHeaderProtocols::Udp => { - let udp_input: UdpPacket = UdpPacket::new_from_bytes_raw_payload( - &input.payload, - IpAddr::V4(input.source_address), - IpAddr::V4(input.destination_address), - )?; - translate_udp4_to_udp6(udp_input, new_source, new_destination)?.into() - } - IpNextHeaderProtocols::Tcp => { - let tcp_input: TcpPacket = TcpPacket::new_from_bytes_raw_payload( - &input.payload, - IpAddr::V4(input.source_address), - IpAddr::V4(input.destination_address), - )?; - translate_tcp4_to_tcp6(tcp_input, new_source, new_destination)?.into() - } - _ => { - log::warn!("Unsupported next level protocol: {}", input.protocol); - input.payload - } - }; - - // Build the output IPv6 packet - let output = Ipv6Packet::new( - 0, - 0, - match input.protocol { - IpNextHeaderProtocols::Icmp => IpNextHeaderProtocols::Icmpv6, - proto => proto, - }, - input.ttl, - new_source, - new_destination, - new_payload, - ); - - // Return the output - Ok(output) -} - -/// Translates an IPv6 packet to an IPv4 packet -pub fn translate_ipv6_to_ipv4( - input: &Ipv6Packet>, - new_source: Ipv4Addr, - new_destination: Ipv4Addr, -) -> Result>, PacketError> { - // Perform recursive translation to determine the new payload - let new_payload = match input.next_header { - IpNextHeaderProtocols::Icmpv6 => { - let icmpv6_input: Icmpv6Packet = Icmpv6Packet::new_from_bytes_raw_payload( - &input.payload, - input.source_address, - input.destination_address, - )?; - Some(translate_icmpv6_to_icmp(icmpv6_input, new_source, new_destination)?.into()) - } - IpNextHeaderProtocols::Udp => { - let udp_input: UdpPacket = UdpPacket::new_from_bytes_raw_payload( - &input.payload, - IpAddr::V6(input.source_address), - IpAddr::V6(input.destination_address), - )?; - Some(translate_udp6_to_udp4(udp_input, new_source, new_destination)?.into()) - } - IpNextHeaderProtocols::Tcp => { - let tcp_input: TcpPacket = TcpPacket::new_from_bytes_raw_payload( - &input.payload, - IpAddr::V6(input.source_address), - IpAddr::V6(input.destination_address), - )?; - Some(translate_tcp6_to_tcp4(tcp_input, new_source, new_destination)?.into()) - } - _ => { - log::warn!("Unsupported next level protocol: {}", input.next_header); - None - } - }; - - // Build the output IPv4 packet - let output = Ipv4Packet::new( - 0, - 0, - 0, - 0, - 0, - input.hop_limit, - match input.next_header { - IpNextHeaderProtocols::Icmpv6 => IpNextHeaderProtocols::Icmp, - proto => proto, - }, - new_source, - new_destination, - vec![], - new_payload.unwrap_or_default(), - ); - - // Return the output - Ok(output) -} diff --git a/src/packet/xlat/mod.rs b/src/packet/xlat/mod.rs deleted file mode 100644 index 0c842c1..0000000 --- a/src/packet/xlat/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Protocol translation logic - -pub mod icmp; -pub mod ip; -pub mod tcp; -pub mod udp; diff --git a/src/packet/xlat/tcp.rs b/src/packet/xlat/tcp.rs deleted file mode 100644 index 2b456c3..0000000 --- a/src/packet/xlat/tcp.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - -use crate::packet::{ - error::PacketError, - protocols::{raw::RawBytes, tcp::TcpPacket}, -}; - -/// Translates an IPv4 TCP packet to an IPv6 TCP packet -pub fn translate_tcp4_to_tcp6( - input: TcpPacket, - new_source_addr: Ipv6Addr, - new_destination_addr: Ipv6Addr, -) -> Result, PacketError> { - // Build the packet - TcpPacket::new( - SocketAddr::new(IpAddr::V6(new_source_addr), input.source().port()), - SocketAddr::new(IpAddr::V6(new_destination_addr), input.destination().port()), - input.sequence, - input.ack_number, - input.flags, - input.window_size, - input.urgent_pointer, - input.options, - input.payload, - ) -} - -/// Translates an IPv6 TCP packet to an IPv4 TCP packet -pub fn translate_tcp6_to_tcp4( - input: TcpPacket, - new_source_addr: Ipv4Addr, - new_destination_addr: Ipv4Addr, -) -> Result, PacketError> { - // Build the packet - TcpPacket::new( - SocketAddr::new(IpAddr::V4(new_source_addr), input.source().port()), - SocketAddr::new(IpAddr::V4(new_destination_addr), input.destination().port()), - input.sequence, - input.ack_number, - input.flags, - input.window_size, - input.urgent_pointer, - input.options, - input.payload, - ) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_translate_tcp4_to_tcp6() { - let input = TcpPacket::new( - "192.0.2.1:1234".parse().unwrap(), - "192.0.2.2:5678".parse().unwrap(), - 123456, - 654321, - 0, - 4096, - 0, - Vec::new(), - RawBytes("Hello, world!".as_bytes().to_vec()), - ) - .unwrap(); - - let result = translate_tcp4_to_tcp6( - input, - "2001:db8::1".parse().unwrap(), - "2001:db8::2".parse().unwrap(), - ) - .unwrap(); - - assert_eq!(result.source(), "[2001:db8::1]:1234".parse().unwrap()); - assert_eq!(result.destination(), "[2001:db8::2]:5678".parse().unwrap()); - assert_eq!(result.sequence, 123456); - assert_eq!(result.ack_number, 654321); - assert_eq!(result.flags, 0); - assert_eq!(result.window_size, 4096); - assert_eq!(result.urgent_pointer, 0); - assert_eq!(result.options.len(), 0); - assert_eq!( - result.payload, - RawBytes("Hello, world!".as_bytes().to_vec()) - ); - } - - #[test] - fn test_translate_tcp6_to_tcp4() { - let input = TcpPacket::new( - "[2001:db8::1]:1234".parse().unwrap(), - "[2001:db8::2]:5678".parse().unwrap(), - 123456, - 654321, - 0, - 4096, - 0, - Vec::new(), - RawBytes("Hello, world!".as_bytes().to_vec()), - ) - .unwrap(); - - let result = translate_tcp6_to_tcp4( - input, - "192.0.2.1".parse().unwrap(), - "192.0.2.2".parse().unwrap(), - ) - .unwrap(); - - assert_eq!(result.source(), "192.0.2.1:1234".parse().unwrap()); - assert_eq!(result.destination(), "192.0.2.2:5678".parse().unwrap()); - assert_eq!(result.sequence, 123456); - assert_eq!(result.ack_number, 654321); - assert_eq!(result.flags, 0); - assert_eq!(result.window_size, 4096); - assert_eq!(result.urgent_pointer, 0); - assert_eq!(result.options.len(), 0); - assert_eq!( - result.payload, - RawBytes("Hello, world!".as_bytes().to_vec()) - ); - } -} diff --git a/src/packet/xlat/udp.rs b/src/packet/xlat/udp.rs deleted file mode 100644 index df2a74b..0000000 --- a/src/packet/xlat/udp.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - -use crate::packet::{ - error::PacketError, - protocols::{raw::RawBytes, udp::UdpPacket}, -}; - -/// Translates an IPv4 UDP packet to an IPv6 UDP packet -pub fn translate_udp4_to_udp6( - input: UdpPacket, - new_source_addr: Ipv6Addr, - new_destination_addr: Ipv6Addr, -) -> Result, PacketError> { - // Build the packet - UdpPacket::new( - SocketAddr::new(IpAddr::V6(new_source_addr), input.source().port()), - SocketAddr::new(IpAddr::V6(new_destination_addr), input.destination().port()), - input.payload, - ) -} - -/// Translates an IPv6 UDP packet to an IPv4 UDP packet -pub fn translate_udp6_to_udp4( - input: UdpPacket, - new_source_addr: Ipv4Addr, - new_destination_addr: Ipv4Addr, -) -> Result, PacketError> { - // Build the packet - UdpPacket::new( - SocketAddr::new(IpAddr::V4(new_source_addr), input.source().port()), - SocketAddr::new(IpAddr::V4(new_destination_addr), input.destination().port()), - input.payload, - ) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::packet::protocols::udp::UdpPacket; - - #[test] - fn test_translate_udp4_to_udp6() { - // Create an IPv4 UDP packet - let ipv4_packet = UdpPacket::new( - "192.0.2.1:1234".parse().unwrap(), - "192.0.2.2:5678".parse().unwrap(), - RawBytes("Hello, world!".as_bytes().to_vec()), - ) - .unwrap(); - - // Translate the packet to IPv6 - let ipv6_packet = translate_udp4_to_udp6( - ipv4_packet, - "2001:db8::1".parse().unwrap(), - "2001:db8::2".parse().unwrap(), - ) - .unwrap(); - - // Ensure the translation is correct - assert_eq!(ipv6_packet.source(), "[2001:db8::1]:1234".parse().unwrap()); - assert_eq!( - ipv6_packet.destination(), - "[2001:db8::2]:5678".parse().unwrap() - ); - assert_eq!( - ipv6_packet.payload, - RawBytes("Hello, world!".as_bytes().to_vec()) - ); - } - - #[test] - fn test_translate_udp6_to_udp4() { - // Create an IPv6 UDP packet - let ipv6_packet = UdpPacket::new( - "[2001:db8::1]:1234".parse().unwrap(), - "[2001:db8::2]:5678".parse().unwrap(), - RawBytes("Hello, world!".as_bytes().to_vec()), - ) - .unwrap(); - - // Translate the packet to IPv4 - let ipv4_packet = translate_udp6_to_udp4( - ipv6_packet, - "192.0.2.1".parse().unwrap(), - "192.0.2.2".parse().unwrap(), - ) - .unwrap(); - - // Ensure the translation is correct - assert_eq!(ipv4_packet.source(), "192.0.2.1:1234".parse().unwrap()); - assert_eq!(ipv4_packet.destination(), "192.0.2.2:5678".parse().unwrap()); - assert_eq!( - ipv4_packet.payload, - RawBytes("Hello, world!".as_bytes().to_vec()) - ); - } -}