Skip to content

Commit

Permalink
feat(s2n-quic-core): use an event handler for XDP decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
camshaft committed May 2, 2023
1 parent e4314d2 commit 5b17b7c
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 124 deletions.
8 changes: 6 additions & 2 deletions common/s2n-codec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ pub mod decoder;
pub mod encoder;
pub mod unaligned;

pub use decoder::*;
pub use encoder::*;
pub use decoder::{
CheckedRange, DecoderBuffer, DecoderBufferMut, DecoderBufferMutResult, DecoderBufferResult,
DecoderError, DecoderParameterizedValue, DecoderParameterizedValueMut, DecoderValue,
DecoderValueMut,
};
pub use encoder::{Encoder, EncoderBuffer, EncoderLenEstimator, EncoderValue};
pub use unaligned::*;
2 changes: 2 additions & 0 deletions quic/s2n-quic-core/src/inet/ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ impl fmt::Display for Protocol {
Self::IPV6_ICMP => "ICMP for IPv6",
Self::IPV6_NO_NXT => "No Next Header for IPv6",
Self::IPV6_OPTS => "Destination Options for IPv6",
Self::UDPLITE => "Lightweight User Datagram",
Self { id } => return write!(f, "[unknown 0x{id:02x}]"),
}
.fmt(f)
Expand Down Expand Up @@ -360,6 +361,7 @@ impl Protocol {
impl_p!(is_ipv6_icmp, IPV6_ICMP, 58);
impl_p!(is_ipv6_no_next, IPV6_NO_NXT, 59);
impl_p!(is_ipv6_options, IPV6_OPTS, 60);
impl_p!(is_udplite, UDPLITE, 136);
}

#[cfg(test)]
Expand Down
6 changes: 3 additions & 3 deletions quic/s2n-quic-core/src/inet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ pub mod ipv6;
pub mod udp;
pub mod unspecified;

pub use datagram::*;
pub use datagram::{AncillaryData, DatagramInfo};
pub use ecn::*;
pub use ip::*;
pub use ipv4::*;
pub use ipv6::*;
pub use ipv4::{IpV4Address, SocketAddressV4};
pub use ipv6::{IpV6Address, SocketAddressV6};
pub use unspecified::*;
218 changes: 120 additions & 98 deletions quic/s2n-quic-core/src/xdp/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,143 @@

use super::{bpf::Decoder, path};
use crate::inet::{
datagram,
ethernet::{self, EtherType},
ip, ipv4, ipv6, udp, SocketAddress,
ip, ipv4, ipv6, udp,
};
use s2n_codec::DecoderError;

type Result<Addr, D> = core::result::Result<Option<(Addr, D)>, DecoderError>;
pub type Result<D = ()> = core::result::Result<Option<D>, DecoderError>;

