mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
chore(eBPF): minor polish (#10282)
Some follow-up polish for the eBPF module: - Changes the cfg's to also include Linux, allowing rust-analyzer to assist with auto-complete etc. - Moves code to sub-modules of `try_handle_turn`, removing the need for making them conditional. - Move all maps to sub-modules to allow for a single place to put comments: In the module documentation at the top. - Removes interface IP learning, these are now configured via env variables.
This commit is contained in:
@@ -4,24 +4,19 @@ version = "0.1.0"
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["aya-ebpf", "aya-log-ebpf", "ebpf-shared", "network-types"]
|
||||
build = ["which"]
|
||||
development = ["hex-literal", "ip-packet"]
|
||||
|
||||
[[bin]]
|
||||
name = "ebpf-turn-router-main" # This needs to be different from the package name otherwise the build-script fails to differentiate between the directory it is built in and the actual binary.
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
[target.'cfg(any(target_arch = "bpf", target_os = "linux"))'.dependencies]
|
||||
aya-ebpf = { workspace = true }
|
||||
aya-log-ebpf = { workspace = true }
|
||||
ebpf-shared = { workspace = true }
|
||||
network-types = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
[target.'cfg(target_os = "linux")'.build-dependencies]
|
||||
which = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||
hex-literal = { workspace = true }
|
||||
ip-packet = { workspace = true }
|
||||
|
||||
@@ -9,28 +9,15 @@ fn main() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Include modules only for BPF target
|
||||
#[cfg(target_arch = "bpf")]
|
||||
mod channel_data;
|
||||
#[cfg(target_arch = "bpf")]
|
||||
mod checksum;
|
||||
#[cfg(target_arch = "bpf")]
|
||||
mod error;
|
||||
#[cfg(target_arch = "bpf")]
|
||||
mod ref_mut_at;
|
||||
#[cfg(target_arch = "bpf")]
|
||||
mod stats;
|
||||
#[cfg(target_arch = "bpf")]
|
||||
#[cfg(any(target_arch = "bpf", target_os = "linux"))]
|
||||
mod try_handle_turn;
|
||||
|
||||
// Everything below is only for BPF target
|
||||
#[cfg(target_arch = "bpf")]
|
||||
#[cfg(any(target_arch = "bpf", target_os = "linux"))]
|
||||
#[aya_ebpf::macros::xdp]
|
||||
// Per-CPU data structures to learn relay interface addresses
|
||||
pub fn handle_turn(ctx: aya_ebpf::programs::XdpContext) -> u32 {
|
||||
use aya_ebpf::bindings::xdp_action;
|
||||
use aya_log_ebpf::{debug, warn};
|
||||
use error::Error;
|
||||
use try_handle_turn::Error;
|
||||
|
||||
try_handle_turn::try_handle_turn(&ctx).unwrap_or_else(|e| match e {
|
||||
Error::NotIp | Error::NotUdp => xdp_action::XDP_PASS,
|
||||
|
||||
@@ -1,68 +1,34 @@
|
||||
use crate::channel_data::CdHdr;
|
||||
use crate::checksum::ChecksumUpdate;
|
||||
use crate::error::Error;
|
||||
use crate::error::SupportedChannel;
|
||||
use crate::ref_mut_at::ref_mut_at;
|
||||
use aya_ebpf::{
|
||||
bindings::xdp_action,
|
||||
helpers::bpf_xdp_adjust_head,
|
||||
macros::map,
|
||||
maps::{HashMap, PerCpuArray},
|
||||
programs::XdpContext,
|
||||
};
|
||||
pub use error::Error;
|
||||
|
||||
use aya_ebpf::{bindings::xdp_action, helpers::bpf_xdp_adjust_head, programs::XdpContext};
|
||||
use aya_log_ebpf::*;
|
||||
use core::net::{Ipv4Addr, Ipv6Addr};
|
||||
use ebpf_shared::{
|
||||
ClientAndChannelV4, ClientAndChannelV6, InterfaceAddressV4, InterfaceAddressV6, PortAndPeerV4,
|
||||
PortAndPeerV6,
|
||||
};
|
||||
use channel_data::CdHdr;
|
||||
use checksum::ChecksumUpdate;
|
||||
use ebpf_shared::{ClientAndChannelV4, ClientAndChannelV6, PortAndPeerV4, PortAndPeerV6};
|
||||
use error::SupportedChannel;
|
||||
use network_types::{
|
||||
eth::{EthHdr, EtherType},
|
||||
ip::{IpProto, Ipv4Hdr, Ipv6Hdr},
|
||||
udp::UdpHdr,
|
||||
};
|
||||
use ref_mut_at::ref_mut_at;
|
||||
|
||||
const NUM_ENTRIES: u32 = 0x10000;
|
||||
const LOWER_PORT: u16 = 49152; // Lower bound for TURN UDP ports
|
||||
const UPPER_PORT: u16 = 65535; // Upper bound for TURN UDP ports
|
||||
const CHAN_START: u16 = 0x4000; // Channel number start
|
||||
const CHAN_END: u16 = 0x7FFF; // Channel number end
|
||||
mod channel_data;
|
||||
mod channel_maps;
|
||||
mod checksum;
|
||||
mod error;
|
||||
mod interface;
|
||||
mod ref_mut_at;
|
||||
mod stats;
|
||||
|
||||
// SAFETY: Testing has shown that these maps are safe to use as long as we aren't
|
||||
// writing to them from multiple threads at the same time. Since we only update these
|
||||
// from the single-threaded eventloop in userspace, we are ok.
|
||||
// See https://github.com/firezone/firezone/issues/10138#issuecomment-3186074350
|
||||
|
||||
#[map]
|
||||
static CHAN_TO_UDP_44: HashMap<ClientAndChannelV4, PortAndPeerV4> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
static UDP_TO_CHAN_44: HashMap<PortAndPeerV4, ClientAndChannelV4> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
static CHAN_TO_UDP_66: HashMap<ClientAndChannelV6, PortAndPeerV6> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
static UDP_TO_CHAN_66: HashMap<PortAndPeerV6, ClientAndChannelV6> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
static CHAN_TO_UDP_46: HashMap<ClientAndChannelV4, PortAndPeerV6> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
static UDP_TO_CHAN_46: HashMap<PortAndPeerV4, ClientAndChannelV6> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
static CHAN_TO_UDP_64: HashMap<ClientAndChannelV6, PortAndPeerV4> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
static UDP_TO_CHAN_64: HashMap<PortAndPeerV6, ClientAndChannelV4> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
|
||||
// Per-CPU data structures to learn relay interface addresses
|
||||
#[map]
|
||||
static INT_ADDR_V4: PerCpuArray<InterfaceAddressV4> = PerCpuArray::with_max_entries(1, 0);
|
||||
#[map]
|
||||
static INT_ADDR_V6: PerCpuArray<InterfaceAddressV6> = PerCpuArray::with_max_entries(1, 0);
|
||||
/// Lower bound for TURN UDP ports
|
||||
const LOWER_PORT: u16 = 49152;
|
||||
/// Upper bound for TURN UDP ports
|
||||
const UPPER_PORT: u16 = 65535;
|
||||
/// Channel number start
|
||||
const CHAN_START: u16 = 0x4000;
|
||||
/// Channel number end
|
||||
const CHAN_END: u16 = 0x7FFF;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_handle_turn(ctx: &XdpContext) -> Result<u32, Error> {
|
||||
@@ -84,8 +50,6 @@ fn try_handle_turn_ipv4(ctx: &XdpContext) -> Result<(), Error> {
|
||||
// SAFETY: The offset must point to the start of a valid `Ipv4Hdr`.
|
||||
let ipv4 = unsafe { ref_mut_at::<Ipv4Hdr>(ctx, EthHdr::LEN)? };
|
||||
|
||||
learn_interface_ipv4_address(ipv4)?;
|
||||
|
||||
if ipv4.proto != IpProto::Udp {
|
||||
return Err(Error::NotUdp);
|
||||
}
|
||||
@@ -111,14 +75,14 @@ fn try_handle_turn_ipv4(ctx: &XdpContext) -> Result<(), Error> {
|
||||
|
||||
if (LOWER_PORT..=UPPER_PORT).contains(&udp.dest()) {
|
||||
try_handle_ipv4_udp_to_channel_data(ctx)?;
|
||||
crate::stats::emit_data_relayed(ctx, udp_payload_len);
|
||||
stats::emit_data_relayed(ctx, udp_payload_len);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if udp.dest() == 3478 {
|
||||
try_handle_ipv4_channel_data_to_udp(ctx)?;
|
||||
crate::stats::emit_data_relayed(ctx, udp_payload_len - CdHdr::LEN as u16);
|
||||
stats::emit_data_relayed(ctx, udp_payload_len - CdHdr::LEN as u16);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
@@ -131,8 +95,6 @@ fn try_handle_turn_ipv6(ctx: &XdpContext) -> Result<(), Error> {
|
||||
// SAFETY: The offset must point to the start of a valid `Ipv6Hdr`.
|
||||
let ipv6 = unsafe { ref_mut_at::<Ipv6Hdr>(ctx, EthHdr::LEN)? };
|
||||
|
||||
learn_interface_ipv6_address(ipv6)?;
|
||||
|
||||
if ipv6.next_hdr != IpProto::Udp {
|
||||
return Err(Error::NotUdp);
|
||||
}
|
||||
@@ -153,14 +115,14 @@ fn try_handle_turn_ipv6(ctx: &XdpContext) -> Result<(), Error> {
|
||||
|
||||
if (LOWER_PORT..=UPPER_PORT).contains(&udp.dest()) {
|
||||
try_handle_ipv6_udp_to_channel_data(ctx)?;
|
||||
crate::stats::emit_data_relayed(ctx, udp_payload_len);
|
||||
stats::emit_data_relayed(ctx, udp_payload_len);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if udp.dest() == 3478 {
|
||||
try_handle_ipv6_channel_data_to_udp(ctx)?;
|
||||
crate::stats::emit_data_relayed(ctx, udp_payload_len - CdHdr::LEN as u16);
|
||||
stats::emit_data_relayed(ctx, udp_payload_len - CdHdr::LEN as u16);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
@@ -168,42 +130,6 @@ fn try_handle_turn_ipv6(ctx: &XdpContext) -> Result<(), Error> {
|
||||
Err(Error::NotTurn)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn learn_interface_ipv4_address(ipv4: &Ipv4Hdr) -> Result<(), Error> {
|
||||
let interface_addr = INT_ADDR_V4
|
||||
.get_ptr_mut(0)
|
||||
.ok_or(Error::InterfaceIpv4AddressAccessFailed)?;
|
||||
|
||||
let dst_ip = ipv4.dst_addr();
|
||||
|
||||
// SAFETY: These are per-cpu maps so we don't need to worry about thread safety.
|
||||
unsafe {
|
||||
if (*interface_addr).get().is_none() {
|
||||
(*interface_addr).set(dst_ip);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn learn_interface_ipv6_address(ipv6: &Ipv6Hdr) -> Result<(), Error> {
|
||||
let interface_addr = INT_ADDR_V6
|
||||
.get_ptr_mut(0)
|
||||
.ok_or(Error::InterfaceIpv6AddressAccessFailed)?;
|
||||
|
||||
let dst_ip = ipv6.dst_addr();
|
||||
|
||||
// SAFETY: These are per-cpu maps so we don't need to worry about thread safety.
|
||||
unsafe {
|
||||
if (*interface_addr).get().is_none() {
|
||||
(*interface_addr).set(dst_ip);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_handle_ipv4_udp_to_channel_data(ctx: &XdpContext) -> Result<(), Error> {
|
||||
// SAFETY: The offset must point to the start of a valid `Ipv4Hdr`.
|
||||
@@ -215,13 +141,13 @@ fn try_handle_ipv4_udp_to_channel_data(ctx: &XdpContext) -> Result<(), Error> {
|
||||
let key = PortAndPeerV4::new(ipv4.src_addr(), udp.dest(), udp.source());
|
||||
|
||||
// SAFETY: We only write to these using a single thread in userspace.
|
||||
if let Some(client_and_channel) = unsafe { UDP_TO_CHAN_44.get(&key) } {
|
||||
if let Some(client_and_channel) = unsafe { channel_maps::UDP_TO_CHAN_44.get(&key) } {
|
||||
handle_ipv4_udp_to_ipv4_channel(ctx, client_and_channel)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// SAFETY: We only write to these using a single thread in userspace.
|
||||
if let Some(client_and_channel) = unsafe { UDP_TO_CHAN_46.get(&key) } {
|
||||
if let Some(client_and_channel) = unsafe { channel_maps::UDP_TO_CHAN_46.get(&key) } {
|
||||
handle_ipv4_udp_to_ipv6_channel(ctx, client_and_channel)?;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -257,14 +183,14 @@ fn try_handle_ipv4_channel_data_to_udp(ctx: &XdpContext) -> Result<(), Error> {
|
||||
let key = ClientAndChannelV4::new(ipv4.src_addr(), udp.source(), channel_number);
|
||||
|
||||
// SAFETY: We only write to these using a single thread in userspace.
|
||||
if let Some(port_and_peer) = unsafe { CHAN_TO_UDP_44.get(&key) } {
|
||||
if let Some(port_and_peer) = unsafe { channel_maps::CHAN_TO_UDP_44.get(&key) } {
|
||||
// IPv4 to IPv4 - existing logic
|
||||
handle_ipv4_channel_to_ipv4_udp(ctx, port_and_peer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// SAFETY: We only write to these using a single thread in userspace.
|
||||
if let Some(port_and_peer) = unsafe { CHAN_TO_UDP_46.get(&key) } {
|
||||
if let Some(port_and_peer) = unsafe { channel_maps::CHAN_TO_UDP_46.get(&key) } {
|
||||
handle_ipv4_channel_to_ipv6_udp(ctx, port_and_peer)?;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -283,13 +209,13 @@ fn try_handle_ipv6_udp_to_channel_data(ctx: &XdpContext) -> Result<(), Error> {
|
||||
let key = PortAndPeerV6::new(ipv6.src_addr(), udp.dest(), udp.source());
|
||||
|
||||
// SAFETY: We only write to these using a single thread in userspace.
|
||||
if let Some(client_and_channel) = unsafe { UDP_TO_CHAN_66.get(&key) } {
|
||||
if let Some(client_and_channel) = unsafe { channel_maps::UDP_TO_CHAN_66.get(&key) } {
|
||||
handle_ipv6_udp_to_ipv6_channel(ctx, client_and_channel)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// SAFETY: We only write to these using a single thread in userspace.
|
||||
if let Some(client_and_channel) = unsafe { UDP_TO_CHAN_64.get(&key) } {
|
||||
if let Some(client_and_channel) = unsafe { channel_maps::UDP_TO_CHAN_64.get(&key) } {
|
||||
handle_ipv6_udp_to_ipv4_channel(ctx, client_and_channel)?;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -325,13 +251,13 @@ fn try_handle_ipv6_channel_data_to_udp(ctx: &XdpContext) -> Result<(), Error> {
|
||||
let key = ClientAndChannelV6::new(ipv6.src_addr(), udp.source(), u16::from_be_bytes(cd.number));
|
||||
|
||||
// SAFETY: We only write to these using a single thread in userspace.
|
||||
if let Some(port_and_peer) = unsafe { CHAN_TO_UDP_66.get(&key) } {
|
||||
if let Some(port_and_peer) = unsafe { channel_maps::CHAN_TO_UDP_66.get(&key) } {
|
||||
handle_ipv6_channel_to_ipv6_udp(ctx, port_and_peer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// SAFETY: We only write to these using a single thread in userspace.
|
||||
if let Some(port_and_peer) = unsafe { CHAN_TO_UDP_64.get(&key) } {
|
||||
if let Some(port_and_peer) = unsafe { channel_maps::CHAN_TO_UDP_64.get(&key) } {
|
||||
handle_ipv6_channel_to_ipv4_udp(ctx, port_and_peer)?;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -556,7 +482,7 @@ fn handle_ipv4_udp_to_ipv6_channel(
|
||||
// 2. IPv4 -> IPv6 header
|
||||
//
|
||||
|
||||
let new_ipv6_src = get_interface_ipv6_address()?;
|
||||
let new_ipv6_src = interface::ipv6_address()?;
|
||||
let new_ipv6_dst = client_and_channel.client_ip();
|
||||
let new_ipv6_len = old_ipv4_len - Ipv4Hdr::LEN as u16 + CdHdr::LEN as u16;
|
||||
|
||||
@@ -850,7 +776,7 @@ fn handle_ipv4_channel_to_ipv6_udp(
|
||||
// 2. IPv6 header
|
||||
//
|
||||
|
||||
let new_ipv6_src = get_interface_ipv6_address()?;
|
||||
let new_ipv6_src = interface::ipv6_address()?;
|
||||
let new_ipv6_dst = port_and_peer.peer_ip();
|
||||
let new_udp_len = old_udp_len - CdHdr::LEN as u16;
|
||||
|
||||
@@ -1094,7 +1020,7 @@ fn handle_ipv6_udp_to_ipv4_channel(
|
||||
// 2. IPv6 -> IPv4 header
|
||||
//
|
||||
|
||||
let new_ipv4_src = get_interface_ipv4_address()?;
|
||||
let new_ipv4_src = interface::ipv4_address()?;
|
||||
let new_ipv4_dst = client_and_channel.client_ip();
|
||||
let new_udp_len = old_udp_len + CdHdr::LEN as u16;
|
||||
let new_ipv4_len = Ipv4Hdr::LEN as u16 + new_udp_len;
|
||||
@@ -1118,7 +1044,7 @@ fn handle_ipv6_udp_to_ipv4_channel(
|
||||
ipv4.set_dst_addr(new_ipv4_dst);
|
||||
|
||||
// Calculate fresh checksum
|
||||
let check = crate::checksum::new_ipv4(ipv4);
|
||||
let check = checksum::new_ipv4(ipv4);
|
||||
ipv4.set_checksum(check);
|
||||
|
||||
//
|
||||
@@ -1366,7 +1292,7 @@ fn handle_ipv6_channel_to_ipv4_udp(
|
||||
// 2. IPv6 -> IPv4 header
|
||||
//
|
||||
|
||||
let new_ipv4_src = get_interface_ipv4_address()?;
|
||||
let new_ipv4_src = interface::ipv4_address()?;
|
||||
let new_ipv4_dst = port_and_peer.peer_ip();
|
||||
let new_ipv4_len = old_udp_len - CdHdr::LEN as u16 + Ipv4Hdr::LEN as u16;
|
||||
|
||||
@@ -1389,7 +1315,7 @@ fn handle_ipv6_channel_to_ipv4_udp(
|
||||
ipv4.set_dst_addr(new_ipv4_dst);
|
||||
|
||||
// Calculate fresh checksum
|
||||
let check = crate::checksum::new_ipv4(ipv4);
|
||||
let check = checksum::new_ipv4(ipv4);
|
||||
ipv4.set_checksum(check);
|
||||
|
||||
//
|
||||
@@ -1443,55 +1369,3 @@ fn adjust_head(ctx: &XdpContext, size: i32) -> Result<(), Error> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_interface_ipv4_address() -> Result<Ipv4Addr, Error> {
|
||||
let interface_addr = INT_ADDR_V4
|
||||
.get_ptr_mut(0)
|
||||
.ok_or(Error::InterfaceIpv4AddressAccessFailed)?;
|
||||
|
||||
// SAFETY: This comes from a per-cpu data structure so we can safely access it.
|
||||
let addr = unsafe { *interface_addr };
|
||||
|
||||
addr.get().ok_or(Error::InterfaceIpv4AddressNotConfigured)
|
||||
}
|
||||
|
||||
fn get_interface_ipv6_address() -> Result<Ipv6Addr, Error> {
|
||||
let interface_addr = INT_ADDR_V6
|
||||
.get_ptr_mut(0)
|
||||
.ok_or(Error::InterfaceIpv6AddressAccessFailed)?;
|
||||
|
||||
// SAFETY: This comes from a per-cpu data structure so we can safely access it.
|
||||
let addr = unsafe { *interface_addr };
|
||||
|
||||
addr.get().ok_or(Error::InterfaceIpv6AddressNotConfigured)
|
||||
}
|
||||
|
||||
#[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_11_mb() {
|
||||
let ipv4_datatypes =
|
||||
core::mem::size_of::<PortAndPeerV4>() + core::mem::size_of::<ClientAndChannelV4>();
|
||||
let ipv6_datatypes =
|
||||
core::mem::size_of::<PortAndPeerV6>() + core::mem::size_of::<ClientAndChannelV6>();
|
||||
|
||||
let ipv4_map_size = ipv4_datatypes as f32 * NUM_ENTRIES as f32 * HASH_MAP_OVERHEAD;
|
||||
let ipv6_map_size = ipv6_datatypes as f32 * NUM_ENTRIES 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 < 11_f32,
|
||||
"Total map size = {total_map_size_mb} MB"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//! Houses all combinations of IPv4 <> IPv6 and Channel <> UDP mappings.
|
||||
//!
|
||||
//! Testing has shown that these maps are safe to use as long as we aren't
|
||||
//! writing to them from multiple threads at the same time. Since we only update these
|
||||
//! from the single-threaded eventloop in userspace, we are ok.
|
||||
//! See <https://github.com/firezone/firezone/issues/10138#issuecomment-3186074350>.
|
||||
|
||||
use aya_ebpf::{macros::map, maps::HashMap};
|
||||
use ebpf_shared::{ClientAndChannelV4, ClientAndChannelV6, PortAndPeerV4, PortAndPeerV6};
|
||||
|
||||
const NUM_ENTRIES: u32 = 0x10000;
|
||||
|
||||
#[map]
|
||||
pub static CHAN_TO_UDP_44: HashMap<ClientAndChannelV4, PortAndPeerV4> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
pub static UDP_TO_CHAN_44: HashMap<PortAndPeerV4, ClientAndChannelV4> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
pub static CHAN_TO_UDP_66: HashMap<ClientAndChannelV6, PortAndPeerV6> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
pub static UDP_TO_CHAN_66: HashMap<PortAndPeerV6, ClientAndChannelV6> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
pub static CHAN_TO_UDP_46: HashMap<ClientAndChannelV4, PortAndPeerV6> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
pub static UDP_TO_CHAN_46: HashMap<PortAndPeerV4, ClientAndChannelV6> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
pub static CHAN_TO_UDP_64: HashMap<ClientAndChannelV6, PortAndPeerV4> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 0);
|
||||
#[map]
|
||||
pub static UDP_TO_CHAN_64: HashMap<PortAndPeerV6, ClientAndChannelV4> =
|
||||
HashMap::with_max_entries(NUM_ENTRIES, 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_11_mb() {
|
||||
let ipv4_datatypes =
|
||||
core::mem::size_of::<PortAndPeerV4>() + core::mem::size_of::<ClientAndChannelV4>();
|
||||
let ipv6_datatypes =
|
||||
core::mem::size_of::<PortAndPeerV6>() + core::mem::size_of::<ClientAndChannelV6>();
|
||||
|
||||
let ipv4_map_size = ipv4_datatypes as f32 * NUM_ENTRIES as f32 * HASH_MAP_OVERHEAD;
|
||||
let ipv6_map_size = ipv6_datatypes as f32 * NUM_ENTRIES 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 < 11_f32,
|
||||
"Total map size = {total_map_size_mb} MB"
|
||||
);
|
||||
}
|
||||
}
|
||||
37
rust/relay/ebpf-turn-router/src/try_handle_turn/interface.rs
Normal file
37
rust/relay/ebpf-turn-router/src/try_handle_turn/interface.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
//! Per-CPU data structures to store relay interface addresses.
|
||||
|
||||
use core::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use aya_ebpf::{macros::map, maps::PerCpuArray};
|
||||
use ebpf_shared::{InterfaceAddressV4, InterfaceAddressV6};
|
||||
|
||||
use crate::try_handle_turn::Error;
|
||||
|
||||
#[map]
|
||||
static INT_ADDR_V4: PerCpuArray<InterfaceAddressV4> = PerCpuArray::with_max_entries(1, 0);
|
||||
#[map]
|
||||
static INT_ADDR_V6: PerCpuArray<InterfaceAddressV6> = PerCpuArray::with_max_entries(1, 0);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn ipv4_address() -> Result<Ipv4Addr, Error> {
|
||||
let interface_addr = INT_ADDR_V4
|
||||
.get_ptr_mut(0)
|
||||
.ok_or(Error::InterfaceIpv4AddressAccessFailed)?;
|
||||
|
||||
// SAFETY: This comes from a per-cpu data structure so we can safely access it.
|
||||
let addr = unsafe { *interface_addr };
|
||||
|
||||
addr.get().ok_or(Error::InterfaceIpv4AddressNotConfigured)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn ipv6_address() -> Result<Ipv6Addr, Error> {
|
||||
let interface_addr = INT_ADDR_V6
|
||||
.get_ptr_mut(0)
|
||||
.ok_or(Error::InterfaceIpv6AddressAccessFailed)?;
|
||||
|
||||
// SAFETY: This comes from a per-cpu data structure so we can safely access it.
|
||||
let addr = unsafe { *interface_addr };
|
||||
|
||||
addr.get().ok_or(Error::InterfaceIpv6AddressNotConfigured)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use aya_ebpf::programs::XdpContext;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::try_handle_turn::Error;
|
||||
|
||||
/// Returns a mutable reference to a type `T` at the specified offset in the packet data.
|
||||
///
|
||||
Reference in New Issue
Block a user