Skip to content

Commit

Permalink
Add AF_XDP syscalls, constants and types
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-alpaca committed Dec 10, 2023
1 parent 5ff2b62 commit 3a57a06
Show file tree
Hide file tree
Showing 18 changed files with 1,116 additions and 13 deletions.
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,24 @@ once_cell = { version = "1.5.2", optional = true }
# libc backend can be selected via adding `--cfg=rustix_use_libc` to
# `RUSTFLAGS` or enabling the `use-libc` cargo feature.
[target.'cfg(all(not(rustix_use_libc), not(miri), target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64"))))'.dependencies]
linux-raw-sys = { version = "0.4.11", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"] }
linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"] }
libc_errno = { package = "errno", version = "0.3.8", default-features = false, optional = true }
libc = { version = "0.2.150", default-features = false, features = ["extra_traits"], optional = true }
libc = { version = "0.2.151", default-features = false, features = ["extra_traits"], optional = true }

# Dependencies for platforms where only libc is supported:
#
# On all other Unix-family platforms, and under Miri, we always use the libc
# backend, so enable its dependencies unconditionally.
[target.'cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64")))))))'.dependencies]
libc_errno = { package = "errno", version = "0.3.8", default-features = false }
libc = { version = "0.2.150", default-features = false, features = ["extra_traits"] }
libc = { version = "0.2.151", default-features = false, features = ["extra_traits"] }

# Additional dependencies for Linux with the libc backend:
#
# Some syscalls do not have libc wrappers, such as in `io_uring`. For these,
# the libc backend uses the linux-raw-sys ABI and `libc::syscall`.
[target.'cfg(all(any(target_os = "android", target_os = "linux"), any(rustix_use_libc, miri, not(all(target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64")))))))'.dependencies]
linux-raw-sys = { version = "0.4.11", default-features = false, features = ["general", "ioctl", "no_std"] }
linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "ioctl", "no_std"] }

# For the libc backend on Windows, use the Winsock API in windows-sys.
[target.'cfg(windows)'.dependencies.windows-sys]
Expand All @@ -74,7 +74,7 @@ default-features = false

[dev-dependencies]
tempfile = "3.5.0"
libc = "0.2.150"
libc = "0.2.151"
libc_errno = { package = "errno", version = "0.3.8", default-features = false }
serial_test = "2.0.0"
memoffset = "0.9.0"
Expand Down Expand Up @@ -141,7 +141,7 @@ io_uring = ["event", "fs", "net", "linux-raw-sys/io_uring"]
mount = []

# Enable `rustix::net::*`.
net = ["linux-raw-sys/net", "linux-raw-sys/netlink", "linux-raw-sys/if_ether"]
net = ["linux-raw-sys/net", "linux-raw-sys/netlink", "linux-raw-sys/if_ether", "linux-raw-sys/xdp"]

# Enable `rustix::thread::*`.
thread = ["linux-raw-sys/prctl"]
Expand Down
26 changes: 26 additions & 0 deletions src/backend/libc/net/msghdr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
use crate::backend::c;
use crate::backend::conv::{msg_control_len, msg_iov_len};
#[cfg(target_os = "linux")]
use crate::backend::net::write_sockaddr::encode_sockaddr_xdp;
use crate::backend::net::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6};

use crate::io::{self, IoSlice, IoSliceMut};
#[cfg(target_os = "linux")]
use crate::net::xdp::SocketAddrXdp;
use crate::net::{RecvAncillaryBuffer, SendAncillaryBuffer, SocketAddrV4, SocketAddrV6};
use crate::utils::as_ptr;

Expand Down Expand Up @@ -124,6 +128,28 @@ pub(crate) fn with_unix_msghdr<R>(
})
}

