Skip to main content

net_io/
lib.rs

1//! # net_io
2//!
3//! The network IO substrate for the `-net` and `-io` crates: the entrance and
4//! exit every service shares. It owns listeners and sockets, the life of each
5//! connection, the limits and timeouts that keep a peer from holding what it
6//! should not, graceful drain, and the seams a connection gate and a TLS engine
7//! plug into. It is protocol-free and has no runtime dependencies.
8//!
9//! ## Quick start
10//!
11//! ```no_run
12//! fn main() -> Result<(), net_io::NetError> {
13//!     // An echo server: one handler call per connection on a bounded worker pool,
14//!     // with every default limit (idle, read, write, minimum rate, caps) applied.
15//!     let server = net_io::serve("127.0.0.1:7000", |conn| {
16//!         let mut buf = [0u8; 4096];
17//!         loop {
18//!             let n = conn.recv(&mut buf)?;
19//!             if n == 0 {
20//!                 return Ok(()); // the peer closed its side
21//!             }
22//!             conn.send_all(&buf[..n])?;
23//!         }
24//!     })?;
25//!
26//!     // A client.
27//!     let mut conn = net_io::connect("127.0.0.1:7000")?;
28//!     conn.send_all(b"ping")?;
29//!     let mut reply = [0u8; 4];
30//!     conn.recv_exact(&mut reply)?;
31//!     assert_eq!(&reply, b"ping");
32//!
33//!     // Stop: close the door, drain open connections, join the workers.
34//!     server.handle().request_stop();
35//!     let report = server.wait_for_stop();
36//!     println!("{} closed gracefully, {} killed", report.graceful, report.killed);
37//!     Ok(())
38//! }
39//! ```
40//!
41//! Everything else (several listeners, a gate, events, the control line) goes
42//! through [`ServerBuilder`]. The full reference with every item, its errors,
43//! and its cost is `docs/API.md` in the
44//! [repository](https://github.com/jamesgober/net-io).
45//!
46//! ## Status
47//!
48//! `0.4.0`: TCP, Unix domain sockets, and UDP on the blocking driver (Linux,
49//! macOS, Windows; Unix sockets and hand-off on Unix), the PROXY protocol, the
50//! TLS seam on listeners, systemd and FastCGI inheritance, and listener
51//! hand-off. The reactor and Windows named pipes arrive in `0.5.0`.
52
53#![doc(html_root_url = "https://docs.rs/net-io")]
54#![cfg_attr(docsrs, feature(doc_cfg))]
55#![deny(unsafe_code)]
56#![deny(unsafe_op_in_unsafe_fn)]
57#![deny(missing_docs)]
58#![cfg_attr(
59    not(test),
60    deny(
61        clippy::unwrap_used,
62        clippy::expect_used,
63        clippy::panic,
64        clippy::todo,
65        clippy::unimplemented,
66        clippy::dbg_macro,
67        clippy::print_stdout,
68        clippy::print_stderr
69    )
70)]
71
72mod accept;
73mod addr;
74mod buf;
75mod completer;
76mod control;
77mod crc32c;
78mod driver;
79mod error;
80mod events;
81mod gate;
82mod handoff;
83mod id;
84mod info;
85mod inherit;
86mod limits;
87mod link;
88#[cfg(feature = "local")]
89mod local;
90mod opts;
91mod proxy;
92#[cfg(feature = "proxy")]
93mod proxy_parse;
94mod rate;
95mod server;
96mod session;
97mod sys;
98mod tcp;
99#[cfg(test)]
100mod testing;
101mod time;
102mod tls;
103mod trusted;
104#[cfg(feature = "udp")]
105mod udp;
106
107/// Entry points for net-io's fuzz targets (`fuzz/`), compiled only under
108/// cargo-fuzz (`--cfg fuzzing`). Not part of the public API and not covered by semver.
109#[cfg(fuzzing)]
110#[doc(hidden)]
111pub mod __fuzz {
112    /// The hand-off manifest decoder.
113    pub fn manifest(data: &[u8]) {
114        crate::handoff::fuzz_manifest(data);
115    }
116
117    /// The systemd environment reader.
118    pub fn systemd_env(data: &[u8]) {
119        crate::inherit::fuzz_systemd_env(data);
120    }
121}
122
123/// Fault injection for net-io's own tests, compiled only with `--cfg net_io_shim`.
124/// Not part of the public API and not covered by semver.
125#[cfg(net_io_shim)]
126#[doc(hidden)]
127pub mod __shim {
128    pub use crate::sys::shim::{Fault, Op, clear, counted, program, start_counting, stop_counting};
129}
130
131use std::net::ToSocketAddrs;
132
133pub use addr::{AddrError, Cidr, PrefixKey, PrefixPolicy, normalize, normalize_socket};
134pub use buf::{Buffer, BufferPool, PoolStats};
135pub use completer::{Completer, PostError};
136pub use control::{ControlLine, ControlState, NoControl, SimpleControl};
137pub use driver::DriverKind;
138pub use error::{
139    CloseReason, ConfigReason, Direction, ErrorClass, KillReason, LimitKind, NetError,
140    OsErrorClass, RefusalCause, TimeoutKind,
141};
142pub use events::{CoalescedKind, Event, EventSink, NoopSink};
143pub use gate::{AdmitStage, Admitted, AllowAll, Denial, Gate, KillHandle, Refusal, WriteCheck};
144pub use handoff::{HandoffError, HandoffPeer, ListenerHandle};
145#[cfg(all(unix, feature = "local"))]
146pub use handoff::{recv_listeners, send_listeners};
147pub use id::{ConnectionId, ListenerId, WorkerId};
148pub use info::{
149    ConnectionInfo, Endpoint, GateTag, Lane, Origin, PeerCredentials, PipeName, Transport, UnixPath,
150};
151#[cfg(unix)]
152pub use inherit::fastcgi_listener;
153pub use inherit::{InheritError, InheritedListener, systemd, systemd_named};
154pub use limits::{Limits, LimitsBuilder, MinRate, Pressure, SizeClass};
155#[cfg(all(unix, feature = "local"))]
156pub use local::{Descriptors, UnixConnection, UnixListener};
157#[cfg(feature = "local")]
158pub use local::{LocalConnection, LocalListener};
159pub use opts::{Keepalive, Linger, ListenOptions, LocalOptions, SocketOptions, TcpOptions};
160pub use proxy::{ProxyError, ProxyFacts, ProxyMode, ProxyVersions};
161#[cfg(feature = "proxy")]
162pub use proxy_parse::{
163    ProxyAddress, ProxyCommand, ProxyHeader, ProxyParser, ProxyProgress, ProxySsl, ProxyTransport,
164    Tlv, Tlvs,
165};
166pub use server::{
167    AdoptSpec, BlockingHandler, DoorState, DrainReport, ListenerSpec, NoGate, NoWork, Server,
168    ServerBuilder, ServerHandle, Sessions, Stats, Workload,
169};
170pub use session::{
171    Input, Output, OutputFull, Phase, Session, SessionContext, SessionFactory, Step, SwapError,
172};
173pub use tcp::{ConnectOptions, ListenerInfo, TcpConnection, TcpListener};
174pub use time::{Clock, ManualClock, MonotonicClock, monotonic_micros};
175pub use tls::{
176    NoTls, NoTlsSession, TlsAcceptor, TlsCloseKind, TlsError, TlsEvent, TlsFailure, TlsInfo,
177    TlsProgress, TlsSession,
178};
179pub use trusted::TrustedProxies;
180#[cfg(feature = "udp")]
181pub use udp::{Datagram, UdpBudget, UdpOptions, UdpSocket, UdpStats};
182
183/// Crate version string, populated by Cargo at build time.
184///
185/// ```
186/// assert!(!net_io::VERSION.is_empty());
187/// ```
188pub const VERSION: &str = env!("CARGO_PKG_VERSION");
189
190/// Binds a TCP listener with safe defaults: backlog 1,024 (capped by the OS);
191/// `SO_REUSEADDR` on Unix; `SO_EXCLUSIVEADDRUSE` on Windows; IPv6-only for IPv6
192/// addresses; close-on-exec. The first resolved address that binds wins.
193///
194/// # Errors
195///
196/// `AddrInUse`, `PermissionDenied`, `AddrNotAvailable`, `NoAddresses`, `Bind`.
197///
198/// # Examples
199///
200/// ```
201/// let listener = net_io::listen("127.0.0.1:0")?;
202/// let addr = listener.local_addr()?;
203/// println!("listening on {addr}, backlog {:?}", listener.info().backlog);
204/// # Ok::<(), net_io::NetError>(())
205/// ```
206pub fn listen<A: ToSocketAddrs>(addr: A) -> Result<TcpListener, NetError> {
207    TcpListener::bind(addr)
208}
209
210/// Connects with a total timeout of 10 s across every resolved address,
211/// `TCP_NODELAY` on, and default limits (read and write timeouts 30 s).
212///
213/// # Errors
214///
215/// `ConnectTimeout`, `ConnectionRefused`, `HostUnreachable`, `NetworkUnreachable`, `NoAddresses`.
216///
217/// # Examples
218///
219/// ```no_run
220/// let mut conn = net_io::connect("example.com:80")?;
221/// conn.send_all(b"HEAD / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")?;
222/// let mut head = [0u8; 512];
223/// let n = conn.recv(&mut head)?;
224/// println!("{}", String::from_utf8_lossy(&head[..n]));
225/// # Ok::<(), net_io::NetError>(())
226/// ```
227pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<TcpConnection, NetError> {
228    TcpConnection::connect(addr)
229}
230
231/// Starts a server on the blocking driver that runs `handler` once per
232/// connection on a pool worker, with default limits and [`AllowAll`] as the
233/// gate; the door is open on return. `Ok(())` closes the connection
234/// gracefully; `Err` closes it abortively.
235///
236/// Keep the returned `Server`: dropping it stops the server. A server facing
237/// the internet should set a [`Gate`] through [`ServerBuilder`].
238///
239/// # Errors
240///
241/// As [`listen`]; `InvalidConfig` when the worker count exceeds the descriptor limit.
242///
243/// # Examples
244///
245/// ```no_run
246/// use std::io::{BufRead, BufReader, Write};
247///
248/// // Handlers can use std::io traits: TcpConnection implements Read and Write.
249/// let server = net_io::serve("127.0.0.1:7001", |conn| {
250///     let mut line = String::new();
251///     BufReader::new(&mut *conn).read_line(&mut line)?;
252///     conn.write_all(line.to_uppercase().as_bytes())?;
253///     Ok(())
254/// })?;
255/// server.wait_for_stop();
256/// # Ok::<(), net_io::NetError>(())
257/// ```
258pub fn serve<A, F>(addr: A, handler: F) -> Result<Server, NetError>
259where
260    A: ToSocketAddrs,
261    F: Fn(&mut TcpConnection) -> Result<(), NetError> + Send + Sync + 'static,
262{
263    let server = ServerBuilder::new()
264        .listener(ListenerSpec::tcp(addr))
265        .driver(DriverKind::Blocking)
266        .handler(handler)
267        .gate(AllowAll)
268        .build()?;
269    server.open_door()?;
270    Ok(server)
271}
272
273/// Starts a server that drives sans-IO [`Session`]s, with default limits and
274/// [`AllowAll`] as the gate; the door is open on return. Any
275/// `Fn(&ConnectionInfo) -> S` is a factory.
276///
277/// # Errors
278///
279/// As [`serve`].
280///
281/// # Examples
282///
283/// ```no_run
284/// use net_io::{Input, Session, SessionContext, Step};
285///
286/// struct Echo;
287/// impl Session for Echo {
288///     fn on_data(&mut self, cx: &mut SessionContext<'_>, input: &mut Input) -> Step {
289///         let taken = cx.output().push_partial(input.bytes());
290///         input.consume(taken);
291///         Step::Write
292///     }
293/// }
294///
295/// let server = net_io::serve_sessions("127.0.0.1:7002", |_info: &net_io::ConnectionInfo| Echo)?;
296/// server.wait_for_stop();
297/// # Ok::<(), net_io::NetError>(())
298/// ```
299pub fn serve_sessions<A, F>(addr: A, factory: F) -> Result<Server, NetError>
300where
301    A: ToSocketAddrs,
302    F: SessionFactory,
303{
304    let server = ServerBuilder::new()
305        .listener(ListenerSpec::tcp(addr))
306        .sessions(factory)
307        .gate(AllowAll)
308        .build()?;
309    server.open_door()?;
310    Ok(server)
311}
312
313/// Opens a local IPC listener: on Unix, a Unix domain socket at the path `name`
314/// with mode `0600` (only the owner may connect; [`LocalOptions`] and
315/// `UnixListener::bind_with` for more). On Windows, named pipes arrive in
316/// `0.5.0`; until then this returns `Unsupported`.
317///
318/// # Errors
319///
320/// `PathTooLong`, `StaleSocketInUse` (a live listener owns the path, or it is
321/// not a socket), `PermissionDenied`, `NotFound`, `Unsupported`, `Os`.
322///
323/// ```no_run
324/// let listener = net_io::listen_local("/run/myapp/control.sock")?;
325/// let (mut conn, info) = listener.accept()?;
326/// println!("peer process {:?}", info.peer_credentials.and_then(|c| c.pid));
327/// conn.send_all(b"ok\n")?;
328/// # Ok::<(), net_io::NetError>(())
329/// ```
330#[cfg(feature = "local")]
331pub fn listen_local(name: &str) -> Result<LocalListener, NetError> {
332    local::listen(name)
333}
334
335/// Connects to a local IPC listener (see [`listen_local`]); waits up to 10 s
336/// when its backlog is full.
337///
338/// # Errors
339///
340/// `NotFound` (no socket, or nobody listening), `PermissionDenied`,
341/// `TimedOut { kind: Connect }`, `PathTooLong`, `Unsupported`, `Os`.
342///
343/// ```no_run
344/// let mut conn = net_io::connect_local("/run/myapp/control.sock")?;
345/// conn.send_all(b"status\n")?;
346/// # Ok::<(), net_io::NetError>(())
347/// ```
348#[cfg(feature = "local")]
349pub fn connect_local(name: &str) -> Result<LocalConnection, NetError> {
350    local::connect(name)
351}
352
353#[cfg(test)]
354mod tests {
355    use super::VERSION;
356
357    #[test]
358    fn test_version_matches_manifest() {
359        assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
360        assert_eq!(VERSION.split('.').count(), 3, "expected MAJOR.MINOR.PATCH");
361    }
362}