diff --git a/rust/relay/ebpf-turn-router/src/error.rs b/rust/relay/ebpf-turn-router/src/error.rs index 123e3243b..4302f817e 100644 --- a/rust/relay/ebpf-turn-router/src/error.rs +++ b/rust/relay/ebpf-turn-router/src/error.rs @@ -6,7 +6,6 @@ pub enum Error { NotUdp, NotTurn, NotIp, - NoMacAddress, Ipv4PacketWithOptions, NotAChannelDataMessage, BadChannelDataLength, @@ -41,7 +40,6 @@ impl aya_log_ebpf::WriteToBuf for Error { Error::NotUdp => "Not a UDP packet".write(buf), Error::NotTurn => "Not TURN traffic".write(buf), Error::NotIp => "Not an IP packet".write(buf), - Error::NoMacAddress => "No MAC address".write(buf), Error::Ipv4PacketWithOptions => "IPv4 packet has options".write(buf), Error::NotAChannelDataMessage => "Not a channel data message".write(buf), Error::BadChannelDataLength => { diff --git a/rust/relay/ebpf-turn-router/src/eth.rs b/rust/relay/ebpf-turn-router/src/eth.rs index 30f5fb4d2..51cb1f519 100644 --- a/rust/relay/ebpf-turn-router/src/eth.rs +++ b/rust/relay/ebpf-turn-router/src/eth.rs @@ -1,14 +1,9 @@ use aya_ebpf::programs::XdpContext; -use aya_ebpf::{macros::map, maps::HashMap}; use aya_log_ebpf::debug; use network_types::eth::{EthHdr, EtherType}; -use core::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - use crate::{error::Error, ref_mut_at::ref_mut_at}; -const MAX_ETHERNET_MAPPINGS: u32 = 0x100000; - pub struct Eth<'a> { inner: &'a mut EthHdr, ctx: &'a XdpContext, @@ -31,89 +26,29 @@ impl<'a> Eth<'a> { self.inner.ether_type } - pub fn src(&self) -> [u8; 6] { - self.inner.src_addr - } - - pub fn dst(&self) -> [u8; 6] { - self.inner.dst_addr - } - - /// Update the Ethernet header with the appropriate destination MAC address based on the new destination IP. + /// Swap source and destination MAC addresses for TURN traffic. + /// + /// NOTE: This only works when all channel data traffic uses the same next hop for relayed + /// traffic as the one it received the packets from. In all cases where the production relays + /// are deployed, this will be a safe assumption to make. We avoid swapping MAC for traffic + /// passed to userspace in case the above assumption does not hold true. #[inline(always)] - pub fn update(self, new_dst_ip: impl Into) -> Result<(), Error> { - let new_dst_mac = match new_dst_ip.into() { - IpAddr::V4(ip) => get_mac_for_ipv4(ip).ok_or(Error::NoMacAddress)?, - IpAddr::V6(ip) => get_mac_for_ipv6(ip).ok_or(Error::NoMacAddress)?, - }; + pub fn swap_macs(self) -> Result<(), Error> { + let old_src = self.inner.src_addr; + let old_dst = self.inner.dst_addr; - let src = self.src(); - let dst = self.dst(); - - let new_src_mac = self.inner.dst_addr; - self.inner.src_addr = new_src_mac; - self.inner.dst_addr = new_dst_mac; + self.inner.src_addr = old_dst; + self.inner.dst_addr = old_src; debug!( self.ctx, - "ETH header update: src {:mac} -> {:mac}; dst {:mac} -> {:mac}", - src, - new_src_mac, - dst, - new_dst_mac, + "ETH header swap: src {:mac} -> {:mac}; dst {:mac} -> {:mac}", + old_src, + old_dst, + old_dst, + old_src, ); Ok(()) } } - -#[map] -static IP4_TO_MAC: HashMap<[u8; 4], [u8; 6]> = HashMap::with_max_entries(MAX_ETHERNET_MAPPINGS, 0); - -#[map] -static IP6_TO_MAC: HashMap<[u8; 16], [u8; 6]> = HashMap::with_max_entries(MAX_ETHERNET_MAPPINGS, 0); - -pub(crate) fn get_mac_for_ipv4(ip: Ipv4Addr) -> Option<[u8; 6]> { - unsafe { IP4_TO_MAC.get(&ip.octets()).copied() } -} - -pub(crate) fn get_mac_for_ipv6(ip: Ipv6Addr) -> Option<[u8; 6]> { - unsafe { IP6_TO_MAC.get(&ip.octets()).copied() } -} - -pub(crate) fn save_mac_for_ipv4(ip: Ipv4Addr, mac: [u8; 6]) { - let _ = IP4_TO_MAC.insert(&ip.octets(), &mac, 0); -} - -pub(crate) fn save_mac_for_ipv6(ip: Ipv6Addr, mac: [u8; 6]) { - let _ = IP6_TO_MAC.insert(&ip.octets(), &mac, 0); -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Memory overhead of an eBPF map. - /// - /// Determined empirically. - const HASH_MAP_OVERHEAD: f32 = 1.5; - - #[test] - fn hashmaps_are_less_than_100_mb() { - let ipv4_datatypes = 4 + 6; - let ipv6_datatypes = 16 + 6; - - let ipv4_map_size = - ipv4_datatypes as f32 * MAX_ETHERNET_MAPPINGS as f32 * HASH_MAP_OVERHEAD; - let ipv6_map_size = - ipv6_datatypes as f32 * MAX_ETHERNET_MAPPINGS as f32 * HASH_MAP_OVERHEAD; - - let total_map_size = (ipv4_map_size + ipv6_map_size) * 2_f32; - let total_map_size_mb = total_map_size / 1024_f32 / 1024_f32; - - assert!( - total_map_size_mb < 100_f32, - "Map size is {total_map_size_mb} MB" - ); - } -} diff --git a/rust/relay/ebpf-turn-router/src/main.rs b/rust/relay/ebpf-turn-router/src/main.rs index 7e6768df2..ca75004d4 100644 --- a/rust/relay/ebpf-turn-router/src/main.rs +++ b/rust/relay/ebpf-turn-router/src/main.rs @@ -88,7 +88,6 @@ pub fn handle_turn(ctx: XdpContext) -> u32 { | Error::NotTurn | Error::NotAChannelDataMessage | Error::Ipv4PacketWithOptions - | Error::NoMacAddress | Error::UnsupportedChannel(_) | Error::NoEntry(_) => { debug!(&ctx, "Passing packet to userspace: {}", e); @@ -128,9 +127,6 @@ fn try_handle_turn_ipv4(ctx: &XdpContext, eth: Eth) -> Result<(), Error> { // Safety: This is the only instance of `Ip4`. let ipv4 = unsafe { Ip4::parse(ctx) }?; - eth::save_mac_for_ipv4(ipv4.src(), eth.src()); - eth::save_mac_for_ipv4(ipv4.dst(), eth.dst()); - if ipv4.protocol() != IpProto::Udp { return Err(Error::NotUdp); } @@ -191,7 +187,7 @@ fn try_handle_ipv4_channel_data_to_udp( let new_dst = port_and_peer.peer_ip(); let new_ipv4_total_len = ipv4.total_len() - CdHdr::LEN as u16; - eth.update(new_dst)?; + eth.swap_macs()?; let pseudo_header = ipv4.update(new_src, new_dst, new_ipv4_total_len); @@ -231,7 +227,7 @@ fn try_handle_ipv4_udp_to_channel_data( let new_dst = client_and_channel.client_ip(); let new_ipv4_total_len = ipv4.total_len() + CdHdr::LEN as u16; - eth.update(new_dst)?; + eth.swap_macs()?; let pseudo_header = ipv4.update(new_src, new_dst, new_ipv4_total_len); @@ -266,9 +262,6 @@ fn try_handle_turn_ipv6(ctx: &XdpContext, eth: Eth) -> Result<(), Error> { // Safety: This is the only instance of `Ip6` in this scope. let ipv6 = unsafe { Ip6::parse(ctx) }?; - eth::save_mac_for_ipv6(ipv6.src(), eth.src()); - eth::save_mac_for_ipv6(ipv6.dst(), eth.dst()); - if ipv6.protocol() != IpProto::Udp { return Err(Error::NotUdp); } @@ -324,7 +317,7 @@ fn try_handle_ipv6_udp_to_channel_data( let new_dst = client_and_channel.client_ip(); let new_ipv6_total_len = ipv6.payload_len() + CdHdr::LEN as u16; - eth.update(new_dst)?; + eth.swap_macs()?; let pseudo_header = ipv6.update(new_src, new_dst, new_ipv6_total_len); @@ -378,7 +371,7 @@ fn try_handle_ipv6_channel_data_to_udp( let new_dst = port_and_peer.peer_ip(); let new_ipv6_payload_len = ipv6.payload_len() - CdHdr::LEN as u16; - eth.update(new_dst)?; + eth.swap_macs()?; let pseudo_header = ipv6.update(new_src, new_dst, new_ipv6_payload_len);