/// Create a message header intended to send with an IPv6 address.
#[cfg(target_os = "linux")]
pub(crate) fn with_xdp_msghdr<R>(
addr: &SocketAddrXdp,
iov: &[IoSlice<'_>],
control: &mut SendAncillaryBuffer<'_, '_, '_>,
f: impl FnOnce(c::msghdr) -> R,
) -> R {
let encoded = encode_sockaddr_xdp(addr);

f({
let mut h = zero_msghdr();
h.msg_name = as_ptr(&encoded) as _;
h.msg_namelen = size_of::<SocketAddrXdp>() as _;
h.msg_iov = iov.as_ptr() as _;
h.msg_iovlen = msg_iov_len(iov.len());
h.msg_control = control.as_control_ptr().cast();
h.msg_controllen = msg_control_len(control.control_len());
h
})
}

/// Create a zero-initialized message header struct value.
#[cfg(all(unix, not(target_os = "redox")))]
pub(crate) fn zero_msghdr() -> c::msghdr {
Expand Down
26 changes: 26 additions & 0 deletions src/backend/libc/net/read_sockaddr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use crate::backend::c;
#[cfg(not(windows))]
use crate::ffi::CStr;
use crate::io;
#[cfg(target_os = "linux")]
use crate::net::xdp::{SockaddrXdpFlags, SocketAddrXdp};
use crate::net::{Ipv4Addr, Ipv6Addr, SocketAddrAny, SocketAddrV4, SocketAddrV6};
use core::mem::size_of;

Expand Down Expand Up @@ -193,6 +195,19 @@ pub(crate) unsafe fn read_sockaddr(
.map(SocketAddrAny::Unix)
}
}
#[cfg(target_os = "linux")]
c::AF_XDP => {
if len < size_of::<c::sockaddr_xdp>() {
return Err(io::Errno::INVAL);
}
let decode = &*storage.cast::<c::sockaddr_xdp>();
Ok(SocketAddrAny::Xdp(SocketAddrXdp::new(
SockaddrXdpFlags::from_bits_retain(decode.sxdp_flags),
u32::from_be(decode.sxdp_ifindex),
u32::from_be(decode.sxdp_queue_id),
u32::from_be(decode.sxdp_shared_umem_fd),
)))
}
_ => Err(io::Errno::INVAL),
}
}
Expand Down Expand Up @@ -301,6 +316,17 @@ unsafe fn inner_read_sockaddr_os(
)
}
}
#[cfg(target_os = "linux")]
c::AF_XDP => {
assert!(len >= size_of::<c::sockaddr_xdp>());
let decode = &*storage.cast::<c::sockaddr_xdp>();
SocketAddrAny::Xdp(SocketAddrXdp::new(
SockaddrXdpFlags::from_bits_retain(decode.sxdp_flags),
u32::from_be(decode.sxdp_ifindex),
u32::from_be(decode.sxdp_queue_id),
u32::from_be(decode.sxdp_shared_umem_fd),
))
}
other => unimplemented!("{:?}", other),
}
}
168 changes: 168 additions & 0 deletions src/backend/libc/net/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::fd::BorrowedFd;
use crate::ffi::CStr;
use crate::io;
use crate::net::sockopt::Timeout;
#[cfg(target_os = "linux")]
use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpRingOffset, XdpStatistics, XdpUmemReg};
#[cfg(not(any(
apple,
windows,
Expand Down Expand Up @@ -73,6 +75,8 @@ use c::TCP_KEEPALIVE as TCP_KEEPIDLE;
use c::TCP_KEEPIDLE;
use core::mem::{size_of, MaybeUninit};
use core::time::Duration;
#[cfg(target_os = "linux")]
use linux_raw_sys::xdp::{xdp_mmap_offsets, xdp_statistics, xdp_statistics_v1};
#[cfg(windows)]
use windows_sys::Win32::Foundation::BOOL;

