Skip to content

Commit

Permalink
Add bind_device_by_index,device_index for Apple
Browse files Browse the repository at this point in the history
  • Loading branch information
pinkisemils authored and Thomasdezeeuw committed May 26, 2021
1 parent 694deff commit f7023b4
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ rustdoc-args = ["--cfg", "docsrs"]
features = ["all"]

[target."cfg(unix)".dependencies]
libc = "0.2.86"
libc = "0.2.95"

[target."cfg(windows)".dependencies]
winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
Expand Down
32 changes: 32 additions & 0 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use std::marker::PhantomData;
use std::mem::{self, size_of, MaybeUninit};
use std::net::Shutdown;
use std::net::{Ipv4Addr, Ipv6Addr};
#[cfg(all(feature = "all", target_vendor = "apple"))]
use std::num::NonZeroU32;
#[cfg(all(
feature = "all",
any(
Expand Down Expand Up @@ -1315,6 +1317,36 @@ impl crate::Socket {
.map(|_| ())
}

/// Sets the value for `IP_BOUND_IF` option on this socket.
///
/// If a socket is bound to an interface, only packets received from that
/// particular interface are processed by the socket.
///
/// If `interface` is `None`, the binding is removed. If the `interface`
/// index is not valid, an error is returned.
///
/// One can use `libc::if_nametoindex` to convert an interface alias to an
/// index.
#[cfg(all(feature = "all", target_vendor = "apple"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_vendor = "apple"))))]
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
let index = interface.map(NonZeroU32::get).unwrap_or(0);
unsafe { setsockopt(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF, index) }
}

/// Gets the value for `IP_BOUND_IF` option on this socket, i.e. the index
/// for the interface to which the socket is bound.
///
/// Returns `None` if the socket is not bound to any interface, otherwise
/// returns an interface index.
#[cfg(all(feature = "all", target_vendor = "apple"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_vendor = "apple"))))]
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
let index =
unsafe { getsockopt::<libc::c_uint>(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF)? };
Ok(NonZeroU32::new(index))
}

/// Get the value of the `SO_INCOMING_CPU` option on this socket.
///
/// For more information about this option, see [`set_cpu_affinity`].
Expand Down
36 changes: 36 additions & 0 deletions tests/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,42 @@ fn device() {
panic!("failed to bind to any device.");
}

#[cfg(all(feature = "all", target_vendor = "apple"))]
#[test]
fn device() {
// Some common network interface on macOS.
const INTERFACES: &[&str] = &["lo\0", "lo0\0", "en0\0"];

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

for interface in INTERFACES.iter() {
let iface_index = std::num::NonZeroU32::new(unsafe {
libc::if_nametoindex(interface.as_ptr() as *const _)
});
// If no index is returned, try another interface alias
if iface_index.is_none() {
continue;
}
if let Err(err) = socket.bind_device_by_index(iface_index) {
// Network interface is not available try another.
if matches!(err.raw_os_error(), Some(libc::ENODEV)) {
eprintln!("error binding to device (`{}`): {}", interface, err);
continue;
} else {
panic!("unexpected error binding device: {}", err);
}
}
assert_eq!(socket.device_index().unwrap(), iface_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.");
}
#[cfg(all(
feature = "all",
any(
Expand Down

0 comments on commit f7023b4

Please sign in to comment.