From 59e976de194985b2ffd080f13130e7472a495a9a Mon Sep 17 00:00:00 2001 From: al8n Date: Thu, 27 Mar 2025 00:12:44 +0800 Subject: [PATCH] Implement `socket_bind_device` and `set_socket_bind_device` --- src/backend/libc/net/sockopt.rs | 38 ++++++++++++++++++++++++++++ src/backend/linux_raw/net/sockopt.rs | 26 ++++++++++++++++++- src/net/sockopt.rs | 24 ++++++++++++++++++ tests/net/sockopt.rs | 23 +++++++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/backend/libc/net/sockopt.rs b/src/backend/libc/net/sockopt.rs index 1251c7f70..f26ae002c 100644 --- a/src/backend/libc/net/sockopt.rs +++ b/src/backend/libc/net/sockopt.rs @@ -69,6 +69,9 @@ use alloc::borrow::ToOwned as _; target_os = "illumos" ))] use alloc::string::String; +#[cfg(feature = "alloc")] +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +use alloc::vec::Vec; #[cfg(apple)] use c::TCP_KEEPALIVE as TCP_KEEPIDLE; #[cfg(not(any(apple, target_os = "haiku", target_os = "nto", target_os = "openbsd")))] @@ -356,6 +359,41 @@ pub(crate) fn socket_recv_buffer_size(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::SOL_SOCKET, c::SO_RCVBUF).map(|size: u32| size as usize) } +#[cfg(feature = "alloc")] +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +pub(crate) fn socket_bind_device(fd: BorrowedFd<'_>) -> io::Result> { + let mut value = MaybeUninit::<[MaybeUninit; 16]>::uninit(); + let mut optlen = 16; + getsockopt_raw( + fd, + c::SOL_SOCKET, + c::SO_BINDTODEVICE, + &mut value, + &mut optlen, + )?; + unsafe { + let value = value.assume_init(); + let slice: &[u8] = core::mem::transmute(&value[..optlen as usize]); + assert!(slice.contains(&b'\0')); + Ok(slice[..optlen as usize - 1].to_vec()) + } +} + +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +pub(crate) fn set_socket_bind_device( + fd: BorrowedFd<'_>, + interface: Option<&[u8]>, +) -> io::Result<()> { + setsockopt( + fd, + c::SOL_SOCKET, + c::SO_BINDTODEVICE, + interface.unwrap_or(&[]), + ) +} + #[inline] pub(crate) fn set_socket_send_buffer_size(fd: BorrowedFd<'_>, size: usize) -> io::Result<()> { let size: c::c_int = size.try_into().map_err(|_| io::Errno::INVAL)?; diff --git a/src/backend/linux_raw/net/sockopt.rs b/src/backend/linux_raw/net/sockopt.rs index bdb26841d..aaae2b04b 100644 --- a/src/backend/linux_raw/net/sockopt.rs +++ b/src/backend/linux_raw/net/sockopt.rs @@ -22,10 +22,12 @@ use crate::net::{ use alloc::borrow::ToOwned as _; #[cfg(feature = "alloc")] use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use core::mem::{size_of, MaybeUninit}; use core::time::Duration; use linux_raw_sys::general::{__kernel_old_timeval, __kernel_sock_timeval}; -use linux_raw_sys::net::{IPV6_MTU, IPV6_MULTICAST_IF, IP_MTU, IP_MULTICAST_IF}; +use linux_raw_sys::net::{IPV6_MTU, IPV6_MULTICAST_IF, IP_MTU, IP_MULTICAST_IF, SO_BINDTODEVICE}; #[cfg(target_os = "linux")] use linux_raw_sys::xdp::{xdp_mmap_offsets, xdp_statistics, xdp_statistics_v1}; #[cfg(target_arch = "x86")] @@ -432,6 +434,28 @@ pub(crate) fn set_socket_incoming_cpu(fd: BorrowedFd<'_>, value: u32) -> io::Res setsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU, value) } +#[cfg(feature = "alloc")] +#[inline] +pub(crate) fn socket_bind_device(fd: BorrowedFd<'_>) -> io::Result> { + let mut value = MaybeUninit::<[MaybeUninit; 16]>::uninit(); + let mut optlen = 16; + getsockopt_raw(fd, c::SOL_SOCKET, SO_BINDTODEVICE, &mut value, &mut optlen)?; + unsafe { + let value = value.assume_init(); + let slice: &[u8] = core::mem::transmute(&value[..optlen as usize]); + assert!(slice.contains(&b'\0')); + Ok(slice[..optlen as usize - 1].to_vec()) + } +} + +#[inline] +pub(crate) fn set_socket_bind_device( + fd: BorrowedFd<'_>, + interface: Option<&[u8]>, +) -> io::Result<()> { + setsockopt(fd, c::SOL_SOCKET, SO_BINDTODEVICE, interface.unwrap_or(&[])) +} + #[inline] pub(crate) fn set_ip_ttl(fd: BorrowedFd<'_>, ttl: u32) -> io::Result<()> { setsockopt(fd, c::IPPROTO_IP, c::IP_TTL, ttl) diff --git a/src/net/sockopt.rs b/src/net/sockopt.rs index b20db42bb..eab164a8a 100644 --- a/src/net/sockopt.rs +++ b/src/net/sockopt.rs @@ -616,6 +616,30 @@ pub fn set_socket_incoming_cpu(fd: Fd, value: u32) -> io::Result<()> { backend::net::sockopt::set_socket_incoming_cpu(fd.as_fd(), value) } +/// `getsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(feature = "alloc")] +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "SO_BINDTODEVICE")] +pub fn socket_bind_device(fd: Fd) -> io::Result> { + backend::net::sockopt::socket_bind_device(fd.as_fd()) +} + +/// `setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, interface)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[doc(alias = "SO_BINDTODEVICE")] +pub fn set_socket_bind_device(fd: Fd, interface: Option<&[u8]>) -> io::Result<()> { + backend::net::sockopt::set_socket_bind_device(fd.as_fd(), interface) +} + /// `setsockopt(fd, IPPROTO_IP, IP_TTL, value)` /// /// See the [module-level documentation] for more. diff --git a/tests/net/sockopt.rs b/tests/net/sockopt.rs index a668afad9..e95608a54 100644 --- a/tests/net/sockopt.rs +++ b/tests/net/sockopt.rs @@ -625,3 +625,26 @@ fn test_sockopts_multicast_ifv6() { Err(e) => panic!("{e}"), } } + +#[cfg(feature = "alloc")] +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[test] +fn test_socket_bind_device() { + let fd = rustix::net::socket(AddressFamily::INET, SocketType::STREAM, None).unwrap(); + + let loopback_index = std::fs::read_to_string("/sys/class/net/lo/ifindex") + .unwrap() + .as_str() + .split_at(1) + .0 + .parse::() + .unwrap(); + let name = rustix::net::netdevice::index_to_name(&fd, loopback_index).unwrap(); + + sockopt::set_socket_bind_device(&fd, Some(name.as_bytes())).unwrap(); + + assert_eq!( + name.as_bytes(), + sockopt::socket_bind_device(&fd).unwrap().as_slice() + ); +}