Repository: alexcrichton/socket2-rs Branch: master Commit: c93d95a0140f Files: 18 Total size: 390.5 KB Directory structure: gitextract_pvnieb04/ ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── src/ │ ├── lib.rs │ ├── sockaddr.rs │ ├── socket.rs │ ├── sockref.rs │ └── sys/ │ ├── unix.rs │ └── windows.rs └── tests/ ├── data/ │ ├── hello_world.txt │ └── lorem_ipsum.txt └── socket.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: push: branches: [ master, "v0.4.x" ] pull_request: branches: [ master, "v0.4.x" ] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full jobs: Test: name: Test runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: build: [stable, msrv-linux, msrv-windows, beta, nightly, macos, windows] include: - build: stable os: ubuntu-latest rust: stable - build: msrv-linux os: ubuntu-latest rust: 1.70.0 - build: msrv-windows os: windows-latest rust: 1.70.0 - build: beta os: ubuntu-latest rust: beta - build: nightly os: ubuntu-latest rust: nightly - build: macos os: macos-latest rust: stable - build: windows os: windows-latest rust: stable steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - uses: taiki-e/install-action@cargo-hack - name: Pin windows-sys for MSRV compatibility if: matrix.os == 'windows-latest' && matrix.rust == '1.70.0' run: cargo update -p windows-sys --precise 0.60.2 - name: Run tests run: cargo hack test --feature-powerset && cargo hack test --feature-powerset --release Rustfmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Check formatting run: cargo fmt --all -- --check Check: runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: false matrix: target: - aarch64-apple-darwin - aarch64-apple-ios - aarch64-apple-tvos - aarch64-apple-visionos - aarch64-apple-watchos - aarch64-linux-android - aarch64-unknown-freebsd - aarch64-unknown-linux-gnu - aarch64-unknown-linux-musl - aarch64-unknown-linux-ohos - aarch64-unknown-netbsd - aarch64-unknown-openbsd - aarch64-unknown-redox - arm-linux-androideabi - arm64_32-apple-watchos - armv6k-nintendo-3ds - armv7-linux-androideabi - armv7-sony-vita-newlibeabihf - armv7-unknown-linux-ohos - i686-linux-android # Broken, see https://github.com/rust-lang/socket2/issues/539. #- i686-unknown-hurd-gnu - i686-unknown-linux-gnu - 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 # directory (os error 2)`, build log: # . #- x86_64-pc-windows-gnu - x86_64-pc-windows-msvc - x86_64-unknown-dragonfly - x86_64-unknown-freebsd - x86_64-unknown-fuchsia - x86_64-unknown-illumos - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl - x86_64-unknown-linux-ohos - x86_64-unknown-netbsd - x86_64-unknown-openbsd - x86_64-unknown-redox - wasm32-wasip2 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: rust-src - uses: taiki-e/install-action@cargo-hack - name: Check build run: cargo hack check -Z build-std=std,panic_abort --feature-powerset --target ${{ matrix.target }} - name: Check docs run: RUSTDOCFLAGS="-D warnings --cfg docsrs" cargo doc -Z build-std=std,panic_abort --no-deps --all-features --target ${{ matrix.target }} Clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy - name: Run Clippy run: cargo clippy --all-targets --all-features -- -D warnings CheckExternalTypes: name: check-external-types (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - windows-latest - ubuntu-latest - macos-latest rust: # `check-external-types` requires a specific Rust nightly version. See # the README for details: https://github.com/awslabs/cargo-check-external-types - nightly-2024-06-30 steps: - uses: actions/checkout@v4 - name: Install Rust ${{ matrix.rust }} uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} - name: Install cargo-check-external-types uses: taiki-e/cache-cargo-install-action@v1 with: tool: cargo-check-external-types@0.1.13 locked: true - name: check-external-types run: cargo check-external-types --all-features vmactions: strategy: fail-fast: false matrix: include: - target: x86_64-unknown-freebsd arch: x86_64 - target: i686-unknown-freebsd arch: i686 name: FreeBSD/${{ matrix.arch }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Test on FreeBSD uses: vmactions/freebsd-vm@v1 with: release: "14.4" usesh: true prepare: | pkg install -y curl cargo-hack kldload cc_newreno # For the tcp_congestion test fetch https://sh.rustup.rs -o rustup.sh sh rustup.sh -y --profile=minimal . $HOME/.cargo/env rustup target add ${{ matrix.target }} run: | . $HOME/.cargo/env cargo hack test --target ${{ matrix.target }} --feature-powerset ================================================ FILE: .gitignore ================================================ /target **/*.rs.bk Cargo.lock .vscode/ ================================================ FILE: CHANGELOG.md ================================================ # 0.6.3 * Added support for wasm32-wasip2. * Added `Socket::(set_)ip_transparent_v6`. * Added `Socket::set_tcp_ack_frequency`. * Support windows-sys v0.61 in addition to v0.60. **POTENTIALLY BREAKING** The MSRV of windows-sys v0.61 is 1.71. To use socket2 with its MSRV of 1.70, please downgrade windows-sys to v0.60.x. This can be done using: `cargo update windows-sys --precise 0.60.2` # 0.6.2 * `MsgHdr` and `MsgHdrMut` are marked as transparent meaning both have the same layout as `msghdr` on Unix and `WSAMSG` on Windows (https://github.com/rust-lang/socket2/pull/635). * Don't set `SO_NOSIGPIPE` when accepting sockets, this is inherited from the parent socket (https://github.com/rust-lang/socket2/pull/632). * Fixes `Socket::tcp_notsent_lowat` by using the correct argument type (https://github.com/rust-lang/socket2/pull/622). # 0.6.1 ## Added * Added support for Windows Registered I/O (RIO) (https://github.com/rust-lang/socket2/pull/604). * Added support for `TCP_NOTSENT_LOWAT` on Linux via `Socket::(set_)tcp_notsent_lowat` (https://github.com/rust-lang/socket2/pull/611). * Added support for `SO_BUSY_POLL` on Linux via `Socket::set_busy_poll` (https://github.com/rust-lang/socket2/pull/607). * `SockFilter::new` is now a const function (https://github.com/rust-lang/socket2/pull/609). ## Changed * Updated the windows-sys dependency to version 0.60 (https://github.com/rust-lang/socket2/pull/605). # 0.6.0 ## Breaking changes All IPv4 methods now have a `_v4` suffix, IPv6 uses `_v6`. TCP methods have a `tcp_` prefix (looked better than a suffix). Furthermore we removed all types from external libraries (i.e. libc or windows-sys) from the public API, allowing us to update those without breaking the API. * Renamed `Socket::freebind_ipv6` to `freebind_v6` (https://github.com/rust-lang/socket2/pull/592). * Renamed `Socket::freebind` to `freebind_v4` (https://github.com/rust-lang/socket2/pull/592). * Renamed `Socket::original_dst` to `original_dst_v4` (https://github.com/rust-lang/socket2/pull/592). * Renamed `Socket::original_dst_ipv6` to `original_dst_v6` (https://github.com/rust-lang/socket2/pull/592). * Bump MSRV to 1.70 (https://github.com/rust-lang/socket2/pull/597). * Use `c_int` from `std::ffi` instead of from libc (https://github.com/rust-lang/socket2/pull/599, https://github.com/rust-lang/socket2/pull/595). * `SockAddr`'s methods now accept/return `SockAddrStorage` instead of `sockaddr_storage`/`SOCKADDR_STORAGE` (https://github.com/rust-lang/socket2/pull/576): * `new` * `try_init` * `as_ptr` * `as_storage` * Add `SockFilter`, wrapper around `libc::sock_filter`, argument to `Socket::attach_filter` (https://github.com/rust-lang/socket2/pull/581). * Various renames of TCP methods on `Socket` (https://github.com/rust-lang/socket2/pull/592): * `keepalive_time` -> `tcp_keepalive_time` * `keepalive_interval` -> `tcp_keepalive_interval` * `keepalive_retries` -> `tcp_keepalive_retries` * `nodelay` -> `tcp_nodelay` * `set_nodelay` -> `set_tcp_nodelay` * `tcp_mss` -> `mss` * `tcp_set_mss` -> `set_mss` * `tcp_cork` -> `cork` * `tcp_set_cork` -> `set_cork` * `tcp_quickack` -> `quickack` * `tcp_set_quickack` -> `set_quickack` * `thin_linear_timeouts` -> `tcp_thin_linear_timeouts`. ## Non-breaking changes * Added `Socket::(set_)priority` (https://github.com/rust-lang/socket2/pull/588). * Added TCP retries on Windows (https://github.com/rust-lang/socket2/pull/557). * Added `SockAddrStorage`, wrapper around `sockaddr_storage`/`SOCKADDR_STORAGE` for usage with `SockAddr` (instead of the types from libc/windows-sys) (https://github.com/rust-lang/socket2/pull/576). * Implemented `Socket::bind_device_by_index_{v4,v6}` on Android and Linux (https://github.com/rust-lang/socket2/pull/572). * Implemented `Copy` and `Clone` for `InterfaceIndexOrAddress` (https://github.com/rust-lang/socket2/pull/571). * Updated to Windows-sys v0.59 (https://github.com/rust-lang/socket2/pull/579). * We now use `OwnedFd`/`OwnedSocket` internally for `Socket` (https://github.com/rust-lang/socket2/pull/600). # 0.5.10 * Add cygwin support (https://github.com/rust-lang/socket2/pull/568, https://github.com/rust-lang/socket2/pull/578). # 0.5.9 * Enable `IP_BOUND_IF` on illumos and Solaris (https://github.com/rust-lang/socket2/pull/561, https://github.com/rust-lang/socket2/pull/566). # 0.5.8 * Added `Socket::(set_)header_included_v4` and `Socket::(set_)header_included_v6` (https://github.com/rust-lang/socket2/pull/518). * Added support for `Socket::original_dst` and `Socket::original_dst_ipv6` on Windows (https://github.com/rust-lang/socket2/pull/529). # 0.5.7 * Added `Socket::(set_)passcred` (https://github.com/rust-lang/socket2/pull/506). * Added `RecvFlags::is_confirm` and `RecvFlags::is_dontroute` (https://github.com/rust-lang/socket2/pull/499). * Added `MsgHdrMut::control_len` (https://github.com/rust-lang/socket2/pull/505). # 0.5.6 * Added `Socket::(set_)multicast_all_v{4,6}` (https://github.com/rust-lang/socket2/pull/485 and https://github.com/rust-lang/socket2/pull/486). * Added support for GNU/Hurd (https://github.com/rust-lang/socket2/pull/474). * Fixes compilation on Haiku (https://github.com/rust-lang/socket2/pull/479 and https://github.com/rust-lang/socket2/pull/482). * Fixes compilation on OpenHarmony (https://github.com/rust-lang/socket2/pull/491). * Update to window-sys v0.52 (https://github.com/rust-lang/socket2/pull/480). # 0.5.5 * Added support for Vita (https://github.com/rust-lang/socket2/pull/465). # 0.5.4 * Deprecated `Socket::(bind_)device_by_index`, replaced by `Socket::(bind_)device_by_index_v4` for IPv4 sockets (https://github.com/rust-lang/socket2/pull/432). * Added `Socket::(bind_)device_by_index_v6` (https://github.com/rust-lang/socket2/pull/432). * Added experimental support for the ESP-IDF framework (https://github.com/rust-lang/socket2/pull/452) * Added `Socket::{send,recv}msg` and `MsgHdr(Mut)` types, wrapping `sendmsg(2)` and `recvmsg(2)` (https://github.com/rust-lang/socket2/pull/447). * Added `Socket::(set_)reuse_port_lb` to retrieve or set `SO_REUSEPORT_LB` on FreeBSD (https://github.com/rust-lang/socket2/pull/442). * Added `Protocol::DIVERT` on FreeBSD and OpenBSD (https://github.com/rust-lang/socket2/pull/448). * Added `Socket::protocol` for Windows (using `WSAPROTOCOL_INFOW`) (https://github.com/rust-lang/socket2/pull/470). * `From` for `SockAddr ` nows sets `ss_len` on platforms that have the fields (most BSDs) (https://github.com/rust-lang/socket2/pull/469). * Change Windows to use `ADDRESS_FAMILY` for `sa_family_t`, this shouldn't affect anything in practice (https://github.com/rust-lang/socket2/pull/463). # 0.5.3 * Added support for two new Android targets `armv7-linux-androideabi` and `i686-linux-android` (https://github.com/rust-lang/socket2/pull/434). * Added `Socket::cookie` to retrieve `SO_COOKIE` on Linux (https://github.com/rust-lang/socket2/pull/437). # 0.5.2 * Added Unix socket methods to `SockAddr` (https://github.com/rust-lang/socket2/pull/403 and https://github.com/rust-lang/socket2/pull/429). * Added `SockAddr::as_storage` (https://github.com/rust-lang/socket2/pull/417). * Added `SockAddr::set_length` (https://github.com/rust-lang/socket2/pull/428). * Added `Protocol::UDPLITE` (https://github.com/rust-lang/socket2/pull/427). * Update windows-sys to 0.48 (https://github.com/rust-lang/socket2/pull/422). * Fixes Fuchsia target after it changes in 1.68, see (https://github.com/rust-lang/socket2/pull/423). * Fixes musl target and adds it to the CI (https://github.com/rust-lang/socket2/pull/426). # 0.5.1 ## Added * `Type::cloexec` for Redox and Solaris (https://github.com/rust-lang/socket2/pull/398). * Generate documentation for more targets on docs.rs (https://github.com/rust-lang/socket2/pull/398). ## Fixed * Generatation of documentation on docs.rs (https://github.com/rust-lang/socket2/pull/398). # 0.5.0 ## Changed * **BREAKING** `SockAddr::init` is renamed to `try_init` to indicate it can fail (https://github.com/rust-lang/socket2/pull/328). * **BREAKING** Remove the returned `Result` from `SockAddr::vsock`, it can't fail (https://github.com/rust-lang/socket2/pull/328). * **BREAKING** `From` is now implemented using the I/O traits `AsFd` and `AsRawSocket` (https://github.com/rust-lang/socket2/pull/325): * **BREAKING** renamed `SockAddr::vsock_addr` `SockAddr::as_vsock_addr` to match the IPv4 and IPv6 methods (https://github.com/rust-lang/socket2/pull/334). * Redox now works on a stable compiler (https://github.com/rust-lang/socket2/pull/326). * Remove copy from `From` implementation for `SockAddr` (https://github.com/rust-lang/socket2/pull/335). * Marked function as constant where possible. * Updated to Rust edition 2021 (https://github.com/rust-lang/socket2/pull/393). ## Added * Links to OS documentation to a lot of methods (https://github.com/rust-lang/socket2/pull/319). * I/O-safety traits (https://github.com/rust-lang/socket2/pull/325): * `AsFd` for `Socket` (Unix only). * `From` for `Socket` (Unix only). * `From` for `OwnedFd` (Unix only). * `AsSocket` for `Socket` (Windows only). * `From` for `Socket` (Windows only). * `From` for `OwnedSocket` (Windows only). * Unix socket support on Windows (https://github.com/rust-lang/socket2/pull/249). * `SockAddr::is_ipv{4,6}` and `SockAddr::domain` (https://github.com/rust-lang/socket2/pull/334). * `Socket::nonblocking` (https://github.com/rust-lang/socket2/pull/348). * `Socket::original_dst(_ipv6)` (https://github.com/rust-lang/socket2/pull/360). * `Socket::(set_)recv_tclass_v6` and `Socket::(set_)tclass_v6` (https://github.com/rust-lang/socket2/pull/364). * `Socket::(set_)tcp_congestion` (https://github.com/rust-lang/socket2/pull/371). * Support for various DCCP socket options in the form of (https://github.com/rust-lang/socket2/pull/359): * `Socket::(set_)dccp_service` * `Socket::dccp_available_ccids` * `Socket::dccp_qpolicy_txqlen` * `Socket::dccp_recv_cscov` * `Socket::dccp_send_cscov` * `Socket::dccp_server_timewait` * `Socket::dccp_server_timewait` * `Socket::dccp_tx_ccid` * `Socket::dccp_xx_ccid` * `Socket::set_dccp_ccid` * `Socket::set_dccp_qpolicy_txqlen` * `Socket::set_dccp_recv_cscov` * `Socket::set_dccp_send_cscov` * `Socket::set_dccp_server_timewait` * `Socket::dccp_cur_mps` * `Socket::peek_send` (https://github.com/rust-lang/socket2/pull/389). * `Protocol::MPTCP` (https://github.com/rust-lang/socket2/pull/349). * `Protocol::SCTP` (https://github.com/rust-lang/socket2/pull/356). * `Protocol::DCCP` (https://github.com/rust-lang/socket2/pull/359). * `Type::DCCP` (https://github.com/rust-lang/socket2/pull/359). * Implement `Eq` and `Hash` for `SockAddr` (https://github.com/rust-lang/socket2/pull/374). * Support for QNX Neutrino (https://github.com/rust-lang/socket2/pull/380). * Support for AIX (https://github.com/rust-lang/socket2/pull/351). # 0.4.10 * Fixed compilation with the `all` on QNX Neutrino (https://github.com/rust-lang/socket2/pull/419). * Added support for ESP-IDF (https://github.com/rust-lang/socket2/pull/455). * Added support for Vita (https://github.com/rust-lang/socket2/pull/475). # 0.4.9 * Fixed compilation on Windows (https://github.com/rust-lang/socket2/pull/409). # 0.4.8 (yanked) This release was broken for Windows. * Added `Socket::peek_sender` (backport) (https://github.com/rust-lang/socket2/pull/404). # 0.4.7 * Fixes compilation on OpenBSD (https://github.com/rust-lang/socket2/pull/344). * Fixes compilation on DragonFlyBSD (https://github.com/rust-lang/socket2/pull/342). # 0.4.6 * Reverted back to the `winapi` dependency as switch to `windows-sys` was a breaking change (https://github.com/rust-lang/socket2/pull/340). Note that we'll will switch to `windows-sys` in v0.5 . * Disable RECVTOS on OpenBSD (https://github.com/rust-lang/socket2/pull/307). * Derive Clone for SockAddr (https://github.com/rust-lang/socket2/pull/311). * Fixes cfg attributes for Fuchsia (https://github.com/rust-lang/socket2/pull/314). # 0.4.5 (yanked) ## Changed * Replace `winapi` dependency with `windows-sys` (https://github.com/rust-lang/socket2/pull/303). ## Added * `Socket::join_ssm_v4` and `Socket::leave_ssm_v4` (https://github.com/rust-lang/socket2/pull/298). * `Socket::set_recv_tos` and `Socket::recv_tos` (https://github.com/rust-lang/socket2/pull/299). ## Fixed * OpenBSD build (https://github.com/rust-lang/socket2/pull/291). # 0.4.4 ## Fixed * Libc v0.2.114 fixed an issue where `ip_mreqn` where was not defined for Linux s390x. # 0.4.3 (yanked) ## Added * `Socket::set_fib`: sets `SO_SETFIB` (https://github.com/rust-lang/socket2/pull/271). * `Socket::attach_filter`, `SO_ATTACH_FILTER` (https://github.com/rust-lang/socket2/commit/6601ed132b37d6e9d178b34918bfb0b236800232). * `Socket::detach_filter`, `SO_DETACH_FILTER` (https://github.com/rust-lang/socket2/commit/6601ed132b37d6e9d178b34918bfb0b236800232). * `Socket::{header_included, set_header_included}`: sets or gets `IP_HDRINCL` (https://github.com/rust-lang/socket2/commit/f9e882ee53c0b4e89c5043b6d709af95c9db5599). * `Socket::{cork, set_cork}`: sets or gets `TCP_CORK` (https://github.com/rust-lang/socket2/commit/50f31f18aac8fd6ef277df2906adeeed9fa391de). * `Socket::{quickack, set_quickack}`: sets or gets `TCP_QUICKACK` (https://github.com/rust-lang/socket2/commit/849eee2abc5d5170d2d3bc635386a2ba13b04530). * `Socket::{thin_linear_timeouts, set_thin_linear_timeouts}`: sets or gets `TCP_THIN_LINEAR_TIMEOUTS` (https://github.com/rust-lang/socket2/commit/24c231ca463a17f51e53e7a554c7915a95bdbcc7). * `Socket::{join_multicast_v4_n, leave_multicast_v4_n}`: extends the existing multicast API by allowing an index to be used (in addition to an address) (https://github.com/rust-lang/socket2/commit/750f83618b967c620bbfdf6ca04de7362bdb42b5). # 0.4.2 ## Added * `Socket::(set_)freebind_ipv6`, getter and setter for `IPV6_FREEBIND`. ## Fixed * Compilation on OpenBSD. * Usage of incorrect flag in `Socket::(set_)freebind`. # 0.4.1 ## Added * Added `SockAddr::new` * Support for `TCP_USER_TIMEOUT`. * Support for `IP_BOUND_IF`. * Support for `IP_TRANSPARENT`. * Enable `Socket::type` on all platforms. * Support for uclibc (for Haiku support). * Added DragonFly support for TCP keepalive (`KEEPINTVL`/`KEEPCNT`). * Documentation for proper use of `SockRef::from`, and the improper use. * Assertion in `SockRef::from` to ensure the raw socket valid. ## Fixed * Compilation on Haiku. * Setting TCP keepalive on Haiku and OpenBSD (by not setting it as it's not supported). * Size check for abstract namespaces in `SockAddr::unix`. * Set noinherit on accepted sockets on Windows when opening sockets. # 0.4.0 ## Added * New `all` feature: enables API that is not available on all tier 1 platforms. * `SockRef` type: used to create a reference to an existing socket, e.g. `std::net::TcpStream`, making all methods of `Socket` available on it. * Support for vectored I/O: * `Socket::recv_vectored`, `Socket::recv_with_flags`. * `Socket::recv_from_vectored`, `Socket::recv_from_vectored_with_flags`. * `Socket::send_vectored`, `Socket::send_vectored_with_flags`. * `Socket::send_to_vectored`, `Socket::send_to_vectored_with_flags`. * In the `Read` and `Write` implementations. * `Socket::new_raw`, `Socket::pair_raw` and `Socket::accept_raw` methods that don't set common flags, such as the close-on-exec flag. * `Socket::accept4`: `accept4(2)` system call. * `Socket::sendfile`: the `sendfile(2)` system call. * `Socket::set_cloexec`: set the close-on-exec flag on Unix. * `Socket::set_no_inherit`: set inherit handle flag on Windows. * `Socket::set_nosigpipe`: set `SO_NOSIGPIPE` on Apple targets. * `Socket::set_mark` and `Socket::mark`, setting/getting the `SO_MARK` socket option. * `Socket::set_cpu_affinity` and `Socket::cpu_affinity`, setting/getting the `SO_INCOMING_CPU` socket option. * `Socket::set_mss` and `Socket::mss`, setting/getting the `TCP_MAXSEG` socket option. * `Socket::set_freebind` and `Socket::freebind`, setting/getting the `IP_FREEBIND` socket option. * `Socket::bind_device` and `Socket::device`, setting/getting the `SO_BINDTODEVICE` socket option. * Adopted Mio's TCP keepalive API: * `Socket::keepalive_time`, * `Socket::keepalive_interval`, * `Socket::keepalive_retries`, * `Socket::set_tcp_keepalive`. * `Socket::is_listener` getting the `SO_ACCEPTCONN` socket option. * `Socket::domain` getting the `SO_DOMAIN` socket option. * `Socket::protocol` getting the `SO_PROTOCOL` socket option. * `Socket::type` getting the `SO_TYPE` socket option. * `Domain::for_address`: the correct `Domain` for a `std::net::SocketAddr`. * `Type::nonblocking`: set `SOCK_NONBLOCK`. * `Type::cloexec`: set `SOCK_CLOEXEC`. * `Type::no_inherit`: set `HANDLE_FLAG_INHERIT`. * `SockAddr::init`: initialises a `SockAddr`. * `MaybeUninitSlice` type: a version of `IoSliceMut` that allows the buffer to be uninitialised, used in `Socket::recv_vectored` and related functions. * `RecvFlags` type: provides additional information about incoming messages, returned by `Socket::recv_vectored` and related functions. * `TcpKeepalive` type: configuration type for a socket's TCP keepalive parameters. ## Changed * Repository moved to . * **BREAKING:** Changed constructor functions into constants: * `Domain::ipv4` => `Domain::IPV4`. * `Domain::ipv6` => `Domain::IPV4`. * `Domain::unix` => `Domain::UNIX`. * `Domain::packet` => `Domain::PACKET`. * `Type::stream` => `Type::STREAM`. * `Type::dgram` => `Type::DGRAM`. * `Type::seqpacket` => `Type::SEQPACKET`. * `Type::raw` => `Type::RAW`. * `Protocol::icmpv4` => `Protocol::ICMPV4`. * `Protocol::icmpv6` => `Protocol::ICMPV6`. * `Protocol::tcp` => `Protocol::TCP`. * `Protocol::udp` => `Protocol::UDP`. * **BREAKING:** Changed the signature of `Socket::recv`, `Socket::recv_vectored` and related methods to accept uninitialised buffers. The `Read` implementation can be used to read into initialised buffers. * **BREAKING:** Renamed `SockAddr::as_std` to `as_socket`. * **BREAKING:** Renamed `SockAddr::as_inet` to `as_socket_ipv4`. * **BREAKING:** Renamed `SockAddr::as_inet6` to `as_socket_ipv6`. * **BREAKING:** Replace all previously existing features (reuseport, pair, unix) with a new all features (see above for description of the all feature). * Use `accept4(2)` with `SOCK_CLOEXEC` in `Socket::accept`, reducing the amount of system calls required. * Marked many functions as constant. * The `Read` implementation now calls `recv(2)` rather than `read(2)`. * Split the `impl` block for the `Socket` type to create groupings for setting and getting different level socket options using `setsockopt(2)`/`getsockopt(2)`. * Updated `winapi` depdency to version 0.3.9 and dropped unused features. ## Removed * Removed the `-rs` suffix from the repository name. * **BREAKING:** Removed `SockAddr::from_raw_parts`, use `SockAddr::init` instead. * **BREAKING:** Removed `Socket::into_*` functions and replaced them with a `From` implementation: * `Socket::into_tcp_stream` => `TcpStream::from(socket)`. * `Socket::into_tcp_listener` => `TcpListener::from(socket)`. * `Socket::into_udp_socket` => `UdpSocket::from(socket)`. * `Socket::into_unix_stream` => `UnixStream::from(socket)`. * `Socket::into_unix_listener` => `UnixListener::from(socket)`. * `Socket::into_unix_datagram` => `UnixDatagram::from(socket)`. * Removed `cfg-if` dependency. * Remove `redox_syscall` depdency. ## Fixes * Fixes the Andoid, Fuchsia, Haiku, iOS, illumos, NetBSD and Redox (nightly only) targets. * Correctly call `recv_from` in `Socket::recv_from_with_flags` (called `recv` previously). * Correctly call `send_to` in `Socket::send_to_with_flags` (called `recv` previously). * Use correct inmutable references in `Socket::send_with_flags` and `Socket::send_out_of_band`. * Use `IPPROTO_IPV6` in `Socket::join_multicast_v6` on Windows. * Use `c_int` instead of `i32` where appropriate. ## From v0.4.0-alpha.1 to v0.4.0-alpha.2 * Fixes the Fuchsia target. * `Socket::device` now returns a `Vec` rather than `CString`. * `Socket::bind_device` now accepts a `&[u8]` rather than `&CStr`. ## From v0.4.0-alpha.2 to v0.4.0-alpha.3 * `Socket::connect_timeout` was added back. ## From v0.4.0-alpha.4 to v0.4.0-alpha.5 * Changed `Socket::set_cpu_affinity` and `Socket::cpu_affinity` to use an immutable reference. ## From v0.4.0-alpha.5 to v0.4.0 * Use `SO_LINGER_SEC` on macOS for `Socket::get/set_linger`. # 0.3.16 * Don't assume the memory layout of `std::net::SocketAddr`. * Other changes omited ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Socket2 There are many ways to contribute to Socket2, including (but not limited to) answering questions, adding new features, fixing bugs or expanding the documentation. This document will focus on adding new features and fixing bugs. If you're adding a new feature please first [open an issue] laying out what would be added and a design you're proposing. Doing this before actually writing the code will save you time if others suggest improvements to the design. Once there is some consensus on 1) the feature is a good addition to Socket2 and 2) the proposed design is the right one, open a [pull request] with the changes. If you're working on fixing a bug please say so on the specific issue so that two people don't work on fixing the same bug. For more complex bugs or fixes please also share your proposed design on the issue tracker, same as for new features. Once you're working on the code it's useful to understand where the code is (or should) be located, the [code structure section] describes how the code of Socket2 is organised. To make sure we don't create the same bug again, or to ensure that new features keep working, please add (regression) test for the changes you've made to the code. The [testing section] below describes how to run the tests and where to add new tests. [open an issue]: https://github.com/rust-lang/socket2/issues/new [pull request]: https://github.com/rust-lang/socket2/compare [code structure section]: #code-structure [testing section]: #testing # Code structure All types and methods that are available on all tier 1 platforms are defined in the first level of the source, i.e. `src/*.rs` files. Additional API that is platform specific, e.g. `Domain::VSOCK`, is defined in `src/sys/*.rs` and only for the platforms that support it. For API that is not available on all tier 1 platforms the `all` feature is used, to indicate to the user that they're using API that might is not available on all platforms. The main `Socket` type is defined in `src/socket.rs` with additional methods defined in the `src/sys/*.rs` files, as per above. The methods on `Socket` are split into multiple `impl` blocks. The first `impl` block contains a collection of system calls for creating and using the socket, e.g. `socket(2)`, `bind(2)`, `listen(2)`, etc. The other implementation blocks are for getting and setting socket options on various levels, e.g. `SOL_SOCKET`, where each block contains a single level. The methods in these block are sorted based on the option name, e.g. `IP_ADD_MEMBERSHIP` rather than `join_multicast_v4`. Finally the last block contains platforms specific methods such as `Socket::freebind` which is (at the time of writing) only available on Android, Linux and Fuchsia, which is defined in the `src/sys/*.rs` files. Other types are mostly defined in `src/lib.rs`, except for `SockAddr` and `SockRef` which have their own file. These types follow the same structure as `Socket`, where OS specific methods are defined in `src/sys/*.rs`, e.g. `Type::cloexec`. # Testing Testing Socket2 is as simple as running `cargo test --all-features`. However Socket2 supports a good number of OSs and features. If you want to test/check all those combinations it's easier to use the [Makefile]. Using `make test_all` it will check all supported OS targets and all combinations of supported features. Note that this requires [cargo-hack] and various rustup targets to be installed. Cargo-hack must be installed manually, the various targets can be installed automatically using `make install_targets` (which uses [rustup]). [Makefile]: ./Makefile [cargo-hack]: https://crates.io/crates/cargo-hack [rustup]: https://rustup.rs ## Adding a test Tests should be added to `tests/socket.rs`, following (roughly) the same order in which the methods are defined on a type. At the bottom of this file it has a macro to create a simple get/set socket option test, more complex API however needs a manually written test. Tests that need to use internal API can be defined directly at the bottom of the source file. No need for a test module since we intend on keeping the number of internal tests low. ================================================ FILE: Cargo.toml ================================================ [package] name = "socket2" version = "0.6.3" authors = [ "Alex Crichton ", "Thomas de Zeeuw " ] license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/rust-lang/socket2" homepage = "https://github.com/rust-lang/socket2" documentation = "https://docs.rs/socket2" description = """ Utilities for handling networking sockets with a maximal amount of configuration possible intended. """ keywords = ["io", "socket", "network"] categories = ["api-bindings", "network-programming"] edition = "2021" rust-version = "1.70" include = [ "Cargo.toml", "LICENSE-APACHE", "LICENSE-MIT", "README.md", "src/**/*.rs", ] [package.metadata.docs.rs] all-features = true default-target = "x86_64-unknown-linux-gnu" targets = [ "aarch64-apple-ios", "aarch64-linux-android", "armv7-linux-androideabi", "i686-linux-android", "x86_64-apple-darwin", "x86_64-pc-solaris", "x86_64-pc-windows-msvc", "x86_64-unknown-freebsd", "x86_64-unknown-fuchsia", "x86_64-unknown-illumos", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-unknown-netbsd", "x86_64-unknown-redox", ] [package.metadata.playground] features = ["all"] [target.'cfg(any(unix, target_os = "wasi"))'.dependencies] libc = "0.2.172" [target.'cfg(windows)'.dependencies.windows-sys] version = ">=0.60, <0.62" features = [ "Win32_Foundation", "Win32_Networking_WinSock", "Win32_System_IO", "Win32_System_Threading", "Win32_System_WindowsProgramming", ] [features] # Enable all API, even ones not available on all OSs. all = [] [package.metadata.cargo_check_external_types] allowed_external_types = [ # Referenced via a type alias. "libc::socklen_t", "libc::*::socklen_t", # libc::socklen_t isn't always detected. "libc::sa_family_t", "libc::*::sa_family_t", # libc::sa_family_t is always detected. "windows_sys::Win32::Networking::WinSock::socklen_t", "windows_sys::Win32::Networking::WinSock::ADDRESS_FAMILY", ] ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2014 Alex Crichton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # Targets available via Rustup that are supported. # NOTE: keep in sync with the CI and docs.rs targets. TARGETS ?= "aarch64-apple-ios" "aarch64-linux-android" "x86_64-apple-darwin" "x86_64-unknown-fuchsia" "x86_64-pc-windows-msvc" "x86_64-pc-solaris" "x86_64-unknown-freebsd" "x86_64-unknown-illumos" "x86_64-unknown-linux-gnu" "x86_64-unknown-linux-musl" "x86_64-unknown-netbsd" "x86_64-unknown-redox" "armv7-linux-androideabi" "i686-linux-android" test: cargo test --all-features # Test everything for the current OS/architecture and check all targets in # $TARGETS. test_all: check_all_targets cargo hack test --feature-powerset cargo hack test --feature-powerset --release # Check all targets using all features. check_all_targets: $(TARGETS) $(TARGETS): cargo hack check --feature-powerset --all-targets --examples --bins --tests --target $@ # Installs all required targets for `check_all_targets`. install_targets: rustup target add $(TARGETS) # NOTE: when using this command you might want to change the `test` target to # only run a subset of the tests you're actively working on. dev: find src/ tests/ Makefile Cargo.toml | entr -d -c $(MAKE) test clean: cargo clean .PHONY: test test_all check_all_targets $(TARGETS) install_targets dev clean ================================================ FILE: README.md ================================================ # Socket2 Socket2 is a crate that provides utilities for creating and using sockets. The goal of this crate is to create and use a socket using advanced configuration options (those that are not available in the types in the standard library) without using any unsafe code. This crate provides as direct as possible access to the system's functionality for sockets, this means little effort to provide cross-platform utilities. It is up to the user to know how to use sockets when using this crate. *If you don't know how to create a socket using libc/system calls then this crate is not for you*. Most, if not all, functions directly relate to the equivalent system call with no error handling applied, so no handling errors such as `EINTR`. As a result using this crate can be a little wordy, but it should give you maximal flexibility over configuration of sockets. See the [API documentation] for more. [API documentation]: https://docs.rs/socket2 # Branches Currently Socket2 supports the following versions: * v0.6 developed in the master branch * v0.5 developed in the [v0.5.x branch] * v0.4 developed in the [v0.4.x branch] [v0.5.x branch]: https://github.com/rust-lang/socket2/tree/v0.5.x [v0.4.x branch]: https://github.com/rust-lang/socket2/tree/v0.4.x # OS support Socket2 attempts to support the same OS/architectures as Rust does, see https://doc.rust-lang.org/nightly/rustc/platform-support.html. However this is not always possible, below is current list of support OSs. *If your favorite OS is not on the list consider contributing it!* ### Tier 1 These OSs are tested with each commit in the CI and must always pass the tests. All functions/types/etc., excluding ones behind the `all` feature, must work on these OSs. * Linux * macOS * Windows ### Tier 2 These OSs are currently build in the CI, but not tested. Not all functions/types/etc. may work on these OSs, even ones **not** behind the `all` feature flag. * Android * FreeBSD * Fuchsia * iOS * illumos * NetBSD * Redox * Solaris * OpenHarmony # Minimum Supported Rust Version (MSRV) Socket2 uses 1.70.0 as MSRV. # License This project is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ================================================ FILE: src/lib.rs ================================================ // Copyright 2015 The Rust Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. #![allow(clippy::needless_lifetimes)] //! Utilities for creating and using sockets. //! //! The goal of this crate is to create and use a socket using advanced //! configuration options (those that are not available in the types in the //! standard library) without using any unsafe code. //! //! This crate provides as direct as possible access to the system's //! functionality for sockets, this means little effort to provide //! cross-platform utilities. It is up to the user to know how to use sockets //! when using this crate. *If you don't know how to create a socket using //! libc/system calls then this crate is not for you*. Most, if not all, //! functions directly relate to the equivalent system call with no error //! handling applied, so no handling errors such as [`EINTR`]. As a result using //! this crate can be a little wordy, but it should give you maximal flexibility //! over configuration of sockets. //! //! [`EINTR`]: std::io::ErrorKind::Interrupted //! //! # Examples //! //! ```no_run //! # fn main() -> std::io::Result<()> { //! use std::net::{SocketAddr, TcpListener}; //! use socket2::{Socket, Domain, Type}; //! //! // Create a TCP listener bound to two addresses. //! let socket = Socket::new(Domain::IPV6, Type::STREAM, None)?; //! //! socket.set_only_v6(false)?; //! let address: SocketAddr = "[::1]:12345".parse().unwrap(); //! socket.bind(&address.into())?; //! socket.listen(128)?; //! //! let listener: TcpListener = socket.into(); //! // ... //! # drop(listener); //! # Ok(()) } //! ``` //! //! ## Features //! //! This crate has a single feature `all`, which enables all functions even ones //! that are not available on all OSs. #![deny(missing_docs, missing_debug_implementations, rust_2018_idioms)] // Automatically generate required OS/features for docs.rs. #![cfg_attr(docsrs, feature(doc_cfg))] // Disallow warnings when running tests. #![cfg_attr(test, deny(warnings))] // Disallow warnings in examples. #![doc(test(attr(deny(warnings))))] use std::fmt; #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] use std::io::IoSlice; #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] use std::marker::PhantomData; #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] use std::mem; use std::mem::MaybeUninit; use std::net::SocketAddr; use std::ops::{Deref, DerefMut}; use std::time::Duration; /// Macro to implement `fmt::Debug` for a type, printing the constant names /// rather than a number. /// /// Note this is used in the `sys` module and thus must be defined before /// defining the modules. macro_rules! impl_debug { ( // Type name for which to implement `fmt::Debug`. $type: path, $( $(#[$target: meta])* // The flag(s) to check. // Need to specific the libc crate because Windows doesn't use // `libc` but `windows_sys`. $libc: ident :: $flag: ident ),+ $(,)* ) => { impl std::fmt::Debug for $type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let string = match self.0 { $( $(#[$target])* $libc :: $flag => stringify!($flag), )+ n => return write!(f, "{n}"), }; f.write_str(string) } } }; } /// Macro to convert from one network type to another. macro_rules! from { ($from: ty, $for: ty) => { impl From<$from> for $for { fn from(socket: $from) -> $for { #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] unsafe { <$for>::from_raw_fd(socket.into_raw_fd()) } #[cfg(windows)] unsafe { <$for>::from_raw_socket(socket.into_raw_socket()) } } } }; } /// Link to online documentation for (almost) all supported OSs. #[rustfmt::skip] macro_rules! man_links { // Links to all OSs. ($syscall: tt ( $section: tt ) ) => { concat!( man_links!(__ intro), man_links!(__ unix $syscall($section)), man_links!(__ windows $syscall($section)), ) }; // Links to Unix-like OSs. (unix: $syscall: tt ( $section: tt ) ) => { concat!( man_links!(__ intro), man_links!(__ unix $syscall($section)), ) }; // Links to Windows only. (windows: $syscall: tt ( $section: tt ) ) => { concat!( man_links!(__ intro), man_links!(__ windows $syscall($section)), ) }; // Internals. (__ intro) => { "\n\nAdditional documentation can be found in manual of the OS:\n\n" }; // List for Unix-like OSs. (__ unix $syscall: tt ( $section: tt ) ) => { concat!( " * DragonFly BSD: \n", " * FreeBSD: \n", " * Linux: \n", " * macOS: (archived, actually for iOS)\n", " * NetBSD: \n", " * OpenBSD: \n", " * iOS: (archived)\n", " * illumos: \n", ) }; // List for Window (so just Windows). (__ windows $syscall: tt ( $section: tt ) ) => { concat!( " * Windows: \n", ) }; } mod sockaddr; mod socket; mod sockref; #[cfg_attr( any(unix, all(target_os = "wasi", not(target_env = "p1"))), path = "sys/unix.rs" )] #[cfg_attr(windows, path = "sys/windows.rs")] mod sys; #[cfg(not(any(windows, unix, all(target_os = "wasi", not(target_env = "p1")))))] compile_error!("Socket2 doesn't support the compile target"); use sys::c_int; pub use sockaddr::{sa_family_t, socklen_t, SockAddr, SockAddrStorage}; #[cfg(not(any( target_os = "haiku", target_os = "illumos", target_os = "netbsd", target_os = "redox", target_os = "solaris", target_os = "wasi", )))] pub use socket::InterfaceIndexOrAddress; pub use socket::Socket; pub use sockref::SockRef; #[cfg(all(feature = "all", target_os = "linux"))] pub use sys::CcidEndpoints; #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] pub use sys::SockFilter; /// Specification of the communication domain for a socket. /// /// This is a newtype wrapper around an integer which provides a nicer API in /// addition to an injection point for documentation. Convenience constants such /// as [`Domain::IPV4`], [`Domain::IPV6`], etc, are provided to avoid reaching /// into libc for various constants. /// /// This type is freely interconvertible with C's `int` type, however, if a raw /// value needs to be provided. #[derive(Copy, Clone, Eq, PartialEq)] pub struct Domain(c_int); impl Domain { /// Domain for IPv4 communication, corresponding to `AF_INET`. pub const IPV4: Domain = Domain(sys::AF_INET); /// Domain for IPv6 communication, corresponding to `AF_INET6`. pub const IPV6: Domain = Domain(sys::AF_INET6); /// Domain for Unix socket communication, corresponding to `AF_UNIX`. #[cfg(not(target_os = "wasi"))] pub const UNIX: Domain = Domain(sys::AF_UNIX); /// Returns the correct domain for `address`. pub const fn for_address(address: SocketAddr) -> Domain { match address { SocketAddr::V4(_) => Domain::IPV4, SocketAddr::V6(_) => Domain::IPV6, } } } impl From for Domain { fn from(d: c_int) -> Domain { Domain(d) } } impl From for c_int { fn from(d: Domain) -> c_int { d.0 } } /// Specification of communication semantics on a socket. /// /// This is a newtype wrapper around an integer which provides a nicer API in /// addition to an injection point for documentation. Convenience constants such /// as [`Type::STREAM`], [`Type::DGRAM`], etc, are provided to avoid reaching /// into libc for various constants. /// /// This type is freely interconvertible with C's `int` type, however, if a raw /// value needs to be provided. #[derive(Copy, Clone, Eq, PartialEq)] pub struct Type(c_int); impl Type { /// Type corresponding to `SOCK_STREAM`. /// /// Used for protocols such as TCP. pub const STREAM: Type = Type(sys::SOCK_STREAM); /// Type corresponding to `SOCK_DGRAM`. /// /// Used for protocols such as UDP. pub const DGRAM: Type = Type(sys::SOCK_DGRAM); /// Type corresponding to `SOCK_DCCP`. /// /// Used for the DCCP protocol. #[cfg(all(feature = "all", target_os = "linux"))] pub const DCCP: Type = Type(sys::SOCK_DCCP); /// Type corresponding to `SOCK_SEQPACKET`. #[cfg(all( feature = "all", not(any(target_os = "espidf", target_os = "wasi", target_os = "horizon")) ))] pub const SEQPACKET: Type = Type(sys::SOCK_SEQPACKET); /// Type corresponding to `SOCK_RAW`. #[cfg(all( feature = "all", not(any( target_os = "redox", target_os = "espidf", target_os = "wasi", target_os = "horizon" )) ))] pub const RAW: Type = Type(sys::SOCK_RAW); } impl From for Type { fn from(t: c_int) -> Type { Type(t) } } impl From for c_int { fn from(t: Type) -> c_int { t.0 } } /// Protocol specification used for creating sockets via `Socket::new`. /// /// This is a newtype wrapper around an integer which provides a nicer API in /// addition to an injection point for documentation. /// /// This type is freely interconvertible with C's `int` type, however, if a raw /// value needs to be provided. #[derive(Copy, Clone, Eq, PartialEq)] pub struct Protocol(c_int); impl Protocol { /// Protocol corresponding to `ICMPv4`. #[cfg(not(target_os = "wasi"))] pub const ICMPV4: Protocol = Protocol(sys::IPPROTO_ICMP); /// Protocol corresponding to `ICMPv6`. #[cfg(not(target_os = "wasi"))] pub const ICMPV6: Protocol = Protocol(sys::IPPROTO_ICMPV6); /// Protocol corresponding to `TCP`. pub const TCP: Protocol = Protocol(sys::IPPROTO_TCP); /// Protocol corresponding to `UDP`. pub const UDP: Protocol = Protocol(sys::IPPROTO_UDP); #[cfg(target_os = "linux")] /// Protocol corresponding to `MPTCP`. pub const MPTCP: Protocol = Protocol(sys::IPPROTO_MPTCP); /// Protocol corresponding to `DCCP`. #[cfg(all(feature = "all", target_os = "linux"))] pub const DCCP: Protocol = Protocol(sys::IPPROTO_DCCP); /// Protocol corresponding to `SCTP`. #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] pub const SCTP: Protocol = Protocol(sys::IPPROTO_SCTP); /// Protocol corresponding to `UDPLITE`. #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", ) ))] pub const UDPLITE: Protocol = Protocol(sys::IPPROTO_UDPLITE); /// Protocol corresponding to `DIVERT`. #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "openbsd")))] pub const DIVERT: Protocol = Protocol(sys::IPPROTO_DIVERT); } impl From for Protocol { fn from(p: c_int) -> Protocol { Protocol(p) } } impl From for c_int { fn from(p: Protocol) -> c_int { p.0 } } /// Flags for incoming messages. /// /// Flags provide additional information about incoming messages. #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] #[derive(Copy, Clone, Eq, PartialEq)] pub struct RecvFlags(c_int); #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] impl RecvFlags { /// Check if the message contains a truncated datagram. /// /// This flag is only used for datagram-based sockets, /// not for stream sockets. /// /// On Unix this corresponds to the `MSG_TRUNC` flag. /// On Windows this corresponds to the `WSAEMSGSIZE` error code. #[cfg(not(target_os = "espidf"))] pub const fn is_truncated(self) -> bool { self.0 & sys::MSG_TRUNC != 0 } } /// A version of [`IoSliceMut`] that allows the buffer to be uninitialised. /// /// [`IoSliceMut`]: std::io::IoSliceMut #[repr(transparent)] pub struct MaybeUninitSlice<'a>(sys::MaybeUninitSlice<'a>); impl<'a> fmt::Debug for MaybeUninitSlice<'a> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.0.as_slice(), fmt) } } impl<'a> MaybeUninitSlice<'a> { /// Creates a new `MaybeUninitSlice` wrapping a byte slice. /// /// # Panics /// /// Panics on Windows if the slice is larger than 4GB. pub fn new(buf: &'a mut [MaybeUninit]) -> MaybeUninitSlice<'a> { MaybeUninitSlice(sys::MaybeUninitSlice::new(buf)) } } impl<'a> Deref for MaybeUninitSlice<'a> { type Target = [MaybeUninit]; fn deref(&self) -> &[MaybeUninit] { self.0.as_slice() } } impl<'a> DerefMut for MaybeUninitSlice<'a> { fn deref_mut(&mut self) -> &mut [MaybeUninit] { self.0.as_mut_slice() } } /// Configures a socket's TCP keepalive parameters. /// /// See [`Socket::set_tcp_keepalive`]. #[derive(Debug, Clone)] pub struct TcpKeepalive { #[cfg_attr( any(target_os = "openbsd", target_os = "haiku", target_os = "vita"), allow(dead_code) )] time: Option, #[cfg(not(any( target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "haiku", target_os = "horizon" )))] interval: Option, #[cfg(not(any( target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "haiku", target_os = "horizon" )))] retries: Option, } impl TcpKeepalive { /// Returns a new, empty set of TCP keepalive parameters. #[allow(clippy::new_without_default)] pub const fn new() -> TcpKeepalive { TcpKeepalive { time: None, #[cfg(not(any( target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "haiku", target_os = "horizon" )))] interval: None, #[cfg(not(any( target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "haiku", target_os = "horizon" )))] retries: None, } } /// Set the amount of time after which TCP keepalive probes will be sent on /// idle connections. /// /// This will set `TCP_KEEPALIVE` on macOS and iOS, and /// `TCP_KEEPIDLE` on all other Unix operating systems, except /// OpenBSD and Haiku which don't support any way to set this /// option. On Windows, this sets the value of the `tcp_keepalive` /// struct's `keepalivetime` field. /// /// Some platforms specify this value in seconds, so sub-second /// specifications may be omitted. pub const fn with_time(self, time: Duration) -> Self { Self { time: Some(time), ..self } } /// Set the value of the `TCP_KEEPINTVL` option. On Windows, this sets the /// value of the `tcp_keepalive` struct's `keepaliveinterval` field. /// /// Sets the time interval between TCP keepalive probes. /// /// Some platforms specify this value in seconds, so sub-second /// specifications may be omitted. #[cfg(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "windows", target_os = "cygwin", all(target_os = "wasi", not(target_env = "p1")), ))] pub const fn with_interval(self, interval: Duration) -> Self { Self { interval: Some(interval), ..self } } /// Set the value of the `TCP_KEEPCNT` option. /// /// Set the maximum number of TCP keepalive probes that will be sent before /// dropping a connection, if TCP keepalive is enabled on this socket. #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "cygwin", target_os = "windows", all(target_os = "wasi", not(target_env = "p1")), ) ))] pub const fn with_retries(self, retries: u32) -> Self { Self { retries: Some(retries), ..self } } } /// Configuration of a `sendmsg(2)` system call. /// /// This wraps `msghdr` on Unix and `WSAMSG` on Windows. Also see [`MsgHdrMut`] /// for the variant used by `recvmsg(2)`. #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] #[repr(transparent)] pub struct MsgHdr<'addr, 'bufs, 'control> { inner: sys::msghdr, #[allow(clippy::type_complexity)] _lifetimes: PhantomData<(&'addr SockAddr, &'bufs IoSlice<'bufs>, &'control [u8])>, } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] impl<'addr, 'bufs, 'control> MsgHdr<'addr, 'bufs, 'control> { /// Create a new `MsgHdr` with all empty/zero fields. #[allow(clippy::new_without_default)] pub fn new() -> MsgHdr<'addr, 'bufs, 'control> { // SAFETY: all zero is valid for `msghdr` and `WSAMSG`. MsgHdr { inner: unsafe { mem::zeroed() }, _lifetimes: PhantomData, } } /// Set the address (name) of the message. /// /// Corresponds to setting `msg_name` and `msg_namelen` on Unix and `name` /// and `namelen` on Windows. pub fn with_addr(mut self, addr: &'addr SockAddr) -> Self { sys::set_msghdr_name(&mut self.inner, addr); self } /// Set the buffer(s) of the message. /// /// Corresponds to setting `msg_iov` and `msg_iovlen` on Unix and `lpBuffers` /// and `dwBufferCount` on Windows. pub fn with_buffers(mut self, bufs: &'bufs [IoSlice<'_>]) -> Self { let ptr = bufs.as_ptr() as *mut _; sys::set_msghdr_iov(&mut self.inner, ptr, bufs.len()); self } /// Set the control buffer of the message. /// /// Corresponds to setting `msg_control` and `msg_controllen` on Unix and /// `Control` on Windows. pub fn with_control(mut self, buf: &'control [u8]) -> Self { let ptr = buf.as_ptr() as *mut _; sys::set_msghdr_control(&mut self.inner, ptr, buf.len()); self } /// Set the flags of the message. /// /// Corresponds to setting `msg_flags` on Unix and `dwFlags` on Windows. pub fn with_flags(mut self, flags: sys::c_int) -> Self { sys::set_msghdr_flags(&mut self.inner, flags); self } } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] impl<'name, 'bufs, 'control> fmt::Debug for MsgHdr<'name, 'bufs, 'control> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { "MsgHdr".fmt(fmt) } } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] unsafe impl Send for MsgHdr<'_, '_, '_> {} /// Configuration of a `recvmsg(2)` system call. /// /// This wraps `msghdr` on Unix and `WSAMSG` on Windows. Also see [`MsgHdr`] for /// the variant used by `sendmsg(2)`. #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] #[repr(transparent)] pub struct MsgHdrMut<'addr, 'bufs, 'control> { inner: sys::msghdr, #[allow(clippy::type_complexity)] _lifetimes: PhantomData<( &'addr mut SockAddr, &'bufs mut MaybeUninitSlice<'bufs>, &'control mut [u8], )>, } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] impl<'addr, 'bufs, 'control> MsgHdrMut<'addr, 'bufs, 'control> { /// Create a new `MsgHdrMut` with all empty/zero fields. #[allow(clippy::new_without_default)] pub fn new() -> MsgHdrMut<'addr, 'bufs, 'control> { // SAFETY: all zero is valid for `msghdr` and `WSAMSG`. MsgHdrMut { inner: unsafe { mem::zeroed() }, _lifetimes: PhantomData, } } /// Set the mutable address (name) of the message. /// /// Corresponds to setting `msg_name` and `msg_namelen` on Unix and `name` /// and `namelen` on Windows. #[allow(clippy::needless_pass_by_ref_mut)] pub fn with_addr(mut self, addr: &'addr mut SockAddr) -> Self { sys::set_msghdr_name(&mut self.inner, addr); self } /// Set the mutable buffer(s) of the message. /// /// Corresponds to setting `msg_iov` and `msg_iovlen` on Unix and `lpBuffers` /// and `dwBufferCount` on Windows. pub fn with_buffers(mut self, bufs: &'bufs mut [MaybeUninitSlice<'_>]) -> Self { sys::set_msghdr_iov(&mut self.inner, bufs.as_mut_ptr().cast(), bufs.len()); self } /// Set the mutable control buffer of the message. /// /// Corresponds to setting `msg_control` and `msg_controllen` on Unix and /// `Control` on Windows. pub fn with_control(mut self, buf: &'control mut [MaybeUninit]) -> Self { sys::set_msghdr_control(&mut self.inner, buf.as_mut_ptr().cast(), buf.len()); self } /// Returns the flags of the message. pub fn flags(&self) -> RecvFlags { sys::msghdr_flags(&self.inner) } /// Gets the length of the control buffer. /// /// Can be used to determine how much, if any, of the control buffer was filled by `recvmsg`. /// /// Corresponds to `msg_controllen` on Unix and `Control.len` on Windows. pub fn control_len(&self) -> usize { sys::msghdr_control_len(&self.inner) } } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] impl<'name, 'bufs, 'control> fmt::Debug for MsgHdrMut<'name, 'bufs, 'control> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { "MsgHdrMut".fmt(fmt) } } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] unsafe impl Send for MsgHdrMut<'_, '_, '_> {} ================================================ FILE: src/sockaddr.rs ================================================ use std::hash::Hash; use std::mem::{self, size_of}; use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; #[cfg(not(target_os = "wasi"))] use std::path::Path; use std::{fmt, io, ptr}; #[cfg(windows)] use windows_sys::Win32::Networking::WinSock::SOCKADDR_IN6_0; #[cfg(not(target_os = "wasi"))] use crate::sys::AF_UNIX; use crate::sys::{c_int, sockaddr_in, sockaddr_in6, sockaddr_storage, AF_INET, AF_INET6}; use crate::Domain; /// The integer type used with `getsockname` on this platform. #[allow(non_camel_case_types)] pub type socklen_t = crate::sys::socklen_t; /// The integer type for the `ss_family` field on this platform. #[allow(non_camel_case_types)] pub type sa_family_t = crate::sys::sa_family_t; /// Rust version of the [`sockaddr_storage`] type. /// /// This type is intended to be used with with direct calls to the `getsockname` syscall. See the /// documentation of [`SockAddr::new`] for examples. /// /// This crate defines its own `sockaddr_storage` type to avoid semver concerns with upgrading /// `windows-sys`. #[repr(transparent)] pub struct SockAddrStorage { storage: sockaddr_storage, } impl SockAddrStorage { /// Construct a new storage containing all zeros. #[inline] pub fn zeroed() -> Self { // SAFETY: All zeros is valid for this type. unsafe { mem::zeroed() } } /// Returns the size of this storage. #[inline] pub fn size_of(&self) -> socklen_t { size_of::() as socklen_t } /// View this type as another type. /// /// # Safety /// /// The type `T` must be one of the `sockaddr_*` types defined by this platform. /// /// # Examples /// ``` /// # #[allow(dead_code)] /// # #[cfg(unix)] mod unix_example { /// # use core::mem::size_of; /// use libc::sockaddr_storage; /// use socket2::{SockAddr, SockAddrStorage, socklen_t}; /// /// fn from_sockaddr_storage(recv_address: &sockaddr_storage) -> SockAddr { /// let mut storage = SockAddrStorage::zeroed(); /// let libc_address = unsafe { storage.view_as::() }; /// *libc_address = *recv_address; /// unsafe { SockAddr::new(storage, size_of::() as socklen_t) } /// } /// # } /// ``` #[inline] pub unsafe fn view_as(&mut self) -> &mut T { assert!(size_of::() <= size_of::()); // SAFETY: This type is repr(transparent) over `sockaddr_storage` and `T` is one of the // `sockaddr_*` types defined by this platform. &mut *(self as *mut Self as *mut T) } } impl std::fmt::Debug for SockAddrStorage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("sockaddr_storage") .field("ss_family", &self.storage.ss_family) .finish_non_exhaustive() } } /// The address of a socket. /// /// `SockAddr`s may be constructed directly to and from the standard library /// [`SocketAddr`], [`SocketAddrV4`], and [`SocketAddrV6`] types. #[derive(Clone)] pub struct SockAddr { storage: sockaddr_storage, len: socklen_t, } #[allow(clippy::len_without_is_empty)] impl SockAddr { /// Create a `SockAddr` from the underlying storage and its length. /// /// # Safety /// /// Caller must ensure that the address family and length match the type of /// storage address. For example if `storage.ss_family` is set to `AF_INET` /// the `storage` must be initialised as `sockaddr_in`, setting the content /// and length appropriately. /// /// # Examples /// /// ``` /// # fn main() -> std::io::Result<()> { /// # #[cfg(unix)] { /// use std::io; /// use std::os::fd::AsRawFd; /// /// use socket2::{SockAddr, SockAddrStorage, Socket, Domain, Type}; /// /// let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?; /// /// // Initialise a `SocketAddr` by calling `getsockname(2)`. /// let mut addr_storage = SockAddrStorage::zeroed(); /// let mut len = addr_storage.size_of(); /// /// // The `getsockname(2)` system call will initialize `storage` for /// // us, setting `len` to the correct length. /// let res = unsafe { /// libc::getsockname( /// socket.as_raw_fd(), /// addr_storage.view_as(), /// &mut len, /// ) /// }; /// if res == -1 { /// return Err(io::Error::last_os_error()); /// } /// /// let address = unsafe { SockAddr::new(addr_storage, len) }; /// # drop(address); /// # } /// # Ok(()) /// # } /// ``` pub const unsafe fn new(storage: SockAddrStorage, len: socklen_t) -> SockAddr { SockAddr { storage: storage.storage, len: len as socklen_t, } } /// Initialise a `SockAddr` by calling the function `init`. /// /// The type of the address storage and length passed to the function `init` /// is OS/architecture specific. /// /// The address is zeroed before `init` is called and is thus valid to /// dereference and read from. The length initialised to the maximum length /// of the storage. /// /// # Safety /// /// Caller must ensure that the address family and length match the type of /// storage address. For example if `storage.ss_family` is set to `AF_INET` /// the `storage` must be initialised as `sockaddr_in`, setting the content /// and length appropriately. /// /// # Examples /// /// ``` /// # fn main() -> std::io::Result<()> { /// # #[cfg(unix)] { /// use std::io; /// use std::os::fd::AsRawFd; /// /// use socket2::{SockAddr, Socket, Domain, Type}; /// /// let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?; /// /// // Initialise a `SocketAddr` by calling `getsockname(2)`. /// let (_, address) = unsafe { /// SockAddr::try_init(|addr_storage, len| { /// // The `getsockname(2)` system call will initialize `storage` for /// // us, setting `len` to the correct length. /// if libc::getsockname(socket.as_raw_fd(), addr_storage.cast(), len) == -1 { /// Err(io::Error::last_os_error()) /// } else { /// Ok(()) /// } /// }) /// }?; /// # drop(address); /// # } /// # Ok(()) /// # } /// ``` pub unsafe fn try_init(init: F) -> io::Result<(T, SockAddr)> where F: FnOnce(*mut SockAddrStorage, *mut socklen_t) -> io::Result, { const STORAGE_SIZE: socklen_t = size_of::() as socklen_t; // NOTE: `SockAddr::unix` depends on the storage being zeroed before // calling `init`. // NOTE: calling `recvfrom` with an empty buffer also depends on the // storage being zeroed before calling `init` as the OS might not // initialise it. let mut storage = SockAddrStorage::zeroed(); let mut len = STORAGE_SIZE; init(&mut storage, &mut len).map(|res| { debug_assert!(len <= STORAGE_SIZE, "overflown address storage"); (res, SockAddr::new(storage, len)) }) } /// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path. /// /// Returns an error if the path is longer than `SUN_LEN`. #[cfg(not(target_os = "wasi"))] pub fn unix

(path: P) -> io::Result where P: AsRef, { crate::sys::unix_sockaddr(path.as_ref()) } /// Set the length of the address. /// /// # Safety /// /// Caller must ensure that the address up to `length` bytes are properly /// initialised. pub unsafe fn set_length(&mut self, length: socklen_t) { self.len = length; } /// Returns this address's family. pub const fn family(&self) -> sa_family_t { self.storage.ss_family } /// Returns this address's `Domain`. pub const fn domain(&self) -> Domain { Domain(self.storage.ss_family as c_int) } /// Returns the size of this address in bytes. pub const fn len(&self) -> socklen_t { self.len } /// Returns a raw pointer to the address. pub const fn as_ptr(&self) -> *const SockAddrStorage { &self.storage as *const sockaddr_storage as *const SockAddrStorage } /// Returns the address as the storage. pub const fn as_storage(self) -> SockAddrStorage { SockAddrStorage { storage: self.storage, } } /// Returns true if this address is in the `AF_INET` (IPv4) family, false otherwise. pub const fn is_ipv4(&self) -> bool { self.storage.ss_family == AF_INET as sa_family_t } /// Returns true if this address is in the `AF_INET6` (IPv6) family, false /// otherwise. pub const fn is_ipv6(&self) -> bool { self.storage.ss_family == AF_INET6 as sa_family_t } /// Returns true if this address is of a unix socket (for local interprocess communication), /// i.e. it is from the `AF_UNIX` family, false otherwise. #[cfg(not(target_os = "wasi"))] pub fn is_unix(&self) -> bool { self.storage.ss_family == AF_UNIX as sa_family_t } /// Returns this address as a `SocketAddr` if it is in the `AF_INET` (IPv4) /// or `AF_INET6` (IPv6) family, otherwise returns `None`. pub fn as_socket(&self) -> Option { if self.storage.ss_family == AF_INET as sa_family_t { // SAFETY: if the `ss_family` field is `AF_INET` then storage must // be a `sockaddr_in`. let addr = unsafe { &*(ptr::addr_of!(self.storage).cast::()) }; let ip = crate::sys::from_in_addr(addr.sin_addr); let port = u16::from_be(addr.sin_port); Some(SocketAddr::V4(SocketAddrV4::new(ip, port))) } else if self.storage.ss_family == AF_INET6 as sa_family_t { // SAFETY: if the `ss_family` field is `AF_INET6` then storage must // be a `sockaddr_in6`. let addr = unsafe { &*(ptr::addr_of!(self.storage).cast::()) }; let ip = crate::sys::from_in6_addr(addr.sin6_addr); let port = u16::from_be(addr.sin6_port); Some(SocketAddr::V6(SocketAddrV6::new( ip, port, addr.sin6_flowinfo, #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] addr.sin6_scope_id, #[cfg(windows)] unsafe { addr.Anonymous.sin6_scope_id }, ))) } else { None } } /// Returns this address as a [`SocketAddrV4`] if it is in the `AF_INET` /// family. pub fn as_socket_ipv4(&self) -> Option { match self.as_socket() { Some(SocketAddr::V4(addr)) => Some(addr), _ => None, } } /// Returns this address as a [`SocketAddrV6`] if it is in the `AF_INET6` /// family. pub fn as_socket_ipv6(&self) -> Option { match self.as_socket() { Some(SocketAddr::V6(addr)) => Some(addr), _ => None, } } /// Returns the initialised storage bytes. fn as_bytes(&self) -> &[u8] { // SAFETY: `self.storage` is a C struct which can always be treated a // slice of bytes. Furthermore, we ensure we don't read any uninitialised // bytes by using `self.len`. unsafe { std::slice::from_raw_parts(self.as_ptr().cast(), self.len as usize) } } } impl From for SockAddr { fn from(addr: SocketAddr) -> SockAddr { match addr { SocketAddr::V4(addr) => addr.into(), SocketAddr::V6(addr) => addr.into(), } } } impl From for SockAddr { fn from(addr: SocketAddrV4) -> SockAddr { // SAFETY: a `sockaddr_storage` of all zeros is valid. let mut storage = unsafe { mem::zeroed::() }; let len = { let storage = unsafe { &mut *ptr::addr_of_mut!(storage).cast::() }; storage.sin_family = AF_INET as sa_family_t; storage.sin_port = addr.port().to_be(); storage.sin_addr = crate::sys::to_in_addr(addr.ip()); #[cfg(not(target_os = "wasi"))] { storage.sin_zero = Default::default(); } mem::size_of::() as socklen_t }; #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "haiku", target_os = "hermit", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "nto", target_os = "openbsd", target_os = "tvos", target_os = "vxworks", target_os = "watchos", ))] { storage.ss_len = len as u8; } SockAddr { storage, len } } } impl From for SockAddr { fn from(addr: SocketAddrV6) -> SockAddr { // SAFETY: a `sockaddr_storage` of all zeros is valid. let mut storage = unsafe { mem::zeroed::() }; let len = { let storage = unsafe { &mut *ptr::addr_of_mut!(storage).cast::() }; storage.sin6_family = AF_INET6 as sa_family_t; storage.sin6_port = addr.port().to_be(); storage.sin6_addr = crate::sys::to_in6_addr(addr.ip()); storage.sin6_flowinfo = addr.flowinfo(); #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] { storage.sin6_scope_id = addr.scope_id(); } #[cfg(windows)] { storage.Anonymous = SOCKADDR_IN6_0 { sin6_scope_id: addr.scope_id(), }; } mem::size_of::() as socklen_t }; #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "haiku", target_os = "hermit", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "nto", target_os = "openbsd", target_os = "tvos", target_os = "vxworks", target_os = "watchos", ))] { storage.ss_len = len as u8; } SockAddr { storage, len } } } impl fmt::Debug for SockAddr { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = fmt.debug_struct("SockAddr"); #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "haiku", target_os = "hermit", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "nto", target_os = "openbsd", target_os = "tvos", target_os = "vxworks", target_os = "watchos", ))] f.field("ss_len", &self.storage.ss_len); f.field("ss_family", &self.storage.ss_family) .field("len", &self.len) .finish() } } impl PartialEq for SockAddr { fn eq(&self, other: &Self) -> bool { self.as_bytes() == other.as_bytes() } } impl Eq for SockAddr {} impl Hash for SockAddr { fn hash(&self, state: &mut H) { self.as_bytes().hash(state); } } #[cfg(test)] mod tests { use super::*; #[test] fn ipv4() { use std::net::Ipv4Addr; let std = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let addr = SockAddr::from(std); assert!(addr.is_ipv4()); assert!(!addr.is_ipv6()); #[cfg(not(target_os = "wasi"))] assert!(!addr.is_unix()); assert_eq!(addr.family(), AF_INET as sa_family_t); assert_eq!(addr.domain(), Domain::IPV4); assert_eq!(addr.len(), size_of::() as socklen_t); assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std))); assert_eq!(addr.as_socket_ipv4(), Some(std)); assert!(addr.as_socket_ipv6().is_none()); let addr = SockAddr::from(SocketAddr::from(std)); assert_eq!(addr.family(), AF_INET as sa_family_t); assert_eq!(addr.len(), size_of::() as socklen_t); assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std))); assert_eq!(addr.as_socket_ipv4(), Some(std)); assert!(addr.as_socket_ipv6().is_none()); #[cfg(all(unix, not(target_os = "wasi")))] { assert!(addr.as_pathname().is_none()); assert!(addr.as_abstract_namespace().is_none()); } } #[test] fn ipv6() { use std::net::Ipv6Addr; let std = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); let addr = SockAddr::from(std); assert!(addr.is_ipv6()); assert!(!addr.is_ipv4()); #[cfg(not(target_os = "wasi"))] assert!(!addr.is_unix()); assert_eq!(addr.family(), AF_INET6 as sa_family_t); assert_eq!(addr.domain(), Domain::IPV6); assert_eq!(addr.len(), size_of::() as socklen_t); assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std))); assert!(addr.as_socket_ipv4().is_none()); assert_eq!(addr.as_socket_ipv6(), Some(std)); let addr = SockAddr::from(SocketAddr::from(std)); assert_eq!(addr.family(), AF_INET6 as sa_family_t); assert_eq!(addr.len(), size_of::() as socklen_t); assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std))); assert!(addr.as_socket_ipv4().is_none()); assert_eq!(addr.as_socket_ipv6(), Some(std)); #[cfg(all(unix, not(target_os = "wasi")))] { assert!(addr.as_pathname().is_none()); assert!(addr.as_abstract_namespace().is_none()); } } #[test] fn ipv4_eq() { use std::net::Ipv4Addr; let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let std2 = SocketAddrV4::new(Ipv4Addr::new(5, 6, 7, 8), 8765); test_eq( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); } #[test] fn ipv4_hash() { use std::net::Ipv4Addr; let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let std2 = SocketAddrV4::new(Ipv4Addr::new(5, 6, 7, 8), 8765); test_hash( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); } #[test] fn ipv6_eq() { use std::net::Ipv6Addr; let std1 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); let std2 = SocketAddrV6::new(Ipv6Addr::new(3, 4, 5, 6, 7, 8, 9, 0), 7654, 13, 14); test_eq( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); } #[test] fn ipv6_hash() { use std::net::Ipv6Addr; let std1 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); let std2 = SocketAddrV6::new(Ipv6Addr::new(3, 4, 5, 6, 7, 8, 9, 0), 7654, 13, 14); test_hash( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); } #[test] fn ipv4_ipv6_eq() { use std::net::Ipv4Addr; use std::net::Ipv6Addr; let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let std2 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); test_eq( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); test_eq( SockAddr::from(std2), SockAddr::from(std2), SockAddr::from(std1), ); } #[test] fn ipv4_ipv6_hash() { use std::net::Ipv4Addr; use std::net::Ipv6Addr; let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876); let std2 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12); test_hash( SockAddr::from(std1), SockAddr::from(std1), SockAddr::from(std2), ); test_hash( SockAddr::from(std2), SockAddr::from(std2), SockAddr::from(std1), ); } #[allow(clippy::eq_op)] // allow a0 == a0 check fn test_eq(a0: SockAddr, a1: SockAddr, b: SockAddr) { assert!(a0 == a0); assert!(a0 == a1); assert!(a1 == a0); assert!(a0 != b); assert!(b != a0); } fn test_hash(a0: SockAddr, a1: SockAddr, b: SockAddr) { assert!(calculate_hash(&a0) == calculate_hash(&a0)); assert!(calculate_hash(&a0) == calculate_hash(&a1)); // technically unequal values can have the same hash, in this case x != z and both have different hashes assert!(calculate_hash(&a0) != calculate_hash(&b)); } fn calculate_hash(x: &SockAddr) -> u64 { use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; let mut hasher = DefaultHasher::new(); x.hash(&mut hasher); hasher.finish() } } ================================================ FILE: src/socket.rs ================================================ // Copyright 2015 The Rust Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::fmt; use std::io::{self, Read, Write}; #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] use std::io::{IoSlice, IoSliceMut}; use std::mem::MaybeUninit; #[cfg(not(target_os = "nto"))] use std::net::Ipv6Addr; use std::net::{self, Ipv4Addr, Shutdown}; #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] use std::os::fd::{FromRawFd, IntoRawFd}; #[cfg(windows)] use std::os::windows::io::{FromRawSocket, IntoRawSocket}; use std::time::Duration; use crate::sys::{self, c_int, getsockopt, setsockopt, Bool}; #[cfg(all(unix, not(any(target_os = "redox", target_os = "horizon"))))] use crate::MsgHdrMut; use crate::{Domain, Protocol, SockAddr, TcpKeepalive, Type}; #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] use crate::{MaybeUninitSlice, MsgHdr, RecvFlags}; /// Owned wrapper around a system socket. /// /// This type simply wraps an instance of a file descriptor (`c_int`) on Unix /// and an instance of `SOCKET` on Windows. This is the main type exported by /// this crate and is intended to mirror the raw semantics of sockets on /// platforms as closely as possible. Almost all methods correspond to /// precisely one libc or OS API call which is essentially just a "Rustic /// translation" of what's below. /// /// ## Converting to and from other types /// /// This type can be freely converted into the network primitives provided by /// the standard library, such as [`TcpStream`] or [`UdpSocket`], using the /// [`From`] trait, see the example below. /// /// [`TcpStream`]: std::net::TcpStream /// [`UdpSocket`]: std::net::UdpSocket /// /// # Notes /// /// Some methods that set options on `Socket` require two system calls to set /// their options without overwriting previously set options. We do this by /// first getting the current settings, applying the desired changes, and then /// updating the settings. This means that the operation is **not** atomic. This /// can lead to a data race when two threads are changing options in parallel. /// /// # Examples /// ```no_run /// # fn main() -> std::io::Result<()> { /// use std::net::{SocketAddr, TcpListener}; /// use socket2::{Socket, Domain, Type}; /// /// // create a TCP listener /// let socket = Socket::new(Domain::IPV6, Type::STREAM, None)?; /// /// let address: SocketAddr = "[::1]:12345".parse().unwrap(); /// let address = address.into(); /// socket.bind(&address)?; /// socket.listen(128)?; /// /// let listener: TcpListener = socket.into(); /// // ... /// # drop(listener); /// # Ok(()) } /// ``` pub struct Socket { inner: sys::Socket, } impl Socket { /// # Safety /// /// The caller must ensure `raw` is a valid file descriptor/socket. NOTE: /// this should really be marked `unsafe`, but this being an internal /// function, often passed as mapping function, it's makes it very /// inconvenient to mark it as `unsafe`. pub(crate) fn from_raw(raw: sys::RawSocket) -> Socket { Socket { // SAFETY: the caller must ensure that `raw` is a valid file // descriptor, but when it isn't it could return I/O errors, or // potentially close a fd it doesn't own. All of that isn't memory // unsafe, so it's not desired but never memory unsafe or causes UB. inner: unsafe { sys::socket_from_raw(raw) }, } } pub(crate) fn as_raw(&self) -> sys::RawSocket { sys::socket_as_raw(&self.inner) } pub(crate) fn into_raw(self) -> sys::RawSocket { sys::socket_into_raw(self.inner) } /// Creates a new socket and sets common flags. /// /// This function corresponds to `socket(2)` on Unix and `WSASocketW` on /// Windows. /// /// On Unix-like systems, the close-on-exec flag is set on the new socket. /// Additionally, on Apple platforms `SOCK_NOSIGPIPE` is set. On Windows, /// the socket is made non-inheritable. /// /// [`Socket::new_raw`] can be used if you don't want these flags to be set. #[doc = man_links!(socket(2))] pub fn new(domain: Domain, ty: Type, protocol: Option) -> io::Result { let ty = set_common_type(ty); Socket::new_raw(domain, ty, protocol).and_then(set_common_flags) } /// 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. pub fn new_raw(domain: Domain, ty: Type, protocol: Option) -> io::Result { let protocol = protocol.map_or(0, |p| p.0); sys::socket(domain.0, ty.0, protocol).map(Socket::from_raw) } /// Creates a pair of sockets which are connected to each other. /// /// This function corresponds to `socketpair(2)`. /// /// This function sets the same flags as in done for [`Socket::new`], /// [`Socket::pair_raw`] can be used if you don't want to set those flags. #[doc = man_links!(unix: socketpair(2))] #[cfg(all(feature = "all", unix))] pub fn pair( domain: Domain, ty: Type, protocol: Option, ) -> io::Result<(Socket, Socket)> { let ty = set_common_type(ty); let (a, b) = Socket::pair_raw(domain, ty, protocol)?; let a = set_common_flags(a)?; let b = set_common_flags(b)?; Ok((a, b)) } /// Creates a pair of sockets which are connected to each other. /// /// This function corresponds to `socketpair(2)`. #[cfg(all(feature = "all", unix))] pub fn pair_raw( domain: Domain, ty: Type, protocol: Option, ) -> io::Result<(Socket, Socket)> { let protocol = protocol.map_or(0, |p| p.0); sys::socketpair(domain.0, ty.0, protocol) .map(|[a, b]| (Socket::from_raw(a), Socket::from_raw(b))) } /// Binds this socket to the specified address. /// /// This function directly corresponds to the `bind(2)` function on Windows /// and Unix. #[doc = man_links!(bind(2))] pub fn bind(&self, address: &SockAddr) -> io::Result<()> { sys::bind(self.as_raw(), address) } /// Initiate a connection on this socket to the specified address. /// /// This function directly corresponds to the `connect(2)` function on /// Windows and Unix. /// /// An error will be returned if `listen` or `connect` has already been /// called on this builder. #[doc = man_links!(connect(2))] /// /// # Notes /// /// When using a non-blocking connect (by setting the socket into /// 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) } /// Initiate a connection on this socket to the specified address, only /// only waiting for a certain period of time for the connection to be /// established. /// /// Unlike many other methods on `Socket`, this does *not* correspond to a /// single C function. It sets the socket to nonblocking mode, connects via /// connect(2), and then waits for the connection to complete with poll(2) /// on Unix and select on Windows. When the connection is complete, the /// socket is set back to blocking mode. On Unix, this will loop over /// `EINTR` errors. /// /// # Warnings /// /// The non-blocking state of the socket is overridden by this function - /// it will be returned in blocking mode on success, and in an indeterminate /// state on failure. /// /// If the connection request times out, it may still be processing in the /// background - a second call to `connect` or `connect_timeout` may fail. pub fn connect_timeout(&self, addr: &SockAddr, timeout: Duration) -> io::Result<()> { self.set_nonblocking(true)?; let res = self.connect(addr); self.set_nonblocking(false)?; match res { Ok(()) => return Ok(()), Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {} #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] Err(ref e) if e.raw_os_error() == Some(libc::EINPROGRESS) => {} Err(e) => return Err(e), } sys::poll_connect(self, timeout) } /// Mark a socket as ready to accept incoming connection requests using /// [`Socket::accept()`]. /// /// This function directly corresponds to the `listen(2)` function on /// Windows and Unix. /// /// An error will be returned if `listen` or `connect` has already been /// called on this builder. #[doc = man_links!(listen(2))] pub fn listen(&self, backlog: c_int) -> io::Result<()> { sys::listen(self.as_raw(), backlog) } /// Accept a new incoming connection from this listener. /// /// This function uses `accept4(2)` on platforms that support it and /// `accept(2)` platforms that do not. /// /// 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( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", ))] return self._accept4(libc::SOCK_CLOEXEC); // Fall back to `accept` on platforms that do not support `accept4`. #[cfg(not(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", )))] { let (socket, addr) = self.accept_raw()?; let socket = set_common_accept_flags(socket)?; // `set_common_flags` does not disable inheritance on Windows because `Socket::new` // unlike `accept` is able to create the socket with inheritance disabled. #[cfg(windows)] socket._set_no_inherit(true)?; Ok((socket, addr)) } } /// Accept a new incoming connection from this listener. /// /// This function directly corresponds to the `accept(2)` function on /// Windows and Unix. pub fn accept_raw(&self) -> io::Result<(Socket, SockAddr)> { sys::accept(self.as_raw()).map(|(inner, addr)| (Socket::from_raw(inner), addr)) } /// Returns the socket address of the local half of this socket. /// /// This function directly corresponds to the `getsockname(2)` function on /// Windows and Unix. #[doc = man_links!(getsockname(2))] /// /// # Notes /// /// Depending on the OS this may return an error if the socket is not /// [bound]. /// /// [bound]: Socket::bind pub fn local_addr(&self) -> io::Result { sys::getsockname(self.as_raw()) } /// Returns the socket address of the remote peer of this socket. /// /// This function directly corresponds to the `getpeername(2)` function on /// Windows and Unix. #[doc = man_links!(getpeername(2))] /// /// # Notes /// /// This returns an error if the socket is not [`connect`ed]. /// /// [`connect`ed]: Socket::connect pub fn peer_addr(&self) -> io::Result { sys::getpeername(self.as_raw()) } /// Returns the [`Type`] of this socket by checking the `SO_TYPE` option on /// this socket. pub fn r#type(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_TYPE).map(Type) } } /// Creates a new independently owned handle to the underlying socket. /// /// # Notes /// /// On Unix this uses `F_DUPFD_CLOEXEC` and thus sets the `FD_CLOEXEC` on /// the returned socket. /// /// On Windows this uses `WSA_FLAG_NO_HANDLE_INHERIT` setting inheriting to /// false. /// /// On Windows this can **not** be used function cannot be used on a /// QOS-enabled socket, see /// . #[cfg(not(target_os = "wasi"))] pub fn try_clone(&self) -> io::Result { sys::try_clone(self.as_raw()).map(Socket::from_raw) } /// Returns true if this socket is set to nonblocking mode, false otherwise. /// /// # Notes /// /// On Unix this corresponds to calling `fcntl` returning the value of /// `O_NONBLOCK`. /// /// On Windows it is not possible retrieve the nonblocking mode status. #[cfg(all( feature = "all", any(unix, all(target_os = "wasi", not(target_env = "p1"))) ))] pub fn nonblocking(&self) -> io::Result { sys::nonblocking(self.as_raw()) } /// Moves this socket into or out of nonblocking mode. /// /// # Notes /// /// On Unix this corresponds to calling `fcntl` (un)setting `O_NONBLOCK`. /// /// On Windows this corresponds to calling `ioctlsocket` (un)setting /// `FIONBIO`. pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { sys::set_nonblocking(self.as_raw(), nonblocking) } /// Shuts down the read, write, or both halves of this connection. /// /// This function will cause all pending and future I/O on the specified /// portions to return immediately with an appropriate value. #[doc = man_links!(shutdown(2))] pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { sys::shutdown(self.as_raw(), how) } /// Receives data on the socket from the remote address to which it is /// connected. /// /// The [`connect`] method will connect this socket to a remote address. /// This method might fail if the socket is not connected. #[doc = man_links!(recv(2))] /// /// [`connect`]: Socket::connect /// /// # Safety /// /// Normally casting a `&mut [u8]` to `&mut [MaybeUninit]` would be /// unsound, as that allows us to write uninitialised bytes to the buffer. /// However this implementation promises to not write uninitialised bytes to /// the `buf`fer and passes it directly to `recv(2)` system call. This /// promise ensures that this function can be called using a `buf`fer of /// type `&mut [u8]`. /// /// Note that the [`io::Read::read`] implementation calls this function with /// a `buf`fer of type `&mut [u8]`, allowing initialised buffers to be used /// without using `unsafe`. pub fn recv(&self, buf: &mut [MaybeUninit]) -> io::Result { self.recv_with_flags(buf, 0) } /// Receives out-of-band (OOB) data on the socket from the remote address to /// which it is connected by setting the `MSG_OOB` flag for this call. /// /// For more information, see [`recv`], [`out_of_band_inline`]. /// /// [`recv`]: Socket::recv /// [`out_of_band_inline`]: Socket::out_of_band_inline #[cfg_attr(target_os = "redox", allow(rustdoc::broken_intra_doc_links))] #[cfg(not(target_os = "wasi"))] pub fn recv_out_of_band(&self, buf: &mut [MaybeUninit]) -> io::Result { self.recv_with_flags(buf, sys::MSG_OOB) } /// Identical to [`recv`] but allows for specification of arbitrary flags to /// the underlying `recv` call. /// /// [`recv`]: Socket::recv pub fn recv_with_flags( &self, buf: &mut [MaybeUninit], flags: sys::c_int, ) -> io::Result { sys::recv(self.as_raw(), buf, flags) } /// Receives data on the socket from the remote address to which it is /// connected. Unlike [`recv`] this allows passing multiple buffers. /// /// The [`connect`] method will connect this socket to a remote address. /// This method might fail if the socket is not connected. /// /// In addition to the number of bytes read, this function returns the flags /// for the received message. See [`RecvFlags`] for more information about /// the returned flags. #[doc = man_links!(recvmsg(2))] /// /// [`recv`]: Socket::recv /// [`connect`]: Socket::connect /// /// # Safety /// /// Normally casting a `IoSliceMut` to `MaybeUninitSlice` would be unsound, /// as that allows us to write uninitialised bytes to the buffer. However /// this implementation promises to not write uninitialised bytes to the /// `bufs` and passes it directly to `recvmsg(2)` system call. This promise /// ensures that this function can be called using `bufs` of type `&mut /// [IoSliceMut]`. /// /// Note that the [`io::Read::read_vectored`] implementation calls this /// function with `buf`s of type `&mut [IoSliceMut]`, allowing initialised /// buffers to be used without using `unsafe`. #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn recv_vectored( &self, bufs: &mut [MaybeUninitSlice<'_>], ) -> io::Result<(usize, RecvFlags)> { self.recv_vectored_with_flags(bufs, 0) } /// Identical to [`recv_vectored`] but allows for specification of arbitrary /// flags to the underlying `recvmsg`/`WSARecv` call. /// /// [`recv_vectored`]: Socket::recv_vectored /// /// # Safety /// /// `recv_from_vectored` makes the same safety guarantees regarding `bufs` /// as [`recv_vectored`]. /// /// [`recv_vectored`]: Socket::recv_vectored #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn recv_vectored_with_flags( &self, bufs: &mut [MaybeUninitSlice<'_>], flags: c_int, ) -> io::Result<(usize, RecvFlags)> { sys::recv_vectored(self.as_raw(), bufs, flags) } /// Receives data on the socket from the remote address to which it is /// connected, without removing that data from the queue. On success, /// returns the number of bytes peeked. /// /// Successive calls return the same data. This is accomplished by passing /// `MSG_PEEK` as a flag to the underlying `recv` system call. /// /// # Safety /// /// `peek` makes the same safety guarantees regarding the `buf`fer as /// [`recv`]. /// /// [`recv`]: Socket::recv pub fn peek(&self, buf: &mut [MaybeUninit]) -> io::Result { self.recv_with_flags(buf, sys::MSG_PEEK) } /// Receives data from the socket. On success, returns the number of bytes /// read and the address from whence the data came. #[doc = man_links!(recvfrom(2))] /// /// # Safety /// /// `recv_from` makes the same safety guarantees regarding the `buf`fer as /// [`recv`]. /// /// [`recv`]: Socket::recv pub fn recv_from(&self, buf: &mut [MaybeUninit]) -> io::Result<(usize, SockAddr)> { self.recv_from_with_flags(buf, 0) } /// Identical to [`recv_from`] but allows for specification of arbitrary /// flags to the underlying `recvfrom` call. /// /// [`recv_from`]: Socket::recv_from pub fn recv_from_with_flags( &self, buf: &mut [MaybeUninit], flags: c_int, ) -> io::Result<(usize, SockAddr)> { sys::recv_from(self.as_raw(), buf, flags) } /// Receives data from the socket. Returns the amount of bytes read, the /// [`RecvFlags`] and the remote address from the data is coming. Unlike /// [`recv_from`] this allows passing multiple buffers. #[doc = man_links!(recvmsg(2))] /// /// [`recv_from`]: Socket::recv_from /// /// # Safety /// /// `recv_from_vectored` makes the same safety guarantees regarding `bufs` /// as [`recv_vectored`]. /// /// [`recv_vectored`]: Socket::recv_vectored #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn recv_from_vectored( &self, bufs: &mut [MaybeUninitSlice<'_>], ) -> io::Result<(usize, RecvFlags, SockAddr)> { self.recv_from_vectored_with_flags(bufs, 0) } /// Identical to [`recv_from_vectored`] but allows for specification of /// arbitrary flags to the underlying `recvmsg`/`WSARecvFrom` call. /// /// [`recv_from_vectored`]: Socket::recv_from_vectored /// /// # Safety /// /// `recv_from_vectored` makes the same safety guarantees regarding `bufs` /// as [`recv_vectored`]. /// /// [`recv_vectored`]: Socket::recv_vectored #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn recv_from_vectored_with_flags( &self, bufs: &mut [MaybeUninitSlice<'_>], flags: c_int, ) -> io::Result<(usize, RecvFlags, SockAddr)> { sys::recv_from_vectored(self.as_raw(), bufs, flags) } /// Receives data from the socket, without removing it from the queue. /// /// Successive calls return the same data. This is accomplished by passing /// `MSG_PEEK` as a flag to the underlying `recvfrom` system call. /// /// On success, returns the number of bytes peeked and the address from /// whence the data came. /// /// # Safety /// /// `peek_from` makes the same safety guarantees regarding the `buf`fer as /// [`recv`]. /// /// # Note: Datagram Sockets /// For datagram sockets, the behavior of this method when `buf` is smaller than /// the datagram at the head of the receive queue differs between Windows and /// Unix-like platforms (Linux, macOS, BSDs, etc: colloquially termed "*nix"). /// /// On *nix platforms, the datagram is truncated to the length of `buf`. /// /// On Windows, an error corresponding to `WSAEMSGSIZE` will be returned. /// /// For consistency between platforms, be sure to provide a sufficiently large buffer to avoid /// truncation; the exact size required depends on the underlying protocol. /// /// If you just want to know the sender of the data, try [`peek_sender`]. /// /// [`recv`]: Socket::recv /// [`peek_sender`]: Socket::peek_sender pub fn peek_from(&self, buf: &mut [MaybeUninit]) -> io::Result<(usize, SockAddr)> { self.recv_from_with_flags(buf, sys::MSG_PEEK) } /// Retrieve the sender for the data at the head of the receive queue. /// /// This is equivalent to calling [`peek_from`] with a zero-sized buffer, /// but suppresses the `WSAEMSGSIZE` error on Windows. /// /// [`peek_from`]: Socket::peek_from pub fn peek_sender(&self) -> io::Result { sys::peek_sender(self.as_raw()) } /// Receive a message from a socket using a message structure. /// /// This is not supported on Windows as calling `WSARecvMsg` (the `recvmsg` /// equivalent) is not straight forward on Windows. See /// /// for an example (in C++). #[doc = man_links!(recvmsg(2))] #[cfg(all(unix, not(any(target_os = "redox", target_os = "horizon"))))] pub fn recvmsg(&self, msg: &mut MsgHdrMut<'_, '_, '_>, flags: sys::c_int) -> io::Result { sys::recvmsg(self.as_raw(), msg, flags) } /// Sends data on the socket to a connected peer. /// /// This is typically used on TCP sockets or datagram sockets which have /// been connected. /// /// On success returns the number of bytes that were sent. #[doc = man_links!(send(2))] pub fn send(&self, buf: &[u8]) -> io::Result { self.send_with_flags(buf, 0) } /// Identical to [`send`] but allows for specification of arbitrary flags to the underlying /// `send` call. /// /// [`send`]: Socket::send pub fn send_with_flags(&self, buf: &[u8], flags: c_int) -> io::Result { sys::send(self.as_raw(), buf, flags) } /// Send data to the connected peer. Returns the amount of bytes written. #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn send_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { self.send_vectored_with_flags(bufs, 0) } /// Identical to [`send_vectored`] but allows for specification of arbitrary /// flags to the underlying `sendmsg`/`WSASend` call. #[doc = man_links!(sendmsg(2))] /// /// [`send_vectored`]: Socket::send_vectored #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn send_vectored_with_flags( &self, bufs: &[IoSlice<'_>], flags: c_int, ) -> io::Result { sys::send_vectored(self.as_raw(), bufs, flags) } /// Sends out-of-band (OOB) data on the socket to connected peer /// by setting the `MSG_OOB` flag for this call. /// /// For more information, see [`send`], [`out_of_band_inline`]. /// /// [`send`]: Socket::send /// [`out_of_band_inline`]: Socket::out_of_band_inline #[cfg_attr(target_os = "redox", allow(rustdoc::broken_intra_doc_links))] #[cfg(not(target_os = "wasi"))] pub fn send_out_of_band(&self, buf: &[u8]) -> io::Result { self.send_with_flags(buf, sys::MSG_OOB) } /// Sends data on the socket to the given address. On success, returns the /// number of bytes written. /// /// This is typically used on UDP or datagram-oriented sockets. #[doc = man_links!(sendto(2))] pub fn send_to(&self, buf: &[u8], addr: &SockAddr) -> io::Result { self.send_to_with_flags(buf, addr, 0) } /// Identical to [`send_to`] but allows for specification of arbitrary flags /// to the underlying `sendto` call. /// /// [`send_to`]: Socket::send_to pub fn send_to_with_flags( &self, buf: &[u8], addr: &SockAddr, flags: c_int, ) -> io::Result { sys::send_to(self.as_raw(), buf, addr, flags) } /// Send data to a peer listening on `addr`. Returns the amount of bytes /// written. #[doc = man_links!(sendmsg(2))] #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn send_to_vectored(&self, bufs: &[IoSlice<'_>], addr: &SockAddr) -> io::Result { self.send_to_vectored_with_flags(bufs, addr, 0) } /// Identical to [`send_to_vectored`] but allows for specification of /// arbitrary flags to the underlying `sendmsg`/`WSASendTo` call. /// /// [`send_to_vectored`]: Socket::send_to_vectored #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn send_to_vectored_with_flags( &self, bufs: &[IoSlice<'_>], addr: &SockAddr, flags: c_int, ) -> io::Result { sys::send_to_vectored(self.as_raw(), bufs, addr, flags) } /// Send a message on a socket using a message structure. #[doc = man_links!(sendmsg(2))] #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub fn sendmsg(&self, msg: &MsgHdr<'_, '_, '_>, flags: sys::c_int) -> io::Result { sys::sendmsg(self.as_raw(), msg, flags) } } /// Set `SOCK_CLOEXEC` and `NO_HANDLE_INHERIT` on the `ty`pe on platforms that /// support it. #[inline(always)] const fn set_common_type(ty: Type) -> Type { // On platforms that support it set `SOCK_CLOEXEC`. #[cfg(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", ))] let ty = ty._cloexec(); // On windows set `NO_HANDLE_INHERIT`. #[cfg(windows)] let ty = ty._no_inherit(); ty } /// Set `FD_CLOEXEC` and `NOSIGPIPE` on the `socket` for platforms that need it. /// /// Sockets created via `accept` should use `set_common_accept_flags` instead. fn set_common_flags(socket: Socket) -> io::Result { // On platforms that don't have `SOCK_CLOEXEC` use `FD_CLOEXEC`. #[cfg(all( unix, not(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "espidf", target_os = "vita", target_os = "cygwin", )) ))] socket._set_cloexec(true)?; // On Apple platforms set `NOSIGPIPE`. #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", ))] socket._set_nosigpipe(true)?; Ok(socket) } /// Set `FD_CLOEXEC` on the `socket` for platforms that need it. /// /// Unlike `set_common_flags` we don't set `NOSIGPIPE` as that is inherited from /// the listener. Furthermore, attempts to set it on a unix socket domain /// results in an error. #[cfg(not(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", )))] fn set_common_accept_flags(socket: Socket) -> io::Result { // On platforms that don't have `SOCK_CLOEXEC` use `FD_CLOEXEC`. #[cfg(all( unix, not(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "espidf", target_os = "vita", target_os = "cygwin", )) ))] socket._set_cloexec(true)?; Ok(socket) } /// A local interface specified by its index or an address assigned to it. /// /// `Index(0)` and `Address(Ipv4Addr::UNSPECIFIED)` are equivalent and indicate /// that an appropriate interface should be selected by the system. #[cfg(not(any( target_os = "haiku", target_os = "illumos", target_os = "netbsd", target_os = "redox", target_os = "solaris", target_os = "wasi", )))] #[derive(Debug, Copy, Clone)] pub enum InterfaceIndexOrAddress { /// An interface index. Index(u32), /// An address assigned to an interface. Address(Ipv4Addr), } /// Socket options get/set using `SOL_SOCKET`. /// /// Additional documentation can be found in documentation of the OS. /// * Linux: /// * Windows: impl Socket { /// Get the value of the `SO_BROADCAST` option for this socket. /// /// For more information about this option, see [`set_broadcast`]. /// /// [`set_broadcast`]: Socket::set_broadcast pub fn broadcast(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_BROADCAST) .map(|broadcast| broadcast != 0) } } /// Set the value of the `SO_BROADCAST` option for this socket. /// /// When enabled, this socket is allowed to send packets to a broadcast /// address. pub fn set_broadcast(&self, broadcast: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::SOL_SOCKET, sys::SO_BROADCAST, broadcast as c_int, ) } } /// Get the value of the `SO_ERROR` option on this socket. /// /// This will retrieve the stored error in the underlying socket, clearing /// the field in the process. This can be useful for checking errors between /// calls. pub fn take_error(&self) -> io::Result> { match unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_ERROR) } { Ok(0) => Ok(None), Ok(errno) => Ok(Some(io::Error::from_raw_os_error(errno))), Err(err) => Err(err), } } /// Get the value of the `SO_KEEPALIVE` option on this socket. /// /// For more information about this option, see [`set_keepalive`]. /// /// [`set_keepalive`]: Socket::set_keepalive pub fn keepalive(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_KEEPALIVE) .map(|keepalive| keepalive != false as Bool) } } /// Set value for the `SO_KEEPALIVE` option on this socket. /// /// Enable sending of keep-alive messages on connection-oriented sockets. pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::SOL_SOCKET, sys::SO_KEEPALIVE, keepalive as c_int, ) } } /// Get the value of the `SO_LINGER` option on this socket. /// /// For more information about this option, see [`set_linger`]. /// /// [`set_linger`]: Socket::set_linger pub fn linger(&self) -> io::Result> { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_LINGER) .map(from_linger) } } /// Set value for the `SO_LINGER` option on this socket. /// /// If `linger` is not `None`, a close(2) or shutdown(2) will not return /// until all queued messages for the socket have been successfully sent or /// the linger timeout has been reached. Otherwise, the call returns /// immediately and the closing is done in the background. When the socket /// is closed as part of exit(2), it always lingers in the background. /// /// # Notes /// /// On most OSs the duration only has a precision of seconds and will be /// silently truncated. /// /// On Apple platforms (e.g. macOS, iOS, etc) this uses `SO_LINGER_SEC`. pub fn set_linger(&self, linger: Option) -> io::Result<()> { let linger = into_linger(linger); unsafe { setsockopt(self.as_raw(), sys::SOL_SOCKET, sys::SO_LINGER, linger) } } /// Get value for the `SO_OOBINLINE` option on this socket. /// /// For more information about this option, see [`set_out_of_band_inline`]. /// /// [`set_out_of_band_inline`]: Socket::set_out_of_band_inline #[cfg(not(any(target_os = "redox", target_os = "wasi")))] pub fn out_of_band_inline(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_OOBINLINE) .map(|oob_inline| oob_inline != 0) } } /// Set value for the `SO_OOBINLINE` option on this socket. /// /// If this option is enabled, out-of-band data is directly placed into the /// receive data stream. Otherwise, out-of-band data is passed only when the /// `MSG_OOB` flag is set during receiving. As per RFC6093, TCP sockets /// using the Urgent mechanism are encouraged to set this flag. #[cfg(not(any(target_os = "redox", target_os = "wasi")))] pub fn set_out_of_band_inline(&self, oob_inline: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::SOL_SOCKET, sys::SO_OOBINLINE, oob_inline as c_int, ) } } /// Get value for the `SO_PASSCRED` option on this socket. /// /// For more information about this option, see [`set_passcred`]. /// /// [`set_passcred`]: Socket::set_passcred #[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) .map(|passcred| passcred != 0) } } /// Set value for the `SO_PASSCRED` option on this socket. /// /// If this option is enabled, enables the receiving of the `SCM_CREDENTIALS` /// control messages. #[cfg(any(target_os = "linux", target_os = "cygwin"))] pub fn set_passcred(&self, passcred: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::SOL_SOCKET, sys::SO_PASSCRED, passcred as c_int, ) } } /// Get value for the `SO_PRIORITY` option on this socket. /// /// For more information about this option, see [`set_priority`]. /// /// [`set_priority`]: Socket::set_priority #[cfg(all( feature = "all", any(target_os = "linux", target_os = "android", target_os = "fuchsia") ))] pub fn priority(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_PRIORITY) .map(|prio| prio as u32) } } /// Set value for the `SO_PRIORITY` option on this socket. /// /// Packets with a higher priority may be processed earlier depending on the selected device /// queueing discipline. #[cfg(all( feature = "all", any(target_os = "linux", target_os = "android", target_os = "fuchsia") ))] pub fn set_priority(&self, priority: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::SOL_SOCKET, sys::SO_PRIORITY, priority as c_int, ) } } /// Get value for the `SO_RCVBUF` option on this socket. /// /// For more information about this option, see [`set_recv_buffer_size`]. /// /// [`set_recv_buffer_size`]: Socket::set_recv_buffer_size pub fn recv_buffer_size(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_RCVBUF) .map(|size| size as usize) } } /// Set value for the `SO_RCVBUF` option on this socket. /// /// Changes the size of the operating system's receive buffer associated /// with the socket. pub fn set_recv_buffer_size(&self, size: usize) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::SOL_SOCKET, sys::SO_RCVBUF, size as c_int, ) } } /// Get value for the `SO_RCVTIMEO` option on this socket. /// /// If the returned timeout is `None`, then `read` and `recv` calls will /// block indefinitely. pub fn read_timeout(&self) -> io::Result> { sys::timeout_opt(self.as_raw(), sys::SOL_SOCKET, sys::SO_RCVTIMEO) } /// Set value for the `SO_RCVTIMEO` option on this socket. /// /// If `timeout` is `None`, then `read` and `recv` calls will block /// indefinitely. pub fn set_read_timeout(&self, duration: Option) -> io::Result<()> { sys::set_timeout_opt(self.as_raw(), sys::SOL_SOCKET, sys::SO_RCVTIMEO, duration) } /// Get the value of the `SO_REUSEADDR` option on this socket. /// /// For more information about this option, see [`set_reuse_address`]. /// /// [`set_reuse_address`]: Socket::set_reuse_address pub fn reuse_address(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_REUSEADDR) .map(|reuse| reuse != 0) } } /// Set value for the `SO_REUSEADDR` option on this socket. /// /// This indicates that further calls to `bind` may allow reuse of local /// addresses. For IPv4 sockets this means that a socket may bind even when /// there's a socket already listening on this port. pub fn set_reuse_address(&self, reuse: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::SOL_SOCKET, sys::SO_REUSEADDR, reuse as c_int, ) } } /// Get the value of the `SO_SNDBUF` option on this socket. /// /// For more information about this option, see [`set_send_buffer_size`]. /// /// [`set_send_buffer_size`]: Socket::set_send_buffer_size pub fn send_buffer_size(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_SNDBUF) .map(|size| size as usize) } } /// Set value for the `SO_SNDBUF` option on this socket. /// /// Changes the size of the operating system's send buffer associated with /// the socket. pub fn set_send_buffer_size(&self, size: usize) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::SOL_SOCKET, sys::SO_SNDBUF, size as c_int, ) } } /// Get value for the `SO_SNDTIMEO` option on this socket. /// /// If the returned timeout is `None`, then `write` and `send` calls will /// block indefinitely. pub fn write_timeout(&self) -> io::Result> { sys::timeout_opt(self.as_raw(), sys::SOL_SOCKET, sys::SO_SNDTIMEO) } /// Set value for the `SO_SNDTIMEO` option on this socket. /// /// If `timeout` is `None`, then `write` and `send` calls will block /// indefinitely. pub fn set_write_timeout(&self, duration: Option) -> io::Result<()> { sys::set_timeout_opt(self.as_raw(), sys::SOL_SOCKET, sys::SO_SNDTIMEO, duration) } } const fn from_linger(linger: sys::linger) -> Option { if linger.l_onoff == 0 { None } else { Some(Duration::from_secs(linger.l_linger as u64)) } } const fn into_linger(duration: Option) -> sys::linger { match duration { Some(duration) => sys::linger { l_onoff: 1, l_linger: duration.as_secs() as _, }, None => sys::linger { l_onoff: 0, l_linger: 0, }, } } /// Socket options for IPv4 sockets, get/set using `IPPROTO_IP` or `SOL_IP`. /// /// Additional documentation can be found in documentation of the OS. /// * Linux: /// * Windows: impl Socket { /// Get the value of the `IP_HDRINCL` option on this socket. /// /// For more information about this option, see [`set_header_included_v4`]. /// /// [`set_header_included_v4`]: Socket::set_header_included_v4 #[cfg(all( feature = "all", not(any( target_os = "redox", target_os = "espidf", target_os = "wasi", target_os = "horizon" )) ))] pub fn header_included_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_HDRINCL) .map(|included| included != 0) } } /// Set the value of the `IP_HDRINCL` option on this socket. /// /// If enabled, the user supplies an IP header in front of the user data. /// Valid only for [`SOCK_RAW`] sockets; see [raw(7)] for more information. /// When this flag is enabled, the values set by `IP_OPTIONS`, [`IP_TTL`], /// and [`IP_TOS`] are ignored. /// /// [`SOCK_RAW`]: Type::RAW /// [raw(7)]: https://man7.org/linux/man-pages/man7/raw.7.html /// [`IP_TTL`]: Socket::set_ttl_v4 /// [`IP_TOS`]: Socket::set_tos_v4 #[cfg_attr( any(target_os = "fuchsia", target_os = "illumos", target_os = "solaris"), allow(rustdoc::broken_intra_doc_links) )] #[cfg(all( feature = "all", not(any( target_os = "redox", target_os = "espidf", target_os = "wasi", target_os = "horizon" )) ))] pub fn set_header_included_v4(&self, included: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_HDRINCL, included as c_int, ) } } /// Get the value of the `IP_TRANSPARENT` option on this socket. /// /// For more information about this option, see [`set_ip_transparent_v4`]. /// /// [`set_ip_transparent_v4`]: Socket::set_ip_transparent_v4 #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] pub fn ip_transparent_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IP, libc::IP_TRANSPARENT) .map(|transparent| transparent != 0) } } /// Set the value of the `IP_TRANSPARENT` option on this socket. /// /// Setting this boolean option enables transparent proxying /// on this socket. This socket option allows the calling /// application to bind to a nonlocal IP address and operate /// both as a client and a server with the foreign address as /// the local endpoint. NOTE: this requires that routing be /// set up in a way that packets going to the foreign address /// are routed through the TProxy box (i.e., the system /// hosting the application that employs the IP_TRANSPARENT /// socket option). Enabling this socket option requires /// superuser privileges (the `CAP_NET_ADMIN` capability). /// /// TProxy redirection with the iptables TPROXY target also /// requires that this option be set on the redirected socket. #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] pub fn set_ip_transparent_v4(&self, transparent: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, libc::IP_TRANSPARENT, transparent as c_int, ) } } /// Join a multicast group using `IP_ADD_MEMBERSHIP` option on this socket. /// /// This function specifies a new multicast group for this socket to join. /// The address must be a valid multicast address, and `interface` is the /// address of the local interface with which the system should join the /// multicast group. If it's [`Ipv4Addr::UNSPECIFIED`] (`INADDR_ANY`) then /// an appropriate interface is chosen by the system. pub fn join_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { let mreq = sys::IpMreq { imr_multiaddr: sys::to_in_addr(multiaddr), imr_interface: sys::to_in_addr(interface), }; unsafe { setsockopt(self.as_raw(), sys::IPPROTO_IP, sys::IP_ADD_MEMBERSHIP, mreq) } } /// Leave a multicast group using `IP_DROP_MEMBERSHIP` option on this socket. /// /// For more information about this option, see [`join_multicast_v4`]. /// /// [`join_multicast_v4`]: Socket::join_multicast_v4 pub fn leave_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { let mreq = sys::IpMreq { imr_multiaddr: sys::to_in_addr(multiaddr), imr_interface: sys::to_in_addr(interface), }; unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_DROP_MEMBERSHIP, mreq, ) } } /// Join a multicast group using `IP_ADD_MEMBERSHIP` option on this socket. /// /// This function specifies a new multicast group for this socket to join. /// The address must be a valid multicast address, and `interface` specifies /// the local interface with which the system should join the multicast /// group. See [`InterfaceIndexOrAddress`]. #[cfg(not(any( target_os = "aix", target_os = "haiku", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "cygwin", target_os = "wasi", target_os = "horizon" )))] pub fn join_multicast_v4_n( &self, multiaddr: &Ipv4Addr, interface: &InterfaceIndexOrAddress, ) -> io::Result<()> { let mreqn = sys::to_mreqn(multiaddr, interface); unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_ADD_MEMBERSHIP, mreqn, ) } } /// Leave a multicast group using `IP_DROP_MEMBERSHIP` option on this socket. /// /// For more information about this option, see [`join_multicast_v4_n`]. /// /// [`join_multicast_v4_n`]: Socket::join_multicast_v4_n #[cfg(not(any( target_os = "aix", target_os = "haiku", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "cygwin", target_os = "wasi", target_os = "horizon" )))] pub fn leave_multicast_v4_n( &self, multiaddr: &Ipv4Addr, interface: &InterfaceIndexOrAddress, ) -> io::Result<()> { let mreqn = sys::to_mreqn(multiaddr, interface); unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_DROP_MEMBERSHIP, mreqn, ) } } /// Join a multicast SSM channel using `IP_ADD_SOURCE_MEMBERSHIP` option on this socket. /// /// This function specifies a new multicast channel for this socket to join. /// The group must be a valid SSM group address, the source must be the address of the sender /// and `interface` is the address of the local interface with which the system should join the /// multicast group. If it's [`Ipv4Addr::UNSPECIFIED`] (`INADDR_ANY`) then /// an appropriate interface is chosen by the system. #[cfg(not(any( target_os = "dragonfly", target_os = "haiku", target_os = "hurd", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "fuchsia", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "wasi", target_os = "horizon" )))] pub fn join_ssm_v4( &self, source: &Ipv4Addr, group: &Ipv4Addr, interface: &Ipv4Addr, ) -> io::Result<()> { let mreqs = sys::IpMreqSource { imr_multiaddr: sys::to_in_addr(group), imr_interface: sys::to_in_addr(interface), imr_sourceaddr: sys::to_in_addr(source), }; unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_ADD_SOURCE_MEMBERSHIP, mreqs, ) } } /// Leave a multicast group using `IP_DROP_SOURCE_MEMBERSHIP` option on this socket. /// /// For more information about this option, see [`join_ssm_v4`]. /// /// [`join_ssm_v4`]: Socket::join_ssm_v4 #[cfg(not(any( target_os = "dragonfly", target_os = "haiku", target_os = "hurd", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "fuchsia", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "wasi", target_os = "horizon" )))] pub fn leave_ssm_v4( &self, source: &Ipv4Addr, group: &Ipv4Addr, interface: &Ipv4Addr, ) -> io::Result<()> { let mreqs = sys::IpMreqSource { imr_multiaddr: sys::to_in_addr(group), imr_interface: sys::to_in_addr(interface), imr_sourceaddr: sys::to_in_addr(source), }; unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_DROP_SOURCE_MEMBERSHIP, mreqs, ) } } /// Get the value of the `IP_MULTICAST_ALL` option for this socket. /// /// For more information about this option, see [`set_multicast_all_v4`]. /// /// [`set_multicast_all_v4`]: Socket::set_multicast_all_v4 #[cfg(all(feature = "all", target_os = "linux"))] pub fn multicast_all_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IP, libc::IP_MULTICAST_ALL) .map(|all| all != 0) } } /// Set the value of the `IP_MULTICAST_ALL` option for this socket. /// /// This option can be used to modify the delivery policy of /// multicast messages. The argument is a boolean /// (defaults to true). If set to true, the socket will receive /// messages from all the groups that have been joined /// globally on the whole system. Otherwise, it will deliver /// messages only from the groups that have been explicitly /// joined (for example via the `IP_ADD_MEMBERSHIP` option) on /// this particular socket. #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_multicast_all_v4(&self, all: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, libc::IP_MULTICAST_ALL, all as c_int, ) } } /// Get the value of the `IP_MULTICAST_IF` option for this socket. /// /// For more information about this option, see [`set_multicast_if_v4`]. /// /// [`set_multicast_if_v4`]: Socket::set_multicast_if_v4 #[cfg(not(target_os = "wasi"))] pub fn multicast_if_v4(&self) -> io::Result { unsafe { getsockopt(self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_IF).map(sys::from_in_addr) } } /// Set the value of the `IP_MULTICAST_IF` option for this socket. /// /// Specifies the interface to use for routing multicast packets. #[cfg(not(target_os = "wasi"))] pub fn set_multicast_if_v4(&self, interface: &Ipv4Addr) -> io::Result<()> { let interface = sys::to_in_addr(interface); unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_IF, interface, ) } } /// Get the value of the `IP_MULTICAST_LOOP` option for this socket. /// /// For more information about this option, see [`set_multicast_loop_v4`]. /// /// [`set_multicast_loop_v4`]: Socket::set_multicast_loop_v4 pub fn multicast_loop_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_LOOP) .map(|loop_v4| loop_v4 != 0) } } /// Set the value of the `IP_MULTICAST_LOOP` option for this socket. /// /// If enabled, multicast packets will be looped back to the local socket. /// Note that this may not have any affect on IPv6 sockets. pub fn set_multicast_loop_v4(&self, loop_v4: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_LOOP, loop_v4 as c_int, ) } } /// Get the value of the `IP_MULTICAST_TTL` option for this socket. /// /// For more information about this option, see [`set_multicast_ttl_v4`]. /// /// [`set_multicast_ttl_v4`]: Socket::set_multicast_ttl_v4 pub fn multicast_ttl_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_TTL) .map(|ttl| ttl as u32) } } /// Set the value of the `IP_MULTICAST_TTL` option for this socket. /// /// Indicates the time-to-live value of outgoing multicast packets for /// this socket. The default value is 1 which means that multicast packets /// don't leave the local network unless explicitly requested. /// /// Note that this may not have any affect on IPv6 sockets. pub fn set_multicast_ttl_v4(&self, ttl: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_TTL, ttl as c_int, ) } } /// Get the value of the `IP_TTL` option for this socket. /// /// For more information about this option, see [`set_ttl_v4`]. /// /// [`set_ttl_v4`]: Socket::set_ttl_v4 pub fn ttl_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_TTL).map(|ttl| ttl as u32) } } /// Set the value of the `IP_TTL` option for this socket. /// /// This value sets the time-to-live field that is used in every packet sent /// from this socket. pub fn set_ttl_v4(&self, ttl: u32) -> io::Result<()> { unsafe { setsockopt(self.as_raw(), sys::IPPROTO_IP, sys::IP_TTL, ttl as c_int) } } /// Set the value of the `IP_TOS` option for this socket. /// /// This value sets the type-of-service field that is used in every packet /// sent from this socket. /// /// NOTE: /// documents that not all versions of windows support `IP_TOS`. #[cfg(not(any( target_os = "fuchsia", target_os = "redox", target_os = "solaris", target_os = "illumos", target_os = "haiku", target_os = "wasi", )))] pub fn set_tos_v4(&self, tos: u32) -> io::Result<()> { unsafe { setsockopt(self.as_raw(), sys::IPPROTO_IP, sys::IP_TOS, tos as c_int) } } /// Get the value of the `IP_TOS` option for this socket. /// /// For more information about this option, see [`set_tos_v4`]. /// /// NOTE: /// documents that not all versions of windows support `IP_TOS`. /// /// [`set_tos_v4`]: Socket::set_tos_v4 #[cfg(not(any( target_os = "fuchsia", target_os = "redox", target_os = "solaris", target_os = "illumos", target_os = "haiku", target_os = "wasi", )))] pub fn tos_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_TOS).map(|tos| tos as u32) } } /// Set the value of the `IP_RECVTOS` option for this socket. /// /// If enabled, the `IP_TOS` ancillary message is passed with /// incoming packets. It contains a byte which specifies the /// Type of Service/Precedence field of the packet header. #[cfg(not(any( target_os = "aix", target_os = "dragonfly", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "cygwin", target_os = "wasi", target_os = "horizon" )))] pub fn set_recv_tos_v4(&self, recv_tos: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IP, sys::IP_RECVTOS, recv_tos as c_int, ) } } /// Get the value of the `IP_RECVTOS` option for this socket. /// /// For more information about this option, see [`set_recv_tos_v4`]. /// /// [`set_recv_tos_v4`]: Socket::set_recv_tos_v4 #[cfg(not(any( target_os = "aix", target_os = "dragonfly", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "cygwin", target_os = "wasi", target_os = "horizon" )))] pub fn recv_tos_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_RECVTOS) .map(|recv_tos| recv_tos > 0) } } /// Get the value for the `SO_ORIGINAL_DST` option on this socket. #[cfg(all( feature = "all", any( target_os = "android", target_os = "fuchsia", target_os = "linux", target_os = "windows", ) ))] pub fn original_dst_v4(&self) -> io::Result { sys::original_dst_v4(self.as_raw()) } } /// Socket options for IPv6 sockets, get/set using `IPPROTO_IPV6` or `SOL_IPV6`. /// /// Additional documentation can be found in documentation of the OS. /// * Linux: /// * Windows: impl Socket { /// Get the value of the `IP_HDRINCL` option on this socket. /// /// For more information about this option, see [`set_header_included_v6`]. /// /// [`set_header_included_v6`]: Socket::set_header_included_v6 #[cfg(all( feature = "all", not(any( target_os = "redox", target_os = "espidf", target_os = "openbsd", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "wasi", target_os = "horizon" )) ))] pub fn header_included_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IP_HDRINCL) .map(|included| included != 0) } } /// Set the value of the `IP_HDRINCL` option on this socket. /// /// If enabled, the user supplies an IP header in front of the user data. /// Valid only for [`SOCK_RAW`] sockets; see [raw(7)] for more information. /// When this flag is enabled, the values set by `IP_OPTIONS` are ignored. /// /// [`SOCK_RAW`]: Type::RAW /// [raw(7)]: https://man7.org/linux/man-pages/man7/raw.7.html #[cfg_attr( any(target_os = "fuchsia", target_os = "illumos", target_os = "solaris"), allow(rustdoc::broken_intra_doc_links) )] #[cfg(all( feature = "all", not(any( target_os = "redox", target_os = "espidf", target_os = "openbsd", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "wasi", target_os = "horizon" )) ))] pub fn set_header_included_v6(&self, included: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, #[cfg(target_os = "linux")] sys::IPV6_HDRINCL, #[cfg(not(target_os = "linux"))] sys::IP_HDRINCL, included as c_int, ) } } /// Get the value of the `IPV6_TRANSPARENT` option on this socket. /// /// For more information about this option, see [`set_ip_transparent_v6`]. /// /// [`set_ip_transparent_v6`]: Socket::set_ip_transparent_v6 #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] pub fn ip_transparent_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, libc::IPV6_TRANSPARENT) .map(|transparent| transparent != 0) } } /// Set the value of the `IPV6_TRANSPARENT` option on this socket. /// /// Setting this boolean option enables transparent proxying /// on this socket. This socket option allows the calling /// application to bind to a nonlocal IP address and operate /// both as a client and a server with the foreign address as /// the local endpoint. NOTE: this requires that routing be /// set up in a way that packets going to the foreign address /// are routed through the TProxy box (i.e., the system /// hosting the application that employs the IPV6_TRANSPARENT /// socket option). Enabling this socket option requires /// superuser privileges (the `CAP_NET_ADMIN` capability). /// /// TProxy redirection with the iptables TPROXY target also /// requires that this option be set on the redirected socket. #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] pub fn set_ip_transparent_v6(&self, transparent: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, libc::IPV6_TRANSPARENT, transparent as c_int, ) } } /// Join a multicast group using `IPV6_ADD_MEMBERSHIP` option on this socket. /// /// Some OSs use `IPV6_JOIN_GROUP` for this option. /// /// This function specifies a new multicast group for this socket to join. /// The address must be a valid multicast address, and `interface` is the /// index of the interface to join/leave (or 0 to indicate any interface). #[cfg(not(target_os = "nto"))] pub fn join_multicast_v6(&self, multiaddr: &Ipv6Addr, interface: u32) -> io::Result<()> { let mreq = sys::Ipv6Mreq { ipv6mr_multiaddr: sys::to_in6_addr(multiaddr), // NOTE: some OSs use `c_int`, others use `c_uint`. ipv6mr_interface: interface as _, }; unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_ADD_MEMBERSHIP, mreq, ) } } /// Leave a multicast group using `IPV6_DROP_MEMBERSHIP` option on this socket. /// /// Some OSs use `IPV6_LEAVE_GROUP` for this option. /// /// For more information about this option, see [`join_multicast_v6`]. /// /// [`join_multicast_v6`]: Socket::join_multicast_v6 #[cfg(not(target_os = "nto"))] pub fn leave_multicast_v6(&self, multiaddr: &Ipv6Addr, interface: u32) -> io::Result<()> { let mreq = sys::Ipv6Mreq { ipv6mr_multiaddr: sys::to_in6_addr(multiaddr), // NOTE: some OSs use `c_int`, others use `c_uint`. ipv6mr_interface: interface as _, }; unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_DROP_MEMBERSHIP, mreq, ) } } /// Get the value of the `IPV6_MULTICAST_HOPS` option for this socket /// /// For more information about this option, see [`set_multicast_hops_v6`]. /// /// [`set_multicast_hops_v6`]: Socket::set_multicast_hops_v6 #[cfg(not(target_os = "wasi"))] pub fn multicast_hops_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_MULTICAST_HOPS) .map(|hops| hops as u32) } } /// Set the value of the `IPV6_MULTICAST_HOPS` option for this socket /// /// Indicates the number of "routers" multicast packets will transit for /// this socket. The default value is 1 which means that multicast packets /// don't leave the local network unless explicitly requested. #[cfg(not(target_os = "wasi"))] pub fn set_multicast_hops_v6(&self, hops: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_MULTICAST_HOPS, hops as c_int, ) } } /// Get the value of the `IPV6_MULTICAST_ALL` option for this socket. /// /// For more information about this option, see [`set_multicast_all_v6`]. /// /// [`set_multicast_all_v6`]: Socket::set_multicast_all_v6 #[cfg(all(feature = "all", target_os = "linux"))] pub fn multicast_all_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, libc::IPV6_MULTICAST_ALL) .map(|all| all != 0) } } /// Set the value of the `IPV6_MULTICAST_ALL` option for this socket. /// /// This option can be used to modify the delivery policy of /// multicast messages. The argument is a boolean /// (defaults to true). If set to true, the socket will receive /// messages from all the groups that have been joined /// globally on the whole system. Otherwise, it will deliver /// messages only from the groups that have been explicitly /// joined (for example via the `IPV6_ADD_MEMBERSHIP` option) on /// this particular socket. #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_multicast_all_v6(&self, all: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, libc::IPV6_MULTICAST_ALL, all as c_int, ) } } /// Get the value of the `IPV6_MULTICAST_IF` option for this socket. /// /// For more information about this option, see [`set_multicast_if_v6`]. /// /// [`set_multicast_if_v6`]: Socket::set_multicast_if_v6 #[cfg(not(target_os = "wasi"))] pub fn multicast_if_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_MULTICAST_IF) .map(|interface| interface as u32) } } /// Set the value of the `IPV6_MULTICAST_IF` option for this socket. /// /// Specifies the interface to use for routing multicast packets. Unlike /// ipv4, this is generally required in ipv6 contexts where network routing /// prefixes may overlap. #[cfg(not(target_os = "wasi"))] pub fn set_multicast_if_v6(&self, interface: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_MULTICAST_IF, interface as c_int, ) } } /// Get the value of the `IPV6_MULTICAST_LOOP` option for this socket. /// /// For more information about this option, see [`set_multicast_loop_v6`]. /// /// [`set_multicast_loop_v6`]: Socket::set_multicast_loop_v6 pub fn multicast_loop_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_MULTICAST_LOOP) .map(|loop_v6| loop_v6 != 0) } } /// Set the value of the `IPV6_MULTICAST_LOOP` option for this socket. /// /// Controls whether this socket sees the multicast packets it sends itself. /// Note that this may not have any affect on IPv4 sockets. pub fn set_multicast_loop_v6(&self, loop_v6: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_MULTICAST_LOOP, loop_v6 as c_int, ) } } /// Get the value of the `IPV6_UNICAST_HOPS` option for this socket. /// /// Specifies the hop limit for ipv6 unicast packets pub fn unicast_hops_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_UNICAST_HOPS) .map(|hops| hops as u32) } } /// Set the value for the `IPV6_UNICAST_HOPS` option on this socket. /// /// Specifies the hop limit for ipv6 unicast packets pub fn set_unicast_hops_v6(&self, hops: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_UNICAST_HOPS, hops as c_int, ) } } /// Get the value of the `IPV6_V6ONLY` option for this socket. /// /// For more information about this option, see [`set_only_v6`]. /// /// [`set_only_v6`]: Socket::set_only_v6 pub fn only_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_V6ONLY) .map(|only_v6| only_v6 != 0) } } /// Set the value for the `IPV6_V6ONLY` option on this socket. /// /// If this is set to `true` then the socket is restricted to sending and /// receiving IPv6 packets only. In this case two IPv4 and IPv6 applications /// can bind the same port at the same time. /// /// If this is set to `false` then the socket can be used to send and /// receive packets from an IPv4-mapped IPv6 address. pub fn set_only_v6(&self, only_v6: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_V6ONLY, only_v6 as c_int, ) } } /// Get the value of the `IPV6_RECVTCLASS` option for this socket. /// /// For more information about this option, see [`set_recv_tclass_v6`]. /// /// [`set_recv_tclass_v6`]: Socket::set_recv_tclass_v6 #[cfg(not(any( target_os = "dragonfly", target_os = "fuchsia", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "hurd", target_os = "espidf", target_os = "vita", target_os = "wasi", target_os = "horizon" )))] pub fn recv_tclass_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_RECVTCLASS) .map(|recv_tclass| recv_tclass > 0) } } /// Set the value of the `IPV6_RECVTCLASS` option for this socket. /// /// If enabled, the `IPV6_TCLASS` ancillary message is passed with incoming /// packets. It contains a byte which specifies the traffic class field of /// the packet header. #[cfg(not(any( target_os = "dragonfly", target_os = "fuchsia", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "hurd", target_os = "espidf", target_os = "vita", target_os = "wasi", target_os = "horizon" )))] pub fn set_recv_tclass_v6(&self, recv_tclass: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_RECVTCLASS, recv_tclass as c_int, ) } } /// Get the value of the `IPV6_RECVHOPLIMIT` option for this socket. /// /// For more information about this option, see [`set_recv_hoplimit_v6`]. /// /// [`set_recv_hoplimit_v6`]: Socket::set_recv_hoplimit_v6 #[cfg(all( feature = "all", not(any( windows, target_os = "dragonfly", target_os = "fuchsia", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "hurd", target_os = "espidf", target_os = "vita", target_os = "cygwin", target_os = "wasi", target_os = "horizon" )) ))] pub fn recv_hoplimit_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_RECVHOPLIMIT) .map(|recv_hoplimit| recv_hoplimit > 0) } } /// Set the value of the `IPV6_RECVHOPLIMIT` option for this socket. /// /// The received hop limit is returned as ancillary data by recvmsg() /// only if the application has enabled the IPV6_RECVHOPLIMIT socket /// option: #[cfg(all( feature = "all", not(any( windows, target_os = "dragonfly", target_os = "fuchsia", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "hurd", target_os = "espidf", target_os = "vita", target_os = "cygwin", target_os = "wasi", target_os = "horizon" )) ))] pub fn set_recv_hoplimit_v6(&self, recv_hoplimit: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_RECVHOPLIMIT, recv_hoplimit as c_int, ) } } /// Get the value for the `IP6T_SO_ORIGINAL_DST` option on this socket. #[cfg(all( feature = "all", any(target_os = "android", target_os = "linux", target_os = "windows") ))] pub fn original_dst_v6(&self) -> io::Result { sys::original_dst_v6(self.as_raw()) } } /// Socket options for TCP sockets, get/set using `IPPROTO_TCP`. /// /// Additional documentation can be found in documentation of the OS. /// * Linux: /// * Windows: impl Socket { /// Get the value of the `TCP_KEEPIDLE` option on this socket. /// /// This returns the value of `TCP_KEEPALIVE` on macOS and iOS and `TCP_KEEPIDLE` on all other /// supported Unix operating systems. #[cfg(all( feature = "all", not(any( windows, target_os = "haiku", target_os = "openbsd", target_os = "vita" )) ))] pub fn tcp_keepalive_time(&self) -> io::Result { sys::tcp_keepalive_time(self.as_raw()) } /// Get the value of the `TCP_KEEPINTVL` option on this socket. /// /// For more information about this option, see [`set_tcp_keepalive`]. /// /// [`set_tcp_keepalive`]: Socket::set_tcp_keepalive #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "cygwin", all(target_os = "wasi", not(target_env = "p1")), ) ))] pub fn tcp_keepalive_interval(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_TCP, sys::TCP_KEEPINTVL) .map(|secs| Duration::from_secs(secs as u64)) } } /// Get the value of the `TCP_KEEPCNT` option on this socket. /// /// For more information about this option, see [`set_tcp_keepalive`]. /// /// [`set_tcp_keepalive`]: Socket::set_tcp_keepalive #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "cygwin", target_os = "windows", all(target_os = "wasi", not(target_env = "p1")), ) ))] pub fn tcp_keepalive_retries(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_TCP, sys::TCP_KEEPCNT) .map(|retries| retries as u32) } } /// Set parameters configuring TCP keepalive probes for this socket. /// /// The supported parameters depend on the operating system, and are /// configured using the [`TcpKeepalive`] struct. At a minimum, all systems /// support configuring the [keepalive time]: the time after which the OS /// will start sending keepalive messages on an idle connection. /// /// [keepalive time]: TcpKeepalive::with_time /// /// # Notes /// /// * This will enable `SO_KEEPALIVE` on this socket, if it is not already /// enabled. /// * On some platforms, such as Windows, any keepalive parameters *not* /// configured by the `TcpKeepalive` struct passed to this function may be /// overwritten with their default values. Therefore, this function should /// either only be called once per socket, or the same parameters should /// be passed every time it is called. /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use socket2::{Socket, TcpKeepalive, Domain, Type}; /// /// # fn main() -> std::io::Result<()> { /// let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?; /// let keepalive = TcpKeepalive::new() /// .with_time(Duration::from_secs(4)); /// // Depending on the target operating system, we may also be able to /// // configure the keepalive probe interval and/or the number of /// // retries here as well. /// /// socket.set_tcp_keepalive(&keepalive)?; /// # Ok(()) } /// ``` /// pub fn set_tcp_keepalive(&self, params: &TcpKeepalive) -> io::Result<()> { self.set_keepalive(true)?; sys::set_tcp_keepalive(self.as_raw(), params) } /// Get the value of the `TCP_NODELAY` option on this socket. /// /// For more information about this option, see [`set_tcp_nodelay`]. /// /// [`set_tcp_nodelay`]: Socket::set_tcp_nodelay pub fn tcp_nodelay(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), sys::IPPROTO_TCP, sys::TCP_NODELAY) .map(|nodelay| nodelay != false as Bool) } } /// Set the value of the `TCP_NODELAY` option on this socket. /// /// If set, this option disables the Nagle algorithm. This means that /// segments are always sent as soon as possible, even if there is only a /// small amount of data. When not set, data is buffered until there is a /// sufficient amount to send out, thereby avoiding the frequent sending of /// small packets. pub fn set_tcp_nodelay(&self, nodelay: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), sys::IPPROTO_TCP, sys::TCP_NODELAY, nodelay as c_int, ) } } /// On Windows this invokes the `SIO_TCP_SET_ACK_FREQUENCY` IOCTL which /// configures the number of TCP segments that must be received before /// the delayed ACK timer is ignored. #[cfg(all(feature = "all", windows))] pub fn set_tcp_ack_frequency(&self, frequency: u8) -> io::Result<()> { sys::set_tcp_ack_frequency(self.as_raw(), frequency) } } impl Read for Socket { fn read(&mut self, buf: &mut [u8]) -> io::Result { // Safety: the `recv` implementation promises not to write uninitialised // bytes to the `buf`fer, so this casting is safe. let buf = unsafe { &mut *(buf as *mut [u8] as *mut [MaybeUninit]) }; self.recv(buf) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { // Safety: both `IoSliceMut` and `MaybeUninitSlice` promise to have the // same layout, that of `iovec`/`WSABUF`. Furthermore, `recv_vectored` // promises to not write uninitialised bytes to the `bufs` and pass it // directly to the `recvmsg` system call, so this is safe. let bufs = unsafe { &mut *(bufs as *mut [IoSliceMut<'_>] as *mut [MaybeUninitSlice<'_>]) }; self.recv_vectored(bufs).map(|(n, _)| n) } } impl<'a> Read for &'a Socket { fn read(&mut self, buf: &mut [u8]) -> io::Result { // Safety: see other `Read::read` impl. let buf = unsafe { &mut *(buf as *mut [u8] as *mut [MaybeUninit]) }; self.recv(buf) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { // Safety: see other `Read::read` impl. let bufs = unsafe { &mut *(bufs as *mut [IoSliceMut<'_>] as *mut [MaybeUninitSlice<'_>]) }; self.recv_vectored(bufs).map(|(n, _)| n) } } impl Write for Socket { fn write(&mut self, buf: &[u8]) -> io::Result { self.send(buf) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { self.send_vectored(bufs) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl<'a> Write for &'a Socket { fn write(&mut self, buf: &[u8]) -> io::Result { self.send(buf) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { self.send_vectored(bufs) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl fmt::Debug for Socket { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Socket") .field("raw", &self.as_raw()) .field("local_addr", &self.local_addr().ok()) .field("peer_addr", &self.peer_addr().ok()) .finish() } } from!(net::TcpStream, Socket); from!(net::TcpListener, Socket); from!(net::UdpSocket, Socket); from!(Socket, net::TcpStream); from!(Socket, net::TcpListener); from!(Socket, net::UdpSocket); ================================================ FILE: src/sockref.rs ================================================ use std::fmt; use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ops::Deref; #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] use std::os::fd::{AsFd, AsRawFd, FromRawFd}; #[cfg(windows)] use std::os::windows::io::{AsRawSocket, AsSocket, FromRawSocket}; use crate::Socket; /// A reference to a [`Socket`] that can be used to configure socket types other /// than the `Socket` type itself. /// /// This allows for example a [`TcpStream`], found in the standard library, to /// be configured using all the additional methods found in the [`Socket`] API. /// /// `SockRef` can be created from any socket type that implements [`AsFd`] /// (Unix) or [`AsSocket`] (Windows) using the [`From`] implementation. /// /// [`TcpStream`]: std::net::TcpStream // Don't use intra-doc links because they won't build on every platform. /// [`AsFd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.AsFd.html /// [`AsSocket`]: https://doc.rust-lang.org/stable/std/os/windows/io/trait.AsSocket.html /// /// # Examples /// /// Below is an example of converting a [`TcpStream`] into a [`SockRef`]. /// /// ``` /// use std::net::{TcpStream, SocketAddr}; /// /// use socket2::SockRef; /// /// # fn main() -> Result<(), Box> { /// // Create `TcpStream` from the standard library. /// let address: SocketAddr = "127.0.0.1:1234".parse()?; /// # let b1 = std::sync::Arc::new(std::sync::Barrier::new(2)); /// # let b2 = b1.clone(); /// # let handle = std::thread::spawn(move || { /// # let listener = std::net::TcpListener::bind(address).unwrap(); /// # b2.wait(); /// # let (stream, _) = listener.accept().unwrap(); /// # std::thread::sleep(std::time::Duration::from_millis(10)); /// # drop(stream); /// # }); /// # b1.wait(); /// let stream = TcpStream::connect(address)?; /// /// // Create a `SockRef`erence to the stream. /// let socket_ref = SockRef::from(&stream); /// // Use `Socket::set_tcp_nodelay` on the stream. /// socket_ref.set_tcp_nodelay(true)?; /// drop(socket_ref); /// /// assert_eq!(stream.nodelay()?, true); /// # handle.join().unwrap(); /// # Ok(()) /// # } /// ``` pub struct SockRef<'s> { /// Because this is a reference we don't own the `Socket`, however `Socket` /// closes itself when dropped, so we use `ManuallyDrop` to prevent it from /// closing itself. socket: ManuallyDrop, /// Because we don't own the socket we need to ensure the socket remains /// open while we have a "reference" to it, the lifetime `'s` ensures this. _lifetime: PhantomData<&'s Socket>, } impl<'s> Deref for SockRef<'s> { type Target = Socket; fn deref(&self) -> &Self::Target { &self.socket } } /// On Windows, a corresponding `From<&impl AsSocket>` implementation exists. #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))] impl<'s, S> From<&'s S> for SockRef<'s> where S: AsFd, { /// The caller must ensure `S` is actually a socket. fn from(socket: &'s S) -> Self { let fd = socket.as_fd().as_raw_fd(); assert!(fd >= 0); SockRef { socket: ManuallyDrop::new(unsafe { Socket::from_raw_fd(fd) }), _lifetime: PhantomData, } } } /// On Unix, a corresponding `From<&impl AsFd>` implementation exists. #[cfg(windows)] impl<'s, S> From<&'s S> for SockRef<'s> where S: AsSocket, { /// See the `From<&impl AsFd>` implementation. fn from(socket: &'s S) -> Self { let socket = socket.as_socket().as_raw_socket(); assert!(socket != windows_sys::Win32::Networking::WinSock::INVALID_SOCKET as _); SockRef { socket: ManuallyDrop::new(unsafe { Socket::from_raw_socket(socket) }), _lifetime: PhantomData, } } } impl fmt::Debug for SockRef<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SockRef") .field("raw", &self.socket.as_raw()) .field("local_addr", &self.socket.local_addr().ok()) .field("peer_addr", &self.socket.peer_addr().ok()) .finish() } } ================================================ FILE: src/sys/unix.rs ================================================ // Copyright 2015 The Rust Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::cmp::min; #[cfg(not(target_os = "wasi"))] use std::ffi::OsStr; #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] use std::io::IoSlice; use std::marker::PhantomData; use std::mem::{self, size_of, MaybeUninit}; use std::net::Shutdown; use std::net::{Ipv4Addr, Ipv6Addr}; #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", target_os = "linux", target_os = "android", ) ))] use std::num::NonZeroU32; #[cfg(all( feature = "all", any( target_os = "aix", target_os = "android", target_os = "freebsd", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "tvos", target_os = "watchos", ) ))] use std::num::NonZeroUsize; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; #[cfg(not(target_os = "wasi"))] use std::os::unix::ffi::OsStrExt; #[cfg(all(feature = "all", unix))] use std::os::unix::net::{UnixDatagram, UnixListener, UnixStream}; #[cfg(not(target_os = "wasi"))] use std::path::Path; use std::ptr; use std::time::{Duration, Instant}; use std::{io, slice}; #[cfg(not(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "cygwin", )))] use libc::ssize_t; use libc::{in6_addr, in_addr}; #[cfg(not(target_os = "wasi"))] use crate::SockAddrStorage; use crate::{Domain, Protocol, SockAddr, TcpKeepalive, Type}; #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] use crate::{MsgHdr, MsgHdrMut, RecvFlags}; pub(crate) use std::ffi::c_int; // Used in `Domain`. #[cfg(not(target_os = "wasi"))] pub(crate) use libc::AF_UNIX; pub(crate) use libc::{AF_INET, AF_INET6}; // Used in `Type`. #[cfg(all(feature = "all", target_os = "linux"))] pub(crate) use libc::SOCK_DCCP; #[cfg(all( feature = "all", not(any( target_os = "redox", target_os = "espidf", target_os = "wasi", target_os = "horizon" )) ))] pub(crate) use libc::SOCK_RAW; #[cfg(all( feature = "all", not(any(target_os = "espidf", target_os = "wasi", target_os = "horizon")) ))] pub(crate) use libc::SOCK_SEQPACKET; pub(crate) use libc::{SOCK_DGRAM, SOCK_STREAM}; // Used in `Protocol`. #[cfg(all(feature = "all", target_os = "linux"))] pub(crate) use libc::IPPROTO_DCCP; #[cfg(target_os = "linux")] pub(crate) use libc::IPPROTO_MPTCP; #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] pub(crate) use libc::IPPROTO_SCTP; #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", ) ))] pub(crate) use libc::IPPROTO_UDPLITE; #[cfg(not(target_os = "wasi"))] pub(crate) use libc::{IPPROTO_ICMP, IPPROTO_ICMPV6}; pub(crate) use libc::{IPPROTO_TCP, IPPROTO_UDP}; // Used in `SockAddr`. #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "openbsd")))] pub(crate) use libc::IPPROTO_DIVERT; pub(crate) use libc::{ sa_family_t, sockaddr, sockaddr_in, sockaddr_in6, sockaddr_storage, socklen_t, }; // Used in `RecvFlags`. #[cfg(not(any( target_os = "redox", target_os = "espidf", target_os = "wasi", target_os = "horizon" )))] pub(crate) use libc::MSG_TRUNC; #[cfg(not(any(target_os = "redox", target_os = "wasi")))] pub(crate) use libc::SO_OOBINLINE; // Used in `Socket`. #[cfg(not(target_os = "nto"))] pub(crate) use libc::ipv6_mreq as Ipv6Mreq; #[cfg(all(feature = "all", target_os = "linux"))] pub(crate) use libc::IPV6_HDRINCL; #[cfg(all( feature = "all", not(any( target_os = "dragonfly", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "espidf", target_os = "vita", target_os = "wasi", target_os = "cygwin", target_os = "horizon" )) ))] pub(crate) use libc::IPV6_RECVHOPLIMIT; #[cfg(not(any( target_os = "dragonfly", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "espidf", target_os = "vita", target_os = "wasi", target_os = "horizon" )))] pub(crate) use libc::IPV6_RECVTCLASS; #[cfg(all( feature = "all", not(any( target_os = "redox", target_os = "espidf", target_os = "wasi", target_os = "horizon" )) ))] pub(crate) use libc::IP_HDRINCL; #[cfg(not(any( target_os = "aix", target_os = "dragonfly", target_os = "fuchsia", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "hurd", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "wasi", target_os = "cygwin", target_os = "horizon" )))] pub(crate) use libc::IP_RECVTOS; #[cfg(not(any( target_os = "fuchsia", target_os = "redox", target_os = "solaris", target_os = "haiku", target_os = "illumos", target_os = "wasi", )))] pub(crate) use libc::IP_TOS; #[cfg(not(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", )))] pub(crate) use libc::SO_LINGER; #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", ))] pub(crate) use libc::SO_LINGER_SEC as SO_LINGER; #[cfg(any(target_os = "linux", target_os = "cygwin"))] pub(crate) use libc::SO_PASSCRED; #[cfg(all( feature = "all", any(target_os = "linux", target_os = "android", target_os = "fuchsia") ))] pub(crate) use libc::SO_PRIORITY; pub(crate) use libc::{ ip_mreq as IpMreq, linger, IPPROTO_IP, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_TTL, MSG_PEEK, SOL_SOCKET, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_RCVBUF, SO_RCVTIMEO, SO_REUSEADDR, SO_SNDBUF, SO_SNDTIMEO, SO_TYPE, TCP_NODELAY, }; #[cfg(not(any( target_os = "dragonfly", target_os = "haiku", target_os = "hurd", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "fuchsia", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "wasi", target_os = "horizon" )))] pub(crate) use libc::{ ip_mreq_source as IpMreqSource, IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, }; #[cfg(not(any( target_os = "dragonfly", target_os = "freebsd", target_os = "haiku", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "nto", target_os = "openbsd", target_os = "solaris", target_os = "tvos", target_os = "watchos", target_os = "wasi", )))] pub(crate) use libc::{IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP}; #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "haiku", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "openbsd", target_os = "solaris", target_os = "tvos", target_os = "watchos", all(target_os = "wasi", not(target_env = "p1")), ))] pub(crate) use libc::{ IPV6_JOIN_GROUP as IPV6_ADD_MEMBERSHIP, IPV6_LEAVE_GROUP as IPV6_DROP_MEMBERSHIP, }; #[cfg(not(target_os = "wasi"))] pub(crate) use libc::{IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IP_MULTICAST_IF, MSG_OOB}; #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "cygwin", all(target_os = "wasi", not(target_env = "p1")), ) ))] pub(crate) use libc::{TCP_KEEPCNT, TCP_KEEPINTVL}; // See this type in the Windows file. pub(crate) type Bool = c_int; #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", all(target_os = "nto", any(target_env = "nto70", target_env = "nto71"),), target_os = "tvos", target_os = "watchos", ))] use libc::TCP_KEEPALIVE as KEEPALIVE_TIME; #[cfg(not(any( target_os = "haiku", target_os = "ios", target_os = "visionos", target_os = "macos", all(target_os = "nto", any(target_env = "nto70", target_env = "nto71"),), target_os = "openbsd", target_os = "tvos", target_os = "watchos", target_os = "vita", )))] use libc::TCP_KEEPIDLE as KEEPALIVE_TIME; /// Helper macro to execute a system call that returns an `io::Result`. macro_rules! syscall { ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{ #[allow(unused_unsafe)] let res = unsafe { libc::$fn($($arg, )*) }; if res == -1 { Err(std::io::Error::last_os_error()) } else { Ok(res) } }}; } /// Maximum size of a buffer passed to system call like `recv` and `send`. #[cfg(not(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "cygwin", )))] const MAX_BUF_LEN: usize = ssize_t::MAX as usize; // The maximum read limit on most posix-like systems is `SSIZE_MAX`, with the // man page quoting that if the count of bytes to read is greater than // `SSIZE_MAX` the result is "unspecified". // // On macOS, however, apparently the 64-bit libc is either buggy or // intentionally showing odd behavior by rejecting any read with a size larger // than or equal to INT_MAX. To handle both of these the read size is capped on // both platforms. #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "cygwin", ))] const MAX_BUF_LEN: usize = c_int::MAX as usize - 1; // TCP_CA_NAME_MAX isn't defined in user space include files(not in libc) #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] const TCP_CA_NAME_MAX: usize = 16; #[cfg(any( all( target_os = "linux", any( target_env = "gnu", all(target_env = "uclibc", target_pointer_width = "64") ) ), target_os = "android", ))] type IovLen = usize; #[cfg(any( all( target_os = "linux", any( target_env = "musl", target_env = "ohos", all(target_env = "uclibc", target_pointer_width = "32") ) ), target_os = "aix", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "haiku", target_os = "hurd", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "netbsd", target_os = "nto", target_os = "openbsd", target_os = "solaris", target_os = "tvos", target_os = "watchos", target_os = "espidf", target_os = "vita", target_os = "cygwin", ))] type IovLen = c_int; /// Unix only API. impl Domain { /// Domain for low-level packet interface, corresponding to `AF_PACKET`. #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub const PACKET: Domain = Domain(libc::AF_PACKET); /// Domain for low-level VSOCK interface, corresponding to `AF_VSOCK`. #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub const VSOCK: Domain = Domain(libc::AF_VSOCK); } impl_debug!( Domain, libc::AF_INET, libc::AF_INET6, #[cfg(not(target_os = "wasi"))] libc::AF_UNIX, #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] libc::AF_PACKET, #[cfg(any(target_os = "android", target_os = "linux"))] libc::AF_VSOCK, libc::AF_UNSPEC, // = 0. ); /// Unix only API. impl Type { /// Set `SOCK_NONBLOCK` on the `Type`. #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", all(target_os = "wasi", not(target_env = "p1")), ) ))] pub const fn nonblocking(self) -> Type { Type(self.0 | libc::SOCK_NONBLOCK) } /// Set `SOCK_CLOEXEC` on the `Type`. #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "cygwin", ) ))] pub const fn cloexec(self) -> Type { self._cloexec() } #[cfg(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "linux", target_os = "netbsd", 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) } } impl_debug!( Type, libc::SOCK_STREAM, libc::SOCK_DGRAM, #[cfg(all(feature = "all", target_os = "linux"))] libc::SOCK_DCCP, #[cfg(not(any( target_os = "redox", target_os = "espidf", target_os = "wasi", target_os = "horizon" )))] libc::SOCK_RAW, #[cfg(not(any( target_os = "redox", target_os = "haiku", target_os = "espidf", target_os = "wasi", target_os = "horizon" )))] libc::SOCK_RDM, #[cfg(not(any(target_os = "espidf", target_os = "wasi", target_os = "horizon")))] libc::SOCK_SEQPACKET, /* TODO: add these optional bit OR-ed flags: #[cfg(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "netbsd", target_os = "openbsd" ))] libc::SOCK_NONBLOCK, #[cfg(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "netbsd", target_os = "openbsd" ))] libc::SOCK_CLOEXEC, */ ); impl_debug!( Protocol, #[cfg(not(target_os = "wasi"))] libc::IPPROTO_ICMP, #[cfg(not(target_os = "wasi"))] libc::IPPROTO_ICMPV6, libc::IPPROTO_TCP, libc::IPPROTO_UDP, #[cfg(target_os = "linux")] libc::IPPROTO_MPTCP, #[cfg(all(feature = "all", target_os = "linux"))] libc::IPPROTO_DCCP, #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] libc::IPPROTO_SCTP, #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", ) ))] libc::IPPROTO_UDPLITE, #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "openbsd")))] libc::IPPROTO_DIVERT, ); /// Unix-only API. #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] impl RecvFlags { /// Check if the message terminates a record. /// /// Not all socket types support the notion of records. For socket types /// that do support it (such as [`SEQPACKET`]), a record is terminated by /// sending a message with the end-of-record flag set. /// /// On Unix this corresponds to the `MSG_EOR` flag. /// /// [`SEQPACKET`]: Type::SEQPACKET #[cfg(not(any(target_os = "espidf", target_os = "horizon")))] pub const fn is_end_of_record(self) -> bool { self.0 & libc::MSG_EOR != 0 } /// Check if the message contains out-of-band data. /// /// This is useful for protocols where you receive out-of-band data /// mixed in with the normal data stream. /// /// On Unix this corresponds to the `MSG_OOB` flag. pub const fn is_out_of_band(self) -> bool { self.0 & libc::MSG_OOB != 0 } /// Check if the confirm flag is set. /// /// This is used by SocketCAN to indicate a frame was sent via the /// socket it is received on. This flag can be interpreted as a /// 'transmission confirmation'. /// /// On Unix this corresponds to the `MSG_CONFIRM` flag. #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub const fn is_confirm(self) -> bool { self.0 & libc::MSG_CONFIRM != 0 } /// Check if the don't route flag is set. /// /// This is used by SocketCAN to indicate a frame was created /// on the local host. /// /// On Unix this corresponds to the `MSG_DONTROUTE` flag. #[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 } } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] impl std::fmt::Debug for RecvFlags { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut s = f.debug_struct("RecvFlags"); #[cfg(not(any(target_os = "espidf", target_os = "horizon")))] s.field("is_end_of_record", &self.is_end_of_record()); s.field("is_out_of_band", &self.is_out_of_band()); #[cfg(not(target_os = "espidf"))] 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", target_os = "cygwin"), ))] s.field("is_dontroute", &self.is_dontroute()); s.finish() } } #[repr(transparent)] pub struct MaybeUninitSlice<'a> { vec: libc::iovec, _lifetime: PhantomData<&'a mut [MaybeUninit]>, } unsafe impl<'a> Send for MaybeUninitSlice<'a> {} unsafe impl<'a> Sync for MaybeUninitSlice<'a> {} impl<'a> MaybeUninitSlice<'a> { pub(crate) fn new(buf: &'a mut [MaybeUninit]) -> MaybeUninitSlice<'a> { MaybeUninitSlice { vec: libc::iovec { iov_base: buf.as_mut_ptr().cast(), iov_len: buf.len(), }, _lifetime: PhantomData, } } pub(crate) fn as_slice(&self) -> &[MaybeUninit] { unsafe { slice::from_raw_parts(self.vec.iov_base.cast(), self.vec.iov_len) } } pub(crate) fn as_mut_slice(&mut self) -> &mut [MaybeUninit] { unsafe { slice::from_raw_parts_mut(self.vec.iov_base.cast(), self.vec.iov_len) } } } /// Returns the offset of the `sun_path` member of the passed unix socket address. #[cfg(not(target_os = "wasi"))] pub(crate) fn offset_of_path(storage: &libc::sockaddr_un) -> usize { let base = storage as *const _ as usize; let path = ptr::addr_of!(storage.sun_path) as usize; path - base } #[cfg(not(target_os = "wasi"))] #[allow(unsafe_op_in_unsafe_fn)] pub(crate) fn unix_sockaddr(path: &Path) -> io::Result { let mut storage = SockAddrStorage::zeroed(); let len = { // SAFETY: sockaddr_un is one of the sockaddr_* types defined by this platform. let storage = unsafe { storage.view_as::() }; let bytes = path.as_os_str().as_bytes(); let too_long = match bytes.first() { None => false, // linux abstract namespaces aren't null-terminated Some(&0) => bytes.len() > storage.sun_path.len(), Some(_) => bytes.len() >= storage.sun_path.len(), }; if too_long { return Err(io::Error::new( io::ErrorKind::InvalidInput, "path must be shorter than SUN_LEN", )); } storage.sun_family = libc::AF_UNIX as sa_family_t; // SAFETY: `bytes` and `addr.sun_path` are not overlapping and // both point to valid memory. // `storage` was initialized to zero above, so the path is // already NULL terminated. unsafe { ptr::copy_nonoverlapping( bytes.as_ptr(), storage.sun_path.as_mut_ptr().cast(), bytes.len(), ); } let sun_path_offset = offset_of_path(storage); sun_path_offset + bytes.len() + match bytes.first() { Some(&0) | None => 0, Some(_) => 1, } }; Ok(unsafe { SockAddr::new(storage, len as socklen_t) }) } // Used in `MsgHdr`. #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) use libc::msghdr; #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn set_msghdr_name(msg: &mut msghdr, name: &SockAddr) { msg.msg_name = name.as_ptr() as *mut _; msg.msg_namelen = name.len(); } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] #[allow(clippy::unnecessary_cast)] // IovLen type can be `usize`. pub(crate) fn set_msghdr_iov(msg: &mut msghdr, ptr: *mut libc::iovec, len: usize) { msg.msg_iov = ptr; msg.msg_iovlen = min(len, IovLen::MAX as usize) as IovLen; } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn set_msghdr_control(msg: &mut msghdr, ptr: *mut libc::c_void, len: usize) { msg.msg_control = ptr; msg.msg_controllen = len as _; } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn set_msghdr_flags(msg: &mut msghdr, flags: c_int) { msg.msg_flags = flags; } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn msghdr_flags(msg: &msghdr) -> RecvFlags { RecvFlags(msg.msg_flags) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn msghdr_control_len(msg: &msghdr) -> usize { msg.msg_controllen as _ } /// Unix only API. impl SockAddr { /// Constructs a `SockAddr` with the family `AF_VSOCK` and the provided CID/port. /// /// # Errors /// /// This function can never fail. In a future version of this library it will be made /// infallible. #[allow(unsafe_op_in_unsafe_fn)] #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub fn vsock(cid: u32, port: u32) -> SockAddr { let mut storage = SockAddrStorage::zeroed(); { // SAFETY: sockaddr_vm is one of the sockaddr_* types defined by this platform. let storage = unsafe { storage.view_as::() }; storage.svm_family = libc::AF_VSOCK as sa_family_t; storage.svm_cid = cid; storage.svm_port = port; } unsafe { SockAddr::new(storage, mem::size_of::() as socklen_t) } } /// Returns this address VSOCK CID/port if it is in the `AF_VSOCK` family, /// otherwise return `None`. #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub fn as_vsock_address(&self) -> Option<(u32, u32)> { if self.family() == libc::AF_VSOCK as sa_family_t { // Safety: if the ss_family field is AF_VSOCK then storage must be a sockaddr_vm. let addr = unsafe { &*(self.as_ptr() as *const libc::sockaddr_vm) }; Some((addr.svm_cid, addr.svm_port)) } else { None } } } /// Unix only API (not available on WASI). #[cfg(not(target_os = "wasi"))] impl SockAddr { /// Returns true if this address is an unnamed address from the `AF_UNIX` family (for local /// interprocess communication), false otherwise. pub fn is_unnamed(&self) -> bool { self.as_sockaddr_un() .map(|storage| { self.len() == offset_of_path(storage) as _ // On some non-linux platforms a zeroed path is returned for unnamed. // 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", target_os = "cygwin"))) && storage.sun_path[0] == 0) }) .unwrap_or_default() } /// Returns the underlying `sockaddr_un` object if this address is from the `AF_UNIX` family, /// otherwise returns `None`. pub(crate) fn as_sockaddr_un(&self) -> Option<&libc::sockaddr_un> { self.is_unix().then(|| { // SAFETY: if unix socket, i.e. the `ss_family` field is `AF_UNIX` then storage must be // a `sockaddr_un`. unsafe { &*self.as_ptr().cast::() } }) } /// Get the length of the path bytes of the address, not including the terminating or initial /// (for abstract names) null byte. /// /// Should not be called on unnamed addresses. fn path_len(&self, storage: &libc::sockaddr_un) -> usize { debug_assert!(!self.is_unnamed()); self.len() as usize - offset_of_path(storage) - 1 } /// Get a u8 slice for the bytes of the pathname or abstract name. /// /// Should not be called on unnamed addresses. fn path_bytes(&self, storage: &libc::sockaddr_un, abstract_name: bool) -> &[u8] { debug_assert!(!self.is_unnamed()); // SAFETY: the pointed objects of type `i8` have the same memory layout as `u8`. The path is // the last field in the storage and so its length is equal to // TOTAL_LENGTH - OFFSET_OF_PATH -1 // Where the 1 is either a terminating null if we have a pathname address, or the initial // null byte, if it's an abstract name address. In the latter case, the path bytes start // after the initial null byte, hence the `offset`. // There is no safe way to convert a `&[i8]` to `&[u8]` unsafe { slice::from_raw_parts( (storage.sun_path.as_ptr() as *const u8).offset(abstract_name as isize), self.path_len(storage), ) } } /// Returns this address as Unix `SocketAddr` if it is an `AF_UNIX` pathname /// address, otherwise returns `None`. pub fn as_unix(&self) -> Option { let path = self.as_pathname()?; // SAFETY: we can represent this as a valid pathname, then so can the // standard library. Some(std::os::unix::net::SocketAddr::from_pathname(path).unwrap()) } /// Returns this address as a `Path` reference if it is an `AF_UNIX` /// pathname address, otherwise returns `None`. pub fn as_pathname(&self) -> Option<&Path> { self.as_sockaddr_un().and_then(|storage| { (self.len() > offset_of_path(storage) as _ && storage.sun_path[0] != 0).then(|| { let path_slice = self.path_bytes(storage, false); Path::new::(OsStrExt::from_bytes(path_slice)) }) }) } /// Returns this address as a slice of bytes representing an abstract address if it is an /// `AF_UNIX` abstract address, otherwise returns `None`. /// /// Abstract addresses are a Linux extension, so this method returns `None` on all non-Linux /// platforms. 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", 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", target_os = "cygwin")))] None } } pub(crate) type Socket = std::os::fd::OwnedFd; pub(crate) type RawSocket = c_int; pub(crate) unsafe fn socket_from_raw(socket: RawSocket) -> Socket { Socket::from_raw_fd(socket) } pub(crate) fn socket_as_raw(socket: &Socket) -> RawSocket { socket.as_raw_fd() } pub(crate) fn socket_into_raw(socket: Socket) -> RawSocket { socket.into_raw_fd() } pub(crate) fn socket(family: c_int, ty: c_int, protocol: c_int) -> io::Result { syscall!(socket(family, ty, protocol)) } #[cfg(all(feature = "all", unix))] pub(crate) fn socketpair(family: c_int, ty: c_int, protocol: c_int) -> io::Result<[RawSocket; 2]> { let mut fds = [0, 0]; syscall!(socketpair(family, ty, protocol, fds.as_mut_ptr())).map(|_| fds) } pub(crate) fn bind(fd: RawSocket, addr: &SockAddr) -> io::Result<()> { syscall!(bind(fd, addr.as_ptr().cast::(), addr.len() as _)).map(|_| ()) } pub(crate) fn connect(fd: RawSocket, addr: &SockAddr) -> io::Result<()> { syscall!(connect(fd, addr.as_ptr().cast::(), addr.len())).map(|_| ()) } pub(crate) fn poll_connect(socket: &crate::Socket, timeout: Duration) -> io::Result<()> { let start = Instant::now(); let mut pollfd = libc::pollfd { fd: socket.as_raw(), events: libc::POLLIN | libc::POLLOUT, revents: 0, }; loop { let elapsed = start.elapsed(); if elapsed >= timeout { return Err(io::ErrorKind::TimedOut.into()); } let timeout = (timeout - elapsed).as_millis(); let timeout = timeout.clamp(1, c_int::MAX as u128) as c_int; match syscall!(poll(&mut pollfd, 1, timeout)) { Ok(0) => return Err(io::ErrorKind::TimedOut.into()), Ok(_) => { // Error or hang up indicates an error (or failure to connect). if (pollfd.revents & libc::POLLHUP) != 0 || (pollfd.revents & libc::POLLERR) != 0 { match socket.take_error() { Ok(Some(err)) | Err(err) => return Err(err), Ok(None) => { return Err(io::Error::new( io::ErrorKind::Other, "no error set after POLLHUP", )) } } } return Ok(()); } // Got interrupted, try again. Err(ref err) if err.kind() == io::ErrorKind::Interrupted => continue, Err(err) => return Err(err), } } } pub(crate) fn listen(fd: RawSocket, backlog: c_int) -> io::Result<()> { syscall!(listen(fd, backlog)).map(|_| ()) } pub(crate) fn accept(fd: RawSocket) -> io::Result<(RawSocket, SockAddr)> { // Safety: `accept` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, len| syscall!(accept(fd, storage.cast(), len))) } } pub(crate) fn getsockname(fd: RawSocket) -> io::Result { // Safety: `accept` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, len| syscall!(getsockname(fd, storage.cast(), len))) } .map(|(_, addr)| addr) } pub(crate) fn getpeername(fd: RawSocket) -> io::Result { // Safety: `accept` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, len| syscall!(getpeername(fd, storage.cast(), len))) } .map(|(_, addr)| addr) } #[cfg(not(target_os = "wasi"))] pub(crate) fn try_clone(fd: RawSocket) -> io::Result { syscall!(fcntl(fd, libc::F_DUPFD_CLOEXEC, 0)) } #[cfg(all( feature = "all", any(unix, all(target_os = "wasi", not(target_env = "p1"))), not(target_os = "vita") ))] pub(crate) fn nonblocking(fd: RawSocket) -> io::Result { let file_status_flags = fcntl_get(fd, libc::F_GETFL)?; Ok((file_status_flags & libc::O_NONBLOCK) != 0) } #[cfg(all(feature = "all", target_os = "vita"))] pub(crate) fn nonblocking(fd: RawSocket) -> io::Result { unsafe { getsockopt::(fd, libc::SOL_SOCKET, libc::SO_NONBLOCK).map(|non_block| non_block != 0) } } #[cfg(not(target_os = "vita"))] pub(crate) fn set_nonblocking(fd: RawSocket, nonblocking: bool) -> io::Result<()> { if nonblocking { fcntl_add(fd, libc::F_GETFL, libc::F_SETFL, libc::O_NONBLOCK) } else { fcntl_remove(fd, libc::F_GETFL, libc::F_SETFL, libc::O_NONBLOCK) } } #[cfg(target_os = "vita")] pub(crate) fn set_nonblocking(fd: RawSocket, nonblocking: bool) -> io::Result<()> { unsafe { setsockopt( fd, libc::SOL_SOCKET, libc::SO_NONBLOCK, nonblocking as c_int, ) } } pub(crate) fn shutdown(fd: RawSocket, how: Shutdown) -> io::Result<()> { let how = match how { Shutdown::Write => libc::SHUT_WR, Shutdown::Read => libc::SHUT_RD, Shutdown::Both => libc::SHUT_RDWR, }; syscall!(shutdown(fd, how)).map(|_| ()) } pub(crate) fn recv(fd: RawSocket, buf: &mut [MaybeUninit], flags: c_int) -> io::Result { syscall!(recv( fd, buf.as_mut_ptr().cast(), min(buf.len(), MAX_BUF_LEN), flags, )) .map(|n| n as usize) } pub(crate) fn recv_from( fd: RawSocket, buf: &mut [MaybeUninit], flags: c_int, ) -> io::Result<(usize, SockAddr)> { // Safety: `recvfrom` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|addr, addrlen| { syscall!(recvfrom( fd, buf.as_mut_ptr().cast(), min(buf.len(), MAX_BUF_LEN), flags, addr.cast(), addrlen )) .map(|n| n as usize) }) } } pub(crate) fn peek_sender(fd: RawSocket) -> io::Result { // Unix-like platforms simply truncate the returned data, so this implementation is trivial. // However, for Windows this requires suppressing the `WSAEMSGSIZE` error, // so that requires a different approach. // NOTE: macOS does not populate `sockaddr` if you pass a zero-sized buffer. let (_, sender) = recv_from(fd, &mut [MaybeUninit::uninit(); 8], MSG_PEEK)?; Ok(sender) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn recv_vectored( fd: RawSocket, bufs: &mut [crate::MaybeUninitSlice<'_>], flags: c_int, ) -> io::Result<(usize, RecvFlags)> { let mut msg = MsgHdrMut::new().with_buffers(bufs); let n = recvmsg(fd, &mut msg, flags)?; Ok((n, msg.flags())) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn recv_from_vectored( fd: RawSocket, bufs: &mut [crate::MaybeUninitSlice<'_>], flags: c_int, ) -> io::Result<(usize, RecvFlags, SockAddr)> { let mut msg = MsgHdrMut::new().with_buffers(bufs); // SAFETY: `recvmsg` initialises the address storage and we set the length // manually. let (n, addr) = unsafe { SockAddr::try_init(|storage, len| { msg.inner.msg_name = storage.cast(); msg.inner.msg_namelen = *len; let n = recvmsg(fd, &mut msg, flags)?; // Set the correct address length. *len = msg.inner.msg_namelen; Ok(n) })? }; Ok((n, msg.flags(), addr)) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn recvmsg( fd: RawSocket, msg: &mut MsgHdrMut<'_, '_, '_>, flags: c_int, ) -> io::Result { syscall!(recvmsg(fd, &mut msg.inner, flags)).map(|n| n as usize) } pub(crate) fn send(fd: RawSocket, buf: &[u8], flags: c_int) -> io::Result { syscall!(send( fd, buf.as_ptr().cast(), min(buf.len(), MAX_BUF_LEN), flags, )) .map(|n| n as usize) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn send_vectored( fd: RawSocket, bufs: &[IoSlice<'_>], flags: c_int, ) -> io::Result { let msg = MsgHdr::new().with_buffers(bufs); sendmsg(fd, &msg, flags) } pub(crate) fn send_to( fd: RawSocket, buf: &[u8], addr: &SockAddr, flags: c_int, ) -> io::Result { syscall!(sendto( fd, buf.as_ptr().cast(), min(buf.len(), MAX_BUF_LEN), flags, addr.as_ptr().cast::(), addr.len(), )) .map(|n| n as usize) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn send_to_vectored( fd: RawSocket, bufs: &[IoSlice<'_>], addr: &SockAddr, flags: c_int, ) -> io::Result { let msg = MsgHdr::new().with_addr(addr).with_buffers(bufs); sendmsg(fd, &msg, flags) } #[cfg(not(any(target_os = "redox", target_os = "wasi", target_os = "horizon")))] pub(crate) fn sendmsg(fd: RawSocket, msg: &MsgHdr<'_, '_, '_>, flags: c_int) -> io::Result { syscall!(sendmsg(fd, &msg.inner, flags)).map(|n| n as usize) } /// Wrapper around `getsockopt` to deal with platform specific timeouts. pub(crate) fn timeout_opt(fd: RawSocket, opt: c_int, val: c_int) -> io::Result> { unsafe { getsockopt(fd, opt, val).map(from_timeval) } } const fn from_timeval(duration: libc::timeval) -> Option { if duration.tv_sec == 0 && duration.tv_usec == 0 { None } else { let sec = duration.tv_sec as u64; let nsec = (duration.tv_usec as u32) * 1000; Some(Duration::new(sec, nsec)) } } /// Wrapper around `setsockopt` to deal with platform specific timeouts. pub(crate) fn set_timeout_opt( fd: RawSocket, opt: c_int, val: c_int, duration: Option, ) -> io::Result<()> { let duration = into_timeval(duration); unsafe { setsockopt(fd, opt, val, duration) } } fn into_timeval(duration: Option) -> libc::timeval { match duration { // https://github.com/rust-lang/libc/issues/1848 #[cfg_attr(target_env = "musl", allow(deprecated))] Some(duration) => libc::timeval { tv_sec: min(duration.as_secs(), libc::time_t::MAX as u64) as libc::time_t, tv_usec: duration.subsec_micros() as libc::suseconds_t, }, None => libc::timeval { tv_sec: 0, tv_usec: 0, }, } } #[cfg(all( feature = "all", not(any(target_os = "haiku", target_os = "openbsd", target_os = "vita")) ))] pub(crate) fn tcp_keepalive_time(fd: RawSocket) -> io::Result { unsafe { getsockopt::(fd, IPPROTO_TCP, KEEPALIVE_TIME) .map(|secs| Duration::from_secs(secs as u64)) } } #[allow(unused_variables)] pub(crate) fn set_tcp_keepalive(fd: RawSocket, keepalive: &TcpKeepalive) -> io::Result<()> { #[cfg(not(any( target_os = "haiku", target_os = "openbsd", target_os = "nto", target_os = "vita" )))] if let Some(time) = keepalive.time { let secs = into_secs(time); unsafe { setsockopt(fd, libc::IPPROTO_TCP, KEEPALIVE_TIME, secs)? } } #[cfg(any( target_os = "aix", target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "cygwin", all(target_os = "wasi", not(target_env = "p1")), ))] { if let Some(interval) = keepalive.interval { let secs = into_secs(interval); unsafe { setsockopt(fd, libc::IPPROTO_TCP, libc::TCP_KEEPINTVL, secs)? } } if let Some(retries) = keepalive.retries { unsafe { setsockopt(fd, libc::IPPROTO_TCP, libc::TCP_KEEPCNT, retries as c_int)? } } } #[cfg(target_os = "nto")] if let Some(time) = keepalive.time { let secs = into_timeval(Some(time)); unsafe { setsockopt(fd, libc::IPPROTO_TCP, KEEPALIVE_TIME, secs)? } } Ok(()) } #[cfg(not(any( target_os = "haiku", target_os = "openbsd", target_os = "nto", target_os = "vita" )))] fn into_secs(duration: Duration) -> c_int { min(duration.as_secs(), c_int::MAX as u64) as c_int } /// Get the flags using `cmd`. #[cfg(not(target_os = "vita"))] fn fcntl_get(fd: RawSocket, cmd: c_int) -> io::Result { syscall!(fcntl(fd, cmd)) } /// Add `flag` to the current set flags of `F_GETFD`. #[cfg(not(target_os = "vita"))] fn fcntl_add(fd: RawSocket, get_cmd: c_int, set_cmd: c_int, flag: c_int) -> io::Result<()> { let previous = fcntl_get(fd, get_cmd)?; let new = previous | flag; if new != previous { syscall!(fcntl(fd, set_cmd, new)).map(|_| ()) } else { // Flag was already set. Ok(()) } } /// Remove `flag` to the current set flags of `F_GETFD`. #[cfg(not(target_os = "vita"))] fn fcntl_remove(fd: RawSocket, get_cmd: c_int, set_cmd: c_int, flag: c_int) -> io::Result<()> { let previous = fcntl_get(fd, get_cmd)?; let new = previous & !flag; if new != previous { syscall!(fcntl(fd, set_cmd, new)).map(|_| ()) } else { // Flag was already set. Ok(()) } } /// Caller must ensure `T` is the correct type for `opt` and `val`. pub(crate) unsafe fn getsockopt(fd: RawSocket, opt: c_int, val: c_int) -> io::Result { let mut payload: MaybeUninit = MaybeUninit::uninit(); let mut len = size_of::() as libc::socklen_t; syscall!(getsockopt( fd, opt, val, payload.as_mut_ptr().cast(), &mut len, )) .map(|_| { debug_assert_eq!(len as usize, size_of::()); // Safety: `getsockopt` initialised `payload` for us. payload.assume_init() }) } /// Caller must ensure `T` is the correct type for `opt` and `val`. pub(crate) unsafe fn setsockopt( fd: RawSocket, opt: c_int, val: c_int, payload: T, ) -> io::Result<()> { let payload = ptr::addr_of!(payload).cast(); syscall!(setsockopt( fd, opt, val, payload, mem::size_of::() as libc::socklen_t, )) .map(|_| ()) } pub(crate) const fn to_in_addr(addr: &Ipv4Addr) -> in_addr { // `s_addr` is stored as BE on all machines, and the array is in BE order. // So the native endian conversion method is used so that it's never // swapped. in_addr { s_addr: u32::from_ne_bytes(addr.octets()), } } pub(crate) fn from_in_addr(in_addr: in_addr) -> Ipv4Addr { Ipv4Addr::from(in_addr.s_addr.to_ne_bytes()) } pub(crate) const fn to_in6_addr(addr: &Ipv6Addr) -> in6_addr { in6_addr { s6_addr: addr.octets(), } } pub(crate) fn from_in6_addr(addr: in6_addr) -> Ipv6Addr { Ipv6Addr::from(addr.s6_addr) } #[cfg(not(any( target_os = "aix", target_os = "haiku", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "nto", target_os = "espidf", target_os = "vita", target_os = "cygwin", target_os = "wasi", target_os = "horizon" )))] pub(crate) const fn to_mreqn( multiaddr: &Ipv4Addr, interface: &crate::socket::InterfaceIndexOrAddress, ) -> libc::ip_mreqn { match interface { crate::socket::InterfaceIndexOrAddress::Index(interface) => libc::ip_mreqn { imr_multiaddr: to_in_addr(multiaddr), imr_address: to_in_addr(&Ipv4Addr::UNSPECIFIED), imr_ifindex: *interface as _, }, crate::socket::InterfaceIndexOrAddress::Address(interface) => libc::ip_mreqn { imr_multiaddr: to_in_addr(multiaddr), imr_address: to_in_addr(interface), imr_ifindex: 0, }, } } #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub(crate) fn original_dst_v4(fd: RawSocket) -> io::Result { // Safety: `getsockopt` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, len| { syscall!(getsockopt( fd, libc::SOL_IP, libc::SO_ORIGINAL_DST, storage.cast(), len )) }) } .map(|(_, addr)| addr) } /// Get the value for the `IP6T_SO_ORIGINAL_DST` option on this socket. /// /// This value contains the original destination IPv6 address of the connection /// redirected using `ip6tables` `REDIRECT` or `TPROXY`. #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub(crate) fn original_dst_v6(fd: RawSocket) -> io::Result { // Safety: `getsockopt` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, len| { syscall!(getsockopt( fd, libc::SOL_IPV6, libc::IP6T_SO_ORIGINAL_DST, storage.cast(), len )) }) } .map(|(_, addr)| addr) } /// Unix only API. impl crate::Socket { /// Accept a new incoming connection from this listener. /// /// This function directly corresponds to the `accept4(2)` function. /// /// This function will block the calling thread until a new connection is /// established. When established, the corresponding `Socket` and the remote /// peer's address will be returned. #[doc = man_links!(unix: accept4(2))] #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", ) ))] 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 = "fuchsia", target_os = "illumos", 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. unsafe { SockAddr::try_init(|storage, len| { syscall!(accept4(self.as_raw(), storage.cast(), len, flags)) .map(crate::Socket::from_raw) }) } } /// Sets `CLOEXEC` on the socket. /// /// # Notes /// /// On supported platforms you can use [`Type::cloexec`]. #[cfg_attr( any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "wasi", target_os = "horizon" ), allow(rustdoc::broken_intra_doc_links) )] #[cfg(all(feature = "all", not(target_os = "vita")))] pub fn set_cloexec(&self, close_on_exec: bool) -> io::Result<()> { self._set_cloexec(close_on_exec) } #[cfg(not(target_os = "vita"))] pub(crate) fn _set_cloexec(&self, close_on_exec: bool) -> io::Result<()> { if close_on_exec { fcntl_add( self.as_raw(), libc::F_GETFD, libc::F_SETFD, libc::FD_CLOEXEC, ) } else { fcntl_remove( self.as_raw(), libc::F_GETFD, libc::F_SETFD, libc::FD_CLOEXEC, ) } } /// 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", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", ) ))] pub fn set_nosigpipe(&self, nosigpipe: bool) -> io::Result<()> { self._set_nosigpipe(nosigpipe) } #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", ))] pub(crate) fn _set_nosigpipe(&self, nosigpipe: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_NOSIGPIPE, nosigpipe as c_int, ) } } /// Gets the value of the `TCP_MAXSEG` option on this socket. /// /// For more information about this option, see [`set_tcp_mss`]. /// /// [`set_tcp_mss`]: crate::Socket::set_tcp_mss #[cfg(all(feature = "all", not(any(target_os = "redox", target_os = "wasi"))))] pub fn tcp_mss(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::IPPROTO_TCP, libc::TCP_MAXSEG) .map(|mss| mss as u32) } } /// Sets the value of the `TCP_MAXSEG` option on this socket. /// /// The `TCP_MAXSEG` option denotes the TCP Maximum Segment Size and is only /// available on TCP sockets. #[cfg(all(feature = "all", not(any(target_os = "redox", target_os = "wasi"))))] pub fn set_tcp_mss(&self, mss: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::IPPROTO_TCP, libc::TCP_MAXSEG, mss as c_int, ) } } /// Returns `true` if `listen(2)` was called on this socket by checking the /// `SO_ACCEPTCONN` option on this socket. #[cfg(all( feature = "all", any( target_os = "aix", target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "cygwin", ) ))] pub fn is_listener(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_ACCEPTCONN) .map(|v| v != 0) } } /// Returns the [`Domain`] of this socket by checking the `SO_DOMAIN` option /// on this socket. #[cfg(all( feature = "all", any( target_os = "android", // TODO: add FreeBSD. // target_os = "freebsd", target_os = "fuchsia", target_os = "linux", ) ))] pub fn domain(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_DOMAIN).map(Domain) } } /// Returns the [`Protocol`] of this socket by checking the `SO_PROTOCOL` /// option on this socket. #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", ) ))] pub fn protocol(&self) -> io::Result> { unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_PROTOCOL).map(|v| match v { 0 => None, p => Some(Protocol(p)), }) } } /// Gets the value for the `SO_MARK` option on this socket. /// /// This value gets the socket mark field for each packet sent through /// this socket. /// /// On Linux this function requires the `CAP_NET_ADMIN` capability. #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn mark(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_MARK) .map(|mark| mark as u32) } } /// Sets the value for the `SO_MARK` option on this socket. /// /// This value sets the socket mark field for each packet sent through /// this socket. Changing the mark can be used for mark-based routing /// without netfilter or for packet filtering. /// /// On Linux this function requires the `CAP_NET_ADMIN` capability. #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn set_mark(&self, mark: u32) -> io::Result<()> { unsafe { setsockopt::( self.as_raw(), libc::SOL_SOCKET, libc::SO_MARK, mark as c_int, ) } } /// Get the value of the `TCP_CORK` option on this socket. /// /// For more information about this option, see [`set_tcp_cork`]. /// /// [`set_tcp_cork`]: crate::Socket::set_tcp_cork #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn tcp_cork(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::IPPROTO_TCP, libc::TCP_CORK) .map(|cork| cork != 0) } } /// Set the value of the `TCP_CORK` option on this socket. /// /// If set, don't send out partial frames. All queued partial frames are /// sent when the option is cleared again. There is a 200 millisecond ceiling on /// the time for which output is corked by `TCP_CORK`. If this ceiling is reached, /// then queued data is automatically transmitted. #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn set_tcp_cork(&self, cork: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::IPPROTO_TCP, libc::TCP_CORK, cork as c_int, ) } } /// Get the value of the `TCP_QUICKACK` option on this socket. /// /// For more information about this option, see [`set_tcp_quickack`]. /// /// [`set_tcp_quickack`]: crate::Socket::set_tcp_quickack #[cfg(all( feature = "all", any( target_os = "android", target_os = "fuchsia", target_os = "linux", target_os = "cygwin", ) ))] pub fn tcp_quickack(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::IPPROTO_TCP, libc::TCP_QUICKACK) .map(|quickack| quickack != 0) } } /// Set the value of the `TCP_QUICKACK` option on this socket. /// /// If set, acks are sent immediately, rather than delayed if needed in accordance to normal /// TCP operation. This flag is not permanent, it only enables a switch to or from quickack mode. /// Subsequent operation of the TCP protocol will once again enter/leave quickack mode depending on /// 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", target_os = "cygwin", ) ))] pub fn set_tcp_quickack(&self, quickack: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::IPPROTO_TCP, libc::TCP_QUICKACK, quickack as c_int, ) } } /// Get the value of the `TCP_THIN_LINEAR_TIMEOUTS` option on this socket. /// /// For more information about this option, see [`set_tcp_thin_linear_timeouts`]. /// /// [`set_tcp_thin_linear_timeouts`]: crate::Socket::set_tcp_thin_linear_timeouts #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn tcp_thin_linear_timeouts(&self) -> io::Result { unsafe { getsockopt::( self.as_raw(), libc::IPPROTO_TCP, libc::TCP_THIN_LINEAR_TIMEOUTS, ) .map(|timeouts| timeouts != 0) } } /// Set the value of the `TCP_THIN_LINEAR_TIMEOUTS` option on this socket. /// /// If set, the kernel will dynamically detect a thin-stream connection if there are less than four packets in flight. /// With less than four packets in flight the normal TCP fast retransmission will not be effective. /// The kernel will modify the retransmission to avoid the very high latencies that thin stream suffer because of exponential backoff. #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn set_tcp_thin_linear_timeouts(&self, timeouts: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::IPPROTO_TCP, libc::TCP_THIN_LINEAR_TIMEOUTS, timeouts as c_int, ) } } /// Get the value of the `TCP_NOTSENT_LOWAT` option on this socket. /// /// For more information about this option, see [`set_tcp_notsent_lowat`]. /// /// [`set_tcp_notsent_lowat`]: crate::Socket::set_tcp_notsent_lowat #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub fn tcp_notsent_lowat(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::IPPROTO_TCP, libc::TCP_NOTSENT_LOWAT) .map(|lowat| lowat as u32) } } /// Set the value of the `TCP_NOTSENT_LOWAT` option on this socket. /// /// If set the kernel will limit the amount of _unsent_ data in the sendbuffer. /// This differs from `set_send_buffer_size` which limits the sum of unsent and unacknowledged data. #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub fn set_tcp_notsent_lowat(&self, lowat: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::IPPROTO_TCP, libc::TCP_NOTSENT_LOWAT, lowat as c_int, ) } } /// Gets the value for the `SO_BINDTODEVICE` option on this socket. /// /// This value gets the socket binded device's interface name. #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn device(&self) -> io::Result>> { // TODO: replace with `MaybeUninit::uninit_array` once stable. let mut buf: [MaybeUninit; libc::IFNAMSIZ] = unsafe { MaybeUninit::uninit().assume_init() }; let mut len = buf.len() as libc::socklen_t; syscall!(getsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTODEVICE, buf.as_mut_ptr().cast(), &mut len, ))?; if len == 0 { Ok(None) } else { let buf = &buf[..len as usize - 1]; // TODO: use `MaybeUninit::slice_assume_init_ref` once stable. Ok(Some(unsafe { &*(buf as *const [_] as *const [u8]) }.into())) } } /// Sets the value for the `SO_BINDTODEVICE` option on this socket. /// /// If a socket is bound to an interface, only packets received from that /// particular interface are processed by the socket. Note that this only /// works for some socket types, particularly `AF_INET` sockets. /// /// If `interface` is `None` or an empty string it removes the binding. #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn bind_device(&self, interface: Option<&[u8]>) -> io::Result<()> { let (value, len) = if let Some(interface) = interface { (interface.as_ptr(), interface.len()) } else { (ptr::null(), 0) }; syscall!(setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTODEVICE, value.cast(), len as libc::socklen_t, )) .map(|_| ()) } /// Sets the value for the `SO_SETFIB` option on this socket. /// /// Bind socket to the specified forwarding table (VRF) on a FreeBSD. #[cfg(all(feature = "all", target_os = "freebsd"))] pub fn set_fib(&self, fib: u32) -> io::Result<()> { syscall!(setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_SETFIB, (&fib as *const u32).cast(), mem::size_of::() as libc::socklen_t, )) .map(|_| ()) } /// Sets the value for `IP_BOUND_IF` or `SO_BINDTOIFINDEX` option on this socket. /// /// If a socket is bound to an interface, only packets received from that /// particular interface are processed by the socket. /// /// If `interface` is `None`, the binding is removed. If the `interface` /// index is not valid, an error is returned. /// /// One can use [`libc::if_nametoindex`] to convert an interface alias to an /// index. #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", target_os = "linux", target_os = "android", ) ))] pub fn bind_device_by_index_v4(&self, interface: Option) -> io::Result<()> { let index = interface.map_or(0, NonZeroU32::get); #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", ))] unsafe { setsockopt(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF, index) } #[cfg(any(target_os = "linux", target_os = "android",))] unsafe { setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTOIFINDEX, index, ) } } /// Sets the value for `IPV6_BOUND_IF` or `SO_BINDTOIFINDEX` option on this socket. /// /// If a socket is bound to an interface, only packets received from that /// particular interface are processed by the socket. /// /// If `interface` is `None`, the binding is removed. If the `interface` /// index is not valid, an error is returned. /// /// One can use [`libc::if_nametoindex`] to convert an interface alias to an /// index. #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", target_os = "linux", target_os = "android", ) ))] pub fn bind_device_by_index_v6(&self, interface: Option) -> io::Result<()> { let index = interface.map_or(0, NonZeroU32::get); #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", ))] unsafe { setsockopt(self.as_raw(), IPPROTO_IPV6, libc::IPV6_BOUND_IF, index) } #[cfg(any(target_os = "linux", target_os = "android",))] unsafe { setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTOIFINDEX, index, ) } } /// Gets the value for `IP_BOUND_IF` or `SO_BINDTOIFINDEX` option on this /// socket, i.e. the index for the interface to which the socket is bound. /// /// Returns `None` if the socket is not bound to any interface, otherwise /// returns an interface index. #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", target_os = "linux", target_os = "android", ) ))] pub fn device_index_v4(&self) -> io::Result> { #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", ))] let index = unsafe { getsockopt::(self.as_raw(), IPPROTO_IP, libc::IP_BOUND_IF)? }; #[cfg(any(target_os = "linux", target_os = "android",))] let index = unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTOIFINDEX)? }; Ok(NonZeroU32::new(index)) } /// Gets the value for `IPV6_BOUND_IF` or `SO_BINDTOIFINDEX` option on this /// socket, i.e. the index for the interface to which the socket is bound. /// /// Returns `None` if the socket is not bound to any interface, otherwise /// returns an interface index. #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", target_os = "linux", target_os = "android", ) ))] pub fn device_index_v6(&self) -> io::Result> { #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "illumos", target_os = "solaris", ))] let index = unsafe { getsockopt::(self.as_raw(), IPPROTO_IPV6, libc::IPV6_BOUND_IF)? }; #[cfg(any(target_os = "linux", target_os = "android",))] let index = unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_BINDTOIFINDEX)? }; Ok(NonZeroU32::new(index)) } /// Get the value of the `SO_INCOMING_CPU` option on this socket. /// /// For more information about this option, see [`set_cpu_affinity`]. /// /// [`set_cpu_affinity`]: crate::Socket::set_cpu_affinity #[cfg(all(feature = "all", target_os = "linux"))] pub fn cpu_affinity(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_INCOMING_CPU) .map(|cpu| cpu as usize) } } /// Set value for the `SO_INCOMING_CPU` option on this socket. /// /// Sets the CPU affinity of the socket. #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_cpu_affinity(&self, cpu: usize) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_INCOMING_CPU, cpu as c_int, ) } } /// Get the value of the `SO_REUSEPORT` option on this socket. /// /// For more information about this option, see [`set_reuse_port`]. /// /// [`set_reuse_port`]: crate::Socket::set_reuse_port #[cfg(all( feature = "all", not(any( target_os = "solaris", target_os = "illumos", target_os = "cygwin", target_os = "wasi" )) ))] pub fn reuse_port(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_REUSEPORT) .map(|reuse| reuse != 0) } } /// Set value for the `SO_REUSEPORT` option on this socket. /// /// This indicates that further calls to `bind` may allow reuse of local /// addresses. For IPv4 sockets this means that a socket may bind even when /// there's a socket already listening on this port. #[cfg(all( feature = "all", not(any( target_os = "solaris", target_os = "illumos", target_os = "cygwin", target_os = "wasi" )) ))] pub fn set_reuse_port(&self, reuse: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_REUSEPORT, reuse as c_int, ) } } /// Get the value of the `SO_REUSEPORT_LB` option on this socket. /// /// For more information about this option, see [`set_reuse_port_lb`]. /// /// [`set_reuse_port_lb`]: crate::Socket::set_reuse_port_lb #[cfg(all(feature = "all", target_os = "freebsd"))] pub fn reuse_port_lb(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_REUSEPORT_LB) .map(|reuse| reuse != 0) } } /// Set value for the `SO_REUSEPORT_LB` option on this socket. /// /// This allows multiple programs or threads to bind to the same port and /// incoming connections will be load balanced using a hash function. #[cfg(all(feature = "all", target_os = "freebsd"))] pub fn set_reuse_port_lb(&self, reuse: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_REUSEPORT_LB, reuse as c_int, ) } } /// Get the value of the `IP_FREEBIND` option on this socket. /// /// For more information about this option, see [`set_freebind_v4`]. /// /// [`set_freebind_v4`]: crate::Socket::set_freebind_v4 #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn freebind_v4(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_IP, libc::IP_FREEBIND) .map(|freebind| freebind != 0) } } /// Set value for the `IP_FREEBIND` option on this socket. /// /// If enabled, this boolean option allows binding to an IP address that is /// nonlocal or does not (yet) exist. This permits listening on a socket, /// without requiring the underlying network interface or the specified /// dynamic IP address to be up at the time that the application is trying /// to bind to it. #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] pub fn set_freebind_v4(&self, freebind: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_IP, libc::IP_FREEBIND, freebind as c_int, ) } } /// Get the value of the `IPV6_FREEBIND` option on this socket. /// /// This is an IPv6 counterpart of `IP_FREEBIND` socket option on /// Android/Linux. For more information about this option, see /// [`set_freebind_v4`]. /// /// [`set_freebind_v4`]: crate::Socket::set_freebind_v4 #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub fn freebind_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_IPV6, libc::IPV6_FREEBIND) .map(|freebind| freebind != 0) } } /// Set value for the `IPV6_FREEBIND` option on this socket. /// /// This is an IPv6 counterpart of `IP_FREEBIND` socket option on /// Android/Linux. For more information about this option, see /// [`set_freebind_v4`]. /// /// [`set_freebind_v4`]: crate::Socket::set_freebind_v4 /// /// # Examples /// /// On Linux: /// /// ``` /// use socket2::{Domain, Socket, Type}; /// use std::io::{self, Error, ErrorKind}; /// /// fn enable_freebind(socket: &Socket) -> io::Result<()> { /// match socket.domain()? { /// Domain::IPV4 => socket.set_freebind_v4(true)?, /// Domain::IPV6 => socket.set_freebind_v6(true)?, /// _ => return Err(Error::new(ErrorKind::Other, "unsupported domain")), /// }; /// Ok(()) /// } /// /// # fn main() -> io::Result<()> { /// # let socket = Socket::new(Domain::IPV6, Type::STREAM, None)?; /// # enable_freebind(&socket) /// # } /// ``` #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] pub fn set_freebind_v6(&self, freebind: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_IPV6, libc::IPV6_FREEBIND, freebind as c_int, ) } } /// Copies data between a `file` and this socket using the `sendfile(2)` /// system call. Because this copying is done within the kernel, /// `sendfile()` is more efficient than the combination of `read(2)` and /// `write(2)`, which would require transferring data to and from user /// space. /// /// Different OSs support different kinds of `file`s, see the OS /// documentation for what kind of files are supported. Generally *regular* /// files are supported by all OSs. #[doc = man_links!(unix: sendfile(2))] /// /// The `offset` is the absolute offset into the `file` to use as starting /// point. /// /// Depending on the OS this function *may* change the offset of `file`. For /// the best results reset the offset of the file before using it again. /// /// The `length` determines how many bytes to send, where a length of `None` /// means it will try to send all bytes. #[cfg(all( feature = "all", any( target_os = "aix", target_os = "android", target_os = "freebsd", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "tvos", target_os = "watchos", ) ))] pub fn sendfile( &self, file: &F, offset: usize, length: Option, ) -> io::Result where F: AsRawFd, { self._sendfile(file.as_raw_fd(), offset as _, length) } #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", ) ))] fn _sendfile( &self, file: RawFd, offset: libc::off_t, length: Option, ) -> io::Result { // On macOS `length` is value-result parameter. It determines the number // of bytes to write and returns the number of bytes written. let mut length = match length { Some(n) => n.get() as libc::off_t, // A value of `0` means send all bytes. None => 0, }; syscall!(sendfile( file, self.as_raw(), offset, &mut length, ptr::null_mut(), 0, )) .map(|_| length as usize) } #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] fn _sendfile( &self, file: RawFd, offset: libc::off_t, length: Option, ) -> io::Result { let count = match length { Some(n) => n.get() as libc::size_t, // The maximum the Linux kernel will write in a single call. None => 0x7ffff000, // 2,147,479,552 bytes. }; let mut offset = offset; syscall!(sendfile(self.as_raw(), file, &mut offset, count)).map(|n| n as usize) } #[cfg(all(feature = "all", target_os = "freebsd"))] fn _sendfile( &self, file: RawFd, offset: libc::off_t, length: Option, ) -> io::Result { let nbytes = match length { Some(n) => n.get() as libc::size_t, // A value of `0` means send all bytes. None => 0, }; let mut sbytes: libc::off_t = 0; syscall!(sendfile( file, self.as_raw(), offset, nbytes, ptr::null_mut(), &mut sbytes, 0, )) .map(|_| sbytes as usize) } #[cfg(all(feature = "all", target_os = "aix"))] fn _sendfile( &self, file: RawFd, offset: libc::off_t, length: Option, ) -> io::Result { let nbytes = match length { Some(n) => n.get() as i64, None => -1, }; let mut params = libc::sf_parms { header_data: ptr::null_mut(), header_length: 0, file_descriptor: file, file_size: 0, file_offset: offset as u64, file_bytes: nbytes, trailer_data: ptr::null_mut(), trailer_length: 0, bytes_sent: 0, }; // AIX doesn't support SF_REUSE, socket will be closed after successful transmission. syscall!(send_file( &mut self.as_raw() as *mut _, &mut params as *mut _, libc::SF_CLOSE as libc::c_uint, )) .map(|_| params.bytes_sent as usize) } /// Set the value of the `TCP_USER_TIMEOUT` option on this socket. /// /// If set, this specifies the maximum amount of time that transmitted data may remain /// unacknowledged or buffered data may remain untransmitted before TCP will forcibly close the /// corresponding connection. /// /// Setting `timeout` to `None` or a zero duration causes the system default timeouts to /// be used. If `timeout` in milliseconds is larger than `c_uint::MAX`, the timeout is clamped /// to `c_uint::MAX`. For example, when `c_uint` is a 32-bit value, this limits the timeout to /// approximately 49.71 days. #[cfg(all( feature = "all", 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| { min(to.as_millis(), libc::c_uint::MAX as u128) as libc::c_uint }); unsafe { setsockopt( self.as_raw(), libc::IPPROTO_TCP, libc::TCP_USER_TIMEOUT, timeout, ) } } /// Get the value of the `TCP_USER_TIMEOUT` option on this socket. /// /// For more information about this option, see [`set_tcp_user_timeout`]. /// /// [`set_tcp_user_timeout`]: crate::Socket::set_tcp_user_timeout #[cfg(all( feature = "all", any( target_os = "android", target_os = "fuchsia", target_os = "linux", target_os = "cygwin", ) ))] pub fn tcp_user_timeout(&self) -> io::Result> { unsafe { getsockopt::(self.as_raw(), libc::IPPROTO_TCP, libc::TCP_USER_TIMEOUT) .map(|millis| { if millis == 0 { None } else { Some(Duration::from_millis(millis as u64)) } }) } } /// Attach Berkeley Packet Filter (BPF) on this socket. /// /// BPF allows a user-space program to attach a filter onto any socket /// and allow or disallow certain types of data to come through the socket. /// /// For more information about this option, see [filter](https://www.kernel.org/doc/html/v5.12/networking/filter.html) #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] pub fn attach_filter(&self, filters: &[SockFilter]) -> io::Result<()> { let prog = libc::sock_fprog { len: filters.len() as u16, // SAFETY: this is safe due to `repr(transparent)`. filter: filters.as_ptr() as *mut _, }; unsafe { setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_ATTACH_FILTER, prog, ) } } /// Detach Berkeley Packet Filter(BPF) from this socket. /// /// For more information about this option, see [`attach_filter`] /// /// [`attach_filter`]: crate::Socket::attach_filter #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] pub fn detach_filter(&self) -> io::Result<()> { unsafe { setsockopt(self.as_raw(), libc::SOL_SOCKET, libc::SO_DETACH_FILTER, 0) } } /// Gets the value for the `SO_COOKIE` option on this socket. /// /// The socket cookie is a unique, kernel-managed identifier tied to each socket. /// Therefore, there is no corresponding `set` helper. /// /// For more information about this option, see [Linux patch](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5daab9db7b65df87da26fd8cfa695fb9546a1ddb) #[cfg(all(feature = "all", target_os = "linux"))] pub fn cookie(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), libc::SOL_SOCKET, libc::SO_COOKIE) } } /// Get the value of the `IPV6_TCLASS` option for this socket. /// /// For more information about this option, see [`set_tclass_v6`]. /// /// [`set_tclass_v6`]: crate::Socket::set_tclass_v6 #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", ) ))] pub fn tclass_v6(&self) -> io::Result { unsafe { getsockopt::(self.as_raw(), IPPROTO_IPV6, libc::IPV6_TCLASS) .map(|tclass| tclass as u32) } } /// Set the value of the `IPV6_TCLASS` option for this socket. /// /// Specifies the traffic class field that is used in every packets /// sent from this socket. #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", ) ))] pub fn set_tclass_v6(&self, tclass: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), IPPROTO_IPV6, libc::IPV6_TCLASS, tclass as c_int, ) } } /// Get the value of the `TCP_CONGESTION` option for this socket. /// /// For more information about this option, see [`set_tcp_congestion`]. /// /// [`set_tcp_congestion`]: crate::Socket::set_tcp_congestion #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] pub fn tcp_congestion(&self) -> io::Result> { let mut payload: [u8; TCP_CA_NAME_MAX] = [0; TCP_CA_NAME_MAX]; let mut len = payload.len() as libc::socklen_t; syscall!(getsockopt( self.as_raw(), IPPROTO_TCP, libc::TCP_CONGESTION, payload.as_mut_ptr().cast(), &mut len, )) .map(|_| payload[..len as usize].to_vec()) } /// Set the value of the `TCP_CONGESTION` option for this socket. /// /// Specifies the TCP congestion control algorithm to use for this socket. /// /// The value must be a valid TCP congestion control algorithm name of the /// platform. For example, Linux may supports "reno", "cubic". #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] pub fn set_tcp_congestion(&self, tcp_ca_name: &[u8]) -> io::Result<()> { syscall!(setsockopt( self.as_raw(), IPPROTO_TCP, libc::TCP_CONGESTION, tcp_ca_name.as_ptr() as *const _, tcp_ca_name.len() as libc::socklen_t, )) .map(|_| ()) } /// Set value for the `DCCP_SOCKOPT_SERVICE` option on this socket. /// /// Sets the DCCP service. The specification mandates use of service codes. /// If this socket option is not set, the socket will fall back to 0 (which /// means that no meaningful service code is present). On active sockets /// this is set before [`connect`]. On passive sockets up to 32 service /// codes can be set before calling [`bind`] /// /// [`connect`]: crate::Socket::connect /// [`bind`]: crate::Socket::bind #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_dccp_service(&self, code: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_SERVICE, code, ) } } /// Get the value of the `DCCP_SOCKOPT_SERVICE` option on this socket. /// /// For more information about this option see [`set_dccp_service`] /// /// [`set_dccp_service`]: crate::Socket::set_dccp_service #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_service(&self) -> io::Result { unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_SERVICE) } } /// Set value for the `DCCP_SOCKOPT_CCID` option on this socket. /// /// This option sets both the TX and RX CCIDs at the same time. #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_dccp_ccid(&self, ccid: u8) -> io::Result<()> { unsafe { setsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_CCID, ccid) } } /// Get the value of the `DCCP_SOCKOPT_TX_CCID` option on this socket. /// /// For more information about this option see [`set_dccp_ccid`]. /// /// [`set_dccp_ccid`]: crate::Socket::set_dccp_ccid #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_tx_ccid(&self) -> io::Result { unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_TX_CCID) } } /// Get the value of the `DCCP_SOCKOPT_RX_CCID` option on this socket. /// /// For more information about this option see [`set_dccp_ccid`]. /// /// [`set_dccp_ccid`]: crate::Socket::set_dccp_ccid #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_xx_ccid(&self) -> io::Result { unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_RX_CCID) } } /// Set value for the `DCCP_SOCKOPT_SERVER_TIMEWAIT` option on this socket. /// /// Enables a listening socket to hold timewait state when closing the /// connection. This option must be set after `accept` returns. #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_dccp_server_timewait(&self, hold_timewait: bool) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_SERVER_TIMEWAIT, hold_timewait as c_int, ) } } /// Get the value of the `DCCP_SOCKOPT_SERVER_TIMEWAIT` option on this socket. /// /// For more information see [`set_dccp_server_timewait`] /// /// [`set_dccp_server_timewait`]: crate::Socket::set_dccp_server_timewait #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_server_timewait(&self) -> io::Result { unsafe { getsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_SERVER_TIMEWAIT, ) } } /// Set value for the `DCCP_SOCKOPT_SEND_CSCOV` option on this socket. /// /// Both this option and `DCCP_SOCKOPT_RECV_CSCOV` are used for setting the /// partial checksum coverage. The default is that checksums always cover /// the entire packet and that only fully covered application data is /// accepted by the receiver. Hence, when using this feature on the sender, /// it must be enabled at the receiver too, with suitable choice of CsCov. #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_dccp_send_cscov(&self, level: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_SEND_CSCOV, level, ) } } /// Get the value of the `DCCP_SOCKOPT_SEND_CSCOV` option on this socket. /// /// For more information on this option see [`set_dccp_send_cscov`]. /// /// [`set_dccp_send_cscov`]: crate::Socket::set_dccp_send_cscov #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_send_cscov(&self) -> io::Result { unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_SEND_CSCOV) } } /// Set the value of the `DCCP_SOCKOPT_RECV_CSCOV` option on this socket. /// /// This option is only useful when combined with [`set_dccp_send_cscov`]. /// /// [`set_dccp_send_cscov`]: crate::Socket::set_dccp_send_cscov #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_dccp_recv_cscov(&self, level: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_RECV_CSCOV, level, ) } } /// Get the value of the `DCCP_SOCKOPT_RECV_CSCOV` option on this socket. /// /// For more information on this option see [`set_dccp_recv_cscov`]. /// /// [`set_dccp_recv_cscov`]: crate::Socket::set_dccp_recv_cscov #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_recv_cscov(&self) -> io::Result { unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_RECV_CSCOV) } } /// Set value for the `DCCP_SOCKOPT_QPOLICY_TXQLEN` option on this socket. /// /// This option sets the maximum length of the output queue. A zero value is /// interpreted as unbounded queue length. #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_dccp_qpolicy_txqlen(&self, length: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_QPOLICY_TXQLEN, length, ) } } /// Get the value of the `DCCP_SOCKOPT_QPOLICY_TXQLEN` on this socket. /// /// For more information on this option see [`set_dccp_qpolicy_txqlen`]. /// /// [`set_dccp_qpolicy_txqlen`]: crate::Socket::set_dccp_qpolicy_txqlen #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_qpolicy_txqlen(&self) -> io::Result { unsafe { getsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_QPOLICY_TXQLEN, ) } } /// Get the value of the `DCCP_SOCKOPT_AVAILABLE_CCIDS` option on this socket. /// /// Returns the list of CCIDs supported by the endpoint. /// /// The parameter `N` is used to get the maximum number of supported /// endpoints. The [documentation] recommends a minimum of four at the time /// of writing. /// /// [documentation]: https://www.kernel.org/doc/html/latest/networking/dccp.html #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_available_ccids(&self) -> io::Result> { let mut endpoints = [0; N]; let mut length = endpoints.len() as libc::socklen_t; syscall!(getsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_AVAILABLE_CCIDS, endpoints.as_mut_ptr().cast(), &mut length, ))?; Ok(CcidEndpoints { endpoints, length }) } /// Get the value of the `DCCP_SOCKOPT_GET_CUR_MPS` option on this socket. /// /// This option retrieves the current maximum packet size (application /// payload size) in bytes. #[cfg(all(feature = "all", target_os = "linux"))] pub fn dccp_cur_mps(&self) -> io::Result { unsafe { getsockopt( self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_GET_CUR_MPS, ) } } /// Get the value for the `SO_BUSY_POLL` option on this socket. /// /// On Linux this function requires the `CAP_NET_ADMIN` capability. #[cfg(all(feature = "all", target_os = "linux"))] pub fn busy_poll(&self) -> io::Result { unsafe { getsockopt(self.as_raw(), libc::SOL_SOCKET, libc::SO_BUSY_POLL) } } /// Set the value for the `SO_BUSY_POLL` option on this socket. /// /// On Linux this function requires the `CAP_NET_ADMIN` capability. #[cfg(all(feature = "all", target_os = "linux"))] pub fn set_busy_poll(&self, busy_poll: u32) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_SOCKET, libc::SO_BUSY_POLL, busy_poll as c_int, ) } } } /// Berkeley Packet Filter (BPF). /// /// See [`Socket::attach_filter`]. /// /// [`Socket::attach_filter`]: crate::Socket::attach_filter #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] #[repr(transparent)] pub struct SockFilter { // This field is only read indirectly by transmutes / pointer casts, so // rustc emits a spurious warning saying that the field is never read. #[allow(dead_code)] filter: libc::sock_filter, } #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] impl SockFilter { /// Create new `SockFilter`. pub const fn new(code: u16, jt: u8, jf: u8, k: u32) -> SockFilter { SockFilter { filter: libc::sock_filter { code, jt, jf, k }, } } } #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] impl std::fmt::Debug for SockFilter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SockFilter").finish_non_exhaustive() } } /// See [`Socket::dccp_available_ccids`]. /// /// [`Socket::dccp_available_ccids`]: crate::Socket::dccp_available_ccids #[cfg(all(feature = "all", target_os = "linux"))] #[derive(Debug)] pub struct CcidEndpoints { endpoints: [u8; N], length: u32, } #[cfg(all(feature = "all", target_os = "linux"))] impl std::ops::Deref for CcidEndpoints { type Target = [u8]; fn deref(&self) -> &[u8] { &self.endpoints[0..self.length as usize] } } impl AsFd for crate::Socket { fn as_fd(&self) -> BorrowedFd<'_> { // SAFETY: lifetime is bound by self. unsafe { BorrowedFd::borrow_raw(self.as_raw()) } } } impl AsRawFd for crate::Socket { fn as_raw_fd(&self) -> RawFd { self.as_raw() } } impl From for OwnedFd { fn from(sock: crate::Socket) -> OwnedFd { // SAFETY: sock.into_raw() always returns a valid fd. unsafe { OwnedFd::from_raw_fd(sock.into_raw()) } } } impl IntoRawFd for crate::Socket { fn into_raw_fd(self) -> c_int { self.into_raw() } } impl From for crate::Socket { fn from(fd: OwnedFd) -> crate::Socket { // SAFETY: `OwnedFd` ensures the fd is valid. unsafe { crate::Socket::from_raw_fd(fd.into_raw_fd()) } } } impl FromRawFd for crate::Socket { unsafe fn from_raw_fd(fd: c_int) -> crate::Socket { crate::Socket::from_raw(fd) } } #[cfg(all(feature = "all", unix))] from!(UnixStream, crate::Socket); #[cfg(all(feature = "all", unix))] from!(UnixListener, crate::Socket); #[cfg(all(feature = "all", unix))] from!(UnixDatagram, crate::Socket); #[cfg(all(feature = "all", unix))] from!(crate::Socket, UnixStream); #[cfg(all(feature = "all", unix))] from!(crate::Socket, UnixListener); #[cfg(all(feature = "all", unix))] from!(crate::Socket, UnixDatagram); #[test] fn in_addr_convertion() { let ip = Ipv4Addr::new(127, 0, 0, 1); let raw = to_in_addr(&ip); // NOTE: `in_addr` is packed on NetBSD and it's unsafe to borrow. let a = raw.s_addr; assert_eq!(a, u32::from_ne_bytes([127, 0, 0, 1])); assert_eq!(from_in_addr(raw), ip); let ip = Ipv4Addr::new(127, 34, 4, 12); let raw = to_in_addr(&ip); let a = raw.s_addr; assert_eq!(a, u32::from_ne_bytes([127, 34, 4, 12])); assert_eq!(from_in_addr(raw), ip); } #[test] fn in6_addr_convertion() { let ip = Ipv6Addr::new(0x2000, 1, 2, 3, 4, 5, 6, 7); let raw = to_in6_addr(&ip); let want = [32, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7]; assert_eq!(raw.s6_addr, want); assert_eq!(from_in6_addr(raw), ip); } ================================================ FILE: src/sys/windows.rs ================================================ // Copyright 2015 The Rust Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::cmp::min; use std::io::{self, IoSlice}; use std::marker::PhantomData; use std::mem::{self, size_of, MaybeUninit}; use std::net::{self, Ipv4Addr, Ipv6Addr, Shutdown}; use std::os::windows::io::{ AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket as StdRawSocket, }; use std::path::Path; use std::sync::Once; use std::time::{Duration, Instant}; use std::{process, ptr, slice}; use windows_sys::Win32::Foundation::{SetHandleInformation, HANDLE, HANDLE_FLAG_INHERIT}; use windows_sys::Win32::Networking::WinSock::{ self, tcp_keepalive, FIONBIO, IN6_ADDR, IN6_ADDR_0, INVALID_SOCKET, IN_ADDR, IN_ADDR_0, POLLERR, POLLHUP, POLLRDNORM, POLLWRNORM, SD_BOTH, SD_RECEIVE, SD_SEND, SIO_KEEPALIVE_VALS, SOCKET_ERROR, WSABUF, WSAEMSGSIZE, WSAESHUTDOWN, WSAPOLLFD, WSAPROTOCOL_INFOW, WSA_FLAG_NO_HANDLE_INHERIT, WSA_FLAG_OVERLAPPED, WSA_FLAG_REGISTERED_IO, }; #[cfg(feature = "all")] use windows_sys::Win32::Networking::WinSock::{ SIO_TCP_SET_ACK_FREQUENCY, TCP_ACK_FREQUENCY_PARAMETERS, }; #[cfg(feature = "all")] use windows_sys::Win32::Networking::WinSock::{ IP6T_SO_ORIGINAL_DST, SOL_IP, SO_ORIGINAL_DST, SO_PROTOCOL_INFOW, }; use windows_sys::Win32::System::Threading::INFINITE; use crate::{MsgHdr, RecvFlags, SockAddr, SockAddrStorage, TcpKeepalive, Type}; #[allow(non_camel_case_types)] pub(crate) type c_int = std::ffi::c_int; /// Fake MSG_TRUNC flag for the [`RecvFlags`] struct. /// /// The flag is enabled when a `WSARecv[From]` call returns `WSAEMSGSIZE`. The /// value of the flag is defined by us. pub(crate) const MSG_TRUNC: c_int = 0x01; // Used in `Domain`. pub(crate) const AF_INET: c_int = windows_sys::Win32::Networking::WinSock::AF_INET as c_int; pub(crate) const AF_INET6: c_int = windows_sys::Win32::Networking::WinSock::AF_INET6 as c_int; pub(crate) const AF_UNIX: c_int = windows_sys::Win32::Networking::WinSock::AF_UNIX as c_int; pub(crate) const AF_UNSPEC: c_int = windows_sys::Win32::Networking::WinSock::AF_UNSPEC as c_int; // Used in `Type`. pub(crate) const SOCK_STREAM: c_int = windows_sys::Win32::Networking::WinSock::SOCK_STREAM as c_int; pub(crate) const SOCK_DGRAM: c_int = windows_sys::Win32::Networking::WinSock::SOCK_DGRAM as c_int; pub(crate) const SOCK_RAW: c_int = windows_sys::Win32::Networking::WinSock::SOCK_RAW as c_int; const SOCK_RDM: c_int = windows_sys::Win32::Networking::WinSock::SOCK_RDM as c_int; pub(crate) const SOCK_SEQPACKET: c_int = windows_sys::Win32::Networking::WinSock::SOCK_SEQPACKET as c_int; // Used in `Protocol`. pub(crate) use windows_sys::Win32::Networking::WinSock::{ IPPROTO_ICMP, IPPROTO_ICMPV6, IPPROTO_TCP, IPPROTO_UDP, }; #[cfg(feature = "all")] pub(crate) use windows_sys::Win32::Networking::WinSock::TCP_KEEPCNT; // Used in `SockAddr`. pub(crate) use windows_sys::Win32::Networking::WinSock::{ SOCKADDR as sockaddr, SOCKADDR_IN as sockaddr_in, SOCKADDR_IN6 as sockaddr_in6, SOCKADDR_STORAGE as sockaddr_storage, }; #[allow(non_camel_case_types)] pub(crate) type sa_family_t = windows_sys::Win32::Networking::WinSock::ADDRESS_FAMILY; #[allow(non_camel_case_types)] pub(crate) type socklen_t = windows_sys::Win32::Networking::WinSock::socklen_t; // Used in `Socket`. #[cfg(feature = "all")] pub(crate) use windows_sys::Win32::Networking::WinSock::IP_HDRINCL; pub(crate) use windows_sys::Win32::Networking::WinSock::{ IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP, IPV6_MREQ as Ipv6Mreq, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_RECVTCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, IP_MREQ as IpMreq, IP_MREQ_SOURCE as IpMreqSource, IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_RECVTOS, IP_TOS, IP_TTL, LINGER as linger, MSG_OOB, MSG_PEEK, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_RCVBUF, SO_RCVTIMEO, SO_REUSEADDR, SO_SNDBUF, SO_SNDTIMEO, SO_TYPE, TCP_NODELAY, }; pub(crate) const IPPROTO_IP: c_int = windows_sys::Win32::Networking::WinSock::IPPROTO_IP as c_int; pub(crate) const SOL_SOCKET: c_int = windows_sys::Win32::Networking::WinSock::SOL_SOCKET as c_int; /// Type used in set/getsockopt to retrieve the `TCP_NODELAY` option. /// /// NOTE: /// documents that options such as `TCP_NODELAY` and `SO_KEEPALIVE` expect a /// `BOOL` (alias for `c_int`, 4 bytes), however in practice this turns out to /// be false (or misleading) as a `bool` (1 byte) is returned by `getsockopt`. pub(crate) type Bool = bool; /// Maximum size of a buffer passed to system call like `recv` and `send`. const MAX_BUF_LEN: usize = c_int::MAX as usize; /// Helper macro to execute a system call that returns an `io::Result`. macro_rules! syscall { ($fn: ident ( $($arg: expr),* $(,)* ), $err_test: path, $err_value: expr) => {{ #[allow(unused_unsafe)] let res = unsafe { windows_sys::Win32::Networking::WinSock::$fn($($arg, )*) }; if $err_test(&res, &$err_value) { Err(io::Error::last_os_error()) } else { Ok(res) } }}; } impl_debug!( crate::Domain, self::AF_INET, self::AF_INET6, self::AF_UNIX, self::AF_UNSPEC, ); /// Windows only API. impl Type { /// Our custom flag to set `WSA_FLAG_NO_HANDLE_INHERIT` on socket creation. /// Trying to mimic `Type::cloexec` on windows. const NO_INHERIT: c_int = 1 << ((size_of::() * 8) - 1); // Last bit. /// Our custom flag to set `WSA_FLAG_REGISTERED_IO` on socket creation. const REGISTERED_IO: c_int = 1 << ((size_of::() * 8) - 2); // Second last bit. /// Set `WSA_FLAG_NO_HANDLE_INHERIT` on the socket. #[cfg(feature = "all")] pub const fn no_inherit(self) -> Type { self._no_inherit() } /// Set `WSA_FLAG_REGISTERED_IO` on the socket. #[cfg(feature = "all")] pub const fn registered_io(self) -> Type { Type(self.0 | Type::REGISTERED_IO) } pub(crate) const fn _no_inherit(self) -> Type { Type(self.0 | Type::NO_INHERIT) } } impl_debug!( crate::Type, self::SOCK_STREAM, self::SOCK_DGRAM, self::SOCK_RAW, self::SOCK_RDM, self::SOCK_SEQPACKET, ); impl_debug!( crate::Protocol, WinSock::IPPROTO_ICMP, WinSock::IPPROTO_ICMPV6, WinSock::IPPROTO_TCP, WinSock::IPPROTO_UDP, ); impl std::fmt::Debug for RecvFlags { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RecvFlags") .field("is_truncated", &self.is_truncated()) .finish() } } #[repr(transparent)] pub struct MaybeUninitSlice<'a> { vec: WSABUF, _lifetime: PhantomData<&'a mut [MaybeUninit]>, } unsafe impl<'a> Send for MaybeUninitSlice<'a> {} unsafe impl<'a> Sync for MaybeUninitSlice<'a> {} impl<'a> MaybeUninitSlice<'a> { pub fn new(buf: &'a mut [MaybeUninit]) -> MaybeUninitSlice<'a> { assert!(buf.len() <= u32::MAX as usize); MaybeUninitSlice { vec: WSABUF { len: buf.len() as u32, buf: buf.as_mut_ptr().cast(), }, _lifetime: PhantomData, } } pub fn as_slice(&self) -> &[MaybeUninit] { unsafe { slice::from_raw_parts(self.vec.buf.cast(), self.vec.len as usize) } } pub fn as_mut_slice(&mut self) -> &mut [MaybeUninit] { unsafe { slice::from_raw_parts_mut(self.vec.buf.cast(), self.vec.len as usize) } } } // Used in `MsgHdr`. pub(crate) use windows_sys::Win32::Networking::WinSock::WSAMSG as msghdr; pub(crate) fn set_msghdr_name(msg: &mut msghdr, name: &SockAddr) { msg.name = name.as_ptr() as *mut _; msg.namelen = name.len(); } pub(crate) fn set_msghdr_iov(msg: &mut msghdr, ptr: *mut WSABUF, len: usize) { msg.lpBuffers = ptr; msg.dwBufferCount = min(len, u32::MAX as usize) as u32; } pub(crate) fn set_msghdr_control(msg: &mut msghdr, ptr: *mut u8, len: usize) { msg.Control.buf = ptr; msg.Control.len = len as u32; } pub(crate) fn set_msghdr_flags(msg: &mut msghdr, flags: c_int) { msg.dwFlags = flags as u32; } pub(crate) fn msghdr_flags(msg: &msghdr) -> RecvFlags { RecvFlags(msg.dwFlags as c_int) } pub(crate) fn msghdr_control_len(msg: &msghdr) -> usize { msg.Control.len as _ } fn init() { static INIT: Once = Once::new(); INIT.call_once(|| { // Initialize winsock through the standard library by just creating a // dummy socket. Whether this is successful or not we drop the result as // libstd will be sure to have initialized winsock. let _ = net::UdpSocket::bind("127.0.0.1:34254"); }); } pub(crate) type Socket = std::os::windows::io::OwnedSocket; pub(crate) type RawSocket = windows_sys::Win32::Networking::WinSock::SOCKET; pub(crate) unsafe fn socket_from_raw(socket: RawSocket) -> Socket { Socket::from_raw_socket(socket as StdRawSocket) } pub(crate) fn socket_as_raw(socket: &Socket) -> RawSocket { socket.as_raw_socket() as RawSocket } pub(crate) fn socket_into_raw(socket: Socket) -> RawSocket { socket.into_raw_socket() as RawSocket } pub(crate) fn socket(family: c_int, mut ty: c_int, protocol: c_int) -> io::Result { init(); // Check if we set our custom flags. let flags = if ty & Type::NO_INHERIT != 0 { ty = ty & !Type::NO_INHERIT; WSA_FLAG_NO_HANDLE_INHERIT } else { 0 }; let flags = if ty & Type::REGISTERED_IO != 0 { ty = ty & !Type::REGISTERED_IO; flags | WSA_FLAG_REGISTERED_IO } else { flags }; syscall!( WSASocketW( family, ty, protocol, ptr::null_mut(), 0, WSA_FLAG_OVERLAPPED | flags, ), PartialEq::eq, INVALID_SOCKET ) } pub(crate) fn bind(socket: RawSocket, addr: &SockAddr) -> io::Result<()> { syscall!( bind(socket, addr.as_ptr().cast::(), addr.len()), PartialEq::ne, 0 ) .map(|_| ()) } pub(crate) fn connect(socket: RawSocket, addr: &SockAddr) -> io::Result<()> { syscall!( connect(socket, addr.as_ptr().cast::(), addr.len()), PartialEq::ne, 0 ) .map(|_| ()) } pub(crate) fn poll_connect(socket: &crate::Socket, timeout: Duration) -> io::Result<()> { let start = Instant::now(); let mut fd_array = WSAPOLLFD { fd: socket.as_raw(), events: (POLLRDNORM | POLLWRNORM) as i16, revents: 0, }; loop { let elapsed = start.elapsed(); if elapsed >= timeout { return Err(io::ErrorKind::TimedOut.into()); } let timeout = (timeout - elapsed).as_millis(); let timeout = clamp(timeout, 1, c_int::MAX as u128) as c_int; match syscall!( WSAPoll(&mut fd_array, 1, timeout), PartialEq::eq, SOCKET_ERROR ) { Ok(0) => return Err(io::ErrorKind::TimedOut.into()), Ok(_) => { // Error or hang up indicates an error (or failure to connect). if (fd_array.revents & POLLERR as i16) != 0 || (fd_array.revents & POLLHUP as i16) != 0 { match socket.take_error() { Ok(Some(err)) => return Err(err), Ok(None) => { return Err(io::Error::new( io::ErrorKind::Other, "no error set after POLLHUP", )) } Err(err) => return Err(err), } } return Ok(()); } // Got interrupted, try again. Err(ref err) if err.kind() == io::ErrorKind::Interrupted => continue, Err(err) => return Err(err), } } } // TODO: use clamp from std lib, stable since 1.50. fn clamp(value: T, min: T, max: T) -> T where T: Ord, { if value <= min { min } else if value >= max { max } else { value } } pub(crate) fn listen(socket: RawSocket, backlog: c_int) -> io::Result<()> { syscall!(listen(socket, backlog), PartialEq::ne, 0).map(|_| ()) } pub(crate) fn accept(socket: RawSocket) -> io::Result<(RawSocket, SockAddr)> { // Safety: `accept` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, len| { syscall!( accept(socket, storage.cast(), len), PartialEq::eq, INVALID_SOCKET ) }) } } pub(crate) fn getsockname(socket: RawSocket) -> io::Result { // Safety: `getsockname` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, len| { syscall!( getsockname(socket, storage.cast(), len), PartialEq::eq, SOCKET_ERROR ) }) } .map(|(_, addr)| addr) } pub(crate) fn getpeername(socket: RawSocket) -> io::Result { // Safety: `getpeername` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, len| { syscall!( getpeername(socket, storage.cast(), len), PartialEq::eq, SOCKET_ERROR ) }) } .map(|(_, addr)| addr) } pub(crate) fn try_clone(socket: RawSocket) -> io::Result { let mut info: MaybeUninit = MaybeUninit::uninit(); syscall!( // NOTE: `process.id` is the same as `GetCurrentProcessId`. WSADuplicateSocketW(socket, process::id(), info.as_mut_ptr()), PartialEq::eq, SOCKET_ERROR )?; // Safety: `WSADuplicateSocketW` intialised `info` for us. let mut info = unsafe { info.assume_init() }; syscall!( WSASocketW( info.iAddressFamily, info.iSocketType, info.iProtocol, &mut info, 0, WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT, ), PartialEq::eq, INVALID_SOCKET ) } pub(crate) fn set_nonblocking(socket: RawSocket, nonblocking: bool) -> io::Result<()> { let mut nonblocking = if nonblocking { 1 } else { 0 }; ioctlsocket(socket, FIONBIO, &mut nonblocking) } pub(crate) fn shutdown(socket: RawSocket, how: Shutdown) -> io::Result<()> { let how = match how { Shutdown::Write => SD_SEND, Shutdown::Read => SD_RECEIVE, Shutdown::Both => SD_BOTH, } as i32; syscall!(shutdown(socket, how), PartialEq::eq, SOCKET_ERROR).map(|_| ()) } pub(crate) fn recv( socket: RawSocket, buf: &mut [MaybeUninit], flags: c_int, ) -> io::Result { let res = syscall!( recv( socket, buf.as_mut_ptr().cast(), min(buf.len(), MAX_BUF_LEN) as c_int, flags, ), PartialEq::eq, SOCKET_ERROR ); match res { Ok(n) => Ok(n as usize), Err(ref err) if err.raw_os_error() == Some(WSAESHUTDOWN as i32) => Ok(0), Err(err) => Err(err), } } pub(crate) fn recv_vectored( socket: RawSocket, bufs: &mut [crate::MaybeUninitSlice<'_>], flags: c_int, ) -> io::Result<(usize, RecvFlags)> { let mut nread = 0; let mut flags = flags as u32; let res = syscall!( WSARecv( socket, bufs.as_mut_ptr().cast(), min(bufs.len(), u32::MAX as usize) as u32, &mut nread, &mut flags, ptr::null_mut(), None, ), PartialEq::eq, SOCKET_ERROR ); match res { Ok(_) => Ok((nread as usize, RecvFlags(0))), Err(ref err) if err.raw_os_error() == Some(WSAESHUTDOWN as i32) => Ok((0, RecvFlags(0))), Err(ref err) if err.raw_os_error() == Some(WSAEMSGSIZE as i32) => { Ok((nread as usize, RecvFlags(MSG_TRUNC))) } Err(err) => Err(err), } } pub(crate) fn recv_from( socket: RawSocket, buf: &mut [MaybeUninit], flags: c_int, ) -> io::Result<(usize, SockAddr)> { // Safety: `recvfrom` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, addrlen| { let res = syscall!( recvfrom( socket, buf.as_mut_ptr().cast(), min(buf.len(), MAX_BUF_LEN) as c_int, flags, storage.cast(), addrlen, ), PartialEq::eq, SOCKET_ERROR ); match res { Ok(n) => Ok(n as usize), Err(ref err) if err.raw_os_error() == Some(WSAESHUTDOWN as i32) => Ok(0), Err(err) => Err(err), } }) } } pub(crate) fn peek_sender(socket: RawSocket) -> io::Result { // Safety: `recvfrom` initialises the `SockAddr` for us. let ((), sender) = unsafe { SockAddr::try_init(|storage, addrlen| { let res = syscall!( recvfrom( socket, // Windows *appears* not to care if you pass a null pointer. ptr::null_mut(), 0, MSG_PEEK, storage.cast(), addrlen, ), PartialEq::eq, SOCKET_ERROR ); match res { Ok(_n) => Ok(()), Err(e) => match e.raw_os_error() { Some(code) if code == (WSAESHUTDOWN as i32) || code == (WSAEMSGSIZE as i32) => { Ok(()) } _ => Err(e), }, } }) }?; Ok(sender) } pub(crate) fn recv_from_vectored( socket: RawSocket, bufs: &mut [crate::MaybeUninitSlice<'_>], flags: c_int, ) -> io::Result<(usize, RecvFlags, SockAddr)> { // Safety: `recvfrom` initialises the `SockAddr` for us. unsafe { SockAddr::try_init(|storage, addrlen| { let mut nread = 0; let mut flags = flags as u32; let res = syscall!( WSARecvFrom( socket, bufs.as_mut_ptr().cast(), min(bufs.len(), u32::MAX as usize) as u32, &mut nread, &mut flags, storage.cast(), addrlen, ptr::null_mut(), None, ), PartialEq::eq, SOCKET_ERROR ); match res { Ok(_) => Ok((nread as usize, RecvFlags(0))), Err(ref err) if err.raw_os_error() == Some(WSAESHUTDOWN as i32) => { Ok((nread as usize, RecvFlags(0))) } Err(ref err) if err.raw_os_error() == Some(WSAEMSGSIZE as i32) => { Ok((nread as usize, RecvFlags(MSG_TRUNC))) } Err(err) => Err(err), } }) } .map(|((n, recv_flags), addr)| (n, recv_flags, addr)) } pub(crate) fn send(socket: RawSocket, buf: &[u8], flags: c_int) -> io::Result { syscall!( send( socket, buf.as_ptr().cast(), min(buf.len(), MAX_BUF_LEN) as c_int, flags, ), PartialEq::eq, SOCKET_ERROR ) .map(|n| n as usize) } pub(crate) fn send_vectored( socket: RawSocket, bufs: &[IoSlice<'_>], flags: c_int, ) -> io::Result { let mut nsent = 0; syscall!( WSASend( socket, // FIXME: From the `WSASend` docs [1]: // > For a Winsock application, once the WSASend function is called, // > the system owns these buffers and the application may not // > access them. // // So what we're doing is actually UB as `bufs` needs to be `&mut // [IoSlice<'_>]`. // // Tracking issue: https://github.com/rust-lang/socket2-rs/issues/129. // // NOTE: `send_to_vectored` has the same problem. // // [1] https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasend bufs.as_ptr() as *mut _, min(bufs.len(), u32::MAX as usize) as u32, &mut nsent, flags as u32, std::ptr::null_mut(), None, ), PartialEq::eq, SOCKET_ERROR ) .map(|_| nsent as usize) } pub(crate) fn send_to( socket: RawSocket, buf: &[u8], addr: &SockAddr, flags: c_int, ) -> io::Result { syscall!( sendto( socket, buf.as_ptr().cast(), min(buf.len(), MAX_BUF_LEN) as c_int, flags, addr.as_ptr().cast::(), addr.len(), ), PartialEq::eq, SOCKET_ERROR ) .map(|n| n as usize) } pub(crate) fn send_to_vectored( socket: RawSocket, bufs: &[IoSlice<'_>], addr: &SockAddr, flags: c_int, ) -> io::Result { let mut nsent = 0; syscall!( WSASendTo( socket, // FIXME: Same problem as in `send_vectored`. bufs.as_ptr() as *mut _, bufs.len().min(u32::MAX as usize) as u32, &mut nsent, flags as u32, addr.as_ptr().cast::(), addr.len(), ptr::null_mut(), None, ), PartialEq::eq, SOCKET_ERROR ) .map(|_| nsent as usize) } pub(crate) fn sendmsg( socket: RawSocket, msg: &MsgHdr<'_, '_, '_>, flags: c_int, ) -> io::Result { let mut nsent = 0; syscall!( WSASendMsg( socket, &msg.inner, flags as u32, &mut nsent, ptr::null_mut(), None, ), PartialEq::eq, SOCKET_ERROR ) .map(|_| nsent as usize) } /// Wrapper around `getsockopt` to deal with platform specific timeouts. pub(crate) fn timeout_opt(fd: RawSocket, lvl: c_int, name: i32) -> io::Result> { unsafe { getsockopt(fd, lvl, name).map(from_ms) } } fn from_ms(duration: u32) -> Option { if duration == 0 { None } else { let secs = duration / 1000; let nsec = (duration % 1000) * 1000000; Some(Duration::new(secs as u64, nsec as u32)) } } /// Wrapper around `setsockopt` to deal with platform specific timeouts. pub(crate) fn set_timeout_opt( socket: RawSocket, level: c_int, optname: i32, duration: Option, ) -> io::Result<()> { let duration = into_ms(duration); unsafe { setsockopt(socket, level, optname, duration) } } fn into_ms(duration: Option) -> u32 { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the // timeouts in windows APIs are typically u32 milliseconds. To translate, we // have two pieces to take care of: // // * Nanosecond precision is rounded up // * Greater than u32::MAX milliseconds (50 days) is rounded up to // INFINITE (never time out). duration.map_or(0, |duration| { min(duration.as_millis(), INFINITE as u128) as u32 }) } pub(crate) fn set_tcp_keepalive(socket: RawSocket, keepalive: &TcpKeepalive) -> io::Result<()> { let mut tcp_keepalive = tcp_keepalive { onoff: 1, keepalivetime: into_ms(keepalive.time), keepaliveinterval: into_ms(keepalive.interval), }; let mut out = 0; syscall!( WSAIoctl( socket, SIO_KEEPALIVE_VALS, &mut tcp_keepalive as *mut _ as *mut _, size_of::() as _, ptr::null_mut(), 0, &mut out, ptr::null_mut(), None, ), PartialEq::eq, SOCKET_ERROR )?; if let Some(retries) = keepalive.retries { unsafe { setsockopt( socket, WinSock::IPPROTO_TCP, WinSock::TCP_KEEPCNT, retries as c_int, )? } } Ok(()) } #[cfg(feature = "all")] pub(crate) fn set_tcp_ack_frequency(socket: RawSocket, frequency: u8) -> io::Result<()> { let mut freq_params = TCP_ACK_FREQUENCY_PARAMETERS { TcpDelayedAckFrequency: frequency, }; let mut out = 0; syscall!( WSAIoctl( socket, SIO_TCP_SET_ACK_FREQUENCY, &mut freq_params as *mut _ as *mut _, size_of::() as _, ptr::null_mut(), 0, &mut out, ptr::null_mut(), None, ), PartialEq::eq, SOCKET_ERROR ) .map(|_| ()) } /// Caller must ensure `T` is the correct type for `level` and `optname`. // NOTE: `optname` is actually `i32`, but all constants are `u32`. pub(crate) unsafe fn getsockopt(socket: RawSocket, level: c_int, optname: i32) -> io::Result { let mut optval: MaybeUninit = MaybeUninit::uninit(); let mut optlen = mem::size_of::() as c_int; syscall!( getsockopt( socket, level as i32, optname, optval.as_mut_ptr().cast(), &mut optlen, ), PartialEq::eq, SOCKET_ERROR ) .map(|_| { debug_assert_eq!(optlen as usize, mem::size_of::()); // Safety: `getsockopt` initialised `optval` for us. optval.assume_init() }) } /// Caller must ensure `T` is the correct type for `level` and `optname`. // NOTE: `optname` is actually `i32`, but all constants are `u32`. pub(crate) unsafe fn setsockopt( socket: RawSocket, level: c_int, optname: i32, optval: T, ) -> io::Result<()> { syscall!( setsockopt( socket, level as i32, optname, (&optval as *const T).cast(), mem::size_of::() as c_int, ), PartialEq::eq, SOCKET_ERROR ) .map(|_| ()) } fn ioctlsocket(socket: RawSocket, cmd: i32, payload: &mut u32) -> io::Result<()> { syscall!( ioctlsocket(socket, cmd, payload), PartialEq::eq, SOCKET_ERROR ) .map(|_| ()) } pub(crate) fn to_in_addr(addr: &Ipv4Addr) -> IN_ADDR { IN_ADDR { S_un: IN_ADDR_0 { // `S_un` is stored as BE on all machines, and the array is in BE // order. So the native endian conversion method is used so that // it's never swapped. S_addr: u32::from_ne_bytes(addr.octets()), }, } } pub(crate) fn from_in_addr(in_addr: IN_ADDR) -> Ipv4Addr { Ipv4Addr::from(unsafe { in_addr.S_un.S_addr }.to_ne_bytes()) } pub(crate) fn to_in6_addr(addr: &Ipv6Addr) -> IN6_ADDR { IN6_ADDR { u: IN6_ADDR_0 { Byte: addr.octets(), }, } } pub(crate) fn from_in6_addr(addr: IN6_ADDR) -> Ipv6Addr { Ipv6Addr::from(unsafe { addr.u.Byte }) } pub(crate) fn to_mreqn( multiaddr: &Ipv4Addr, interface: &crate::socket::InterfaceIndexOrAddress, ) -> IpMreq { IpMreq { imr_multiaddr: to_in_addr(multiaddr), // Per https://docs.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-ip_mreq#members: // // imr_interface // // The local IPv4 address of the interface or the interface index on // which the multicast group should be joined or dropped. This value is // in network byte order. If this member specifies an IPv4 address of // 0.0.0.0, the default IPv4 multicast interface is used. // // To use an interface index of 1 would be the same as an IP address of // 0.0.0.1. imr_interface: match interface { crate::socket::InterfaceIndexOrAddress::Index(interface) => { to_in_addr(&(*interface).into()) } crate::socket::InterfaceIndexOrAddress::Address(interface) => to_in_addr(interface), }, } } #[cfg(feature = "all")] pub(crate) fn original_dst_v4(socket: RawSocket) -> io::Result { unsafe { SockAddr::try_init(|storage, len| { syscall!( getsockopt( socket, SOL_IP as i32, SO_ORIGINAL_DST as i32, storage.cast(), len, ), PartialEq::eq, SOCKET_ERROR ) }) } .map(|(_, addr)| addr) } #[cfg(feature = "all")] pub(crate) fn original_dst_v6(socket: RawSocket) -> io::Result { unsafe { SockAddr::try_init(|storage, len| { syscall!( getsockopt( socket, SOL_IP as i32, IP6T_SO_ORIGINAL_DST as i32, storage.cast(), len, ), PartialEq::eq, SOCKET_ERROR ) }) } .map(|(_, addr)| addr) } #[allow(unsafe_op_in_unsafe_fn)] pub(crate) fn unix_sockaddr(path: &Path) -> io::Result { let mut storage = SockAddrStorage::zeroed(); let len = { let storage = unsafe { storage.view_as::() }; // Windows expects a UTF-8 path here even though Windows paths are // usually UCS-2 encoded. If Rust exposed OsStr's Wtf8 encoded // buffer, this could be used directly, relying on Windows to // validate the path, but Rust hides this implementation detail. // // See . let bytes = path .to_str() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "path must be valid UTF-8"))? .as_bytes(); // Windows appears to allow non-null-terminated paths, but this is // not documented, so do not rely on it yet. // // See . if bytes.len() >= storage.sun_path.len() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "path must be shorter than SUN_LEN", )); } storage.sun_family = crate::sys::AF_UNIX as sa_family_t; // SAFETY: casting `[u8]` to `[i8]` is safe. let b = unsafe { &*(bytes as *const [u8] as *const [i8]) }; // `storage` was initialized to zero above, so the path is // already null terminated. storage.sun_path[..bytes.len()].copy_from_slice(b); let base = storage as *const _ as usize; let path = &storage.sun_path as *const _ as usize; let sun_path_offset = path - base; sun_path_offset + bytes.len() + 1 }; Ok(unsafe { SockAddr::new(storage, len as socklen_t) }) } /// Windows only API. impl crate::Socket { /// Sets `HANDLE_FLAG_INHERIT` 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 // `windows_sys::Win32::Networking::WinSock::` path. let res = unsafe { SetHandleInformation( self.as_raw() as HANDLE, HANDLE_FLAG_INHERIT, !no_inherit as _, ) }; if res == 0 { // Zero means error. Err(io::Error::last_os_error()) } else { Ok(()) } } /// Returns the [`Protocol`] of this socket by checking the `SO_PROTOCOL_INFOW` /// option on this socket. /// /// [`Protocol`]: crate::Protocol #[cfg(feature = "all")] pub fn protocol(&self) -> io::Result> { let info = unsafe { getsockopt::(self.as_raw(), SOL_SOCKET, SO_PROTOCOL_INFOW)? }; match info.iProtocol { 0 => Ok(None), p => Ok(Some(crate::Protocol::from(p))), } } } impl AsSocket for crate::Socket { fn as_socket(&self) -> BorrowedSocket<'_> { // SAFETY: lifetime is bound by self. unsafe { BorrowedSocket::borrow_raw(self.as_raw() as StdRawSocket) } } } impl AsRawSocket for crate::Socket { fn as_raw_socket(&self) -> StdRawSocket { self.as_raw() as StdRawSocket } } impl From for OwnedSocket { fn from(sock: crate::Socket) -> OwnedSocket { // SAFETY: sock.into_raw() always returns a valid fd. unsafe { OwnedSocket::from_raw_socket(sock.into_raw() as StdRawSocket) } } } impl IntoRawSocket for crate::Socket { fn into_raw_socket(self) -> StdRawSocket { self.into_raw() as StdRawSocket } } impl From for crate::Socket { fn from(fd: OwnedSocket) -> crate::Socket { // SAFETY: `OwnedFd` ensures the fd is valid. unsafe { crate::Socket::from_raw_socket(fd.into_raw_socket()) } } } impl FromRawSocket for crate::Socket { unsafe fn from_raw_socket(socket: StdRawSocket) -> crate::Socket { crate::Socket::from_raw(socket as RawSocket) } } #[test] fn in_addr_convertion() { let ip = Ipv4Addr::new(127, 0, 0, 1); let raw = to_in_addr(&ip); assert_eq!(unsafe { raw.S_un.S_addr }, 127 << 0 | 1 << 24); assert_eq!(from_in_addr(raw), ip); let ip = Ipv4Addr::new(127, 34, 4, 12); let raw = to_in_addr(&ip); assert_eq!( unsafe { raw.S_un.S_addr }, 127 << 0 | 34 << 8 | 4 << 16 | 12 << 24 ); assert_eq!(from_in_addr(raw), ip); } #[test] fn in6_addr_convertion() { let ip = Ipv6Addr::new(0x2000, 1, 2, 3, 4, 5, 6, 7); let raw = to_in6_addr(&ip); let want = [ 0x2000u16.to_be(), 1u16.to_be(), 2u16.to_be(), 3u16.to_be(), 4u16.to_be(), 5u16.to_be(), 6u16.to_be(), 7u16.to_be(), ]; assert_eq!(unsafe { raw.u.Word }, want); assert_eq!(from_in6_addr(raw), ip); } ================================================ FILE: tests/data/hello_world.txt ================================================ Hello world! ================================================ FILE: tests/data/lorem_ipsum.txt ================================================ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec blandit ex vel turpis mollis, sed pretium purus ultricies. Nam sagittis sapien in nisl bibendum pulvinar. Proin sit amet luctus est. Fusce id elit laoreet, varius nunc in, semper massa. Mauris viverra turpis sed metus tincidunt, eu interdum elit ultrices. Ut ut sapien vitae velit aliquet venenatis ut volutpat odio. Aenean pellentesque odio at tellus iaculis porta. Vivamus est orci, luctus eget sollicitudin at, mattis accumsan risus. Donec iaculis a augue sed tristique. Vivamus iaculis ac quam eu tincidunt. Nam in nunc mollis, gravida mauris eu, lobortis nibh. Maecenas lacinia est in purus elementum efficitur. Proin tincidunt bibendum ante nec venenatis. Morbi at orci varius, efficitur nibh a, commodo libero. Pellentesque eget velit maximus, rutrum quam ac, tincidunt enim. Quisque euismod felis non sagittis porta. Nulla facilisi. Nullam ac consequat mi, quis eleifend risus. Duis tincidunt, eros in dapibus aliquam, erat metus mollis purus, sit amet lacinia enim velit vitae augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus laoreet sagittis justo. Nullam luctus metus facilisis erat euismod, ac dapibus urna feugiat. Mauris varius euismod porta. Donec vestibulum elit id diam tempor, ac accumsan risus accumsan. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec sit amet arcu ut libero efficitur tincidunt sed ut sem. Nam in cursus lectus, quis faucibus lacus. Aliquam elit urna, hendrerit at leo quis, rutrum porta velit. Fusce quam dolor, lacinia a tristique sit amet, imperdiet sed mi. Mauris augue nulla, bibendum hendrerit congue id, vulputate ut quam. Nam tempor vel arcu nec volutpat. Sed ac risus sagittis, dictum augue at, elementum diam. Fusce nulla velit, auctor sit amet dapibus quis, lobortis a nisi. Quisque orci eros, fermentum et est at, tincidunt lacinia eros. Aenean quis ultrices nunc. Etiam ac nisi sapien. Sed eu metus nec nisl faucibus mattis. Phasellus pharetra arcu nec lorem consequat, at ullamcorper tellus fringilla. Integer lectus ante, mollis a felis et, fringilla pellentesque mi. Morbi lorem odio, bibendum quis tortor eget, mattis consequat nisi. Nunc ut justo turpis. Praesent ultricies mi ac dui vestibulum hendrerit ut et nulla. Nulla porta neque arcu, sit amet suscipit nulla iaculis et. Sed hendrerit massa ac nisl ultricies, id mattis metus maximus. Ut suscipit, urna vitae rutrum placerat, sapien ex semper nulla, eu finibus purus magna accumsan dui. Ut sed dolor pellentesque, tempus lorem ut, mollis dui. Donec mi sapien, dignissim ac ultrices a, ullamcorper ut augue. In sagittis leo a nunc pharetra porttitor. Aliquam magna libero, finibus vel augue et, venenatis venenatis sapien. Nullam consequat mi non lectus lacinia, a varius est tempus. Fusce sit amet tellus orci. Curabitur ut risus maximus, pellentesque leo vitae, porttitor ex. Nam eleifend porta nibh vitae luctus. Nulla quis sapien hendrerit metus eleifend condimentum sed non massa. Nulla dictum urna justo, et iaculis sem gravida sit amet. Pellentesque in metus quis nibh hendrerit fringilla. Aliquam varius vitae mi vitae fringilla. Donec et ligula non dui suscipit porta vel ac lectus. Fusce in sodales augue. Praesent mattis tincidunt elit. Etiam venenatis finibus ligula, eget viverra lacus dapibus eu. Proin pretium mi sed lacus tincidunt pretium. Integer rhoncus arcu dui. In id lacus volutpat, tristique nulla nec, euismod nulla. Vestibulum rhoncus mauris tincidunt pellentesque auctor. Maecenas nunc ante, tempor ut ipsum et, viverra pretium ligula. Quisque ullamcorper turpis libero, eu convallis velit malesuada sed. Nulla libero nulla, dapibus vitae est non, congue rhoncus risus. Aliquam eleifend lectus eu nulla commodo, nec consectetur risus volutpat. Mauris dapibus risus eu nunc semper dignissim. Mauris a dignissim nibh. Donec maximus, est sed varius vestibulum, diam ligula porttitor nibh, a sagittis mauris augue ut est. Aenean sed enim magna. Nam cursus tellus lacus, ac aliquam arcu sollicitudin congue. Etiam a purus quis nisi congue tempor vel vitae quam. Pellentesque at placerat nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris et iaculis leo, ut tempor elit. Donec accumsan gravida vulputate. Praesent consectetur diam tristique, finibus libero eget, elementum erat. Aenean cursus suscipit tristique. Praesent ante sapien, pellentesque quis mauris id, pharetra mattis diam. Quisque quis elementum purus. Curabitur in sem molestie, hendrerit sem vehicula, vulputate risus. Mauris sit amet leo condimentum, consequat arcu nec, molestie nisl. Fusce eleifend sit amet dui a porta. Nullam et eleifend sem, a porttitor lectus. Duis nec porttitor velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse in interdum tortor. Proin at justo est. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed pulvinar ullamcorper congue. Donec ultricies aliquam turpis, sollicitudin bibendum dui. Nam volutpat, neque et consequat porttitor, orci elit aliquet est, a sagittis risus magna ac nunc. Pellentesque ut pretium leo, quis bibendum erat. Nulla ornare diam libero, vel lobortis orci lobortis ac. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vestibulum bibendum id libero at egestas. Suspendisse a mollis sem. Nunc gravida massa sed dui posuere lobortis. Aenean eget malesuada justo, eget ullamcorper ipsum. Sed fringilla enim vel sapien condimentum fringilla. Vivamus a massa sapien. Sed consequat ipsum orci, nec ultricies felis aliquet ac. Mauris mattis velit at urna pharetra tristique. Praesent urna tellus, pellentesque eu molestie sed, suscipit id felis. Quisque euismod odio vel imperdiet facilisis. Mauris mauris urna, vehicula eget tempus vel, aliquet sed ex. Donec orci mauris, mattis sit amet augue sit amet, molestie maximus lacus. Fusce a dapibus lectus, eu placerat ligula. Sed nec interdum mauris. Nullam auctor vel tortor in suscipit. Mauris dui orci, interdum ac laoreet eget, fringilla a orci. Duis mollis viverra metus, vitae finibus dolor aliquet quis. Aliquam erat volutpat. Aenean in mi scelerisque leo varius facilisis. Proin tincidunt mauris sed enim fringilla, quis ullamcorper nibh interdum. Nam quis libero in nunc gravida sodales ut dapibus libero. Vivamus leo augue, mattis ut turpis sit amet, vestibulum semper sapien. Quisque erat purus, placerat viverra sem quis, cursus volutpat nisi. Proin a lectus in elit luctus mattis. Vivamus metus diam, vulputate nec blandit sed, ultricies quis lectus. Donec ante dui, consectetur vitae justo ut, efficitur scelerisque ante. Aenean aliquet ut neque non dictum. Nunc at gravida arcu. Pellentesque dui neque, malesuada sit amet porta ac, lobortis id felis. Donec viverra erat quis ante finibus, a euismod orci bibendum. Sed vel risus non lacus fermentum egestas vitae at nisi. Pellentesque ullamcorper vestibulum fermentum. Mauris ante mi, fringilla non dui at, dignissim posuere velit. Duis dictum nibh quis massa sollicitudin convallis. Phasellus dapibus turpis convallis, pretium arcu sit amet, fringilla turpis. Vivamus ut risus tincidunt, blandit tellus at, fermentum mi. Etiam ut metus ut lorem consectetur sagittis. Praesent ut velit ut purus dapibus eleifend id et lectus. Sed pharetra in magna lacinia pharetra. Praesent tincidunt sem a gravida eleifend. Maecenas suscipit mi ut nisi viverra placerat. Cras sed faucibus massa. Duis sit amet imperdiet odio. Nulla non magna cursus, pharetra lacus sed, lacinia velit. Nunc luctus pretium elit quis tempor. Aenean nec felis nec ante facilisis eleifend. Nulla faucibus commodo fermentum. Proin convallis imperdiet facilisis. Ut nec dapibus leo. Pellentesque aliquam hendrerit ipsum mollis convallis. Sed laoreet vehicula urna, ac ornare risus egestas non. Aenean dapibus commodo massa, at aliquam elit consectetur eu. In libero risus, mollis vel dui vel, bibendum bibendum lorem. Sed a odio ac est sollicitudin pharetra fermentum eleifend dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eget justo pellentesque tellus pulvinar porta. Aenean accumsan orci id lorem malesuada ultricies. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam eleifend elementum lacus, et hendrerit nisi tempus ut. Vestibulum id tincidunt ante. Nulla luctus consectetur hendrerit. Praesent et bibendum ipsum, a volutpat purus. Suspendisse quis tellus velit. Aliquam erat volutpat. Vestibulum in felis posuere, sodales turpis in, mollis augue. Quisque placerat pretium tortor, nec vulputate erat dapibus sit amet. Morbi vehicula fringilla finibus. Ut congue ligula tellus, et posuere purus rhoncus ut. Proin fermentum ligula eget augue ullamcorper mollis. Vivamus venenatis fringilla odio, sit amet tempus metus porta at. Nunc eget libero tincidunt, viverra lorem sed, congue nibh. Aliquam id dolor mollis, rutrum mi ac, lobortis nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque quis pellentesque arcu. Duis iaculis diam in neque mattis, at rutrum nulla egestas. Curabitur feugiat efficitur convallis. Donec sit amet risus ex. Donec vel tortor at quam aliquet porttitor sagittis gravida justo. Morbi at mattis eros, non condimentum diam. Maecenas dictum pharetra ex. Sed in arcu eu purus vehicula placerat nec blandit purus. In ex augue, dignissim in velit eget, ornare convallis mi. Cras ut orci quis neque vulputate rutrum id id quam. In molestie orci id est mollis, eu varius odio tempor. Nulla sapien tellus, tempus volutpat lacus volutpat, dapibus varius orci. Aliquam risus felis, varius sed vehicula quis, elementum et urna. Quisque purus nunc, hendrerit eget imperdiet euismod, eleifend sit amet erat. Integer sed lacus quis sapien hendrerit ultrices. Fusce vehicula nulla nisl, sed maximus est scelerisque id. Pellentesque mattis, sapien quis accumsan rutrum, lacus odio blandit est, non malesuada velit diam sed enim. Mauris scelerisque laoreet ex, sed eleifend justo fringilla in. Nullam ligula urna, imperdiet sit amet luctus sed, placerat sed est. In maximus sem vitae est lacinia pellentesque. Ut congue dolor at ligula porta, egestas pulvinar nulla dignissim. Ut accumsan viverra mauris, eget eleifend nibh aliquet imperdiet. Donec in pretium turpis. Nam accumsan ligula lectus, consectetur eleifend arcu tempus dignissim. Nam malesuada vulputate mauris, et lobortis dui feugiat sit amet. Pellentesque pulvinar mauris iaculis urna lobortis rutrum. Integer tristique laoreet accumsan. Suspendisse potenti. Quisque finibus eleifend eleifend. Nam sagittis, enim et sodales placerat, quam sapien pulvinar libero, et mattis est purus id augue. Praesent vitae faucibus diam. In elementum vel mi eget finibus. Morbi non nisi et erat auctor pretium. Quisque ac tortor nulla. Suspendisse imperdiet magna vel elit dapibus aliquet. Fusce massa arcu, semper vitae orci quis, facilisis semper leo. Quisque eget scelerisque magna. Sed et neque ut quam semper cursus quis a sem. Nulla in erat eu nibh dapibus venenatis. Vestibulum tincidunt lacinia felis, at vulputate risus mollis at. Suspendisse vitae justo dapibus, auctor massa quis, consequat felis. Proin id enim urna. Mauris sed eleifend tortor. Cras semper mi sem. Sed quis eros et nibh pretium malesuada. Etiam ac lacus sit amet mi placerat lobortis. Proin dignissim lacus non nulla dictum, sed lobortis quam vehicula. Ut quis gravida urna, facilisis pretium quam. Quisque at tristique lacus. Duis tempus augue eu eleifend porttitor. Nulla diam sapien, posuere non pretium imperdiet, egestas nec purus. Ut neque purus, cursus a augue nec, sollicitudin ultrices erat. Vestibulum hendrerit bibendum massa sed egestas. Aenean rhoncus tincidunt nibh, vitae finibus mi porttitor sed. Proin elementum quis nunc at pretium. Suspendisse potenti. ================================================ FILE: tests/socket.rs ================================================ #![allow(clippy::bool_assert_comparison)] #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "tvos", target_os = "watchos", ) ))] use std::fs::File; use std::io; #[cfg(not(any(target_os = "redox", target_os = "vita", target_os = "wasi")))] use std::io::IoSlice; use std::io::Read; use std::io::Write; #[cfg(not(target_os = "vita"))] use std::mem::MaybeUninit; use std::mem::{self}; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpStream}; #[cfg(not(any(target_os = "redox", target_os = "vita")))] use std::net::{Ipv6Addr, SocketAddrV6}; #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "tvos", target_os = "watchos", ) ))] use std::num::NonZeroUsize; #[cfg(target_os = "wasi")] use std::os::fd::AsRawFd; #[cfg(unix)] use std::os::unix::io::AsRawFd; #[cfg(windows)] use std::os::windows::io::AsRawSocket; #[cfg(unix)] use std::path::Path; use std::str; #[cfg(not(target_os = "vita"))] use std::thread; use std::time::Duration; use std::{env, fs}; #[cfg(windows)] use windows_sys::Win32::Foundation::{GetHandleInformation, HANDLE_FLAG_INHERIT}; #[cfg(not(any(target_os = "redox", target_os = "vita", target_os = "wasi")))] use socket2::MaybeUninitSlice; #[cfg(not(target_os = "vita"))] use socket2::TcpKeepalive; use socket2::{Domain, Protocol, SockAddr, Socket, Type}; #[test] fn domain_for_address() { let ipv4: SocketAddr = "127.0.0.1:8080".parse().unwrap(); assert!(ipv4.is_ipv4()); let ipv6: SocketAddr = "[::1]:8080".parse().unwrap(); assert!(ipv6.is_ipv6()); assert_eq!(Domain::for_address(ipv4), Domain::IPV4); assert_eq!(Domain::for_address(ipv6), Domain::IPV6); } #[test] fn domain_fmt_debug() { let tests = &[ (Domain::IPV4, "AF_INET"), (Domain::IPV6, "AF_INET6"), #[cfg(not(target_os = "wasi"))] (Domain::UNIX, "AF_UNIX"), #[cfg(all(feature = "all", any(target_os = "fuchsia", target_os = "linux")))] (Domain::PACKET, "AF_PACKET"), #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] (Domain::VSOCK, "AF_VSOCK"), (0.into(), "AF_UNSPEC"), (500.into(), "500"), ]; let mut buf = Vec::new(); for (input, want) in tests { buf.clear(); write!(buf, "{input:?}").unwrap(); let got = str::from_utf8(&buf).unwrap(); assert_eq!(got, *want); } } #[test] fn type_fmt_debug() { let tests = &[ (Type::STREAM, "SOCK_STREAM"), (Type::DGRAM, "SOCK_DGRAM"), #[cfg(all(feature = "all", not(target_os = "wasi")))] (Type::SEQPACKET, "SOCK_SEQPACKET"), #[cfg(all(feature = "all", not(any(target_os = "redox", target_os = "wasi"))))] (Type::RAW, "SOCK_RAW"), (500.into(), "500"), ]; let mut buf = Vec::new(); for (input, want) in tests { buf.clear(); write!(buf, "{input:?}").unwrap(); let got = str::from_utf8(&buf).unwrap(); assert_eq!(got, *want); } } #[test] fn protocol_fmt_debug() { let tests = &[ #[cfg(not(target_os = "wasi"))] (Protocol::ICMPV4, "IPPROTO_ICMP"), #[cfg(not(target_os = "wasi"))] (Protocol::ICMPV6, "IPPROTO_ICMPV6"), (Protocol::TCP, "IPPROTO_TCP"), (Protocol::UDP, "IPPROTO_UDP"), #[cfg(target_os = "linux")] (Protocol::MPTCP, "IPPROTO_MPTCP"), #[cfg(all(feature = "all", target_os = "linux"))] (Protocol::DCCP, "IPPROTO_DCCP"), #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] (Protocol::SCTP, "IPPROTO_SCTP"), (500.into(), "500"), ]; let mut buf = Vec::new(); for (input, want) in tests { buf.clear(); write!(buf, "{input:?}").unwrap(); let got = str::from_utf8(&buf).unwrap(); assert_eq!(got, *want); } } #[test] #[should_panic] #[cfg(unix)] fn from_invalid_raw_fd_should_panic() { use std::os::unix::io::FromRawFd; let _socket = unsafe { Socket::from_raw_fd(-1) }; } #[test] #[cfg(not(target_os = "wasi"))] fn socket_address_unix() { let string = "/tmp/socket"; let addr = SockAddr::unix(string).unwrap(); assert!(addr.as_socket_ipv4().is_none()); assert!(addr.as_socket_ipv6().is_none()); assert!(!addr.is_ipv4()); assert!(!addr.is_ipv6()); assert!(addr.is_unix()); assert_eq!(addr.domain(), Domain::UNIX); #[cfg(unix)] { assert!(!addr.is_unnamed()); assert_eq!(addr.as_pathname(), Some(Path::new(string))); assert_eq!(addr.as_abstract_namespace(), None); let unix = addr.as_unix().unwrap(); assert_eq!(addr.as_pathname(), unix.as_pathname()); } } #[test] #[cfg(not(target_os = "wasi"))] fn socket_address_unix_unnamed() { let addr = SockAddr::unix("").unwrap(); assert!(addr.as_socket_ipv4().is_none()); assert!(addr.as_socket_ipv6().is_none()); assert!(!addr.is_ipv4()); assert!(!addr.is_ipv6()); assert!(addr.is_unix()); assert_eq!(addr.domain(), Domain::UNIX); #[cfg(unix)] { assert!(addr.is_unnamed()); assert_eq!(addr.as_pathname(), None); assert_eq!(addr.as_abstract_namespace(), None); assert!(addr.as_unix().is_none()); } } #[test] #[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(); assert_eq!( addr.len() as usize, std::mem::size_of::() ); assert!(!addr.is_unnamed()); // The first byte is the opening null bytes of an abstract address, should not be included. assert_eq!(addr.as_abstract_namespace(), Some(&path.as_bytes()[1..])); assert!(addr.as_pathname().is_none()); assert!(!addr.is_unnamed()); } #[test] #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] fn socket_address_vsock() { let addr = SockAddr::vsock(1, 9999); assert!(addr.as_socket_ipv4().is_none()); assert!(addr.as_socket_ipv6().is_none()); assert_eq!(addr.as_vsock_address().unwrap(), (1, 9999)); } #[test] #[cfg(not(target_os = "wasi"))] // All WASI sockets are always non-blocking by design. fn set_nonblocking() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); assert_nonblocking(&socket, false); socket.set_nonblocking(true).unwrap(); assert_nonblocking(&socket, true); socket.set_nonblocking(false).unwrap(); assert_nonblocking(&socket, false); } fn assert_common_flags(socket: &Socket, expected: bool) { #[cfg(all(unix, not(target_os = "vita")))] assert_close_on_exec(socket, expected); #[cfg(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", ))] assert_flag_no_sigpipe(socket, expected); #[cfg(windows)] assert_flag_no_inherit(socket, expected); // Vita does not have process API, so neither SO_NOSIGPIPE nor FD_CLOEXEC are supported on this platform #[cfg(target_os = "vita")] { let _ = socket; let _ = expected; } } #[test] fn common_flags() { let listener = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); assert_common_flags(&listener, true); listener.bind(&any_ipv4()).unwrap(); listener.listen(1).unwrap(); let client = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); client.connect(&listener.local_addr().unwrap()).unwrap(); let accepted = listener.accept().unwrap().0; assert_common_flags(&accepted, true); } #[test] fn no_common_flags() { let listener = Socket::new_raw(Domain::IPV4, Type::STREAM, None).unwrap(); assert_common_flags(&listener, false); listener.bind(&any_ipv4()).unwrap(); listener.listen(1).unwrap(); let client = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); client.connect(&listener.local_addr().unwrap()).unwrap(); let accepted = listener.accept_raw().unwrap().0; assert_common_flags(&accepted, false); } #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "netbsd", target_os = "openbsd" ) ))] #[test] fn type_nonblocking() { let ty = Type::STREAM.nonblocking(); let socket = Socket::new(Domain::IPV4, ty, None).unwrap(); assert_nonblocking(&socket, true); } /// Assert that `NONBLOCK` is set on `socket`. #[cfg(any(unix, target_os = "wasi"))] #[track_caller] pub fn assert_nonblocking(socket: &Socket, want: bool) { #[cfg(all(feature = "all", any(unix, target_os = "wasi")))] assert_eq!(socket.nonblocking().unwrap(), want, "non-blocking option"); #[cfg(not(any( all(feature = "all", any(unix, target_os = "wasi")), target_os = "vita" )))] { let flags = unsafe { libc::fcntl(socket.as_raw_fd(), libc::F_GETFL) }; assert_eq!(flags & libc::O_NONBLOCK != 0, want, "non-blocking option"); } #[cfg(all(target_os = "vita", not(feature = "all")))] { let mut optval: libc::c_int = 0; let mut optlen = std::mem::size_of::() as libc::socklen_t; let res = unsafe { libc::getsockopt( socket.as_raw_fd(), libc::SOL_SOCKET, libc::SO_NONBLOCK, &mut optval as *mut libc::c_int as _, &mut optlen, ) }; assert_eq!(res, 0, "unable to get non-blocing option"); assert_eq!(optval > 0, want, "non-blocking option"); } } #[cfg(windows)] #[track_caller] pub fn assert_nonblocking(_: &Socket, _: bool) { // No way to get this information... } #[cfg(all(unix, feature = "all", not(target_os = "vita")))] #[test] fn set_cloexec() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).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( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "hurd", target_os = "linux", target_os = "netbsd", target_os = "openbsd" ) ))] #[test] fn type_cloexec() { let ty = Type::STREAM.cloexec(); let socket = Socket::new(Domain::IPV4, ty, None).unwrap(); assert_close_on_exec(&socket, true); } /// Assert that `CLOEXEC` is set on `socket`. #[cfg(unix)] #[track_caller] pub fn assert_close_on_exec(socket: &S, want: bool) where S: AsRawFd, { let flags = unsafe { libc::fcntl(socket.as_raw_fd(), libc::F_GETFD) }; assert_eq!(flags & libc::FD_CLOEXEC != 0, want, "CLOEXEC option"); } #[cfg(all(feature = "all", windows))] #[test] fn set_no_inherit() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).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))] #[test] fn type_no_inherit() { let ty = Type::STREAM.no_inherit(); let socket = Socket::new(Domain::IPV4, ty, None).unwrap(); assert_flag_no_inherit(&socket, true); } /// Assert that `FLAG_INHERIT` is not set on `socket`. #[cfg(windows)] #[track_caller] pub fn assert_flag_no_inherit(socket: &S, want: bool) where S: AsRawSocket, { let mut flags = 0; if unsafe { GetHandleInformation(socket.as_raw_socket() as _, &mut flags) } == 0 { let err = io::Error::last_os_error(); panic!("unexpected error: {err}"); } assert_eq!( flags & HANDLE_FLAG_INHERIT != 0, !want, "FLAG_INHERIT option" ); } #[cfg(all(feature = "all", windows))] #[test] fn type_registered_io() { let ty = Type::DGRAM.registered_io(); let socket = Socket::new(Domain::IPV4, ty, None).unwrap(); assert_registered_io(&socket); } /// Assert that registered I/O is enabled on `socket`. #[cfg(windows)] #[track_caller] pub fn assert_registered_io(socket: &S) where S: AsRawSocket, { use std::ptr; use windows_sys::core::GUID; use windows_sys::Win32::Networking::WinSock; let mut table = MaybeUninit::::uninit(); let guid = WinSock::WSAID_MULTIPLE_RIO; let mut bytes = 0; let r = unsafe { WinSock::WSAIoctl( socket.as_raw_socket() as _, WinSock::SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER, (&guid as *const GUID) as *const _, mem::size_of_val(&guid) as u32, table.as_mut_ptr() as *mut _, mem::size_of_val(&table) as u32, (&mut bytes as *mut i32) as *mut _, ptr::null_mut(), None, ) }; if r != 0 { let err = io::Error::last_os_error(); panic!("unexpected error: {err}"); } } #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", ) ))] #[test] fn set_nosigpipe() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).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(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", ))] #[track_caller] pub fn assert_flag_no_sigpipe(socket: &S, want: bool) where S: AsRawFd, { use std::mem::size_of; let mut flags: libc::c_int = 0; let mut length = size_of::() as libc::socklen_t; let res = unsafe { libc::getsockopt( socket.as_raw_fd(), libc::SOL_SOCKET, libc::SO_NOSIGPIPE, &mut flags as *mut _ as *mut _, &mut length, ) }; if res != 0 { panic!("unexpected error: {}", io::Error::last_os_error()); } assert_eq!(length as usize, size_of::()); assert_eq!(flags, want as _); } const DATA: &[u8] = b"hello world"; #[test] #[cfg(not(target_os = "wasi"))] // WASI uses start-connect/finish-connect, no connect_timeout. fn connect_timeout_unrouteable() { // This IP is unroutable, so connections should always time out. let addr = "10.255.255.1:80".parse::().unwrap().into(); let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); match socket.connect_timeout(&addr, Duration::from_millis(250)) { Ok(_) => panic!("unexpected success"), Err(ref err) if err.kind() == io::ErrorKind::TimedOut => {} Err(err) => panic!("unexpected error {err}"), } } #[test] // Loopback has special behavior on vita. // WASI uses start-connect/finish-connect, no connect_timeout. #[cfg(not(any(target_os = "vita", target_os = "wasi")))] fn connect_timeout_unbound() { // Bind and drop a socket to track down a "probably unassigned" port. let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); let addr = "127.0.0.1:0".parse::().unwrap().into(); socket.bind(&addr).unwrap(); let addr = socket.local_addr().unwrap(); drop(socket); let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); match socket.connect_timeout(&addr, Duration::from_millis(250)) { Ok(_) => panic!("unexpected success"), Err(ref err) if err.kind() == io::ErrorKind::ConnectionRefused || err.kind() == io::ErrorKind::TimedOut => {} Err(err) => panic!("unexpected error {err}"), } } #[test] #[cfg(not(target_os = "wasi"))] // WASI uses start-connect/finish-connect, no connect_timeout. fn connect_timeout_valid() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); socket .bind(&"127.0.0.1:0".parse::().unwrap().into()) .unwrap(); socket.listen(128).unwrap(); let addr = socket.local_addr().unwrap(); let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); socket .connect_timeout(&addr, Duration::from_millis(250)) .unwrap(); } #[test] #[cfg(all(feature = "all", unix))] fn pair() { let (mut a, mut b) = Socket::pair(Domain::UNIX, Type::STREAM, None).unwrap(); let _ = a.write(DATA).unwrap(); let mut buf = [0; DATA.len() + 1]; let n = b.read(&mut buf).unwrap(); assert_eq!(n, DATA.len()); assert_eq!(&buf[..n], DATA); } #[cfg(not(target_os = "wasi"))] fn unix_sockets_supported() -> bool { #[cfg(windows)] { // Only some versions of Windows support Unix sockets. match Socket::new(Domain::UNIX, Type::STREAM, None) { Ok(_) => {} Err(err) if err.raw_os_error() == Some(windows_sys::Win32::Networking::WinSock::WSAEAFNOSUPPORT as i32) => { return false; } Err(err) => panic!("socket error: {err}"), } } #[cfg(target_os = "vita")] return false; #[cfg(not(target_os = "vita"))] true } #[test] #[cfg(not(target_os = "wasi"))] fn unix() { if !unix_sockets_supported() { return; } let mut path = env::temp_dir(); path.push("socket2.unix"); let _ = fs::remove_dir_all(&path); fs::create_dir_all(&path).unwrap(); path.push("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; let _ = a.write(DATA).unwrap(); let mut buf = [0; DATA.len() + 1]; let n = b.read(&mut buf).unwrap(); assert_eq!(n, DATA.len()); assert_eq!(&buf[..n], DATA); } #[test] #[cfg(not(target_os = "wasi"))] fn unix_accept() { if !unix_sockets_supported() { return; } let mut path = env::temp_dir(); path.push("socket2.unix_accept"); let _ = fs::remove_dir_all(&path); fs::create_dir_all(&path).unwrap(); path.push("unix_accept"); let listener = Socket::new(Domain::UNIX, Type::STREAM, None).unwrap(); listener.bind(&SockAddr::unix(&path).unwrap()).unwrap(); listener.listen(1).unwrap(); Socket::new(Domain::UNIX, Type::STREAM, None) .unwrap() .connect(&SockAddr::unix(path).unwrap()) .unwrap(); let (socket, _) = listener.accept().unwrap(); assert_common_flags(&socket, true); } #[test] #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] #[ignore = "using VSOCK family requires optional kernel support (works when enabled)"] fn vsock() { let addr = SockAddr::vsock(libc::VMADDR_CID_LOCAL, libc::VMADDR_PORT_ANY); let listener = Socket::new(Domain::VSOCK, Type::STREAM, None).unwrap(); listener.bind(&addr).unwrap(); listener.listen(10).unwrap(); let (_, port) = listener.local_addr().unwrap().as_vsock_address().unwrap(); let addr = SockAddr::vsock(libc::VMADDR_CID_LOCAL, port); let mut a = Socket::new(Domain::VSOCK, Type::STREAM, None).unwrap(); a.connect(&addr).unwrap(); let mut b = listener.accept().unwrap().0; let _ = a.write(DATA).unwrap(); let mut buf = [0; DATA.len() + 1]; let n = b.read(&mut buf).unwrap(); assert_eq!(n, DATA.len()); assert_eq!(&buf[..n], DATA); } #[test] #[cfg(not(any(target_os = "vita", target_os = "wasi")))] // Vita and WASI do not support OOB fn out_of_band() { let listener = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); listener.bind(&any_ipv4()).unwrap(); listener.listen(1).unwrap(); let sender = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); sender.bind(&any_ipv4()).unwrap(); sender.connect(&listener.local_addr().unwrap()).unwrap(); let (receiver, _) = listener.accept().unwrap(); sender.send(DATA).unwrap(); const FIRST: &[u8] = b"!"; assert_eq!(sender.send_out_of_band(FIRST).unwrap(), FIRST.len()); // On macOS if no `MSG_OOB` is available it will return `EINVAL`, to prevent // this from happening we'll sleep to ensure the data is present. thread::sleep(Duration::from_millis(10)); let mut buf = [MaybeUninit::new(1); DATA.len() + 1]; let n = receiver.recv_out_of_band(&mut buf).unwrap(); assert_eq!(n, FIRST.len()); assert_eq!(unsafe { assume_init(&buf[..n]) }, FIRST); let n = receiver.recv(&mut buf).unwrap(); assert_eq!(n, DATA.len()); assert_eq!(unsafe { assume_init(&buf[..n]) }, DATA); } #[test] #[cfg(not(any(target_os = "redox", target_os = "vita", target_os = "wasi")))] // WASI: SO_RCVTIMEO excluded, used in test setup. fn udp_peek_sender() { let (socket_a, socket_b) = udp_pair_unconnected(); let socket_a_addr = socket_a.local_addr().unwrap(); let socket_b_addr = socket_b.local_addr().unwrap(); socket_b.send_to(b"Hello, world!", &socket_a_addr).unwrap(); let sender_addr = socket_a.peek_sender().unwrap(); assert_eq!(sender_addr.as_socket(), socket_b_addr.as_socket()); } #[test] #[cfg(not(any(target_os = "redox", target_os = "vita", target_os = "wasi")))] fn send_recv_vectored() { let (socket_a, socket_b) = udp_pair_connected(); let sent = socket_a .send_vectored(&[ IoSlice::new(b"the"), IoSlice::new(b"weeknight"), IoSlice::new(b"would"), IoSlice::new(b"yellow"), ]) .unwrap(); assert_eq!(sent, 23); let mut the = [MaybeUninit::new(1); 3]; let mut wee = [MaybeUninit::new(2); 3]; let mut knight = [MaybeUninit::new(3); 6]; let mut would = [MaybeUninit::new(4); 5]; let mut yell = [MaybeUninit::new(5); 4]; let mut ow = [MaybeUninit::new(6); 2]; let (received, flags) = socket_b .recv_vectored(&mut [ MaybeUninitSlice::new(&mut the), MaybeUninitSlice::new(&mut wee), MaybeUninitSlice::new(&mut knight), MaybeUninitSlice::new(&mut would), MaybeUninitSlice::new(&mut yell), MaybeUninitSlice::new(&mut ow), ]) .unwrap(); assert_eq!(received, 23); #[cfg(all(unix, not(target_os = "redox")))] assert_eq!(flags.is_end_of_record(), false); #[cfg(all(unix, not(target_os = "redox")))] assert_eq!(flags.is_out_of_band(), false); assert_eq!(flags.is_truncated(), false); assert_eq!(unsafe { assume_init(&the) }, b"the"); assert_eq!(unsafe { assume_init(&wee) }, b"wee"); assert_eq!(unsafe { assume_init(&knight) }, b"knight"); assert_eq!(unsafe { assume_init(&would) }, b"would"); assert_eq!(unsafe { assume_init(&yell) }, b"yell"); assert_eq!(unsafe { assume_init(&ow) }, b"ow"); } #[test] #[cfg(not(any(target_os = "redox", target_os = "vita", target_os = "wasi")))] fn send_from_recv_to_vectored() { let (socket_a, socket_b) = udp_pair_unconnected(); let addr_a = socket_a.local_addr().unwrap(); let addr_b = socket_b.local_addr().unwrap(); let sent = socket_a .send_to_vectored( &[ IoSlice::new(b"surgeon"), IoSlice::new(b"has"), IoSlice::new(b"menswear"), ], &addr_b, ) .unwrap(); assert_eq!(sent, 18); let mut surgeon = [MaybeUninit::new(10); 7]; let mut has = [MaybeUninit::new(11); 3]; let mut men = [MaybeUninit::new(12); 3]; let mut swear = [MaybeUninit::new(13); 5]; let (received, flags, addr) = socket_b .recv_from_vectored(&mut [ MaybeUninitSlice::new(&mut surgeon), MaybeUninitSlice::new(&mut has), MaybeUninitSlice::new(&mut men), MaybeUninitSlice::new(&mut swear), ]) .unwrap(); assert_eq!(received, 18); #[cfg(all(unix, not(target_os = "redox")))] assert_eq!(flags.is_end_of_record(), false); #[cfg(all(unix, not(target_os = "redox")))] assert_eq!(flags.is_out_of_band(), false); assert_eq!(flags.is_truncated(), false); #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] assert_eq!(flags.is_confirm(), false); #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] assert_eq!(flags.is_dontroute(), false); assert_eq!( addr.as_socket_ipv6().unwrap(), addr_a.as_socket_ipv6().unwrap() ); assert_eq!(unsafe { assume_init(&surgeon) }, b"surgeon"); assert_eq!(unsafe { assume_init(&has) }, b"has"); assert_eq!(unsafe { assume_init(&men) }, b"men"); assert_eq!(unsafe { assume_init(&swear) }, b"swear"); } #[test] #[cfg(not(any(target_os = "redox", target_os = "vita", target_os = "wasi")))] fn sendmsg() { let (socket_a, socket_b) = udp_pair_unconnected(); const DATA: &[u8] = b"Hello, World!"; let bufs = &[IoSlice::new(DATA)]; let addr_b = socket_b.local_addr().unwrap(); let msg = socket2::MsgHdr::new().with_addr(&addr_b).with_buffers(bufs); let sent = socket_a.sendmsg(&msg, 0).unwrap(); assert_eq!(sent, DATA.len()); let mut buf = Vec::with_capacity(DATA.len() + 1); let received = socket_b.recv(buf.spare_capacity_mut()).unwrap(); assert_eq!(received, DATA.len()); } #[test] #[cfg(not(any(target_os = "redox", target_os = "vita", target_os = "wasi")))] fn recv_vectored_truncated() { let (socket_a, socket_b) = udp_pair_connected(); let sent = socket_a .send(b"do not feed the gremlins after midnight") .unwrap(); assert_eq!(sent, 39); let mut buffer = [MaybeUninit::new(20); 24]; let (received, flags) = socket_b .recv_vectored(&mut [MaybeUninitSlice::new(&mut buffer)]) .unwrap(); assert_eq!(received, 24); assert_eq!(flags.is_truncated(), true); assert_eq!(unsafe { assume_init(&buffer) }, b"do not feed the gremlins"); } #[test] #[cfg(not(any(target_os = "redox", target_os = "vita", target_os = "wasi")))] fn recv_from_vectored_truncated() { let (socket_a, socket_b) = udp_pair_unconnected(); let addr_a = socket_a.local_addr().unwrap(); let addr_b = socket_b.local_addr().unwrap(); let sent = socket_a .send_to(b"do not feed the gremlins after midnight", &addr_b) .unwrap(); assert_eq!(sent, 39); let mut buffer = [MaybeUninit::new(30); 24]; let (received, flags, addr) = socket_b .recv_from_vectored(&mut [MaybeUninitSlice::new(&mut buffer)]) .unwrap(); assert_eq!(received, 24); assert_eq!(flags.is_truncated(), true); assert_eq!( addr.as_socket_ipv6().unwrap(), addr_a.as_socket_ipv6().unwrap() ); assert_eq!(unsafe { assume_init(&buffer) }, b"do not feed the gremlins"); } /// Create a pair of non-connected UDP sockets suitable for unit tests. #[cfg(not(any(target_os = "redox", target_os = "vita")))] fn udp_pair_unconnected() -> (Socket, Socket) { // Use ephemeral ports assigned by the OS. let unspecified_addr = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0); let socket_a = Socket::new(Domain::IPV6, Type::DGRAM, None).unwrap(); let socket_b = Socket::new(Domain::IPV6, Type::DGRAM, None).unwrap(); socket_a.bind(&unspecified_addr.into()).unwrap(); socket_b.bind(&unspecified_addr.into()).unwrap(); // Set low timeouts to prevent the tests from blocking. socket_a .set_read_timeout(Some(std::time::Duration::from_millis(10))) .unwrap(); socket_b .set_read_timeout(Some(std::time::Duration::from_millis(10))) .unwrap(); socket_a .set_write_timeout(Some(std::time::Duration::from_millis(10))) .unwrap(); socket_b .set_write_timeout(Some(std::time::Duration::from_millis(10))) .unwrap(); (socket_a, socket_b) } /// Create a pair of connected UDP sockets suitable for unit tests. #[cfg(not(any(target_os = "redox", target_os = "vita")))] fn udp_pair_connected() -> (Socket, Socket) { let (socket_a, socket_b) = udp_pair_unconnected(); let addr_a = socket_a.local_addr().unwrap(); let addr_b = socket_b.local_addr().unwrap(); socket_a.connect(&addr_b).unwrap(); socket_b.connect(&addr_a).unwrap(); (socket_a, socket_b) } #[test] #[cfg(not(target_os = "vita"))] fn tcp_keepalive() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); let params = TcpKeepalive::new().with_time(Duration::from_secs(200)); #[cfg(all( feature = "all", any( target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "windows", ) ))] let params = params.with_interval(Duration::from_secs(30)); #[cfg(all( feature = "all", any( target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "windows" ) ))] let params = params.with_retries(10); // Set the parameters. socket.set_tcp_keepalive(¶ms).unwrap(); #[cfg(all( feature = "all", not(any(windows, target_os = "haiku", target_os = "openbsd")) ))] assert_eq!( socket.tcp_keepalive_time().unwrap(), Duration::from_secs(200) ); #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", ) ))] assert_eq!( socket.tcp_keepalive_interval().unwrap(), Duration::from_secs(30) ); #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "tvos", target_os = "watchos", target_os = "windows", ) ))] assert_eq!(socket.tcp_keepalive_retries().unwrap(), 10); } #[cfg(all(feature = "all", any(target_os = "fuchsia", target_os = "linux")))] #[test] #[ignore = "setting `SO_BINDTODEVICE` requires the `CAP_NET_RAW` capability (works when running as root)"] fn device() { // Some common network interface on Linux. const INTERFACES: &[&str] = &["lo", "lo0", "eth0", "wlan0"]; let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); assert_eq!(socket.device().unwrap(), None); for interface in INTERFACES.iter() { if let Err(err) = socket.bind_device(Some(interface.as_bytes())) { // Network interface is not available try another. if matches!(err.raw_os_error(), Some(libc::ENODEV)) { eprintln!("error binding to device (`{interface}`): {err}"); continue; } else { panic!("unexpected error binding device: {err}"); } } assert_eq!( socket.device().unwrap().as_deref(), Some(interface.as_bytes()) ); socket.bind_device(None).unwrap(); assert_eq!(socket.device().unwrap(), None); // Just need to do it with one interface. return; } panic!("failed to bind to any device."); } #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "solaris", target_os = "illumos", ) ))] #[test] fn device() { // Some common network interface on macOS. const INTERFACES: &[&str] = &["lo\0", "lo0\0", "en0\0"]; let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); assert_eq!(socket.device_index_v4().unwrap(), None); for interface in INTERFACES.iter() { let iface_index = std::num::NonZeroU32::new(unsafe { libc::if_nametoindex(interface.as_ptr() as *const _) }); // If no index is returned, try another interface alias if iface_index.is_none() { continue; } if let Err(err) = socket.bind_device_by_index_v4(iface_index) { // Network interface is not available try another. if matches!(err.raw_os_error(), Some(libc::ENODEV)) { eprintln!("error binding to device (`{interface}`): {err}"); continue; } else { panic!("unexpected error binding device: {err}"); } } assert_eq!(socket.device_index_v4().unwrap(), iface_index); socket.bind_device_by_index_v4(None).unwrap(); assert_eq!(socket.device_index_v4().unwrap(), None); // Just need to do it with one interface. return; } panic!("failed to bind to any device."); } #[cfg(all( feature = "all", any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "solaris", target_os = "illumos", ) ))] #[test] fn device_v6() { // Some common network interface on macOS. const INTERFACES: &[&str] = &["lo\0", "lo0\0", "en0\0"]; let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap(); assert_eq!(socket.device_index_v6().unwrap(), None); for interface in INTERFACES.iter() { let iface_index = std::num::NonZeroU32::new(unsafe { libc::if_nametoindex(interface.as_ptr() as *const _) }); // If no index is returned, try another interface alias if iface_index.is_none() { continue; } if let Err(err) = socket.bind_device_by_index_v6(iface_index) { // Network interface is not available try another. if matches!(err.raw_os_error(), Some(libc::ENODEV)) { eprintln!("error binding to device (`{interface}`): {err}"); continue; } else { panic!("unexpected error binding device: {err}"); } } assert_eq!(socket.device_index_v6().unwrap(), iface_index); socket.bind_device_by_index_v6(None).unwrap(); assert_eq!(socket.device_index_v6().unwrap(), None); // Just need to do it with one interface. return; } panic!("failed to bind to any device."); } #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "ios", target_os = "visionos", target_os = "linux", target_os = "macos", target_os = "tvos", target_os = "watchos", ) ))] #[test] fn sendfile() { #[derive(Debug)] struct TestFile { path: &'static str, data: &'static [u8], } const HELLO_WORLD: TestFile = TestFile { path: "tests/data/hello_world.txt", data: include_bytes!("data/hello_world.txt"), }; const LOREM: TestFile = TestFile { path: "tests/data/lorem_ipsum.txt", data: include_bytes!("data/lorem_ipsum.txt"), }; let listener = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); listener.bind(&any_ipv4()).unwrap(); listener.listen(1).unwrap(); let address = listener.local_addr().unwrap(); let sender = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); sender.connect(&address).unwrap(); let (receiver, _) = listener.accept().unwrap(); // Send a simple hello world file. { let file = File::open(HELLO_WORLD.path).unwrap(); let n = sender.sendfile(&file, 0, None).unwrap(); assert_eq!(n, HELLO_WORLD.data.len()); let mut buf = Vec::with_capacity(HELLO_WORLD.data.len() + 1); let n = receiver.recv(buf.spare_capacity_mut()).unwrap(); assert_eq!(n, HELLO_WORLD.data.len()); unsafe { buf.set_len(n) }; assert_eq!(buf, HELLO_WORLD.data); } // Send a larger file in two calls. { let file = File::open(LOREM.path).unwrap(); let n = sender .sendfile(&file, 0, NonZeroUsize::new(LOREM.data.len() / 2)) .unwrap(); assert_eq!(n, LOREM.data.len() / 2); let offset = n; let n = sender.sendfile(&file, offset, None).unwrap(); assert_eq!(offset + n, LOREM.data.len()); let mut buf = Vec::with_capacity(LOREM.data.len() + 1); let mut total = 0; while total < LOREM.data.len() { let n = receiver.recv(buf.spare_capacity_mut()).unwrap(); unsafe { buf.set_len(buf.len() + n) }; total += n; } assert_eq!(total, LOREM.data.len()); assert_eq!(buf, LOREM.data); } } #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", ) ))] #[test] fn is_listener() { let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); assert_eq!(socket.is_listener().unwrap(), false); socket.listen(1).unwrap(); assert_eq!(socket.is_listener().unwrap(), true); } #[cfg(all( feature = "all", any( target_os = "android", // TODO: add FreeBSD. // target_os = "freebsd", target_os = "fuchsia", target_os = "linux", ) ))] #[test] fn domain() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); assert_eq!(socket.domain().unwrap(), Domain::IPV4); let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap(); assert_eq!(socket.domain().unwrap(), Domain::IPV6); let socket = Socket::new(Domain::UNIX, Type::STREAM, None).unwrap(); assert_eq!(socket.domain().unwrap(), Domain::UNIX); } #[cfg(all( feature = "all", any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "windows", ) ))] #[test] fn protocol() { let socket = Socket::new(Domain::UNIX, Type::STREAM, None).unwrap(); assert_eq!(socket.protocol().unwrap(), None); /* Don't have permission for this on CI. let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::ICMPV4)).unwrap(); assert_eq!(socket.protocol().unwrap(), Some(Protocol::ICMPV4)); let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::ICMPV6)).unwrap(); assert_eq!(socket.protocol().unwrap(), Some(Protocol::ICMPV6)); */ let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); assert_eq!(socket.protocol().unwrap(), Some(Protocol::TCP)); let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP)).unwrap(); assert_eq!(socket.protocol().unwrap(), Some(Protocol::UDP)); } #[test] fn r#type() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); assert_eq!(socket.r#type().unwrap(), Type::STREAM); #[cfg(not(target_os = "vita"))] { let socket = Socket::new(Domain::IPV6, Type::DGRAM, None).unwrap(); assert_eq!(socket.r#type().unwrap(), Type::DGRAM); } // macos doesn't support seqpacket #[cfg(all( unix, not(any( target_os = "ios", target_os = "visionos", target_os = "macos", target_os = "tvos", target_os = "watchos", target_os = "vita", target_os = "cygwin", )), feature = "all", ))] { let socket = Socket::new(Domain::UNIX, Type::SEQPACKET, None).unwrap(); assert_eq!(socket.r#type().unwrap(), Type::SEQPACKET); } } #[cfg(all(feature = "all", target_os = "linux"))] #[test] fn cpu_affinity() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); // NOTE: This requires at least 2 CPU cores. let cpu = socket.cpu_affinity().unwrap(); let want = usize::from(cpu == 0); socket.set_cpu_affinity(want).unwrap(); assert_eq!(socket.cpu_affinity().unwrap(), want); } #[test] fn niche() { if mem::size_of::>() == mem::size_of::() { assert_eq!(mem::size_of::>(), mem::size_of::()); } } fn any_ipv4() -> SockAddr { SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0).into() } /// Assume the `buf`fer to be initialised. // TODO: replace with `MaybeUninit::slice_assume_init_ref` once stable. #[cfg(not(target_os = "vita"))] // Loopback has special behavior on vita unsafe fn assume_init(buf: &[MaybeUninit]) -> &[u8] { &*(buf as *const [MaybeUninit] as *const [u8]) } /// Macro to create a simple test to set and get a socket option. macro_rules! test { // Test using the `arg`ument as expected return value. ($( #[ $attr: meta ] )* $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { test!($( #[$attr] )* $get_fn, $set_fn($arg), $arg); }; ($( #[ $attr: meta ] )* $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { #[test] $( #[$attr] )* fn $get_fn() { test!(__ Domain::IPV4, $get_fn, $set_fn($arg), $expected); #[cfg(not(target_os = "vita"))] test!(__ Domain::IPV6, $get_fn, $set_fn($arg), $expected); } }; // Only test using a IPv4 socket. ($( #[ $attr: meta ] )* IPv4 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { #[test] $( #[$attr] )* fn $get_fn() { test!(__ Domain::IPV4, $get_fn, $set_fn($arg), $arg); } }; // Only test using a IPv6 socket. ($( #[ $attr: meta ] )* IPv6 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { #[test] $( #[$attr] )* fn $get_fn() { test!(__ Domain::IPV6, $get_fn, $set_fn($arg), $arg); } }; // Internal to this macro. (__ $ty: expr, $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { let socket = Socket::new($ty, Type::STREAM, None).expect("failed to create `Socket`"); let initial = socket.$get_fn().expect("failed to get initial value"); let arg = $arg; assert_ne!(initial, arg, "initial value and argument are the same"); socket.$set_fn(arg).expect("failed to set option"); let got = socket.$get_fn().expect("failed to get value"); let expected = $expected; assert_eq!(got, expected, "set and get values differ"); }; } const SET_BUF_SIZE: usize = 4096; // Linux doubles the buffer size for kernel usage, and exposes that when // retrieving the buffer size. #[cfg(not(target_os = "linux"))] const GET_BUF_SIZE: usize = SET_BUF_SIZE; #[cfg(target_os = "linux")] const GET_BUF_SIZE: usize = 2 * SET_BUF_SIZE; test!(tcp_nodelay, set_tcp_nodelay(true)); test!( recv_buffer_size, set_recv_buffer_size(SET_BUF_SIZE), GET_BUF_SIZE ); test!( send_buffer_size, set_send_buffer_size(SET_BUF_SIZE), GET_BUF_SIZE ); #[cfg(not(any(target_os = "redox", target_os = "wasi")))] 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", target_os = "cygwin", target_os = "wasi", )) ))] 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(any(target_os = "redox", target_os = "cygwin")), ))] test!( #[cfg_attr(target_os = "linux", ignore = "Different value returned")] tcp_mss, set_tcp_mss(256) ); #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] test!( #[ignore = "setting `IP_TRANSPARENT` requires the `CAP_NET_ADMIN` capability (works when running as root)"] IPv4 ip_transparent_v4, set_ip_transparent_v4(true) ); #[cfg(all(feature = "all", any(target_os = "fuchsia", target_os = "linux")))] test!( #[ignore = "setting `SO_MARK` requires the `CAP_NET_ADMIN` capability (works when running as root)"] mark, set_mark(123) ); #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] test!(tcp_cork, set_tcp_cork(true)); #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] test!(tcp_quickack, set_tcp_quickack(false)); #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] test!(tcp_thin_linear_timeouts, set_tcp_thin_linear_timeouts(true)); #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] test!(tcp_notsent_lowat, set_tcp_notsent_lowat(16 * 1024)); #[cfg(not(target_os = "wasi"))] // WASI: SO_LINGER not yet included (https://github.com/WebAssembly/WASI/issues/709). test!(linger, set_linger(Some(Duration::from_secs(10)))); test!( read_timeout, set_read_timeout(Some(Duration::from_secs(10))) ); test!(keepalive, set_keepalive(true)); #[cfg(all(feature = "all", any(target_os = "fuchsia", target_os = "linux")))] test!(freebind_v4, set_freebind_v4(true)); #[cfg(all(feature = "all", target_os = "linux"))] test!(IPv6 freebind_v6, set_freebind_v6(true)); test!(IPv4 ttl_v4, set_ttl_v4(40)); #[cfg(not(any( target_os = "fuchsia", target_os = "redox", target_os = "solaris", target_os = "illumos", target_os = "haiku", target_os = "cygwin", target_os = "wasi", )))] test!(IPv4 tos_v4, set_tos_v4(96)); #[cfg(not(any( target_os = "dragonfly", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "windows", target_os = "vita", target_os = "haiku", target_os = "cygwin", target_os = "wasi", )))] test!(IPv4 recv_tos_v4, set_recv_tos_v4(true)); #[cfg(not(any(windows, target_os = "cygwin", target_os = "wasi")))] // TODO: returns `WSAENOPROTOOPT` (10042) on Windows. WASI: SO_BROADCAST not yet included (https://github.com/WebAssembly/WASI/issues/721). test!(IPv4 broadcast, set_broadcast(true)); #[cfg(not(target_os = "vita"))] test!(IPv6 unicast_hops_v6, set_unicast_hops_v6(20)); #[cfg(not(any( windows, target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "vita", target_os = "cygwin", target_os = "wasi", // IPV6_V6ONLY always true in WASI, setter not exposed (https://github.com/WebAssembly/WASI/issues/747). )))] test!(IPv6 only_v6, set_only_v6(true)); // IPv6 socket are already IPv6 only on FreeBSD and Windows. #[cfg(any(windows, target_os = "freebsd", target_os = "cygwin"))] test!(IPv6 only_v6, set_only_v6(false)); #[cfg(all( feature = "all", any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "openbsd" ) ))] test!(IPv6 tclass_v6, set_tclass_v6(96)); #[cfg(not(any( target_os = "dragonfly", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "windows", target_os = "vita", target_os = "haiku", target_os = "cygwin", target_os = "wasi", )))] test!(IPv6 recv_tclass_v6, set_recv_tclass_v6(true)); #[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))] test!( #[ignore = "setting `IPV6_TRANSPARENT` requires the `CAP_NET_ADMIN` capability (works when running as root)"] IPv6 ip_transparent_v6, set_ip_transparent_v6(true) ); #[cfg(all( feature = "all", not(any( target_os = "dragonfly", target_os = "fuchsia", target_os = "hurd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "windows", target_os = "vita", target_os = "haiku", target_os = "cygwin", target_os = "wasi", )) ))] test!(IPv6 recv_hoplimit_v6, set_recv_hoplimit_v6(true)); #[cfg(all( feature = "all", any(target_os = "android", target_os = "fuchsia", target_os = "linux") ))] test!( tcp_user_timeout, set_tcp_user_timeout(Some(Duration::from_secs(10))) ); #[cfg(all(feature = "all", target_os = "linux"))] test!(IPv4 multicast_all_v4, set_multicast_all_v4(false)); #[cfg(all(feature = "all", target_os = "linux"))] test!(IPv6 multicast_all_v6, set_multicast_all_v6(false)); #[test] #[cfg(not(any( target_os = "haiku", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", target_os = "vita", target_os = "cygwin", target_os = "wasi", )))] fn join_leave_multicast_v4_n() { let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap(); let multiaddr = Ipv4Addr::new(224, 0, 1, 1); let interface = socket2::InterfaceIndexOrAddress::Index(0); match socket.leave_multicast_v4_n(&multiaddr, &interface) { Ok(()) => panic!("leaving an unjoined group should fail"), Err(err) => { assert_eq!(err.kind(), io::ErrorKind::AddrNotAvailable); #[cfg(unix)] assert_eq!(err.raw_os_error(), Some(libc::EADDRNOTAVAIL)); } }; socket .join_multicast_v4_n(&multiaddr, &interface) .expect("join multicast group"); socket .leave_multicast_v4_n(&multiaddr, &interface) .expect("leave multicast group"); } #[test] #[cfg(not(any( target_os = "dragonfly", target_os = "haiku", target_os = "hurd", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "fuchsia", target_os = "vita", target_os = "wasi", )))] fn join_leave_ssm_v4() { let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap(); let g = Ipv4Addr::new(232, 123, 52, 36); let s = Ipv4Addr::new(62, 40, 109, 31); let interface = Ipv4Addr::new(0, 0, 0, 0); socket.join_ssm_v4(&s, &g, &interface).expect("Joined SSM"); socket.leave_ssm_v4(&s, &g, &interface).expect("Left SSM"); } #[test] #[cfg(all(feature = "all", not(any(target_os = "redox", target_os = "wasi"))))] fn header_included() { let socket = match Socket::new(Domain::IPV4, Type::RAW, None) { Ok(socket) => socket, // Need certain permissions to create a raw sockets. Err(ref err) if err.kind() == io::ErrorKind::PermissionDenied => return, #[cfg(unix)] Err(ref err) if err.raw_os_error() == Some(libc::EPROTONOSUPPORT) => return, Err(err) => panic!("unexpected error creating socket: {err}"), }; let initial = socket .header_included_v4() .expect("failed to get initial value"); assert_eq!(initial, false, "initial value and argument are the same"); socket .set_header_included_v4(true) .expect("failed to set option"); let got = socket.header_included_v4().expect("failed to get value"); assert_eq!(got, true, "set and get values differ"); } #[test] #[cfg(all( feature = "all", not(any( target_os = "redox", target_os = "espidf", target_os = "openbsd", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "wasi", )) ))] fn header_included_ipv6() { let socket = match Socket::new(Domain::IPV6, Type::RAW, None) { Ok(socket) => socket, // Need certain permissions to create a raw sockets. Err(ref err) if err.kind() == io::ErrorKind::PermissionDenied => return, #[cfg(unix)] Err(ref err) if err.raw_os_error() == Some(libc::EPROTONOSUPPORT) => return, Err(err) => panic!("unexpected error creating socket: {err}"), }; let initial = socket .header_included_v6() .expect("failed to get initial value"); assert_eq!(initial, false, "initial value and argument are the same"); socket .set_header_included_v6(true) .expect("failed to set option"); let got = socket.header_included_v6().expect("failed to get value"); assert_eq!(got, true, "set and get values differ"); } #[test] #[cfg(all( feature = "all", any( target_os = "android", target_os = "fuchsia", target_os = "linux", target_os = "windows" ) ))] fn original_dst_v4() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); #[cfg(not(target_os = "windows"))] let expected = Some(libc::ENOENT); #[cfg(target_os = "windows")] let expected = Some(windows_sys::Win32::Networking::WinSock::WSAEINVAL); match socket.original_dst_v4() { Ok(_) => panic!("original_dst_v4 on non-redirected socket should fail"), Err(err) => assert_eq!(err.raw_os_error(), expected), } let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap(); match socket.original_dst_v4() { Ok(_) => panic!("original_dst_v4 on non-redirected socket should fail"), Err(err) => assert_eq!(err.raw_os_error(), expected), } } #[test] #[cfg(all( feature = "all", any(target_os = "android", target_os = "linux", target_os = "windows") ))] fn original_dst_v6() { let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap(); #[cfg(not(target_os = "windows"))] let expected = Some(libc::ENOENT); #[cfg(target_os = "windows")] let expected = Some(windows_sys::Win32::Networking::WinSock::WSAEINVAL); #[cfg(not(target_os = "windows"))] let expected_v4 = Some(libc::EOPNOTSUPP); #[cfg(target_os = "windows")] let expected_v4 = Some(windows_sys::Win32::Networking::WinSock::WSAEINVAL); match socket.original_dst_v6() { Ok(_) => panic!("original_dst_v6 on non-redirected socket should fail"), Err(err) => assert_eq!(err.raw_os_error(), expected), } // Not supported on IPv4 socket. let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); match socket.original_dst_v6() { Ok(_) => panic!("original_dst_v6 on non-redirected socket should fail"), Err(err) => assert_eq!(err.raw_os_error(), expected_v4), } } #[test] #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] fn tcp_congestion() { let socket: Socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); // Get and set current tcp_ca let origin_tcp_ca = socket .tcp_congestion() .expect("failed to get tcp congestion algorithm"); socket .set_tcp_congestion(&origin_tcp_ca) .expect("failed to set tcp congestion algorithm"); // Return a Err when set a non-exist tcp_ca socket .set_tcp_congestion(b"tcp_congestion_does_not_exist") .unwrap_err(); let cur_tcp_ca = socket.tcp_congestion().unwrap(); assert_eq!( cur_tcp_ca, origin_tcp_ca, "expected {origin_tcp_ca:?} but get {cur_tcp_ca:?}" ); let cur_tcp_ca = cur_tcp_ca.splitn(2, |num| *num == 0).next().unwrap(); const OPTIONS: [&str; 2] = [ "cubic", #[cfg(target_os = "linux")] "reno", #[cfg(target_os = "freebsd")] "newreno", ]; // Set a new tcp ca let new_tcp_ca = if cur_tcp_ca == OPTIONS[0].as_bytes() { OPTIONS[1].as_bytes() } else { OPTIONS[0].as_bytes() }; if let Err(e) = socket.set_tcp_congestion(new_tcp_ca) { if e.raw_os_error() == Some(libc::ESRCH) { panic!( "Could not set the {:?} CC protocol. Is the kernel module loaded?", new_tcp_ca ); } else { panic!("set_tcp_congestion: {e}"); } } // Check if new tcp ca is successfully set let cur_tcp_ca = socket.tcp_congestion().unwrap(); assert_eq!( cur_tcp_ca.splitn(2, |num| *num == 0).next().unwrap(), new_tcp_ca, ); } #[test] #[cfg(all(feature = "all", windows))] fn tcp_set_ack_frequency() { let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); match socket.set_tcp_ack_frequency(1) { Ok(_) => {} Err(err) => panic!("set_tcp_ack_frequency on tcp socket should succeed, error: {err}"), } let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)).unwrap(); match socket.set_tcp_ack_frequency(1) { Ok(_) => panic!("set_tcp_ack_frequency on udp socket should fail"), Err(err) => assert_eq!( err.raw_os_error(), Some(windows_sys::Win32::Networking::WinSock::WSAEOPNOTSUPP) ), } } #[test] #[ignore = "DCCP support is not enabled in all kernels of majors Linux distros"] #[cfg(all(feature = "all", target_os = "linux"))] fn dccp() { let listener = Socket::new(Domain::IPV4, Type::DCCP, Some(Protocol::DCCP)).unwrap(); let addr = "127.0.0.1:0".parse::().unwrap().into(); listener.set_dccp_service(45).unwrap(); assert!(listener.dccp_service().unwrap() == 45); assert!(listener.dccp_cur_mps().unwrap() > 0); assert!(listener.dccp_available_ccids::<4>().unwrap().len() >= 3); assert!( listener.dccp_send_cscov().unwrap() == 0, "sender cscov should be zero by default" ); listener.set_dccp_ccid(2).unwrap(); listener.set_dccp_qpolicy_txqlen(6).unwrap(); assert!(listener.dccp_qpolicy_txqlen().unwrap() == 6); listener.bind(&addr).unwrap(); listener.listen(10).unwrap(); let mut client = Socket::new(Domain::IPV4, Type::DCCP, Some(Protocol::DCCP)).unwrap(); client.set_dccp_service(45).unwrap(); client.connect(&addr).unwrap(); let (mut accepted, _) = listener.accept().unwrap(); let msg = "Hello World!"; assert!(client.write(msg.as_bytes()).unwrap() == msg.len()); let mut recv_buf = [0_u8; 64]; assert!(accepted.read(&mut recv_buf).unwrap() == msg.len()); } #[test] #[cfg(all(feature = "all", target_os = "linux"))] fn cookie() { let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); let first_socket_cookie = socket.cookie(); match first_socket_cookie { Ok(_) => {} Err(err) => panic!("Could not get socket cookie, err: {err}"), } //Fetch cookie again and make sure it's the same value (ALWAYS should be, smoke test) let second_socket_cookie = socket.cookie(); match second_socket_cookie { Ok(cookie) => assert_eq!(cookie, first_socket_cookie.unwrap()), Err(err) => panic!("Could not get socket cookie a second time, err: {err}"), } } #[cfg(all(unix, target_os = "linux"))] #[test] fn set_passcred() { let socket = Socket::new(Domain::UNIX, Type::DGRAM, None).unwrap(); assert!(!socket.passcred().unwrap()); socket.set_passcred(true).unwrap(); assert!(socket.passcred().unwrap()); let socket = Socket::new(Domain::UNIX, Type::STREAM, None).unwrap(); assert!(!socket.passcred().unwrap()); socket.set_passcred(true).unwrap(); assert!(socket.passcred().unwrap()); } #[cfg(all(feature = "all", target_os = "linux"))] #[test] fn set_priority() { let socket = Socket::new(Domain::UNIX, Type::DGRAM, None).unwrap(); assert!(socket.priority().unwrap() == 0); // test priorities 6 .. 0; values above 6 require additional privileges for i in (0..=6).rev() { socket.set_priority(i).unwrap(); assert!(socket.priority().unwrap() == i); } } #[cfg(all(feature = "all", target_os = "linux"))] #[test] fn set_busy_poll() { let socket = Socket::new(Domain::UNIX, Type::DGRAM, None).unwrap(); assert!(socket.busy_poll().unwrap() == 0); // test busy poll values 0 .. 6; values above 6 require additional privileges for i in (0..=6).rev() { socket.set_busy_poll(i).unwrap(); assert!(socket.busy_poll().unwrap() == i); } }