Skip to content

Adds device_index and bind_device_by_index to android and linux #573

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ targets = [
features = ["all"]

[target."cfg(unix)".dependencies]
libc = "0.2.171"
libc = "0.2.172"

[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.52"
Expand Down
41 changes: 41 additions & 0 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ use std::net::{Ipv4Addr, Ipv6Addr};
#[cfg(all(
feature = "all",
any(
target_os = "android",
target_os = "ios",
target_os = "visionos",
target_os = "linux",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
Expand Down Expand Up @@ -1805,6 +1807,45 @@ impl crate::Socket {
.map(|_| ())
}

/// Gets the value for the `SO_BINDTOIFINDEX` option on this socket.
///
/// This value gets the socket binded device's interface index.
#[cfg(all(
feature = "all",
any(target_os = "android", target_os = "linux")
))]
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
let index_raw = unsafe {
getsockopt::<libc::c_uint>(self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTOIFINDEX)?
};
Ok(NonZeroU32::new(index_raw))
}

/// Sets the value for `SO_BINDTOIFINDEX` option on this socket.
///
/// If a socket is bound to an interface, only packets received from that
/// particular interface are processed by the socket. Note that this only
/// works for some socket types, particularly `AF_INET` sockets.
///
/// If `interface` is `None`, the binding is removed. If the `interface`
/// index is not valid, an error is returned.
#[cfg(all(
feature = "all",
any(target_os = "android", target_os = "linux")
))]
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
let index = interface.map_or(0, NonZeroU32::get);
unsafe {
setsockopt(
self.as_raw(),
libc::SOL_SOCKET,
libc::SO_BINDTOIFINDEX,
index as c_int
)
}

}

/// Sets the value for the `SO_SETFIB` option on this socket.
///
/// Bind socket to the specified forwarding table (VRF) on a FreeBSD.
Expand Down
32 changes: 32 additions & 0 deletions tests/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,38 @@ fn device() {
panic!("failed to bind to any device.");
}

#[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))]
#[test]
#[ignore = "setting `SO_BINDTOIFINDEX` requires the `CAP_NET_RAW` capability (works when running as root)"]
fn device_by_index() {
const INTERFACE_INDICES: &[u32;3] = &[1, 2, 3];

let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
assert_eq!(socket.device().unwrap(), None);

for if_index in INTERFACE_INDICES {
if let Err(err) = socket.bind_device_by_index(std::num::NonZeroU32::new(*if_index)) {
if matches!(err.raw_os_error(), Some(libc::ENODEV)) {
eprintln!("error binding to interface index (`{if_index}`): {err}");
continue;
} else {
panic!("unexpected error binding device: {}", err);
}
}
assert_eq!(
socket.device_index().unwrap(),
std::num::NonZeroU32::new(*if_index)
);

socket.bind_device_by_index(None).unwrap();
assert_eq!(socket.device_index().unwrap(), None);
// Just need to do it with one interface.
return;
}

panic!("failed to bind to any device by IFINDEX.");
}

#[cfg(all(
feature = "all",
any(
Expand Down
Loading