diff --git a/Cargo.toml b/Cargo.toml index d0566282..a5fa1b66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ targets = [ features = ["all"] [target."cfg(unix)".dependencies] -libc = "0.2.171" +libc = "0.2.172" [target.'cfg(windows)'.dependencies.windows-sys] version = "0.52" diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 8e24f4e5..a808f228 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -17,8 +17,11 @@ use std::net::{Ipv4Addr, Ipv6Addr}; #[cfg(all( feature = "all", any( + target_os = "android", + target_os = "fuchsia", target_os = "ios", target_os = "visionos", + target_os = "linux", target_os = "macos", target_os = "tvos", target_os = "watchos", @@ -1782,7 +1785,8 @@ impl crate::Socket { /// /// If a socket is bound to an interface, only packets received from that /// particular interface are processed by the socket. Note that this only - /// works for some socket types, particularly `AF_INET` sockets. + /// works for some socket types, particularly `AF_INET` and `AF_INET6` + /// sockets. /// /// If `interface` is `None` or an empty string it removes the binding. #[cfg(all( @@ -1820,6 +1824,31 @@ impl crate::Socket { .map(|_| ()) } + /// Sets the value for `SO_BINDTOIFINDEX` option on this socket. + /// + /// If a socket is bound to an interface, only packets received from that + /// particular interface are processed by the socket. Note that this only + /// works for some socket types, particularly `AF_INET` and `AF_INET6` + /// sockets. + /// + /// If `interface` is `None`, the binding is removed. If the `interface` + /// index is not valid, an error is returned. + #[cfg(all( + feature = "all", + any(target_os = "android", target_os = "fuchsia", target_os = "linux") + ))] + pub fn bind_device_by_index(&self, interface: Option) -> io::Result<()> { + let index = interface.map_or(0, NonZeroU32::get); + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_SOCKET, + libc::SO_BINDTOIFINDEX, + index as c_int, + ) + } + } + /// Sets the value for `IP_BOUND_IF` option on this socket. /// /// If a socket is bound to an interface, only packets received from that @@ -1874,6 +1903,21 @@ impl crate::Socket { unsafe { setsockopt(self.as_raw(), IPPROTO_IPV6, libc::IPV6_BOUND_IF, index) } } + /// Gets the value for the `SO_BINDTOIFINDEX` option on this socket. + /// + /// Returns `None` if the socket is not bound to any interface, otherwise + /// returns an interface index. + #[cfg(all( + feature = "all", + any(target_os = "android", target_os = "fuchsia", target_os = "linux") + ))] + pub fn device_index(&self) -> io::Result> { + let index_raw = unsafe { + getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTOIFINDEX)? + }; + Ok(NonZeroU32::new(index_raw)) + } + /// Gets the value for `IP_BOUND_IF` option on this socket, i.e. the index /// for the interface to which the socket is bound. /// diff --git a/tests/socket.rs b/tests/socket.rs index a2dca668..d33d9f4a 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -982,6 +982,41 @@ fn device() { panic!("failed to bind to any device."); } +#[cfg(all( + feature = "all", + any(target_os = "android", target_os = "fuchsia", target_os = "linux") +))] +#[test] +#[ignore = "setting `SO_BINDTOIFINDEX` requires the `CAP_NET_RAW` capability (works when running as root)"] +fn device_by_index() { + const INTERFACE_INDICES: &[u32; 3] = &[1, 2, 3]; + + let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); + assert_eq!(socket.device().unwrap(), None); + + for if_index in INTERFACE_INDICES { + if let Err(err) = socket.bind_device_by_index(std::num::NonZeroU32::new(*if_index)) { + if matches!(err.raw_os_error(), Some(libc::ENODEV)) { + eprintln!("error binding to interface index (`{if_index}`): {err}"); + continue; + } else { + panic!("unexpected error binding device: {}", err); + } + } + assert_eq!( + socket.device_index().unwrap(), + std::num::NonZeroU32::new(*if_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 by IFINDEX."); +} + #[cfg(all( feature = "all", any(