Skip to content

Commit 80023b3

Browse files
committed
Windows: Add support for Unix sockets
Newer versions of Windows support AF_UNIX stream sockets. This change adds Windows support for the `SockAddr::unix` function and the `Domain::UNIX` constant. Since Unix sockets are now available on all tier1 platforms, this also removes `all` feature requirement from the `SockAddr::unix` function.
1 parent eaa6300 commit 80023b3

File tree

6 files changed

+132
-81
lines changed

6 files changed

+132
-81
lines changed

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ add new tests.
3333

3434
All types and methods that are available on all tier 1 platforms are defined in
3535
the first level of the source, i.e. `src/*.rs` files. Additional API that is
36-
platform specific, e.g. `Domain::UNIX`, is defined in `src/sys/*.rs` and only
36+
platform specific, e.g. `Domain::VSOCK`, is defined in `src/sys/*.rs` and only
3737
for the platforms that support it. For API that is not available on all tier 1
3838
platforms the `all` feature is used, to indicate to the user that they're using
3939
API that might is not available on all platforms.

src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ impl Domain {
210210
/// Domain for IPv6 communication, corresponding to `AF_INET6`.
211211
pub const IPV6: Domain = Domain(sys::AF_INET6);
212212

213+
/// Domain for Unix socket communication, corresponding to `AF_UNIX`.
214+
pub const UNIX: Domain = Domain(sys::AF_UNIX);
215+
213216
/// Returns the correct domain for `address`.
214217
pub const fn for_address(address: SocketAddr) -> Domain {
215218
match address {

src/sockaddr.rs

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::mem::{self, size_of, MaybeUninit};
22
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
3+
use std::path::Path;
34
use std::{fmt, io};
45

56
#[cfg(windows)]
@@ -210,6 +211,16 @@ impl SockAddr {
210211
_ => None,
211212
}
212213
}
214+
215+
/// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path.
216+
///
217+
/// Returns an error if the path is longer than `SUN_LEN`.
218+
pub fn unix<P>(path: P) -> io::Result<SockAddr>
219+
where
220+
P: AsRef<Path>,
221+
{
222+
crate::sys::unix_sockaddr(path.as_ref())
223+
}
213224
}
214225

215226
impl From<SocketAddr> for SockAddr {

src/sys/unix.rs

+50-73
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use std::num::NonZeroU32;
2525
)
2626
))]
2727
use std::num::NonZeroUsize;
28-
#[cfg(feature = "all")]
2928
use std::os::unix::ffi::OsStrExt;
3029
#[cfg(all(
3130
feature = "all",
@@ -40,9 +39,7 @@ use std::os::unix::io::RawFd;
4039
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd};
4140
#[cfg(feature = "all")]
4241
use std::os::unix::net::{UnixDatagram, UnixListener, UnixStream};
43-
#[cfg(feature = "all")]
4442
use std::path::Path;
45-
#[cfg(not(all(target_os = "redox", not(feature = "all"))))]
4643
use std::ptr;
4744
use std::time::{Duration, Instant};
4845
use std::{io, slice};
@@ -58,7 +55,7 @@ use crate::{Domain, Protocol, SockAddr, TcpKeepalive, Type};
5855
pub(crate) use libc::c_int;
5956

6057
// Used in `Domain`.
61-
pub(crate) use libc::{AF_INET, AF_INET6};
58+
pub(crate) use libc::{AF_INET, AF_INET6, AF_UNIX};
6259
// Used in `Type`.
6360
#[cfg(all(feature = "all", not(target_os = "redox")))]
6461
pub(crate) use libc::SOCK_RAW;
@@ -222,10 +219,6 @@ type IovLen = c_int;
222219

223220
/// Unix only API.
224221
impl Domain {
225-
/// Domain for Unix socket communication, corresponding to `AF_UNIX`.
226-
#[cfg_attr(docsrs, doc(cfg(unix)))]
227-
pub const UNIX: Domain = Domain(libc::AF_UNIX);
228-
229222
/// Domain for low-level packet interface, corresponding to `AF_PACKET`.
230223
#[cfg(all(
231224
feature = "all",
@@ -460,71 +453,56 @@ impl<'a> MaybeUninitSlice<'a> {
460453
}
461454
}
462455

