Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v4 and v6 versions of bind_device_by_index. #432

Merged
merged 1 commit into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
Thomasdezeeuw marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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