pub trait Validator {
pub trait EventHandler: Sized {
#[inline(always)]
fn validate_local_port(&self, port: u16) -> bool {
let _ = port;
true
fn decode_packet<'a, D: Decoder<'a>>(&mut self, buffer: D) -> Result<D> {
decode_packet_with_event(buffer, self)
}

#[inline(always)]
fn validate_local_ipv4(&self, ip: &ipv4::IpV4Address) -> bool {
let _ = ip;
true
fn on_ethernet_header(&mut self, header: &ethernet::Header) -> Result {
let _ = header;
Ok(Some(()))
}

#[inline(always)]
fn validate_local_ipv6(&self, ip: &ipv6::IpV6Address) -> bool {
let _ = ip;
true
fn on_ipv4_header(&mut self, header: &ipv4::Header) -> Result {
let _ = header;
Ok(Some(()))
}

#[inline(always)]
fn on_ipv6_header(&mut self, header: &ipv6::Header) -> Result {
let _ = header;
Ok(Some(()))
}

#[inline(always)]
fn on_udp_header(&mut self, header: &udp::Header) -> Result {
let _ = header;
Ok(Some(()))
}
}

impl Validator for () {}
impl EventHandler for () {}

impl EventHandler for path::Tuple {
#[inline(always)]
fn on_ethernet_header(&mut self, header: &ethernet::Header) -> Result {
self.remote_address.mac = header.source;
self.local_address.mac = header.destination;
Ok(Some(()))
}

#[inline(always)]
fn on_ipv4_header(&mut self, header: &ipv4::Header) -> Result {
self.remote_address.ip = header.source.into();
self.local_address.ip = header.destination.into();
Ok(Some(()))
}

#[inline(always)]
fn on_ipv6_header(&mut self, header: &ipv6::Header) -> Result {
self.remote_address.ip = header.source.into();
self.local_address.ip = header.destination.into();
Ok(Some(()))
}

#[inline(always)]
fn on_udp_header(&mut self, header: &udp::Header) -> Result {
self.remote_address.port = header.source.get();
self.local_address.port = header.destination.get();
Ok(Some(()))
}
}

impl<P: EventHandler> EventHandler for datagram::Header<P> {
#[inline(always)]
fn on_ethernet_header(&mut self, header: &ethernet::Header) -> Result {
self.path.on_ethernet_header(header)
}

#[inline(always)]
fn on_ipv4_header(&mut self, header: &ipv4::Header) -> Result {
self.path.on_ipv4_header(header)?;
self.ecn = header.tos().ecn();
Ok(Some(()))
}

#[inline(always)]
fn on_ipv6_header(&mut self, header: &ipv6::Header) -> Result {
self.path.on_ipv6_header(header)?;
self.ecn = header.vtcfl().ecn();
Ok(Some(()))
}

#[inline(always)]
fn on_udp_header(&mut self, header: &udp::Header) -> Result {
self.path.on_udp_header(header)
}
}

/// Decodes a path tuple and payload from a raw packet
#[inline(always)]
pub fn decode_packet<'a, D: Decoder<'a>>(buffer: D) -> Result<path::Tuple, D> {
decode_packet_validator(buffer, &())
pub fn decode_packet<'a, D: Decoder<'a>>(
buffer: D,
) -> core::result::Result<Option<(datagram::Header<path::Tuple>, D)>, DecoderError> {
let mut header = datagram::Header {
path: path::Tuple::UNSPECIFIED,
ecn: Default::default(),
};
match decode_packet_with_event(buffer, &mut header)? {
Some(buffer) => Ok(Some((header, buffer))),
None => Ok(None),
}
}

/// Decodes a path tuple and payload from a raw packet
#[inline(always)]
pub fn decode_packet_validator<'a, D: Decoder<'a>, V: Validator>(
pub fn decode_packet_with_event<'a, D: Decoder<'a>, E: EventHandler>(
buffer: D,
validator: &V,
) -> Result<path::Tuple, D> {
events: &mut E,
) -> Result<D> {
let (header, buffer) = buffer.decode::<&ethernet::Header>()?;

let result = match *header.ethertype() {
EtherType::IPV4 => decode_ipv4(buffer, validator),
EtherType::IPV6 => decode_ipv6(buffer, validator),
if events.on_ethernet_header(header)?.is_none() {
return Ok(None);
}

match *header.ethertype() {
EtherType::IPV4 => decode_ipv4(buffer, events),
EtherType::IPV6 => decode_ipv6(buffer, events),
// pass the packet on to the OS network stack if we don't understand it
_ => return Ok(None),
}?;

Ok(result.map(|(tuple, buffer)| {
let remote_address = path::RemoteAddress {
mac: *header.source(),
ip: tuple.source.ip(),
port: tuple.source.port(),
};
let local_address = path::LocalAddress {
mac: *header.destination(),
ip: tuple.destination.ip(),
port: tuple.destination.port(),
};
let tuple = path::Tuple {
remote_address,
local_address,
};
(tuple, buffer)
}))
_ => Ok(None),
}
}

#[inline(always)]
fn decode_ipv4<'a, D: Decoder<'a>, V: Validator>(
buffer: D,
validator: &V,
) -> Result<Tuple<SocketAddress>, D> {
fn decode_ipv4<'a, D: Decoder<'a>, E: EventHandler>(buffer: D, events: &mut E) -> Result<D> {
let (header, buffer) = buffer.decode::<&ipv4::Header>()?;

if !validator.validate_local_ipv4(header.destination()) {
if events.on_ipv4_header(header)?.is_none() {
return Ok(None);
}

Expand All @@ -103,64 +163,45 @@ fn decode_ipv4<'a, D: Decoder<'a>, V: Validator>(
let options_len = count_without_header as usize * (32 / 8);
let (_options, buffer) = buffer.decode_slice(options_len)?;

Ok(
parse_ip_protocol(protocol, buffer, validator)?.map(|(ports, buffer)| {
let source = header.source().with_port(ports.source).into();
let destination = header.destination().with_port(ports.destination).into();
let tuple = Tuple {
source,
destination,
};
(tuple, buffer)
}),
)
parse_ip_protocol(protocol, buffer, events)
}

#[inline(always)]
fn decode_ipv6<'a, D: Decoder<'a>, V: Validator>(
buffer: D,
validator: &V,
) -> Result<Tuple<SocketAddress>, D> {
fn decode_ipv6<'a, D: Decoder<'a>, E: EventHandler>(buffer: D, events: &mut E) -> Result<D> {
let (header, buffer) = buffer.decode::<&ipv6::Header>()?;

if !validator.validate_local_ipv6(header.destination()) {
if events.on_ipv6_header(header)?.is_none() {
return Ok(None);
}

let protocol = header.next_header();

// TODO parse Hop-by-hop/Options headers, for now we'll just forward the packet on to the OS

Ok(
parse_ip_protocol(protocol, buffer, validator)?.map(|(ports, buffer)| {
let source = header.source().with_port(ports.source).into();
let destination = header.destination().with_port(ports.destination).into();
let tuple = Tuple {
source,
destination,
};
(tuple, buffer)
}),
)
parse_ip_protocol(protocol, buffer, events)
}

#[inline]
fn parse_ip_protocol<'a, D: Decoder<'a>, V: Validator>(
fn parse_ip_protocol<'a, D: Decoder<'a>, E: EventHandler>(
protocol: &ip::Protocol,
buffer: D,
validator: &V,
) -> Result<Tuple<u16>, D> {
events: &mut E,
) -> Result<D> {
match *protocol {
ip::Protocol::UDP => parse_udp(buffer, validator),
ip::Protocol::UDP | ip::Protocol::UDPLITE => parse_udp(buffer, events),
// pass the packet on to the OS network stack if we don't understand it
_ => Ok(None),
}
}

#[inline(always)]
fn parse_udp<'a, D: Decoder<'a>, V: Validator>(buffer: D, validator: &V) -> Result<Tuple<u16>, D> {
fn parse_udp<'a, D: Decoder<'a>, E: EventHandler>(buffer: D, events: &mut E) -> Result<D> {
let (header, buffer) = buffer.decode::<&udp::Header>()?;

if events.on_udp_header(header)?.is_none() {
return Ok(None);
}

// NOTE: duvet doesn't know how to parse this RFC since it doesn't follow more modern formatting
//# https://www.rfc-editor.org/rfc/rfc768
//# Length is the length in octets of this user datagram including this
Expand All @@ -172,26 +213,7 @@ fn parse_udp<'a, D: Decoder<'a>, V: Validator>(buffer: D, validator: &V) -> Resu
.ok_or(DecoderError::InvariantViolation("invalid UDP length"))?;
let (udp_payload, _remaining) = buffer.decode_slice(payload_len as usize)?;

let source = header.source().get();
let destination = header.destination().get();

if !validator.validate_local_port(destination) {
return Ok(None);
}

let tuple = Tuple {
source,
destination,
};

Ok(Some((tuple, udp_payload)))
}

/// A generic tuple over an address type
#[derive(Clone, Copy, Debug)]
struct Tuple<Addr> {
source: Addr,
destination: Addr,
Ok(Some(udp_payload))
}

#[cfg(test)]
Expand Down
7 changes: 4 additions & 3 deletions quic/s2n-quic-core/src/xdp/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,15 @@ mod tests {
return;
}

let (mut path, payload) =
let (mut header, payload) =
crate::xdp::decoder::decode_packet(DecoderBufferMut::new(&mut buffer))
.unwrap()
.unwrap();

path.swap();
header.path.swap();

assert!(Handle::eq(&path, &message.path));
assert!(Handle::eq(&header.path, &message.path));
assert_eq!(header.ecn, message.ecn);
assert_eq!(payload.into_less_safe_slice(), &message.payload);
});
}
Expand Down
5 changes: 5 additions & 0 deletions quic/s2n-quic-core/src/xdp/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ pub struct Tuple {
}

impl Tuple {
pub const UNSPECIFIED: Self = Self {
remote_address: RemoteAddress::UNSPECIFIED,
local_address: LocalAddress::UNSPECIFIED,
};

#[inline]
pub fn swap(&mut self) {
core::mem::swap(&mut self.remote_address.mac, &mut self.local_address.mac);
Expand Down
2 changes: 1 addition & 1 deletion tools/xdp/ebpf/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[toolchain]
# pin the version to prevent random breakage
channel = "nightly-2023-03-12"
channel = "nightly-2023-04-28"
# The source code of rustc, provided by the rust-src component, is needed for
# building eBPF programs.
components = [ "rustc", "cargo", "rust-src" ]
Loading

0 comments on commit 5b17b7c

Please sign in to comment.