1

Working on UDP proxy

This commit is contained in:
Evan Pratten 2023-07-16 16:09:11 -04:00
parent a1ba812a5c
commit a08b561807
5 changed files with 116 additions and 22 deletions

@ -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

@ -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<Ipv4Net>,
@ -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<AddressMappingRule>,
/// How long to hold a dynamic mapping for
#[serde(rename = "ReservationDuration", default="default_reservation_duration")]

@ -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<Option<IpPacket>, Nat64Error> {
packet: IpPacket<'a>,
) -> Result<Option<IpPacket<'a>>, 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);

@ -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};
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};

71
src/nat/xlat/udp.rs Normal file

@ -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<IpPacket, UdpProxyError> {
// 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!(),
}
}