Skip to content

Commit 28d6de7

Browse files
committed
Add IPv6 versions of bind_device_by_index
1 parent fea2cb4 commit 28d6de7

File tree

2 files changed

+175
-7
lines changed

2 files changed

+175
-7
lines changed

src/sys/unix.rs

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,6 +1833,33 @@ impl crate::Socket {
18331833
.map(|_| ())
18341834
}
18351835

1836+
/// This method is deprecated, use [`crate::Socket::bind_device_by_index_v4`].
1837+
#[cfg(all(
1838+
feature = "all",
1839+
any(
1840+
target_os = "ios",
1841+
target_os = "macos",
1842+
target_os = "tvos",
1843+
target_os = "watchos",
1844+
)
1845+
))]
1846+
#[cfg_attr(
1847+
docsrs,
1848+
doc(cfg(all(
1849+
feature = "all",
1850+
any(
1851+
target_os = "ios",
1852+
target_os = "macos",
1853+
target_os = "tvos",
1854+
target_os = "watchos",
1855+
)
1856+
)))
1857+
)]
1858+
#[deprecated = "Use `Socket::device_index_v4` instead"]
1859+
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
1860+
self.bind_device_by_index_v4(interface)
1861+
}
1862+
18361863
/// Sets the value for `IP_BOUND_IF` option on this socket.
18371864
///
18381865
/// If a socket is bound to an interface, only packets received from that
@@ -1864,11 +1891,47 @@ impl crate::Socket {
18641891
)
18651892
)))
18661893
)]
1867-
pub fn bind_device_by_index(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
1894+
pub fn bind_device_by_index_v4(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
18681895
let index = interface.map_or(0, NonZeroU32::get);
18691896
unsafe { setsockopt(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF, index) }
18701897
}
18711898

1899+
/// Sets the value for `IPV6_BOUND_IF` option on this socket.
1900+
///
1901+
/// If a socket is bound to an interface, only packets received from that
1902+
/// particular interface are processed by the socket.
1903+
///
1904+
/// If `interface` is `None`, the binding is removed. If the `interface`
1905+
/// index is not valid, an error is returned.
1906+
///
1907+
/// One can use [`libc::if_nametoindex`] to convert an interface alias to an
1908+
/// index.
1909+
#[cfg(all(
1910+
feature = "all",
1911+
any(
1912+
target_os = "ios",
1913+
target_os = "macos",
1914+
target_os = "tvos",
1915+
target_os = "watchos",
1916+
)
1917+
))]
1918+
#[cfg_attr(
1919+
docsrs,
1920+
doc(cfg(all(
1921+
feature = "all",
1922+
any(
1923+
target_os = "ios",
1924+
target_os = "macos",
1925+
target_os = "tvos",
1926+
target_os = "watchos",
1927+
)
1928+
)))
1929+
)]
1930+
pub fn bind_device_by_index_v6(&self, interface: Option<NonZeroU32>) -> io::Result<()> {
1931+
let index = interface.map_or(0, NonZeroU32::get);
1932+
unsafe { setsockopt(self.as_raw(), IPPROTO_IPV6, libc::IPV6_BOUND_IF, index) }
1933+
}
1934+
18721935
/// Gets the value for `IP_BOUND_IF` option on this socket, i.e. the index
18731936
/// for the interface to which the socket is bound.
18741937
///
@@ -1895,12 +1958,72 @@ impl crate::Socket {
18951958
)
18961959
)))
18971960
)]
1898-
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
1961+
pub fn device_index_v4(&self) -> io::Result<Option<NonZeroU32>> {
18991962
let index =
19001963
unsafe { getsockopt::<libc::c_uint>(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF)? };
19011964
Ok(NonZeroU32::new(index))
19021965
}
19031966

1967+
/// This method is deprecated, use [`crate::Socket::device_index_v4`].
1968+
#[cfg(all(
1969+
feature = "all",
1970+
any(
1971+
target_os = "ios",
1972+
target_os = "macos",
1973+
target_os = "tvos",
1974+
target_os = "watchos",
1975+
)
1976+
))]
1977+
#[cfg_attr(
1978+
docsrs,
1979+
doc(cfg(all(
1980+
feature = "all",
1981+
any(
1982+
target_os = "ios",
1983+
target_os = "macos",
1984+
target_os = "tvos",
1985+
target_os = "watchos",
1986+
)
1987+
)))
1988+
)]
1989+
#[deprecated = "Use `Socket::device_index_v4` instead"]
1990+
pub fn device_index(&self) -> io::Result<Option<NonZeroU32>> {
1991+
self.device_index_v4()
1992+
}
1993+
1994+
/// Gets the value for `IPV6_BOUND_IF` option on this socket, i.e. the index
1995+
/// for the interface to which the socket is bound.
1996+
///
1997+
/// Returns `None` if the socket is not bound to any interface, otherwise
1998+
/// returns an interface index.
1999+
#[cfg(all(
2000+
feature = "all",
2001+
any(
2002+
target_os = "ios",
2003+
target_os = "macos",
2004+
target_os = "tvos",
2005+
target_os = "watchos",
2006+
)
2007+
))]
2008+
#[cfg_attr(
2009+
docsrs,
2010+
doc(cfg(all(
2011+
feature = "all",
2012+
any(
2013+
target_os = "ios",
2014+
target_os = "macos",
2015+
target_os = "tvos",
2016+
target_os = "watchos",
2017+
)
2018+
)))
2019+
)]
2020+
pub fn device_index_v6(&self) -> io::Result<Option<NonZeroU32>> {
2021+
let index = unsafe {
2022+
getsockopt::<libc::c_uint>(self.as_raw(), IPPROTO_IPV6, libc::IPV6_BOUND_IF)?
2023+
};
2024+
Ok(NonZeroU32::new(index))
2025+
}
2026+
19042027
/// Get the value of the `SO_INCOMING_CPU` option on this socket.
19052028
///
19062029
/// For more information about this option, see [`set_cpu_affinity`].

tests/socket.rs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ fn device() {
926926
const INTERFACES: &[&str] = &["lo\0", "lo0\0", "en0\0"];
927927

928928
let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
929-
assert_eq!(socket.device_index().unwrap(), None);
929+
assert_eq!(socket.device_index_v4().unwrap(), None);
930930

931931
for interface in INTERFACES.iter() {
932932
let iface_index = std::num::NonZeroU32::new(unsafe {
@@ -936,7 +936,7 @@ fn device() {
936936
if iface_index.is_none() {
937937
continue;
938938
}
939-
if let Err(err) = socket.bind_device_by_index(iface_index) {
939+
if let Err(err) = socket.bind_device_by_index_v4(iface_index) {
940940
// Network interface is not available try another.
941941
if matches!(err.raw_os_error(), Some(libc::ENODEV)) {
942942
eprintln!("error binding to device (`{interface}`): {err}");
@@ -945,10 +945,55 @@ fn device() {
945945
panic!("unexpected error binding device: {}", err);
946946
}
947947
}
948-
assert_eq!(socket.device_index().unwrap(), iface_index);
948+
assert_eq!(socket.device_index_v4().unwrap(), iface_index);
949949

950-
socket.bind_device_by_index(None).unwrap();
951-
assert_eq!(socket.device_index().unwrap(), None);
950+
socket.bind_device_by_index_v4(None).unwrap();
951+
assert_eq!(socket.device_index_v4().unwrap(), None);
952+
// Just need to do it with one interface.
953+
return;
954+
}
955+
956+
panic!("failed to bind to any device.");
957+
}
958+
959+
#[cfg(all(
960+
feature = "all",
961+
any(
962+
target_os = "ios",
963+
target_os = "macos",
964+
target_os = "tvos",
965+
target_os = "watchos",
966+
)
967+
))]
968+
#[test]
969+
fn device_v6() {
970+
// Some common network interface on macOS.
971+
const INTERFACES: &[&str] = &["lo\0", "lo0\0", "en0\0"];
972+
973+
let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap();
974+
assert_eq!(socket.device_index_v6().unwrap(), None);
975+
976+
for interface in INTERFACES.iter() {
977+
let iface_index = std::num::NonZeroU32::new(unsafe {
978+
libc::if_nametoindex(interface.as_ptr() as *const _)
979+
});
980+
// If no index is returned, try another interface alias
981+
if iface_index.is_none() {
982+
continue;
983+
}
984+
if let Err(err) = socket.bind_device_by_index_v6(iface_index) {
985+
// Network interface is not available try another.
986+
if matches!(err.raw_os_error(), Some(libc::ENODEV)) {
987+
eprintln!("error binding to device (`{interface}`): {err}");
988+
continue;
989+
} else {
990+
panic!("unexpected error binding device: {}", err);
991+
}
992+
}
993+
assert_eq!(socket.device_index_v6().unwrap(), iface_index);
994+
995+
socket.bind_device_by_index_v6(None).unwrap();
996+
assert_eq!(socket.device_index_v6().unwrap(), None);
952997
// Just need to do it with one interface.
953998
return;
954999
}

0 commit comments

Comments
 (0)