Skip to content

Commit c76a3e4

Browse files
committed
Adds bind_device_by_index to android and linux
This PR adds bind_device_by_index to android and linux OS, which binds a socket to a device by its interface index.
1 parent 36dec54 commit c76a3e4

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ targets = [
5151
features = ["all"]
5252

5353
[target."cfg(unix)".dependencies]
54-
libc = "0.2.171"
54+
libc = "0.2.172"
5555

5656
[target.'cfg(windows)'.dependencies.windows-sys]
5757
version = "0.52"

src/sys/unix.rs

+34
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ use std::net::{Ipv4Addr, Ipv6Addr};
1717
#[cfg(all(
1818
feature = "all",
1919
any(
20+
target_os = "android",
2021
target_os = "ios",
2122
target_os = "visionos",
23+
target_os = "linux",
2224
target_os = "macos",
2325
target_os = "tvos",
2426
target_os = "watchos",
@@ -1805,6 +1807,38 @@ impl crate::Socket {
18051807
.map(|_| ())
18061808
}
18071809

1810+
/// Gets the value for the `SO_BINDTOIFINDEX` option on this socket.
1811+
///
1812+
/// This value gets the socket binded device's interface index.
1813+
#[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))]
1814+
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
1815+
let index_raw = unsafe {
1816+
getsockopt::<libc::c_uint>(self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTOIFINDEX)?
1817+
};
1818+
Ok(NonZeroU32::new(index_raw))
1819+
}
1820+
1821+
/// Sets the value for `SO_BINDTOIFINDEX` option on this socket.
1822+
///
1823+
/// If a socket is bound to an interface, only packets received from that
1824+
/// particular interface are processed by the socket. Note that this only
1825+
/// works for some socket types, particularly `AF_INET` sockets.
1826+
///
1827+
/// If `interface` is `None`, the binding is removed. If the `interface`
1828+
/// index is not valid, an error is returned.
1829+
#[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))]
1830+
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
1831+
let index = interface.map_or(0, NonZeroU32::get);
1832+
unsafe {
1833+
setsockopt(
1834+
self.as_raw(),
1835+
libc::SOL_SOCKET,
1836+
libc::SO_BINDTOIFINDEX,
1837+
index as c_int,
1838+
)
1839+
}
1840+
}
1841+
18081842
/// Sets the value for the `SO_SETFIB` option on this socket.
18091843
///
18101844
/// Bind socket to the specified forwarding table (VRF) on a FreeBSD.

tests/socket.rs

+32
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,38 @@ fn device() {
982982
panic!("failed to bind to any device.");
983983
}
984984

985+
#[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))]
986+
#[test]
987+
#[ignore = "setting `SO_BINDTOIFINDEX` requires the `CAP_NET_RAW` capability (works when running as root)"]
988+
fn device_by_index() {
989+
const INTERFACE_INDICES: &[u32; 3] = &[1, 2, 3];
990+
991+
let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
992+
assert_eq!(socket.device().unwrap(), None);
993+
994+
for if_index in INTERFACE_INDICES {
995+
if let Err(err) = socket.bind_device_by_index(std::num::NonZeroU32::new(*if_index)) {
996+
if matches!(err.raw_os_error(), Some(libc::ENODEV)) {
997+
eprintln!("error binding to interface index (`{if_index}`): {err}");
998+
continue;
999+
} else {
1000+
panic!("unexpected error binding device: {}", err);
1001+
}
1002+
}
1003+
assert_eq!(
1004+
socket.device_index().unwrap(),
1005+
std::num::NonZeroU32::new(*if_index)
1006+
);
1007+
1008+
socket.bind_device_by_index(None).unwrap();
1009+
assert_eq!(socket.device_index().unwrap(), None);
1010+
// Just need to do it with one interface.
1011+
return;
1012+
}
1013+
1014+
panic!("failed to bind to any device by IFINDEX.");
1015+
}
1016+
9851017
#[cfg(all(
9861018
feature = "all",
9871019
any(

0 commit comments

Comments
 (0)