diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 935c6387..61e3e393 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -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) -> 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 @@ -1864,11 +1891,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. /// @@ -1895,12 +1958,72 @@ impl crate::Socket { ) ))) )] - pub fn device_index(&self) -> io::Result> { + pub fn device_index_v4(&self) -> io::Result> { let index = unsafe { getsockopt::(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> { + 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> { + 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 af601442..2a3f8063 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -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 { @@ -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}"); @@ -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; }