Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(s2n-quic-xdp): add syscall for NIC queues #1755

Merged
merged 2 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions tools/xdp/s2n-quic-xdp/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* automatically generated by rust-bindgen 0.65.1 */

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#![allow(non_camel_case_types)]

pub const ETHTOOL_GCHANNELS: u32 = 60;
pub type __u32 = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct ethtool_channels {
pub cmd: __u32,
pub max_rx: __u32,
pub max_tx: __u32,
pub max_other: __u32,
pub max_combined: __u32,
pub rx_count: __u32,
pub tx_count: __u32,
pub other_count: __u32,
pub combined_count: __u32,
}
24 changes: 10 additions & 14 deletions tools/xdp/s2n-quic-xdp/src/if_xdp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use crate::Result;
use bitflags::bitflags;
use core::mem::size_of;
use std::ffi::CStr;
use std::{ffi::CStr, os::unix::io::AsRawFd};

bitflags!(
/// Options for the `flags` field in [`Address`]
Expand Down Expand Up @@ -79,21 +79,17 @@ impl Address {
/// If the device does not exist, then an error is returned.
#[inline]
pub fn set_if_name(&mut self, name: &CStr) -> Result<&mut Self> {
unsafe {
let ifindex = libc::if_nametoindex(name.as_ptr());

// https://man7.org/linux/man-pages/man3/if_nametoindex.3.html
// > On success, if_nametoindex() returns the index number of the
// > network interface; on error, 0 is returned and errno is set to
// > indicate the error.
if ifindex == 0 {
return Err(std::io::Error::last_os_error());
}

self.ifindex = ifindex;
}
self.ifindex = crate::syscall::if_nametoindex(name)?;
Ok(self)
}

/// Configures the address with a shared UMEM region from a previous socket file descriptor
pub fn set_shared_umem<F: AsRawFd>(&mut self, fd: &F) -> &mut Self {
// clear out all of the previous flags except the shared - the others will be inherited
self.flags = XdpFlags::SHARED_UMEM;
self.shared_umem_fd = fd.as_raw_fd() as _;
self
}
}

bitflags!(
Expand Down
3 changes: 3 additions & 0 deletions tools/xdp/s2n-quic-xdp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ macro_rules! trace {
}}
}

/// Low-level bindings to various linux userspace APIs
mod bindings;

/// Default BPF programs to direct QUIC traffic
pub mod bpf;
/// Primitive types for AF-XDP kernel APIs
Expand Down
16 changes: 14 additions & 2 deletions tools/xdp/s2n-quic-xdp/src/ring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ macro_rules! impl_producer {
pub fn capacity(&self) -> usize {
self.0.cursor.capacity() as _
}

/// Returns the socket associated with the ring
#[inline]
pub fn socket(&self) -> &socket::Fd {
&self.0.socket
}
};
}

Expand Down Expand Up @@ -179,6 +185,12 @@ macro_rules! impl_consumer {
self.0.cursor.capacity() as _
}

/// Returns the socket associated with the ring
#[inline]
pub fn socket(&self) -> &socket::Fd {
&self.0.socket
}

