diff --git a/src/socket.rs b/src/socket.rs index 8c164365..37d60023 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -50,41 +50,6 @@ use crate::{Domain, Protocol, SockAddr, Type}; /// can lead to a data race when two threads are changing options in parallel. /// /// # Examples -/// -/// Creating a new socket setting all advisable flags. -/// -#[cfg_attr(feature = "all", doc = "```")] // Protocol::cloexec requires the `all` feature. -#[cfg_attr(not(feature = "all"), doc = "```ignore")] -/// # fn main() -> std::io::Result<()> { -/// use socket2::{Protocol, Domain, Type, Socket}; -/// -/// let domain = Domain::IPV4; -/// let ty = Type::STREAM; -/// let protocol = Protocol::TCP; -/// -/// // On platforms that support it set `SOCK_CLOEXEC`. -/// #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "netbsd", target_os = "openbsd"))] -/// let ty = ty.cloexec(); -/// -/// // On windows set `WSA_FLAG_NO_HANDLE_INHERIT`. -/// #[cfg(windows)] -/// let ty = ty.no_inherit(); -/// -/// let socket = Socket::new(domain, ty, Some(protocol))?; -/// -/// // On platforms that don't support `SOCK_CLOEXEC`, use `FD_CLOEXEC`. -/// #[cfg(all(not(windows), not(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "netbsd", target_os = "openbsd"))))] -/// socket.set_cloexec(true)?; -/// -/// // On macOS and iOS set `NOSIGPIPE`. -/// #[cfg(target_vendor = "apple")] -/// socket.set_nosigpipe(true)?; -/// -/// # drop(socket); -/// # Ok(()) -/// # } -/// ``` -/// /// ```no_run /// # fn main() -> std::io::Result<()> { /// use std::net::{SocketAddr, TcpListener}; @@ -112,27 +77,51 @@ pub struct Socket { impl Socket { /// Creates a new socket ready to be configured. /// - /// This function corresponds to `socket(2)` on Unix and `WSASocketW` on - /// Windows and simply creates a new socket, no other configuration is done - /// and further functions must be invoked to configure this socket. + /// This function corresponds to `socket(2)` on Unix and `WSASocketW` on Windows. /// /// # Notes /// - /// The standard library sets the `CLOEXEC` flag on Unix on sockets, this - /// function does **not** do this, but its advisable. On supported platforms - /// [`Type::cloexec`] can be used for this, or by using - /// [`Socket::set_cloexec`]. - /// - /// Furthermore on macOS and iOS `NOSIGPIPE` is not set, this can be done - /// using [`Socket::set_nosigpipe`]. + /// On Unix-like systems, the close-on-exec flag is set on the new socket. + /// Additionally, on macOS X and iOS the `SOCK_NOSIGPIPE` option is set. + /// On Windows, the socket handle is made non-inheritable. /// - /// Similarly on Windows the `HANDLE_FLAG_INHERIT` is **not** set to zero, - /// but again in most cases its advisable to do so. This can be doing using - /// [`Socket::set_no_inherit`]. + /// If possible, the close-on-exec flag will be set atomically when the socket is created. + /// However, on some platforms the flag is to be set with a separate syscall, + /// which leaves a small window for race conditions. /// - /// See the `Socket` documentation for a full example of setting all the - /// above mentioned flags. + /// Use [`Socket::new_raw`] if you don't want these flags and options to be set. pub fn new(domain: Domain, ty: Type, protocol: Option) -> io::Result { + // On platforms that support it set `SOCK_CLOEXEC` atomically. + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + ))] + let ty = ty._cloexec(); + + // On windows set `NO_HANDLE_INHERIT` atomically. + #[cfg(windows)] + let ty = ty._no_inherit(); + + set_common_flags(Socket::new_raw(domain, ty, protocol)?) + } + + /// Creates a new socket ready to be configured. + /// + /// This function corresponds to `socket(2)` on Unix and `WSASocketW` on + /// Windows and simply creates a new socket, no other configuration is done + /// and further functions must be invoked to configure this socket. + /// + /// # Notes + /// + /// This function does not set the close-on-exec flag or the `SOCK_SIGNOPIPE` option + /// for the created socket. On Windows, the created socket handle will be inheritable. + /// You should normally use [`Socket::new`], which creates sockets with the advised + /// flags and options for the current platform. + pub fn new_raw(domain: Domain, ty: Type, protocol: Option) -> io::Result { let protocol = protocol.map(|p| p.0).unwrap_or(0); sys::socket(domain.0, ty.0, protocol).map(|inner| Socket { inner }) } @@ -143,8 +132,15 @@ impl Socket { /// /// # Notes /// - /// Much like [`Socket::new`] this doesn't set any flags, which might be - /// advisable. + /// On Unix-like systems, the close-on-exec flag is set on the new sockets. + /// Additionally, on macOS X and iOS the `SOCK_NOSIGPIPE` option is set. + /// On Windows, the socket handles are made non-inheritable. + /// + /// If possible, the close-on-exec flag will be set atomically when the socket is created. + /// However, on some platforms the flag is to be set with a separate syscall, + /// which leaves a small window for race conditions. + /// + /// Use [`Socket::pair_raw`] if you don't want these flags and options to be set. /// /// This function is only available on Unix. #[cfg(all(feature = "all", unix))] @@ -152,6 +148,39 @@ impl Socket { domain: Domain, ty: Type, protocol: Option, + ) -> io::Result<(Socket, Socket)> { + // On platforms that support it set `SOCK_CLOEXEC` atomically. + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + ))] + let ty = ty._cloexec(); + + let (a, b) = Socket::pair_raw(domain, ty, protocol)?; + Ok((set_common_flags(a)?, set_common_flags(b)?)) + } + + /// Creates a pair of sockets which are connected to each other. + /// + /// This function corresponds to `socketpair(2)`. + /// + /// # Notes + /// + /// This function does not set the close-on-exec flag or the `SOCK_SIGNOPIPE` option + /// for the created sockets. You should normally use [`Socket::pair`], + /// which creates sockets with the advised + /// flags and options for the current platform. + /// + /// This function is only available on Unix. + #[cfg(all(feature = "all", unix))] + pub fn pair_raw( + domain: Domain, + ty: Type, + protocol: Option, ) -> io::Result<(Socket, Socket)> { let protocol = protocol.map(|p| p.0).unwrap_or(0); sys::socketpair(domain.0, ty.0, protocol) @@ -196,6 +225,50 @@ impl Socket { sys::listen(self.inner, backlog) } + /// Accept a new incoming connection from this listener. + /// + /// This function corresponds to the `accept(2)` function on + /// Windows and Unix. + /// + /// # Notes + /// + /// On Unix-like systems, the close-on-exec flag is set on the new socket. + /// Additionally, on macOS X and iOS the `SOCK_NOSIGPIPE` option is set. + /// On Windows, the socket handle is made non-inheritable. + /// + /// If possible, the close-on-exec flag will be set atomically when the socket is created. + /// However, on some platforms the flag is to be set with a separate syscall, + /// which leaves a small window for race conditions. + /// + /// Use [`Socket::accept_raw`] if you don't want these flags and options to be set. + pub fn accept(&self) -> io::Result<(Socket, SockAddr)> { + // On platforms that support it set `SOCK_CLOEXEC` atomically. + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + ))] + let (sock, addr) = self._accept4(libc::SOCK_CLOEXEC)?; + + #[cfg(not(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + )))] + let (sock, addr) = self.accept_raw()?; + + #[cfg(windows)] + sock._set_no_inherit(true)?; + + Ok((sock, addr)) + } + /// Accept a new incoming connection from this listener. /// /// This function directly corresponds to the `accept(2)` function on @@ -207,10 +280,11 @@ impl Socket { /// /// # Notes /// - /// Like [`Socket::new`] this will not set any flags. If that is desirable, - /// e.g. setting `CLOEXEC`, [`Socket::accept4`] can be used on supported - /// OSes or [`Socket::set_cloexec`] can be called. - pub fn accept(&self) -> io::Result<(Socket, SockAddr)> { + /// This function does not set the close-on-exec flag or the `SOCK_SIGNOPIPE` option + /// for the created socket. On Windows, the created socket handle will be inheritable. + /// You should normally use [`Socket::accept_raw`], which creates sockets with the advisable + /// flags and options for the current platform. + pub fn accept_raw(&self) -> io::Result<(Socket, SockAddr)> { sys::accept(self.inner).map(|(inner, addr)| (Socket { inner }, addr)) } @@ -1017,6 +1091,30 @@ impl Drop for Socket { } } +/// Fix-up a socket after creation by setting common platform specific flags. +#[allow(unused)] +fn set_common_flags(mut sock: Socket) -> io::Result { + // On unix platforms that can't set CLOEXEC atomically, set it here. + #[cfg(all( + unix, + not(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + )) + ))] + sock._set_cloexec(true)?; + + // On Apple platforms, set the NOSIGPIPE socket option. + #[cfg(target_vendor = "apple")] + sock._set_nosigpipe(true)?; + + Ok(sock) +} + #[cfg(test)] mod test { use std::net::SocketAddr; diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 2b02309c..178a1f3e 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -167,6 +167,18 @@ impl Type { ) ))] pub const fn cloexec(self) -> Type { + self._cloexec() + } + + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub(crate) const fn _cloexec(self) -> Type { Type(self.0 | libc::SOCK_CLOEXEC) } } @@ -396,24 +408,32 @@ impl crate::Socket { ) ))] pub fn accept4(&self, flags: c_int) -> io::Result<(crate::Socket, SockAddr)> { + self._accept4(flags) + } + + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub(crate) fn _accept4(&self, flags: c_int) -> io::Result<(crate::Socket, SockAddr)> { + // Safety: zeroed `sockaddr_storage` is valid. let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() }; let mut len = mem::size_of_val(&storage) as socklen_t; - - let res = syscall!(accept4( + syscall!(accept4( self.inner, &mut storage as *mut _ as *mut _, &mut len, - flags, - )); - match res { - Ok(inner) => { - let socket = crate::Socket { inner }; - let addr = - unsafe { SockAddr::from_raw_parts(&storage as *const _ as *const _, len) }; - Ok((socket, addr)) - } - Err(e) => Err(e), - } + flags + )) + .map(|inner| { + let addr = unsafe { SockAddr::from_raw_parts(&storage as *const _ as *const _, len) }; + (crate::Socket { inner }, addr) + }) } /// Sets `CLOEXEC` on the socket. @@ -421,7 +441,12 @@ impl crate::Socket { /// # Notes /// /// On supported platforms you can use [`Protocol::cloexec`]. + #[cfg(feature = "all")] pub fn set_cloexec(&self, close_on_exec: bool) -> io::Result<()> { + self._set_cloexec(close_on_exec) + } + + pub(crate) fn _set_cloexec(&self, close_on_exec: bool) -> io::Result<()> { if close_on_exec { fcntl_add(self.inner, libc::F_GETFD, libc::F_SETFD, libc::FD_CLOEXEC) } else { @@ -436,12 +461,17 @@ impl crate::Socket { /// Only supported on Apple platforms (`target_vendor = "apple"`). #[cfg(all(feature = "all", target_vendor = "apple"))] pub fn set_nosigpipe(&self, nosigpipe: bool) -> io::Result<()> { + self._set_nosigpipe(nosigpipe) + } + + #[cfg(target_vendor = "apple")] + pub(crate) fn _set_nosigpipe(&self, nosigpipe: bool) -> io::Result<()> { unsafe { - setsockopt::( + setsockopt( self.inner, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, - nosigpipe as _, + nosigpipe as c_int, ) } } @@ -490,7 +520,7 @@ unsafe fn getsockopt(fd: SysSocket, opt: c_int, val: c_int) -> io::Result } /// Caller must ensure `T` is the correct type for `opt` and `val`. -#[cfg(all(feature = "all", target_vendor = "apple"))] +#[cfg(target_vendor = "apple")] unsafe fn setsockopt(fd: SysSocket, opt: c_int, val: c_int, payload: T) -> io::Result<()> { let payload = &payload as *const T as *const c_void; syscall!(setsockopt( @@ -503,15 +533,6 @@ unsafe fn setsockopt(fd: SysSocket, opt: c_int, val: c_int, payload: T) -> io .map(|_| ()) } -/* - setsockopt::( - self.inner, - libc::SOL_SOCKET, - libc::SO_NOSIGPIPE, - nosigpipe as _, - ) -*/ - #[repr(transparent)] // Required during rewriting. pub struct Socket { fd: SysSocket, @@ -1018,6 +1039,12 @@ impl Socket { } } +impl Drop for Socket { + fn drop(&mut self) { + close(self.fd); + } +} + impl Read for Socket { fn read(&mut self, buf: &mut [u8]) -> io::Result { <&Socket>::read(&mut &*self, buf) @@ -1085,7 +1112,7 @@ impl IntoRawFd for Socket { impl FromRawFd for Socket { unsafe fn from_raw_fd(fd: c_int) -> Socket { - Socket { fd: fd } + Socket { fd } } } @@ -1105,9 +1132,7 @@ impl IntoRawFd for crate::Socket { impl FromRawFd for crate::Socket { unsafe fn from_raw_fd(fd: c_int) -> crate::Socket { - crate::Socket { - inner: Socket::from_raw_fd(fd).inner(), - } + crate::Socket { inner: fd } } } diff --git a/src/sys/windows.rs b/src/sys/windows.rs index fa109be1..9d3c0e5b 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -93,6 +93,10 @@ impl Type { /// Set `WSA_FLAG_NO_HANDLE_INHERIT` on the socket. #[cfg(feature = "all")] pub const fn no_inherit(self) -> Type { + self._no_inherit() + } + + pub(crate) const fn _no_inherit(self) -> Type { Type(self.0 | Type::NO_INHERIT) } } @@ -297,7 +301,12 @@ fn ioctlsocket(socket: SysSocket, cmd: c_long, payload: &mut u_long) -> io::Resu /// Windows only API. impl crate::Socket { /// Sets `HANDLE_FLAG_INHERIT` to zero using `SetHandleInformation`. + #[cfg(feature = "all")] pub fn set_no_inherit(&self, no_inherit: bool) -> io::Result<()> { + self._set_no_inherit(no_inherit) + } + + pub(crate) fn _set_no_inherit(&self, no_inherit: bool) -> io::Result<()> { // NOTE: can't use `syscall!` because it expects the function in the // `sock::` path. let res = unsafe { @@ -882,6 +891,12 @@ impl Socket { } } +impl Drop for Socket { + fn drop(&mut self) { + close(self.socket); + } +} + impl Read for Socket { fn read(&mut self, buf: &mut [u8]) -> io::Result { <&Socket>::read(&mut &*self, buf) @@ -967,7 +982,7 @@ impl IntoRawSocket for crate::Socket { impl FromRawSocket for crate::Socket { unsafe fn from_raw_socket(socket: RawSocket) -> crate::Socket { crate::Socket { - inner: Socket::from_raw_socket(socket).inner(), + inner: socket as SysSocket, } } } diff --git a/tests/socket.rs b/tests/socket.rs index 5b10a8ea..973ff10b 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1,4 +1,4 @@ -#[cfg(any(windows, all(feature = "all", target_vendor = "apple")))] +#[cfg(any(windows, target_vendor = "apple"))] use std::io; #[cfg(unix)] use std::os::unix::io::AsRawFd; @@ -26,6 +26,28 @@ fn set_nonblocking() { assert_nonblocking(&socket, false); } +#[test] +fn default_flags() { + let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); + #[cfg(unix)] + assert_close_on_exec(&socket, true); + #[cfg(target_vendor = "apple")] + assert_flag_no_sigpipe(&socket, true); + #[cfg(windows)] + assert_flag_no_inherit(&socket, true); +} + +#[test] +fn no_default_flags() { + let socket = Socket::new_raw(Domain::IPV4, Type::STREAM, None).unwrap(); + #[cfg(unix)] + assert_close_on_exec(&socket, false); + #[cfg(target_vendor = "apple")] + assert_flag_no_sigpipe(&socket, false); + #[cfg(windows)] + assert_flag_no_inherit(&socket, false); +} + #[cfg(all( feature = "all", any( @@ -61,17 +83,17 @@ pub fn assert_nonblocking(_: &S, _: bool) { // No way to get this information... } -#[cfg(unix)] +#[cfg(all(unix, feature = "all"))] #[test] fn set_cloexec() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); - assert_close_on_exec(&socket, false); - - socket.set_cloexec(true).unwrap(); assert_close_on_exec(&socket, true); socket.set_cloexec(false).unwrap(); assert_close_on_exec(&socket, false); + + socket.set_cloexec(true).unwrap(); + assert_close_on_exec(&socket, true); } #[cfg(all( @@ -103,17 +125,17 @@ where assert_eq!(flags & libc::FD_CLOEXEC != 0, want, "CLOEXEC option"); } -#[cfg(windows)] +#[cfg(all(windows, feature = "all"))] #[test] fn set_no_inherit() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); - assert_flag_no_inherit(&socket, false); - - socket.set_no_inherit(true).unwrap(); assert_flag_no_inherit(&socket, true); socket.set_no_inherit(false).unwrap(); assert_flag_no_inherit(&socket, false); + + socket.set_no_inherit(true).unwrap(); + assert_flag_no_inherit(&socket, true); } #[cfg(all(feature = "all", windows))] @@ -147,17 +169,17 @@ where #[test] fn set_nosigpipe() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); - assert_flag_no_sigpipe(&socket, false); - - socket.set_nosigpipe(true).unwrap(); assert_flag_no_sigpipe(&socket, true); socket.set_nosigpipe(false).unwrap(); assert_flag_no_sigpipe(&socket, false); + + socket.set_nosigpipe(true).unwrap(); + assert_flag_no_sigpipe(&socket, true); } /// Assert that `SO_NOSIGPIPE` is set on `socket`. -#[cfg(all(feature = "all", target_vendor = "apple"))] +#[cfg(target_vendor = "apple")] #[track_caller] pub fn assert_flag_no_sigpipe(socket: &S, want: bool) where