diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7a5126d2..10987177 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -81,6 +81,7 @@ jobs: - sparcv9-sun-solaris - x86_64-apple-darwin - x86_64-apple-ios + - x86_64-pc-cygwin - x86_64-pc-solaris # Fails with: # `rror calling dlltool 'x86_64-w64-mingw32-dlltool': No such file or diff --git a/src/lib.rs b/src/lib.rs index 8f593163..4ebd5288 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -522,6 +522,7 @@ impl TcpKeepalive { target_os = "tvos", target_os = "watchos", target_os = "windows", + target_os = "cygwin", ))] #[cfg_attr( docsrs, @@ -567,6 +568,7 @@ impl TcpKeepalive { target_os = "netbsd", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ) ))] #[cfg_attr( diff --git a/src/socket.rs b/src/socket.rs index 698d3556..38bb1062 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -202,6 +202,11 @@ impl Socket { /// non-blocking mode before calling this function), socket option can't be /// set *while connecting*. This will cause errors on Windows. Socket /// options can be safely set before and after connecting the socket. + /// + /// On Cygwin, a Unix domain socket connect blocks until the server accepts + /// it. If the behavior is not expected, try [`Socket::set_no_peercred`] + /// (Cygwin only). + #[allow(rustdoc::broken_intra_doc_links)] // Socket::set_no_peercred pub fn connect(&self, address: &SockAddr) -> io::Result<()> { sys::connect(self.as_raw(), address) } @@ -262,6 +267,13 @@ impl Socket { /// This function sets the same flags as in done for [`Socket::new`], /// [`Socket::accept_raw`] can be used if you don't want to set those flags. #[doc = man_links!(accept(2))] + /// + /// # Notes + /// + /// On Cygwin, a Unix domain socket connect blocks until the server accepts + /// it. If the behavior is not expected, try [`Socket::set_no_peercred`] + /// (Cygwin only). + #[allow(rustdoc::broken_intra_doc_links)] // Socket::set_no_peercred pub fn accept(&self) -> io::Result<(Socket, SockAddr)> { // Use `accept4` on platforms that support it. #[cfg(any( @@ -273,6 +285,7 @@ impl Socket { target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "cygwin", ))] return self._accept4(libc::SOCK_CLOEXEC); @@ -286,6 +299,7 @@ impl Socket { target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "cygwin", )))] { let (socket, addr) = self.accept_raw()?; @@ -765,6 +779,7 @@ const fn set_common_type(ty: Type) -> Type { target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "cygwin", ))] let ty = ty._cloexec(); @@ -794,6 +809,7 @@ fn set_common_flags(socket: Socket) -> io::Result { target_os = "openbsd", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )) ))] socket._set_cloexec(true)?; @@ -971,8 +987,8 @@ impl Socket { /// For more information about this option, see [`set_passcred`]. /// /// [`set_passcred`]: Socket::set_passcred - #[cfg(all(unix, target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all(unix, target_os = "linux"))))] + #[cfg(any(target_os = "linux", target_os = "cygwin"))] + #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "cygwin"))))] pub fn passcred(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_PASSCRED) @@ -984,8 +1000,8 @@ impl Socket { /// /// If this option is enabled, enables the receiving of the `SCM_CREDENTIALS` /// control messages. - #[cfg(all(unix, target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all(unix, target_os = "linux"))))] + #[cfg(any(target_os = "linux", target_os = "cygwin"))] + #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "cygwin"))))] pub fn set_passcred(&self, passcred: bool) -> io::Result<()> { unsafe { setsockopt( @@ -1306,6 +1322,7 @@ impl Socket { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub fn join_multicast_v4_n( &self, @@ -1339,6 +1356,7 @@ impl Socket { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub fn leave_multicast_v4_n( &self, @@ -1631,6 +1649,7 @@ impl Socket { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub fn set_recv_tos(&self, recv_tos: bool) -> io::Result<()> { unsafe { @@ -1662,6 +1681,7 @@ impl Socket { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub fn recv_tos(&self) -> io::Result { unsafe { @@ -2039,6 +2059,7 @@ impl Socket { target_os = "hurd", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )) ))] pub fn recv_hoplimit_v6(&self) -> io::Result { @@ -2067,6 +2088,7 @@ impl Socket { target_os = "hurd", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )) ))] pub fn set_recv_hoplimit_v6(&self, recv_hoplimit: bool) -> io::Result<()> { @@ -2136,6 +2158,7 @@ impl Socket { target_os = "netbsd", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ) ))] #[cfg_attr( @@ -2185,6 +2208,7 @@ impl Socket { target_os = "netbsd", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ) ))] #[cfg_attr( diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 2e90bced..fbe4cc3a 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -72,6 +72,7 @@ use std::{io, slice}; target_os = "macos", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", )))] use libc::ssize_t; use libc::{in6_addr, in_addr}; @@ -138,6 +139,7 @@ pub(crate) use libc::ipv6_mreq as Ipv6Mreq; target_os = "haiku", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )) ))] pub(crate) use libc::IPV6_RECVHOPLIMIT; @@ -171,6 +173,7 @@ pub(crate) use libc::IP_HDRINCL; target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub(crate) use libc::IP_RECVTOS; #[cfg(not(any( @@ -197,7 +200,7 @@ pub(crate) use libc::SO_LINGER; target_os = "watchos", ))] pub(crate) use libc::SO_LINGER_SEC as SO_LINGER; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "cygwin"))] pub(crate) use libc::SO_PASSCRED; pub(crate) use libc::{ ip_mreq as IpMreq, linger, IPPROTO_IP, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, @@ -269,6 +272,7 @@ pub(crate) use libc::{ target_os = "netbsd", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ) ))] pub(crate) use libc::{TCP_KEEPCNT, TCP_KEEPINTVL}; @@ -318,6 +322,7 @@ macro_rules! syscall { target_os = "macos", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", )))] const MAX_BUF_LEN: usize = ssize_t::MAX as usize; @@ -335,6 +340,7 @@ const MAX_BUF_LEN: usize = ssize_t::MAX as usize; target_os = "macos", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ))] const MAX_BUF_LEN: usize = c_int::MAX as usize - 1; @@ -381,6 +387,7 @@ type IovLen = usize; target_os = "watchos", target_os = "espidf", target_os = "vita", + target_os = "cygwin", ))] type IovLen = c_int; @@ -439,7 +446,8 @@ impl Type { target_os = "illumos", target_os = "linux", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_os = "cygwin", ) ))] #[cfg_attr( @@ -477,6 +485,7 @@ impl Type { target_os = "openbsd", target_os = "redox", target_os = "solaris", + target_os = "cygwin", ) ))] #[cfg_attr( @@ -514,6 +523,7 @@ impl Type { target_os = "openbsd", target_os = "redox", target_os = "solaris", + target_os = "cygwin", ))] pub(crate) const fn _cloexec(self) -> Type { Type(self.0 | libc::SOCK_CLOEXEC) @@ -631,10 +641,16 @@ impl RecvFlags { /// on the local host. /// /// On Unix this corresponds to the `MSG_DONTROUTE` flag. - #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] + #[cfg(all( + feature = "all", + any(target_os = "android", target_os = "linux", target_os = "cygwin"), + ))] #[cfg_attr( docsrs, - doc(cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))) + doc(cfg(all( + feature = "all", + any(target_os = "android", target_os = "linux", target_os = "cygwin") + ))) )] pub const fn is_dontroute(self) -> bool { self.0 & libc::MSG_DONTROUTE != 0 @@ -652,7 +668,10 @@ impl std::fmt::Debug for RecvFlags { s.field("is_truncated", &self.is_truncated()); #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] s.field("is_confirm", &self.is_confirm()); - #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] + #[cfg(all( + feature = "all", + any(target_os = "android", target_os = "linux", target_os = "cygwin"), + ))] s.field("is_dontroute", &self.is_dontroute()); s.finish() } @@ -832,7 +851,7 @@ impl SockAddr { // Abstract addresses only exist on Linux. // NOTE: although Fuchsia does define `AF_UNIX` it's not actually implemented. // See https://github.com/rust-lang/socket2/pull/403#discussion_r1123557978 - || (cfg!(not(any(target_os = "linux", target_os = "android"))) + || (cfg!(not(any(target_os = "linux", target_os = "android", target_os = "cygwin"))) && storage.sun_path[0] == 0) }) .unwrap_or_default() @@ -905,14 +924,14 @@ impl SockAddr { pub fn as_abstract_namespace(&self) -> Option<&[u8]> { // NOTE: although Fuchsia does define `AF_UNIX` it's not actually implemented. // See https://github.com/rust-lang/socket2/pull/403#discussion_r1123557978 - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(any(target_os = "linux", target_os = "android", target_os = "cygwin"))] { self.as_sockaddr_un().and_then(|storage| { (self.len() > offset_of_path(storage) as _ && storage.sun_path[0] == 0) .then(|| self.path_bytes(storage, true)) }) } - #[cfg(not(any(target_os = "linux", target_os = "android")))] + #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "cygwin")))] None } } @@ -1273,6 +1292,7 @@ pub(crate) fn set_tcp_keepalive(fd: Socket, keepalive: &TcpKeepalive) -> io::Res target_os = "netbsd", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ))] { if let Some(interval) = keepalive.interval { @@ -1406,6 +1426,7 @@ pub(crate) fn from_in6_addr(addr: in6_addr) -> Ipv6Addr { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub(crate) const fn to_mreqn( multiaddr: &Ipv4Addr, @@ -1487,6 +1508,7 @@ impl crate::Socket { target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "cygwin", ) ))] #[cfg_attr( @@ -1518,6 +1540,7 @@ impl crate::Socket { target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "cygwin", ))] pub(crate) fn _accept4(&self, flags: c_int) -> io::Result<(crate::Socket, SockAddr)> { // Safety: `accept4` initialises the `SockAddr` for us. @@ -1569,6 +1592,31 @@ impl crate::Socket { } } + /// Sets `SO_PEERCRED` to null on the socket. + /// + /// This is a Cygwin extension. + /// + /// Normally the Unix domain sockets of Cygwin are implemented by TCP sockets, + /// so it performs a handshake on `connect` and `accept` to verify the remote + /// connection and exchange peer cred info. At the time of writing, this + /// means that `connect` on a Unix domain socket will block until the server + /// calls `accept` on Cygwin. This behavior is inconsistent with most other + /// platforms, and this option can be used to disable that. + /// + /// See also: the [mailing list](https://inbox.sourceware.org/cygwin/TYCPR01MB10926FF8926CA63704867ADC8F8AA2@TYCPR01MB10926.jpnprd01.prod.outlook.com/) + #[cfg(target_os = "cygwin")] + #[cfg(any(doc, target_os = "cygwin"))] + pub fn set_no_peercred(&self) -> io::Result<()> { + syscall!(setsockopt( + self.as_raw(), + libc::SOL_SOCKET, + libc::SO_PEERCRED, + ptr::null_mut(), + 0, + )) + .map(|_| ()) + } + /// Sets `SO_NOSIGPIPE` on the socket. #[cfg(all( feature = "all", @@ -1656,6 +1704,7 @@ impl crate::Socket { target_os = "freebsd", target_os = "fuchsia", target_os = "linux", + target_os = "cygwin", ) ))] #[cfg_attr( @@ -1848,13 +1897,23 @@ impl crate::Socket { /// [`set_quickack`]: crate::Socket::set_quickack #[cfg(all( feature = "all", - any(target_os = "android", target_os = "fuchsia", target_os = "linux") + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "cygwin", + ) ))] #[cfg_attr( docsrs, doc(cfg(all( feature = "all", - any(target_os = "android", target_os = "fuchsia", target_os = "linux") + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "cygwin" + ) ))) )] pub fn quickack(&self) -> io::Result { @@ -1872,13 +1931,23 @@ impl crate::Socket { /// internal protocol processing and factors such as delayed ack timeouts occurring and data transfer. #[cfg(all( feature = "all", - any(target_os = "android", target_os = "fuchsia", target_os = "linux") + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "cygwin", + ) ))] #[cfg_attr( docsrs, doc(cfg(all( feature = "all", - any(target_os = "android", target_os = "fuchsia", target_os = "linux") + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "cygwin" + ) ))) )] pub fn set_quickack(&self, quickack: bool) -> io::Result<()> { @@ -2279,14 +2348,14 @@ impl crate::Socket { /// [`set_reuse_port`]: crate::Socket::set_reuse_port #[cfg(all( feature = "all", - not(any(target_os = "solaris", target_os = "illumos")) + not(any(target_os = "solaris", target_os = "illumos", target_os = "cygwin")) ))] #[cfg_attr( docsrs, doc(cfg(all( feature = "all", unix, - not(any(target_os = "solaris", target_os = "illumos")) + not(any(target_os = "solaris", target_os = "illumos", target_os = "cygwin")) ))) )] pub fn reuse_port(&self) -> io::Result { @@ -2303,14 +2372,14 @@ impl crate::Socket { /// there's a socket already listening on this port. #[cfg(all( feature = "all", - not(any(target_os = "solaris", target_os = "illumos")) + not(any(target_os = "solaris", target_os = "illumos", target_os = "cygwin")) ))] #[cfg_attr( docsrs, doc(cfg(all( feature = "all", unix, - not(any(target_os = "solaris", target_os = "illumos")) + not(any(target_os = "solaris", target_os = "illumos", target_os = "cygwin")) ))) )] pub fn set_reuse_port(&self, reuse: bool) -> io::Result<()> { @@ -2650,13 +2719,23 @@ impl crate::Socket { /// approximately 49.71 days. #[cfg(all( feature = "all", - any(target_os = "android", target_os = "fuchsia", target_os = "linux") + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "cygwin", + ) ))] #[cfg_attr( docsrs, doc(cfg(all( feature = "all", - any(target_os = "android", target_os = "fuchsia", target_os = "linux") + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "cygwin" + ) ))) )] pub fn set_tcp_user_timeout(&self, timeout: Option) -> io::Result<()> { @@ -2680,13 +2759,23 @@ impl crate::Socket { /// [`set_tcp_user_timeout`]: crate::Socket::set_tcp_user_timeout #[cfg(all( feature = "all", - any(target_os = "android", target_os = "fuchsia", target_os = "linux") + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "cygwin", + ) ))] #[cfg_attr( docsrs, doc(cfg(all( feature = "all", - any(target_os = "android", target_os = "fuchsia", target_os = "linux") + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "cygwin" + ) ))) )] pub fn tcp_user_timeout(&self) -> io::Result> { @@ -2762,7 +2851,8 @@ impl crate::Socket { target_os = "linux", target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_os = "cygwin", ) ))] #[cfg_attr( @@ -2802,7 +2892,8 @@ impl crate::Socket { target_os = "linux", target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_os = "cygwin", ) ))] #[cfg_attr( diff --git a/tests/socket.rs b/tests/socket.rs index 4180f19c..b80fbb6c 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -186,7 +186,10 @@ fn socket_address_unix_unnamed() { } #[test] -#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "all"))] +#[cfg(all( + any(target_os = "linux", target_os = "android", target_os = "cygwin"), + feature = "all", +))] fn socket_address_unix_abstract_namespace() { let path = "\0h".repeat(108 / 2); let addr = SockAddr::unix(&path).unwrap(); @@ -568,10 +571,14 @@ fn unix() { let addr = SockAddr::unix(path).unwrap(); let listener = Socket::new(Domain::UNIX, Type::STREAM, None).unwrap(); + #[cfg(target_os = "cygwin")] + listener.set_no_peercred().unwrap(); listener.bind(&addr).unwrap(); listener.listen(10).unwrap(); let mut a = Socket::new(Domain::UNIX, Type::STREAM, None).unwrap(); + #[cfg(target_os = "cygwin")] + a.set_no_peercred().unwrap(); a.connect(&addr).unwrap(); let mut b = listener.accept().unwrap().0; @@ -1247,6 +1254,7 @@ fn r#type() { target_os = "tvos", target_os = "watchos", target_os = "vita", + target_os = "cygwin", )), feature = "all", ))] @@ -1356,12 +1364,21 @@ test!(out_of_band_inline, set_out_of_band_inline(true)); test!(reuse_address, set_reuse_address(true)); #[cfg(all( feature = "all", - not(any(windows, target_os = "solaris", target_os = "illumos")) + not(any( + windows, + target_os = "solaris", + target_os = "illumos", + target_os = "cygwin", + )) ))] test!(reuse_port, set_reuse_port(true)); #[cfg(all(feature = "all", target_os = "freebsd"))] test!(reuse_port_lb, set_reuse_port_lb(true)); -#[cfg(all(feature = "all", unix, not(target_os = "redox")))] +#[cfg(all( + feature = "all", + unix, + not(any(target_os = "redox", target_os = "cygwin")), +))] test!( #[cfg_attr(target_os = "linux", ignore = "Different value returned")] mss, @@ -1413,6 +1430,7 @@ test!(IPv4 ttl, set_ttl(40)); target_os = "solaris", target_os = "illumos", target_os = "haiku", + target_os = "cygwin", )))] test!(IPv4 tos, set_tos(96)); @@ -1428,10 +1446,11 @@ test!(IPv4 tos, set_tos(96)); target_os = "windows", target_os = "vita", target_os = "haiku", + target_os = "cygwin", )))] test!(IPv4 recv_tos, set_recv_tos(true)); -#[cfg(not(windows))] // TODO: returns `WSAENOPROTOOPT` (10042) on Windows. +#[cfg(not(any(windows, target_os = "cygwin")))] // TODO: returns `WSAENOPROTOOPT` (10042) on Windows. test!(IPv4 broadcast, set_broadcast(true)); #[cfg(not(target_os = "vita"))] @@ -1442,11 +1461,12 @@ test!(IPv6 unicast_hops_v6, set_unicast_hops_v6(20)); target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", - target_os = "vita" + target_os = "vita", + target_os = "cygwin", )))] test!(IPv6 only_v6, set_only_v6(true)); // IPv6 socket are already IPv6 only on FreeBSD and Windows. -#[cfg(any(windows, target_os = "freebsd"))] +#[cfg(any(windows, target_os = "freebsd", target_os = "cygwin"))] test!(IPv6 only_v6, set_only_v6(false)); #[cfg(all( @@ -1476,6 +1496,7 @@ test!(IPv6 tclass_v6, set_tclass_v6(96)); target_os = "windows", target_os = "vita", target_os = "haiku", + target_os = "cygwin", )))] test!(IPv6 recv_tclass_v6, set_recv_tclass_v6(true)); @@ -1493,6 +1514,7 @@ test!(IPv6 recv_tclass_v6, set_recv_tclass_v6(true)); target_os = "windows", target_os = "vita", target_os = "haiku", + target_os = "cygwin", )) ))] test!(IPv6 recv_hoplimit_v6, set_recv_hoplimit_v6(true)); @@ -1520,6 +1542,7 @@ test!(IPv6 multicast_all_v6, set_multicast_all_v6(false)); target_os = "redox", target_os = "solaris", target_os = "vita", + target_os = "cygwin", )))] fn join_leave_multicast_v4_n() { let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap();