Skip to content

Commit

Permalink
Add Socket::(bind_)device
Browse files Browse the repository at this point in the history
Backport of commit 3851430.
  • Loading branch information
Thomasdezeeuw committed Dec 21, 2020
1 parent beeebd0 commit 1dfd15d
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<Option<CString>> {
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
Expand Down
64 changes: 64 additions & 0 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<Option<CString>> {
// TODO: replace with `MaybeUninit::uninit_array` once stable.
let mut buf: [MaybeUninit<u8>; libc::IFNAMSIZ] =
unsafe { MaybeUninit::<[MaybeUninit<u8>; 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<u32> {
unsafe {
let raw: c_int = self.getsockopt(libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS)?;
Expand Down

0 comments on commit 1dfd15d

Please sign in to comment.