From 1dfd15d8f3626eb91ff6238155d3bfbfaed59cf5 Mon Sep 17 00:00:00 2001 From: Thomas de Zeeuw Date: Mon, 21 Dec 2020 12:18:07 +0100 Subject: [PATCH] Add Socket::(bind_)device Backport of commit 3851430dec41c204d6219a84c47ca6885622a98e. --- src/socket.rs | 26 ++++++++++++++++++++ src/sys/unix.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/socket.rs b/src/socket.rs index c69262ca..1f1fcc92 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -8,6 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#[cfg(target_os = "linux")] +use std::ffi::{CStr, CString}; use std::fmt; use std::io::{self, Read, Write}; use std::net::{self, Ipv4Addr, Ipv6Addr, Shutdown}; @@ -412,6 +414,30 @@ impl Socket { self.inner.set_mark(mark) } + /// Gets the value for the `SO_BINDTODEVICE` option on this socket. + /// + /// This value gets the socket binded device's interface name. + /// + /// This function is only available on Linux. + #[cfg(target_os = "linux")] + pub fn device(&self) -> io::Result> { + self.inner.device() + } + + /// Sets the value for the `SO_BINDTODEVICE` 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` sockets. + /// + /// If `interface` is `None` or an empty string it removes the binding. + /// + /// This function is only available on Linux. + #[cfg(target_os = "linux")] + pub fn bind_device(&self, interface: Option<&CStr>) -> io::Result<()> { + self.inner.bind_device(interface) + } + /// Gets the value of the `IPV6_UNICAST_HOPS` option for this socket. /// /// Specifies the hop limit for ipv6 unicast packets diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 5f3c27c9..87794002 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -9,16 +9,24 @@ // except according to those terms. use std::cmp; +#[cfg(target_os = "linux")] +use std::ffi::{CStr, CString}; use std::fmt; use std::io; use std::io::{ErrorKind, Read, Write}; use std::mem; +#[cfg(target_os = "linux")] +use std::mem::MaybeUninit; use std::net::Shutdown; use std::net::{self, Ipv4Addr, Ipv6Addr}; use std::ops::Neg; #[cfg(feature = "unix")] use std::os::unix::net::{UnixDatagram, UnixListener, UnixStream}; use std::os::unix::prelude::*; +#[cfg(target_os = "linux")] +use std::ptr; +#[cfg(target_os = "linux")] +use std::slice; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; @@ -563,6 +571,62 @@ impl Socket { unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_MARK, mark as c_int) } } + #[cfg(target_os = "linux")] + pub fn device(&self) -> io::Result> { + // TODO: replace with `MaybeUninit::uninit_array` once stable. + let mut buf: [MaybeUninit; libc::IFNAMSIZ] = + unsafe { MaybeUninit::<[MaybeUninit; libc::IFNAMSIZ]>::uninit().assume_init() }; + let mut len = buf.len() as libc::socklen_t; + let len = unsafe { + cvt(libc::getsockopt( + self.fd, + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + buf.as_mut_ptr().cast(), + &mut len, + ))? + }; + if len == 0 { + Ok(None) + } else { + // Allocate a buffer for `CString` with the length including the + // null terminator. + let len = len as usize; + let mut name = Vec::with_capacity(len); + + // TODO: use `MaybeUninit::slice_assume_init_ref` once stable. + // Safety: `len` bytes are writen by the OS, this includes a null + // terminator. However we don't copy the null terminator because + // `CString::from_vec_unchecked` adds its own null terminator. + let buf = unsafe { slice::from_raw_parts(buf.as_ptr().cast(), len - 1) }; + name.extend_from_slice(buf); + + // Safety: the OS initialised the string for us, which shouldn't + // include any null bytes. + Ok(Some(unsafe { CString::from_vec_unchecked(name) })) + } + } + + #[cfg(target_os = "linux")] + pub fn bind_device(&self, interface: Option<&CStr>) -> io::Result<()> { + let (value, len) = if let Some(interface) = interface { + (interface.as_ptr(), interface.to_bytes_with_nul().len()) + } else { + (ptr::null(), 0) + }; + + unsafe { + cvt(libc::setsockopt( + self.fd, + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + value.cast(), + len as libc::socklen_t, + )) + .map(|_| ()) + } + } + pub fn unicast_hops_v6(&self) -> io::Result { unsafe { let raw: c_int = self.getsockopt(libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS)?;