Expand Down Expand Up @@ -963,6 +967,170 @@ pub(crate) fn get_socket_peercred(fd: BorrowedFd<'_>) -> io::Result<UCred> {
getsockopt(fd, c::SOL_SOCKET, c::SO_PEERCRED)
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn set_xdp_umem_reg(fd: BorrowedFd<'_>, value: XdpUmemReg) -> io::Result<()> {
setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_REG, value)
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn set_xdp_umem_fill_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_FILL_RING, value)
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn set_xdp_umem_completion_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_COMPLETION_RING, value)
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn set_xdp_tx_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
setsockopt(fd, c::SOL_XDP, c::XDP_TX_RING, value)
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn set_xdp_rx_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
setsockopt(fd, c::SOL_XDP, c::XDP_RX_RING, value)
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn get_xdp_mmap_offsets(fd: BorrowedFd<'_>) -> io::Result<XdpMmapOffsets> {
// The kernel will write `xdp_mmap_offsets` or `xdp_mmap_offsets_v1` to the supplied pointer,
// depending on the kernel version. Both structs only contain u64 values.
// By using the larger of both as the parameter, we can shuffle the values to the non-v1 version
// returned by `get_xdp_mmap_offsets` while keeping the return type unaffected by the kernel
// version. This works because C will layout all struct members one after the other.

let mut optlen = core::mem::size_of::<xdp_mmap_offsets>().try_into().unwrap();
debug_assert!(
optlen as usize >= core::mem::size_of::<c::c_int>(),
"Socket APIs don't ever use `bool` directly"
);
let mut value = MaybeUninit::<xdp_mmap_offsets>::zeroed();
getsockopt_raw(fd, c::SOL_XDP, c::XDP_MMAP_OFFSETS, &mut value, &mut optlen)?;

if optlen as usize == core::mem::size_of::<c::xdp_mmap_offsets_v1>() {
// Safety: All members of xdp_mmap_offsets are u64 and thus are correctly initialized
// by `MaybeUninit::<xdp_statistics>::zeroed()`
let xpd_mmap_offsets = unsafe { value.assume_init() };
Ok(XdpMmapOffsets {
rx: XdpRingOffset {
producer: xpd_mmap_offsets.rx.producer,
consumer: xpd_mmap_offsets.rx.consumer,
desc: xpd_mmap_offsets.rx.desc,
flags: None,
},
tx: XdpRingOffset {
producer: xpd_mmap_offsets.rx.flags,
consumer: xpd_mmap_offsets.tx.producer,
desc: xpd_mmap_offsets.tx.consumer,
flags: None,
},
fr: XdpRingOffset {
producer: xpd_mmap_offsets.tx.desc,
consumer: xpd_mmap_offsets.tx.flags,
desc: xpd_mmap_offsets.fr.producer,
flags: None,
},
cr: XdpRingOffset {
producer: xpd_mmap_offsets.fr.consumer,
consumer: xpd_mmap_offsets.fr.desc,
desc: xpd_mmap_offsets.fr.flags,
flags: None,
},
})
} else {
assert_eq!(
optlen as usize,
core::mem::size_of::<xdp_mmap_offsets>(),
"unexpected getsockopt size"
);
// Safety: All members of xdp_mmap_offsets are u64 and thus are correctly initialized
// by `MaybeUninit::<xdp_statistics>::zeroed()`
let xpd_mmap_offsets = unsafe { value.assume_init() };
Ok(XdpMmapOffsets {
rx: XdpRingOffset {
producer: xpd_mmap_offsets.rx.producer,
consumer: xpd_mmap_offsets.rx.consumer,
desc: xpd_mmap_offsets.rx.desc,
flags: Some(xpd_mmap_offsets.rx.flags),
},
tx: XdpRingOffset {
producer: xpd_mmap_offsets.tx.producer,
consumer: xpd_mmap_offsets.tx.consumer,
desc: xpd_mmap_offsets.tx.desc,
flags: Some(xpd_mmap_offsets.tx.flags),
},
fr: XdpRingOffset {
producer: xpd_mmap_offsets.fr.producer,
consumer: xpd_mmap_offsets.fr.consumer,
desc: xpd_mmap_offsets.fr.desc,
flags: Some(xpd_mmap_offsets.fr.flags),
},
cr: XdpRingOffset {
producer: xpd_mmap_offsets.cr.producer,
consumer: xpd_mmap_offsets.cr.consumer,
desc: xpd_mmap_offsets.cr.desc,
flags: Some(xpd_mmap_offsets.cr.flags),
},
})
}
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn get_xdp_statistics(fd: BorrowedFd<'_>) -> io::Result<XdpStatistics> {
let mut optlen = core::mem::size_of::<xdp_statistics>().try_into().unwrap();
debug_assert!(
optlen as usize >= core::mem::size_of::<c::c_int>(),
"Socket APIs don't ever use `bool` directly"
);
let mut value = MaybeUninit::<xdp_statistics>::zeroed();
getsockopt_raw(fd, c::SOL_XDP, c::XDP_STATISTICS, &mut value, &mut optlen)?;

if optlen as usize == core::mem::size_of::<xdp_statistics_v1>() {
// Safety: All members of xdp_statistics are u64 and thus are correctly initialized
// by `MaybeUninit::<xdp_statistics>::zeroed()`
let xdp_statistics = unsafe { value.assume_init() };
Ok(XdpStatistics {
rx_dropped: xdp_statistics.rx_dropped,
rx_invalid_descs: xdp_statistics.rx_dropped,
tx_invalid_descs: xdp_statistics.rx_dropped,
rx_ring_full: None,
rx_fill_ring_empty_descs: None,
tx_ring_empty_descs: None,
})
} else {
assert_eq!(
optlen as usize,
core::mem::size_of::<xdp_statistics>(),
"unexpected getsockopt size"
);
// Safety: All members of xdp_statistics are u64 and thus are correctly initialized
// by `MaybeUninit::<xdp_statistics>::zeroed()`
let xdp_statistics = unsafe { value.assume_init() };
Ok(XdpStatistics {
rx_dropped: xdp_statistics.rx_dropped,
rx_invalid_descs: xdp_statistics.rx_invalid_descs,
tx_invalid_descs: xdp_statistics.tx_invalid_descs,
rx_ring_full: Some(xdp_statistics.rx_ring_full),
rx_fill_ring_empty_descs: Some(xdp_statistics.rx_fill_ring_empty_descs),
tx_ring_empty_descs: Some(xdp_statistics.tx_ring_empty_descs),
})
}
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn get_xdp_options(fd: BorrowedFd<'_>) -> io::Result<XdpOptionsFlags> {
getsockopt(fd, c::SOL_XDP, c::XDP_OPTIONS)
}

#[inline]
fn to_ip_mreq(multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> c::ip_mreq {
c::ip_mreq {
Expand Down
Loading

0 comments on commit 3a57a06

Please sign in to comment.