#[cfg(test)]
pub fn set_flags(&mut self, flags: crate::if_xdp::RingFlags) {
*self.0.cursor.flags_mut() = flags;
Expand Down Expand Up @@ -272,13 +284,13 @@ pub mod testing {
let cons = $consumer(Ring {
cursor: consumer_cursor,
area: area.clone(),
socket: Fd::invalid(),
socket: Fd::from_raw(-1),
});

let prod = $producer(Ring {
cursor: producer_cursor,
area,
socket: Fd::invalid(),
socket: Fd::from_raw(-1),
});

(cons, prod)
Expand Down
9 changes: 4 additions & 5 deletions tools/xdp/s2n-quic-xdp/src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ impl Fd {
Ok(())
}

/// Returns an invalid file descriptor
/// Creates a socket from a raw file descriptor
///
/// This should only be used in tests to avoid creating an actual socket.
#[cfg(test)]
pub fn invalid() -> Self {
Self(Arc::new(Inner(-1)))
/// This can be useful for automatically cleaning up a socket on drop
pub(crate) fn from_raw(value: RawFd) -> Self {
Self(Arc::new(Inner(value)))
}
}

Expand Down
129 changes: 121 additions & 8 deletions tools/xdp/s2n-quic-xdp/src/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ use crate::{
use core::{mem::size_of, ptr::NonNull};
use libc::{c_void, AF_XDP, SOCK_RAW, SOL_XDP};
use std::{
ffi::CStr,
io,
os::unix::io::{AsRawFd, RawFd},
path::Path,
};

/// Calls the given libc function and wraps the result in an `io::Result`.
Expand Down Expand Up @@ -159,11 +161,19 @@ pub fn bind<Fd: AsRawFd>(fd: &Fd, addr: &mut Address) -> Result<()> {
/// This should be called after checking if the TX ring needs a wake up.
#[inline]
pub fn wake_tx<Fd: AsRawFd>(fd: &Fd) -> Result<()> {
let msg = unsafe {
// Safety: msghdr is zeroable
core::mem::zeroed()
unsafe {
// after some testing, `sendto` is better than `sengmsg` here since it doesn't have to copy
// the msghdr from userspace, which would be zeroed and meaningless
libc!(sendto(
fd.as_raw_fd(),
core::ptr::null_mut(),
0,
libc::MSG_DONTWAIT,
core::ptr::null_mut(),
0,
))?;
};
unsafe { libc!(sendmsg(fd.as_raw_fd(), &msg, libc::MSG_DONTWAIT,)) }?;

Ok(())
}

Expand Down Expand Up @@ -231,6 +241,104 @@ pub unsafe fn munmap(addr: NonNull<c_void>, len: usize) -> Result<()> {
Ok(())
}

/// Converts an interface name to its index
///
/// Returns an error if the name could not be resolved.
pub fn if_nametoindex(name: &CStr) -> Result<u32> {
unsafe {
let ifindex = libc::if_nametoindex(name.as_ptr());

// https://man7.org/linux/man-pages/man3/if_nametoindex.3.html
// > On success, if_nametoindex() returns the index number of the
// > network interface; on error, 0 is returned and errno is set to
// > indicate the error.
if ifindex == 0 {
return Err(std::io::Error::last_os_error());
}

Ok(ifindex)
}
}

/// Returns the maximum number of queues that can be opened on a particular network device
pub fn max_queues(ifname: &str) -> u32 {
// See https://github.com/xdp-project/xdp-tools/blob/1c8662f4bb44445454c8f66df56ccd11274d4e30/lib/libxdp/xsk.c#L436

// First try the ethtool API
if let Some(queues) = ethtool_queues(ifname).filter(|v| *v > 0) {
return queues;
}

// Then try to query the number from sysfs
if let Some(queues) = sysfs_queues(ifname).filter(|v| *v > 0) {
return queues;
}

// If none of the previous methods worked, then just default to a single queue
1
}

/// Queries the number of queues for a given interface with ethtool APIs
fn ethtool_queues(ifname: &str) -> Option<u32> {
// See https://github.com/xdp-project/xdp-tools/blob/1c8662f4bb44445454c8f66df56ccd11274d4e30/lib/libxdp/xsk.c#L436

let fd = unsafe { libc!(socket(libc::AF_LOCAL, libc::SOCK_DGRAM, 0)).ok()? };
// close the FD on drop
let fd = crate::socket::Fd::from_raw(fd);

let mut channels = unsafe { core::mem::zeroed::<crate::bindings::ethtool_channels>() };
channels.cmd = crate::bindings::ETHTOOL_GCHANNELS;

let mut ifreq = unsafe { core::mem::zeroed::<libc::ifreq>() };
ifreq.ifr_ifru.ifru_data = (&mut channels) as *mut _ as *mut _;

assert!(ifname.len() < ifreq.ifr_name.len());
unsafe {
core::ptr::copy_nonoverlapping(
ifname.as_bytes().as_ptr(),
&mut ifreq.ifr_name as *mut _ as *mut u8,
ifname.len(),
);
}

unsafe {
libc!(ioctl(fd.as_raw_fd(), libc::SIOCETHTOOL, &mut ifreq)).ok()?;
}

// Take the max of the max values, each driver returns in a different way
let queues = channels
.max_rx
.max(channels.max_tx)
.max(channels.max_combined);

Some(queues)
}

/// Queries the number of queues for a given interface with sysfs
fn sysfs_queues(ifname: &str) -> Option<u32> {
// See https://github.com/xdp-project/xdp-tools/blob/1c8662f4bb44445454c8f66df56ccd11274d4e30/lib/libxdp/xsk.c#L408

let mut rx = 0;
let mut tx = 0;

let path = Path::new("/sys/class/net").join(ifname).join("queues");

for entry in path.read_dir().ok()?.flatten() {
let path = entry.path();

if let Some(path) = path.file_name().map(|p| p.to_string_lossy()) {
if path.starts_with("rx") {
rx += 1;
} else if path.starts_with("tx") {
tx += 1;
}
}
}

let queues = rx.max(tx).max(1);
Some(queues)
}

#[inline]
fn xdp_option<Fd: AsRawFd, T: Sized>(fd: &Fd, opt: SocketOptions, value: &mut T) -> Result<usize> {
let mut optlen = size_of::<T>() as libc::socklen_t;
Expand Down Expand Up @@ -271,6 +379,13 @@ mod tests {
use crate::mmap::Mmap;
use core::ffi::CStr;

#[test]
fn max_queues_test() {
// we can't make any assumptions about the test environment but we can at least make sure
// it returns >=1
assert!(max_queues("lo") >= 1);
}

#[test]
fn syscall_test() {
// This call requires `CAP_NET_RAW`. If the test doesn't have this set, then log and skip
Expand All @@ -290,6 +405,8 @@ mod tests {
);
return;
};
// close the FD on drop
let _owned_fd = crate::socket::Fd::from_raw(fd);

// the ring sizes need to be a power of 2
let ring_size = 32u32;
Expand Down Expand Up @@ -339,9 +456,5 @@ mod tests {
}
}
}

unsafe {
let _ = libc!(close(fd));
}
}
}
1 change: 1 addition & 0 deletions tools/xdp/xtask/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[dependencies]
anyhow = "1"
bindgen = "0.65"
clap = { version = "4.1", features = ["derive"] }
elf = "0.7"
env_logger = "0.10"
Expand Down
30 changes: 30 additions & 0 deletions tools/xdp/xtask/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::path::Path;

pub fn run() -> Result<(), anyhow::Error> {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));

let bindings = bindgen::Builder::default()
.header(root.join("src/bindings/input.h").display().to_string())
.allowlist_var("ETHTOOL_GCHANNELS")
.allowlist_type("ethtool_channels")
.rust_target(bindgen::RustTarget::Stable_1_47)
.layout_tests(false)
.raw_line(
r#"
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#![allow(non_camel_case_types)]
"#
.trim(),
)
.generate()?;

let out = root.join("../s2n-quic-xdp/src/bindings.rs");
bindings.write_to_file(out)?;

Ok(())
}
1 change: 1 addition & 0 deletions tools/xdp/xtask/src/bindings/input.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include <linux/ethtool.h>
3 changes: 3 additions & 0 deletions tools/xdp/xtask/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

mod bindings;
mod build_ebpf;
mod ci;
mod disasm;
Expand All @@ -19,6 +20,7 @@ pub struct Options {
#[derive(Debug, Parser)]
enum Command {
BuildEbpf,
Bindings,
Ci,
Disasm,
Run(run::Options),
Expand All @@ -30,6 +32,7 @@ fn main() {
use Command::*;
let ret = match opts.command {
BuildEbpf => build_ebpf::run(),
Bindings => bindings::run(),
Ci => ci::run(),
Disasm => disasm::run(),
Run(opts) => run::run(opts),
Expand Down