Skip to content

Commit

Permalink
Add IPv6 versions of bind_device_by_index
Browse files Browse the repository at this point in the history
  • Loading branch information
pinkisemils authored and Thomasdezeeuw committed May 29, 2023
1 parent fea2cb4 commit 5ca3960
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 7 deletions.
127 changes: 125 additions & 2 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1833,6 +1833,33 @@ impl crate::Socket {
.map(|_| ())
}

/// This method is deprecated, use [`crate::Socket::bind_device_by_index_v4`].
#[cfg(all(
feature = "all",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "all",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
)))
)]
#[deprecated = "Use `Socket::device_index_v4` instead"]
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
self.bind_device_by_index_v4(interface)
}

/// 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 @@ -1864,11 +1891,47 @@ impl crate::Socket {
)
)))
)]
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
pub fn bind_device_by_index_v4(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
let index = interface.map_or(0, NonZeroU32::get);
unsafe { setsockopt(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF, index) }
}

/// Sets the value for `IPV6_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",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "all",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
)))
)]
pub fn bind_device_by_index_v6(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
let index = interface.map_or(0, NonZeroU32::get);
unsafe { setsockopt(self.as_raw(), IPPROTO_IPV6, libc::IPV6_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.
///
Expand All @@ -1895,12 +1958,72 @@ impl crate::Socket {
)
)))
)]
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
pub fn device_index_v4(&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))
}

/// This method is deprecated, use [`crate::Socket::device_index_v4`].
#[cfg(all(
feature = "all",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "all",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
)))
)]
#[deprecated = "Use `Socket::device_index_v4` instead"]
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
self.device_index_v4()
}

/// Gets the value for `IPV6_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",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "all",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
)))
)]
pub fn device_index_v6(&self) -> io::Result<Option<NonZeroU32>> {
let index = unsafe {
getsockopt::<libc::c_uint>(self.as_raw(), IPPROTO_IPV6, libc::IPV6_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
55 changes: 50 additions & 5 deletions tests/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,7 @@ fn device() {
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);
assert_eq!(socket.device_index_v4().unwrap(), None);

for interface in INTERFACES.iter() {
let iface_index = std::num::NonZeroU32::new(unsafe {
Expand All @@ -936,7 +936,7 @@ fn device() {
if iface_index.is_none() {
continue;
}
if let Err(err) = socket.bind_device_by_index(iface_index) {
if let Err(err) = socket.bind_device_by_index_v4(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}");
Expand All @@ -945,10 +945,55 @@ fn device() {
panic!("unexpected error binding device: {}", err);
}
}
assert_eq!(socket.device_index().unwrap(), iface_index);
assert_eq!(socket.device_index_v4().unwrap(), iface_index);

socket.bind_device_by_index(None).unwrap();
assert_eq!(socket.device_index().unwrap(), None);
socket.bind_device_by_index_v4(None).unwrap();
assert_eq!(socket.device_index_v4().unwrap(), None);
// Just need to do it with one interface.
return;
}

panic!("failed to bind to any device.");
}

#[cfg(all(
feature = "all",
any(
target_os = "ios",
target_os = "macos",
target_os = "tvos",
target_os = "watchos",
)
))]
#[test]
fn device_v6() {
// Some common network interface on macOS.
const INTERFACES: &[&str] = &["lo\0", "lo0\0", "en0\0"];

let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap();
assert_eq!(socket.device_index_v6().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_v6(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_v6().unwrap(), iface_index);

socket.bind_device_by_index_v6(None).unwrap();
assert_eq!(socket.device_index_v6().unwrap(), None);
// Just need to do it with one interface.
return;
}
Expand Down

0 comments on commit 5ca3960

Please sign in to comment.