Skip to content

Commit b896177

Browse files
committed
Adds bind_device_by_index to linux-like OS
This commit adds bind_device_by_index to android, fuchsia, and linux OS. This binds socket to a particular interface similarly to bind_device except that the interfaces are referenced by its index rather than name.
1 parent 36dec54 commit b896177

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
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

+45-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ use std::net::{Ipv4Addr, Ipv6Addr};
1717
#[cfg(all(
1818
feature = "all",
1919
any(
20+
target_os = "android",
21+
target_os = "fuchsia",
2022
target_os = "ios",
2123
target_os = "visionos",
24+
target_os = "linux",
2225
target_os = "macos",
2326
target_os = "tvos",
2427
target_os = "watchos",
@@ -1782,7 +1785,8 @@ impl crate::Socket {
17821785
///
17831786
/// If a socket is bound to an interface, only packets received from that
17841787
/// particular interface are processed by the socket. Note that this only
1785-
/// works for some socket types, particularly `AF_INET` sockets.
1788+
/// works for some socket types, particularly `AF_INET` and `AF_INET6`
1789+
/// sockets.
17861790
///
17871791
/// If `interface` is `None` or an empty string it removes the binding.
17881792
#[cfg(all(
@@ -1820,6 +1824,31 @@ impl crate::Socket {
18201824
.map(|_| ())
18211825
}
18221826

1827+
/// Sets the value for `SO_BINDTOIFINDEX` option on this socket.
1828+
///
1829+
/// If a socket is bound to an interface, only packets received from that
1830+
/// particular interface are processed by the socket. Note that this only
1831+
/// works for some socket types, particularly `AF_INET` and `AF_INET6`
1832+
/// sockets.
1833+
///
1834+
/// If `interface` is `None`, the binding is removed. If the `interface`
1835+
/// index is not valid, an error is returned.
1836+
#[cfg(all(
1837+
feature = "all",
1838+
any(target_os = "android", target_os = "fuchsia", target_os = "linux")
1839+
))]
1840+
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
1841+
let index = interface.map_or(0, NonZeroU32::get);
1842+
unsafe {
1843+
setsockopt(
1844+
self.as_raw(),
1845+
libc::SOL_SOCKET,
1846+
libc::SO_BINDTOIFINDEX,
1847+
index as c_int,
1848+
)
1849+
}
1850+
}
1851+
18231852
/// Sets the value for `IP_BOUND_IF` option on this socket.
18241853
///
18251854
/// If a socket is bound to an interface, only packets received from that
@@ -1874,6 +1903,21 @@ impl crate::Socket {
18741903
unsafe { setsockopt(self.as_raw(), IPPROTO_IPV6, libc::IPV6_BOUND_IF, index) }
18751904
}
18761905

1906+
/// Gets the value for the `SO_BINDTOIFINDEX` option on this socket.
1907+
///
1908+
/// Returns `None` if the socket is not bound to any interface, otherwise
1909+
/// returns an interface index.
1910+
#[cfg(all(
1911+
feature = "all",
1912+
any(target_os = "android", target_os = "fuchsia", target_os = "linux")
1913+
))]
1914+
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
1915+
let index_raw = unsafe {
1916+
getsockopt::<libc::c_uint>(self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTOIFINDEX)?
1917+
};
1918+
Ok(NonZeroU32::new(index_raw))
1919+
}
1920+
18771921
/// Gets the value for `IP_BOUND_IF` option on this socket, i.e. the index
18781922
/// for the interface to which the socket is bound.
18791923
///

tests/socket.rs

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

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

0 commit comments

Comments
 (0)