chore(relay): log when encountering unsupported channel mappings (#8617)

Currently, the relays eBPF module only supports routing from IPv4 to
IPv4 as well as IPv6 to IPv6. In general, TURN servers can also route
from IPv4 to IPv6 and vice versa. Our userspace routing supports that
but doing the same in the eBPF code is a bit more involved. We'd need to
move around the headers a bit more (IPv4 and IPv6 headers are different
in size), as well as configure the respective "source" address for each
interface. Currently, we simply take the destination address of the
incoming packet as the new source address. When routing across IP
versions, that doesn't work.

To gain some more insight into how often this happens, we add these
additional maps and populate them. This allows us to emit a dedicated
log message whenever we encounter a packet for such a mapping.

First, we always do check for an entry in the maps that we can handle.
If we can't we check the other map and special-case the error.
Otherwise, we fall back to the previous "no entry" error. We shouldn't
really see these "no entry" errors anymore now, unless someone starts
probing our relays for active channels.
This commit is contained in:
Thomas Eizinger
2025-04-02 23:07:59 +11:00
committed by GitHub
parent bac5cfa4cb
commit e7cf00eb53
3 changed files with 153 additions and 22 deletions

View File

@@ -10,12 +10,29 @@ pub enum Error {
Ipv4PacketWithOptions,
NotAChannelDataMessage,
BadChannelDataLength,
NoChannelBinding,
NoEntry(SupportedChannel),
UnsupportedChannel(UnsupportedChannel),
XdpLoadBytesFailed,
XdpAdjustHeadFailed,
XdpStoreBytesFailed,
}
#[derive(Debug, Clone, Copy)]
pub enum SupportedChannel {
UdpToChan44,
ChanToUdp44,
UdpToChan66,
ChanToUdp66,
}
#[derive(Debug, Clone, Copy)]
pub enum UnsupportedChannel {
UdpToChan46,
ChanToUdp46,
UdpToChan64,
ChanToUdp64,
}
impl aya_log_ebpf::WriteToBuf for Error {
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
let msg = match self {
@@ -27,7 +44,30 @@ impl aya_log_ebpf::WriteToBuf for Error {
Error::Ipv4PacketWithOptions => "IPv4 packet has options",
Error::NotAChannelDataMessage => "Not a channel data message",
Error::BadChannelDataLength => "Channel data length does not match packet length",
Error::NoChannelBinding => "No channel binding",
Error::NoEntry(SupportedChannel::UdpToChan44) => {
"No entry in UDPv4 to channel IPv4 map"
}
Error::NoEntry(SupportedChannel::ChanToUdp44) => {
"No entry in channel IPv4 to UDPv4 map"
}
Error::NoEntry(SupportedChannel::UdpToChan66) => {
"No entry in UDPv6 to channel IPv6 map"
}
Error::NoEntry(SupportedChannel::ChanToUdp66) => {
"No entry in channel IPv6 to UDPv6 map"
}
Error::UnsupportedChannel(UnsupportedChannel::UdpToChan46) => {
"Relaying UDPv4 to channel IPv6 is not supported"
}
Error::UnsupportedChannel(UnsupportedChannel::ChanToUdp46) => {
"Relaying channel IPv4 to UDPv6 is not supported"
}
Error::UnsupportedChannel(UnsupportedChannel::UdpToChan64) => {
"Relaying UDPv6 to channel IPv4 is not supported"
}
Error::UnsupportedChannel(UnsupportedChannel::ChanToUdp64) => {
"Relaying channel IPv6 to UDPv4 is not supported"
}
Error::XdpLoadBytesFailed => "Failed to load bytes",
Error::XdpAdjustHeadFailed => "Failed to adjust head",
Error::XdpStoreBytesFailed => "Failed to store bytes",

View File

@@ -11,6 +11,7 @@ use aya_ebpf::{
use aya_log_ebpf::*;
use channel_data::{CdHdr, ChannelData};
use ebpf_shared::{ClientAndChannelV4, ClientAndChannelV6, PortAndPeerV4, PortAndPeerV6};
use error::{SupportedChannel, UnsupportedChannel};
use eth::Eth;
use ip4::{Ip4, Ipv4Hdr};
use ip6::Ip6;
@@ -38,9 +39,8 @@ mod udp;
const NUM_ENTRIES: u32 = 0x10000;
/// Channel mappings from an IPv4 socket + channel number to an IPv4 socket + port.
///
/// TODO: Update flags to `BPF_F_NO_PREALLOC` to guarantee atomicity? Needs research.
// TODO: Update flags to `BPF_F_NO_PREALLOC` to guarantee atomicity? Needs research.
#[map]
static CHAN_TO_UDP_44: HashMap<ClientAndChannelV4, PortAndPeerV4> =
HashMap::with_max_entries(NUM_ENTRIES, 0);
@@ -53,6 +53,18 @@ static CHAN_TO_UDP_66: HashMap<ClientAndChannelV6, PortAndPeerV6> =
#[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);
#[xdp]
pub fn handle_turn(ctx: XdpContext) -> u32 {
@@ -68,7 +80,8 @@ pub fn handle_turn(ctx: XdpContext) -> u32 {
| Error::XdpLoadBytesFailed
| Error::PacketTooShort
| Error::NoMacAddress
| Error::NoChannelBinding => {
| Error::UnsupportedChannel(_)
| Error::NoEntry(_) => {
debug!(&ctx, "Passing packet to userspace: {}", e);
xdp_action::XDP_PASS
@@ -147,10 +160,16 @@ fn try_handle_ipv4_channel_data_to_udp(
) -> Result<(), Error> {
let cd = ChannelData::parse(ctx, Ipv4Hdr::LEN)?;
let key = ClientAndChannelV4::new(ipv4.src(), udp.src(), cd.number());
// SAFETY: ???
let port_and_peer =
unsafe { CHAN_TO_UDP_44.get(&ClientAndChannelV4::new(ipv4.src(), udp.src(), cd.number())) }
.ok_or(Error::NoChannelBinding)?;
let port_and_peer = unsafe { CHAN_TO_UDP_44.get(&key) }.ok_or_else(|| {
if unsafe { CHAN_TO_UDP_46.get(&key) }.is_some() {
return Error::UnsupportedChannel(UnsupportedChannel::ChanToUdp46);
}
Error::NoEntry(SupportedChannel::ChanToUdp44)
})?;
let new_src = ipv4.dst(); // The IP we received the packet on will be the new source IP.
let new_dst = port_and_peer.peer_ip();
@@ -182,9 +201,15 @@ fn try_handle_ipv4_udp_to_channel_data(
ipv4: Ip4,
udp: Udp,
) -> Result<(), Error> {
let client_and_channel =
unsafe { UDP_TO_CHAN_44.get(&PortAndPeerV4::new(ipv4.src(), udp.dst(), udp.src())) }
.ok_or(Error::NoChannelBinding)?;
let key = PortAndPeerV4::new(ipv4.src(), udp.dst(), udp.src());
let client_and_channel = unsafe { UDP_TO_CHAN_44.get(&key) }.ok_or_else(|| {
if unsafe { UDP_TO_CHAN_46.get(&key) }.is_some() {
return Error::UnsupportedChannel(UnsupportedChannel::UdpToChan46);
}
Error::NoEntry(SupportedChannel::UdpToChan44)
})?;
let new_src = ipv4.dst(); // The IP we received the packet on will be the new source IP.
let new_dst = client_and_channel.client_ip();
@@ -266,9 +291,15 @@ fn try_handle_ipv6_udp_to_channel_data(
ipv6: Ip6,
udp: Udp,
) -> Result<(), Error> {
let client_and_channel =
unsafe { UDP_TO_CHAN_66.get(&PortAndPeerV6::new(ipv6.src(), udp.dst(), udp.src())) }
.ok_or(Error::NoChannelBinding)?;
let key = PortAndPeerV6::new(ipv6.src(), udp.dst(), udp.src());
let client_and_channel = unsafe { UDP_TO_CHAN_66.get(&key) }.ok_or_else(|| {
if unsafe { UDP_TO_CHAN_64.get(&key) }.is_some() {
return Error::UnsupportedChannel(UnsupportedChannel::UdpToChan64);
}
Error::NoEntry(SupportedChannel::UdpToChan66)
})?;
let new_src = ipv6.dst(); // The IP we received the packet on will be the new source IP.
let new_dst = client_and_channel.client_ip();
@@ -311,10 +342,16 @@ fn try_handle_ipv6_channel_data_to_udp(
) -> Result<(), Error> {
let cd = ChannelData::parse(ctx, Ipv6Hdr::LEN)?;
let key = ClientAndChannelV6::new(ipv6.src(), udp.src(), cd.number());
// SAFETY: ???
let port_and_peer =
unsafe { CHAN_TO_UDP_66.get(&ClientAndChannelV6::new(ipv6.src(), udp.src(), cd.number())) }
.ok_or(Error::NoChannelBinding)?;
let port_and_peer = unsafe { CHAN_TO_UDP_66.get(&key) }.ok_or_else(|| {
if unsafe { CHAN_TO_UDP_64.get(&key) }.is_some() {
return Error::UnsupportedChannel(UnsupportedChannel::ChanToUdp64);
}
Error::NoEntry(SupportedChannel::ChanToUdp66)
})?;
let new_src = ipv6.dst(); // The IP we received the packet on will be the new source IP.
let new_dst = port_and_peer.peer_ip();

View File

@@ -135,8 +135,25 @@ impl Program {
self.udp_to_chan_66_map_mut()?
.insert(port_and_peer, client_and_channel, 0)?;
}
(SocketAddr::V4(_), SocketAddr::V6(_)) | (SocketAddr::V6(_), SocketAddr::V4(_)) => {
// Relaying between IPv4 and IPv6 is not supported in the eBPF kernel.
(SocketAddr::V4(client), SocketAddr::V6(peer)) => {
let client_and_channel =
ClientAndChannelV4::from_socket(client, channel_number.value());
let port_and_peer = PortAndPeerV6::from_socket(peer, allocation_port.value());
self.chan_to_udp_46_map_mut()?
.insert(client_and_channel, port_and_peer, 0)?;
self.udp_to_chan_64_map_mut()?
.insert(port_and_peer, client_and_channel, 0)?;
}
(SocketAddr::V6(client), SocketAddr::V4(peer)) => {
let client_and_channel =
ClientAndChannelV6::from_socket(client, channel_number.value());
let port_and_peer = PortAndPeerV4::from_socket(peer, allocation_port.value());
self.chan_to_udp_64_map_mut()?
.insert(client_and_channel, port_and_peer, 0)?;
self.udp_to_chan_46_map_mut()?
.insert(port_and_peer, client_and_channel, 0)?;
}
}
@@ -170,8 +187,21 @@ impl Program {
self.chan_to_udp_66_map_mut()?.remove(&client_and_channel)?;
self.udp_to_chan_66_map_mut()?.remove(&port_and_peer)?;
}
(SocketAddr::V4(_), SocketAddr::V6(_)) | (SocketAddr::V6(_), SocketAddr::V4(_)) => {
// Relaying between IPv4 and IPv6 is not supported in the eBPF kernel.
(SocketAddr::V4(client), SocketAddr::V6(peer)) => {
let client_and_channel =
ClientAndChannelV4::from_socket(client, channel_number.value());
let port_and_peer = PortAndPeerV6::from_socket(peer, allocation_port.value());
self.chan_to_udp_46_map_mut()?.remove(&client_and_channel)?;
self.udp_to_chan_64_map_mut()?.remove(&port_and_peer)?;
}
(SocketAddr::V6(client), SocketAddr::V4(peer)) => {
let client_and_channel =
ClientAndChannelV6::from_socket(client, channel_number.value());
let port_and_peer = PortAndPeerV4::from_socket(peer, allocation_port.value());
self.chan_to_udp_64_map_mut()?.remove(&client_and_channel)?;
self.udp_to_chan_46_map_mut()?.remove(&port_and_peer)?;
}
}
@@ -208,6 +238,30 @@ impl Program {
self.hash_map_mut("UDP_TO_CHAN_66")
}
fn chan_to_udp_46_map_mut(
&mut self,
) -> Result<HashMap<&mut MapData, ClientAndChannelV4, PortAndPeerV6>> {
self.hash_map_mut("CHAN_TO_UDP_46")
}
fn udp_to_chan_46_map_mut(
&mut self,
) -> Result<HashMap<&mut MapData, PortAndPeerV4, ClientAndChannelV6>> {
self.hash_map_mut("UDP_TO_CHAN_46")
}
fn chan_to_udp_64_map_mut(
&mut self,
) -> Result<HashMap<&mut MapData, ClientAndChannelV6, PortAndPeerV4>> {
self.hash_map_mut("CHAN_TO_UDP_64")
}
fn udp_to_chan_64_map_mut(
&mut self,
) -> Result<HashMap<&mut MapData, PortAndPeerV6, ClientAndChannelV4>> {
self.hash_map_mut("UDP_TO_CHAN_64")
}
fn config_array_mut(&mut self) -> Result<Array<&mut MapData, Config>> {
self.array_mut("CONFIG")
}