463-
/// Unix only API.
464-
impl SockAddr {
465-
/// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path.
466-
///
467-
/// # Failure
468-
///
469-
/// Returns an error if the path is longer than `SUN_LEN`.
470-
#[cfg(feature = "all")]
471-
#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "all"))))]
472-
#[allow(unused_unsafe)] // TODO: replace with `unsafe_op_in_unsafe_fn` once stable.
473-
pub fn unix<P>(path: P) -> io::Result<SockAddr>
474-
where
475-
P: AsRef<Path>,
476-
{
456+
#[allow(unused_unsafe)] // TODO: replace with `unsafe_op_in_unsafe_fn` once stable.
457+
pub(crate) fn unix_sockaddr(path: &Path) -> io::Result<SockAddr> {
458+
// SAFETY: a `sockaddr_storage` of all zeros is valid.
459+
let mut storage = unsafe { mem::zeroed::<sockaddr_storage>() };
460+
let len = {
461+
let storage: &mut libc::sockaddr_un =
462+
unsafe { &mut *(&mut storage as *mut sockaddr_storage).cast() };
463+
464+
let bytes = path.as_os_str().as_bytes();
465+
let too_long = match bytes.first() {
466+
None => false,
467+
// linux abstract namespaces aren't null-terminated
468+
Some(&0) => bytes.len() > storage.sun_path.len(),
469+
Some(_) => bytes.len() >= storage.sun_path.len(),
470+
};
471+
if too_long {
472+
return Err(io::Error::new(
473+
io::ErrorKind::InvalidInput,
474+
"path must be shorter than SUN_LEN",
475+
));
476+
}
477+
478+
storage.sun_family = libc::AF_UNIX as sa_family_t;
479+
// Safety: `bytes` and `addr.sun_path` are not overlapping and
480+
// both point to valid memory.
481+
// `storage` was initialized to zero above, so the path is
482+
// already null terminated.
477483
unsafe {
478-
SockAddr::try_init(|storage, len| {
479-
// Safety: `SockAddr::try_init` zeros the address, which is a
480-
// valid representation.
481-
let storage: &mut libc::sockaddr_un = unsafe { &mut *storage.cast() };
482-
let len: &mut socklen_t = unsafe { &mut *len };
483-
484-
let bytes = path.as_ref().as_os_str().as_bytes();
485-
let too_long = match bytes.first() {
486-
None => false,
487-
// linux abstract namespaces aren't null-terminated
488-
Some(&0) => bytes.len() > storage.sun_path.len(),
489-
Some(_) => bytes.len() >= storage.sun_path.len(),
490-
};
491-
if too_long {
492-
return Err(io::Error::new(
493-
io::ErrorKind::InvalidInput,
494-
"path must be shorter than SUN_LEN",
495-
));
496-
}
484+
ptr::copy_nonoverlapping(
485+
bytes.as_ptr(),
486+
storage.sun_path.as_mut_ptr() as *mut u8,
487+
bytes.len(),
488+
)
489+
};
497490

498-
storage.sun_family = libc::AF_UNIX as sa_family_t;
499-
// Safety: `bytes` and `addr.sun_path` are not overlapping and
500-
// both point to valid memory.
501-
// `SockAddr::try_init` zeroes the memory, so the path is
502-
// already null terminated.
503-
unsafe {
504-
ptr::copy_nonoverlapping(
505-
bytes.as_ptr(),
506-
storage.sun_path.as_mut_ptr() as *mut u8,
507-
bytes.len(),
508-
)
509-
};
510-
511-
let base = storage as *const _ as usize;
512-
let path = &storage.sun_path as *const _ as usize;
513-
let sun_path_offset = path - base;
514-
let length = sun_path_offset
515-
+ bytes.len()
516-
+ match bytes.first() {
517-
Some(&0) | None => 0,
518-
Some(_) => 1,
519-
};
520-
*len = length as socklen_t;
521-
522-
Ok(())
523-
})
524-
}
525-
.map(|(_, addr)| addr)
526-
}
491+
let base = storage as *const _ as usize;
492+
let path = &storage.sun_path as *const _ as usize;
493+
let sun_path_offset = path - base;
494+
sun_path_offset
495+
+ bytes.len()
496+
+ match bytes.first() {
497+
Some(&0) | None => 0,
498+
Some(_) => 1,
499+
}
500+
};
501+
Ok(unsafe { SockAddr::new(storage, len as socklen_t) })
502+
}
527503

