diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 20ca0d1c..f3b7fce3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -83,6 +83,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 4d39b05d..6f8a4a4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -516,6 +516,7 @@ impl TcpKeepalive { target_os = "tvos", target_os = "watchos", target_os = "windows", + target_os = "cygwin", ))] pub const fn with_interval(self, interval: Duration) -> Self { Self { @@ -543,6 +544,7 @@ impl TcpKeepalive { target_os = "netbsd", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ) ))] pub const fn with_retries(self, retries: u32) -> Self { diff --git a/src/socket.rs b/src/socket.rs index 7f2a1edf..b6e92f54 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -200,6 +200,9 @@ 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`. pub fn connect(&self, address: &SockAddr) -> io::Result<()> { sys::connect(self.as_raw(), address) } @@ -260,6 +263,11 @@ 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`. pub fn accept(&self) -> io::Result<(Socket, SockAddr)> { // Use `accept4` on platforms that support it. #[cfg(any( @@ -271,6 +279,7 @@ impl Socket { target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "cygwin", ))] return self._accept4(libc::SOCK_CLOEXEC); @@ -284,6 +293,7 @@ impl Socket { target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "cygwin", )))] { let (socket, addr) = self.accept_raw()?; @@ -752,6 +762,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(); @@ -781,6 +792,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)?; @@ -956,7 +968,7 @@ impl Socket { /// For more information about this option, see [`set_passcred`]. /// /// [`set_passcred`]: Socket::set_passcred - #[cfg(all(unix, target_os = "linux"))] + #[cfg(all(unix, any(target_os = "linux", target_os = "cygwin")))] pub fn passcred(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_PASSCRED) @@ -968,7 +980,7 @@ impl Socket { /// /// If this option is enabled, enables the receiving of the `SCM_CREDENTIALS` /// control messages. - #[cfg(all(unix, target_os = "linux"))] + #[cfg(all(unix, any(target_os = "linux", target_os = "cygwin")))] pub fn set_passcred(&self, passcred: bool) -> io::Result<()> { unsafe { setsockopt( @@ -1254,6 +1266,7 @@ impl Socket { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub fn join_multicast_v4_n( &self, @@ -1287,6 +1300,7 @@ impl Socket { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub fn leave_multicast_v4_n( &self, @@ -1577,6 +1591,7 @@ impl Socket { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub fn set_recv_tos_v4(&self, recv_tos: bool) -> io::Result<()> { unsafe { @@ -1608,6 +1623,7 @@ impl Socket { target_os = "nto", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )))] pub fn recv_tos_v4(&self) -> io::Result { unsafe { @@ -1978,6 +1994,7 @@ impl Socket { target_os = "hurd", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )) ))] pub fn recv_hoplimit_v6(&self) -> io::Result { @@ -2006,6 +2023,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<()> { @@ -2063,6 +2081,7 @@ impl Socket { target_os = "netbsd", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ) ))] pub fn keepalive_interval(&self) -> io::Result { @@ -2092,6 +2111,7 @@ impl Socket { target_os = "netbsd", target_os = "tvos", target_os = "watchos", + target_os = "cygwin", ) ))] pub fn keepalive_retries(&self) -> io::Result { diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 8e24f4e5..bc808f11 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}; @@ -140,6 +141,7 @@ pub(crate) use libc::IPV6_HDRINCL; target_os = "haiku", target_os = "espidf", target_os = "vita", + target_os = "cygwin", )) ))] pub(crate) use libc::IPV6_RECVHOPLIMIT; @@ -173,6 +175,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( @@ -199,7 +202,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, @@ -271,6 +274,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}; @@ -278,6 +282,8 @@ pub(crate) use libc::{TCP_KEEPCNT, TCP_KEEPINTVL}; // See this type in the Windows file. pub(crate) type Bool = c_int; +#[cfg(target_os = "cygwin")] +use libc::SO_PEERCRED; #[cfg(any( target_os = "ios", target_os = "visionos", @@ -320,6 +326,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; @@ -337,6 +344,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; @@ -383,6 +391,7 @@ type IovLen = usize; target_os = "watchos", target_os = "espidf", target_os = "vita", + target_os = "cygwin", ))] type IovLen = c_int; @@ -425,7 +434,8 @@ impl Type { target_os = "illumos", target_os = "linux", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_os = "cygwin", ) ))] pub const fn nonblocking(self) -> Type { @@ -447,6 +457,7 @@ impl Type { target_os = "openbsd", target_os = "redox", target_os = "solaris", + target_os = "cygwin", ) ))] pub const fn cloexec(self) -> Type { @@ -465,6 +476,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) @@ -578,7 +590,10 @@ 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"), + ))] pub const fn is_dontroute(self) -> bool { self.0 & libc::MSG_DONTROUTE != 0 } @@ -595,7 +610,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() } @@ -767,7 +785,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() @@ -840,14 +858,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 } } @@ -1200,6 +1218,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 { @@ -1333,6 +1352,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, @@ -1414,6 +1434,7 @@ impl crate::Socket { target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "cygwin", ) ))] pub fn accept4(&self, flags: c_int) -> io::Result<(crate::Socket, SockAddr)> { @@ -1429,6 +1450,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. @@ -1479,6 +1501,28 @@ 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. Therefore, `connect` blocks until + /// the server `accept`s it. This option turns off the handshake. Both client + /// and server should turn it off to let `connect` returns immediately. + /// + /// See also: the [mailing list](https://inbox.sourceware.org/cygwin/TYCPR01MB10926FF8926CA63704867ADC8F8AA2@TYCPR01MB10926.jpnprd01.prod.outlook.com/) + #[cfg(target_os = "cygwin")] + pub fn set_no_peercred(&self) -> io::Result<()> { + syscall!(setsockopt( + self.as_raw(), + SOL_SOCKET, + SO_PEERCRED, + ptr::null_mut(), + 0, + )) + .map(|_| ()) + } + /// Sets `SO_NOSIGPIPE` on the socket. #[cfg(all( feature = "all", @@ -1551,6 +1595,7 @@ impl crate::Socket { target_os = "freebsd", target_os = "fuchsia", target_os = "linux", + target_os = "cygwin", ) ))] pub fn is_listener(&self) -> io::Result { @@ -1680,7 +1725,12 @@ 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", + ) ))] pub fn quickack(&self) -> io::Result { unsafe { @@ -1697,7 +1747,12 @@ 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", + ) ))] pub fn set_quickack(&self, quickack: bool) -> io::Result<()> { unsafe { @@ -1956,7 +2011,7 @@ 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")) ))] pub fn reuse_port(&self) -> io::Result { unsafe { @@ -1972,7 +2027,7 @@ 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")) ))] pub fn set_reuse_port(&self, reuse: bool) -> io::Result<()> { unsafe { @@ -2272,7 +2327,12 @@ 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", + ) ))] pub fn set_tcp_user_timeout(&self, timeout: Option) -> io::Result<()> { let timeout = timeout.map_or(0, |to| { @@ -2295,7 +2355,12 @@ 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", + ) ))] pub fn tcp_user_timeout(&self) -> io::Result> { unsafe { @@ -2369,7 +2434,8 @@ impl crate::Socket { target_os = "linux", target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_os = "cygwin", ) ))] pub fn tclass_v6(&self) -> io::Result { @@ -2393,7 +2459,8 @@ impl crate::Socket { target_os = "linux", target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_os = "cygwin", ) ))] pub fn set_tclass_v6(&self, tclass: u32) -> io::Result<()> { diff --git a/tests/socket.rs b/tests/socket.rs index a2dca668..77e30d35 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_v4, set_ttl_v4(40)); target_os = "solaris", target_os = "illumos", target_os = "haiku", + target_os = "cygwin", )))] test!(IPv4 tos_v4, set_tos_v4(96)); @@ -1428,10 +1446,11 @@ test!(IPv4 tos_v4, set_tos_v4(96)); target_os = "windows", target_os = "vita", target_os = "haiku", + target_os = "cygwin", )))] test!(IPv4 recv_tos_v4, set_recv_tos_v4(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();