diff --git a/Makefile b/Makefile index 5f5abb1..e49d733 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SRC=$(wildcard src/*.rs) $(wildcard src/**/*.rs) Cargo.toml +SRC=$(wildcard src/*.rs) $(wildcard src/**/*.rs) $(wildcard src/**/**/*.rs) Cargo.toml target/debug/protomask: $(SRC) cargo build diff --git a/src/config.rs b/src/config.rs index 7d5333f..a0262a1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,11 +11,11 @@ use ipnet::{Ipv4Net, Ipv6Net}; #[derive(Debug, serde::Deserialize)] pub struct InterfaceConfig { /// IPv4 router address - #[serde(rename = "Address4")] - pub address_v4: Ipv4Addr, - /// IPv6 router address - #[serde(rename = "Address6")] - pub address_v6: Ipv6Addr, + // #[serde(rename = "Address4")] + // pub address_v4: Ipv4Addr, + // /// IPv6 router address + // #[serde(rename = "Address6")] + // pub address_v6: Ipv6Addr, /// Ipv4 pool #[serde(rename = "Pool")] pub pool: Vec, @@ -42,7 +42,7 @@ fn default_reservation_duration() -> Duration { #[derive(Debug, serde::Deserialize)] pub struct RulesConfig { /// Static mapping rules - #[serde(rename = "MapStatic")] + #[serde(rename = "MapStatic", default="Vec::new")] pub static_map: Vec, /// How long to hold a dynamic mapping for #[serde(rename = "ReservationDuration", default="default_reservation_duration")] diff --git a/src/nat/mod.rs b/src/nat/mod.rs index 60da0d4..f20dd92 100644 --- a/src/nat/mod.rs +++ b/src/nat/mod.rs @@ -6,7 +6,11 @@ use std::{ use ipnet::{Ipv4Net, Ipv6Net}; use pnet_packet::ip::IpNextHeaderProtocols; -use self::{interface::Nat64Interface, packet::IpPacket, table::Nat64Table}; +use self::{ + interface::Nat64Interface, + packet::{IpPacket, PacketError}, + table::{Nat64Table, TableError}, +}; mod interface; mod packet; @@ -21,6 +25,8 @@ pub enum Nat64Error { InterfaceError(#[from] interface::InterfaceError), #[error(transparent)] IoError(#[from] std::io::Error), + #[error(transparent)] + UdpProxyError(#[from] xlat::UdpProxyError), } pub struct Nat64 { @@ -66,14 +72,27 @@ impl Nat64 { // Parse in to a more friendly format match IpPacket::new(&buffer[..packet_len]) { // Try to process the packet - Ok(inbound_packet) => match self.process_packet(inbound_packet).await? { - // If data is returned, send it back out the interface - Some(outbound_packet) => { - let packet_bytes = outbound_packet.to_bytes(); - self.interface.send(&packet_bytes)?; - } - // Otherwise, we can assume that the packet was dealt with, and can move on - None => {} + Ok(inbound_packet) => match self.process_packet(inbound_packet).await { + Ok(inbound_packet) => match inbound_packet { + // If data is returned, send it back out the interface + Some(outbound_packet) => { + let packet_bytes = outbound_packet.to_bytes(); + log::debug!("Sending packet: {:?}", packet_bytes); + self.interface.send(&packet_bytes).unwrap(); + } + // Otherwise, we can assume that the packet was dealt with, and can move on + None => {} + }, + + // Some errors are non-critical as far as this loop is concerned + Err(error) => match error { + Nat64Error::TableError(TableError::NoIpv6Mapping(address)) => { + log::debug!("No IPv6 mapping for {}", address); + } + error => { + return Err(error); + } + }, }, Err(error) => { log::error!("Failed to parse packet: {}", error); @@ -89,10 +108,10 @@ impl Nat64 { } impl Nat64 { - async fn process_packet( + async fn process_packet<'a>( &mut self, - packet: IpPacket<'_>, - ) -> Result, Nat64Error> { + packet: IpPacket<'a>, + ) -> Result>, Nat64Error> { // The destination of the packet must be within a prefix we care about if match packet.get_destination() { IpAddr::V4(ipv4_addr) => !self.table.is_address_within_pool(&ipv4_addr), @@ -136,7 +155,9 @@ impl Nat64 { match next_header_protocol { IpNextHeaderProtocols::Icmp => unimplemented!(), IpNextHeaderProtocols::Icmpv6 => unimplemented!(), - IpNextHeaderProtocols::Udp => unimplemented!(), + IpNextHeaderProtocols::Udp => Ok(Some( + xlat::proxy_udp_packet(packet, new_source, new_destination).await?, + )), IpNextHeaderProtocols::Tcp => unimplemented!(), next_header_protocol => { log::warn!("Unsupported next header protocol: {}", next_header_protocol); diff --git a/src/nat/xlat/mod.rs b/src/nat/xlat/mod.rs index f0e107a..1beb656 100644 --- a/src/nat/xlat/mod.rs +++ b/src/nat/xlat/mod.rs @@ -2,6 +2,8 @@ mod icmp; mod ip; +mod udp; -pub use icmp::{icmpv6_to_icmp, icmp_to_icmpv6}; -pub use ip::{ipv4_to_ipv6, ipv6_to_ipv4}; \ No newline at end of file +pub use icmp::{icmp_to_icmpv6, icmpv6_to_icmp}; +pub use ip::{ipv4_to_ipv6, ipv6_to_ipv4}; +pub use udp::{proxy_udp_packet,UdpProxyError}; diff --git a/src/nat/xlat/udp.rs b/src/nat/xlat/udp.rs new file mode 100644 index 0000000..db805db --- /dev/null +++ b/src/nat/xlat/udp.rs @@ -0,0 +1,71 @@ +use std::net::{IpAddr, SocketAddr}; + +use pnet_packet::{ + ip::IpNextHeaderProtocols, + ipv4::{self, Ipv4Packet, MutableIpv4Packet}, + ipv6::{Ipv6Packet, MutableIpv6Packet}, + udp::UdpPacket, + Packet, +}; +use tokio::net::UdpSocket; + +use crate::nat::packet::IpPacket; + +#[derive(Debug, thiserror::Error)] +pub enum UdpProxyError { + #[error("Packet too short. Got {0} bytes")] + PacketTooShort(usize), + #[error(transparent)] + IoError(#[from] std::io::Error), +} + +/// Extracts information from an original packet, and proxies UDP contents via a new source and destination +pub async fn proxy_udp_packet<'a>( + original_packet: IpPacket<'a>, + new_source: IpAddr, + new_destination: IpAddr, +) -> Result { + // Parse the original packet's payload to extract UDP data + let udp_packet = UdpPacket::new(original_packet.get_payload()) + .ok_or_else(|| UdpProxyError::PacketTooShort(original_packet.get_payload().len()))?; + + // Construct a new output packet + match (&original_packet, new_source, new_destination) { + // Translate IPv4(UDP) to IPv6(UDP) + (IpPacket::V4(_), IpAddr::V6(new_source), IpAddr::V6(new_destination)) => { + let mut output = + MutableIpv6Packet::owned(vec![0u8; 40 + udp_packet.packet().len()]).unwrap(); + output.set_version(6); + output.set_source(new_source); + output.set_destination(new_destination); + output.set_hop_limit(original_packet.get_ttl()); + output.set_next_header(IpNextHeaderProtocols::Udp); + output.set_payload_length(udp_packet.packet().len() as u16); + output.set_payload(udp_packet.packet()); + Ok(IpPacket::V6( + Ipv6Packet::owned(output.to_immutable().packet().to_vec()).unwrap(), + )) + } + + // Translate IPv6(UDP) to IPv4(UDP) + (IpPacket::V6(_), IpAddr::V4(new_source), IpAddr::V4(new_destination)) => { + let mut output = + MutableIpv4Packet::owned(vec![0u8; 20 + udp_packet.packet().len()]).unwrap(); + output.set_version(4); + output.set_source(new_source); + output.set_destination(new_destination); + output.set_ttl(original_packet.get_ttl()); + output.set_next_level_protocol(IpNextHeaderProtocols::Udp); + output.set_header_length(5); + output.set_total_length(20 + udp_packet.packet().len() as u16); + output.set_payload(udp_packet.packet()); + output.set_checksum(0); + output.set_checksum(ipv4::checksum(&output.to_immutable())); + Ok(IpPacket::V4( + Ipv4Packet::owned(output.to_immutable().packet().to_vec()).unwrap(), + )) + } + + _ => unreachable!(), + } +}