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
46 changes: 45 additions & 1 deletion src/sys/unix.rs
Copy link
Collaborator

@Darksonn Darksonn May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea: Should we let the v4/v6 methods also exist on Linux and just have them call bind_device_by_index? That way, it's easier to write code that supports both cases.

Thoughts @Thomasdezeeuw ?

Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ use std::net::{Ipv4Addr, Ipv6Addr};
#[cfg(all(
feature = "all",
any(
target_os = "android",
target_os = "fuchsia",
target_os = "ios",
target_os = "visionos",
target_os = "linux",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
Expand Down Expand Up @@ -1782,7 +1785,8 @@ impl crate::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.
/// works for some socket types, particularly `AF_INET` and `AF_INET6`
/// sockets.
///
/// If `interface` is `None` or an empty string it removes the binding.
#[cfg(all(
Expand Down Expand Up @@ -1820,6 +1824,31 @@ impl crate::Socket {
.map(|_| ())
}

/// 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` and `AF_INET6`
/// 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 = "fuchsia", 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 `IP_BOUND_IF` option on this socket.
///
/// If a socket is bound to an interface, only packets received from that
Expand Down Expand Up @@ -1874,6 +1903,21 @@ impl crate::Socket {
unsafe { setsockopt(self.as_raw(), IPPROTO_IPV6, libc::IPV6_BOUND_IF, index) }
}

/// Gets the value for the `SO_BINDTOIFINDEX` option on this socket.
///
/// Returns `None` if the socket is not bound to any interface, otherwise
/// returns an interface index.
#[cfg(all(
feature = "all",
any(target_os = "android", target_os = "fuchsia", 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))
}

/// Gets the value for `IP_BOUND_IF` option on this socket, i.e. the index
/// for the interface to which the socket is bound.
///
Expand Down
35 changes: 35 additions & 0 deletions tests/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,41 @@ fn device() {
panic!("failed to bind to any device.");
}

#[cfg(all(
feature = "all",
any(target_os = "android", target_os = "fuchsia", 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