mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
refactor(eBPF): imply XDP_TX from Ok(()) (#8604)
Currently, the eBPF code isn't consistent in how it handles XDP actions. For some cases, we return errors and then map them to `XDP_PASS` or `XDP_DROP`. For others, we return `Ok(XDP_PASS)`. This is unnecessarily hard to understand. We refactor the eBPF kernel to ALWAYS use `Error`s for all code-paths that don't end in `XDP_TX`, i.e. when we successfully modified the packet and want to send it back out. In addition, we also change the way we log these errors. Not all errors are equal and most `XDP_PASS` actions don't need to be logged. Those packets are simply passing through. Finally, we also introduce new checks in case any calls to the eBPF helper functions fail. Related: #7518
This commit is contained in:
@@ -1,36 +1,34 @@
|
||||
use core::num::NonZeroUsize;
|
||||
|
||||
use aya_ebpf::bindings::xdp_action;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Error {
|
||||
PacketTooShort,
|
||||
NotUdp,
|
||||
NotTurn,
|
||||
NotIp,
|
||||
Ipv4PacketWithOptions,
|
||||
NotAChannelDataMessage,
|
||||
BadChannelDataLength,
|
||||
NoChannelBinding,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn xdp_action(&self) -> xdp_action::Type {
|
||||
match self {
|
||||
Error::PacketTooShort => xdp_action::XDP_PASS,
|
||||
Error::Ipv4PacketWithOptions => xdp_action::XDP_PASS,
|
||||
Error::BadChannelDataLength => xdp_action::XDP_DROP,
|
||||
Error::NotAChannelDataMessage => xdp_action::XDP_PASS,
|
||||
Error::NoChannelBinding => xdp_action::XDP_PASS,
|
||||
}
|
||||
}
|
||||
XdpLoadBytesFailed,
|
||||
XdpAdjustHeadFailed,
|
||||
XdpStoreBytesFailed,
|
||||
}
|
||||
|
||||
impl aya_log_ebpf::WriteToBuf for Error {
|
||||
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
|
||||
let msg = match self {
|
||||
Error::PacketTooShort => "Packet is too short",
|
||||
Error::NotUdp => "Not a UDP packet",
|
||||
Error::NotTurn => "Not TURN traffic",
|
||||
Error::NotIp => "Not an IP packet",
|
||||
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::XdpLoadBytesFailed => "Failed to load bytes",
|
||||
Error::XdpAdjustHeadFailed => "Failed to adjust head",
|
||||
Error::XdpStoreBytesFailed => "Failed to store bytes",
|
||||
};
|
||||
|
||||
msg.write(buf)
|
||||
|
||||
@@ -56,12 +56,28 @@ static UDP_TO_CHAN_66: HashMap<PortAndPeerV6, ClientAndChannelV6> =
|
||||
|
||||
#[xdp]
|
||||
pub fn handle_turn(ctx: XdpContext) -> u32 {
|
||||
try_handle_turn(&ctx).unwrap_or_else(|e| {
|
||||
let action = e.xdp_action();
|
||||
try_handle_turn(&ctx).unwrap_or_else(|e| match e {
|
||||
Error::NotUdp
|
||||
| Error::NotTurn
|
||||
| Error::NotIp
|
||||
| Error::NotAChannelDataMessage
|
||||
| Error::Ipv4PacketWithOptions => xdp_action::XDP_PASS,
|
||||
|
||||
debug!(&ctx, "Failed to handle packet {}; action = {}", e, action);
|
||||
Error::XdpStoreBytesFailed
|
||||
| Error::XdpAdjustHeadFailed
|
||||
| Error::XdpLoadBytesFailed
|
||||
| Error::PacketTooShort
|
||||
| Error::NoChannelBinding => {
|
||||
debug!(&ctx, "Failed to handle packet: {}", e);
|
||||
|
||||
action
|
||||
xdp_action::XDP_PASS
|
||||
}
|
||||
|
||||
Error::BadChannelDataLength => {
|
||||
debug!(&ctx, "Failed to handle packet: {}; dropping", e);
|
||||
|
||||
xdp_action::XDP_DROP
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,27 +85,24 @@ pub fn handle_turn(ctx: XdpContext) -> u32 {
|
||||
fn try_handle_turn(ctx: &XdpContext) -> Result<u32, Error> {
|
||||
let eth = Eth::parse(ctx)?;
|
||||
|
||||
let action = match eth.ether_type() {
|
||||
match eth.ether_type() {
|
||||
EtherType::Ipv4 => try_handle_turn_ipv4(ctx)?,
|
||||
EtherType::Ipv6 => try_handle_turn_ipv6(ctx)?,
|
||||
_ => return Ok(xdp_action::XDP_PASS),
|
||||
_ => return Err(Error::NotIp),
|
||||
};
|
||||
|
||||
// If we send the packet back out, swap the source and destination MAC addresses.
|
||||
// We will have adjusted the packet pointers so we need to reparse the packet.
|
||||
if action == xdp_action::XDP_TX {
|
||||
Eth::parse(ctx)?.swap_src_and_dst();
|
||||
}
|
||||
// If we get to here, we modified the packet and need to send it back out again.
|
||||
Eth::parse(ctx)?.swap_src_and_dst();
|
||||
|
||||
Ok(action)
|
||||
Ok(xdp_action::XDP_TX)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_handle_turn_ipv4(ctx: &XdpContext) -> Result<u32, Error> {
|
||||
fn try_handle_turn_ipv4(ctx: &XdpContext) -> Result<(), Error> {
|
||||
let ipv4 = Ip4::parse(ctx)?;
|
||||
|
||||
if ipv4.protocol() != IpProto::Udp {
|
||||
return Ok(xdp_action::XDP_PASS);
|
||||
return Err(Error::NotUdp);
|
||||
}
|
||||
|
||||
let udp = Udp::parse(ctx, Ipv4Hdr::LEN)?; // TODO: Change the API so we parse the UDP header _from_ the ipv4 struct?
|
||||
@@ -106,28 +119,24 @@ fn try_handle_turn_ipv4(ctx: &XdpContext) -> Result<u32, Error> {
|
||||
);
|
||||
|
||||
if config::allocation_range().contains(&udp.dst()) {
|
||||
let action = try_handle_ipv4_udp_to_channel_data(ctx, ipv4, udp)?;
|
||||
try_handle_ipv4_udp_to_channel_data(ctx, ipv4, udp)?;
|
||||
stats::emit_data_relayed(ctx, udp_payload_len);
|
||||
|
||||
return Ok(action);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if udp.dst() == 3478 {
|
||||
let action = try_handle_ipv4_channel_data_to_udp(ctx, ipv4, udp)?;
|
||||
try_handle_ipv4_channel_data_to_udp(ctx, ipv4, udp)?;
|
||||
stats::emit_data_relayed(ctx, udp_payload_len - CdHdr::LEN as u16);
|
||||
|
||||
return Ok(action);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(xdp_action::XDP_PASS)
|
||||
Err(Error::NotTurn)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_handle_ipv4_channel_data_to_udp(
|
||||
ctx: &XdpContext,
|
||||
ipv4: Ip4,
|
||||
udp: Udp,
|
||||
) -> Result<u32, Error> {
|
||||
fn try_handle_ipv4_channel_data_to_udp(ctx: &XdpContext, ipv4: Ip4, udp: Udp) -> Result<(), Error> {
|
||||
let cd = ChannelData::parse(ctx, Ipv4Hdr::LEN)?;
|
||||
|
||||
// SAFETY: ???
|
||||
@@ -147,17 +156,13 @@ fn try_handle_ipv4_channel_data_to_udp(
|
||||
new_udp_len,
|
||||
);
|
||||
|
||||
remove_channel_data_header_ipv4(ctx);
|
||||
remove_channel_data_header_ipv4(ctx)?;
|
||||
|
||||
Ok(xdp_action::XDP_TX)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_handle_ipv4_udp_to_channel_data(
|
||||
ctx: &XdpContext,
|
||||
ipv4: Ip4,
|
||||
udp: Udp,
|
||||
) -> Result<u32, Error> {
|
||||
fn try_handle_ipv4_udp_to_channel_data(ctx: &XdpContext, 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)?;
|
||||
@@ -180,17 +185,17 @@ fn try_handle_ipv4_udp_to_channel_data(
|
||||
|
||||
let channel_data_header = [cd_num[0], cd_num[1], cd_len[0], cd_len[1]];
|
||||
|
||||
add_channel_data_header_ipv4(ctx, channel_data_header);
|
||||
add_channel_data_header_ipv4(ctx, channel_data_header)?;
|
||||
|
||||
Ok(xdp_action::XDP_TX)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_handle_turn_ipv6(ctx: &XdpContext) -> Result<u32, Error> {
|
||||
fn try_handle_turn_ipv6(ctx: &XdpContext) -> Result<(), Error> {
|
||||
let ipv6 = Ip6::parse(ctx)?;
|
||||
|
||||
if ipv6.protocol() != IpProto::Udp {
|
||||
return Ok(xdp_action::XDP_PASS);
|
||||
return Err(Error::NotUdp);
|
||||
}
|
||||
|
||||
let udp = Udp::parse(ctx, Ipv6Hdr::LEN)?; // TODO: Change the API so we parse the UDP header _from_ the ipv6 struct?
|
||||
@@ -207,27 +212,23 @@ fn try_handle_turn_ipv6(ctx: &XdpContext) -> Result<u32, Error> {
|
||||
);
|
||||
|
||||
if config::allocation_range().contains(&udp.dst()) {
|
||||
let action = try_handle_ipv6_udp_to_channel_data(ctx, ipv6, udp)?;
|
||||
try_handle_ipv6_udp_to_channel_data(ctx, ipv6, udp)?;
|
||||
stats::emit_data_relayed(ctx, udp_payload_len);
|
||||
|
||||
return Ok(action);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if udp.dst() == 3478 {
|
||||
let action = try_handle_ipv6_channel_data_to_udp(ctx, ipv6, udp)?;
|
||||
try_handle_ipv6_channel_data_to_udp(ctx, ipv6, udp)?;
|
||||
stats::emit_data_relayed(ctx, udp_payload_len - CdHdr::LEN as u16);
|
||||
|
||||
return Ok(action);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(xdp_action::XDP_PASS)
|
||||
Err(Error::NotTurn)
|
||||
}
|
||||
|
||||
fn try_handle_ipv6_udp_to_channel_data(
|
||||
ctx: &XdpContext,
|
||||
ipv6: Ip6,
|
||||
udp: Udp,
|
||||
) -> Result<u32, Error> {
|
||||
fn try_handle_ipv6_udp_to_channel_data(ctx: &XdpContext, 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)?;
|
||||
@@ -250,16 +251,12 @@ fn try_handle_ipv6_udp_to_channel_data(
|
||||
|
||||
let channel_data_header = [cd_num[0], cd_num[1], cd_len[0], cd_len[1]];
|
||||
|
||||
add_channel_data_header_ipv6(ctx, channel_data_header);
|
||||
add_channel_data_header_ipv6(ctx, channel_data_header)?;
|
||||
|
||||
Ok(xdp_action::XDP_TX)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_handle_ipv6_channel_data_to_udp(
|
||||
ctx: &XdpContext,
|
||||
ipv6: Ip6,
|
||||
udp: Udp,
|
||||
) -> Result<u32, Error> {
|
||||
fn try_handle_ipv6_channel_data_to_udp(ctx: &XdpContext, ipv6: Ip6, udp: Udp) -> Result<(), Error> {
|
||||
let cd = ChannelData::parse(ctx, Ipv6Hdr::LEN)?;
|
||||
|
||||
// SAFETY: ???
|
||||
@@ -279,9 +276,9 @@ fn try_handle_ipv6_channel_data_to_udp(
|
||||
new_udp_len,
|
||||
);
|
||||
|
||||
remove_channel_data_header_ipv6(ctx);
|
||||
remove_channel_data_header_ipv6(ctx)?;
|
||||
|
||||
Ok(xdp_action::XDP_TX)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Defines our panic handler.
|
||||
|
||||
@@ -9,42 +9,52 @@ use network_types::{
|
||||
udp::UdpHdr,
|
||||
};
|
||||
|
||||
use crate::channel_data::CdHdr;
|
||||
use crate::{channel_data::CdHdr, error::Error};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn remove_channel_data_header_ipv4(ctx: &XdpContext) {
|
||||
pub fn remove_channel_data_header_ipv4(ctx: &XdpContext) -> Result<(), Error> {
|
||||
move_headers::<{ CdHdr::LEN as i32 }, { Ipv4Hdr::LEN }>(ctx)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn add_channel_data_header_ipv4(ctx: &XdpContext, mut header: [u8; 4]) {
|
||||
move_headers::<{ -(CdHdr::LEN as i32) }, { Ipv4Hdr::LEN }>(ctx);
|
||||
pub fn add_channel_data_header_ipv4(ctx: &XdpContext, mut header: [u8; 4]) -> Result<(), Error> {
|
||||
move_headers::<{ -(CdHdr::LEN as i32) }, { Ipv4Hdr::LEN }>(ctx)?;
|
||||
let offset = (EthHdr::LEN + Ipv4Hdr::LEN + UdpHdr::LEN) as u32;
|
||||
|
||||
let header_ptr = &mut header as *mut _ as *mut c_void;
|
||||
let header_len = CdHdr::LEN as u32;
|
||||
|
||||
unsafe { bpf_xdp_store_bytes(ctx.ctx, offset, header_ptr, header_len) };
|
||||
if unsafe { bpf_xdp_store_bytes(ctx.ctx, offset, header_ptr, header_len) } < 0 {
|
||||
return Err(Error::XdpStoreBytesFailed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn remove_channel_data_header_ipv6(ctx: &XdpContext) {
|
||||
pub fn remove_channel_data_header_ipv6(ctx: &XdpContext) -> Result<(), Error> {
|
||||
move_headers::<{ CdHdr::LEN as i32 }, { Ipv6Hdr::LEN }>(ctx)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn add_channel_data_header_ipv6(ctx: &XdpContext, mut header: [u8; 4]) {
|
||||
move_headers::<{ -(CdHdr::LEN as i32) }, { Ipv6Hdr::LEN }>(ctx);
|
||||
pub fn add_channel_data_header_ipv6(ctx: &XdpContext, mut header: [u8; 4]) -> Result<(), Error> {
|
||||
move_headers::<{ -(CdHdr::LEN as i32) }, { Ipv6Hdr::LEN }>(ctx)?;
|
||||
let offset = (EthHdr::LEN + Ipv6Hdr::LEN + UdpHdr::LEN) as u32;
|
||||
|
||||
let header_ptr = &mut header as *mut _ as *mut c_void;
|
||||
let header_len = CdHdr::LEN as u32;
|
||||
|
||||
unsafe { bpf_xdp_store_bytes(ctx.ctx, offset, header_ptr, header_len) };
|
||||
if unsafe { bpf_xdp_store_bytes(ctx.ctx, offset, header_ptr, header_len) } < 0 {
|
||||
return Err(Error::XdpStoreBytesFailed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn move_headers<const DELTA: i32, const IP_HEADER_LEN: usize>(ctx: &XdpContext) {
|
||||
fn move_headers<const DELTA: i32, const IP_HEADER_LEN: usize>(
|
||||
ctx: &XdpContext,
|
||||
) -> Result<(), Error> {
|
||||
// Scratch space for our headers.
|
||||
// IPv6 headers are always 40 bytes long.
|
||||
// IPv4 headers are between 20 and 60 bytes long.
|
||||
@@ -58,11 +68,18 @@ fn move_headers<const DELTA: i32, const IP_HEADER_LEN: usize>(ctx: &XdpContext)
|
||||
let headers_len = (EthHdr::LEN + IP_HEADER_LEN + UdpHdr::LEN) as u32;
|
||||
|
||||
// Copy headers into buffer.
|
||||
unsafe { bpf_xdp_load_bytes(ctx.ctx, 0, headers_ptr, headers_len) };
|
||||
if unsafe { bpf_xdp_load_bytes(ctx.ctx, 0, headers_ptr, headers_len) } < 0 {
|
||||
return Err(Error::XdpLoadBytesFailed);
|
||||
}
|
||||
|
||||
// Move the head for the packet by `DELTA`.
|
||||
unsafe { bpf_xdp_adjust_head(ctx.ctx, DELTA) };
|
||||
if unsafe { bpf_xdp_adjust_head(ctx.ctx, DELTA) } < 0 {
|
||||
return Err(Error::XdpAdjustHeadFailed);
|
||||
}
|
||||
|
||||
// Copy the headers back.
|
||||
unsafe { bpf_xdp_store_bytes(ctx.ctx, 0, headers_ptr, headers_len) };
|
||||
if unsafe { bpf_xdp_store_bytes(ctx.ctx, 0, headers_ptr, headers_len) } < 0 {
|
||||
return Err(Error::XdpStoreBytesFailed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user