Skip to content

Commit 3427eb2

Browse files
author
Gerald LATKOVIC
committed
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, )?; ```
1 parent 478594e commit 3427eb2

File tree

3 files changed

+144
-0
lines changed

3 files changed

+144
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](https://semver.org/).
77

88
### Added
99

10+
- Added `Protocol` type and `socket_with_protocol()` function to allow creating
11+
sockets with arbitrary protocol numbers. Includes `Protocol::ethernet()` helper
12+
for Ethernet protocols (like `ETH_P_ARP`) that handles network byte order
13+
conversion automatically. ([#854](https://github.com/nix-rust/nix/issues/854))
1014
- termios: Add definition for IUCLC to supported platforms
1115
([#2702](https://github.com/nix-rust/nix/pull/2702))
1216
- termios: Add definition for XCASE for supported platforms

src/sys/socket/mod.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,61 @@ impl SockProtocol {
260260
#[allow(non_upper_case_globals)]
261261
#[cfg(target_endian = "little")]
262262
pub const EthIp: SockProtocol = unsafe { std::mem::transmute::<i32, SockProtocol>((libc::ETH_P_IP as u16).to_be() as i32) };
263+
}
264+
265+
/// A raw protocol number for use with [`socket_with_protocol`].
266+
///
267+
/// This type allows specifying arbitrary protocol numbers that may not be
268+
/// defined in [`SockProtocol`], such as Ethernet protocol numbers for
269+
/// `AF_PACKET` sockets.
270+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
271+
pub struct Protocol(c_int);
272+
273+
impl Protocol {
274+
/// Create a `Protocol` from a raw protocol number.
275+
///
276+
/// The value is passed directly to the `socket(2)` syscall without modification.
277+
pub const fn new(proto: c_int) -> Self {
278+
Protocol(proto)
279+
}
280+
281+
/// Create a `Protocol` from an Ethernet protocol number (e.g., `libc::ETH_P_ARP`).
282+
///
283+
/// This method automatically handles the conversion to network byte order, which is
284+
/// required when passing Ethernet protocol numbers to the `socket(2)` syscall for
285+
/// `AF_PACKET` sockets.
286+
///
287+
/// # Example
288+
///
289+
/// ```
290+
/// # #[cfg(linux_android)]
291+
/// # {
292+
/// use nix::sys::socket::{socket_with_protocol, AddressFamily, SockType, SockFlag, Protocol};
293+
///
294+
/// // Create a protocol for capturing ARP packets
295+
/// let proto = Protocol::ethernet(libc::ETH_P_ARP as u16);
296+
/// // let sock = socket_with_protocol(AddressFamily::Packet, SockType::Raw, SockFlag::empty(), proto);
297+
/// # }
298+
/// ```
299+
///
300+
/// See [`packet(7)`](https://man7.org/linux/man-pages/man7/packet.7.html) for more details.
301+
#[cfg(linux_android)]
302+
pub const fn ethernet(proto: u16) -> Self {
303+
Protocol(proto.to_be() as c_int)
304+
}
263305

306+
/// Returns the raw protocol number.
307+
pub const fn as_raw(self) -> c_int {
308+
self.0
309+
}
264310
}
311+
312+
impl From<SockProtocol> for Protocol {
313+
fn from(p: SockProtocol) -> Self {
314+
Protocol(p as c_int)
315+
}
316+
}
317+
265318
#[cfg(linux_android)]
266319
libc_bitflags! {
267320
/// Configuration flags for `SO_TIMESTAMPING` interface
@@ -2251,6 +2304,55 @@ pub fn socket<T: Into<Option<SockProtocol>>>(
22512304
}
22522305
}
22532306

