1

Add experimental profiling support

This commit is contained in:
Evan Pratten 2023-07-19 10:36:53 -04:00
parent c6db02c80a
commit edd83fad99
16 changed files with 100 additions and 22 deletions

View File

@ -12,10 +12,13 @@ license = "GPL-3.0"
keywords = [] keywords = []
categories = [] categories = []
[features]
default = []
profiler = ["profiling/profile-with-puffin", "puffin_http", "puffin"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
protomask-tun = { path = "protomask-tun", version = "0.1.0" }
tokio = { version = "1.29.1", features = [ tokio = { version = "1.29.1", features = [
"macros", "macros",
"rt-multi-thread", "rt-multi-thread",
@ -25,6 +28,9 @@ tokio = { version = "1.29.1", features = [
clap = { version = "4.3.11", features = ["derive"] } clap = { version = "4.3.11", features = ["derive"] }
serde = { version = "1.0.171", features = ["derive"] } serde = { version = "1.0.171", features = ["derive"] }
ipnet = { version = "2.8.0", features = ["serde"] } ipnet = { version = "2.8.0", features = ["serde"] }
puffin_http = { version = "^0.13.0", optional = true }
puffin = { version = "0.12.1", optional = true }
profiling = "1.0.8"
toml = "0.7.6" toml = "0.7.6"
log = "0.4.19" log = "0.4.19"
fern = "0.6.2" fern = "0.6.2"
@ -36,7 +42,7 @@ bimap = "0.6.3"
pnet_packet = "0.33.0" pnet_packet = "0.33.0"
rtnetlink = "0.13.0" rtnetlink = "0.13.0"
futures = "0.3.28" futures = "0.3.28"
protomask-tun = { path = "protomask-tun", version = "0.1.0" } cfg-if = "1.0.0"
[[bin]] [[bin]]
name = "protomask" name = "protomask"

View File

@ -1,5 +1,6 @@
//! This is the entrypoint for `protomask` from the command line. //! This is the entrypoint for `protomask` from the command line.
use cfg_if::cfg_if;
use clap::Parser; use clap::Parser;
use config::Config; use config::Config;
use logging::enable_logger; use logging::enable_logger;
@ -26,6 +27,16 @@ pub async fn main() {
std::process::exit(1); std::process::exit(1);
} }
// Enable the profiler if enabled by build flags
cfg_if! {
if #[cfg(feature = "profiler")] {
let puffin_listen_addr = format!("[::]:{}", puffin_http::DEFAULT_PORT);
log::info!("Puffin HTTP server listening on: {}", puffin_listen_addr);
let _puffin_server = puffin_http::Server::new(&puffin_listen_addr).unwrap();
puffin::set_scopes_on(true);
}
}
// Create the NAT64 instance // Create the NAT64 instance
let mut nat64 = Nat64::new( let mut nat64 = Nat64::new(
config.nat64_prefix, config.nat64_prefix,

17
src/nat/error.rs Normal file
View File

@ -0,0 +1,17 @@
use super::table;
#[derive(Debug, thiserror::Error)]
pub enum Nat64Error {
#[error(transparent)]
TableError(#[from] table::TableError),
#[error(transparent)]
TunError(#[from] protomask_tun::Error),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
PacketHandlingError(#[from] crate::packet::error::PacketError),
#[error(transparent)]
PacketReceiveError(#[from] tokio::sync::broadcast::error::RecvError),
#[error(transparent)]
PacketSendError(#[from] tokio::sync::mpsc::error::SendError<Vec<u8>>),
}

View File

@ -4,6 +4,7 @@ use crate::packet::{
}; };
use self::{ use self::{
error::Nat64Error,
table::Nat64Table, table::Nat64Table,
utils::{embed_address, extract_address}, utils::{embed_address, extract_address},
}; };
@ -15,27 +16,10 @@ use std::{
}; };
use tokio::sync::{broadcast, mpsc}; use tokio::sync::{broadcast, mpsc};
mod error;
mod table; mod table;
mod utils; mod utils;
#[derive(Debug, thiserror::Error)]
pub enum Nat64Error {
#[error(transparent)]
TableError(#[from] table::TableError),
#[error(transparent)]
TunError(#[from] protomask_tun::Error),
#[error(transparent)]
IoError(#[from] std::io::Error),
// #[error(transparent)]
// XlatError(#[from] xlat::PacketTranslationError),
#[error(transparent)]
PacketHandlingError(#[from] crate::packet::error::PacketError),
#[error(transparent)]
PacketReceiveError(#[from] broadcast::error::RecvError),
#[error(transparent)]
PacketSendError(#[from] mpsc::error::SendError<Vec<u8>>),
}
pub struct Nat64 { pub struct Nat64 {
table: Nat64Table, table: Nat64Table,
interface: TunDevice, interface: TunDevice,
@ -81,6 +65,9 @@ impl Nat64 {
// Process packets in a loop // Process packets in a loop
loop { loop {
// Start a new profiler frame
profiling::finish_frame!();
// Try to read a packet // Try to read a packet
match rx.recv().await { match rx.recv().await {
Ok(packet) => { Ok(packet) => {
@ -90,6 +77,8 @@ impl Nat64 {
// Separate logic is needed for handling IPv4 vs IPv6 packets, so a check must be done here // Separate logic is needed for handling IPv4 vs IPv6 packets, so a check must be done here
match packet[0] >> 4 { match packet[0] >> 4 {
4 => { 4 => {
profiling::scope!("Ipv4 to IPv6");
// Parse the packet // Parse the packet
let packet: Ipv4Packet<Vec<u8>> = packet.try_into()?; let packet: Ipv4Packet<Vec<u8>> = packet.try_into()?;
@ -113,14 +102,37 @@ impl Nat64 {
}); });
} }
6 => { 6 => {
profiling::scope!("Ipv6 to IPv4");
// Parse the packet // Parse the packet
let packet: Ipv6Packet<Vec<u8>> = packet.try_into()?; let packet: Ipv6Packet<Vec<u8>> = 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
);
continue;
}
// Get the new source and dest addresses // Get the new source and dest addresses
let new_source = let new_source =
self.table.get_or_assign_ipv4(packet.source_address)?; self.table.get_or_assign_ipv4(packet.source_address)?;
let new_destination = extract_address(packet.destination_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
);
continue;
}
// Spawn a task to process the packet // Spawn a task to process the packet
tokio::spawn(async move { tokio::spawn(async move {
let output = let output =

View File

@ -47,6 +47,7 @@ impl Nat64Table {
} }
/// Make a reservation for an IP address pair for eternity /// Make a reservation for an IP address pair for eternity
#[profiling::function]
pub fn add_infinite_reservation( pub fn add_infinite_reservation(
&mut self, &mut self,
ipv6: Ipv6Addr, ipv6: Ipv6Addr,
@ -68,6 +69,7 @@ impl Nat64Table {
} }
/// Check if a given address exists in the table /// Check if a given address exists in the table
#[profiling::function]
pub fn contains(&self, address: &IpAddr) -> bool { pub fn contains(&self, address: &IpAddr) -> bool {
match address { match address {
IpAddr::V4(ipv4) => self.reservations.contains_right(ipv4), IpAddr::V4(ipv4) => self.reservations.contains_right(ipv4),
@ -76,6 +78,7 @@ impl Nat64Table {
} }
/// Get or assign an IPv4 address for the given IPv6 address /// Get or assign an IPv4 address for the given IPv6 address
#[profiling::function]
pub fn get_or_assign_ipv4(&mut self, ipv6: Ipv6Addr) -> Result<Ipv4Addr, TableError> { pub fn get_or_assign_ipv4(&mut self, ipv6: Ipv6Addr) -> Result<Ipv4Addr, TableError> {
// Prune old reservations // Prune old reservations
self.prune(); self.prune();
@ -110,6 +113,7 @@ impl Nat64Table {
} }
/// Try to find an IPv6 address for the given IPv4 address /// Try to find an IPv6 address for the given IPv4 address
#[profiling::function]
pub fn get_reverse(&mut self, ipv4: Ipv4Addr) -> Result<Ipv6Addr, TableError> { pub fn get_reverse(&mut self, ipv4: Ipv4Addr) -> Result<Ipv6Addr, TableError> {
// Prune old reservations // Prune old reservations
self.prune(); self.prune();
@ -129,11 +133,13 @@ impl Nat64Table {
} }
/// Check if an address is within the IPv4 pool /// Check if an address is within the IPv4 pool
#[profiling::function]
pub fn is_address_within_pool(&self, address: &Ipv4Addr) -> bool { pub fn is_address_within_pool(&self, address: &Ipv4Addr) -> bool {
self.ipv4_pool.iter().any(|net| net.contains(address)) self.ipv4_pool.iter().any(|net| net.contains(address))
} }
/// Calculate the translated version of any address /// Calculate the translated version of any address
#[profiling::function]
pub fn calculate_xlat_addr( pub fn calculate_xlat_addr(
&mut self, &mut self,
input: &IpAddr, input: &IpAddr,
@ -186,6 +192,7 @@ impl Nat64Table {
impl Nat64Table { impl Nat64Table {
/// Prune old reservations /// Prune old reservations
#[profiling::function]
pub fn prune(&mut self) { pub fn prune(&mut self) {
let now = Instant::now(); let now = Instant::now();

View File

@ -29,6 +29,7 @@ where
{ {
type Error = PacketError; type Error = PacketError;
#[profiling::function]
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
// Parse the packet // Parse the packet
let packet = let packet =
@ -43,11 +44,11 @@ where
} }
} }
impl<T> Into<Vec<u8>> for IcmpPacket<T> impl<T> Into<Vec<u8>> for IcmpPacket<T>
where where
T: Into<Vec<u8>>, T: Into<Vec<u8>>,
{ {
#[profiling::function]
fn into(self) -> Vec<u8> { fn into(self) -> Vec<u8> {
// Convert the payload into raw bytes // Convert the payload into raw bytes
let payload: Vec<u8> = self.payload.into(); let payload: Vec<u8> = self.payload.into();

View File

@ -42,6 +42,7 @@ where
T: From<Vec<u8>>, T: From<Vec<u8>>,
{ {
/// Construct a new ICMPv6 packet from raw bytes /// Construct a new ICMPv6 packet from raw bytes
#[profiling::function]
pub fn new_from_bytes( pub fn new_from_bytes(
bytes: &[u8], bytes: &[u8],
source_address: Ipv6Addr, source_address: Ipv6Addr,
@ -64,6 +65,7 @@ where
impl Icmpv6Packet<RawBytes> { impl Icmpv6Packet<RawBytes> {
/// Construct a new ICMPv6 packet with a raw payload from raw bytes /// Construct a new ICMPv6 packet with a raw payload from raw bytes
#[profiling::function]
pub fn new_from_bytes_raw_payload( pub fn new_from_bytes_raw_payload(
bytes: &[u8], bytes: &[u8],
source_address: Ipv6Addr, source_address: Ipv6Addr,
@ -88,6 +90,7 @@ impl<T> Into<Vec<u8>> for Icmpv6Packet<T>
where where
T: Into<Vec<u8>>, T: Into<Vec<u8>>,
{ {
#[profiling::function]
fn into(self) -> Vec<u8> { fn into(self) -> Vec<u8> {
// Convert the payload into raw bytes // Convert the payload into raw bytes
let payload: Vec<u8> = self.payload.into(); let payload: Vec<u8> = self.payload.into();

View File

@ -53,6 +53,7 @@ impl<T> Ipv4Packet<T> {
} }
} }
#[profiling::function]
fn options_length_words(&self) -> u8 { fn options_length_words(&self) -> u8 {
self.options self.options
.iter() .iter()
@ -68,6 +69,7 @@ where
{ {
type Error = PacketError; type Error = PacketError;
#[profiling::function]
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
// Parse the packet // Parse the packet
let packet = let packet =
@ -94,6 +96,7 @@ impl<T> Into<Vec<u8>> for Ipv4Packet<T>
where where
T: Into<Vec<u8>> + Clone, T: Into<Vec<u8>> + Clone,
{ {
#[profiling::function]
fn into(self) -> Vec<u8> { fn into(self) -> Vec<u8> {
// Convert the payload into raw bytes // Convert the payload into raw bytes
let payload: Vec<u8> = self.payload.clone().into(); let payload: Vec<u8> = self.payload.clone().into();

View File

@ -44,6 +44,7 @@ where
{ {
type Error = PacketError; type Error = PacketError;
#[profiling::function]
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
// Parse the packet // Parse the packet
let packet = let packet =
@ -66,6 +67,7 @@ impl<T> Into<Vec<u8>> for Ipv6Packet<T>
where where
T: Into<Vec<u8>>, T: Into<Vec<u8>>,
{ {
#[profiling::function]
fn into(self) -> Vec<u8> { fn into(self) -> Vec<u8> {
// Convert the payload into raw bytes // Convert the payload into raw bytes
let payload: Vec<u8> = self.payload.into(); let payload: Vec<u8> = self.payload.into();

View File

@ -113,6 +113,7 @@ where
T: From<Vec<u8>>, T: From<Vec<u8>>,
{ {
/// Construct a new TCP packet from bytes /// Construct a new TCP packet from bytes
#[profiling::function]
pub fn new_from_bytes( pub fn new_from_bytes(
bytes: &[u8], bytes: &[u8],
source_address: IpAddr, source_address: IpAddr,
@ -147,6 +148,7 @@ where
impl TcpPacket<RawBytes> { impl TcpPacket<RawBytes> {
/// Construct a new TCP packet with a raw payload from bytes /// Construct a new TCP packet with a raw payload from bytes
#[profiling::function]
pub fn new_from_bytes_raw_payload( pub fn new_from_bytes_raw_payload(
bytes: &[u8], bytes: &[u8],
source_address: IpAddr, source_address: IpAddr,
@ -183,6 +185,7 @@ impl<T> Into<Vec<u8>> for TcpPacket<T>
where where
T: Into<Vec<u8>>, T: Into<Vec<u8>>,
{ {
#[profiling::function]
fn into(self) -> Vec<u8> { fn into(self) -> Vec<u8> {
// Get the options length in words // Get the options length in words
let options_length = self.options_length(); let options_length = self.options_length();

View File

@ -84,6 +84,7 @@ where
T: From<Vec<u8>>, T: From<Vec<u8>>,
{ {
/// Construct a new UDP packet from bytes /// Construct a new UDP packet from bytes
#[profiling::function]
pub fn new_from_bytes( pub fn new_from_bytes(
bytes: &[u8], bytes: &[u8],
source_address: IpAddr, source_address: IpAddr,
@ -112,6 +113,7 @@ where
impl UdpPacket<RawBytes> { impl UdpPacket<RawBytes> {
/// Construct a new UDP packet with a raw payload from bytes /// Construct a new UDP packet with a raw payload from bytes
#[profiling::function]
pub fn new_from_bytes_raw_payload( pub fn new_from_bytes_raw_payload(
bytes: &[u8], bytes: &[u8],
source_address: IpAddr, source_address: IpAddr,
@ -142,6 +144,7 @@ impl<T> Into<Vec<u8>> for UdpPacket<T>
where where
T: Into<Vec<u8>>, T: Into<Vec<u8>>,
{ {
#[profiling::function]
fn into(self) -> Vec<u8> { fn into(self) -> Vec<u8> {
// Convert the payload into raw bytes // Convert the payload into raw bytes
let payload: Vec<u8> = self.payload.into(); let payload: Vec<u8> = self.payload.into();

View File

@ -12,6 +12,7 @@ use super::ip::{translate_ipv4_to_ipv6, translate_ipv6_to_ipv4};
mod type_code; mod type_code;
/// Translates an ICMP packet to an ICMPv6 packet /// Translates an ICMP packet to an ICMPv6 packet
#[profiling::function]
pub fn translate_icmp_to_icmpv6( pub fn translate_icmp_to_icmpv6(
input: IcmpPacket<RawBytes>, input: IcmpPacket<RawBytes>,
new_source: Ipv6Addr, new_source: Ipv6Addr,
@ -55,6 +56,7 @@ pub fn translate_icmp_to_icmpv6(
} }
/// Translates an ICMPv6 packet to an ICMP packet /// Translates an ICMPv6 packet to an ICMP packet
#[profiling::function]
pub fn translate_icmpv6_to_icmp( pub fn translate_icmpv6_to_icmp(
input: Icmpv6Packet<RawBytes>, input: Icmpv6Packet<RawBytes>,
new_source: Ipv4Addr, new_source: Ipv4Addr,
@ -83,7 +85,7 @@ pub fn translate_icmpv6_to_icmp(
buffer.extend_from_slice(&inner_payload); buffer.extend_from_slice(&inner_payload);
buffer buffer
}) })
}, }
_ => input.payload, _ => input.payload,
}; };

View File

@ -8,6 +8,7 @@ use pnet_packet::{
use crate::packet::error::PacketError; use crate::packet::error::PacketError;
/// Best effort translation from an ICMP type and code to an ICMPv6 type and code /// Best effort translation from an ICMP type and code to an ICMPv6 type and code
#[profiling::function]
pub fn translate_type_and_code_4_to_6( pub fn translate_type_and_code_4_to_6(
icmp_type: IcmpType, icmp_type: IcmpType,
icmp_code: IcmpCode, icmp_code: IcmpCode,
@ -55,6 +56,7 @@ pub fn translate_type_and_code_4_to_6(
} }
/// Best effort translation from an ICMPv6 type and code to an ICMP type and code /// Best effort translation from an ICMPv6 type and code to an ICMP type and code
#[profiling::function]
pub fn translate_type_and_code_6_to_4( pub fn translate_type_and_code_6_to_4(
icmp_type: Icmpv6Type, icmp_type: Icmpv6Type,
icmp_code: Icmpv6Code, icmp_code: Icmpv6Code,

View File

@ -17,6 +17,7 @@ use super::{
}; };
/// Translates an IPv4 packet to an IPv6 packet /// Translates an IPv4 packet to an IPv6 packet
#[profiling::function]
pub fn translate_ipv4_to_ipv6( pub fn translate_ipv4_to_ipv6(
input: Ipv4Packet<Vec<u8>>, input: Ipv4Packet<Vec<u8>>,
new_source: Ipv6Addr, new_source: Ipv6Addr,
@ -69,6 +70,7 @@ pub fn translate_ipv4_to_ipv6(
} }
/// Translates an IPv6 packet to an IPv4 packet /// Translates an IPv6 packet to an IPv4 packet
#[profiling::function]
pub fn translate_ipv6_to_ipv4( pub fn translate_ipv6_to_ipv4(
input: Ipv6Packet<Vec<u8>>, input: Ipv6Packet<Vec<u8>>,
new_source: Ipv4Addr, new_source: Ipv4Addr,

View File

@ -6,6 +6,7 @@ use crate::packet::{
}; };
/// Translates an IPv4 TCP packet to an IPv6 TCP packet /// Translates an IPv4 TCP packet to an IPv6 TCP packet
#[profiling::function]
pub fn translate_tcp4_to_tcp6( pub fn translate_tcp4_to_tcp6(
input: TcpPacket<RawBytes>, input: TcpPacket<RawBytes>,
new_source_addr: Ipv6Addr, new_source_addr: Ipv6Addr,
@ -26,6 +27,7 @@ pub fn translate_tcp4_to_tcp6(
} }
/// Translates an IPv6 TCP packet to an IPv4 TCP packet /// Translates an IPv6 TCP packet to an IPv4 TCP packet
#[profiling::function]
pub fn translate_tcp6_to_tcp4( pub fn translate_tcp6_to_tcp4(
input: TcpPacket<RawBytes>, input: TcpPacket<RawBytes>,
new_source_addr: Ipv4Addr, new_source_addr: Ipv4Addr,

View File

@ -6,6 +6,7 @@ use crate::packet::{
}; };
/// Translates an IPv4 UDP packet to an IPv6 UDP packet /// Translates an IPv4 UDP packet to an IPv6 UDP packet
#[profiling::function]
pub fn translate_udp4_to_udp6( pub fn translate_udp4_to_udp6(
input: UdpPacket<RawBytes>, input: UdpPacket<RawBytes>,
new_source_addr: Ipv6Addr, new_source_addr: Ipv6Addr,
@ -20,6 +21,7 @@ pub fn translate_udp4_to_udp6(
} }
/// Translates an IPv6 UDP packet to an IPv4 UDP packet /// Translates an IPv6 UDP packet to an IPv4 UDP packet
#[profiling::function]
pub fn translate_udp6_to_udp4( pub fn translate_udp6_to_udp4(
input: UdpPacket<RawBytes>, input: UdpPacket<RawBytes>,
new_source_addr: Ipv4Addr, new_source_addr: Ipv4Addr,