Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
102 changes: 102 additions & 0 deletions src/sys/socket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,61 @@ impl SockProtocol {
#[allow(non_upper_case_globals)]
#[cfg(target_endian = "little")]
pub const EthIp: SockProtocol = unsafe { std::mem::transmute::<i32, SockProtocol>((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<SockProtocol> for Protocol {
fn from(p: SockProtocol) -> Self {
Protocol(p as c_int)
}
}

#[cfg(linux_android)]
libc_bitflags! {
/// Configuration flags for `SO_TIMESTAMPING` interface
Expand Down Expand Up @@ -2251,6 +2304,55 @@ pub fn socket<T: Into<Option<SockProtocol>>>(
}
}

/// 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<OwnedFd> {
// 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)
Expand Down
38 changes: 38 additions & 0 deletions test/sys/test_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}