504+
/// Unix only API.
505+
impl SockAddr {
528506
/// Constructs a `SockAddr` with the family `AF_VSOCK` and the provided CID/port.
529507
///
530508
/// # Errors
@@ -538,9 +516,8 @@ impl SockAddr {
538516
doc(cfg(all(feature = "all", any(target_os = "android", target_os = "linux"))))
539517
)]
540518
pub fn vsock(cid: u32, port: u32) -> SockAddr {
541-
// SAFETY: a `sockaddr_storage` of all zeros is valid, hence we can
542-
// safely assume it's initialised.
543-
let mut storage = unsafe { MaybeUninit::<sockaddr_storage>::zeroed().assume_init() };
519+
// SAFETY: a `sockaddr_storage` of all zeros is valid.
520+
let mut storage = unsafe { mem::zeroed::<sockaddr_storage>() };
544521
{
545522
let storage: &mut libc::sockaddr_vm =
546523
unsafe { &mut *((&mut storage as *mut sockaddr_storage).cast()) };

src/sys/windows.rs

+46-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::net::{self, Ipv4Addr, Ipv6Addr, Shutdown};
1414
use std::os::windows::io::{
1515
AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket,
1616
};
17+
use std::path::Path;
1718
use std::sync::Once;
1819
use std::time::{Duration, Instant};
1920
use std::{process, ptr, slice};
@@ -41,8 +42,8 @@ pub(crate) const MSG_TRUNC: c_int = 0x01;
4142
// Used in `Domain`.
4243
pub(crate) const AF_INET: c_int = windows_sys::Win32::Networking::WinSock::AF_INET as c_int;
4344
pub(crate) const AF_INET6: c_int = windows_sys::Win32::Networking::WinSock::AF_INET6 as c_int;
44-
const AF_UNIX: c_int = windows_sys::Win32::Networking::WinSock::AF_UNIX as c_int;
45-
const AF_UNSPEC: c_int = windows_sys::Win32::Networking::WinSock::AF_UNSPEC as c_int;
45+
pub(crate) const AF_UNIX: c_int = windows_sys::Win32::Networking::WinSock::AF_UNIX as c_int;
46+
pub(crate) const AF_UNSPEC: c_int = windows_sys::Win32::Networking::WinSock::AF_UNSPEC as c_int;
4647
// Used in `Type`.
4748
pub(crate) const SOCK_STREAM: c_int = windows_sys::Win32::Networking::WinSock::SOCK_STREAM as c_int;
4849
pub(crate) const SOCK_DGRAM: c_int = windows_sys::Win32::Networking::WinSock::SOCK_DGRAM as c_int;
@@ -774,6 +775,49 @@ pub(crate) fn to_mreqn(
774775
}
775776
}
776777

778+
#[allow(unused_unsafe)] // TODO: replace with `unsafe_op_in_unsafe_fn` once stable.
779+
pub(crate) fn unix_sockaddr(path: &Path) -> io::Result<SockAddr> {
780+
// SAFETY: a `sockaddr_storage` of all zeros is valid.
781+
let mut storage = unsafe { mem::zeroed::<sockaddr_storage>() };
782+
let len = {
783+
let storage: &mut windows_sys::Win32::Networking::WinSock::sockaddr_un =
784+
unsafe { &mut *(&mut storage as *mut sockaddr_storage).cast() };
785+
786+
// Windows expects a UTF-8 path here even though Windows paths are
787+
// usually UCS-2 encoded. If Rust exposed OsStr's Wtf8 encoded
788+
// buffer, this could be used directly, relying on Windows to
789+
// validate the path, but Rust hides this implementation detail.
790+
//
791+
// See <https://github.com/rust-lang/rust/pull/95290>.
792+
let bytes = path
793+
.to_str()
794+
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "path must be valid UTF-8"))?
795+
.as_bytes();
796+
797+
// Windows appears to allow non-null-terminated paths, but this is
798+
// not documented, so do not rely on it yet.
799+
//
800+
// See <https://github.com/rust-lang/socket2/issues/331>.
801+
if bytes.len() >= storage.sun_path.len() {
802+
return Err(io::Error::new(
803+
io::ErrorKind::InvalidInput,
804+
"path must be shorter than SUN_LEN",
805+
));
806+
}
807+
808+
storage.sun_family = crate::sys::AF_UNIX as sa_family_t;
809+
// `storage` was initialized to zero above, so the path is
810+
// already null terminated.
811+
storage.sun_path[..bytes.len()].copy_from_slice(bytes);
812+
813+
let base = storage as *const _ as usize;
814+
let path = &storage.sun_path as *const _ as usize;
815+
let sun_path_offset = path - base;
816+
sun_path_offset + bytes.len() + 1
817+
};
818+
Ok(unsafe { SockAddr::new(storage, len as socklen_t) })
819+
}
820+
777821
/// Windows only API.
778822
impl crate::Socket {
779823
/// Sets `HANDLE_FLAG_INHERIT` using `SetHandleInformation`.

tests/socket.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use std::fs::File;
1212
use std::io;
1313
#[cfg(not(target_os = "redox"))]
1414
use std::io::IoSlice;
15-
#[cfg(all(unix, feature = "all"))]
1615
use std::io::Read;
1716
use std::io::Write;
1817
use std::mem::{self, MaybeUninit};
@@ -36,7 +35,6 @@ use std::os::windows::io::AsRawSocket;
3635
use std::str;
3736
use std::thread;
3837
use std::time::Duration;
39-
#[cfg(all(unix, feature = "all"))]
4038
use std::{env, fs};
4139

4240
#[cfg(windows)]
@@ -62,7 +60,6 @@ fn domain_fmt_debug() {
6260
let tests = &[
6361
(Domain::IPV4, "AF_INET"),
6462
(Domain::IPV6, "AF_INET6"),
65-
#[cfg(unix)]
6663
(Domain::UNIX, "AF_UNIX"),
6764
#[cfg(all(feature = "all", any(target_os = "fuchsia", target_os = "linux")))]
6865
(Domain::PACKET, "AF_PACKET"),
@@ -130,7 +127,6 @@ fn from_invalid_raw_fd_should_panic() {
130127
}
131128

132129
#[test]
133-
#[cfg(all(unix, feature = "all"))]
134130
fn socket_address_unix() {
135131
let string = "/tmp/socket";
136132
let addr = SockAddr::unix(string).unwrap();
@@ -429,9 +425,29 @@ fn pair() {
429425
assert_eq!(&buf[..n], DATA);
430426
}
431427

428+
fn unix_sockets_supported() -> bool {
429+
#[cfg(windows)]
430+
{
431+
// Only some versions of Windows support Unix sockets.
432+
match Socket::new(Domain::UNIX, Type::STREAM, None) {
433+
Ok(_) => {}
434+
Err(err)
435+
if err.raw_os_error()
436+
== Some(windows_sys::Win32::Networking::WinSock::WSAEAFNOSUPPORT as i32) =>
437+
{
438+
return false;
439+
}
440+
Err(err) => panic!("socket error: {}", err),
441+
}
442+
}
443+
true
444+
}
445+
432446
#[test]
433-
#[cfg(all(feature = "all", unix))]
434447
fn unix() {
448+
if !unix_sockets_supported() {
449+
return;
450+
}
435451
let mut path = env::temp_dir();
436452
path.push("socket2");
437453
let _ = fs::remove_dir_all(&path);

0 commit comments

Comments
 (0)