icmp translation
This commit is contained in:
parent
6cd9bdb6d6
commit
fc567c63ff
171
src/nat/icmp.rs
Normal file
171
src/nat/icmp.rs
Normal file
@ -0,0 +1,171 @@
|
||||
//! ICMP packets require their own translation system
|
||||
|
||||
use colored::Colorize;
|
||||
use pnet_packet::{
|
||||
icmp::{self, Icmp, IcmpCode, IcmpPacket, IcmpType, MutableIcmpPacket},
|
||||
icmpv6::Icmpv6Packet,
|
||||
Packet,
|
||||
};
|
||||
|
||||
pub fn icmpv6_to_icmp<'a>(input: &'a Icmpv6Packet<'a>) -> Option<IcmpPacket<'a>> {
|
||||
let data = match input.get_icmpv6_type().0 {
|
||||
// Destination Unreachable
|
||||
1 => Icmp {
|
||||
icmp_type: IcmpType(3),
|
||||
// A best guess translation of ICMP codes. Feel free to open a PR to improve this :)
|
||||
icmp_code: IcmpCode(match input.get_icmpv6_code().0 {
|
||||
// No route to destination -> Destination network unreachable
|
||||
0 => 0,
|
||||
// Communication with destination administratively prohibited -> Communication administratively prohibited
|
||||
1 => 13,
|
||||
// Beyond scope of source address -> Destination network unreachable
|
||||
2 => 0,
|
||||
// Address unreachable -> Destination host unreachable
|
||||
3 => 1,
|
||||
// Port unreachable -> Destination port unreachable
|
||||
4 => 3,
|
||||
// Source address failed ingress/egress policy -> Source route failed
|
||||
5 => 5,
|
||||
// Reject route to destination -> Destination network unreachable
|
||||
6 => 0,
|
||||
// Error in Source Routing Header -> Destination network unreachable
|
||||
7 => 0,
|
||||
// All others -> Destination network unreachable
|
||||
_ => 0,
|
||||
}),
|
||||
checksum: 0,
|
||||
payload: input.payload().to_vec(),
|
||||
},
|
||||
// Time Exceeded
|
||||
3 => Icmp {
|
||||
icmp_type: IcmpType(11),
|
||||
icmp_code: IcmpCode(input.get_icmpv6_code().0),
|
||||
checksum: 0,
|
||||
payload: input.payload().to_vec(),
|
||||
},
|
||||
// Echo Request
|
||||
128 => Icmp {
|
||||
icmp_type: IcmpType(8),
|
||||
icmp_code: IcmpCode(0),
|
||||
checksum: 0,
|
||||
payload: input.payload().to_vec(),
|
||||
},
|
||||
// Echo Reply
|
||||
129 => Icmp {
|
||||
icmp_type: IcmpType(0),
|
||||
icmp_code: IcmpCode(0),
|
||||
checksum: 0,
|
||||
payload: input.payload().to_vec(),
|
||||
},
|
||||
_ => {
|
||||
log::warn!("ICMPv6 type {} not supported", input.get_icmpv6_type().0);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Debug logging
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
{
|
||||
log::debug!("> Input ICMP Type: {}", input.get_icmpv6_type().0.to_string().bright_cyan());
|
||||
log::debug!("> Input ICMP Code: {}", input.get_icmpv6_code().0.to_string().bright_cyan());
|
||||
log::debug!("> Output ICMP Type: {}", data.icmp_type.0.to_string().bright_cyan());
|
||||
log::debug!("> Output ICMP Code: {}", data.icmp_code.0.to_string().bright_cyan());
|
||||
}
|
||||
|
||||
// Create new ICMP packet
|
||||
let mut output = MutableIcmpPacket::owned(vec![0u8; IcmpPacket::packet_size(&data)]).unwrap();
|
||||
output.populate(&data);
|
||||
output.set_checksum(icmp::checksum(&output.to_immutable()));
|
||||
|
||||
IcmpPacket::owned(output.to_immutable().packet().to_vec())
|
||||
}
|
||||
|
||||
pub fn icmp_to_icmpv6<'a>(input: &'a IcmpPacket<'a>) -> Option<Icmpv6Packet<'a>> {
|
||||
let data = match input.get_icmp_type().0 {
|
||||
// Destination Unreachable
|
||||
3 => Icmp {
|
||||
icmp_type: IcmpType(1),
|
||||
// A best guess translation of ICMP codes. Feel free to open a PR to improve this :)
|
||||
icmp_code: IcmpCode(match input.get_icmp_code().0 {
|
||||
// Destination network unreachable -> No route to destination
|
||||
0 => 0,
|
||||
// Destination host unreachable -> Address unreachable
|
||||
1 => 3,
|
||||
// Destination protocol unreachable -> No route to destination
|
||||
2 => 0,
|
||||
// Destination port unreachable -> Port unreachable
|
||||
3 => 4,
|
||||
// Fragmentation required, and DF flag set -> Packet too big
|
||||
4 => 2,
|
||||
// Source route failed -> Source address failed ingress/egress policy
|
||||
5 => 5,
|
||||
// Destination network unknown -> No route to destination
|
||||
6 => 0,
|
||||
// Destination host unknown -> Address unreachable
|
||||
7 => 3,
|
||||
// Source host isolated -> No route to destination
|
||||
8 => 0,
|
||||
// Network administratively prohibited -> Communication with destination administratively prohibited
|
||||
9 => 1,
|
||||
// Host administratively prohibited -> Communication with destination administratively prohibited
|
||||
10 => 1,
|
||||
// Network unreachable for ToS -> No route to destination
|
||||
11 => 0,
|
||||
// Host unreachable for ToS -> Address unreachable
|
||||
12 => 3,
|
||||
// Communication administratively prohibited -> Communication with destination administratively prohibited
|
||||
13 => 1,
|
||||
// Host Precedence Violation -> Communication with destination administratively prohibited
|
||||
14 => 1,
|
||||
// Precedence cutoff in effect -> Communication with destination administratively prohibited
|
||||
15 => 1,
|
||||
// All others -> No route to destination
|
||||
_ => 0,
|
||||
}),
|
||||
checksum: 0,
|
||||
payload: input.payload().to_vec(),
|
||||
},
|
||||
// Time Exceeded
|
||||
11 => Icmp {
|
||||
icmp_type: IcmpType(3),
|
||||
icmp_code: IcmpCode(input.get_icmp_code().0),
|
||||
checksum: 0,
|
||||
payload: input.payload().to_vec(),
|
||||
},
|
||||
// Echo Request
|
||||
8 => Icmp {
|
||||
icmp_type: IcmpType(128),
|
||||
icmp_code: IcmpCode(0),
|
||||
checksum: 0,
|
||||
payload: input.payload().to_vec(),
|
||||
},
|
||||
|
||||
// Echo Reply
|
||||
0 => Icmp {
|
||||
icmp_type: IcmpType(129),
|
||||
icmp_code: IcmpCode(0),
|
||||
checksum: 0,
|
||||
payload: input.payload().to_vec(),
|
||||
},
|
||||
_ => {
|
||||
log::warn!("ICMP type {} not supported", input.get_icmp_type().0);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Debug logging
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
{
|
||||
log::debug!("> Input ICMP Type: {}", input.get_icmp_type().0.to_string().bright_cyan());
|
||||
log::debug!("> Input ICMP Code: {}", input.get_icmp_code().0.to_string().bright_cyan());
|
||||
log::debug!("> Output ICMP Type: {}", data.icmp_type.0.to_string().bright_cyan());
|
||||
log::debug!("> Output ICMP Code: {}", data.icmp_code.0.to_string().bright_cyan());
|
||||
}
|
||||
|
||||
// Create new ICMP packet
|
||||
let mut output = MutableIcmpPacket::owned(vec![0u8; IcmpPacket::packet_size(&data)]).unwrap();
|
||||
output.populate(&data);
|
||||
output.set_checksum(icmp::checksum(&output.to_immutable()));
|
||||
|
||||
Icmpv6Packet::owned(output.to_immutable().packet().to_vec())
|
||||
}
|
128
src/nat/mod.rs
128
src/nat/mod.rs
@ -2,19 +2,22 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use bimap::BiMap;
|
||||
use colored::Colorize;
|
||||
use ipnet::{IpAdd, Ipv4Net, Ipv6Net};
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
use pnet_packet::{
|
||||
icmpv6::Icmpv6Packet,
|
||||
ip::IpNextHeaderProtocols,
|
||||
ipv6::{Ipv6Packet, MutableIpv6Packet},
|
||||
Packet, ipv4::{Ipv4Packet, MutableIpv4Packet}, icmp::IcmpPacket,
|
||||
};
|
||||
use tokio::process::Command;
|
||||
use tun_tap::{Iface, Mode};
|
||||
|
||||
use crate::nat::{
|
||||
packet::{xlat_v6_to_v4, IpPacket},
|
||||
utils::bytes_to_hex_str,
|
||||
};
|
||||
use crate::nat::packet::{xlat_v6_to_v4, IpPacket};
|
||||
|
||||
use self::packet::xlat_v4_to_v6;
|
||||
|
||||
mod icmp;
|
||||
mod packet;
|
||||
mod utils;
|
||||
|
||||
/// A cleaner way to execute a CLI command
|
||||
macro_rules! command {
|
||||
@ -25,8 +28,17 @@ macro_rules! command {
|
||||
}}
|
||||
}
|
||||
|
||||
/// Converts bytes to a hex string for debugging
|
||||
fn bytes_to_hex_str(bytes: &[u8]) -> String {
|
||||
bytes
|
||||
.iter()
|
||||
.map(|val| format!("{:02x}", val))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
pub struct Nat64 {
|
||||
/// Handle for the TUN interface
|
||||
/// Handle for the Tun interface
|
||||
interface: Iface,
|
||||
/// Instance IPv4 address
|
||||
instance_v4: Ipv4Addr,
|
||||
@ -89,7 +101,15 @@ impl Nat64 {
|
||||
// Add every IPv4 prefix to the routing table
|
||||
for prefix in ipv4_pool.iter() {
|
||||
log::debug!("Adding route {} via {}", prefix, interface_name);
|
||||
command!("ip", "route", "add", prefix.to_string(), "dev", interface_name).await?;
|
||||
command!(
|
||||
"ip",
|
||||
"route",
|
||||
"add",
|
||||
prefix.to_string(),
|
||||
"dev",
|
||||
interface_name
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Build a reservation list
|
||||
@ -232,6 +252,7 @@ impl Nat64 {
|
||||
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());
|
||||
log::debug!("> Next Header: {}", input_packet.get_next_header().to_string().bright_cyan());
|
||||
}
|
||||
|
||||
// Ignore packets that aren't destined for the NAT instance
|
||||
@ -240,6 +261,12 @@ impl Nat64 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Drop packets with 0 TTL
|
||||
if input_packet.get_ttl() == 0 {
|
||||
log::debug!("{}", "Ignoring packet. TTL is 0".yellow());
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Handle packet translation
|
||||
let output_packet = match input_packet {
|
||||
IpPacket::V4(packet) => {
|
||||
@ -254,17 +281,42 @@ impl Nat64 {
|
||||
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);
|
||||
// Handle inner packet conversion for protocols that don't support both v4 and v6
|
||||
if let Some(packet) = Ipv4Packet::owned(match packet.get_next_level_protocol() {
|
||||
// ICMP must be translated to ICMPv6
|
||||
IpNextHeaderProtocols::Icmp => {
|
||||
if let Some(new_payload) =
|
||||
icmp::icmp_to_icmpv6(&IcmpPacket::new(packet.payload()).unwrap())
|
||||
{
|
||||
// Mutate the input packet
|
||||
let mut packet =
|
||||
MutableIpv4Packet::owned(packet.packet().to_vec()).unwrap();
|
||||
packet.set_next_level_protocol(IpNextHeaderProtocols::Icmpv6);
|
||||
packet.set_payload(&new_payload.packet().to_vec());
|
||||
packet.packet().to_vec()
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the translated packet header
|
||||
log::debug!(
|
||||
"> Translated Header: {}",
|
||||
bytes_to_hex_str(&data[0..40]).bright_cyan()
|
||||
);
|
||||
// By default, packets can be directly fed to the next function
|
||||
_ => packet.packet().to_vec(),
|
||||
}) {
|
||||
// Translate the packet
|
||||
let translated = xlat_v4_to_v6(&packet, new_source, new_dest, true);
|
||||
|
||||
// Log the translated packet header
|
||||
log::debug!(
|
||||
"> Translated Header: {}",
|
||||
bytes_to_hex_str(&translated[0..40]).bright_cyan()
|
||||
);
|
||||
|
||||
// Return the translated packet
|
||||
translated
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Return the translated packet
|
||||
data
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
@ -281,17 +333,41 @@ impl Nat64 {
|
||||
log::debug!("> Mapped IPv4 Destination: {}", new_dest.to_string().bright_cyan());
|
||||
}
|
||||
|
||||
// Translate the packet
|
||||
let data = xlat_v6_to_v4(&packet, new_source, new_dest);
|
||||
// Handle inner packet conversion for protocols that don't support both v4 and v6
|
||||
if let Some(packet) = Ipv6Packet::owned(match packet.get_next_header() {
|
||||
// ICMPv6 must be translated to ICMP
|
||||
IpNextHeaderProtocols::Icmpv6 => {
|
||||
if let Some(new_payload) =
|
||||
icmp::icmpv6_to_icmp(&Icmpv6Packet::new(packet.payload()).unwrap())
|
||||
{
|
||||
// Mutate the input packet
|
||||
let mut packet =
|
||||
MutableIpv6Packet::owned(packet.packet().to_vec()).unwrap();
|
||||
packet.set_next_header(IpNextHeaderProtocols::Icmp);
|
||||
packet.set_payload(&new_payload.packet().to_vec());
|
||||
packet.packet().to_vec()
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the translated packet header
|
||||
log::debug!(
|
||||
"> Translated Header: {}",
|
||||
bytes_to_hex_str(&data[0..20]).bright_cyan()
|
||||
);
|
||||
// By default, packets can be directly fed to the next function
|
||||
_ => packet.packet().to_vec(),
|
||||
}) {
|
||||
// Translate the packet
|
||||
let translated = xlat_v6_to_v4(&packet, new_source, new_dest, true);
|
||||
|
||||
// Return the translated packet
|
||||
data
|
||||
// Log the translated packet header
|
||||
log::debug!(
|
||||
"> Translated Header: {}",
|
||||
bytes_to_hex_str(&translated[0..20]).bright_cyan()
|
||||
);
|
||||
|
||||
// Return the translated packet
|
||||
translated
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
// // use etherparse::{IpHeader, Ipv4Header, Ipv4Extensions};
|
||||
use pnet_packet::{
|
||||
ethernet::EtherTypes::Ipv6,
|
||||
ip::IpNextHeaderProtocol,
|
||||
ipv4::{checksum, Ipv4, Ipv4Packet, MutableIpv4Packet},
|
||||
ipv6::{Ipv6, Ipv6Packet, MutableIpv6Packet},
|
||||
Packet,
|
||||
@ -74,12 +73,29 @@ impl IpPacket<'_> {
|
||||
IpPacket::V6(packet) => packet.packet().len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next header
|
||||
pub fn get_next_header(&self) -> IpNextHeaderProtocol {
|
||||
match self {
|
||||
IpPacket::V4(packet) => packet.get_next_level_protocol(),
|
||||
IpPacket::V6(packet) => packet.get_next_header(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the TTL
|
||||
pub fn get_ttl(&self) -> u8 {
|
||||
match self {
|
||||
IpPacket::V4(packet) => packet.get_ttl(),
|
||||
IpPacket::V6(packet) => packet.get_hop_limit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xlat_v6_to_v4(
|
||||
ipv6_packet: &Ipv6Packet,
|
||||
new_source: Ipv4Addr,
|
||||
new_dest: Ipv4Addr,
|
||||
decr_ttl: bool,
|
||||
) -> Vec<u8> {
|
||||
let data = Ipv4 {
|
||||
version: 4,
|
||||
@ -98,10 +114,15 @@ pub fn xlat_v6_to_v4(
|
||||
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();
|
||||
let mut packet = MutableIpv4Packet::owned(vec![0; 20 + ipv6_packet.payload().len()]).unwrap();
|
||||
packet.populate(&data);
|
||||
packet.set_checksum(checksum(&packet.to_immutable()));
|
||||
|
||||
// Decrement the TTL if needed
|
||||
if decr_ttl {
|
||||
packet.set_ttl(packet.get_ttl() - 1);
|
||||
}
|
||||
|
||||
let mut output = packet.to_immutable().packet().to_vec();
|
||||
// TODO: There is a bug here.. for now, force write header size
|
||||
output[0] = 0x45;
|
||||
@ -112,6 +133,7 @@ pub fn xlat_v4_to_v6(
|
||||
ipv4_packet: &Ipv4Packet,
|
||||
new_source: Ipv6Addr,
|
||||
new_dest: Ipv6Addr,
|
||||
decr_ttl: bool,
|
||||
) -> Vec<u8> {
|
||||
let data = Ipv6 {
|
||||
version: 6,
|
||||
@ -124,8 +146,13 @@ pub fn xlat_v4_to_v6(
|
||||
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();
|
||||
let mut packet = MutableIpv6Packet::owned(vec![0; 40 + ipv4_packet.payload().len()]).unwrap();
|
||||
packet.populate(&data);
|
||||
|
||||
// Decrement the TTL if needed
|
||||
if decr_ttl {
|
||||
packet.set_hop_limit(packet.get_hop_limit() - 1);
|
||||
}
|
||||
|
||||
packet.to_immutable().packet().to_vec()
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use ipnet::Ipv6Net;
|
||||
|
||||
/// Calculates the checksum value for an IPv4 header
|
||||
pub fn ipv4_header_checksum(header: &[u8]) -> u16 {
|
||||
let mut sum = 0u32;
|
||||
|
||||
// Iterate over the header in 16-bit chunks
|
||||
for i in (0..header.len()).step_by(2) {
|
||||
// Combine the two bytes into a 16-bit integer
|
||||
let word = ((header[i] as u16) << 8) | (header[i + 1] as u16);
|
||||
|
||||
// Add to the sum
|
||||
sum = sum.wrapping_add(word as u32);
|
||||
}
|
||||
|
||||
// Fold the carry bits
|
||||
while sum >> 16 != 0 {
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
}
|
||||
|
||||
// Return the checksum
|
||||
!(sum as u16)
|
||||
}
|
||||
|
||||
/// Convert bytes to an IPv6 address
|
||||
pub fn bytes_to_ipv6_addr(bytes: &[u8]) -> Ipv6Addr {
|
||||
assert!(bytes.len() == 16);
|
||||
let mut octets = [0u8; 16];
|
||||
octets.copy_from_slice(bytes);
|
||||
Ipv6Addr::from(octets)
|
||||
}
|
||||
|
||||
/// Convert bytes to an IPv4 address
|
||||
pub fn bytes_to_ipv4_addr(bytes: &[u8]) -> Ipv4Addr {
|
||||
assert!(bytes.len() == 4);
|
||||
let mut octets = [0u8; 4];
|
||||
octets.copy_from_slice(bytes);
|
||||
Ipv4Addr::from(octets)
|
||||
}
|
||||
|
||||
/// Converts bytes to a hex string for debugging
|
||||
pub fn bytes_to_hex_str(bytes: &[u8]) -> String {
|
||||
bytes
|
||||
.iter()
|
||||
.map(|val| format!("{:02x}", val))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
/// Calculate the appropriate IPv6 address that maps to an IPv4 address
|
||||
pub fn ipv4_to_ipv6(v4: &Ipv4Addr, prefix: &Ipv6Net) -> Ipv6Addr {
|
||||
let net_addr_bytes = prefix.network().octets();
|
||||
let v4_bytes = v4.octets();
|
||||
return Ipv6Addr::new(
|
||||
u16::from_be_bytes([net_addr_bytes[0], net_addr_bytes[1]]),
|
||||
u16::from_be_bytes([net_addr_bytes[2], net_addr_bytes[3]]),
|
||||
u16::from_be_bytes([net_addr_bytes[4], net_addr_bytes[5]]),
|
||||
u16::from_be_bytes([net_addr_bytes[6], net_addr_bytes[7]]),
|
||||
u16::from_be_bytes([net_addr_bytes[8], net_addr_bytes[9]]),
|
||||
u16::from_be_bytes([net_addr_bytes[10], net_addr_bytes[11]]),
|
||||
u16::from_be_bytes([v4_bytes[0], v4_bytes[1]]),
|
||||
u16::from_be_bytes([v4_bytes[2], v4_bytes[3]]),
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user