diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4d1eb1c..2270824 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -11,16 +11,6 @@ "group": "build", "label": "rust: cargo build" }, - { - "type": "cargo", - "command": "doc", - "problemMatcher": [ - "$rustc", - "$rust-panic" - ], - "group": "build", - "label": "rust: cargo doc" - }, { "type": "cargo", "command": "test", @@ -30,15 +20,6 @@ ], "group": "test", "label": "rust: cargo test" - }, - { - "type": "cargo", - "command": "run", - "problemMatcher": [ - "$rustc", - "$rust-panic" - ], - "label": "rust: cargo run" - } + } ] } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 7e48118..9dbce6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,8 @@ thiserror = "1.0.43" colored = "2.0.4" tun-tap = "0.1.3" bimap = "0.6.3" +pnet_packet = "0.33.0" +# etherparse = "0.13.0" [[bin]] name = "protomask" diff --git a/src/nat/mod.rs b/src/nat/mod.rs index 6cb8c7f..eb74cb1 100644 --- a/src/nat/mod.rs +++ b/src/nat/mod.rs @@ -2,22 +2,24 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use bimap::BiMap; use colored::Colorize; -use ipnet::{Ipv4Net, Ipv6Net}; +use ipnet::{IpAdd, Ipv4Net, Ipv6Net}; use tokio::process::Command; use tun_tap::{Iface, Mode}; use crate::nat::{ - packet::{make_ipv4_packet, make_ipv6_packet}, - utils::{bytes_to_hex_str, bytes_to_ipv4_addr, bytes_to_ipv6_addr, ipv4_to_ipv6}, + packet::{xlat_v6_to_v4, IpPacket}, + utils::bytes_to_hex_str, }; +use self::packet::xlat_v4_to_v6; + mod packet; mod utils; -/// A cleaner way to execute an `ip` command -macro_rules! iproute2 { - ($($arg:expr),*) => {{ - Command::new("ip") +/// A cleaner way to execute a CLI command +macro_rules! command { + ($cmd:expr, $($arg:expr),*) => {{ + Command::new($cmd) $(.arg($arg))* .status() }} @@ -54,51 +56,40 @@ impl Nat64 { static_mappings: Vec<(Ipv4Addr, Ipv6Addr)>, ) -> Result { // Bring up tun interface - let interface = Iface::new("nat64i%d", Mode::Tun)?; + let interface = Iface::without_packet_info("nat64i%d", Mode::Tun)?; // Configure the interface let interface_name = interface.name(); log::info!("Configuring interface {}", interface_name); - // Add the nat addresses - log::debug!("Assigning {} to {}", nat_v4, interface_name); - iproute2!( - "address", - "add", - format!("{}/32", nat_v4), - "dev", - interface_name - ) - .await?; - log::debug!("Assigning {} to {}", nat_v6, interface_name); - iproute2!( - "address", - "add", - format!("{}/128", nat_v6), - "dev", - interface_name - ) - .await?; + #[cfg_attr(rustfmt, rustfmt_skip)] + { + // Add the nat addresses + log::debug!("Assigning {} to {}", nat_v4, interface_name); + command!("ip", "address", "add", format!("{}/32", nat_v4), "dev", interface_name).await?; + log::debug!("Assigning {} to {}", nat_v6, interface_name); + command!("ip", "address", "add", format!("{}/128", nat_v6), "dev", interface_name ).await?; - // Bring up the interface - log::debug!("Bringing up {}", interface_name); - iproute2!("link", "set", "dev", interface_name, "up").await?; + // Bring up the interface + log::debug!("Bringing up {}", interface_name); + command!("ip", "link", "set", "dev", interface_name, "up").await?; - // Add route for IPv6 prefix - log::debug!("Adding route {} via {}", ipv6_prefix, interface_name); - iproute2!( - "route", - "add", - ipv6_prefix.to_string(), - "dev", - interface_name - ) - .await?; + // Add route for IPv6 prefix + log::debug!("Adding route {} via {}", ipv6_prefix, interface_name); + command!("ip", "route", "add", ipv6_prefix.to_string(), "dev", interface_name).await?; + + // Configure iptables + log::debug!("Configuring iptables"); + command!("iptables", "-A", "FORWARD", "-i", interface_name, "-j", "ACCEPT").await?; + command!("iptables", "-A", "FORWARD", "-o", interface_name, "-j", "ACCEPT").await?; + command!("ip6tables", "-A", "FORWARD", "-i", interface_name, "-j", "ACCEPT").await?; + command!("ip6tables", "-A", "FORWARD", "-o", interface_name, "-j", "ACCEPT").await?; + } // Add every IPv4 prefix to the routing table for prefix in ipv4_pool.iter() { log::debug!("Adding route {} via {}", prefix, interface_name); - iproute2!("route", "add", prefix.to_string(), "dev", interface_name).await?; + command!("ip", "route", "add", prefix.to_string(), "dev", interface_name).await?; } // Build a reservation list @@ -130,8 +121,7 @@ impl Nat64 { .unwrap(); // Allocate a buffer for incoming packets - // NOTE: Add 4 to account for the Tun header - let mut buffer = vec![0; (mtu as usize) + 4]; + let mut buffer = vec![0; mtu as usize]; log::info!("Translating packets"); loop { @@ -149,21 +139,21 @@ impl Nat64 { } /// Internal function that checks if a destination address is allowed to be processed - // fn is_dest_allowed(&self, dest: IpAddr) -> bool { - // return dest == self.instance_v4 - // || dest == self.instance_v6 - // || match dest { - // IpAddr::V4(addr) => self.ipv4_pool.iter().any(|prefix| prefix.contains(&addr)), - // IpAddr::V6(addr) => self.ipv6_prefix.contains(&addr), - // }; - // } + fn is_dest_allowed(&self, dest: IpAddr) -> bool { + return dest == self.instance_v4 + || dest == self.instance_v6 + || match dest { + IpAddr::V4(addr) => self.ipv4_pool.iter().any(|prefix| prefix.contains(&addr)), + IpAddr::V6(addr) => self.ipv6_prefix.contains(&addr), + }; + } /// Calculate a unique IPv4 address inside the pool for a given IPv6 address fn calculate_ipv4(&self, _addr: Ipv6Addr) -> Option { // Search the list of possible IPv4 addresses for prefix in self.ipv4_pool.iter() { for addr in prefix.hosts() { - // If this address is avalible, use it + // If this address is available, use it if !self.pool_reservations.contains_left(&addr) { return Some(addr); } @@ -173,127 +163,143 @@ impl Nat64 { None } + /// Embeds an IPv4 address into an IPv6 address + fn embed_v4_into_v6(&self, addr: Ipv4Addr) -> Ipv6Addr { + let mut octets = [0u8; 16]; + octets[..12].copy_from_slice(&self.ipv6_prefix.network().octets()[..12]); + octets[12..].copy_from_slice(&addr.octets()); + Ipv6Addr::from(octets) + } + + /// Extracts an IPv4 address from an IPv6 address + fn extract_v4_from_v6(&self, addr: Ipv6Addr) -> Ipv4Addr { + let mut octets = [0u8; 4]; + octets.copy_from_slice(&addr.octets()[12..]); + Ipv4Addr::from(octets) + } + + /// Gets or creates a reservation for a given address + fn get_or_create_reservation(&mut self, addr: IpAddr) -> Option { + match addr { + IpAddr::V4(addr) => { + if self.pool_reservations.contains_left(&addr) { + return Some(IpAddr::V6( + *self.pool_reservations.get_by_left(&addr).unwrap(), + )); + } else { + return None; + } + } + IpAddr::V6(addr) => { + // If the address is already reserved, return it + if self.pool_reservations.contains_right(&addr) { + return Some(IpAddr::V4( + *self.pool_reservations.get_by_right(&addr).unwrap(), + )); + } + + // Otherwise, calculate a new address + let new_addr = self.calculate_ipv4(addr)?; + self.pool_reservations.insert(new_addr, addr); + return Some(IpAddr::V4(new_addr)); + } + } + } + /// Internal function to process an incoming packet. /// If `Some` is returned, the result is sent back out the interface async fn process(&mut self, packet: &[u8]) -> Result>, std::io::Error> { - // Ignore the first 4 bytes, which are the Tun header - let tun_header = &packet[..4]; - let packet = &packet[4..]; + // Parse the packet + let input_packet = IpPacket::new(&packet); + if let None = input_packet { + log::warn!( + "{}", + format!( + "Malformed packet received: version: {}, len: {}", + packet[0] >> 4, + packet.len() + ) + .yellow() + ); + return Ok(None); + } + let input_packet = input_packet.unwrap(); - // Log the packet - log::debug!("Processing packet with length: {}", packet.len()); - log::debug!( - "> Tun Header: {}", - bytes_to_hex_str(tun_header).bright_cyan() - ); - log::debug!("> IP Header: {}", bytes_to_hex_str(packet).bright_cyan()); + // Log some info about the packet + #[cfg_attr(rustfmt, rustfmt_skip)] + { + log::debug!("Processing packet with length: {}", input_packet.len().to_string().bright_cyan()); + log::debug!("> IP Header: {}", bytes_to_hex_str(input_packet.get_header()).bright_cyan()); + log::debug!("> Source: {}", input_packet.get_source().to_string().bright_cyan()); + log::debug!("> Destination: {}", input_packet.get_destination().to_string().bright_cyan()); + } - match packet[0] >> 4 { - 4 => { - // Parse the source and destination addresses - let source_addr = bytes_to_ipv4_addr(&packet[12..16]); - let dest_addr = bytes_to_ipv4_addr(&packet[16..20]); - log::debug!("> Source: {}", source_addr.to_string().bright_cyan()); - log::debug!("> Destination: {}", dest_addr.to_string().bright_cyan()); + // Ignore packets that aren't destined for the NAT instance + if !self.is_dest_allowed(input_packet.get_destination()) { + log::debug!("{}", "Ignoring packet. Invalid destination".yellow()); + return Ok(None); + } - // Only accept packets destined to hosts in the reservation list - // TODO: Should also probably let the nat addr pass - if !self.pool_reservations.contains_left(&dest_addr) { - log::debug!("{}", "Ignoring packet. Invalid destination".yellow()); + // Handle packet translation + let output_packet = match input_packet { + IpPacket::V4(packet) => { + let new_source = self.embed_v4_into_v6(packet.get_source()); + let new_dest = + self.get_or_create_reservation(std::net::IpAddr::V4(packet.get_destination())); + if let Some(IpAddr::V6(new_dest)) = new_dest { + // Log the new addresses + #[cfg_attr(rustfmt, rustfmt_skip)] + { + log::debug!("> Mapped IPv6 Source: {}", new_source.to_string().bright_cyan()); + log::debug!("> Mapped IPv6 Destination: {}", new_dest.to_string().bright_cyan()); + } + + // Translate the packet + let data = xlat_v4_to_v6(&packet, new_source, new_dest); + + // Log the translated packet header + log::debug!( + "> Translated Header: {}", + bytes_to_hex_str(&data[0..40]).bright_cyan() + ); + + // Return the translated packet + data + } else { return Ok(None); } - - // Get the IPv6 source and destination addresses - let source_addr_v6 = ipv4_to_ipv6(&source_addr, &self.ipv6_prefix); - let dest_addr_v6 = self.pool_reservations.get_by_left(&dest_addr).unwrap(); - log::debug!( - "> Mapped IPv6 Source: {}", - source_addr_v6.to_string().bright_cyan() - ); - log::debug!( - "> Mapped IPv6 Destination: {}", - dest_addr_v6.to_string().bright_cyan() - ); - - // Build an IPv6 packet using this information and the original packet's payload - let translated = make_ipv6_packet( - packet[8], - match packet[9] { - 1 => 58, - _ => packet[9], - }, - &source_addr_v6, - &dest_addr_v6, - &packet[20..], - ); - let mut response = vec![0; 4 + translated.len()]; - response[..4].copy_from_slice(tun_header); - response[4..].copy_from_slice(&translated); - log::debug!( - "> Translated Header: {}", - bytes_to_hex_str(&response[4..40]).bright_cyan() - ); - log::debug!("{}", "Sending translated packet".bright_green()); - return Ok(Some(response)); } - 6 => { - // Parse the source and destination addresses - let source_addr = bytes_to_ipv6_addr(&packet[8..24]); - let dest_addr = bytes_to_ipv6_addr(&packet[24..40]); - log::debug!("> Source: {}", source_addr.to_string().bright_cyan()); - log::debug!("> Destination: {}", dest_addr.to_string().bright_cyan()); + IpPacket::V6(packet) => { + let new_source = + self.get_or_create_reservation(std::net::IpAddr::V6(packet.get_source())); + let new_dest = self.extract_v4_from_v6(packet.get_destination()); + if let Some(IpAddr::V4(new_source)) = new_source { + // Log the new addresses + #[cfg_attr(rustfmt, rustfmt_skip)] + { + log::debug!("> Mapped IPv4 Source: {}", new_source.to_string().bright_cyan()); + log::debug!("> Mapped IPv4 Destination: {}", new_dest.to_string().bright_cyan()); + } - // Only process packets destined for the NAT prefix - if !self.ipv6_prefix.contains(&dest_addr) { - log::debug!("{}", "Ignoring packet. Invalid destination".yellow()); + // Translate the packet + let data = xlat_v6_to_v4(&packet, new_source, new_dest); + + // Log the translated packet header + log::debug!( + "> Translated Header: {}", + bytes_to_hex_str(&data[0..20]).bright_cyan() + ); + + // Return the translated packet + data + } else { return Ok(None); } - - // If the source address doesn't have a reservation, calculate its corresponding IPv4 address and insert into the map - if !self.pool_reservations.contains_right(&source_addr) { - let source_addr_v4 = self.calculate_ipv4(source_addr).unwrap(); - self.pool_reservations.insert(source_addr_v4, source_addr); - } - - // Get the mapped source address - let source_addr_v4 = self.pool_reservations.get_by_right(&source_addr).unwrap(); - log::debug!( - "> Mapped IPv4 Source: {}", - source_addr_v4.to_string().bright_cyan() - ); - - // Convert the destination address to IPv4 - let dest_addr_v4 = Ipv4Addr::new(packet[36], packet[37], packet[38], packet[39]); - log::debug!( - "> Mapped IPv4 Destination: {}", - dest_addr_v4.to_string().bright_cyan() - ); - - // Build an IPv4 packet using this information and the original packet's payload - let translated = make_ipv4_packet( - packet[7], - match packet[6] { - 58 => 1, - _ => packet[6], - }, - source_addr_v4, - &dest_addr_v4, - &packet[40..], - ); - let mut response = vec![0; 4 + translated.len()]; - response[..4].copy_from_slice(tun_header); - response[4..].copy_from_slice(&translated); - log::debug!( - "> Translated Header: {}", - bytes_to_hex_str(&response[4..24]).bright_cyan() - ); - log::debug!("{}", "Sending translated packet".bright_green()); - return Ok(Some(response)); - } - _ => { - log::warn!("Unknown IP version: {}", packet[0] >> 4); - return Ok(None); } }; + + // Build the response + log::debug!("{}", "Sending translated packet".bright_green()); + return Ok(Some(output_packet)); } } diff --git a/src/nat/packet.rs b/src/nat/packet.rs index c9b95cb..fd04bd7 100644 --- a/src/nat/packet.rs +++ b/src/nat/packet.rs @@ -1,75 +1,131 @@ -use std::net::{Ipv4Addr, Ipv6Addr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use super::utils::ipv4_header_checksum; +// // use etherparse::{IpHeader, Ipv4Header, Ipv4Extensions}; +use pnet_packet::{ + ethernet::EtherTypes::Ipv6, + ipv4::{checksum, Ipv4, Ipv4Packet, MutableIpv4Packet}, + ipv6::{Ipv6, Ipv6Packet, MutableIpv6Packet}, + Packet, +}; -/// Constructs an IPv4 packet -pub fn make_ipv4_packet( - ttl: u8, - protocol: u8, - source: &Ipv4Addr, - destination: &Ipv4Addr, - payload: &[u8], -) -> Vec { - // Allocate an empty buffer - let mut buffer = vec![0; 20 + payload.len()]; - - // Write version and header length - buffer[0] = 0x45; - - // DSCP and ECN - let dscp = 0u8; - let ecn = 0u8; - buffer[1] = (dscp << 2) | ecn; - - buffer[2] = (buffer.len() >> 8) as u8; // Total length - buffer[3] = buffer.len() as u8; // Total length (contd.) - buffer[4] = 0x00; // Identification - buffer[6] = 0x00; // Flags and fragment offset - buffer[7] = 0x00; // Fragment offset (contd.) - buffer[8] = ttl; // TTL - buffer[9] = protocol; // Protocol - buffer[10] = 0x00; // Header checksum - buffer[11] = 0x00; // Header checksum (contd.) - buffer[12..16].copy_from_slice(&source.octets()); // Source address - buffer[16..20].copy_from_slice(&destination.octets()); // Destination address - - // Calculate the checksum - let checksum = ipv4_header_checksum(&buffer[0..20]); - buffer[10] = (checksum >> 8) as u8; - buffer[11] = checksum as u8; - - // Copy the payload - buffer[20..].copy_from_slice(payload); - - // Return the buffer - buffer +/// A protocol-agnostic packet type +#[derive(Debug)] +pub enum IpPacket<'a> { + /// IPv4 packet + V4(Ipv4Packet<'a>), + /// IPv6 packet + V6(Ipv6Packet<'a>), } -pub fn make_ipv6_packet( - hop_limit: u8, - next_header: u8, - source: &Ipv6Addr, - destination: &Ipv6Addr, - payload: &[u8], -) -> Vec { - // Allocate an empty buffer - let mut buffer = vec![0; 40 + payload.len()]; +impl IpPacket<'_> { + /// Creates a new packet from a byte slice + pub fn new<'a>(bytes: &'a [u8]) -> Option> { + match bytes[0] >> 4 { + 4 => Some(IpPacket::V4(Ipv4Packet::new(bytes)?)), + 6 => Some(IpPacket::V6(Ipv6Packet::new(bytes)?)), + _ => None, + } + } - // Write basic info - buffer[0] = 0x60; // Version and traffic class - buffer[1] = 0x00; // Traffic class (contd.) and flow label - buffer[2] = 0x00; // Flow label (contd.) - buffer[3] = 0x00; // Flow label (contd.) - buffer[4] = (buffer.len() >> 8) as u8; // Payload length - buffer[5] = buffer.len() as u8; // Payload length (contd.) - buffer[6] = next_header; // Next header - buffer[7] = hop_limit; // Hop limit - buffer[8..24].copy_from_slice(&source.octets()); // Source address - buffer[24..40].copy_from_slice(&destination.octets()); // Destination address + /// Returns the source address + pub fn get_source(&self) -> IpAddr { + match self { + IpPacket::V4(packet) => IpAddr::V4(packet.get_source()), + IpPacket::V6(packet) => IpAddr::V6(packet.get_source()), + } + } - // Copy the payload - buffer[40..].copy_from_slice(payload); + /// Returns the destination address + pub fn get_destination(&self) -> IpAddr { + match self { + IpPacket::V4(packet) => IpAddr::V4(packet.get_destination()), + IpPacket::V6(packet) => IpAddr::V6(packet.get_destination()), + } + } - // Return the buffer - buffer + /// Returns the packet header + pub fn get_header(&self) -> &[u8] { + match self { + IpPacket::V4(packet) => packet.packet()[..20].as_ref(), + IpPacket::V6(packet) => packet.packet()[..40].as_ref(), + } + } + + /// Returns the packet payload + pub fn get_payload(&self) -> &[u8] { + match self { + IpPacket::V4(packet) => packet.payload(), + IpPacket::V6(packet) => packet.payload(), + } + } + + /// Converts the packet to a byte vector + pub fn to_bytes(&self) -> Vec { + match self { + IpPacket::V4(packet) => packet.packet().to_vec(), + IpPacket::V6(packet) => packet.packet().to_vec(), + } + } + + /// Returns the packet length + pub fn len(&self) -> usize { + match self { + IpPacket::V4(packet) => packet.packet().len(), + IpPacket::V6(packet) => packet.packet().len(), + } + } +} + +pub fn xlat_v6_to_v4( + ipv6_packet: &Ipv6Packet, + new_source: Ipv4Addr, + new_dest: Ipv4Addr, +) -> Vec { + let data = Ipv4 { + version: 4, + header_length: 20, + dscp: 0, + ecn: 0, + total_length: 20 + ipv6_packet.payload().len() as u16, + identification: 0, + flags: 0, + fragment_offset: 0, + ttl: ipv6_packet.get_hop_limit(), + next_level_protocol: ipv6_packet.get_next_header(), + checksum: 0, + source: new_source, + destination: new_dest, + options: vec![], + payload: ipv6_packet.payload().to_vec(), + }; + let mut buffer = vec![0; 20 + ipv6_packet.payload().len()]; + let mut packet = MutableIpv4Packet::new(buffer.as_mut()).unwrap(); + packet.populate(&data); + packet.set_checksum(checksum(&packet.to_immutable())); + let mut output = packet.to_immutable().packet().to_vec(); + // TODO: There is a bug here.. for now, force write header size + output[0] = 0x45; + output +} + +pub fn xlat_v4_to_v6( + ipv4_packet: &Ipv4Packet, + new_source: Ipv6Addr, + new_dest: Ipv6Addr, +) -> Vec { + let data = Ipv6 { + version: 6, + traffic_class: 0, + flow_label: 0, + payload_length: 40 + ipv4_packet.payload().len() as u16, + next_header: ipv4_packet.get_next_level_protocol(), + hop_limit: ipv4_packet.get_ttl(), + source: new_source, + destination: new_dest, + payload: ipv4_packet.payload().to_vec(), + }; + let mut buffer = vec![0; 40 + ipv4_packet.payload().len()]; + let mut packet = MutableIpv6Packet::new(buffer.as_mut()).unwrap(); + packet.populate(&data); + packet.to_immutable().packet().to_vec() }