diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 4880006c..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,16 +0,0 @@ -environment: - matrix: - - TARGET: x86_64-pc-windows-msvc -install: - - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - rustup-init.exe -y --default-host x86_64-pc-windows-msvc - - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - - if NOT "%TARGET%" == "x86_64-pc-windows-msvc" rustup target add %TARGET% - - rustc -V - - cargo -V - -build: false - -test_script: - - cargo test - - cargo test --features reuseport diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..c82a077e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,77 @@ +name: CI +on: [push, pull_request] + +jobs: + test: + name: Test + runs-on: ${{ matrix.os }} + strategy: + matrix: + build: [stable, beta, nightly, macos, windows] + include: + - build: stable + os: ubuntu-latest + rust: stable + - 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@master + - name: Install Rust (rustup) + run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} + shell: bash + - run: cargo test + - run: cargo test --features reuseport + - run: cargo test --features unix + - run: cargo test --features pair + - run: cargo test --features "reuseport unix pair" + + rustfmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Install Rust + run: rustup update stable && rustup default stable && rustup component add rustfmt + - run: cargo fmt -- --check + + check: + name: Check + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64-unknown-redox] + steps: + - uses: actions/checkout@master + - name: Install Rust + run: rustup update nightly && rustup default nightly + - run: rustup target add ${{ matrix.target }} + - run: cargo check --target ${{ matrix.target }} + continue-on-error: true + + publish_docs: + name: Publish Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Install Rust + run: rustup update stable && rustup default stable + - name: Build documentation + run: cargo doc --no-deps --all-features + - name: Publish documentation + run: | + cd target/doc + git init + git add . + git -c user.name='ci' -c user.email='ci' commit -m init + git push -f -q https://git:${{ secrets.github_token }}@github.com/${{ github.repository }} HEAD:gh-pages + if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' diff --git a/.gitignore b/.gitignore index 39ad86cc..75c73087 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -target/ +/target **/*.rs.bk Cargo.lock diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7375a515..00000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -language: rust -sudo: false - -matrix: - include: - - rust: 1.21.0 - - rust: stable - - os: osx - - rust: beta - - rust: nightly - - - rust: nightly - before_script: - - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH - script: - - cargo doc --no-deps --all-features - after_success: - - travis-cargo --only nightly doc-upload - - - rust: nightly - before_script: - - rustup target add x86_64-unknown-redox --toolchain nightly - script: - - cargo check --target x86_64-unknown-redox - -script: - - cargo test - - cargo test --features "reuseport unix pair" - -env: - global: - secure: "qibsiOrfM/GjYgYFXycSqKMwIK9ZR4cvHZsSqTtqrtxGq5Q7jTwMqdDl8KHDgX1a4it4tGay+7joex8k2zL6OQ+FljQGQq54EDiGw82HWix/fBpOMjMszw+GEDMG/9hUSb6HFdzAKLPAsBRvIs2QteJ60GhL/w4Z/EmfHlVKMnVsYUjfBf5BNlkv8yFvRMY6QqL+F85N7dDQ7JAgdiP79jR7LP8IlCEu/8pgSrf9pSqAHSC1Co1CaN8uhhMlcIIOZ5qYAK4Xty26r2EDzPm5Lw2Bd7a4maN0x+Be2DJvrnX30QkJNNU1XhxYkeZEeUCYAlUhBE5nBHpyyrbAxv+rJodPeyRl5EVpyqi8htPVmcnuA2XpNoHCud7CnzxaFytGvAC5kp0EgS7f3ac4hTnZXCfP0CvnT5UyWfWv9yLwQycdYcAsV4TnKxVAw4ykApGey+h0dyIM2VnzRPOo9D2ZS+JpzPHtx/PXD7aN7IungfTj4PmT+i00QNzkzJR9BqYKmEDBUcz6MLctg4D6xChhN8Go4hvk22F0RVyvEg1MAvXc07EKeWXG/VZ+H2frcPEceMGRBBHiOfOEE/2utNYgvIcmQxd1hvbm3cQOIjeXU2rGneN86cSmx7zNlfOyJUoBfsgGvSEzRxUueibUCaujB/El70HGrMlTnXeERiyd/2Y=" - -notifications: - email: - on_success: never diff --git a/Cargo.toml b/Cargo.toml index f783047f..d8629b65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "socket2" -version = "0.3.8" +version = "0.3.19" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" @@ -10,6 +10,7 @@ description = """ Utilities for handling networking sockets with a maximal amount of configuration possible intended. """ +edition = "2018" [package.metadata.docs.rs] all-features = true @@ -18,12 +19,9 @@ all-features = true version = "0.3.3" features = ["handleapi", "ws2def", "ws2ipdef", "ws2tcpip", "minwindef"] -[target."cfg(any(unix, target_os = \"redox\"))".dependencies] -cfg-if = "0.1" -libc = "0.2.42" - -[target."cfg(target_os = \"redox\")".dependencies] -redox_syscall = "0.1.38" +[target."cfg(unix)".dependencies] +cfg-if = "1.0" +libc = { version = "0.2.66", features = ["align"] } [dev-dependencies] tempdir = "0.3" diff --git a/README.md b/README.md index 2936503f..10d9b1c8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ # socket2-rs -[![Build Status](https://travis-ci.org/alexcrichton/socket2-rs.svg?branch=master)](https://travis-ci.org/alexcrichton/socket2-rs) -[![Build status](https://ci.appveyor.com/api/projects/status/hovebj1gr4bgm3d9?svg=true)](https://ci.appveyor.com/project/alexcrichton/socket2-rs) - [Documentation](https://docs.rs/socket2) # License diff --git a/src/lib.rs b/src/lib.rs index 16e745d4..c0354f12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,77 +37,58 @@ #![doc(html_root_url = "https://docs.rs/socket2/0.3")] #![deny(missing_docs)] -#[cfg(unix)] -#[macro_use] -extern crate cfg_if; -#[cfg(target_os = "redox")] -extern crate cfg_if; -#[cfg(any(unix, target_os = "redox"))] -extern crate libc; -#[cfg(target_os = "redox")] -extern crate syscall; - -#[cfg(windows)] -extern crate winapi; - -#[cfg(test)] -extern crate tempdir; +use crate::utils::NetInt; -use utils::NetInt; - -#[cfg(any(unix, target_os = "redox"))] -use libc::{sockaddr_storage, socklen_t}; -#[cfg(windows)] -use winapi::shared::ws2def::SOCKADDR_STORAGE as sockaddr_storage; -#[cfg(windows)] -use winapi::um::ws2tcpip::socklen_t; +/// 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 `winapi`. + $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) + } + } + }; +} mod sockaddr; mod socket; mod utils; -#[cfg_attr(unix, path = "sys/unix/mod.rs")] -#[cfg_attr(target_os = "redox", path = "sys/redox/mod.rs")] -#[cfg_attr(windows, path = "sys/windows.rs")] +#[cfg(test)] +mod tests; + +#[cfg(unix)] +#[path = "sys/unix.rs"] +mod sys; +#[cfg(windows)] +#[path = "sys/windows.rs"] mod sys; -/// Newtype, 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. -/// -/// # Examples -/// -/// ```no_run -/// use std::net::SocketAddr; -/// use socket2::{Socket, Domain, Type, SockAddr}; -/// -/// // create a TCP listener bound to two addresses -/// let socket = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); -/// -/// socket.bind(&"127.0.0.1:12345".parse::().unwrap().into()).unwrap(); -/// socket.bind(&"127.0.0.1:12346".parse::().unwrap().into()).unwrap(); -/// socket.listen(128).unwrap(); -/// -/// let listener = socket.into_tcp_listener(); -/// // ... -/// ``` -pub struct Socket { - inner: sys::Socket, -} +use sys::c_int; -/// The address of a socket. -/// -/// `SockAddr`s may be constructed directly to and from the standard library -/// `SocketAddr`, `SocketAddrV4`, and `SocketAddrV6` types. -pub struct SockAddr { - storage: sockaddr_storage, - len: socklen_t, -} +pub use sockaddr::SockAddr; +pub use socket::Socket; /// Specification of the communication domain for a socket. /// @@ -116,10 +97,34 @@ pub struct SockAddr { /// such as `Domain::ipv4`, `Domain::ipv6`, etc, are provided to avoid reaching /// into libc for various constants. /// -/// This type is freely interconvertible with the `i32` type, however, if a raw +/// This type is freely interconvertible with C's `int` type, however, if a raw /// value needs to be provided. #[derive(Copy, Clone)] -pub struct Domain(i32); +pub struct Domain(c_int); + +impl Domain { + /// Domain for IPv4 communication, corresponding to `AF_INET`. + pub fn ipv4() -> Domain { + Domain(sys::AF_INET) + } + + /// Domain for IPv6 communication, corresponding to `AF_INET6`. + pub fn ipv6() -> Domain { + Domain(sys::AF_INET6) + } +} + +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. /// @@ -128,26 +133,98 @@ pub struct Domain(i32); /// such as `Type::stream`, `Type::dgram`, etc, are provided to avoid reaching /// into libc for various constants. /// -/// This type is freely interconvertible with the `i32` type, however, if a raw +/// This type is freely interconvertible with C's `int` type, however, if a raw /// value needs to be provided. #[derive(Copy, Clone)] -pub struct Type(i32); +pub struct Type(c_int); + +impl Type { + /// Type corresponding to `SOCK_STREAM`. + /// + /// Used for protocols such as TCP. + pub fn stream() -> Type { + Type(sys::SOCK_STREAM) + } + + /// Type corresponding to `SOCK_DGRAM`. + /// + /// Used for protocols such as UDP. + pub fn dgram() -> Type { + Type(sys::SOCK_DGRAM) + } + + /// Type corresponding to `SOCK_SEQPACKET`. + pub fn seqpacket() -> Type { + Type(sys::SOCK_SEQPACKET) + } + + /// Type corresponding to `SOCK_RAW`. + #[cfg(not(target_os = "redox"))] + pub fn 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 the `i32` type, however, if a raw +/// This type is freely interconvertible with C's `int` type, however, if a raw /// value needs to be provided. #[derive(Copy, Clone)] -pub struct Protocol(i32); +pub struct Protocol(c_int); + +impl Protocol { + /// Protocol corresponding to `ICMPv4`. + pub fn icmpv4() -> Self { + Protocol(sys::IPPROTO_ICMP) + } + + /// Protocol corresponding to `ICMPv6`. + pub fn icmpv6() -> Self { + Protocol(sys::IPPROTO_ICMPV6) + } + + /// Protocol corresponding to `TCP`. + pub fn tcp() -> Self { + Protocol(sys::IPPROTO_TCP) + } + + /// Protocol corresponding to `UDP`. + pub fn udp() -> Self { + Protocol(sys::IPPROTO_UDP) + } +} + +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 + } +} fn hton(i: I) -> I { i.to_be() } -#[cfg(not(target_os = "redox"))] fn ntoh(i: I) -> I { I::from_be(i) } diff --git a/src/sockaddr.rs b/src/sockaddr.rs index 5e335db9..dd8ffc01 100644 --- a/src/sockaddr.rs +++ b/src/sockaddr.rs @@ -1,21 +1,35 @@ use std::fmt; -use std::mem; -use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::mem::{self, MaybeUninit}; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::ptr; #[cfg(any(unix, target_os = "redox"))] -use libc::{sa_family_t, sockaddr, sockaddr_in, sockaddr_storage, socklen_t, AF_INET6, - sockaddr_in6, AF_INET}; +use libc::{ + in6_addr, in_addr, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6, sockaddr_storage, + socklen_t, AF_INET, AF_INET6, +}; #[cfg(windows)] -use winapi::shared::ws2def::{ADDRESS_FAMILY as sa_family_t, AF_INET6, SOCKADDR as sockaddr, - SOCKADDR_IN as sockaddr_in, SOCKADDR_STORAGE as sockaddr_storage, - AF_INET}; +use winapi::shared::in6addr::{in6_addr_u, IN6_ADDR as in6_addr}; #[cfg(windows)] -use winapi::um::ws2tcpip::socklen_t; +use winapi::shared::inaddr::{in_addr_S_un, IN_ADDR as in_addr}; +#[cfg(windows)] +use winapi::shared::ws2def::{ + ADDRESS_FAMILY as sa_family_t, AF_INET, AF_INET6, SOCKADDR as sockaddr, + SOCKADDR_IN as sockaddr_in, SOCKADDR_STORAGE as sockaddr_storage, +}; #[cfg(windows)] -use winapi::shared::ws2ipdef::SOCKADDR_IN6_LH as sockaddr_in6; +use winapi::shared::ws2ipdef::{SOCKADDR_IN6_LH_u, SOCKADDR_IN6_LH as sockaddr_in6}; +#[cfg(windows)] +use winapi::um::ws2tcpip::socklen_t; -use SockAddr; +/// The address of a socket. +/// +/// `SockAddr`s may be constructed directly to and from the standard library +/// `SocketAddr`, `SocketAddrV4`, and `SocketAddrV6` types. +pub struct SockAddr { + storage: sockaddr_storage, + len: socklen_t, +} impl fmt::Debug for SockAddr { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { @@ -33,16 +47,17 @@ impl fmt::Debug for SockAddr { impl SockAddr { /// Constructs a `SockAddr` from its raw components. pub unsafe fn from_raw_parts(addr: *const sockaddr, len: socklen_t) -> SockAddr { - let mut storage = mem::uninitialized::(); + let mut storage = MaybeUninit::::zeroed(); ptr::copy_nonoverlapping( addr as *const _ as *const u8, - &mut storage as *mut _ as *mut u8, + storage.as_mut_ptr() as *mut u8, len as usize, ); SockAddr { - storage: storage, - len: len, + // This is safe as we written the address to `storage` above. + storage: storage.assume_init(), + len, } } @@ -59,10 +74,10 @@ impl SockAddr { where P: AsRef<::std::path::Path>, { + use libc::{c_char, sockaddr_un, AF_UNIX}; use std::cmp::Ordering; use std::io; use std::os::unix::ffi::OsStrExt; - use libc::{c_char, sockaddr_un, AF_UNIX}; unsafe { let mut addr = mem::zeroed::(); @@ -109,24 +124,63 @@ impl SockAddr { } } - unsafe fn as_(&self, family: sa_family_t) -> Option { - if self.storage.ss_family != family { - return None; - } - - Some(mem::transmute_copy(&self.storage)) - } - /// Returns this address as a `SocketAddrV4` if it is in the `AF_INET` /// family. pub fn as_inet(&self) -> Option { - unsafe { self.as_(AF_INET as sa_family_t) } + match self.as_std() { + Some(SocketAddr::V4(addr)) => Some(addr), + _ => None, + } } - /// Returns this address as a `SocketAddrV4` if it is in the `AF_INET6` + /// Returns this address as a `SocketAddrV6` if it is in the `AF_INET6` /// family. pub fn as_inet6(&self) -> Option { - unsafe { self.as_(AF_INET6 as sa_family_t) } + match self.as_std() { + Some(SocketAddr::V6(addr)) => Some(addr), + _ => None, + } + } + + /// Returns this address as a `SocketAddr` if it is in the `AF_INET` + /// or `AF_INET6` family, otherwise returns `None`. + pub fn as_std(&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 { &*(&self.storage as *const _ as *const sockaddr_in) }; + + #[cfg(unix)] + let ip = Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()); + #[cfg(windows)] + let ip = { + let ip_bytes = unsafe { addr.sin_addr.S_un.S_un_b() }; + Ipv4Addr::from([ip_bytes.s_b1, ip_bytes.s_b2, ip_bytes.s_b3, ip_bytes.s_b4]) + }; + 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 { &*(&self.storage as *const _ as *const sockaddr_in6) }; + + #[cfg(unix)] + let ip = Ipv6Addr::from(addr.sin6_addr.s6_addr); + #[cfg(windows)] + let ip = Ipv6Addr::from(*unsafe { addr.sin6_addr.u.Byte() }); + let port = u16::from_be(addr.sin6_port); + Some(SocketAddr::V6(SocketAddrV6::new( + ip, + port, + addr.sin6_flowinfo, + #[cfg(unix)] + addr.sin6_scope_id, + #[cfg(windows)] + unsafe { + *addr.u.sin6_scope_id() + }, + ))) + } else { + None + } } /// Returns this address's family. @@ -145,34 +199,92 @@ impl SockAddr { } } -// SocketAddrV4 and SocketAddrV6 are just wrappers around sockaddr_in and sockaddr_in6 - -// check to make sure that the sizes at least match up -fn _size_checks(v4: SocketAddrV4, v6: SocketAddrV6) { - unsafe { - mem::transmute::(v4); - mem::transmute::(v6); - } -} - impl From for SockAddr { fn from(addr: SocketAddrV4) -> SockAddr { - unsafe { - SockAddr::from_raw_parts( - &addr as *const _ as *const _, - mem::size_of::() as socklen_t, - ) + #[cfg(unix)] + let sin_addr = in_addr { + s_addr: u32::from_ne_bytes(addr.ip().octets()), + }; + #[cfg(windows)] + let sin_addr = unsafe { + let mut s_un = mem::zeroed::(); + *s_un.S_addr_mut() = u32::from_ne_bytes(addr.ip().octets()); + in_addr { S_un: s_un } + }; + + let sockaddr_in = sockaddr_in { + sin_family: AF_INET as sa_family_t, + sin_port: addr.port().to_be(), + sin_addr, + sin_zero: Default::default(), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + sin_len: 0, + }; + let mut storage = MaybeUninit::::zeroed(); + // Safety: A `sockaddr_in` is memory compatible with a `sockaddr_storage` + unsafe { (storage.as_mut_ptr() as *mut sockaddr_in).write(sockaddr_in) }; + SockAddr { + storage: unsafe { storage.assume_init() }, + len: mem::size_of::() as socklen_t, } } } impl From for SockAddr { fn from(addr: SocketAddrV6) -> SockAddr { - unsafe { - SockAddr::from_raw_parts( - &addr as *const _ as *const _, - mem::size_of::() as socklen_t, - ) + #[cfg(unix)] + let sin6_addr = in6_addr { + s6_addr: addr.ip().octets(), + }; + #[cfg(windows)] + let sin6_addr = unsafe { + let mut u = mem::zeroed::(); + *u.Byte_mut() = addr.ip().octets(); + in6_addr { u } + }; + #[cfg(windows)] + let u = unsafe { + let mut u = mem::zeroed::(); + *u.sin6_scope_id_mut() = addr.scope_id(); + u + }; + + let sockaddr_in6 = sockaddr_in6 { + sin6_family: AF_INET6 as sa_family_t, + sin6_port: addr.port().to_be(), + sin6_addr, + sin6_flowinfo: addr.flowinfo(), + #[cfg(unix)] + sin6_scope_id: addr.scope_id(), + #[cfg(windows)] + u, + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + sin6_len: 0, + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + __sin6_src_id: 0, + }; + let mut storage = MaybeUninit::::zeroed(); + // Safety: A `sockaddr_in6` is memory compatible with a `sockaddr_storage` + unsafe { (storage.as_mut_ptr() as *mut sockaddr_in6).write(sockaddr_in6) }; + SockAddr { + storage: unsafe { storage.assume_init() }, + len: mem::size_of::() as socklen_t, } } } diff --git a/src/socket.rs b/src/socket.rs index b3297d66..1f1fcc92 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -8,20 +8,52 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#[cfg(target_os = "linux")] +use std::ffi::{CStr, CString}; use std::fmt; use std::io::{self, Read, Write}; use std::net::{self, Ipv4Addr, Ipv6Addr, Shutdown}; -use std::time::Duration; #[cfg(all(unix, feature = "unix"))] use std::os::unix::net::{UnixDatagram, UnixListener, UnixStream}; +use std::time::Duration; #[cfg(any(unix, target_os = "redox"))] -use libc as c; +use libc::MSG_OOB; #[cfg(windows)] -use winapi::shared::ws2def as c; - -use sys; -use {Domain, Protocol, SockAddr, Socket, Type}; +use winapi::um::winsock2::MSG_OOB; + +use crate::sys; +use crate::{Domain, Protocol, SockAddr, Type}; + +/// Newtype, 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. +/// +/// # Examples +/// +/// ```no_run +/// use std::net::SocketAddr; +/// use socket2::{Socket, Domain, Type, SockAddr}; +/// +/// // create a TCP listener bound to two addresses +/// let socket = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); +/// +/// socket.bind(&"127.0.0.1:12345".parse::().unwrap().into()).unwrap(); +/// socket.bind(&"127.0.0.1:12346".parse::().unwrap().into()).unwrap(); +/// socket.listen(128).unwrap(); +/// +/// let listener = socket.into_tcp_listener(); +/// // ... +/// ``` +pub struct Socket { + // The `sys` module most have access to the socket. + pub(crate) inner: sys::Socket, +} impl Socket { /// Creates a new socket ready to be configured. @@ -213,7 +245,26 @@ impl Socket { /// /// [`connect`]: #method.connect pub fn recv(&self, buf: &mut [u8]) -> io::Result { - self.inner.recv(buf) + self.inner.recv(buf, 0) + } + + /// Identical to [`recv`] but allows for specification of arbitrary flags to the underlying + /// `recv` call. + /// + /// [`recv`]: #method.recv + pub fn recv_with_flags(&self, buf: &mut [u8], flags: i32) -> io::Result { + self.inner.recv(buf, flags) + } + + /// 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`]: #method.recv + /// [`out_of_band_inline`]: #method.out_of_band_inline + pub fn recv_out_of_band(&self, buf: &mut [u8]) -> io::Result { + self.inner.recv(buf, MSG_OOB) } /// Receives data on the socket from the remote adress to which it is @@ -229,7 +280,19 @@ impl Socket { /// Receives data from the socket. On success, returns the number of bytes /// read and the address from whence the data came. pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SockAddr)> { - self.inner.recv_from(buf) + self.inner.recv_from(buf, 0) + } + + /// Identical to [`recv_from`] but allows for specification of arbitrary flags to the underlying + /// `recvfrom` call. + /// + /// [`recv_from`]: #method.recv_from + pub fn recv_from_with_flags( + &self, + buf: &mut [u8], + flags: i32, + ) -> io::Result<(usize, SockAddr)> { + self.inner.recv_from(buf, flags) } /// Receives data from the socket, without removing it from the queue. @@ -250,7 +313,26 @@ impl Socket { /// /// On success returns the number of bytes that were sent. pub fn send(&self, buf: &[u8]) -> io::Result { - self.inner.send(buf) + self.inner.send(buf, 0) + } + + /// Identical to [`send`] but allows for specification of arbitrary flags to the underlying + /// `send` call. + /// + /// [`send`]: #method.send + pub fn send_with_flags(&self, buf: &[u8], flags: i32) -> io::Result { + self.inner.send(buf, 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`]: #method.send + /// [`out_of_band_inline`]: #method.out_of_band_inline + pub fn send_out_of_band(&self, buf: &[u8]) -> io::Result { + self.inner.send(buf, MSG_OOB) } /// Sends data on the socket to the given address. On success, returns the @@ -259,7 +341,15 @@ impl Socket { /// This is typically used on UDP or datagram-oriented sockets. On success /// returns the number of bytes that were sent. pub fn send_to(&self, buf: &[u8], addr: &SockAddr) -> io::Result { - self.inner.send_to(buf, addr) + self.inner.send_to(buf, 0, addr) + } + + /// Identical to [`send_to`] but allows for specification of arbitrary flags to the underlying + /// `sendto` call. + /// + /// [`send_to`]: #method.send_to + pub fn send_to_with_flags(&self, buf: &[u8], addr: &SockAddr, flags: i32) -> io::Result { + self.inner.send_to(buf, flags, addr) } // ================================================ @@ -281,6 +371,73 @@ impl Socket { self.inner.set_ttl(ttl) } + /// Gets 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(unix, not(target_os = "redox")))] + pub fn mss(&self) -> io::Result { + self.inner.mss() + } + + /// 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(unix, not(target_os = "redox")))] + pub fn set_mss(&self, mss: u32) -> io::Result<()> { + self.inner.set_mss(mss) + } + + /// 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. + /// + /// This function is only available on Linux and requires the + /// `CAP_NET_ADMIN` capability. + #[cfg(target_os = "linux")] + pub fn mark(&self) -> io::Result { + self.inner.mark() + } + + /// 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. + /// + /// This function is only available on Linux and requires the + /// `CAP_NET_ADMIN` capability. + #[cfg(target_os = "linux")] + pub fn set_mark(&self, mark: u32) -> io::Result<()> { + self.inner.set_mark(mark) + } + + /// Gets the value for the `SO_BINDTODEVICE` option on this socket. + /// + /// This value gets the socket binded device's interface name. + /// + /// This function is only available on Linux. + #[cfg(target_os = "linux")] + pub fn device(&self) -> io::Result> { + self.inner.device() + } + + /// 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. + /// + /// This function is only available on Linux. + #[cfg(target_os = "linux")] + pub fn bind_device(&self, interface: Option<&CStr>) -> io::Result<()> { + self.inner.bind_device(interface) + } + /// Gets the value of the `IPV6_UNICAST_HOPS` option for this socket. /// /// Specifies the hop limit for ipv6 unicast packets @@ -628,24 +785,50 @@ impl Socket { self.inner.set_keepalive(keepalive) } + /// Returns the value of the `SO_OOBINLINE` flag of the underlying socket. + /// For more information about this option, see [`set_out_of_band_inline`][link]. + /// + /// [link]: #method.set_out_of_band_inline + pub fn out_of_band_inline(&self) -> io::Result { + self.inner.out_of_band_inline() + } + + /// Sets the `SO_OOBINLINE` flag of the underlying socket. + /// as per RFC6093, TCP sockets using the Urgent mechanism + /// are encouraged to set this flag. + /// + /// If this flag is not set, the `MSG_OOB` flag is needed + /// while `recv`ing to aquire the out-of-band data. + pub fn set_out_of_band_inline(&self, oob_inline: bool) -> io::Result<()> { + self.inner.set_out_of_band_inline(oob_inline) + } + /// Check the value of the `SO_REUSEPORT` option on this socket. /// /// This function is only available on Unix when the `reuseport` feature is /// enabled. - #[cfg(all(unix, feature = "reuseport"))] + #[cfg(all( + unix, + not(any(target_os = "solaris", target_os = "illumos")), + feature = "reuseport" + ))] pub fn reuse_port(&self) -> io::Result { self.inner.reuse_port() } /// Set value for the `SO_REUSEPORT` option on this socket. /// - /// This indicates that futher calls to `bind` may allow reuse of local + /// 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. /// /// This function is only available on Unix when the `reuseport` feature is /// enabled. - #[cfg(all(unix, feature = "reuseport"))] + #[cfg(all( + unix, + not(any(target_os = "solaris", target_os = "illumos")), + feature = "reuseport" + ))] pub fn set_reuse_port(&self, reuse: bool) -> io::Result<()> { self.inner.set_reuse_port(reuse) } @@ -779,111 +962,6 @@ impl From for UnixDatagram { } } -impl Domain { - /// Domain for IPv4 communication, corresponding to `AF_INET`. - pub fn ipv4() -> Domain { - Domain(c::AF_INET) - } - - /// Domain for IPv6 communication, corresponding to `AF_INET6`. - pub fn ipv6() -> Domain { - Domain(c::AF_INET6) - } - - /// Domain for Unix socket communication, corresponding to `AF_UNIX`. - /// - /// This function is only available on Unix when the `unix` feature is - /// activated. - #[cfg(all(unix, feature = "unix"))] - pub fn unix() -> Domain { - Domain(c::AF_UNIX) - } -} - -impl From for Domain { - fn from(a: i32) -> Domain { - Domain(a) - } -} - -impl From for i32 { - fn from(a: Domain) -> i32 { - a.0 - } -} - -impl Type { - /// Type corresponding to `SOCK_STREAM` - /// - /// Used for protocols such as TCP. - pub fn stream() -> Type { - Type(c::SOCK_STREAM) - } - - /// Type corresponding to `SOCK_DGRAM` - /// - /// Used for protocols such as UDP. - pub fn dgram() -> Type { - Type(c::SOCK_DGRAM) - } - - /// Type corresponding to `SOCK_SEQPACKET` - pub fn seqpacket() -> Type { - Type(sys::SOCK_SEQPACKET) - } - - /// Type corresponding to `SOCK_RAW` - pub fn raw() -> Type { - Type(sys::SOCK_RAW) - } -} - -impl ::Protocol { - /// Protocol corresponding to `ICMPv4` - pub fn icmpv4() -> Self { - ::Protocol(sys::IPPROTO_ICMP) - } - - /// Protocol corresponding to `ICMPv6` - pub fn icmpv6() -> Self { - ::Protocol(sys::IPPROTO_ICMPV6) - } - - /// Protocol corresponding to `TCP` - pub fn tcp() -> Self { - ::Protocol(sys::IPPROTO_TCP) - } - - /// Protocol corresponding to `UDP` - pub fn udp() -> Self { - ::Protocol(sys::IPPROTO_UDP) - } -} - -impl From for Type { - fn from(a: i32) -> Type { - Type(a) - } -} - -impl From for i32 { - fn from(a: Type) -> i32 { - a.0 - } -} - -impl From for Protocol { - fn from(a: i32) -> Protocol { - Protocol(a) - } -} - -impl From for i32 { - fn from(a: Protocol) -> i32 { - a.0 - } -} - #[cfg(test)] mod test { use std::net::SocketAddr; @@ -982,4 +1060,69 @@ mod test { #[cfg(unix)] assert_eq!(socket.keepalive().unwrap(), None); } + + #[test] + fn nodelay() { + let socket = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); + + assert!(socket.set_nodelay(true).is_ok()); + + let result = socket.nodelay(); + + assert!(result.is_ok()); + assert!(result.unwrap()); + } + + #[test] + fn out_of_band_inline() { + let socket = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); + + assert_eq!(socket.out_of_band_inline().unwrap(), false); + + socket.set_out_of_band_inline(true).unwrap(); + assert_eq!(socket.out_of_band_inline().unwrap(), true); + } + + #[test] + #[cfg(any(target_os = "windows", target_os = "linux"))] + fn out_of_band_send_recv() { + let s1 = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); + s1.bind(&"127.0.0.1:0".parse::().unwrap().into()) + .unwrap(); + let s1_addr = s1.local_addr().unwrap(); + s1.listen(1).unwrap(); + + let s2 = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); + s2.connect(&s1_addr).unwrap(); + + let (s3, _) = s1.accept().unwrap(); + + let mut buf = [0; 10]; + // send some plain inband data + s2.send(&mut buf).unwrap(); + // send a single out of band byte + assert_eq!(s2.send_out_of_band(&mut [b"!"[0]]).unwrap(), 1); + // recv the OOB data first + assert_eq!(s3.recv_out_of_band(&mut buf).unwrap(), 1); + assert_eq!(buf[0], b"!"[0]); + assert_eq!(s3.recv(&mut buf).unwrap(), 10); + } + + #[test] + fn tcp() { + let s1 = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); + s1.bind(&"127.0.0.1:0".parse::().unwrap().into()) + .unwrap(); + let s1_addr = s1.local_addr().unwrap(); + s1.listen(1).unwrap(); + + let s2 = Socket::new(Domain::ipv4(), Type::stream(), None).unwrap(); + s2.connect(&s1_addr).unwrap(); + + let (s3, _) = s1.accept().unwrap(); + + let mut buf = [0; 11]; + assert_eq!(s2.send(&mut buf).unwrap(), 11); + assert_eq!(s3.recv(&mut buf).unwrap(), 11); + } } diff --git a/src/sys/redox/mod.rs b/src/sys/redox/mod.rs deleted file mode 100644 index 3882fc99..00000000 --- a/src/sys/redox/mod.rs +++ /dev/null @@ -1,832 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// 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; -use std::fmt; -use std::io::{ErrorKind, Read, Write}; -use std::io; -use std::mem; -use std::net::Shutdown; -use std::net::{self, Ipv4Addr, Ipv6Addr}; -use std::ops::Neg; -use std::os::unix::prelude::*; -use std::time::Duration; -use syscall; -use std::fs::File; - -use libc::{self, c_uint, c_int, c_void, socklen_t, ssize_t}; - -use libc::IPV6_ADD_MEMBERSHIP; -use libc::IPV6_DROP_MEMBERSHIP; - -const MSG_NOSIGNAL: c_int = 0x0; - -use libc::TCP_KEEPIDLE as KEEPALIVE_OPTION; - -use SockAddr; -use utils::One; - -pub const IPPROTO_TCP: i32 = libc::IPPROTO_TCP; - -pub const IPPROTO_ICMP: i32 = -1; -pub const IPPROTO_ICMPV6: i32 = -1; -pub const IPPROTO_UDP: i32 = -1; -pub const SOCK_RAW: i32 = -1; -pub const SOCK_SEQPACKET: i32 = -1; - -pub struct Socket { - fd: c_int, -} - -impl Socket { - pub fn new(family: c_int, ty: c_int, protocol: c_int) -> io::Result { - if ty == -1 { - return Err(io::Error::new(ErrorKind::Other, "Type not implemented yet")); - } - if protocol == -1 { - return Err(io::Error::new(ErrorKind::Other, "Protocol not implemented yet")); - } - unsafe { - let fd = cvt(libc::socket(family, ty, protocol))?; - let fd = Socket::from_raw_fd(fd as RawFd); - set_cloexec(fd.as_raw_fd() as c_int)?; - Ok(fd) - } - } - - pub fn pair(_family: c_int, _ty: c_int, _protocol: c_int) -> io::Result<(Socket, Socket)> { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn bind(&self, addr: &SockAddr) -> io::Result<()> { - unsafe { cvt(libc::bind(self.fd, addr.as_ptr(), addr.len() as _)).map(|_| ()) } - } - - pub fn listen(&self, backlog: i32) -> io::Result<()> { - unsafe { cvt(libc::listen(self.fd, backlog)).map(|_| ()) } - } - - pub fn connect(&self, addr: &SockAddr) -> io::Result<()> { - unsafe { cvt(libc::connect(self.fd, addr.as_ptr(), addr.len())).map(|_| ()) } - } - - pub fn connect_timeout(&self, addr: &SockAddr, timeout: Duration) -> io::Result<()> { - if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 { - return Err(io::Error::new( - ErrorKind::InvalidInput, - "cannot set a 0 duration timeout", - )); - } - if timeout.as_secs() > ::std::i64::MAX as u64 { - return Err(io::Error::new( - ErrorKind::InvalidInput, - "too large duration", - )); - } - - self.connect(addr)?; - - let mut event = File::open("event:")?; - let mut time = File::open("time:")?; - - event.write(&syscall::Event { - id: self.fd as usize, - flags: syscall::EVENT_WRITE, - data: 0 - })?; - - event.write(&syscall::Event { - id: time.as_raw_fd(), - flags: syscall::EVENT_WRITE, - data: 1 - })?; - - let mut current = syscall::TimeSpec::default(); - time.read(&mut current)?; - current.tv_sec += timeout.as_secs() as i64; - current.tv_nsec += timeout.subsec_nanos() as i32; - time.write(¤t)?; - - let mut out = syscall::Event::default(); - event.read(&mut out)?; - - if out.data == 1 { // the timeout we registered - return Err(io::Error::new( - ErrorKind::TimedOut, - "connection timed out", - )); - } - - Ok(()) - } - - pub fn local_addr(&self) -> io::Result { - unsafe { - let mut storage: libc::sockaddr_storage = mem::zeroed(); - let mut len = mem::size_of_val(&storage) as libc::socklen_t; - cvt(libc::getsockname( - self.fd, - &mut storage as *mut _ as *mut _, - &mut len, - ))?; - Ok(SockAddr::from_raw_parts( - &storage as *const _ as *const _, - len, - )) - } - } - - pub fn peer_addr(&self) -> io::Result { - unsafe { - let mut storage: libc::sockaddr_storage = mem::zeroed(); - let mut len = mem::size_of_val(&storage) as libc::socklen_t; - cvt(libc::getpeername( - self.fd, - &mut storage as *mut _ as *mut _, - &mut len - ))?; - Ok(SockAddr::from_raw_parts( - &storage as *const _ as *const _, - len as c_uint, - )) - } - } - - pub fn try_clone(&self) -> io::Result { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - #[allow(unused_mut)] - pub fn accept(&self) -> io::Result<(Socket, SockAddr)> { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn take_error(&self) -> io::Result> { - unsafe { - let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_ERROR)?; - if raw == 0 { - Ok(None) - } else { - Ok(Some(io::Error::from_raw_os_error(raw as i32))) - } - } - } - - pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { - unsafe { - let previous = cvt(libc::fcntl(self.fd, libc::F_GETFL))?; - let new = if nonblocking { - previous | libc::O_NONBLOCK - } else { - previous & !libc::O_NONBLOCK - }; - if new != previous { - cvt(libc::fcntl(self.fd, libc::F_SETFL, new))?; - } - Ok(()) - } - } - - pub fn shutdown(&self, _how: Shutdown) -> io::Result<()> { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn recv(&self, buf: &mut [u8]) -> io::Result { - unsafe { - let n = cvt({ - libc::recv( - self.fd, - buf.as_mut_ptr() as *mut c_void, - cmp::min(buf.len(), max_len()), - 0, - ) - })?; - Ok(n as usize) - } - } - - pub fn peek(&self, _buf: &mut [u8]) -> io::Result { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SockAddr)> { - self.recvfrom(buf, 0) - } - - pub fn peek_from(&self, _buf: &mut [u8]) -> io::Result<(usize, SockAddr)> { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - fn recvfrom(&self, buf: &mut [u8], flags: c_int) -> io::Result<(usize, SockAddr)> { - unsafe { - let mut storage: libc::sockaddr_storage = mem::zeroed(); - let mut addrlen = mem::size_of_val(&storage) as socklen_t; - - let n = cvt({ - libc::recvfrom( - self.fd, - buf.as_mut_ptr() as *mut c_void, - cmp::min(buf.len(), max_len()), - flags, - &mut storage as *mut _ as *mut _, - &mut addrlen, - ) - })?; - let addr = SockAddr::from_raw_parts(&storage as *const _ as *const _, addrlen); - Ok((n as usize, addr)) - } - } - - pub fn send(&self, buf: &[u8]) -> io::Result { - unsafe { - let n = cvt({ - libc::send( - self.fd, - buf.as_ptr() as *const c_void, - cmp::min(buf.len(), max_len()), - MSG_NOSIGNAL, - ) - })?; - Ok(n as usize) - } - } - - pub fn send_to(&self, buf: &[u8], addr: &SockAddr) -> io::Result { - unsafe { - let n = cvt({ - libc::sendto( - self.fd, - buf.as_ptr() as *const c_void, - cmp::min(buf.len(), max_len()), - MSG_NOSIGNAL, - addr.as_ptr(), - addr.len(), - ) - })?; - Ok(n as usize) - } - } - - // ================================================ - - pub fn ttl(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::IPPROTO_IP, libc::IP_TTL)?; - Ok(raw as u32) - } - } - - pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { - unsafe { self.setsockopt(libc::IPPROTO_IP, libc::IP_TTL, ttl as c_int) } - } - - pub fn unicast_hops_v6(&self) -> io::Result { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn set_unicast_hops_v6(&self, _hops: u32) -> io::Result<()> { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn only_v6(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::IPPROTO_IPV6, libc::IPV6_V6ONLY)?; - Ok(raw != 0) - } - } - - pub fn set_only_v6(&self, only_v6: bool) -> io::Result<()> { - unsafe { self.setsockopt(libc::IPPROTO_IPV6, libc::IPV6_V6ONLY, only_v6 as c_int) } - } - - pub fn read_timeout(&self) -> io::Result> { - unsafe { - Ok(timeval2dur(self.getsockopt( - libc::SOL_SOCKET, - libc::SO_RCVTIMEO, - )?)) - } - } - - pub fn set_read_timeout(&self, dur: Option) -> io::Result<()> { - unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_RCVTIMEO, dur2timeval(dur)?) } - } - - pub fn write_timeout(&self) -> io::Result> { - unsafe { - Ok(timeval2dur(self.getsockopt( - libc::SOL_SOCKET, - libc::SO_SNDTIMEO, - )?)) - } - } - - pub fn set_write_timeout(&self, dur: Option) -> io::Result<()> { - unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_SNDTIMEO, dur2timeval(dur)?) } - } - - pub fn nodelay(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::IPPROTO_TCP, libc::TCP_NODELAY)?; - Ok(raw != 0) - } - } - - pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { - unsafe { self.setsockopt(libc::IPPROTO_TCP, libc::TCP_NODELAY, nodelay as c_int) } - } - - pub fn broadcast(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_BROADCAST)?; - Ok(raw != 0) - } - } - - pub fn set_broadcast(&self, broadcast: bool) -> io::Result<()> { - unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_BROADCAST, broadcast as c_int) } - } - - pub fn multicast_loop_v4(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::IPPROTO_IP, libc::IP_MULTICAST_LOOP)?; - Ok(raw != 0) - } - } - - pub fn set_multicast_loop_v4(&self, multicast_loop_v4: bool) -> io::Result<()> { - unsafe { - self.setsockopt( - libc::IPPROTO_IP, - libc::IP_MULTICAST_LOOP, - multicast_loop_v4 as c_int, - ) - } - } - - pub fn multicast_ttl_v4(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::IPPROTO_IP, libc::IP_MULTICAST_TTL)?; - Ok(raw as u32) - } - } - - pub fn set_multicast_ttl_v4(&self, multicast_ttl_v4: u32) -> io::Result<()> { - unsafe { - self.setsockopt( - libc::IPPROTO_IP, - libc::IP_MULTICAST_TTL, - multicast_ttl_v4 as c_int, - ) - } - } - - pub fn multicast_hops_v6(&self) -> io::Result { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn set_multicast_hops_v6(&self, _hops: u32) -> io::Result<()> { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn multicast_if_v4(&self) -> io::Result { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn set_multicast_if_v4(&self, _interface: &Ipv4Addr) -> io::Result<()> { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn multicast_if_v6(&self) -> io::Result { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn set_multicast_if_v6(&self, _interface: u32) -> io::Result<()> { - return Err(io::Error::new(ErrorKind::Other, "Not implemented yet")); - } - - pub fn multicast_loop_v6(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::IPPROTO_IPV6, libc::IPV6_MULTICAST_LOOP)?; - Ok(raw != 0) - } - } - - pub fn set_multicast_loop_v6(&self, multicast_loop_v6: bool) -> io::Result<()> { - unsafe { - self.setsockopt( - libc::IPPROTO_IPV6, - libc::IPV6_MULTICAST_LOOP, - multicast_loop_v6 as c_int, - ) - } - } - - pub fn join_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { - let multiaddr = to_s_addr(multiaddr); - let interface = to_s_addr(interface); - let mreq = libc::ip_mreq { - imr_multiaddr: libc::in_addr { s_addr: multiaddr }, - imr_interface: libc::in_addr { s_addr: interface }, - }; - unsafe { self.setsockopt(libc::IPPROTO_IP, libc::IP_ADD_MEMBERSHIP, mreq) } - } - - pub fn join_multicast_v6(&self, multiaddr: &Ipv6Addr, interface: u32) -> io::Result<()> { - let multiaddr = to_in6_addr(multiaddr); - let mreq = libc::ipv6_mreq { - ipv6mr_multiaddr: multiaddr, - ipv6mr_interface: to_ipv6mr_interface(interface), - }; - unsafe { self.setsockopt(libc::IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq) } - } - - pub fn leave_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { - let multiaddr = to_s_addr(multiaddr); - let interface = to_s_addr(interface); - let mreq = libc::ip_mreq { - imr_multiaddr: libc::in_addr { s_addr: multiaddr }, - imr_interface: libc::in_addr { s_addr: interface }, - }; - unsafe { self.setsockopt(libc::IPPROTO_IP, libc::IP_DROP_MEMBERSHIP, mreq) } - } - - pub fn leave_multicast_v6(&self, multiaddr: &Ipv6Addr, interface: u32) -> io::Result<()> { - let multiaddr = to_in6_addr(multiaddr); - let mreq = libc::ipv6_mreq { - ipv6mr_multiaddr: multiaddr, - ipv6mr_interface: to_ipv6mr_interface(interface), - }; - unsafe { self.setsockopt(libc::IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, mreq) } - } - - pub fn linger(&self) -> io::Result> { - unsafe { - Ok(linger2dur(self.getsockopt( - libc::SOL_SOCKET, - libc::SO_LINGER, - )?)) - } - } - - pub fn set_linger(&self, dur: Option) -> io::Result<()> { - unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_LINGER, dur2linger(dur)) } - } - - pub fn set_reuse_address(&self, reuse: bool) -> io::Result<()> { - unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_REUSEADDR, reuse as c_int) } - } - - pub fn reuse_address(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_REUSEADDR)?; - Ok(raw != 0) - } - } - - pub fn recv_buffer_size(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_RCVBUF)?; - Ok(raw as usize) - } - } - - pub fn set_recv_buffer_size(&self, size: usize) -> io::Result<()> { - unsafe { - // TODO: casting usize to a c_int should be a checked cast - self.setsockopt(libc::SOL_SOCKET, libc::SO_RCVBUF, size as c_int) - } - } - - pub fn send_buffer_size(&self) -> io::Result { - unsafe { - let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_SNDBUF)?; - Ok(raw as usize) - } - } - - pub fn set_send_buffer_size(&self, size: usize) -> io::Result<()> { - unsafe { - // TODO: casting usize to a c_int should be a checked cast - self.setsockopt(libc::SOL_SOCKET, libc::SO_SNDBUF, size as c_int) - } - } - - pub fn keepalive(&self) -> io::Result> { - unsafe { - let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_KEEPALIVE)?; - if raw == 0 { - return Ok(None); - } - let secs: c_int = self.getsockopt(libc::IPPROTO_TCP, KEEPALIVE_OPTION)?; - Ok(Some(Duration::new(secs as u64, 0))) - } - } - - pub fn set_keepalive(&self, keepalive: Option) -> io::Result<()> { - unsafe { - self.setsockopt( - libc::SOL_SOCKET, - libc::SO_KEEPALIVE, - keepalive.is_some() as c_int, - )?; - if let Some(dur) = keepalive { - // TODO: checked cast here - self.setsockopt( - libc::IPPROTO_TCP, - KEEPALIVE_OPTION, - dur.as_secs() as c_int, - )?; - } - Ok(()) - } - } - - unsafe fn setsockopt(&self, opt: c_int, val: c_int, payload: T) -> io::Result<()> - where - T: Copy, - { - let payload = &payload as *const T as *const c_void; - cvt(libc::setsockopt( - self.fd, - opt, - val, - payload, - mem::size_of::() as libc::socklen_t, - ))?; - Ok(()) - } - - unsafe fn getsockopt(&self, opt: c_int, val: c_int) -> io::Result { - let mut slot: T = mem::zeroed(); - let mut len = mem::size_of::() as libc::socklen_t; - cvt(libc::getsockopt( - self.fd, - opt, - val, - &mut slot as *mut _ as *mut _, - &mut len, - ))?; - assert_eq!(len as usize, mem::size_of::()); - Ok(slot) - } -} - -impl Read for Socket { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - <&Socket>::read(&mut &*self, buf) - } -} - -impl<'a> Read for &'a Socket { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - unsafe { - let n = cvt({ - libc::read( - self.fd, - buf.as_mut_ptr() as *mut c_void, - cmp::min(buf.len(), max_len()), - ) - })?; - Ok(n as usize) - } - } -} - -impl Write for Socket { - fn write(&mut self, buf: &[u8]) -> io::Result { - <&Socket>::write(&mut &*self, buf) - } - - fn flush(&mut self) -> io::Result<()> { - <&Socket>::flush(&mut &*self) - } -} - -impl<'a> Write for &'a Socket { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.send(buf) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl fmt::Debug for Socket { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut f = f.debug_struct("Socket"); - f.field("fd", &self.fd); - if let Ok(addr) = self.local_addr() { - f.field("local_addr", &addr); - } - if let Ok(addr) = self.peer_addr() { - f.field("peer_addr", &addr); - } - f.finish() - } -} - -impl AsRawFd for Socket { - fn as_raw_fd(&self) -> RawFd { - self.fd as RawFd - } -} - -impl IntoRawFd for Socket { - fn into_raw_fd(self) -> RawFd { - let fd = self.fd as RawFd; - mem::forget(self); - return fd; - } -} - -impl FromRawFd for Socket { - unsafe fn from_raw_fd(fd: RawFd) -> Socket { - Socket { fd: fd as c_int } - } -} - -impl AsRawFd for ::Socket { - fn as_raw_fd(&self) -> RawFd { - self.inner.as_raw_fd() - } -} - -impl IntoRawFd for ::Socket { - fn into_raw_fd(self) -> RawFd { - self.inner.into_raw_fd() - } -} - -impl FromRawFd for ::Socket { - unsafe fn from_raw_fd(fd: RawFd) -> ::Socket { - ::Socket { - inner: Socket::from_raw_fd(fd), - } - } -} - -impl Drop for Socket { - fn drop(&mut self) { - unsafe { - let _ = libc::close(self.fd); - } - } -} - -impl From for net::TcpStream { - fn from(socket: Socket) -> net::TcpStream { - unsafe { net::TcpStream::from_raw_fd(socket.into_raw_fd()) } - } -} - -impl From for net::TcpListener { - fn from(socket: Socket) -> net::TcpListener { - unsafe { net::TcpListener::from_raw_fd(socket.into_raw_fd()) } - } -} - -impl From for net::UdpSocket { - fn from(socket: Socket) -> net::UdpSocket { - unsafe { net::UdpSocket::from_raw_fd(socket.into_raw_fd()) } - } -} - -impl From for Socket { - fn from(socket: net::TcpStream) -> Socket { - unsafe { Socket::from_raw_fd(socket.into_raw_fd()) } - } -} - -impl From for Socket { - fn from(socket: net::TcpListener) -> Socket { - unsafe { Socket::from_raw_fd(socket.into_raw_fd()) } - } -} - -impl From for Socket { - fn from(socket: net::UdpSocket) -> Socket { - unsafe { Socket::from_raw_fd(socket.into_raw_fd()) } - } -} - -fn max_len() -> 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". - ::max_value() as usize -} - -fn cvt>(t: T) -> io::Result { - let one: T = T::one(); - if t == -one { - Err(io::Error::last_os_error()) - } else { - Ok(t) - } -} - -fn set_cloexec(fd: c_int) -> io::Result<()> { - unsafe { - let previous = cvt(libc::fcntl(fd, libc::F_GETFD))?; - let new = previous | syscall::O_CLOEXEC as i32; - if new != previous { - cvt(libc::fcntl(fd, libc::F_SETFD, new))?; - } - Ok(()) - } -} - -fn dur2timeval(dur: Option) -> io::Result { - match dur { - Some(dur) => { - if dur.as_secs() == 0 && dur.subsec_nanos() == 0 { - return Err(io::Error::new( - ErrorKind::InvalidInput, - "cannot set a 0 duration timeout", - )); - } - - let secs = if dur.as_secs() > libc::time_t::max_value() as u64 { - libc::time_t::max_value() - } else { - dur.as_secs() as libc::time_t - }; - let mut timeout = libc::timeval { - tv_sec: secs, - tv_usec: (dur.subsec_nanos() / 1000) as libc::suseconds_t, - }; - if timeout.tv_sec == 0 && timeout.tv_usec == 0 { - timeout.tv_usec = 1; - } - Ok(timeout) - } - None => Ok(libc::timeval { - tv_sec: 0, - tv_usec: 0, - }), - } -} - -fn timeval2dur(raw: libc::timeval) -> Option { - if raw.tv_sec == 0 && raw.tv_usec == 0 { - None - } else { - let sec = raw.tv_sec as u64; - let nsec = (raw.tv_usec as u32) * 1000; - Some(Duration::new(sec, nsec)) - } -} - -fn to_s_addr(addr: &Ipv4Addr) -> libc::in_addr_t { - let octets = addr.octets(); - ::hton( - ((octets[0] as libc::in_addr_t) << 24) | ((octets[1] as libc::in_addr_t) << 16) - | ((octets[2] as libc::in_addr_t) << 8) | ((octets[3] as libc::in_addr_t) << 0), - ) -} - -fn to_in6_addr(addr: &Ipv6Addr) -> libc::in6_addr { - let mut ret: libc::in6_addr = unsafe { mem::zeroed() }; - ret.s6_addr = addr.octets(); - return ret; -} - -fn to_ipv6mr_interface(value: u32) -> libc::c_uint { - value as libc::c_uint -} - -fn linger2dur(linger_opt: libc::linger) -> Option { - if linger_opt.l_onoff == 0 { - None - } else { - Some(Duration::from_secs(linger_opt.l_linger as u64)) - } -} - -fn dur2linger(dur: Option) -> libc::linger { - match dur { - Some(d) => libc::linger { - l_onoff: 1, - l_linger: d.as_secs() as c_int, - }, - None => libc::linger { - l_onoff: 0, - l_linger: 0, - }, - } -} - -#[test] -fn test_ip() { - let ip = Ipv4Addr::new(127, 0, 0, 1); - assert_eq!(ip, from_s_addr(to_s_addr(&ip))); -} diff --git a/src/sys/unix/mod.rs b/src/sys/unix.rs similarity index 77% rename from src/sys/unix/mod.rs rename to src/sys/unix.rs index 96e01b78..87794002 100644 --- a/src/sys/unix/mod.rs +++ b/src/sys/unix.rs @@ -9,26 +9,48 @@ // except according to those terms. use std::cmp; +#[cfg(target_os = "linux")] +use std::ffi::{CStr, CString}; use std::fmt; -use std::io::{ErrorKind, Read, Write}; use std::io; +use std::io::{ErrorKind, Read, Write}; use std::mem; +#[cfg(target_os = "linux")] +use std::mem::MaybeUninit; use std::net::Shutdown; use std::net::{self, Ipv4Addr, Ipv6Addr}; use std::ops::Neg; -use std::os::unix::prelude::*; -use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT}; -use std::time::{Duration, Instant}; #[cfg(feature = "unix")] use std::os::unix::net::{UnixDatagram, UnixListener, UnixStream}; +use std::os::unix::prelude::*; +#[cfg(target_os = "linux")] +use std::ptr; +#[cfg(target_os = "linux")] +use std::slice; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::{Duration, Instant}; + +use libc::{self, c_void, socklen_t, ssize_t}; -use libc::{self, c_int, c_void, socklen_t, ssize_t}; +use crate::{Domain, Type}; -cfg_if! { +pub use libc::c_int; + +// Used in `Domain`. +pub(crate) use libc::{AF_INET, AF_INET6}; +// Used in `Type`. +#[cfg(not(target_os = "redox"))] +pub(crate) use libc::SOCK_RAW; +pub(crate) use libc::{SOCK_DGRAM, SOCK_SEQPACKET, SOCK_STREAM}; +// Used in `Protocol`. +pub(crate) use libc::{IPPROTO_ICMP, IPPROTO_ICMPV6, IPPROTO_TCP, IPPROTO_UDP}; + +cfg_if::cfg_if! { if #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "openbsd", target_os = "netbsd", - target_os = "solaris", target_os = "haiku"))] { + target_os = "solaris", target_os = "illumos", + target_os = "haiku"))] { use libc::IPV6_JOIN_GROUP as IPV6_ADD_MEMBERSHIP; use libc::IPV6_LEAVE_GROUP as IPV6_DROP_MEMBERSHIP; } else { @@ -37,18 +59,7 @@ cfg_if! { } } -cfg_if! { - if #[cfg(any(target_os = "linux", target_os = "android", - target_os = "dragonfly", target_os = "freebsd", - target_os = "openbsd", target_os = "netbsd", - target_os = "haiku", target_os = "bitrig"))] { - use libc::MSG_NOSIGNAL; - } else { - const MSG_NOSIGNAL: c_int = 0x0; - } -} - -cfg_if! { +cfg_if::cfg_if! { if #[cfg(any(target_os = "macos", target_os = "ios"))] { use libc::TCP_KEEPALIVE as KEEPALIVE_OPTION; } else if #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "haiku"))] { @@ -58,19 +69,112 @@ cfg_if! { } } -use SockAddr; -use utils::One; +use crate::utils::One; +use crate::SockAddr; -pub const IPPROTO_ICMP: i32 = libc::IPPROTO_ICMP; -pub const IPPROTO_ICMPV6: i32 = libc::IPPROTO_ICMPV6; -pub const IPPROTO_TCP: i32 = libc::IPPROTO_TCP; -pub const IPPROTO_UDP: i32 = libc::IPPROTO_UDP; -pub const SOCK_SEQPACKET: i32 = libc::SOCK_SEQPACKET; -pub const SOCK_RAW: i32 = libc::SOCK_RAW; +/// Unix only API. +impl Domain { + /// Domain for Unix socket communication, corresponding to `AF_UNIX`. + pub fn unix() -> Domain { + Domain(libc::AF_UNIX) + } -#[macro_use] -#[cfg(target_os = "linux")] -mod weak; + /// Domain for low-level packet interface, corresponding to `AF_PACKET`. + /// + /// # Notes + /// + /// This function is only available on Linux. + #[cfg(target_os = "linux")] + pub fn packet() -> Domain { + Domain(libc::AF_PACKET) + } +} + +impl_debug!( + Domain, + libc::AF_INET, + libc::AF_INET6, + libc::AF_UNIX, + libc::AF_UNSPEC, // = 0. +); + +/// Unix only API. +impl Type { + /// Set `SOCK_NONBLOCK` on the `Type`. + /// + /// # Notes + /// + /// This function is only available on Android, DragonFlyBSD, FreeBSD, + /// Linux, NetBSD and OpenBSD. + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub fn non_blocking(self) -> Type { + Type(self.0 | libc::SOCK_NONBLOCK) + } + + /// Set `SOCK_CLOEXEC` on the `Type`. + /// + /// # Notes + /// + /// This function is only available on Android, DragonFlyBSD, FreeBSD, + /// Linux, NetBSD and OpenBSD. + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub fn cloexec(self) -> Type { + Type(self.0 | libc::SOCK_CLOEXEC) + } +} + +impl_debug!( + crate::Type, + libc::SOCK_STREAM, + libc::SOCK_DGRAM, + #[cfg(not(target_os = "redox"))] + libc::SOCK_RAW, + #[cfg(not(any(target_os = "haiku", target_os = "redox")))] + libc::SOCK_RDM, + libc::SOCK_SEQPACKET, + /* TODO: add these optional bit OR-ed flags: + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + 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 = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))] + libc::SOCK_CLOEXEC, + */ +); + +impl_debug!( + crate::Protocol, + libc::IPPROTO_ICMP, + libc::IPPROTO_ICMPV6, + libc::IPPROTO_TCP, + libc::IPPROTO_UDP, +); pub struct Socket { fd: c_int, @@ -96,7 +200,7 @@ impl Socket { let fd = cvt(libc::socket(family, ty, protocol))?; let fd = Socket::from_raw_fd(fd); set_cloexec(fd.as_raw_fd())?; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] { fd.setsockopt(libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1i32)?; } @@ -111,7 +215,7 @@ impl Socket { let fds = (Socket::from_raw_fd(fds[0]), Socket::from_raw_fd(fds[1])); set_cloexec(fds.0.as_raw_fd())?; set_cloexec(fds.1.as_raw_fd())?; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] { fds.0 .setsockopt(libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1i32)?; @@ -248,7 +352,7 @@ impl Socket { #[cfg(not(any(target_os = "android", target_os = "haiku")))] use libc::F_DUPFD_CLOEXEC; - static CLOEXEC_FAILED: AtomicBool = ATOMIC_BOOL_INIT; + static CLOEXEC_FAILED: AtomicBool = AtomicBool::new(false); unsafe { if !CLOEXEC_FAILED.load(Ordering::Relaxed) { match cvt(libc::fcntl(self.fd, F_DUPFD_CLOEXEC, 0)) { @@ -280,23 +384,19 @@ impl Socket { let mut socket = None; #[cfg(target_os = "linux")] { - weak! { - fn accept4(c_int, *mut libc::sockaddr, *mut socklen_t, c_int) -> c_int - } - if let Some(f) = accept4.get() { - let res = cvt_r(|| unsafe { - f( - self.fd, - &mut storage as *mut _ as *mut _, - &mut len, - libc::SOCK_CLOEXEC, - ) - }); - match res { - Ok(fd) => socket = Some(Socket { fd: fd }), - Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => {} - Err(e) => return Err(e), - } + let res = cvt_r(|| unsafe { + libc::syscall( + libc::SYS_accept4, + self.fd as libc::c_long, + &mut storage as *mut _ as libc::c_long, + &mut len, + libc::SOCK_CLOEXEC as libc::c_long, + ) as libc::c_int + }); + match res { + Ok(fd) => socket = Some(Socket { fd: fd }), + Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => {} + Err(e) => return Err(e), } } @@ -350,14 +450,14 @@ impl Socket { Ok(()) } - pub fn recv(&self, buf: &mut [u8]) -> io::Result { + pub fn recv(&self, buf: &mut [u8], flags: c_int) -> io::Result { unsafe { let n = cvt({ libc::recv( self.fd, buf.as_mut_ptr() as *mut c_void, cmp::min(buf.len(), max_len()), - 0, + flags, ) })?; Ok(n as usize) @@ -378,15 +478,11 @@ impl Socket { } } - pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SockAddr)> { - self.recvfrom(buf, 0) - } - pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SockAddr)> { - self.recvfrom(buf, libc::MSG_PEEK) + self.recv_from(buf, libc::MSG_PEEK) } - fn recvfrom(&self, buf: &mut [u8], flags: c_int) -> io::Result<(usize, SockAddr)> { + pub fn recv_from(&self, buf: &mut [u8], flags: c_int) -> io::Result<(usize, SockAddr)> { unsafe { let mut storage: libc::sockaddr_storage = mem::zeroed(); let mut addrlen = mem::size_of_val(&storage) as socklen_t; @@ -406,28 +502,28 @@ impl Socket { } } - pub fn send(&self, buf: &[u8]) -> io::Result { + pub fn send(&self, buf: &[u8], flags: c_int) -> io::Result { unsafe { let n = cvt({ libc::send( self.fd, buf.as_ptr() as *const c_void, cmp::min(buf.len(), max_len()), - MSG_NOSIGNAL, + flags, ) })?; Ok(n as usize) } } - pub fn send_to(&self, buf: &[u8], addr: &SockAddr) -> io::Result { + pub fn send_to(&self, buf: &[u8], flags: c_int, addr: &SockAddr) -> io::Result { unsafe { let n = cvt({ libc::sendto( self.fd, buf.as_ptr() as *const c_void, cmp::min(buf.len(), max_len()), - MSG_NOSIGNAL, + flags, addr.as_ptr(), addr.len(), ) @@ -449,6 +545,88 @@ impl Socket { unsafe { self.setsockopt(libc::IPPROTO_IP, libc::IP_TTL, ttl as c_int) } } + #[cfg(not(target_os = "redox"))] + pub fn mss(&self) -> io::Result { + unsafe { + let raw: c_int = self.getsockopt(libc::IPPROTO_TCP, libc::TCP_MAXSEG)?; + Ok(raw as u32) + } + } + + #[cfg(not(target_os = "redox"))] + pub fn set_mss(&self, mss: u32) -> io::Result<()> { + unsafe { self.setsockopt(libc::IPPROTO_TCP, libc::TCP_MAXSEG, mss as c_int) } + } + + #[cfg(target_os = "linux")] + pub fn mark(&self) -> io::Result { + unsafe { + let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_MARK)?; + Ok(raw as u32) + } + } + + #[cfg(target_os = "linux")] + pub fn set_mark(&self, mark: u32) -> io::Result<()> { + unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_MARK, mark as c_int) } + } + + #[cfg(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::<[MaybeUninit; libc::IFNAMSIZ]>::uninit().assume_init() }; + let mut len = buf.len() as libc::socklen_t; + let len = unsafe { + cvt(libc::getsockopt( + self.fd, + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + buf.as_mut_ptr().cast(), + &mut len, + ))? + }; + if len == 0 { + Ok(None) + } else { + // Allocate a buffer for `CString` with the length including the + // null terminator. + let len = len as usize; + let mut name = Vec::with_capacity(len); + + // TODO: use `MaybeUninit::slice_assume_init_ref` once stable. + // Safety: `len` bytes are writen by the OS, this includes a null + // terminator. However we don't copy the null terminator because + // `CString::from_vec_unchecked` adds its own null terminator. + let buf = unsafe { slice::from_raw_parts(buf.as_ptr().cast(), len - 1) }; + name.extend_from_slice(buf); + + // Safety: the OS initialised the string for us, which shouldn't + // include any null bytes. + Ok(Some(unsafe { CString::from_vec_unchecked(name) })) + } + } + + #[cfg(target_os = "linux")] + pub fn bind_device(&self, interface: Option<&CStr>) -> io::Result<()> { + let (value, len) = if let Some(interface) = interface { + (interface.as_ptr(), interface.to_bytes_with_nul().len()) + } else { + (ptr::null(), 0) + }; + + unsafe { + cvt(libc::setsockopt( + self.fd, + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + value.cast(), + len as libc::socklen_t, + )) + .map(|_| ()) + } + } + pub fn unicast_hops_v6(&self) -> io::Result { unsafe { let raw: c_int = self.getsockopt(libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS)?; @@ -479,10 +657,9 @@ impl Socket { pub fn read_timeout(&self) -> io::Result> { unsafe { - Ok(timeval2dur(self.getsockopt( - libc::SOL_SOCKET, - libc::SO_RCVTIMEO, - )?)) + Ok(timeval2dur( + self.getsockopt(libc::SOL_SOCKET, libc::SO_RCVTIMEO)?, + )) } } @@ -492,10 +669,9 @@ impl Socket { pub fn write_timeout(&self) -> io::Result> { unsafe { - Ok(timeval2dur(self.getsockopt( - libc::SOL_SOCKET, - libc::SO_SNDTIMEO, - )?)) + Ok(timeval2dur( + self.getsockopt(libc::SOL_SOCKET, libc::SO_SNDTIMEO)?, + )) } } @@ -659,10 +835,9 @@ impl Socket { pub fn linger(&self) -> io::Result> { unsafe { - Ok(linger2dur(self.getsockopt( - libc::SOL_SOCKET, - libc::SO_LINGER, - )?)) + Ok(linger2dur( + self.getsockopt(libc::SOL_SOCKET, libc::SO_LINGER)?, + )) } } @@ -729,17 +904,17 @@ impl Socket { )?; if let Some(dur) = keepalive { // TODO: checked cast here - self.setsockopt( - libc::IPPROTO_TCP, - KEEPALIVE_OPTION, - dur.as_secs() as c_int, - )?; + self.setsockopt(libc::IPPROTO_TCP, KEEPALIVE_OPTION, dur.as_secs() as c_int)?; } Ok(()) } } - #[cfg(all(unix, feature = "reuseport"))] + #[cfg(all( + unix, + not(any(target_os = "solaris", target_os = "illumos")), + feature = "reuseport" + ))] pub fn reuse_port(&self) -> io::Result { unsafe { let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_REUSEPORT)?; @@ -747,11 +922,26 @@ impl Socket { } } - #[cfg(all(unix, feature = "reuseport"))] + #[cfg(all( + unix, + not(any(target_os = "solaris", target_os = "illumos")), + feature = "reuseport" + ))] pub fn set_reuse_port(&self, reuse: bool) -> io::Result<()> { unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_REUSEPORT, reuse as c_int) } } + pub fn out_of_band_inline(&self) -> io::Result { + unsafe { + let raw: c_int = self.getsockopt(libc::SOL_SOCKET, libc::SO_OOBINLINE)?; + Ok(raw != 0) + } + } + + pub fn set_out_of_band_inline(&self, oob_inline: bool) -> io::Result<()> { + unsafe { self.setsockopt(libc::SOL_SOCKET, libc::SO_OOBINLINE, oob_inline as c_int) } + } + unsafe fn setsockopt(&self, opt: c_int, val: c_int, payload: T) -> io::Result<()> where T: Copy, @@ -815,7 +1005,7 @@ impl Write for Socket { impl<'a> Write for &'a Socket { fn write(&mut self, buf: &[u8]) -> io::Result { - self.send(buf) + self.send(buf, 0) } fn flush(&mut self) -> io::Result<()> { @@ -857,21 +1047,21 @@ impl FromRawFd for Socket { } } -impl AsRawFd for ::Socket { +impl AsRawFd for crate::Socket { fn as_raw_fd(&self) -> c_int { self.inner.as_raw_fd() } } -impl IntoRawFd for ::Socket { +impl IntoRawFd for crate::Socket { fn into_raw_fd(self) -> c_int { self.inner.into_raw_fd() } } -impl FromRawFd for ::Socket { - unsafe fn from_raw_fd(fd: c_int) -> ::Socket { - ::Socket { +impl FromRawFd for crate::Socket { + unsafe fn from_raw_fd(fd: c_int) -> crate::Socket { + crate::Socket { inner: Socket::from_raw_fd(fd), } } @@ -1055,14 +1245,16 @@ fn timeval2dur(raw: libc::timeval) -> Option { fn to_s_addr(addr: &Ipv4Addr) -> libc::in_addr_t { let octets = addr.octets(); - ::hton( - ((octets[0] as libc::in_addr_t) << 24) | ((octets[1] as libc::in_addr_t) << 16) - | ((octets[2] as libc::in_addr_t) << 8) | ((octets[3] as libc::in_addr_t) << 0), + crate::hton( + ((octets[0] as libc::in_addr_t) << 24) + | ((octets[1] as libc::in_addr_t) << 16) + | ((octets[2] as libc::in_addr_t) << 8) + | ((octets[3] as libc::in_addr_t) << 0), ) } fn from_s_addr(in_addr: libc::in_addr_t) -> Ipv4Addr { - let h_addr = ::ntoh(in_addr); + let h_addr = crate::ntoh(in_addr); let a: u8 = (h_addr >> 24) as u8; let b: u8 = (h_addr >> 16) as u8; @@ -1114,3 +1306,12 @@ fn test_ip() { let ip = Ipv4Addr::new(127, 0, 0, 1); assert_eq!(ip, from_s_addr(to_s_addr(&ip))); } + +#[test] +fn test_out_of_band_inline() { + let tcp = Socket::new(libc::AF_INET, libc::SOCK_STREAM, 0).unwrap(); + assert_eq!(tcp.out_of_band_inline().unwrap(), false); + + tcp.set_out_of_band_inline(true).unwrap(); + assert_eq!(tcp.out_of_band_inline().unwrap(), true); +} diff --git a/src/sys/unix/weak.rs b/src/sys/unix/weak.rs deleted file mode 100644 index 6767a0f9..00000000 --- a/src/sys/unix/weak.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2016 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// 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::marker; -use std::mem; -use std::sync::atomic::{AtomicUsize, Ordering}; - -use libc; - -macro_rules! weak { - (fn $name:ident($($t:ty),*) -> $ret:ty) => ( - #[allow(bad_style)] - static $name: ::sys::weak::Weak $ret> = - ::sys::weak::Weak { - name: concat!(stringify!($name), "\0"), - addr: ::std::sync::atomic::ATOMIC_USIZE_INIT, - _marker: ::std::marker::PhantomData, - }; - ) -} - -pub struct Weak { - pub name: &'static str, - pub addr: AtomicUsize, - pub _marker: marker::PhantomData, -} - -impl Weak { - pub fn get(&self) -> Option<&F> { - assert_eq!(mem::size_of::(), mem::size_of::()); - unsafe { - if self.addr.load(Ordering::SeqCst) == 0 { - let ptr = match fetch(self.name) { - 1 => 1, - n => n, - }; - self.addr.store(ptr, Ordering::SeqCst); - } - if self.addr.load(Ordering::SeqCst) == 0 { - None - } else { - mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr) - } - } - } -} - -unsafe fn fetch(name: &str) -> usize { - let name = name.as_bytes(); - assert_eq!(name[name.len() - 1], 0); - libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize -} diff --git a/src/sys/windows.rs b/src/sys/windows.rs index e6c8f114..443ef265 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -10,30 +10,29 @@ use std::cmp; use std::fmt; -use std::io::{Read, Write}; use std::io; +use std::io::{Read, Write}; use std::mem; use std::net::Shutdown; use std::net::{self, Ipv4Addr, Ipv6Addr}; use std::os::windows::prelude::*; use std::ptr; -use std::sync::{Once, ONCE_INIT}; +use std::sync::Once; use std::time::Duration; -use winapi::ctypes::{c_char, c_int, c_long, c_ulong}; -use winapi::shared::inaddr::*; +use winapi::ctypes::{c_char, c_long, c_ulong}; use winapi::shared::in6addr::*; +use winapi::shared::inaddr::*; use winapi::shared::minwindef::DWORD; use winapi::shared::ntdef::{HANDLE, ULONG}; -use winapi::shared::ws2def::*; -use winapi::shared::ws2def; +use winapi::shared::ws2def::{self, *}; use winapi::shared::ws2ipdef::*; use winapi::um::handleapi::SetHandleInformation; use winapi::um::processthreadsapi::GetCurrentProcessId; use winapi::um::winbase::INFINITE; use winapi::um::winsock2 as sock; -use SockAddr; +use crate::SockAddr; const HANDLE_FLAG_INHERIT: DWORD = 0x00000001; const MSG_PEEK: c_int = 0x2; @@ -43,12 +42,42 @@ const SD_SEND: c_int = 1; const SIO_KEEPALIVE_VALS: DWORD = 0x98000004; const WSA_FLAG_OVERLAPPED: DWORD = 0x01; -pub const IPPROTO_ICMP: i32 = ws2def::IPPROTO_ICMP as i32; -pub const IPPROTO_ICMPV6: i32 = ws2def::IPPROTO_ICMPV6 as i32; -pub const IPPROTO_TCP: i32 = ws2def::IPPROTO_TCP as i32; -pub const IPPROTO_UDP: i32 = ws2def::IPPROTO_UDP as i32; -pub const SOCK_SEQPACKET: i32 = ws2def::SOCK_SEQPACKET as i32; -pub const SOCK_RAW: i32 = ws2def::SOCK_RAW as i32; +pub use winapi::ctypes::c_int; + +// Used in `Domain`. +pub(crate) use winapi::shared::ws2def::{AF_INET, AF_INET6}; +// Used in `Type`. +pub(crate) use winapi::shared::ws2def::{SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET, SOCK_STREAM}; +// Used in `Protocol`. +pub(crate) const IPPROTO_ICMP: c_int = winapi::shared::ws2def::IPPROTO_ICMP as c_int; +pub(crate) const IPPROTO_ICMPV6: c_int = winapi::shared::ws2def::IPPROTO_ICMPV6 as c_int; +pub(crate) const IPPROTO_TCP: c_int = winapi::shared::ws2def::IPPROTO_TCP as c_int; +pub(crate) const IPPROTO_UDP: c_int = winapi::shared::ws2def::IPPROTO_UDP as c_int; + +impl_debug!( + crate::Domain, + ws2def::AF_INET, + ws2def::AF_INET6, + ws2def::AF_UNIX, + ws2def::AF_UNSPEC, // = 0. +); + +impl_debug!( + crate::Type, + ws2def::SOCK_STREAM, + ws2def::SOCK_DGRAM, + ws2def::SOCK_RAW, + ws2def::SOCK_RDM, + ws2def::SOCK_SEQPACKET, +); + +impl_debug!( + crate::Protocol, + self::IPPROTO_ICMP, + self::IPPROTO_ICMPV6, + self::IPPROTO_TCP, + self::IPPROTO_UDP, +); #[repr(C)] struct tcp_keepalive { @@ -58,7 +87,7 @@ struct tcp_keepalive { } fn init() { - static INIT: Once = ONCE_INIT; + static INIT: Once = Once::new(); INIT.call_once(|| { // Initialize winsock through the standard library by just creating a @@ -285,14 +314,14 @@ impl Socket { } } - pub fn recv(&self, buf: &mut [u8]) -> io::Result { + pub fn recv(&self, buf: &mut [u8], flags: c_int) -> io::Result { unsafe { let n = { sock::recv( self.socket, buf.as_mut_ptr() as *mut c_char, clamp(buf.len()), - 0, + flags, ) }; match n { @@ -321,15 +350,11 @@ impl Socket { } } - pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SockAddr)> { - self.recvfrom(buf, 0) - } - pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SockAddr)> { - self.recvfrom(buf, MSG_PEEK) + self.recv_from(buf, MSG_PEEK) } - fn recvfrom(&self, buf: &mut [u8], flags: c_int) -> io::Result<(usize, SockAddr)> { + pub fn recv_from(&self, buf: &mut [u8], flags: c_int) -> io::Result<(usize, SockAddr)> { unsafe { let mut storage: SOCKADDR_STORAGE = mem::zeroed(); let mut addrlen = mem::size_of_val(&storage) as c_int; @@ -354,14 +379,14 @@ impl Socket { } } - pub fn send(&self, buf: &[u8]) -> io::Result { + pub fn send(&self, buf: &[u8], flags: c_int) -> io::Result { unsafe { let n = { sock::send( self.socket, buf.as_ptr() as *const c_char, clamp(buf.len()), - 0, + flags, ) }; if n == sock::SOCKET_ERROR { @@ -372,14 +397,14 @@ impl Socket { } } - pub fn send_to(&self, buf: &[u8], addr: &SockAddr) -> io::Result { + pub fn send_to(&self, buf: &[u8], flags: c_int, addr: &SockAddr) -> io::Result { unsafe { let n = { sock::sendto( self.socket, buf.as_ptr() as *const c_char, clamp(buf.len()), - 0, + flags, addr.as_ptr(), addr.len(), ) @@ -445,13 +470,13 @@ impl Socket { pub fn nodelay(&self) -> io::Result { unsafe { - let raw: c_int = self.getsockopt(IPPROTO_TCP, TCP_NODELAY)?; + let raw: c_char = self.getsockopt(IPPROTO_TCP, TCP_NODELAY)?; Ok(raw != 0) } } pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { - unsafe { self.setsockopt(IPPROTO_TCP, TCP_NODELAY, nodelay as c_int) } + unsafe { self.setsockopt(IPPROTO_TCP, TCP_NODELAY, nodelay as c_char) } } pub fn broadcast(&self) -> io::Result { @@ -688,6 +713,17 @@ impl Socket { } } + pub fn out_of_band_inline(&self) -> io::Result { + unsafe { + let raw: c_int = self.getsockopt(SOL_SOCKET, SO_OOBINLINE)?; + Ok(raw != 0) + } + } + + pub fn set_out_of_band_inline(&self, oob_inline: bool) -> io::Result<()> { + unsafe { self.setsockopt(SOL_SOCKET, SO_OOBINLINE, oob_inline as c_int) } + } + unsafe fn setsockopt(&self, opt: c_int, val: c_int, payload: T) -> io::Result<()> where T: Copy, @@ -738,7 +774,7 @@ impl Read for Socket { impl<'a> Read for &'a Socket { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.recv(buf) + self.recv(buf, 0) } } @@ -754,7 +790,7 @@ impl Write for Socket { impl<'a> Write for &'a Socket { fn write(&mut self, buf: &[u8]) -> io::Result { - self.send(buf) + self.send(buf, 0) } fn flush(&mut self) -> io::Result<()> { @@ -798,21 +834,21 @@ impl FromRawSocket for Socket { } } -impl AsRawSocket for ::Socket { +impl AsRawSocket for crate::Socket { fn as_raw_socket(&self) -> RawSocket { self.inner.as_raw_socket() } } -impl IntoRawSocket for ::Socket { +impl IntoRawSocket for crate::Socket { fn into_raw_socket(self) -> RawSocket { self.inner.into_raw_socket() } } -impl FromRawSocket for ::Socket { - unsafe fn from_raw_socket(socket: RawSocket) -> ::Socket { - ::Socket { +impl FromRawSocket for crate::Socket { + unsafe fn from_raw_socket(socket: RawSocket) -> crate::Socket { + crate::Socket { inner: Socket::from_raw_socket(socket), } } @@ -876,7 +912,8 @@ fn dur2ms(dur: Option) -> io::Result { // * Nanosecond precision is rounded up // * Greater than u32::MAX milliseconds (50 days) is rounded up to // INFINITE (never time out). - let ms = dur.as_secs() + let ms = dur + .as_secs() .checked_mul(1000) .and_then(|ms| ms.checked_add((dur.subsec_nanos() as u64) / 1_000_000)) .and_then(|ms| { @@ -918,8 +955,10 @@ fn ms2dur(raw: DWORD) -> Option { fn to_s_addr(addr: &Ipv4Addr) -> in_addr_S_un { let octets = addr.octets(); - let res = ::hton( - ((octets[0] as ULONG) << 24) | ((octets[1] as ULONG) << 16) | ((octets[2] as ULONG) << 8) + let res = crate::hton( + ((octets[0] as ULONG) << 24) + | ((octets[1] as ULONG) << 16) + | ((octets[2] as ULONG) << 8) | ((octets[3] as ULONG) << 0), ); let mut new_addr: in_addr_S_un = unsafe { mem::zeroed() }; @@ -928,7 +967,7 @@ fn to_s_addr(addr: &Ipv4Addr) -> in_addr_S_un { } fn from_s_addr(in_addr: in_addr_S_un) -> Ipv4Addr { - let h_addr = ::ntoh(unsafe { *in_addr.S_addr() }); + let h_addr = crate::ntoh(unsafe { *in_addr.S_addr() }); let a: u8 = (h_addr >> 24) as u8; let b: u8 = (h_addr >> 16) as u8; @@ -972,3 +1011,12 @@ fn test_ip() { let ip = Ipv4Addr::new(127, 0, 0, 1); assert_eq!(ip, from_s_addr(to_s_addr(&ip))); } + +#[test] +fn test_out_of_band_inline() { + let tcp = Socket::new(AF_INET, SOCK_STREAM, 0).unwrap(); + assert_eq!(tcp.out_of_band_inline().unwrap(), false); + + tcp.set_out_of_band_inline(true).unwrap(); + assert_eq!(tcp.out_of_band_inline().unwrap(), true); +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 00000000..4a2f1f8e --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,62 @@ +use std::io::Write; +use std::str; + +use crate::{Domain, Protocol, Type}; + +#[test] +fn domain_fmt_debug() { + let tests = &[ + (Domain::ipv4(), "AF_INET"), + (Domain::ipv6(), "AF_INET6"), + #[cfg(unix)] + (Domain::unix(), "AF_UNIX"), + (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"), + (Type::seqpacket(), "SOCK_SEQPACKET"), + (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 = &[ + (Protocol::icmpv4(), "IPPROTO_ICMP"), + (Protocol::icmpv6(), "IPPROTO_ICMPV6"), + (Protocol::tcp(), "IPPROTO_TCP"), + (Protocol::udp(), "IPPROTO_UDP"), + (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); + } +}