2307+
/// Create an endpoint for communication, with a raw protocol number.
2308+
///
2309+
/// This is similar to [`socket`], but accepts a [`Protocol`] instead of a
2310+
/// [`SockProtocol`], allowing for arbitrary protocol numbers that may not be
2311+
/// defined in the `SockProtocol` enum.
2312+
///
2313+
/// This is particularly useful for `AF_PACKET` sockets where Ethernet protocol
2314+
/// numbers (like `ETH_P_ARP`) need to be specified in network byte order.
2315+
///
2316+
/// # Example
2317+
///
2318+
/// ```no_run
2319+
/// # #[cfg(linux_android)]
2320+
/// # fn main() -> nix::Result<()> {
2321+
/// use nix::sys::socket::{socket_with_protocol, AddressFamily, SockType, SockFlag, Protocol};
2322+
///
2323+
/// // Create a raw socket to capture ARP packets
2324+
/// let proto = Protocol::ethernet(libc::ETH_P_ARP as u16);
2325+
/// let sock = socket_with_protocol(AddressFamily::Packet, SockType::Raw, SockFlag::empty(), proto)?;
2326+
/// # Ok(())
2327+
/// # }
2328+
/// # #[cfg(not(linux_android))]
2329+
/// # fn main() {}
2330+
/// ```
2331+
///
2332+
/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html)
2333+
pub fn socket_with_protocol(
2334+
domain: AddressFamily,
2335+
ty: SockType,
2336+
flags: SockFlag,
2337+
protocol: Protocol,
2338+
) -> Result<OwnedFd> {
2339+
// SockFlags are usually embedded into `ty`, but we don't do that in `nix` because it's a
2340+
// little easier to understand by separating it out. So we have to merge these bitfields
2341+
// here.
2342+
let mut ty = ty as c_int;
2343+
ty |= flags.bits();
2344+
2345+
let res = unsafe { libc::socket(domain as c_int, ty, protocol.as_raw()) };
2346+
2347+
match res {
2348+
-1 => Err(Errno::last()),
2349+
fd => {
2350+
// Safe because libc::socket returned success
2351+
unsafe { Ok(OwnedFd::from_raw_fd(fd)) }
2352+
}
2353+
}
2354+
}
2355+
22542356
/// Create a pair of connected sockets
22552357
///
22562358
/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html)

test/sys/test_socket.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3183,3 +3183,41 @@ fn can_open_routing_socket() {
31833183
socket(AddressFamily::Route, SockType::Raw, SockFlag::empty(), None)
31843184
.expect("Failed to open routing socket");
31853185
}
3186+
3187+
/// Test that `Protocol::ethernet` can be used to create packet sockets.
3188+
/// This test requires root privileges, so we just verify the protocol compiles
3189+
/// and can be passed to socket_with_protocol() - we don't require the socket
3190+
/// creation to succeed.
3191+
#[cfg(linux_android)]
3192+
#[test]
3193+
fn test_protocol_ethernet() {
3194+
use nix::sys::socket::{
3195+
socket_with_protocol, AddressFamily, Protocol, SockFlag, SockType,
3196+
};
3197+
3198+
// Create a protocol using the ethernet helper
3199+
let proto = Protocol::ethernet(libc::ETH_P_ARP as u16);
3200+
3201+
// Verify the byte order conversion is correct
3202+
// ETH_P_ARP is 0x0806, after to_be() on little-endian it becomes 0x0608
3203+
let expected = (libc::ETH_P_ARP as u16).to_be() as libc::c_int;
3204+
assert_eq!(proto.as_raw(), expected);
3205+
3206+
// Try to create a packet socket with the custom protocol.
3207+
// This will fail with EPERM if not root, but that's fine - we're testing
3208+
// that Protocol::ethernet() produces a value that can be passed to socket.
3209+
let result = socket_with_protocol(
3210+
AddressFamily::Packet,
3211+
SockType::Raw,
3212+
SockFlag::empty(),
3213+
proto,
3214+
);
3215+
3216+
// Either succeeds (if root) or fails with EPERM/EACCES (if not root)
3217+
// Any other error would indicate a problem with our protocol value
3218+
match result {
3219+
Ok(_) => (), // Success - we have privileges
3220+
Err(nix::errno::Errno::EPERM) | Err(nix::errno::Errno::EACCES) => (), // Expected without root
3221+
Err(e) => panic!("Unexpected error: {}", e),
3222+
}
3223+
}

0 commit comments

Comments
 (0)