diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 6720f31d6..e1eb45760 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -73,6 +73,18 @@ pub mod trace_point; pub mod uprobe; pub mod xdp; +// `libc` exposes `SO_ATTACH_REUSEPORT_EBPF` on all architectures, but +// `SO_DETACH_REUSEPORT_BPF` is still commented out in libc's +// `src/unix/linux_like/linux/arch/{mips,powerpc,sparc}/mod.rs`. +// The values below are the asm-generic constants (52 and 68), which are +// correct for every architecture aya supports; sparc uses different values +// but aya does not target sparc. Both are defined locally to keep them +// consistent rather than mixing a libc constant with a hand-written one. +// TODO(https://github.com/rust-lang/libc/commit/95c9572): use libc's +// constants once this lands in a released libc version. +pub(crate) const SO_ATTACH_REUSEPORT_EBPF: libc::c_int = 52; +pub(crate) const SO_DETACH_REUSEPORT_BPF: libc::c_int = 68; + use std::{ borrow::Cow, ffi::CString, @@ -120,7 +132,7 @@ pub use crate::programs::{ sk_reuseport::{SkReuseport, SkReuseportAttachType, SkReuseportError}, sk_skb::{SkSkb, SkSkbKind}, sock_ops::SockOps, - socket_filter::{SocketFilter, SocketFilterError}, + socket_filter::{ReusePortSocketFilter, SocketFilter, SocketFilterError}, tc::{SchedClassifier, TcAttachType, TcError, TcHandle}, tp_btf::BtfTracePoint, trace_point::{TracePoint, TracePointError}, @@ -297,6 +309,8 @@ pub enum Program { TracePoint(TracePoint), /// A [`SocketFilter`] program SocketFilter(SocketFilter), + /// A [`ReusePortSocketFilter`] program + ReusePortSocketFilter(ReusePortSocketFilter), /// A [`Xdp`] program Xdp(Xdp), /// A [`SkMsg`] program @@ -353,7 +367,7 @@ impl Program { match self { Self::KProbe(_) | Self::UProbe(_) => ProgramType::KProbe, Self::TracePoint(_) => ProgramType::TracePoint, - Self::SocketFilter(_) => ProgramType::SocketFilter, + Self::SocketFilter(_) | Self::ReusePortSocketFilter(_) => ProgramType::SocketFilter, Self::Xdp(_) => ProgramType::Xdp, Self::SkMsg(_) => ProgramType::SkMsg, Self::SkSkb(_) => ProgramType::SkSkb, @@ -396,6 +410,7 @@ impl Program { Self::UProbe(p) => p.pin(path), Self::TracePoint(p) => p.pin(path), Self::SocketFilter(p) => p.pin(path), + Self::ReusePortSocketFilter(p) => p.pin(path), Self::Xdp(p) => p.pin(path), Self::SkMsg(p) => p.pin(path), Self::SkSkb(p) => p.pin(path), @@ -430,6 +445,7 @@ impl Program { Self::UProbe(mut p) => p.unload(), Self::TracePoint(mut p) => p.unload(), Self::SocketFilter(mut p) => p.unload(), + Self::ReusePortSocketFilter(mut p) => p.unload(), Self::Xdp(mut p) => p.unload(), Self::SkMsg(mut p) => p.unload(), Self::SkSkb(mut p) => p.unload(), @@ -466,6 +482,7 @@ impl Program { Self::UProbe(p) => p.fd(), Self::TracePoint(p) => p.fd(), Self::SocketFilter(p) => p.fd(), + Self::ReusePortSocketFilter(p) => p.fd(), Self::Xdp(p) => p.fd(), Self::SkMsg(p) => p.fd(), Self::SkSkb(p) => p.fd(), @@ -503,6 +520,7 @@ impl Program { Self::UProbe(p) => p.info(), Self::TracePoint(p) => p.info(), Self::SocketFilter(p) => p.info(), + Self::ReusePortSocketFilter(p) => p.info(), Self::Xdp(p) => p.info(), Self::SkMsg(p) => p.info(), Self::SkSkb(p) => p.info(), @@ -848,6 +866,7 @@ impl_program_unload!( UProbe, TracePoint, SocketFilter, + ReusePortSocketFilter, Xdp, SkMsg, SkSkb, @@ -892,6 +911,7 @@ impl_fd!( UProbe, TracePoint, SocketFilter, + ReusePortSocketFilter, Xdp, SkMsg, SkSkb, @@ -1130,7 +1150,14 @@ macro_rules! impl_program_test_run { } } -impl_program_test_run!(SocketFilter, SchedClassifier, Xdp, CgroupSkb, FlowDissector); +impl_program_test_run!( + SocketFilter, + ReusePortSocketFilter, + SchedClassifier, + Xdp, + CgroupSkb, + FlowDissector, +); impl TestRun for RawTracePoint { type Opts<'a> = RawTracePointRunOptions; @@ -1239,6 +1266,7 @@ impl_program_pin!( UProbe, TracePoint, SocketFilter, + ReusePortSocketFilter, Xdp, SkMsg, SkSkb, @@ -1391,6 +1419,8 @@ impl_from_prog_info!( unsafe KProbe kind : ProbeKind, unsafe UProbe kind : ProbeKind, TracePoint, + SocketFilter, + ReusePortSocketFilter, Xdp attach_type : XdpAttachType, SkMsg, SkSkb kind : SkSkbKind, @@ -1441,11 +1471,55 @@ macro_rules! impl_try_from_program { } } +// Socket filters are special: Aya follows libbpf section rules, so +// `SEC("socket")` always loads as `Program::SocketFilter`. There is no +// `socket/reuseport` section to distinguish the reuseport use at load time. +// The two uses have different return value semantics, so a program is not +// expected to be meaningful for both; modeling them as separate program +// abstractions is useful even though they share a section. +macro_rules! impl_try_from_socket_filter_program { + ($ty:ident, $variant:ident, $other_variant:ident $(,)?) => { + impl<'a> TryFrom<&'a Program> for &'a $ty { + type Error = ProgramError; + + fn try_from(program: &'a Program) -> Result<&'a $ty, ProgramError> { + match program { + Program::$variant(p) => Ok(p), + Program::$other_variant(p) => { + // SAFETY: Both variants are `repr(transparent)` + // wrappers around the same `ProgramData` field. + Ok(unsafe { &*std::ptr::from_ref(p).cast::<$ty>() }) + } + _ => Err(ProgramError::UnexpectedProgramType), + } + } + } + + impl<'a> TryFrom<&'a mut Program> for &'a mut $ty { + type Error = ProgramError; + + fn try_from(program: &'a mut Program) -> Result<&'a mut $ty, ProgramError> { + match program { + Program::$variant(p) => Ok(p), + Program::$other_variant(other) => { + // SAFETY: Both variants are `repr(transparent)` + // wrappers around the same `ProgramData` field. + Ok(unsafe { &mut *std::ptr::from_mut(other).cast::<$ty>() }) + } + _ => Err(ProgramError::UnexpectedProgramType), + } + } + } + }; +} + +impl_try_from_socket_filter_program!(SocketFilter, SocketFilter, ReusePortSocketFilter); +impl_try_from_socket_filter_program!(ReusePortSocketFilter, ReusePortSocketFilter, SocketFilter); + impl_try_from_program!( KProbe, UProbe, TracePoint, - SocketFilter, Xdp, SkMsg, SkSkb, @@ -1477,6 +1551,7 @@ impl_info!( UProbe, TracePoint, SocketFilter, + ReusePortSocketFilter, Xdp, SkMsg, SkSkb, diff --git a/aya/src/programs/sk_reuseport.rs b/aya/src/programs/sk_reuseport.rs index ae504f68c..c421bfe7e 100644 --- a/aya/src/programs/sk_reuseport.rs +++ b/aya/src/programs/sk_reuseport.rs @@ -14,20 +14,11 @@ use thiserror::Error; use crate::{ VerifierLogLevel, programs::{ - ProgramData, ProgramError, ProgramType, links::FdLink, load_program_with_attach_type, + ProgramData, ProgramError, ProgramType, SO_ATTACH_REUSEPORT_EBPF, SO_DETACH_REUSEPORT_BPF, + links::FdLink, load_program_with_attach_type, }, }; -// `libc` exposes `SO_ATTACH_REUSEPORT_EBPF` on all architectures, but -// `SO_DETACH_REUSEPORT_BPF` is still commented out in libc's -// `src/unix/linux_like/linux/arch/{mips,powerpc,sparc}/mod.rs`. -// The values below are the asm-generic constants (52 and 68), which are -// correct for every architecture aya supports; sparc uses different values -// but aya does not target sparc. Both are defined locally to keep them -// consistent rather than mixing a libc constant with a hand-written one. -const SO_ATTACH_REUSEPORT_EBPF: libc::c_int = 52; -const SO_DETACH_REUSEPORT_BPF: libc::c_int = 68; - macro_rules! setsockopt_reuseport { ($socket:expr, $option:ident, $value:expr) => {{ let value = $value; diff --git a/aya/src/programs/socket_filter.rs b/aya/src/programs/socket_filter.rs index 49a2d7b33..c01ce7f43 100644 --- a/aya/src/programs/socket_filter.rs +++ b/aya/src/programs/socket_filter.rs @@ -15,25 +15,26 @@ use thiserror::Error; use crate::{ VerifierLogLevel, programs::{ - ProgramData, ProgramError, ProgramType, links::FdLink, load_program_without_attach_type, + ProgramData, ProgramError, ProgramType, SO_ATTACH_REUSEPORT_EBPF, SO_DETACH_REUSEPORT_BPF, + links::FdLink, load_program_without_attach_type, }, }; macro_rules! setsockopt_socket_filter { - ($socket:expr, $option:ident, $value:expr) => {{ + ($socket:expr, $option:expr, $option_name:expr, $value:expr) => {{ let value = $value; let ret = unsafe { setsockopt( $socket, SOL_SOCKET, - $option as libc::c_int, + $option, ptr::from_ref(value).cast(), size_of_val(value) as libc::socklen_t, ) }; if ret < 0 { Err(SocketFilterError::SetsockoptError { - option: stringify!($option), + option: $option_name, io_error: io::Error::last_os_error(), }) } else { @@ -58,22 +59,35 @@ pub enum SocketFilterError { /// A program used to inspect and filter incoming packets on a socket. /// -/// [`SocketFilter`] programs are attached on sockets and can be used to inspect -/// and filter incoming packets. +/// This is a `BPF_PROG_TYPE_SOCKET_FILTER` program attached as a regular +/// socket filter. The same kernel program type can also be attached to +/// `SO_REUSEPORT` groups through [`ReusePortSocketFilter`]. /// -/// Each socket has one filter slot. Attaching a new [`SocketFilter`] replaces -/// the socket's current filter, and detaching clears that current filter -/// regardless of which program installed it. Aya therefore does not expose a -/// link-style attachment handle for [`SocketFilter`] or automatically track -/// socket filter attachments for cleanup. Dropping [`SocketFilter`] or -/// [`crate::Ebpf`] does not detach the filter; call [`SocketFilter::detach`] -/// explicitly when you want to remove it, or close the socket. +/// Since both abstractions use the same libbpf `SEC("socket")` section, +/// converting from [`crate::programs::Program`] with `try_into` selects this +/// abstraction from the caller's requested type rather than from distinct load +/// metadata. +/// +/// The return value is interpreted as a packet length: `0` drops the packet, a +/// value greater than or equal to the packet length accepts the whole packet, +/// and a smaller positive value trims the packet to that length. +/// +/// Regular socket filters are scoped to one socket. `SO_ATTACH_BPF` writes the +/// socket's `sk->sk_filter` field: +/// +/// +/// Attaching a new program replaces the current program in that slot, and +/// detaching clears the slot regardless of which program installed it. On the +/// same socket, [`SocketFilter`] and [`ReusePortSocketFilter`] use different +/// kernel slots and do not affect each other. For that reason, [`SocketFilter`] +/// does not expose a link-style attachment handle or automatically track +/// attachments for cleanup. Dropping [`SocketFilter`] or [`crate::Ebpf`] does +/// not detach the program; call [`SocketFilter::detach`] explicitly when you +/// want to remove it, or close the socket. /// /// # Minimum kernel version /// -/// The minimum kernel version required to use this feature is 3.19. -/// `BPF_PROG_TYPE_SOCKET_FILTER` and `SO_ATTACH_BPF` are present in Linux -/// v3.19: +/// `BPF_PROG_TYPE_SOCKET_FILTER` and `SO_ATTACH_BPF` are present in Linux 3.19: /// /// /// @@ -101,6 +115,10 @@ pub enum SocketFilterError { /// prog.attach(&client)?; /// # Ok::<(), Error>(()) /// ``` +// Invariant: `TryFrom` casts references between `SocketFilter` and +// `ReusePortSocketFilter`. Keep both types as transparent wrappers around the +// same `ProgramData` field. +#[repr(transparent)] #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_SOCKET_FILTER")] pub struct SocketFilter { @@ -117,42 +135,50 @@ impl SocketFilter { load_program_without_attach_type(BPF_PROG_TYPE_SOCKET_FILTER, data) } - /// Attaches the filter on the given socket. + /// Attaches the program on the given socket. /// - /// If the socket already has a filter attached, attaching again replaces - /// the current filter instead of returning an already-attached error. This - /// follows the kernel model: each socket has one filter slot and cannot run - /// multiple socket filters together. The kernel detach API also clears the - /// socket's current filter slot; it cannot detach a specific program - /// attachment. For that reason, Aya does not provide link-level RAII - /// semantics for socket filters. Dropping [`SocketFilter`] or [`crate::Ebpf`] - /// does not detach the filter. Call [`SocketFilter::detach`] explicitly when - /// you want to remove it, or close the socket. + /// If the socket already has a regular filter attached, attaching again + /// replaces the current filter instead of returning an already-attached + /// error. pub fn attach(&self, socket: T) -> Result<(), ProgramError> { - let prog_fd = self.fd()?; - let prog_fd = prog_fd.as_fd().as_raw_fd(); + let prog_fd = self.fd()?.as_fd().as_raw_fd(); let socket = socket.as_fd().as_raw_fd(); - setsockopt_socket_filter!(socket, SO_ATTACH_BPF, &prog_fd)?; + setsockopt_socket_filter!( + socket, + SO_ATTACH_BPF as libc::c_int, + "SO_ATTACH_BPF", + &prog_fd + )?; Ok(()) } - /// Detaches the current filter from the given socket. + /// Detaches the current regular socket filter from the given socket. /// /// Detaching clears the socket's current filter slot, regardless of which - /// program was used to attach that filter. Unlike [`SocketFilter::attach`], - /// this operation does not require the program to remain loaded in this - /// process. If another filter replaced this program on the same socket, - /// detaching will remove that replacement filter. + /// program was used to attach that filter. If another filter replaced this + /// program on the same socket, detaching will remove that replacement + /// filter. It does not affect the socket's reuseport group selector. + /// + /// Unlike [`SocketFilter::attach`], this operation does not require the + /// program to remain loaded in this process. pub fn detach(socket: T) -> Result<(), ProgramError> { let socket = socket.as_fd().as_raw_fd(); // `SO_DETACH_BPF` is an alias for `SO_DETACH_FILTER`; Linux's // `sk_setsockopt()` requires an `int` optval, but the detach branch only - // calls `sk_detach_filter(sk)` and does not use a program fd: - // https://github.com/torvalds/linux/blob/v6.9/net/core/sock.c#L1413-L1414 + // calls `sk_detach_filter(sk)` and does not use a program fd. + // The generic `SOL_SOCKET` path still requires an int-sized optval + // before dispatching on the specific sockopt. + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock.c#L1409-L1414 let dummy: libc::c_int = 0; - setsockopt_socket_filter!(socket, SO_DETACH_BPF, &dummy)?; + + setsockopt_socket_filter!( + socket, + SO_DETACH_BPF as libc::c_int, + "SO_DETACH_BPF", + &dummy + )?; Ok(()) } @@ -169,3 +195,182 @@ impl SocketFilter { Ok(Self { data }) } } + +/// A socket filter program used as a `SO_REUSEPORT` group selector. +/// +/// This is a `BPF_PROG_TYPE_SOCKET_FILTER` program attached through +/// `SO_ATTACH_REUSEPORT_EBPF`. The same kernel program type can also be +/// attached as a regular socket filter through [`SocketFilter`]. +/// +/// Since both abstractions use the same libbpf `SEC("socket")` section, +/// converting from [`crate::programs::Program`] with `try_into` selects this +/// abstraction from the caller's requested type rather than from distinct load +/// metadata. +/// +/// The program return value is interpreted as the selected socket index in the +/// reuseport group. The socket must already be configured with `SO_REUSEPORT`. +/// +/// Regular [`SocketFilter`] programs and [`ReusePortSocketFilter`] programs use +/// separate kernel-managed slots. Regular filters are scoped to one socket; +/// reuseport selectors are scoped to the whole `SO_REUSEPORT` group. +/// `SO_ATTACH_REUSEPORT_EBPF` writes the group's `reuse->prog` field: +/// +/// +/// Attaching or detaching one type does not affect the other. For reuseport +/// groups, attaching through any socket in the group replaces the program used +/// by the entire group, and detaching through any socket in the group clears +/// the selector. [`ReusePortSocketFilter`] does not expose a link-style +/// attachment handle or automatically track group attachments for cleanup. +/// Dropping [`ReusePortSocketFilter`] or [`crate::Ebpf`] does not detach the +/// program; call [`ReusePortSocketFilter::detach`] explicitly when you want to +/// remove it, or close all sockets in the group so the group itself is +/// destroyed. +/// +/// [`SkReuseport`](crate::programs::SkReuseport) is the purpose-built program +/// type for `SO_REUSEPORT` selection on newer kernels; `ReusePortSocketFilter` +/// is useful when you need the older `SO_ATTACH_REUSEPORT_EBPF` socket-filter +/// path or want to attach an existing `SEC("socket")` program to a reuseport +/// group. +/// +/// # Minimum kernel version +/// +/// `SO_ATTACH_REUSEPORT_EBPF` can attach socket filter programs to UDP +/// `SO_REUSEPORT` groups starting in Linux 4.5 and TCP groups starting in +/// Linux 4.6: +/// +/// +/// +/// `SO_DETACH_REUSEPORT_BPF` is handled starting in Linux 5.3: +/// +/// +/// # Examples +/// +/// ```no_run +/// # #[derive(Debug, thiserror::Error)] +/// # enum Error { +/// # #[error(transparent)] +/// # IO(#[from] std::io::Error), +/// # #[error(transparent)] +/// # Map(#[from] aya::maps::MapError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Ebpf(#[from] aya::EbpfError) +/// # } +/// # let mut bpf = aya::Ebpf::load(&[])?; +/// use std::{ +/// io, +/// net::{Ipv4Addr, SocketAddrV4, TcpListener}, +/// os::fd::AsRawFd, +/// }; +/// +/// use aya::programs::ReusePortSocketFilter; +/// use nix::sys::socket::{ +/// AddressFamily, Backlog, SockFlag, SockType, SockaddrIn, bind, listen, setsockopt, +/// socket, sockopt::ReusePort, +/// }; +/// +/// // `SO_REUSEPORT` must be set before `bind(2)`. `std::net::TcpListener` +/// // does not expose that pre-bind socket setup step, so this example uses +/// // `nix` to create and configure the socket directly. +/// fn reuseport_listener(port: u16) -> io::Result { +/// let fd = socket( +/// AddressFamily::Inet, +/// SockType::Stream, +/// SockFlag::empty(), +/// None, +/// ) +/// .map_err(io::Error::other)?; +/// +/// setsockopt(&fd, ReusePort, &true).map_err(io::Error::other)?; +/// +/// let addr = SockaddrIn::from(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)); +/// bind(fd.as_raw_fd(), &addr).map_err(io::Error::other)?; +/// listen(&fd, Backlog::MAXCONN).map_err(io::Error::other)?; +/// +/// Ok(TcpListener::from(fd)) +/// } +/// +/// # #[cfg(target_os = "linux")] { +/// let listener = reuseport_listener(8080)?; +/// let program: &mut ReusePortSocketFilter = bpf.program_mut("select_socket").unwrap().try_into()?; +/// program.load()?; +/// program.attach(&listener)?; +/// # } +/// # Ok::<(), Error>(()) +/// ``` +// Invariant: `TryFrom` casts references between `SocketFilter` and +// `ReusePortSocketFilter`. Keep both types as transparent wrappers around the +// same `ProgramData` field. +#[repr(transparent)] +#[derive(Debug)] +#[doc(alias = "BPF_PROG_TYPE_SOCKET_FILTER")] +pub struct ReusePortSocketFilter { + pub(crate) data: ProgramData, +} + +impl ReusePortSocketFilter { + /// The type of the program according to the kernel. + pub const PROGRAM_TYPE: ProgramType = ProgramType::SocketFilter; + + /// Loads the program inside the kernel. + pub fn load(&mut self) -> Result<(), ProgramError> { + let Self { data } = self; + load_program_without_attach_type(BPF_PROG_TYPE_SOCKET_FILTER, data) + } + + /// Attaches the program as a `SO_REUSEPORT` selector. + /// + /// Attaching through any socket in the group replaces the program used by + /// the entire group. + pub fn attach(&self, socket: T) -> Result<(), ProgramError> { + let prog_fd = self.fd()?.as_fd().as_raw_fd(); + let socket = socket.as_fd().as_raw_fd(); + + setsockopt_socket_filter!( + socket, + SO_ATTACH_REUSEPORT_EBPF, + "SO_ATTACH_REUSEPORT_EBPF", + &prog_fd + )?; + Ok(()) + } + + /// Detaches the current reuseport selector from the socket's group. + /// + /// Detaching through any socket in a reuseport group removes the program + /// from the entire group, regardless of which socket in that group was used + /// to attach it. Detaching a reuseport selector from a socket that is not + /// in a reuseport group returns `EINVAL`. It does not affect regular + /// filters attached to individual sockets in the group. + pub fn detach(socket: T) -> Result<(), ProgramError> { + let socket = socket.as_fd().as_raw_fd(); + + // `SO_DETACH_REUSEPORT_BPF` identifies the target group from the + // socket. The generic `SOL_SOCKET` path still requires an int-sized + // optval before dispatching on the specific sockopt. + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock.c#L1409-L1414 + let dummy: libc::c_int = 0; + + setsockopt_socket_filter!( + socket, + SO_DETACH_REUSEPORT_BPF, + "SO_DETACH_REUSEPORT_BPF", + &dummy + )?; + Ok(()) + } + + /// Creates a program from a pinned entry on a bpffs. + /// + /// `ReusePortSocketFilter` does not use link-style attachments, so this + /// only restores access to the pinned program itself. + /// + /// Dropping the returned value unloads the local program FD, but does not + /// detach the selector from any reuseport group. This will also not unload + /// the program from the kernel while it remains pinned. + pub fn from_pin>(path: P) -> Result { + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; + Ok(Self { data }) + } +} diff --git a/test/integration-common/src/lib.rs b/test/integration-common/src/lib.rs index 5daec1638..6b487a225 100644 --- a/test/integration-common/src/lib.rs +++ b/test/integration-common/src/lib.rs @@ -242,8 +242,14 @@ pub mod sk_reuseport { pub mod socket_filter { pub const PASS_HITS_INDEX: u32 = 0; pub const TRIM_HITS_INDEX: u32 = 1; - pub const PATH_HITS_MAX_ENTRIES: u32 = 2; + + pub const REUSEPORT_SELECT_FIRST_HITS_INDEX: u32 = 2; + pub const REUSEPORT_SELECT_SECOND_HITS_INDEX: u32 = 3; + pub const PATH_HITS_MAX_ENTRIES: u32 = 4; + pub const TRIM_DELTA_BYTES: u32 = 4; + pub const REUSEPORT_FIRST_LISTENER_INDEX: i64 = 0; + pub const REUSEPORT_SECOND_LISTENER_INDEX: i64 = 1; } pub mod stack_trace { diff --git a/test/integration-ebpf/src/socket_filter.rs b/test/integration-ebpf/src/socket_filter.rs index 00a76cc49..82c614f3d 100644 --- a/test/integration-ebpf/src/socket_filter.rs +++ b/test/integration-ebpf/src/socket_filter.rs @@ -8,7 +8,9 @@ use aya_ebpf::{ programs::SkBuffContext, }; use integration_common::socket_filter::{ - PASS_HITS_INDEX, PATH_HITS_MAX_ENTRIES, TRIM_DELTA_BYTES, TRIM_HITS_INDEX, + PASS_HITS_INDEX, PATH_HITS_MAX_ENTRIES, REUSEPORT_FIRST_LISTENER_INDEX, + REUSEPORT_SECOND_LISTENER_INDEX, REUSEPORT_SELECT_FIRST_HITS_INDEX, + REUSEPORT_SELECT_SECOND_HITS_INDEX, TRIM_DELTA_BYTES, TRIM_HITS_INDEX, }; #[cfg(not(test))] extern crate ebpf_panic; @@ -38,3 +40,25 @@ fn trim_packets(ctx: SkBuffContext) -> i64 { record_hit(TRIM_HITS_INDEX); i64::from(ctx.len().saturating_sub(TRIM_DELTA_BYTES)) } + +#[socket_filter] +fn select_first(_ctx: SkBuffContext) -> i64 { + record_hit(REUSEPORT_SELECT_FIRST_HITS_INDEX); + // For SO_ATTACH_REUSEPORT_EBPF, the return value selects + // `reuse->socks[index]`: + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock_reuseport.c#L517-L525 + // The integration test binds `first` before `second`. The kernel initialises + // the first group member at socks[0] and appends later sockets at + // socks[num_socks], so index 0 selects `first`: + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock_reuseport.c#L233-L238 + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock_reuseport.c#L124-L130 + REUSEPORT_FIRST_LISTENER_INDEX +} + +#[socket_filter] +fn select_second(_ctx: SkBuffContext) -> i64 { + record_hit(REUSEPORT_SELECT_SECOND_HITS_INDEX); + // `second` is appended after `first` joins the reuseport group, so index 1 + // selects `second`. + REUSEPORT_SECOND_LISTENER_INDEX +} diff --git a/test/integration-test/src/tests/sk_reuseport.rs b/test/integration-test/src/tests/sk_reuseport.rs index ad6e367fc..64b2a31b5 100644 --- a/test/integration-test/src/tests/sk_reuseport.rs +++ b/test/integration-test/src/tests/sk_reuseport.rs @@ -154,6 +154,7 @@ async fn sk_reuseport_selects_expected_listener( #[case] socket_map: &str, #[case] select_prog: &str, ) { + // Aya's CI VM init may leave `lo` down; NetNsGuard brings it up. let _netns = NetNsGuard::new(); let mut ebpf = Ebpf::load(crate::SK_REUSEPORT).unwrap(); @@ -215,6 +216,7 @@ async fn sk_reuseport_clear_index_changes_selection( #[case] socket_map: &str, #[case] clear_prog: &str, ) { + // Aya's CI VM init may leave `lo` down; NetNsGuard brings it up. let _netns = NetNsGuard::new(); let mut ebpf = Ebpf::load(crate::SK_REUSEPORT).unwrap(); @@ -275,6 +277,7 @@ async fn sk_reuseport_clear_index_changes_selection( #[case::btf("select_socket_btf")] #[test_attr(test_log::test(tokio::test))] async fn sk_reuseport_stays_attached_until_explicit_detach(#[case] select_prog: &str) { + // Aya's CI VM init may leave `lo` down; NetNsGuard brings it up. let _netns = NetNsGuard::new(); let mut ebpf = Ebpf::load(crate::SK_REUSEPORT).unwrap(); @@ -314,6 +317,7 @@ async fn sk_reuseport_migrates_to_expected_listener( return; } + // Aya's CI VM init may leave `lo` down; NetNsGuard brings it up. let _netns = NetNsGuard::new(); match std::fs::write("/proc/sys/net/ipv4/tcp_migrate_req", "1") { Ok(()) => {} diff --git a/test/integration-test/src/tests/socket_filter.rs b/test/integration-test/src/tests/socket_filter.rs index 0281321d8..738cc5ce9 100644 --- a/test/integration-test/src/tests/socket_filter.rs +++ b/test/integration-test/src/tests/socket_filter.rs @@ -1,15 +1,32 @@ -use std::time::Duration; +use std::{ + io, + net::{Ipv4Addr, SocketAddr}, + time::Duration, +}; +use assert_matches::assert_matches; use aya::{ Ebpf, maps::{Array, MapData}, - programs::{ProgramType, SocketFilter}, + programs::{ProgramError, ProgramType, ReusePortSocketFilter, SocketFilter, SocketFilterError}, sys::is_program_supported, + util::KernelVersion, +}; +use integration_common::socket_filter::{ + PASS_HITS_INDEX, REUSEPORT_FIRST_LISTENER_INDEX, REUSEPORT_SECOND_LISTENER_INDEX, + REUSEPORT_SELECT_FIRST_HITS_INDEX, REUSEPORT_SELECT_SECOND_HITS_INDEX, TRIM_DELTA_BYTES, + TRIM_HITS_INDEX, +}; +use libc::{EINVAL, ENOENT}; +use tokio::{ + net::{TcpListener, TcpSocket, TcpStream, UnixDatagram}, + time::timeout, }; -use integration_common::socket_filter::{PASS_HITS_INDEX, TRIM_DELTA_BYTES, TRIM_HITS_INDEX}; -use tokio::{net::UnixDatagram, time::timeout}; + +use crate::utils::NetNsGuard; const IO_TIMEOUT: Duration = Duration::from_secs(10); +const ACCEPT_TIMEOUT: Duration = Duration::from_secs(10); const TEST_PAYLOAD: &[u8] = b"hello-world"; // Leave room for one extra byte so an unexpectedly long datagram is not // truncated to the expected length. @@ -19,6 +36,66 @@ fn read_hits(hits: &Array, index: u32) -> u64 { hits.get(&index, 0).unwrap() } +fn reuseport_detach_supported() -> bool { + let kernel_version = KernelVersion::current().unwrap(); + // `SO_DETACH_REUSEPORT_BPF` is handled starting in Linux 5.3: + // https://github.com/torvalds/linux/blob/v5.3/net/core/sock.c#L1042-L1044 + if kernel_version < KernelVersion::new(5, 3, 0) { + eprintln!( + "skipping test on kernel {kernel_version:?}, SO_DETACH_REUSEPORT_BPF requires 5.3" + ); + return false; + } + + true +} + +fn reuseport_listener(port: u16) -> io::Result { + const LISTEN_BACKLOG: u32 = 1; + + // `SO_REUSEPORT` must be set before `bind(2)`. `tokio::net::TcpListener` + // is already bound, so create the socket through `TcpSocket` first. + let socket = TcpSocket::new_v4()?; + socket.set_reuseport(true)?; + socket.bind(SocketAddr::from((Ipv4Addr::LOCALHOST, port)))?; + socket.listen(LISTEN_BACKLOG) +} + +fn reuseport_listeners() -> [TcpListener; 2] { + let first = reuseport_listener(0).expect("failed to create first reuseport listener"); + let port = first + .local_addr() + .expect("failed to read first reuseport listener address") + .port(); + let second = reuseport_listener(port).expect("failed to create second reuseport listener"); + [first, second] +} + +async fn accept_from_either(first: &TcpListener, second: &TcpListener) -> i64 { + // Keep these return values aligned with the indexes returned by the eBPF + // reuseport selectors. `reuseport_listeners()` binds `first` before + // `second`, and the kernel indexes reuseport sockets by group insertion + // order, not by the socket used later for SO_ATTACH_REUSEPORT_EBPF: + // - first socket becomes socks[0]: + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock_reuseport.c#L233-L238 + // - later sockets are appended at socks[num_socks]: + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock_reuseport.c#L124-L130 + timeout(ACCEPT_TIMEOUT, async { + tokio::select! { + result = first.accept() => { + result.expect("failed to accept connection"); + REUSEPORT_FIRST_LISTENER_INDEX + } + result = second.accept() => { + result.expect("failed to accept connection"); + REUSEPORT_SECOND_LISTENER_INDEX + } + } + }) + .await + .expect("timed out waiting for accept") +} + async fn send_and_assert(sender: &UnixDatagram, receiver: &UnixDatagram, trim_bytes: usize) { sender .send(TEST_PAYLOAD) @@ -94,6 +171,336 @@ async fn socket_filter_program_can_trim_packets_and_detach() { ); } +#[test_log::test(tokio::test)] +async fn socket_filter_attach_types_use_separate_slots() { + if !is_program_supported(ProgramType::SocketFilter).unwrap() { + eprintln!("skipping test - socket_filter program not supported"); + return; + } + + if !reuseport_detach_supported() { + return; + } + + // Aya's CI VM init may leave `lo` down; NetNsGuard brings it up. + let _netns = NetNsGuard::new(); + let listener = reuseport_listener(0).expect("failed to create reuseport listener"); + + let mut ebpf = Ebpf::load(crate::SOCKET_FILTER).unwrap(); + let path_hits: Array<_, u64> = ebpf.take_map("path_hits").unwrap().try_into().unwrap(); + + { + let prog: &mut SocketFilter = ebpf + .program_mut("pass_packets") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.attach(&listener).unwrap(); + } + + // Attaching a regular socket filter must not populate the reuseport + // selector slot. + let err = ReusePortSocketFilter::detach(&listener).unwrap_err(); + assert_matches!( + err, + ProgramError::SocketFilterError(SocketFilterError::SetsockoptError { + option: "SO_DETACH_REUSEPORT_BPF", + io_error, + }) if io_error.raw_os_error() == Some(ENOENT) + ); + SocketFilter::detach(&listener).expect("regular socket filter should still be attached"); + + { + let prog: &mut ReusePortSocketFilter = ebpf + .program_mut("select_first") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.attach(&listener).unwrap(); + } + + // A TCP listener is awkward for a regular socket filter path assertion, so + // only smoke-test that the reuseport selector actually runs. + let _client = TcpStream::connect(listener.local_addr().unwrap()) + .await + .unwrap(); + timeout(ACCEPT_TIMEOUT, listener.accept()) + .await + .unwrap() + .unwrap(); + assert!( + read_hits(&path_hits, REUSEPORT_SELECT_FIRST_HITS_INDEX) > 0, + "reuseport path did not run", + ); + + // Conversely, attaching a reuseport selector must not populate the + // socket's regular filter slot. + let err = SocketFilter::detach(&listener).unwrap_err(); + assert_matches!( + err, + ProgramError::SocketFilterError(SocketFilterError::SetsockoptError { + option: "SO_DETACH_BPF", + io_error, + }) if io_error.raw_os_error() == Some(ENOENT) + ); + // The reuseport selector should still be attached after the wrong detach + // type above. + ReusePortSocketFilter::detach(&listener).expect("reuseport selector should still be attached"); + + { + let prog: &mut SocketFilter = ebpf + .program_mut("pass_packets") + .unwrap() + .try_into() + .unwrap(); + prog.attach(&listener).unwrap(); + } + { + let prog: &mut ReusePortSocketFilter = ebpf + .program_mut("select_first") + .unwrap() + .try_into() + .unwrap(); + prog.attach(&listener).unwrap(); + } + + // Both slots can be populated on the same socket at the same time. + ReusePortSocketFilter::detach(&listener).expect("failed to detach reuseport selector"); + SocketFilter::detach(&listener).expect("failed to detach regular socket filter"); +} + +#[test_log::test(tokio::test)] +async fn socket_filter_reuseport_selects_listener_and_detaches() { + if !is_program_supported(ProgramType::SocketFilter).unwrap() { + eprintln!("skipping test - socket_filter program not supported"); + return; + } + + if !reuseport_detach_supported() { + return; + } + + // Aya's CI VM init may leave `lo` down; NetNsGuard brings it up. + let _netns = NetNsGuard::new(); + let [first, second] = reuseport_listeners(); + let addr = first.local_addr().unwrap(); + + let mut ebpf = Ebpf::load(crate::SOCKET_FILTER).unwrap(); + let path_hits: Array<_, u64> = ebpf.take_map("path_hits").unwrap().try_into().unwrap(); + let prog: &mut ReusePortSocketFilter = ebpf + .program_mut("select_second") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.attach(&first).unwrap(); + + let _client = TcpStream::connect(addr).await.unwrap(); + assert_eq!( + accept_from_either(&first, &second).await, + REUSEPORT_SECOND_LISTENER_INDEX, + "reuseport socket filter did not select the second listener", + ); + assert!( + read_hits(&path_hits, REUSEPORT_SELECT_SECOND_HITS_INDEX) > 0, + "reuseport path did not run", + ); + let hits_before_detach = read_hits(&path_hits, REUSEPORT_SELECT_SECOND_HITS_INDEX); + + // Reuseport detach is group-scoped: detaching through `second` clears the + // selector for the whole group, including `first`. + ReusePortSocketFilter::detach(&second).unwrap(); + + let err = ReusePortSocketFilter::detach(&first).unwrap_err(); + assert_matches!( + err, + ProgramError::SocketFilterError(SocketFilterError::SetsockoptError { + option: "SO_DETACH_REUSEPORT_BPF", + io_error, + }) if io_error.raw_os_error() == Some(ENOENT) + ); + + let _client = TcpStream::connect(addr).await.unwrap(); + accept_from_either(&first, &second).await; + assert_eq!( + read_hits(&path_hits, REUSEPORT_SELECT_SECOND_HITS_INDEX), + hits_before_detach, + "reuseport path ran after detach", + ); +} + +#[test_log::test(tokio::test)] +async fn socket_filter_reuseport_stays_attached_after_ebpf_drop() { + if !is_program_supported(ProgramType::SocketFilter).unwrap() { + eprintln!("skipping test - socket_filter program not supported"); + return; + } + + if !reuseport_detach_supported() { + return; + } + + // Aya's CI VM init may leave `lo` down; NetNsGuard brings it up. + let _netns = NetNsGuard::new(); + let [first, second] = reuseport_listeners(); + let addr = first.local_addr().unwrap(); + + // Dropping `Ebpf` at the end of this scope releases local program FDs, but + // the reuseport group slot owns the attachment and must keep it active. + let path_hits: Array<_, u64> = { + let mut ebpf = Ebpf::load(crate::SOCKET_FILTER).unwrap(); + let path_hits: Array<_, u64> = ebpf.take_map("path_hits").unwrap().try_into().unwrap(); + let prog: &mut ReusePortSocketFilter = ebpf + .program_mut("select_second") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.attach(&first).unwrap(); + path_hits + }; + + let _client = TcpStream::connect(addr).await.unwrap(); + assert_eq!( + accept_from_either(&first, &second).await, + REUSEPORT_SECOND_LISTENER_INDEX, + "dropping Ebpf detached the reuseport socket filter", + ); + let hits_before_detach = read_hits(&path_hits, REUSEPORT_SELECT_SECOND_HITS_INDEX); + assert!( + hits_before_detach > 0, + "reuseport path did not run after Ebpf drop", + ); + + ReusePortSocketFilter::detach(&first).unwrap(); + + let _client = TcpStream::connect(addr).await.unwrap(); + accept_from_either(&first, &second).await; + assert_eq!( + read_hits(&path_hits, REUSEPORT_SELECT_SECOND_HITS_INDEX), + hits_before_detach, + "reuseport path ran after detach", + ); +} + +#[test_log::test(tokio::test)] +async fn socket_filter_reuseport_replacement_uses_latest_program() { + if !is_program_supported(ProgramType::SocketFilter).unwrap() { + eprintln!("skipping test - socket_filter program not supported"); + return; + } + + if !reuseport_detach_supported() { + return; + } + + // Aya's CI VM init may leave `lo` down; NetNsGuard brings it up. + let _netns = NetNsGuard::new(); + let [first, second] = reuseport_listeners(); + let addr = first.local_addr().unwrap(); + + let mut ebpf = Ebpf::load(crate::SOCKET_FILTER).unwrap(); + let path_hits: Array<_, u64> = ebpf.take_map("path_hits").unwrap().try_into().unwrap(); + + // Drop the mutable program reference before exercising the attachment. The + // kernel slot, not a link handle, owns the attachment, so ending this scope + // must not detach it. + { + let first_prog: &mut ReusePortSocketFilter = ebpf + .program_mut("select_first") + .unwrap() + .try_into() + .unwrap(); + first_prog.load().unwrap(); + first_prog.attach(&first).unwrap(); + } + + let _client = TcpStream::connect(addr).await.unwrap(); + assert_eq!( + accept_from_either(&first, &second).await, + REUSEPORT_FIRST_LISTENER_INDEX, + "first reuseport socket filter did not select the first listener", + ); + let first_hits_before_replacement = read_hits(&path_hits, REUSEPORT_SELECT_FIRST_HITS_INDEX); + assert!( + first_hits_before_replacement > 0, + "first reuseport path did not run", + ); + + // A second attach through the same reuseport group replaces `reuse->prog`; + // it does not create a second attachment or change the socket indexes: + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock_reuseport.c#L684-L712 + { + let second_prog: &mut ReusePortSocketFilter = ebpf + .program_mut("select_second") + .unwrap() + .try_into() + .unwrap(); + second_prog.load().unwrap(); + second_prog.attach(&first).unwrap(); + } + + let _client = TcpStream::connect(addr).await.unwrap(); + assert_eq!( + accept_from_either(&first, &second).await, + REUSEPORT_SECOND_LISTENER_INDEX, + "replacement reuseport socket filter did not select the second listener", + ); + assert_eq!( + read_hits(&path_hits, REUSEPORT_SELECT_FIRST_HITS_INDEX), + first_hits_before_replacement, + "replaced reuseport path ran after replacement", + ); + assert!( + read_hits(&path_hits, REUSEPORT_SELECT_SECOND_HITS_INDEX) > 0, + "replacement reuseport path did not run", + ); + + ReusePortSocketFilter::detach(&second).unwrap(); +} + +#[test_log::test(tokio::test)] +async fn socket_filter_reuseport_errors_without_reuseport() { + if !is_program_supported(ProgramType::SocketFilter).unwrap() { + eprintln!("skipping test - socket_filter program not supported"); + return; + } + + let kernel_version = KernelVersion::current().unwrap(); + if kernel_version < KernelVersion::new(4, 6, 0) { + eprintln!( + "skipping test on kernel {kernel_version:?}, TCP SO_ATTACH_REUSEPORT_EBPF requires 4.6" + ); + return; + } + + let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap(); + + let mut ebpf = Ebpf::load(crate::SOCKET_FILTER).unwrap(); + let prog: &mut ReusePortSocketFilter = ebpf + .program_mut("select_second") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + + // A bound listener without `SO_REUSEPORT` has no reuseport group; the + // `SO_ATTACH_REUSEPORT_EBPF` path rejects that in `reuseport_attach_prog()` + // with `-EINVAL`: + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock.c#L1396-L1405 + // https://github.com/torvalds/linux/blob/v6.9/net/core/sock_reuseport.c#L698-L700 + let err = prog.attach(&listener).unwrap_err(); + assert_matches!( + err, + ProgramError::SocketFilterError(SocketFilterError::SetsockoptError { + option: "SO_ATTACH_REUSEPORT_EBPF", + io_error, + }) if io_error.raw_os_error() == Some(EINVAL) + ); +} + #[test_log::test(tokio::test)] async fn socket_filter_replacement_stays_attached_until_explicit_detach() { if !is_program_supported(ProgramType::SocketFilter).unwrap() { diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 8300c9ba5..6a00de0e0 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -4499,7 +4499,46 @@ impl core::marker::Unpin for aya::programs::socket_filter::SocketFilterError impl core::marker::UnsafeUnpin for aya::programs::socket_filter::SocketFilterError impl !core::panic::unwind_safe::RefUnwindSafe for aya::programs::socket_filter::SocketFilterError impl !core::panic::unwind_safe::UnwindSafe for aya::programs::socket_filter::SocketFilterError -pub struct aya::programs::socket_filter::SocketFilter +#[repr(transparent)] pub struct aya::programs::socket_filter::ReusePortSocketFilter +impl aya::programs::socket_filter::ReusePortSocketFilter +pub const aya::programs::socket_filter::ReusePortSocketFilter::PROGRAM_TYPE: aya::programs::ProgramType +pub fn aya::programs::socket_filter::ReusePortSocketFilter::attach(&self, T) -> core::result::Result<(), aya::programs::ProgramError> +pub fn aya::programs::socket_filter::ReusePortSocketFilter::detach(T) -> core::result::Result<(), aya::programs::ProgramError> +pub fn aya::programs::socket_filter::ReusePortSocketFilter::from_pin>(P) -> core::result::Result +pub fn aya::programs::socket_filter::ReusePortSocketFilter::load(&mut self) -> core::result::Result<(), aya::programs::ProgramError> +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError> +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::from_program_info(aya::programs::ProgramInfo, alloc::borrow::Cow<'static, str>) -> core::result::Result +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::info(&self) -> core::result::Result +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::pin>(&mut self, P) -> core::result::Result<(), aya::pin::PinError> +pub fn aya::programs::socket_filter::ReusePortSocketFilter::unpin(&mut self) -> core::result::Result<(), std::io::error::Error> +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::unload(&mut self) -> core::result::Result<(), aya::programs::ProgramError> +impl aya::programs::TestRun for aya::programs::socket_filter::ReusePortSocketFilter +pub type aya::programs::socket_filter::ReusePortSocketFilter::Opts<'a> = aya::programs::TestRunOptions<'a> +pub type aya::programs::socket_filter::ReusePortSocketFilter::Result = aya::programs::TestRunResult +pub fn aya::programs::socket_filter::ReusePortSocketFilter::test_run(&self, Self::Opts) -> core::result::Result +impl core::fmt::Debug for aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::ops::drop::Drop for aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::drop(&mut self) +impl<'a> core::convert::TryFrom<&'a aya::programs::Program> for &'a aya::programs::socket_filter::ReusePortSocketFilter +pub type &'a aya::programs::socket_filter::ReusePortSocketFilter::Error = aya::programs::ProgramError +pub fn &'a aya::programs::socket_filter::ReusePortSocketFilter::try_from(&'a aya::programs::Program) -> core::result::Result<&'a aya::programs::socket_filter::ReusePortSocketFilter, aya::programs::ProgramError> +impl<'a> core::convert::TryFrom<&'a mut aya::programs::Program> for &'a mut aya::programs::socket_filter::ReusePortSocketFilter +pub type &'a mut aya::programs::socket_filter::ReusePortSocketFilter::Error = aya::programs::ProgramError +pub fn &'a mut aya::programs::socket_filter::ReusePortSocketFilter::try_from(&'a mut aya::programs::Program) -> core::result::Result<&'a mut aya::programs::socket_filter::ReusePortSocketFilter, aya::programs::ProgramError> +impl core::marker::Freeze for aya::programs::socket_filter::ReusePortSocketFilter +impl core::marker::Send for aya::programs::socket_filter::ReusePortSocketFilter +impl core::marker::Sync for aya::programs::socket_filter::ReusePortSocketFilter +impl core::marker::Unpin for aya::programs::socket_filter::ReusePortSocketFilter +impl core::marker::UnsafeUnpin for aya::programs::socket_filter::ReusePortSocketFilter +impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::socket_filter::ReusePortSocketFilter +impl core::panic::unwind_safe::UnwindSafe for aya::programs::socket_filter::ReusePortSocketFilter +#[repr(transparent)] pub struct aya::programs::socket_filter::SocketFilter impl aya::programs::socket_filter::SocketFilter pub const aya::programs::socket_filter::SocketFilter::PROGRAM_TYPE: aya::programs::ProgramType pub fn aya::programs::socket_filter::SocketFilter::attach(&self, T) -> core::result::Result<(), aya::programs::ProgramError> @@ -4509,6 +4548,8 @@ pub fn aya::programs::socket_filter::SocketFilter::load(&mut self) -> core::resu impl aya::programs::socket_filter::SocketFilter pub fn aya::programs::socket_filter::SocketFilter::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError> impl aya::programs::socket_filter::SocketFilter +pub fn aya::programs::socket_filter::SocketFilter::from_program_info(aya::programs::ProgramInfo, alloc::borrow::Cow<'static, str>) -> core::result::Result +impl aya::programs::socket_filter::SocketFilter pub fn aya::programs::socket_filter::SocketFilter::info(&self) -> core::result::Result impl aya::programs::socket_filter::SocketFilter pub fn aya::programs::socket_filter::SocketFilter::pin>(&mut self, P) -> core::result::Result<(), aya::pin::PinError> @@ -5383,6 +5424,7 @@ pub aya::programs::Program::Lsm(aya::programs::lsm::Lsm) pub aya::programs::Program::LsmCgroup(aya::programs::lsm_cgroup::LsmCgroup) pub aya::programs::Program::PerfEvent(aya::programs::perf_event::PerfEvent) pub aya::programs::Program::RawTracePoint(aya::programs::raw_trace_point::RawTracePoint) +pub aya::programs::Program::ReusePortSocketFilter(aya::programs::socket_filter::ReusePortSocketFilter) pub aya::programs::Program::SchedClassifier(aya::programs::tc::SchedClassifier) pub aya::programs::Program::SkLookup(aya::programs::sk_lookup::SkLookup) pub aya::programs::Program::SkMsg(aya::programs::sk_msg::SkMsg) @@ -5467,6 +5509,9 @@ pub fn &'a aya::programs::sk_skb::SkSkb::try_from(&'a aya::programs::Program) -> impl<'a> core::convert::TryFrom<&'a aya::programs::Program> for &'a aya::programs::sock_ops::SockOps pub type &'a aya::programs::sock_ops::SockOps::Error = aya::programs::ProgramError pub fn &'a aya::programs::sock_ops::SockOps::try_from(&'a aya::programs::Program) -> core::result::Result<&'a aya::programs::sock_ops::SockOps, aya::programs::ProgramError> +impl<'a> core::convert::TryFrom<&'a aya::programs::Program> for &'a aya::programs::socket_filter::ReusePortSocketFilter +pub type &'a aya::programs::socket_filter::ReusePortSocketFilter::Error = aya::programs::ProgramError +pub fn &'a aya::programs::socket_filter::ReusePortSocketFilter::try_from(&'a aya::programs::Program) -> core::result::Result<&'a aya::programs::socket_filter::ReusePortSocketFilter, aya::programs::ProgramError> impl<'a> core::convert::TryFrom<&'a aya::programs::Program> for &'a aya::programs::socket_filter::SocketFilter pub type &'a aya::programs::socket_filter::SocketFilter::Error = aya::programs::ProgramError pub fn &'a aya::programs::socket_filter::SocketFilter::try_from(&'a aya::programs::Program) -> core::result::Result<&'a aya::programs::socket_filter::SocketFilter, aya::programs::ProgramError> @@ -5551,6 +5596,9 @@ pub fn &'a mut aya::programs::sk_skb::SkSkb::try_from(&'a mut aya::programs::Pro impl<'a> core::convert::TryFrom<&'a mut aya::programs::Program> for &'a mut aya::programs::sock_ops::SockOps pub type &'a mut aya::programs::sock_ops::SockOps::Error = aya::programs::ProgramError pub fn &'a mut aya::programs::sock_ops::SockOps::try_from(&'a mut aya::programs::Program) -> core::result::Result<&'a mut aya::programs::sock_ops::SockOps, aya::programs::ProgramError> +impl<'a> core::convert::TryFrom<&'a mut aya::programs::Program> for &'a mut aya::programs::socket_filter::ReusePortSocketFilter +pub type &'a mut aya::programs::socket_filter::ReusePortSocketFilter::Error = aya::programs::ProgramError +pub fn &'a mut aya::programs::socket_filter::ReusePortSocketFilter::try_from(&'a mut aya::programs::Program) -> core::result::Result<&'a mut aya::programs::socket_filter::ReusePortSocketFilter, aya::programs::ProgramError> impl<'a> core::convert::TryFrom<&'a mut aya::programs::Program> for &'a mut aya::programs::socket_filter::SocketFilter pub type &'a mut aya::programs::socket_filter::SocketFilter::Error = aya::programs::ProgramError pub fn &'a mut aya::programs::socket_filter::SocketFilter::try_from(&'a mut aya::programs::Program) -> core::result::Result<&'a mut aya::programs::socket_filter::SocketFilter, aya::programs::ProgramError> @@ -6659,6 +6707,45 @@ impl core::marker::Unpin for aya::programs::RawTracePointTestRunResult impl core::marker::UnsafeUnpin for aya::programs::RawTracePointTestRunResult impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::RawTracePointTestRunResult impl core::panic::unwind_safe::UnwindSafe for aya::programs::RawTracePointTestRunResult +#[repr(transparent)] pub struct aya::programs::ReusePortSocketFilter +impl aya::programs::socket_filter::ReusePortSocketFilter +pub const aya::programs::socket_filter::ReusePortSocketFilter::PROGRAM_TYPE: aya::programs::ProgramType +pub fn aya::programs::socket_filter::ReusePortSocketFilter::attach(&self, T) -> core::result::Result<(), aya::programs::ProgramError> +pub fn aya::programs::socket_filter::ReusePortSocketFilter::detach(T) -> core::result::Result<(), aya::programs::ProgramError> +pub fn aya::programs::socket_filter::ReusePortSocketFilter::from_pin>(P) -> core::result::Result +pub fn aya::programs::socket_filter::ReusePortSocketFilter::load(&mut self) -> core::result::Result<(), aya::programs::ProgramError> +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError> +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::from_program_info(aya::programs::ProgramInfo, alloc::borrow::Cow<'static, str>) -> core::result::Result +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::info(&self) -> core::result::Result +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::pin>(&mut self, P) -> core::result::Result<(), aya::pin::PinError> +pub fn aya::programs::socket_filter::ReusePortSocketFilter::unpin(&mut self) -> core::result::Result<(), std::io::error::Error> +impl aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::unload(&mut self) -> core::result::Result<(), aya::programs::ProgramError> +impl aya::programs::TestRun for aya::programs::socket_filter::ReusePortSocketFilter +pub type aya::programs::socket_filter::ReusePortSocketFilter::Opts<'a> = aya::programs::TestRunOptions<'a> +pub type aya::programs::socket_filter::ReusePortSocketFilter::Result = aya::programs::TestRunResult +pub fn aya::programs::socket_filter::ReusePortSocketFilter::test_run(&self, Self::Opts) -> core::result::Result +impl core::fmt::Debug for aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::ops::drop::Drop for aya::programs::socket_filter::ReusePortSocketFilter +pub fn aya::programs::socket_filter::ReusePortSocketFilter::drop(&mut self) +impl<'a> core::convert::TryFrom<&'a aya::programs::Program> for &'a aya::programs::socket_filter::ReusePortSocketFilter +pub type &'a aya::programs::socket_filter::ReusePortSocketFilter::Error = aya::programs::ProgramError +pub fn &'a aya::programs::socket_filter::ReusePortSocketFilter::try_from(&'a aya::programs::Program) -> core::result::Result<&'a aya::programs::socket_filter::ReusePortSocketFilter, aya::programs::ProgramError> +impl<'a> core::convert::TryFrom<&'a mut aya::programs::Program> for &'a mut aya::programs::socket_filter::ReusePortSocketFilter +pub type &'a mut aya::programs::socket_filter::ReusePortSocketFilter::Error = aya::programs::ProgramError +pub fn &'a mut aya::programs::socket_filter::ReusePortSocketFilter::try_from(&'a mut aya::programs::Program) -> core::result::Result<&'a mut aya::programs::socket_filter::ReusePortSocketFilter, aya::programs::ProgramError> +impl core::marker::Freeze for aya::programs::socket_filter::ReusePortSocketFilter +impl core::marker::Send for aya::programs::socket_filter::ReusePortSocketFilter +impl core::marker::Sync for aya::programs::socket_filter::ReusePortSocketFilter +impl core::marker::Unpin for aya::programs::socket_filter::ReusePortSocketFilter +impl core::marker::UnsafeUnpin for aya::programs::socket_filter::ReusePortSocketFilter +impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::socket_filter::ReusePortSocketFilter +impl core::panic::unwind_safe::UnwindSafe for aya::programs::socket_filter::ReusePortSocketFilter pub struct aya::programs::SchedClassifier impl aya::programs::tc::SchedClassifier pub const aya::programs::tc::SchedClassifier::PROGRAM_TYPE: aya::programs::ProgramType @@ -6891,7 +6978,7 @@ impl core::marker::Unpin for aya::programs::sock_ops::SockOps impl core::marker::UnsafeUnpin for aya::programs::sock_ops::SockOps impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::sock_ops::SockOps impl core::panic::unwind_safe::UnwindSafe for aya::programs::sock_ops::SockOps -pub struct aya::programs::SocketFilter +#[repr(transparent)] pub struct aya::programs::SocketFilter impl aya::programs::socket_filter::SocketFilter pub const aya::programs::socket_filter::SocketFilter::PROGRAM_TYPE: aya::programs::ProgramType pub fn aya::programs::socket_filter::SocketFilter::attach(&self, T) -> core::result::Result<(), aya::programs::ProgramError> @@ -6901,6 +6988,8 @@ pub fn aya::programs::socket_filter::SocketFilter::load(&mut self) -> core::resu impl aya::programs::socket_filter::SocketFilter pub fn aya::programs::socket_filter::SocketFilter::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError> impl aya::programs::socket_filter::SocketFilter +pub fn aya::programs::socket_filter::SocketFilter::from_program_info(aya::programs::ProgramInfo, alloc::borrow::Cow<'static, str>) -> core::result::Result +impl aya::programs::socket_filter::SocketFilter pub fn aya::programs::socket_filter::SocketFilter::info(&self) -> core::result::Result impl aya::programs::socket_filter::SocketFilter pub fn aya::programs::socket_filter::SocketFilter::pin>(&mut self, P) -> core::result::Result<(), aya::pin::PinError> @@ -7268,6 +7357,10 @@ impl aya::programs::TestRun for aya::programs::raw_trace_point::RawTracePoint pub type aya::programs::raw_trace_point::RawTracePoint::Opts<'a> = aya::programs::RawTracePointRunOptions pub type aya::programs::raw_trace_point::RawTracePoint::Result = aya::programs::RawTracePointTestRunResult pub fn aya::programs::raw_trace_point::RawTracePoint::test_run(&self, Self::Opts) -> core::result::Result +impl aya::programs::TestRun for aya::programs::socket_filter::ReusePortSocketFilter +pub type aya::programs::socket_filter::ReusePortSocketFilter::Opts<'a> = aya::programs::TestRunOptions<'a> +pub type aya::programs::socket_filter::ReusePortSocketFilter::Result = aya::programs::TestRunResult +pub fn aya::programs::socket_filter::ReusePortSocketFilter::test_run(&self, Self::Opts) -> core::result::Result impl aya::programs::TestRun for aya::programs::socket_filter::SocketFilter pub type aya::programs::socket_filter::SocketFilter::Opts<'a> = aya::programs::TestRunOptions<'a> pub type aya::programs::socket_filter::SocketFilter::Result = aya::programs::TestRunResult @@ -7661,6 +7754,10 @@ impl aya::programs::TestRun for aya::programs::raw_trace_point::RawTracePoint pub type aya::programs::raw_trace_point::RawTracePoint::Opts<'a> = aya::programs::RawTracePointRunOptions pub type aya::programs::raw_trace_point::RawTracePoint::Result = aya::programs::RawTracePointTestRunResult pub fn aya::programs::raw_trace_point::RawTracePoint::test_run(&self, Self::Opts) -> core::result::Result +impl aya::programs::TestRun for aya::programs::socket_filter::ReusePortSocketFilter +pub type aya::programs::socket_filter::ReusePortSocketFilter::Opts<'a> = aya::programs::TestRunOptions<'a> +pub type aya::programs::socket_filter::ReusePortSocketFilter::Result = aya::programs::TestRunResult +pub fn aya::programs::socket_filter::ReusePortSocketFilter::test_run(&self, Self::Opts) -> core::result::Result impl aya::programs::TestRun for aya::programs::socket_filter::SocketFilter pub type aya::programs::socket_filter::SocketFilter::Opts<'a> = aya::programs::TestRunOptions<'a> pub type aya::programs::socket_filter::SocketFilter::Result = aya::programs::TestRunResult