From 51e966fb2a089a2a906c988fd36c8eb7d6042560 Mon Sep 17 00:00:00 2001 From: Gerald LATKOVIC Date: Fri, 30 Jan 2026 14:31:56 +0100 Subject: [PATCH] Add Protocol type and socket_with_protocol() for arbitrary protocols Add a new `Protocol` type and `socket_with_protocol()` function that allow creating sockets with arbitrary protocol numbers not defined in `SockProtocol`. This is particularly useful for AF_PACKET sockets where Ethernet protocol numbers (like ETH_P_ARP, ETH_P_802_1Q, etc.) need to be specified. The `Protocol::ethernet()` helper automatically handles the conversion to network byte order, which is required for Ethernet protocols. Example usage: ```rust let proto = Protocol::ethernet(libc::ETH_P_ARP as u16); let sock = socket_with_protocol( AddressFamily::Packet, SockType::Raw, SockFlag::empty(), proto, )?; ``` --- CHANGELOG.md | 4 ++ src/sys/socket/mod.rs | 102 ++++++++++++++++++++++++++++++++++++++++ test/sys/test_socket.rs | 38 +++++++++++++++ 3 files changed, 144 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5002ff7d3d..1a41c6dc08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +- Added `Protocol` type and `socket_with_protocol()` function to allow creating + sockets with arbitrary protocol numbers. Includes `Protocol::ethernet()` helper + for Ethernet protocols (like `ETH_P_ARP`) that handles network byte order + conversion automatically. ([#854](https://github.com/nix-rust/nix/issues/854)) - termios: Add definition for IUCLC to supported platforms ([#2702](https://github.com/nix-rust/nix/pull/2702)) - termios: Add definition for XCASE for supported platforms diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 4faa3010f7..4b22d652ae 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -260,8 +260,61 @@ impl SockProtocol { #[allow(non_upper_case_globals)] #[cfg(target_endian = "little")] pub const EthIp: SockProtocol = unsafe { std::mem::transmute::((libc::ETH_P_IP as u16).to_be() as i32) }; +} + +/// A raw protocol number for use with [`socket_with_protocol`]. +/// +/// This type allows specifying arbitrary protocol numbers that may not be +/// defined in [`SockProtocol`], such as Ethernet protocol numbers for +/// `AF_PACKET` sockets. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Protocol(c_int); + +impl Protocol { + /// Create a `Protocol` from a raw protocol number. + /// + /// The value is passed directly to the `socket(2)` syscall without modification. + pub const fn new(proto: c_int) -> Self { + Protocol(proto) + } + + /// Create a `Protocol` from an Ethernet protocol number (e.g., `libc::ETH_P_ARP`). + /// + /// This method automatically handles the conversion to network byte order, which is + /// required when passing Ethernet protocol numbers to the `socket(2)` syscall for + /// `AF_PACKET` sockets. + /// + /// # Example + /// + /// ``` + /// # #[cfg(linux_android)] + /// # { + /// use nix::sys::socket::{socket_with_protocol, AddressFamily, SockType, SockFlag, Protocol}; + /// + /// // Create a protocol for capturing ARP packets + /// let proto = Protocol::ethernet(libc::ETH_P_ARP as u16); + /// // let sock = socket_with_protocol(AddressFamily::Packet, SockType::Raw, SockFlag::empty(), proto); + /// # } + /// ``` + /// + /// See [`packet(7)`](https://man7.org/linux/man-pages/man7/packet.7.html) for more details. + #[cfg(linux_android)] + pub const fn ethernet(proto: u16) -> Self { + Protocol(proto.to_be() as c_int) + } + /// Returns the raw protocol number. + pub const fn as_raw(self) -> c_int { + self.0 + } } + +impl From for Protocol { + fn from(p: SockProtocol) -> Self { + Protocol(p as c_int) + } +} + #[cfg(linux_android)] libc_bitflags! { /// Configuration flags for `SO_TIMESTAMPING` interface @@ -2251,6 +2304,55 @@ pub fn socket>>( } } +/// Create an endpoint for communication, with a raw protocol number. +/// +/// This is similar to [`socket`], but accepts a [`Protocol`] instead of a +/// [`SockProtocol`], allowing for arbitrary protocol numbers that may not be +/// defined in the `SockProtocol` enum. +/// +/// This is particularly useful for `AF_PACKET` sockets where Ethernet protocol +/// numbers (like `ETH_P_ARP`) need to be specified in network byte order. +/// +/// # Example +/// +/// ```no_run +/// # #[cfg(linux_android)] +/// # fn main() -> nix::Result<()> { +/// use nix::sys::socket::{socket_with_protocol, AddressFamily, SockType, SockFlag, Protocol}; +/// +/// // Create a raw socket to capture ARP packets +/// let proto = Protocol::ethernet(libc::ETH_P_ARP as u16); +/// let sock = socket_with_protocol(AddressFamily::Packet, SockType::Raw, SockFlag::empty(), proto)?; +/// # Ok(()) +/// # } +/// # #[cfg(not(linux_android))] +/// # fn main() {} +/// ``` +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html) +pub fn socket_with_protocol( + domain: AddressFamily, + ty: SockType, + flags: SockFlag, + protocol: Protocol, +) -> Result { + // SockFlags are usually embedded into `ty`, but we don't do that in `nix` because it's a + // little easier to understand by separating it out. So we have to merge these bitfields + // here. + let mut ty = ty as c_int; + ty |= flags.bits(); + + let res = unsafe { libc::socket(domain as c_int, ty, protocol.as_raw()) }; + + match res { + -1 => Err(Errno::last()), + fd => { + // Safe because libc::socket returned success + unsafe { Ok(OwnedFd::from_raw_fd(fd)) } + } + } +} + /// Create a pair of connected sockets /// /// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html) diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 5a423c3066..4a85a0c62e 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -3183,3 +3183,41 @@ fn can_open_routing_socket() { socket(AddressFamily::Route, SockType::Raw, SockFlag::empty(), None) .expect("Failed to open routing socket"); } + +/// Test that `Protocol::ethernet` can be used to create packet sockets. +/// This test requires root privileges, so we just verify the protocol compiles +/// and can be passed to socket_with_protocol() - we don't require the socket +/// creation to succeed. +#[cfg(linux_android)] +#[test] +fn test_protocol_ethernet() { + use nix::sys::socket::{ + socket_with_protocol, AddressFamily, Protocol, SockFlag, SockType, + }; + + // Create a protocol using the ethernet helper + let proto = Protocol::ethernet(libc::ETH_P_ARP as u16); + + // Verify the byte order conversion is correct + // ETH_P_ARP is 0x0806, after to_be() on little-endian it becomes 0x0608 + let expected = (libc::ETH_P_ARP as u16).to_be() as libc::c_int; + assert_eq!(proto.as_raw(), expected); + + // Try to create a packet socket with the custom protocol. + // This will fail with EPERM if not root, but that's fine - we're testing + // that Protocol::ethernet() produces a value that can be passed to socket. + let result = socket_with_protocol( + AddressFamily::Packet, + SockType::Raw, + SockFlag::empty(), + proto, + ); + + // Either succeeds (if root) or fails with EPERM/EACCES (if not root) + // Any other error would indicate a problem with our protocol value + match result { + Ok(_) => (), // Success - we have privileges + Err(nix::errno::Errno::EPERM) | Err(nix::errno::Errno::EACCES) => (), // Expected without root + Err(e) => panic!("Unexpected error: {}", e), + } +}