Skip to content

Commit 1dfd15d

Browse files
committed
Add Socket::(bind_)device
Backport of commit 3851430.
1 parent beeebd0 commit 1dfd15d

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

src/socket.rs

+26
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
#[cfg(target_os = "linux")]
12+
use std::ffi::{CStr, CString};
1113
use std::fmt;
1214
use std::io::{self, Read, Write};
1315
use std::net::{self, Ipv4Addr, Ipv6Addr, Shutdown};
@@ -412,6 +414,30 @@ impl Socket {
412414
self.inner.set_mark(mark)
413415
}
414416

417+
/// Gets the value for the `SO_BINDTODEVICE` option on this socket.
418+
///
419+
/// This value gets the socket binded device's interface name.
420+
///
421+
/// This function is only available on Linux.
422+
#[cfg(target_os = "linux")]
423+
pub fn device(&self) -> io::Result<Option<CString>> {
424+
self.inner.device()
425+
}
426+
427+
/// Sets the value for the `SO_BINDTODEVICE` option on this socket.
428+
///
429+
/// If a socket is bound to an interface, only packets received from that
430+
/// particular interface are processed by the socket. Note that this only
431+
/// works for some socket types, particularly `AF_INET` sockets.
432+
///
433+
/// If `interface` is `None` or an empty string it removes the binding.
434+
///
435+
/// This function is only available on Linux.
436+
#[cfg(target_os = "linux")]
437+
pub fn bind_device(&self, interface: Option<&CStr>) -> io::Result<()> {
438+
self.inner.bind_device(interface)
439+
}
440+
415441
/// Gets the value of the `IPV6_UNICAST_HOPS` option for this socket.
416442
///
417443
/// Specifies the hop limit for ipv6 unicast packets

src/sys/unix.rs

+64
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@
99
// except according to those terms.
1010

1111
use std::cmp;
12+
#[cfg(target_os = "linux")]
13+
use std::ffi::{CStr, CString};
1214
use std::fmt;
1315
use std::io;
1416
use std::io::{ErrorKind, Read, Write};
1517
use std::mem;
18+
#[cfg(target_os = "linux")]
19+
use std::mem::MaybeUninit;
1620
use std::net::Shutdown;
1721
use std::net::{self, Ipv4Addr, Ipv6Addr};
1822
use std::ops::Neg;
1923
#[cfg(feature = "unix")]
2024
use std::os::unix::net::{UnixDatagram, UnixListener, UnixStream};
2125
use std::os::unix::prelude::*;
26+
#[cfg(target_os = "linux")]
27+
use std::ptr;
28+
#[cfg(target_os = "linux")]
29+
use std::slice;
2230
use std::sync::atomic::{AtomicBool, Ordering};
2331
use std::time::{Duration, Instant};
2432

@@ -563,6 +571,62 @@ impl Socket {
563571
unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_MARK, mark as c_int) }
564572
}
565573

574+
#[cfg(target_os = "linux")]
575+
pub fn device(&self) -> io::Result<Option<CString>> {
576+
// TODO: replace with `MaybeUninit::uninit_array` once stable.
577+
let mut buf: [MaybeUninit<u8>; libc::IFNAMSIZ] =
578+
unsafe { MaybeUninit::<[MaybeUninit<u8>; libc::IFNAMSIZ]>::uninit().assume_init() };
579+
let mut len = buf.len() as libc::socklen_t;
580+
let len = unsafe {
581+
cvt(libc::getsockopt(
582+
self.fd,
583+
libc::SOL_SOCKET,
584+
libc::SO_BINDTODEVICE,
585+
buf.as_mut_ptr().cast(),
586+
&mut len,
587+
))?
588+
};
589+
if len == 0 {
590+
Ok(None)
591+
} else {
592+
// Allocate a buffer for `CString` with the length including the
593+
// null terminator.
594+
let len = len as usize;
595+
let mut name = Vec::with_capacity(len);
596+
597+
// TODO: use `MaybeUninit::slice_assume_init_ref` once stable.
598+
// Safety: `len` bytes are writen by the OS, this includes a null
599+
// terminator. However we don't copy the null terminator because
600+
// `CString::from_vec_unchecked` adds its own null terminator.
601+
let buf = unsafe { slice::from_raw_parts(buf.as_ptr().cast(), len - 1) };
602+
name.extend_from_slice(buf);
603+
604+
// Safety: the OS initialised the string for us, which shouldn't
605+
// include any null bytes.
606+
Ok(Some(unsafe { CString::from_vec_unchecked(name) }))
607+
}
608+
}
609+
610+
#[cfg(target_os = "linux")]
611+
pub fn bind_device(&self, interface: Option<&CStr>) -> io::Result<()> {
612+
let (value, len) = if let Some(interface) = interface {
613+
(interface.as_ptr(), interface.to_bytes_with_nul().len())
614+
} else {
615+
(ptr::null(), 0)
616+
};
617+
618+
unsafe {
619+
cvt(libc::setsockopt(
620+
self.fd,
621+
libc::SOL_SOCKET,
622+
libc::SO_BINDTODEVICE,
623+
value.cast(),
624+
len as libc::socklen_t,
625+
))
626+
.map(|_| ())
627+
}
628+
}
629+
566630
pub fn unicast_hops_v6(&self) -> io::Result<u32> {
567631
unsafe {
568632
let raw: c_int = self.getsockopt(libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS)?;

0 commit comments

Comments
 (0)