From d1ddfc8c691536fc7500e2c8600021642b4413cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20Dupr=C3=A9=20Bertoni?= Date: Wed, 7 Feb 2024 20:20:03 +0200 Subject: [PATCH] Add support for Packet MMAP --- Cargo.toml | 6 +- examples/packet/inner.rs | 366 +++++++++++++ examples/packet/main.rs | 32 ++ src/backend/linux_raw/c.rs | 21 +- src/backend/linux_raw/net/read_sockaddr.rs | 8 + src/backend/linux_raw/net/sockopt.rs | 60 ++- src/backend/linux_raw/net/syscalls.rs | 81 +++ src/backend/linux_raw/net/write_sockaddr.rs | 17 + src/net/mod.rs | 2 + src/net/packet.rs | 542 ++++++++++++++++++++ src/net/send_recv/mod.rs | 23 + src/net/send_recv/msg.rs | 4 + src/net/socket.rs | 19 + src/net/socket_addr_any.rs | 17 + src/net/sockopt.rs | 76 +++ 15 files changed, 1260 insertions(+), 14 deletions(-) create mode 100644 examples/packet/inner.rs create mode 100644 examples/packet/main.rs create mode 100644 src/net/packet.rs diff --git a/Cargo.toml b/Cargo.toml index 61132b0c7..6f0465893 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ 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.12", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"] } +linux-raw-sys = { version = "0.6.4", 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.152", default-features = false, features = ["extra_traits"], optional = true } @@ -53,7 +53,7 @@ libc = { version = "0.2.152", default-features = false, features = ["extra_trait # 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.12", default-features = false, features = ["general", "ioctl", "no_std"] } +linux-raw-sys = { version = "0.6.4", 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] @@ -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", "linux-raw-sys/xdp"] +net = ["linux-raw-sys/net", "linux-raw-sys/netlink", "linux-raw-sys/if_ether", "linux-raw-sys/if_packet", "linux-raw-sys/xdp"] # Enable `rustix::thread::*`. thread = ["linux-raw-sys/prctl"] diff --git a/examples/packet/inner.rs b/examples/packet/inner.rs new file mode 100644 index 000000000..d88bc0e0e --- /dev/null +++ b/examples/packet/inner.rs @@ -0,0 +1,366 @@ +use rustix::event::{poll, PollFd, PollFlags}; +use rustix::fd::OwnedFd; +use rustix::mm::{mmap, munmap, MapFlags, ProtFlags}; +use rustix::net::{ + bind_link, eth, + netdevice::name_to_index, + packet::{PacketHeader2, PacketReq, PacketReqAny, PacketStatus, SocketAddrLink}, + send, socket_with, + sockopt::{set_packet_rx_ring, set_packet_tx_ring, set_packet_version, PacketVersion}, + AddressFamily, SendFlags, SocketFlags, SocketType, +}; +use std::{cell::Cell, collections::VecDeque, env, ffi::c_void, io, ptr, slice, str}; + +#[derive(Debug)] +pub struct Socket { + fd: OwnedFd, + block_size: usize, + block_count: usize, + frame_size: usize, + frame_count: usize, + rx: Cell<*mut c_void>, + tx: Cell<*mut c_void>, +} + +impl Socket { + fn new( + name: &str, + block_size: usize, + block_count: usize, + frame_size: usize, + ) -> io::Result { + let family = AddressFamily::PACKET; + let type_ = SocketType::RAW; + let flags = SocketFlags::empty(); + let fd = socket_with(family, type_, flags, None)?; + + let index = name_to_index(&fd, name)?; + + set_packet_version(&fd, PacketVersion::V2)?; + + let frame_count = (block_size * block_count) / frame_size; + let req = PacketReq { + block_size: block_size as u32, + block_nr: block_count as u32, + frame_size: frame_size as u32, + frame_nr: frame_count as u32, + }; + + let req = PacketReqAny::V2(req); + set_packet_rx_ring(&fd, &req)?; + set_packet_tx_ring(&fd, &req)?; + + let addr = SocketAddrLink::new(eth::ALL, index); + bind_link(&fd, &addr)?; + + let rx = unsafe { + mmap( + ptr::null_mut(), + block_size * block_count * 2, + ProtFlags::READ | ProtFlags::WRITE, + MapFlags::SHARED, + &fd, + 0, + ) + }?; + let tx = unsafe { rx.add(block_size * block_count) }; + + Ok(Self { + fd, + block_size, + block_count, + frame_size, + frame_count, + rx: Cell::new(rx), + tx: Cell::new(tx), + }) + } + + /// Returns a reader object for receiving packets. + pub fn reader(&self) -> Reader<'_> { + assert!(!self.rx.get().is_null()); + Reader { + socket: self, + // Take ring pointer. + ring: self.rx.replace(ptr::null_mut()), + } + } + + /// Returns a writer object for transmitting packets. + pub fn writer(&self) -> Writer<'_> { + assert!(!self.tx.get().is_null()); + Writer { + socket: self, + // Take ring pointer. + ring: self.tx.replace(ptr::null_mut()), + } + } + + /// Flushes the transmit buffer. + pub fn flush(&self) -> io::Result<()> { + send(&self.fd, &[], SendFlags::empty())?; + Ok(()) + } +} + +impl Drop for Socket { + fn drop(&mut self) { + debug_assert!(!self.rx.get().is_null()); + debug_assert!(!self.tx.get().is_null()); + unsafe { + let _ = munmap(self.rx.get(), self.block_size * self.block_count * 2); + } + } +} + +/// TODO +#[derive(Debug)] +pub struct Packet<'r> { + header: &'r mut PacketHeader2, +} + +impl<'r> Packet<'r> { + pub fn payload(&self) -> &[u8] { + let ptr = self.header.payload_rx(); + let len = self.header.len as usize; + unsafe { slice::from_raw_parts(ptr, len) } + } +} + +impl<'r> Drop for Packet<'r> { + fn drop(&mut self) { + self.header.status = PacketStatus::empty(); + } +} + +/// TODO +#[derive(Debug)] +pub struct Slot<'w> { + header: &'w mut PacketHeader2, +} + +impl<'w> Slot<'w> { + pub fn write(&mut self, payload: &[u8]) { + let ptr = self.header.payload_tx(); + // TODO verify length + let len = payload.len(); + unsafe { + ptr.copy_from_nonoverlapping(payload.as_ptr(), len); + self.header.len = len as u32; + } + } +} + +impl<'w> Drop for Slot<'w> { + fn drop(&mut self) { + self.header.status = PacketStatus::SEND_REQUEST; + } +} + +/// A reader object for receiving packets. +#[derive(Debug)] +pub struct Reader<'s> { + socket: &'s Socket, + ring: *mut c_void, // Owned +} + +impl<'s> Reader<'s> { + /// Returns an iterator over received packets. + /// The iterator blocks until at least one packet is received. + /// + /// # Lifetimes + /// + /// - `'s`: The lifetime of the socket. + /// - `'r`: The lifetime of the received packets. + pub fn wait<'r>(&'r mut self) -> io::Result> + where + 's: 'r, + { + let flags = PollFlags::IN | PollFlags::RDNORM | PollFlags::ERR; + let pfd = PollFd::new(&self.socket.fd, flags); + let pfd = &mut [pfd]; + let n = poll(pfd, -1)?; + assert_eq!(n, 1); + Ok(ReadIter { + reader: self, + index: 0, + }) + } +} + +impl<'s> Drop for Reader<'s> { + fn drop(&mut self) { + // Give back ring pointer. + self.socket.rx.set(self.ring); + } +} + +/// A writer object for transmitting packets. +#[derive(Debug)] +pub struct Writer<'s> { + socket: &'s Socket, + ring: *mut c_void, // Owned +} + +impl<'s> Writer<'s> { + /// Returns an iterator over available slots for transmitting packets. + /// The iterator blocks until at least one slot is available. + /// + /// # Lifetimes + /// + /// - `'s`: The lifetime of the socket. + /// - `'w`: The lifetime of the slots. + pub fn wait<'w>(&'w mut self) -> io::Result> + where + 's: 'w, + { + let flags = PollFlags::OUT | PollFlags::WRNORM | PollFlags::ERR; + let pfd = PollFd::new(&self.socket.fd, flags); + let pfd = &mut [pfd]; + let n = poll(pfd, -1)?; + assert_eq!(n, 1); + Ok(WriteIter { + writer: self, + index: 0, + }) + } +} + +impl<'s> Drop for Writer<'s> { + fn drop(&mut self) { + // Give back ring pointer. + self.socket.tx.set(self.ring); + } +} + +/// An iterator over received packets. +#[derive(Debug)] +pub struct ReadIter<'s, 'r> { + reader: &'r mut Reader<'s>, + index: usize, +} + +impl<'s, 'r> Iterator for ReadIter<'s, 'r> { + type Item = Packet<'r>; + + fn next(&mut self) -> Option { + while self.index < self.reader.socket.frame_count { + let base = unsafe { + self.reader + .ring + .add(self.index * self.reader.socket.frame_size) + }; + self.index += 1; + + if let Some(header) = unsafe { PacketHeader2::from_rx_ptr(base) } { + return Some(Packet { header }); + } + } + None + } +} + +/// An iterator over available slots for transmitting packets. +#[derive(Debug)] +pub struct WriteIter<'s, 'w> { + writer: &'w mut Writer<'s>, + index: usize, +} + +impl<'s, 'w> Iterator for WriteIter<'s, 'w> { + type Item = Slot<'w>; + + fn next(&mut self) -> Option { + while self.index < self.writer.socket.frame_count { + let base = unsafe { + self.writer + .ring + .add(self.index * self.writer.socket.frame_size) + }; + self.index += 1; + + if let Some(header) = unsafe { PacketHeader2::from_tx_ptr(base) } { + return Some(Slot { header }); + } + } + None + } +} + +// ECHO server +fn server(socket: Socket, mut count: usize) -> io::Result<()> { + let mut reader = socket.reader(); + let mut writer = socket.writer(); + + while count > 0 { + let mut queue = VecDeque::new(); + + for packet in reader.wait()? { + queue.push_back(packet); + } + + while let Some(packet) = queue.pop_front() { + let mut iter = writer.wait()?.take(count); + while let Some(mut slot) = iter.next() { + let mut payload = packet.payload().to_vec(); + assert_eq!(payload[12..14], [0x08, 0x00]); + payload.swap(14, 15); + + slot.write(&payload); + drop(slot); + count -= 1; + } + drop(packet); + } + + socket.flush()?; + } + + Ok(()) +} + +// ECHO client +fn client(socket: Socket, mut count: usize) -> io::Result<()> { + let mut reader = socket.reader(); + let mut writer = socket.writer(); + + while count > 0 { + let mut iter = writer.wait()?.take(count); + while let Some(mut slot) = iter.next() { + let payload = &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Destination + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Source + 0x08, 0x00, // Type (IPv4, but not really) + 0x13, 0x37, // Payload (some value) + ]; + + slot.write(payload); + drop(slot); + count -= 1; + } + + socket.flush()?; + + for packet in reader.wait()? { + assert_eq!(packet.payload()[14..16], [0x37, 0x13]); + } + } + + Ok(()) +} + +pub fn main() -> io::Result<()> { + let mut args = env::args().skip(1); + let name = args.next().expect("name"); + let mode = args.next().expect("mode"); + let count = args.next().expect("count"); + + let socket = Socket::new(&name, 4096, 4, 2048)?; + let count = count.parse().unwrap(); + + match mode.as_str() { + "server" => server(socket, count), + "client" => client(socket, count), + _ => panic!("invalid mode"), + } +} diff --git a/examples/packet/main.rs b/examples/packet/main.rs new file mode 100644 index 000000000..bc6b269de --- /dev/null +++ b/examples/packet/main.rs @@ -0,0 +1,32 @@ +//! Packet MMAP. + +#[cfg(all( + feature = "mm", + feature = "net", + feature = "event", + feature = "std", + target_os = "linux" +))] +mod inner; + +#[cfg(all( + feature = "mm", + feature = "net", + feature = "event", + feature = "std", + target_os = "linux" +))] +fn main() -> std::io::Result<()> { + inner::main() +} + +#[cfg(any( + not(feature = "mm"), + not(feature = "net"), + not(feature = "event"), + not(feature = "std"), + not(target_os = "linux") +))] +fn main() -> Result<(), &'static str> { + Err("This example requires --features=mm,net,event,std and is only supported on Linux.") +} diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs index b2cd5bdcb..9c9b1726a 100644 --- a/src/backend/linux_raw/c.rs +++ b/src/backend/linux_raw/c.rs @@ -55,13 +55,14 @@ pub(crate) use linux_raw_sys::{ cmsg_macros::*, general::{O_CLOEXEC as SOCK_CLOEXEC, O_NONBLOCK as SOCK_NONBLOCK}, if_ether::*, + if_packet::*, net::{ linger, msghdr, sockaddr, sockaddr_in, sockaddr_in6, sockaddr_un, socklen_t, AF_DECnet, __kernel_sa_family_t as sa_family_t, __kernel_sockaddr_storage as sockaddr_storage, - cmsghdr, in6_addr, in_addr, ip_mreq, ip_mreq_source, ip_mreqn, ipv6_mreq, AF_APPLETALK, - AF_ASH, AF_ATMPVC, AF_ATMSVC, AF_AX25, AF_BLUETOOTH, AF_BRIDGE, AF_CAN, AF_ECONET, - AF_IEEE802154, AF_INET, AF_INET6, AF_IPX, AF_IRDA, AF_ISDN, AF_IUCV, AF_KEY, AF_LLC, - AF_NETBEUI, AF_NETLINK, AF_NETROM, AF_PACKET, AF_PHONET, AF_PPPOX, AF_RDS, AF_ROSE, + cmsghdr, ifreq, in6_addr, in_addr, ip_mreq, ip_mreq_source, ip_mreqn, ipv6_mreq, + AF_APPLETALK, AF_ASH, AF_ATMPVC, AF_ATMSVC, AF_AX25, AF_BLUETOOTH, AF_BRIDGE, AF_CAN, + AF_ECONET, AF_IEEE802154, AF_INET, AF_INET6, AF_IPX, AF_IRDA, AF_ISDN, AF_IUCV, AF_KEY, + AF_LLC, AF_NETBEUI, AF_NETLINK, AF_NETROM, AF_PACKET, AF_PHONET, AF_PPPOX, AF_RDS, AF_ROSE, AF_RXRPC, AF_SECURITY, AF_SNA, AF_TIPC, AF_UNIX, AF_UNSPEC, AF_WANPIPE, AF_X25, AF_XDP, IP6T_SO_ORIGINAL_DST, IPPROTO_FRAGMENT, IPPROTO_ICMPV6, IPPROTO_MH, IPPROTO_ROUTING, IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP, IPV6_FREEBIND, IPV6_MULTICAST_HOPS, @@ -71,12 +72,12 @@ pub(crate) use linux_raw_sys::{ MSG_CMSG_CLOEXEC, MSG_CONFIRM, MSG_DONTROUTE, MSG_DONTWAIT, MSG_EOR, MSG_ERRQUEUE, MSG_MORE, MSG_NOSIGNAL, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, SCM_CREDENTIALS, SCM_RIGHTS, SHUT_RD, SHUT_RDWR, SHUT_WR, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, - SOCK_STREAM, SOL_SOCKET, SOL_XDP, SO_ACCEPTCONN, SO_BROADCAST, SO_COOKIE, SO_DOMAIN, - SO_ERROR, SO_INCOMING_CPU, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_ORIGINAL_DST, - SO_PASSCRED, SO_PROTOCOL, SO_RCVBUF, SO_RCVTIMEO_NEW, SO_RCVTIMEO_NEW as SO_RCVTIMEO, - SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_REUSEPORT, SO_SNDBUF, SO_SNDTIMEO_NEW, - SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE, TCP_CONGESTION, TCP_CORK, - TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_NODELAY, TCP_QUICKACK, + SOCK_STREAM, SOL_PACKET, SOL_SOCKET, SOL_XDP, SO_ACCEPTCONN, SO_BROADCAST, SO_COOKIE, + SO_DOMAIN, SO_ERROR, SO_INCOMING_CPU, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, + SO_ORIGINAL_DST, SO_PASSCRED, SO_PROTOCOL, SO_RCVBUF, SO_RCVTIMEO_NEW, + SO_RCVTIMEO_NEW as SO_RCVTIMEO, SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_REUSEPORT, SO_SNDBUF, + SO_SNDTIMEO_NEW, SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE, TCP_CONGESTION, + TCP_CORK, TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_NODELAY, TCP_QUICKACK, TCP_THIN_LINEAR_TIMEOUTS, TCP_USER_TIMEOUT, }, netlink::*, diff --git a/src/backend/linux_raw/net/read_sockaddr.rs b/src/backend/linux_raw/net/read_sockaddr.rs index 23e1d641d..8539d3a29 100644 --- a/src/backend/linux_raw/net/read_sockaddr.rs +++ b/src/backend/linux_raw/net/read_sockaddr.rs @@ -127,6 +127,10 @@ pub(crate) unsafe fn read_sockaddr( u32::from_be(decode.sxdp_shared_umem_fd), ))) } + #[cfg(target_os = "linux")] + c::AF_PACKET => { + todo!(); + } _ => Err(io::Errno::NOTSUP), } } @@ -216,6 +220,10 @@ pub(crate) unsafe fn read_sockaddr_os(storage: *const c::sockaddr, len: usize) - u32::from_be(decode.sxdp_shared_umem_fd), )) } + #[cfg(target_os = "linux")] + c::AF_PACKET => { + todo!(); + } other => unimplemented!("{:?}", other), } } diff --git a/src/backend/linux_raw/net/sockopt.rs b/src/backend/linux_raw/net/sockopt.rs index d8066032e..6c8c5ea38 100644 --- a/src/backend/linux_raw/net/sockopt.rs +++ b/src/backend/linux_raw/net/sockopt.rs @@ -11,7 +11,9 @@ use crate::fd::BorrowedFd; #[cfg(feature = "alloc")] use crate::ffi::CStr; use crate::io; -use crate::net::sockopt::Timeout; +#[cfg(target_os = "linux")] +use crate::net::packet::{PacketReqAny, PacketStats, PacketStats3, PacketStatsAny}; +use crate::net::sockopt::{PacketVersion, Timeout}; #[cfg(target_os = "linux")] use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpRingOffset, XdpStatistics, XdpUmemReg}; use crate::net::{ @@ -967,6 +969,62 @@ pub(crate) fn get_xdp_options(fd: BorrowedFd<'_>) -> io::Result getsockopt(fd, c::SOL_XDP, c::XDP_OPTIONS) } +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn set_packet_rx_ring(fd: BorrowedFd<'_>, value: &PacketReqAny) -> io::Result<()> { + match *value { + PacketReqAny::V1(value) | PacketReqAny::V2(value) => { + setsockopt(fd, c::SOL_PACKET, c::PACKET_RX_RING, value) + } + PacketReqAny::V3(value) => setsockopt(fd, c::SOL_PACKET, c::PACKET_RX_RING, value), + } +} + +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn set_packet_tx_ring(fd: BorrowedFd<'_>, value: &PacketReqAny) -> io::Result<()> { + match *value { + PacketReqAny::V1(value) | PacketReqAny::V2(value) => { + setsockopt(fd, c::SOL_PACKET, c::PACKET_TX_RING, value) + } + PacketReqAny::V3(value) => setsockopt(fd, c::SOL_PACKET, c::PACKET_TX_RING, value), + } +} + +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn set_packet_version(fd: BorrowedFd<'_>, value: PacketVersion) -> io::Result<()> { + setsockopt(fd, c::SOL_PACKET, c::PACKET_VERSION, value) +} + +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn get_packet_version(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::SOL_PACKET, c::PACKET_VERSION) +} + +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn get_packet_stats( + fd: BorrowedFd<'_>, + version: PacketVersion, +) -> io::Result { + match version { + PacketVersion::V1 => { + let stats: PacketStats = getsockopt(fd, c::SOL_PACKET, c::PACKET_STATISTICS)?; + Ok(PacketStatsAny::V1(stats)) + } + PacketVersion::V2 => { + let stats: PacketStats = getsockopt(fd, c::SOL_PACKET, c::PACKET_STATISTICS)?; + Ok(PacketStatsAny::V2(stats)) + } + PacketVersion::V3 => { + let stats: PacketStats3 = getsockopt(fd, c::SOL_PACKET, c::PACKET_STATISTICS)?; + Ok(PacketStatsAny::V3(stats)) + } + } +} + #[inline] fn to_ip_mreq(multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> c::ip_mreq { c::ip_mreq { diff --git a/src/backend/linux_raw/net/syscalls.rs b/src/backend/linux_raw/net/syscalls.rs index 4d4427a40..862d6ec4e 100644 --- a/src/backend/linux_raw/net/syscalls.rs +++ b/src/backend/linux_raw/net/syscalls.rs @@ -13,6 +13,8 @@ use super::msghdr::{ use super::read_sockaddr::{initialize_family_to_unspec, maybe_read_sockaddr_os, read_sockaddr_os}; use super::send_recv::{RecvFlags, SendFlags}; #[cfg(target_os = "linux")] +use super::write_sockaddr::encode_sockaddr_link; +#[cfg(target_os = "linux")] use super::write_sockaddr::encode_sockaddr_xdp; use super::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6}; use crate::backend::c; @@ -23,6 +25,8 @@ use crate::backend::conv::{ use crate::fd::{BorrowedFd, OwnedFd}; use crate::io::{self, IoSlice, IoSliceMut}; #[cfg(target_os = "linux")] +use crate::net::packet::SocketAddrLink; +#[cfg(target_os = "linux")] use crate::net::xdp::SocketAddrXdp; use crate::net::{ AddressFamily, Protocol, RecvAncillaryBuffer, RecvMsgReturn, SendAncillaryBuffer, Shutdown, @@ -439,6 +443,18 @@ pub(crate) fn sendmsg_xdp( }) } +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn sendmsg_link( + _sockfd: BorrowedFd<'_>, + _addr: &SocketAddrLink, + _iov: &[IoSlice<'_>], + _control: &mut SendAncillaryBuffer<'_, '_, '_>, + _msg_flags: SendFlags, +) -> io::Result { + todo!() +} + #[inline] pub(crate) fn shutdown(fd: BorrowedFd<'_>, how: Shutdown) -> io::Result<()> { #[cfg(not(target_arch = "x86"))] @@ -660,6 +676,45 @@ pub(crate) fn sendto_xdp( } } +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn sendto_link( + fd: BorrowedFd<'_>, + buf: &[u8], + flags: SendFlags, + addr: &SocketAddrLink, +) -> io::Result { + let (buf_addr, buf_len) = slice(buf); + + #[cfg(not(target_arch = "x86"))] + unsafe { + ret_usize(syscall_readonly!( + __NR_sendto, + fd, + buf_addr, + buf_len, + flags, + by_ref(&encode_sockaddr_link(addr)), + size_of::() + )) + } + #[cfg(target_arch = "x86")] + unsafe { + ret_usize(syscall_readonly!( + __NR_socketcall, + x86_sys(SYS_SENDTO), + slice_just_addr::, _>(&[ + fd.into(), + buf_addr, + buf_len, + flags.into(), + by_ref(&encode_sockaddr_link(addr)), + size_of::(), + ]) + )) + } +} + #[inline] pub(crate) unsafe fn recv( fd: BorrowedFd<'_>, @@ -931,6 +986,32 @@ pub(crate) fn bind_xdp(fd: BorrowedFd<'_>, addr: &SocketAddrXdp) -> io::Result<( } } +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn bind_link(fd: BorrowedFd<'_>, addr: &SocketAddrLink) -> io::Result<()> { + #[cfg(not(target_arch = "x86"))] + unsafe { + ret(syscall_readonly!( + __NR_bind, + fd, + by_ref(&encode_sockaddr_link(addr)), + size_of::() + )) + } + #[cfg(target_arch = "x86")] + unsafe { + ret(syscall_readonly!( + __NR_socketcall, + x86_sys(SYS_BIND), + slice_just_addr::, _>(&[ + fd.into(), + by_ref(&encode_sockaddr_link(addr)), + size_of::(), + ]) + )) + } +} + #[inline] pub(crate) fn connect_v4(fd: BorrowedFd<'_>, addr: &SocketAddrV4) -> io::Result<()> { #[cfg(not(target_arch = "x86"))] diff --git a/src/backend/linux_raw/net/write_sockaddr.rs b/src/backend/linux_raw/net/write_sockaddr.rs index fb6e51edb..5f46ea56e 100644 --- a/src/backend/linux_raw/net/write_sockaddr.rs +++ b/src/backend/linux_raw/net/write_sockaddr.rs @@ -4,6 +4,8 @@ use crate::backend::c; #[cfg(target_os = "linux")] +use crate::net::packet::SocketAddrLink; +#[cfg(target_os = "linux")] use crate::net::xdp::SocketAddrXdp; use crate::net::{SocketAddrAny, SocketAddrStorage, SocketAddrUnix, SocketAddrV4, SocketAddrV6}; use core::mem::size_of; @@ -18,6 +20,8 @@ pub(crate) unsafe fn write_sockaddr( SocketAddrAny::Unix(unix) => write_sockaddr_unix(unix, storage), #[cfg(target_os = "linux")] SocketAddrAny::Xdp(xdp) => write_sockaddr_xdp(xdp, storage), + #[cfg(target_os = "linux")] + SocketAddrAny::Link(link) => write_sockaddr_link(link, storage), } } @@ -80,3 +84,16 @@ unsafe fn write_sockaddr_xdp(xdp: &SocketAddrXdp, storage: *mut SocketAddrStorag core::ptr::write(storage.cast(), encoded); size_of::() } + +#[cfg(target_os = "linux")] +pub(crate) fn encode_sockaddr_link(link: &SocketAddrLink) -> c::sockaddr_ll { + // SAFETY: both types have the same memory layout + unsafe { (link as *const _ as *const c::sockaddr_ll).read() } +} + +#[cfg(target_os = "linux")] +unsafe fn write_sockaddr_link(link: &SocketAddrLink, storage: *mut SocketAddrStorage) -> usize { + let encoded = encode_sockaddr_link(link); + core::ptr::write(storage.cast(), encoded); + size_of::() +} diff --git a/src/net/mod.rs b/src/net/mod.rs index 7ec8bc698..5a7962a27 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -18,6 +18,8 @@ mod wsa; #[cfg(linux_kernel)] pub mod netdevice; +#[cfg(linux_kernel)] +pub mod packet; pub mod sockopt; pub use crate::maybe_polyfill::net::{ diff --git a/src/net/packet.rs b/src/net/packet.rs new file mode 100644 index 000000000..ac7669e39 --- /dev/null +++ b/src/net/packet.rs @@ -0,0 +1,542 @@ +//! Packet MMAP. +#![allow(unsafe_code)] + +use super::{AddressFamily, Protocol}; +use crate::backend::c; +use bitflags::bitflags; +use core::{ + ffi::c_void, + fmt, + mem::{align_of, size_of, MaybeUninit}, + slice, +}; + +/// A type for holding raw integer packet types. +pub type RawPacketType = u8; + +/// `PACKET_*` constants. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct PacketType(pub(crate) RawPacketType); + +#[rustfmt::skip] +impl PacketType { + /// `PACKET_HOST` + pub const HOST: Self = Self(c::PACKET_HOST as _); + + /// `PACKET_BROADCAST` + pub const BROADCAST: Self = Self(c::PACKET_BROADCAST as _); + + /// `PACKET_MULTICAST` + pub const MULTICAST: Self = Self(c::PACKET_MULTICAST as _); + + /// `PACKET_OTHERHOST` + pub const OTHERHOST: Self = Self(c::PACKET_OTHERHOST as _); + + /// `PACKET_OUTGOING` + pub const OUTGOING: Self = Self(c::PACKET_OUTGOING as _); +} + +// TODO maybe separate RX and TX flags (and other flags) +bitflags! { + /// `TP_STATUS_*` constants. + /// + /// `TP_STATUS_KERNEL` == 0 + /// `TP_STATUS_AVAILABLE` == 0 + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] + pub struct PacketStatus: u32 { + /// `TP_STATUS_USER` + const USER = bitcast!(c::TP_STATUS_USER); + /// `TP_STATUS_COPY` + const COPY = bitcast!(c::TP_STATUS_COPY); + /// `TP_STATUS_LOSING` + const LOSING = bitcast!(c::TP_STATUS_LOSING); + /// `TP_STATUS_CSUMNOTREADY` + const CSUMNOTREADY = bitcast!(c::TP_STATUS_CSUMNOTREADY); + /// `TP_STATUS_VLAN_VALID` + const VLAN_VALID = bitcast!(c::TP_STATUS_VLAN_VALID); + /// `TP_STATUS_BLK_TMO` + const BLK_TMO = bitcast!(c::TP_STATUS_BLK_TMO); + /// `TP_STATUS_VLAN_TPID_VALID` + const VLAN_TPID_VALID = bitcast!(c::TP_STATUS_VLAN_TPID_VALID); + /// `TP_STATUS_CSUM_VALID` + const CSUM_VALID = bitcast!(c::TP_STATUS_CSUM_VALID); + /// `TP_STATUS_GSO_TCP` + const GSO_TCP = bitcast!(c::TP_STATUS_GSO_TCP); + + /// `TP_STATUS_SEND_REQUEST` + const SEND_REQUEST = bitcast!(c::TP_STATUS_SEND_REQUEST); + /// `TP_STATUS_SENDING` + const SENDING = bitcast!(c::TP_STATUS_SENDING); + /// `TP_STATUS_WRONG_FORMAT` + const WRONG_FORMAT = bitcast!(c::TP_STATUS_WRONG_FORMAT); + + /// `TP_STATUS_TS_SOFTWARE` + const TS_SOFTWARE = bitcast!(c::TP_STATUS_TS_SOFTWARE); + /// `TP_STATUS_TS_SYS_HARDWARE` + const TS_SYS_HARDWARE = bitcast!(c::TP_STATUS_TS_SYS_HARDWARE); + /// `TP_STATUS_TS_RAW_HARDWARE` + const TS_RAW_HARDWARE = bitcast!(c::TP_STATUS_TS_RAW_HARDWARE); + + /// + const _ = !0; + } +} + +/// `struct sockaddr_ll` +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] +#[allow(missing_docs)] +pub struct SocketAddrLink { + pub family: u16, + pub protocol: u16, + pub ifindex: i32, + pub hatype: u16, + pub pkttype: u8, + pub halen: u8, + pub addr: [u8; 8], +} + +impl SocketAddrLink { + /// Constructs a new link-layer socket address. + pub const fn new(protocol: Protocol, index: u32) -> Self { + let protocol = protocol.as_raw().get(); + debug_assert!(protocol <= u16::MAX as u32); + debug_assert!(index <= i32::MAX as u32); + Self { + family: AddressFamily::PACKET.as_raw(), + protocol: protocol as _, + ifindex: index as _, + hatype: 0, + pkttype: 0, + halen: 0, + addr: [0; 8], + } + } +} + +#[rustfmt::skip] +impl fmt::Display for SocketAddrLink { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let [a, b, c, d, e, f, g, h] = self.addr; + match self.halen { + 0 => write!(fmt, "empty"), + 1 => write!(fmt, "{a:02x}"), + 2 => write!(fmt, "{a:02x}:{b:02x}"), + 3 => write!(fmt, "{a:02x}:{b:02x}:{c:02x}"), + 4 => write!(fmt, "{a:02x}:{b:02x}:{c:02x}:{d:02x}"), + 5 => write!(fmt, "{a:02x}:{b:02x}:{c:02x}:{d:02x}:{e:02x}"), + 6 => write!(fmt, "{a:02x}:{b:02x}:{c:02x}:{d:02x}:{e:02x}:{f:02x}"), + 7 => write!(fmt, "{a:02x}:{b:02x}:{c:02x}:{d:02x}:{e:02x}:{f:02x}:{g:02x}"), + 8 => write!(fmt, "{a:02x}:{b:02x}:{c:02x}:{d:02x}:{e:02x}:{f:02x}:{g:02x}:{h:02x}"), + _ => unimplemented!("halen > 8"), + } + } +} + +/// `TPACKET_ALIGN` +pub const fn align(x: usize) -> usize { + let v = c::TPACKET_ALIGNMENT as usize; + (x + v - 1) & !(v - 1) +} + +/// `TPACKET_HDRLEN` +pub const PACKET_HEADER_LEN: usize = + align(size_of::()) + size_of::(); +/// `TPACKET2_HDRLEN` +pub const PACKET_HEADER2_LEN: usize = + align(size_of::()) + size_of::(); +/// `TPACKET3_HDRLEN` +pub const PACKET_HEADER3_LEN: usize = + align(size_of::()) + size_of::(); + +/// `struct tpacket_hdr` +#[repr(C)] +#[allow(missing_docs)] +pub struct PacketHeader { + pub status: u64, + pub len: u32, + pub snaplen: u32, + pub mac: u16, + pub net: u16, + pub sec: u32, + pub usec: u32, + _private: (), +} + +impl PacketHeader { + // TODO +} + +/// `struct tpacket2_hdr` +#[repr(C)] +#[allow(missing_docs)] +pub struct PacketHeader2 { + pub status: PacketStatus, + pub len: u32, + pub snaplen: u32, + pub mac: u16, + pub net: u16, + pub sec: u32, + pub nsec: u32, + pub vlan_tci: u16, + pub vlan_tpid: u16, + padding: [u8; 4], + _private: (), +} + +impl PacketHeader2 { + /// TODO + /// + /// # Safety + /// + /// The pointer must be properly aligned and non-null and point to + /// a valid `tpacket2_hdr` structure inside a `RX` ring buffer. + pub unsafe fn from_rx_ptr<'a>(ptr: *mut c_void) -> Option<&'a mut Self> { + assert_eq!(ptr.align_offset(c::TPACKET_ALIGNMENT as _), 0); + // TODO or should we return None? + debug_assert!(!ptr.is_null()); + // First read the status field without creating a reference. + let status = { + let ptr = ptr as *const u32; + PacketStatus::from_bits_truncate(ptr.read()) + }; + if status.contains(PacketStatus::USER) { + Some(&mut *(ptr as *mut Self)) + } else { + None + } + } + + /// TODO + /// + /// # Safety + /// + /// The pointer must be properly aligned and non-null and point to + /// a valid `tpacket2_hdr` structure inside a `TX` ring buffer. + pub unsafe fn from_tx_ptr<'a>(ptr: *mut c_void) -> Option<&'a mut Self> { + assert_eq!(ptr.align_offset(c::TPACKET_ALIGNMENT as _), 0); + // TODO or should we return None? + debug_assert!(!ptr.is_null()); + // First read the status field without creating a reference. + let status = { + let ptr = ptr as *const u32; + PacketStatus::from_bits_truncate(ptr.read()) + }; + if status.is_empty() { + // Available + Some(&mut *(ptr as *mut Self)) + } else { + None + } + } + + /// TODO + pub fn addr(&self) -> &SocketAddrLink { + let ptr = self as *const Self as *const u8; + unsafe { + let ptr = ptr.add(size_of::()); + let ptr = ptr.add(ptr.align_offset(c::TPACKET_ALIGNMENT as _)); + &*(ptr as *const SocketAddrLink) + } + } + + /// TODO + pub fn addr_mut(&mut self) -> &mut SocketAddrLink { + let ptr = self as *mut Self as *mut u8; + unsafe { + let ptr = ptr.add(size_of::()); + let ptr = ptr.add(ptr.align_offset(c::TPACKET_ALIGNMENT as _)); + &mut *(ptr as *mut SocketAddrLink) + } + } + + //pub unsafe fn mac(&self) -> Option<&[u8]> { + // // FIXME if using DGRAM, the mac header is not present + // if self.mac == 0 { + // return None; + // } + // debug_assert!( + // self.mac >= (size_of::() + size_of::()) as _ + // ); + + // // TODO might want to add other hardware types + // let len = match self.addr().hatype { + // // TODO use ARPHRD_* constants + // 1 => c::ETH_HLEN, // Ethernet + // _ => unimplemented!("unsupported hardware type"), + // }; + // debug_assert!(len <= (self.net - self.mac).into()); + + // let ptr = self.as_ptr() as *const u8; + // unsafe { + // // XXX do we just assume that `tp_mac` is valid? + // let ptr = ptr.add(self.mac as _); + // let ptr = ptr.add(ptr.align_offset(c::TPACKET_ALIGNMENT as _)); + // Some(slice::from_raw_parts(ptr, len as _)) + // } + //} + + /// TODO + pub fn payload_rx(&self) -> *const u8 { + let ptr = self as *const Self as *const u8; + unsafe { + let ptr = ptr.add(self.mac as _); + ptr + } + } + + /// TODO + pub fn payload_tx(&mut self) -> *mut u8 { + let ptr = self as *mut Self as *mut u8; + unsafe { + let ptr = ptr.add(size_of::()); + let ptr = ptr.add(ptr.align_offset(c::TPACKET_ALIGNMENT as _)); + ptr + } + } +} + +impl fmt::Debug for PacketHeader2 { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("PacketHeader2") + .field("status", &self.status) + .field("len", &self.len) + .field("snaplen", &self.snaplen) + .field("mac", &self.mac) + .field("net", &self.net) + .field("sec", &self.sec) + .field("nsec", &self.nsec) + .field("vlan_tci", &self.vlan_tci) + .field("vlan_tpid", &self.vlan_tpid) + .finish() + } +} + +/// `struct tpacket3_hdr` +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[allow(missing_docs)] +pub struct PacketHeader3 { + pub next_offset: u32, + pub sec: u32, + pub nsec: u32, + pub snaplen: u32, + pub len: u32, + pub status: u32, + pub mac: u16, + pub net: u16, + pub inner: PacketHeader3Inner, + pub padding: [u8; 8], +} + +impl PacketHeader3 { + // TODO +} + +/// `struct tpacket_hdr_variant1` +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[allow(missing_docs)] +pub struct PacketHeader3Inner { + pub rxhash: u32, + pub vlan_tci: u32, + pub vlan_tpid: u16, + pub padding: u16, +} + +/// `struct tpacket_req` +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[allow(missing_docs)] +pub struct PacketReq { + pub block_size: u32, + pub block_nr: u32, + pub frame_size: u32, + pub frame_nr: u32, +} + +/// `struct tpacket_req3` +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[allow(missing_docs)] +pub struct PacketReq3 { + pub block_size: u32, + pub block_nr: u32, + pub frame_size: u32, + pub frame_nr: u32, + pub retire_blk_tov: u32, + pub sizeof_priv: u32, + pub feature_req_word: u32, +} + +/// Packet MMAP settings for use with [`set_packet_rx_ring`] and [`set_packet_tx_ring`]. +#[repr(C)] +#[allow(missing_docs)] +pub enum PacketReqAny { + V1(PacketReq), + V2(PacketReq), + V3(PacketReq3), +} + +/// `struct tpacket_stats` +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[allow(missing_docs)] +pub struct PacketStats { + pub packets: u32, + pub drops: u32, +} + +/// `struct tpacket_stats_v3` +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[allow(missing_docs)] +pub struct PacketStats3 { + pub packets: u32, + pub drops: u32, + pub freeze_q_cnt: u32, +} + +/// Packet MMAP stats. +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[allow(missing_docs)] +pub enum PacketStatsAny { + V1(PacketStats), + V2(PacketStats), + V3(PacketStats3), +} + +/// `struct tpacket_auxdata` +#[repr(C)] +#[allow(missing_docs)] +pub struct PacketAuxData { + pub status: u32, + pub len: u32, + pub snaplen: u32, + pub mac: u16, + pub net: u16, + pub vlan_tci: u16, + pub vlan_tpid: u16, +} + +#[test] +fn if_packet_layouts() { + check_renamed_type!(SocketAddrLink, sockaddr_ll); + check_renamed_struct_renamed_field!(SocketAddrLink, sockaddr_ll, family, sll_family); + check_renamed_struct_renamed_field!(SocketAddrLink, sockaddr_ll, protocol, sll_protocol); + check_renamed_struct_renamed_field!(SocketAddrLink, sockaddr_ll, ifindex, sll_ifindex); + check_renamed_struct_renamed_field!(SocketAddrLink, sockaddr_ll, hatype, sll_hatype); + check_renamed_struct_renamed_field!(SocketAddrLink, sockaddr_ll, pkttype, sll_pkttype); + check_renamed_struct_renamed_field!(SocketAddrLink, sockaddr_ll, halen, sll_halen); + check_renamed_struct_renamed_field!(SocketAddrLink, sockaddr_ll, addr, __bindgen_anon_1); + + check_renamed_type!(PacketHeader, tpacket_hdr); + check_renamed_struct_renamed_field!(PacketHeader, tpacket_hdr, status, tp_status); + check_renamed_struct_renamed_field!(PacketHeader, tpacket_hdr, len, tp_len); + check_renamed_struct_renamed_field!(PacketHeader, tpacket_hdr, snaplen, tp_snaplen); + check_renamed_struct_renamed_field!(PacketHeader, tpacket_hdr, mac, tp_mac); + check_renamed_struct_renamed_field!(PacketHeader, tpacket_hdr, net, tp_net); + check_renamed_struct_renamed_field!(PacketHeader, tpacket_hdr, sec, tp_sec); + check_renamed_struct_renamed_field!(PacketHeader, tpacket_hdr, usec, tp_usec); + + check_renamed_type!(PacketHeader2, tpacket2_hdr); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, status, tp_status); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, len, tp_len); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, snaplen, tp_snaplen); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, mac, tp_mac); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, net, tp_net); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, sec, tp_sec); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, nsec, tp_nsec); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, vlan_tci, tp_vlan_tci); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, vlan_tpid, tp_vlan_tpid); + check_renamed_struct_renamed_field!(PacketHeader2, tpacket2_hdr, padding, tp_padding); + + check_renamed_type!(PacketHeader3, tpacket3_hdr); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, next_offset, tp_next_offset); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, sec, tp_sec); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, nsec, tp_nsec); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, snaplen, tp_snaplen); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, len, tp_len); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, status, tp_status); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, mac, tp_mac); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, net, tp_net); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, inner, __bindgen_anon_1); + check_renamed_struct_renamed_field!(PacketHeader3, tpacket3_hdr, padding, tp_padding); + + check_renamed_type!(PacketHeader3Inner, tpacket_hdr_variant1); + check_renamed_struct_renamed_field!( + PacketHeader3Inner, + tpacket_hdr_variant1, + rxhash, + tp_rxhash + ); + check_renamed_struct_renamed_field!( + PacketHeader3Inner, + tpacket_hdr_variant1, + vlan_tci, + tp_vlan_tci + ); + check_renamed_struct_renamed_field!( + PacketHeader3Inner, + tpacket_hdr_variant1, + vlan_tpid, + tp_vlan_tpid + ); + check_renamed_struct_renamed_field!( + PacketHeader3Inner, + tpacket_hdr_variant1, + padding, + tp_padding + ); + + check_renamed_type!(PacketReq, tpacket_req); + check_renamed_struct_renamed_field!(PacketReq, tpacket_req, block_size, tp_block_size); + check_renamed_struct_renamed_field!(PacketReq, tpacket_req, block_nr, tp_block_nr); + check_renamed_struct_renamed_field!(PacketReq, tpacket_req, frame_size, tp_frame_size); + check_renamed_struct_renamed_field!(PacketReq, tpacket_req, frame_nr, tp_frame_nr); + + check_renamed_type!(PacketReq3, tpacket_req3); + check_renamed_struct_renamed_field!(PacketReq3, tpacket_req3, block_size, tp_block_size); + check_renamed_struct_renamed_field!(PacketReq3, tpacket_req3, block_nr, tp_block_nr); + check_renamed_struct_renamed_field!(PacketReq3, tpacket_req3, frame_size, tp_frame_size); + check_renamed_struct_renamed_field!(PacketReq3, tpacket_req3, frame_nr, tp_frame_nr); + check_renamed_struct_renamed_field!( + PacketReq3, + tpacket_req3, + retire_blk_tov, + tp_retire_blk_tov + ); + check_renamed_struct_renamed_field!(PacketReq3, tpacket_req3, sizeof_priv, tp_sizeof_priv); + check_renamed_struct_renamed_field!( + PacketReq3, + tpacket_req3, + feature_req_word, + tp_feature_req_word + ); + + check_renamed_type!(PacketStats, tpacket_stats); + check_renamed_struct_renamed_field!(PacketStats, tpacket_stats, packets, tp_packets); + check_renamed_struct_renamed_field!(PacketStats, tpacket_stats, drops, tp_drops); + + check_renamed_type!(PacketStats3, tpacket_stats_v3); + check_renamed_struct_renamed_field!(PacketStats3, tpacket_stats_v3, packets, tp_packets); + check_renamed_struct_renamed_field!(PacketStats3, tpacket_stats_v3, drops, tp_drops); + check_renamed_struct_renamed_field!( + PacketStats3, + tpacket_stats_v3, + freeze_q_cnt, + tp_freeze_q_cnt + ); + + check_renamed_type!(PacketAuxData, tpacket_auxdata); + check_renamed_struct_renamed_field!(PacketAuxData, tpacket_auxdata, status, tp_status); + check_renamed_struct_renamed_field!(PacketAuxData, tpacket_auxdata, len, tp_len); + check_renamed_struct_renamed_field!(PacketAuxData, tpacket_auxdata, snaplen, tp_snaplen); + check_renamed_struct_renamed_field!(PacketAuxData, tpacket_auxdata, mac, tp_mac); + check_renamed_struct_renamed_field!(PacketAuxData, tpacket_auxdata, net, tp_net); + check_renamed_struct_renamed_field!(PacketAuxData, tpacket_auxdata, vlan_tci, tp_vlan_tci); + check_renamed_struct_renamed_field!(PacketAuxData, tpacket_auxdata, vlan_tpid, tp_vlan_tpid); +} diff --git a/src/net/send_recv/mod.rs b/src/net/send_recv/mod.rs index 1ae4fdb39..24d5e93df 100644 --- a/src/net/send_recv/mod.rs +++ b/src/net/send_recv/mod.rs @@ -4,6 +4,8 @@ use crate::buffer::split_init; #[cfg(target_os = "linux")] +use crate::net::packet::SocketAddrLink; +#[cfg(target_os = "linux")] use crate::net::xdp::SocketAddrXdp; #[cfg(unix)] use crate::net::SocketAddrUnix; @@ -265,6 +267,8 @@ fn _sendto_any( SocketAddrAny::Unix(unix) => backend::net::syscalls::sendto_unix(fd, buf, flags, unix), #[cfg(target_os = "linux")] SocketAddrAny::Xdp(xdp) => backend::net::syscalls::sendto_xdp(fd, buf, flags, xdp), + #[cfg(target_os = "linux")] + SocketAddrAny::Link(link) => backend::net::syscalls::sendto_link(fd, buf, flags, link), } } @@ -401,3 +405,22 @@ pub fn sendto_xdp( ) -> io::Result { backend::net::syscalls::sendto_xdp(fd.as_fd(), buf, flags, addr) } + +/// `sendto(fd, buf, flags, addr, sizeof(struct sockaddr_ll))`—Writes data +/// to a socket to a specific link-layer address. +/// +/// # References +/// - [Linux] +/// +/// [Linux]: https://man7.org/linux/man-pages/man2/sendto.2.html +#[cfg(target_os = "linux")] +#[inline] +#[doc(alias = "sendto")] +pub fn sendto_link( + fd: Fd, + buf: &[u8], + flags: SendFlags, + addr: &SocketAddrLink, +) -> io::Result { + backend::net::syscalls::sendto_link(fd.as_fd(), buf, flags, addr) +} diff --git a/src/net/send_recv/msg.rs b/src/net/send_recv/msg.rs index 629e4656a..4e232b5d9 100644 --- a/src/net/send_recv/msg.rs +++ b/src/net/send_recv/msg.rs @@ -770,6 +770,10 @@ pub fn sendmsg_any( Some(SocketAddrAny::Xdp(addr)) => { backend::net::syscalls::sendmsg_xdp(socket.as_fd(), addr, iov, control, flags) } + #[cfg(target_os = "linux")] + Some(SocketAddrAny::Link(addr)) => { + backend::net::syscalls::sendmsg_link(socket.as_fd(), addr, iov, control, flags) + } } } diff --git a/src/net/socket.rs b/src/net/socket.rs index bf1aa7c9f..0ff8b46c9 100644 --- a/src/net/socket.rs +++ b/src/net/socket.rs @@ -3,6 +3,8 @@ use crate::net::{SocketAddr, SocketAddrAny, SocketAddrV4, SocketAddrV6}; use crate::{backend, io}; use backend::fd::{AsFd, BorrowedFd}; +#[cfg(target_os = "linux")] +use crate::net::packet::SocketAddrLink; #[cfg(target_os = "linux")] use crate::net::xdp::SocketAddrXdp; pub use crate::net::{AddressFamily, Protocol, Shutdown, SocketFlags, SocketType}; @@ -172,6 +174,8 @@ fn _bind_any(sockfd: BorrowedFd<'_>, addr: &SocketAddrAny) -> io::Result<()> { SocketAddrAny::Unix(unix) => backend::net::syscalls::bind_unix(sockfd, unix), #[cfg(target_os = "linux")] SocketAddrAny::Xdp(xdp) => backend::net::syscalls::bind_xdp(sockfd, xdp), + #[cfg(target_os = "linux")] + SocketAddrAny::Link(link) => backend::net::syscalls::bind_link(sockfd, link), } } @@ -288,6 +292,19 @@ pub fn bind_xdp(sockfd: Fd, addr: &SocketAddrXdp) -> io::Result<()> { backend::net::syscalls::bind_xdp(sockfd.as_fd(), addr) } +/// `bind(sockfd, addr, sizeof(struct sockaddr_ll))`—Binds a socket to a link-layer address. +/// +/// # References +/// - [Linux] +/// +/// [Linux]: https://man7.org/linux/man2/bind.2.html +#[cfg(target_os = "linux")] +#[inline] +#[doc(alias = "bind")] +pub fn bind_link(sockfd: Fd, addr: &SocketAddrLink) -> io::Result<()> { + backend::net::syscalls::bind_link(sockfd.as_fd(), addr) +} + /// `connect(sockfd, addr)`—Initiates a connection to an IP address. /// /// On Windows, a non-blocking socket returns [`Errno::WOULDBLOCK`] if the @@ -369,6 +386,8 @@ fn _connect_any(sockfd: BorrowedFd<'_>, addr: &SocketAddrAny) -> io::Result<()> SocketAddrAny::Unix(unix) => backend::net::syscalls::connect_unix(sockfd, unix), #[cfg(target_os = "linux")] SocketAddrAny::Xdp(_) => Err(io::Errno::OPNOTSUPP), + #[cfg(target_os = "linux")] + SocketAddrAny::Link(_) => Err(io::Errno::OPNOTSUPP), } } diff --git a/src/net/socket_addr_any.rs b/src/net/socket_addr_any.rs index 3be80a3ad..13a5b544b 100644 --- a/src/net/socket_addr_any.rs +++ b/src/net/socket_addr_any.rs @@ -9,6 +9,8 @@ //! OS-specific socket address representations in memory. #![allow(unsafe_code)] +#[cfg(target_os = "linux")] +use crate::net::packet::SocketAddrLink; #[cfg(target_os = "linux")] use crate::net::xdp::SocketAddrXdp; #[cfg(unix)] @@ -35,6 +37,9 @@ pub enum SocketAddrAny { /// `struct sockaddr_xdp` #[cfg(target_os = "linux")] Xdp(SocketAddrXdp), + /// `struct sockaddr_ll` + #[cfg(target_os = "linux")] + Link(SocketAddrLink), } impl From for SocketAddrAny { @@ -69,6 +74,14 @@ impl From for SocketAddrAny { } } +#[cfg(target_os = "linux")] +impl From for SocketAddrAny { + #[inline] + fn from(from: SocketAddrLink) -> Self { + Self::Link(from) + } +} + impl SocketAddrAny { /// Return the address family of this socket address. #[inline] @@ -80,6 +93,8 @@ impl SocketAddrAny { Self::Unix(_) => AddressFamily::UNIX, #[cfg(target_os = "linux")] Self::Xdp(_) => AddressFamily::XDP, + #[cfg(target_os = "linux")] + Self::Link(_) => AddressFamily::PACKET, } } @@ -117,6 +132,8 @@ impl fmt::Debug for SocketAddrAny { Self::Unix(unix) => unix.fmt(fmt), #[cfg(target_os = "linux")] Self::Xdp(xdp) => xdp.fmt(fmt), + #[cfg(target_os = "linux")] + Self::Link(link) => link.fmt(fmt), } } } diff --git a/src/net/sockopt.rs b/src/net/sockopt.rs index 47ab1a5be..3a6fe3d17 100644 --- a/src/net/sockopt.rs +++ b/src/net/sockopt.rs @@ -143,6 +143,8 @@ #![doc(alias = "getsockopt")] #![doc(alias = "setsockopt")] +#[cfg(linux_kernel)] +use crate::net::packet::{PacketReqAny, PacketStatsAny}; #[cfg(target_os = "linux")] use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpStatistics, XdpUmemReg}; #[cfg(not(any( @@ -1472,6 +1474,79 @@ pub fn get_xdp_options(fd: Fd) -> io::Result { backend::net::sockopt::get_xdp_options(fd.as_fd()) } +/// `setsockopt(fd, SOL_SOCKET, PACKET_RX_RING, value)` +/// +/// # References +/// - [Linux] +/// +/// [Linux]: https://www.kernel.org/doc/html/next/networking/packet_mmap.html#packet-mmap-settings +#[cfg(linux_kernel)] +#[doc(alias = "PACKET_RX_RING")] +pub fn set_packet_rx_ring(fd: Fd, value: &PacketReqAny) -> io::Result<()> { + backend::net::sockopt::set_packet_rx_ring(fd.as_fd(), value) +} + +/// `setsockopt(fd, SOL_SOCKET, PACKET_TX_RING, value)` +/// +/// # References +/// - [Linux] +/// +/// [Linux]: https://www.kernel.org/doc/html/next/networking/packet_mmap.html#packet-mmap-settings +#[cfg(linux_kernel)] +#[doc(alias = "PACKET_TX_RING")] +pub fn set_packet_tx_ring(fd: Fd, value: &PacketReqAny) -> io::Result<()> { + backend::net::sockopt::set_packet_tx_ring(fd.as_fd(), value) +} + +/// Packet MMAP versions for use with [`set_packet_version`]. +#[repr(u32)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum PacketVersion { + /// `TPACKET_V1` + V1 = c::tpacket_versions::TPACKET_V1 as _, + /// `TPACKET_V2` + V2 = c::tpacket_versions::TPACKET_V2 as _, + /// `TPACKET_V3` + V3 = c::tpacket_versions::TPACKET_V3 as _, +} + +/// `setsockopt(fd, SOL_PACKET, PACKET_VERSION, value)` +/// +/// # References +/// - [Linux] +/// +/// [Linux]: https://www.kernel.org/doc/html/next/networking/packet_mmap.html#what-tpacket-versions-are-available-and-when-to-use-them +#[cfg(linux_kernel)] +#[doc(alias = "PACKET_VERSION")] +pub fn set_packet_version(fd: Fd, value: PacketVersion) -> io::Result<()> { + backend::net::sockopt::set_packet_version(fd.as_fd(), value) +} + +/// `getsockopt(fd, SOL_PACKET, PACKET_VERSION)` +/// +/// # References +/// - [Linux] +/// +/// [Linux]: https://www.kernel.org/doc/html/next/networking/packet_mmap.html#what-tpacket-versions-are-available-and-when-to-use-them +#[cfg(linux_kernel)] +#[doc(alias = "PACKET_VERSION")] +pub fn get_packet_version(fd: Fd) -> io::Result { + backend::net::sockopt::get_packet_version(fd.as_fd()) +} + +/// `getsockopt(fd, SOL_PACKET, PACKET_STATISTICS)` +/// +/// # References +/// - [Linux] +/// +/// [Linux]: https://www.kernel.org/doc/html/next/networking/packet_mmap.html +#[cfg(linux_kernel)] +#[doc(alias = "PACKET_STATISTICS")] +pub fn get_packet_stats(fd: Fd, version: PacketVersion) -> io::Result { + backend::net::sockopt::get_packet_stats(fd.as_fd(), version) +} + #[test] fn test_sizes() { use c::c_int; @@ -1479,4 +1554,5 @@ fn test_sizes() { // Backend code needs to cast these to `c_int` so make sure that cast // isn't lossy. assert_eq_size!(Timeout, c_int); + assert_eq_size!(PacketVersion, c_int); }