diff --git a/CHANGELOG.md b/CHANGELOG.md index d873417f..b1503622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 0.5.3 + +## Changed +* Deprecated `bind_device_by_index` and added versioned + IPv4 and IPv6 anaolgues `bind_device_by_index_v4` and + `bind_device_by_index_v6.` + + # 0.5.2 * Add Unix socket methods to `SockAddr` diff --git a/src/sys/unix.rs b/src/sys/unix.rs index e852ea94..fa098f32 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -1833,6 +1833,36 @@ impl crate::Socket { .map(|_| ()) } + /// This method is deprecated, use [`bind_device_by_index_v4`]. + /// + /// [`bind_device_by_index_v4`]: 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] + pub fn bind_device_by_index(&self, interface: Option) -> 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 `IP_BOUND_IF` option on this socket. /// /// If a socket is bound to an interface, only packets received from that @@ -1864,11 +1894,47 @@ impl crate::Socket { ) ))) )] - pub fn bind_device_by_index(&self, interface: Option) -> io::Result<()> { + pub fn bind_device_by_index_v4(&self, interface: Option) -> 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) -> 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. /// @@ -1901,6 +1967,39 @@ impl crate::Socket { Ok(NonZeroU32::new(index)) } + /// 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> { + let index = unsafe { + getsockopt::(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`]. diff --git a/tests/socket.rs b/tests/socket.rs index 2ea8c255..4569c8ca 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -956,6 +956,51 @@ fn device() { 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; + } + + panic!("failed to bind to any device."); +} + #[cfg(all( feature = "all", any(