Skip to content

Commit f7023b4

Browse files
pinkisemilsThomasdezeeuw
authored andcommitted
Add bind_device_by_index,device_index for Apple
1 parent 694deff commit f7023b4

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ rustdoc-args = ["--cfg", "docsrs"]
3333
features = ["all"]
3434

3535
[target."cfg(unix)".dependencies]
36-
libc = "0.2.86"
36+
libc = "0.2.95"
3737

3838
[target."cfg(windows)".dependencies]
3939
winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }

src/sys/unix.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use std::marker::PhantomData;
1313
use std::mem::{self, size_of, MaybeUninit};
1414
use std::net::Shutdown;
1515
use std::net::{Ipv4Addr, Ipv6Addr};
16+
#[cfg(all(feature = "all", target_vendor = "apple"))]
17+
use std::num::NonZeroU32;
1618
#[cfg(all(
1719
feature = "all",
1820
any(
@@ -1315,6 +1317,36 @@ impl crate::Socket {
13151317
.map(|_| ())
13161318
}
13171319

1320+
/// Sets the value for `IP_BOUND_IF` option on this socket.
1321+
///
1322+
/// If a socket is bound to an interface, only packets received from that
1323+
/// particular interface are processed by the socket.
1324+
///
1325+
/// If `interface` is `None`, the binding is removed. If the `interface`
1326+
/// index is not valid, an error is returned.
1327+
///
1328+
/// One can use `libc::if_nametoindex` to convert an interface alias to an
1329+
/// index.
1330+
#[cfg(all(feature = "all", target_vendor = "apple"))]
1331+
#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_vendor = "apple"))))]
1332+
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
1333+
let index = interface.map(NonZeroU32::get).unwrap_or(0);
1334+
unsafe { setsockopt(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF, index) }
1335+
}
1336+
1337+
/// Gets the value for `IP_BOUND_IF` option on this socket, i.e. the index
1338+
/// for the interface to which the socket is bound.
1339+
///
1340+
/// Returns `None` if the socket is not bound to any interface, otherwise
1341+
/// returns an interface index.
1342+
#[cfg(all(feature = "all", target_vendor = "apple"))]
1343+
#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_vendor = "apple"))))]
1344+
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
1345+
let index =
1346+
unsafe { getsockopt::<libc::c_uint>(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF)? };
1347+
Ok(NonZeroU32::new(index))
1348+
}
1349+
13181350
/// Get the value of the `SO_INCOMING_CPU` option on this socket.
13191351
///
13201352
/// For more information about this option, see [`set_cpu_affinity`].

tests/socket.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,42 @@ fn device() {
787787
panic!("failed to bind to any device.");
788788
}
789789

790+
#[cfg(all(feature = "all", target_vendor = "apple"))]
791+
#[test]
792+
fn device() {
793+
// Some common network interface on macOS.
794+
const INTERFACES: &[&str] = &["lo\0", "lo0\0", "en0\0"];
795+
796+
let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
797+
assert_eq!(socket.device_index().unwrap(), None);
798+
799+
for interface in INTERFACES.iter() {
800+
let iface_index = std::num::NonZeroU32::new(unsafe {
801+
libc::if_nametoindex(interface.as_ptr() as *const _)
802+
});
803+
// If no index is returned, try another interface alias
804+
if iface_index.is_none() {
805+
continue;
806+
}
807+
if let Err(err) = socket.bind_device_by_index(iface_index) {
808+
// Network interface is not available try another.
809+
if matches!(err.raw_os_error(), Some(libc::ENODEV)) {
810+
eprintln!("error binding to device (`{}`): {}", interface, err);
811+
continue;
812+
} else {
813+
panic!("unexpected error binding device: {}", err);
814+
}
815+
}
816+
assert_eq!(socket.device_index().unwrap(), iface_index);
817+
818+
socket.bind_device_by_index(None).unwrap();
819+
assert_eq!(socket.device_index().unwrap(), None);
820+
// Just need to do it with one interface.
821+
return;
822+
}
823+
824+
panic!("failed to bind to any device.");
825+
}
790826
#[cfg(all(
791827
feature = "all",
792828
any(

0 commit comments

Comments
 (0)