use std::{
io::{ErrorKind, Read, Write},
net::SocketAddr,
};
use mio::net::{TcpListener, TcpStream, UdpSocket};
use rustls::{ProtocolVersion, ServerConnection};
use rusty_ulid::Ulid;
use socket2::{Domain, Protocol, Socket, Type};
use sozu_command::{config::MAX_LOOP_ITERATIONS, logging::ansi_palette, ready::Ready};
use crate::metrics::names;
#[derive(thiserror::Error, Debug)]
pub enum ServerBindError {
#[error("could not set bind to socket: {0}")]
BindError(std::io::Error),
#[error("could not listen on socket: {0}")]
Listen(std::io::Error),
#[error("could not set socket to nonblocking: {0}")]
SetNonBlocking(std::io::Error),
#[error("could not set reuse address: {0}")]
SetReuseAddress(std::io::Error),
#[error("could not set reuse address: {0}")]
SetReusePort(std::io::Error),
#[error("Could not create socket: {0}")]
SocketCreationError(std::io::Error),
#[error("Invalid socket address '{address}': {error}")]
InvalidSocketAddress { address: String, error: String },
#[error(
"could not bind the listener {address} to the network interface '{interface}' \
(SO_BINDTODEVICE): {error}. The interface must exist when the listener is activated, \
and on Linux kernels before 5.7 binding a socket to an interface requires the \
CAP_NET_RAW capability"
)]
BindToDevice {
address: SocketAddr,
interface: String,
error: std::io::Error,
},
#[error(
"the listener {address} names the network interface '{interface}', but binding a \
socket to an interface (SO_BINDTODEVICE) is only supported on Linux"
)]
BindToDeviceUnsupported {
address: SocketAddr,
interface: String,
},
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum SocketResult {
Continue,
Closed,
WouldBlock,
Error,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum TransportProtocol {
Tcp,
Ssl2,
Ssl3,
Tls1_0,
Tls1_1,
Tls1_2,
Tls1_3,
}
pub trait SocketHandler {
fn socket_read(&mut self, buf: &mut [u8]) -> (usize, SocketResult);
fn socket_write(&mut self, buf: &[u8]) -> (usize, SocketResult);
fn socket_write_vectored(&mut self, _buf: &[std::io::IoSlice]) -> (usize, SocketResult);
fn socket_wants_write(&self) -> bool {
false
}
fn socket_close(&mut self) {}
fn socket_write_then_close(&mut self, buf: &[u8]) -> (usize, SocketResult) {
let (size, status) = self.socket_write(buf);
if size == buf.len() {
self.socket_close();
}
(size, status)
}
fn socket_ref(&self) -> &TcpStream;
fn socket_mut(&mut self) -> &mut TcpStream;
fn peer_addr(&self) -> Option<SocketAddr>;
fn protocol(&self) -> TransportProtocol;
fn read_error(&self);
fn write_error(&self);
fn session_ulid(&self) -> Option<Ulid> {
None
}
}
macro_rules! log_socket_context {
($self:expr) => {{
let (open, reset, grey, gray, white) = ansi_palette();
let ulid = match $self.session_ulid() {
Some(ulid) => ulid.to_string(),
None => "-".to_string(),
};
let snapshot = crate::socket::stats::socket_snapshot($self.socket_ref());
let rtt = snapshot.as_ref().map(|s| s.rtt);
let state = snapshot.as_ref().map(|s| s.state);
format!(
"[{ulid} - - -]\t{open}SOCKET{reset}\t{grey}Session{reset}({gray}peer{reset}={white}{peer:?}{reset}, {gray}local{reset}={white}{local:?}{reset}, {gray}rtt{reset}={white}{rtt:?}{reset}, {gray}state{reset}={white}{state:?}{reset}, {gray}protocol{reset}={white}{protocol:?}{reset})\t >>>",
open = open,
reset = reset,
grey = grey,
gray = gray,
white = white,
ulid = ulid,
peer = crate::socket::SocketHandler::peer_addr($self),
local = $self.socket_ref().local_addr().ok(),
rtt = rtt,
state = state,
protocol = $self.protocol(),
)
}};
}
fn log_socket_module_prefix(
stream: &TcpStream,
session_ulid: Option<Ulid>,
configured_peer: Option<SocketAddr>,
) -> String {
let (open, reset, grey, gray, white) = ansi_palette();
let ulid = match session_ulid {
Some(ulid) => ulid.to_string(),
None => "-".to_string(),
};
let snapshot = crate::socket::stats::socket_snapshot(stream);
let rtt = snapshot.as_ref().map(|s| s.rtt);
let state = snapshot.as_ref().map(|s| s.state);
format!(
"[{ulid} - - -]\t{open}SOCKET{reset}\t{grey}Session{reset}({gray}peer{reset}={white}{peer:?}{reset}, {gray}local{reset}={white}{local:?}{reset}, {gray}rtt{reset}={white}{rtt:?}{reset}, {gray}state{reset}={white}{state:?}{reset}, {gray}protocol{reset}={white}Tcp{reset})\t >>>",
peer = configured_peer.or_else(|| stream.peer_addr().ok()),
local = stream.local_addr().ok(),
)
}
fn plain_socket_read<R: Read>(
stream: &mut R,
buf: &mut [u8],
) -> (usize, SocketResult, Option<std::io::Error>) {
if buf.is_empty() {
return (0, SocketResult::Continue, None);
}
let read = loop {
match stream.read(buf) {
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
read => break read,
}
};
match read {
Ok(0) => (0, SocketResult::Closed, None),
Ok(sz) => {
debug_assert!(
sz <= buf.len(),
"read reported {sz} bytes into a {}-byte slice",
buf.len()
);
if sz == buf.len() {
(sz, SocketResult::Continue, None)
} else {
(sz, SocketResult::WouldBlock, None)
}
}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => (0, SocketResult::WouldBlock, None),
ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted => {
incr!(names::tcp::READ_RESET);
(0, SocketResult::Closed, None)
}
ErrorKind::BrokenPipe | ErrorKind::ConnectionRefused => (0, SocketResult::Closed, None),
_ => (0, SocketResult::Error, Some(e)),
},
}
}
fn tcp_socket_read(
stream: &mut TcpStream,
buf: &mut [u8],
session_ulid: Option<Ulid>,
configured_peer: Option<SocketAddr>,
) -> (usize, SocketResult) {
let (size, result, fault) = plain_socket_read(stream, buf);
if let Some(e) = fault {
match e.kind() {
ErrorKind::HostUnreachable
| ErrorKind::NetworkUnreachable
| ErrorKind::TimedOut
| ErrorKind::NotConnected => warn!(
"{} socket_read error={:?}",
log_socket_module_prefix(stream, session_ulid, configured_peer),
e
),
_ => error!(
"{} socket_read error={:?}",
log_socket_module_prefix(stream, session_ulid, configured_peer),
e
),
}
}
(size, result)
}
#[derive(Debug)]
enum PlainWriteFault {
LoopLimit,
Io(std::io::Error),
}
fn plain_write_error(e: std::io::Error) -> (SocketResult, Option<PlainWriteFault>) {
match e.kind() {
ErrorKind::WouldBlock => (SocketResult::WouldBlock, None),
ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted => {
incr!(names::tcp::WRITE_RESET);
incr!(names::tcp::WRITE_ERROR);
(SocketResult::Closed, None)
}
ErrorKind::BrokenPipe | ErrorKind::ConnectionRefused => {
incr!(names::tcp::WRITE_ERROR);
(SocketResult::Closed, None)
}
_ => {
incr!(names::tcp::WRITE_ERROR);
(SocketResult::Error, Some(PlainWriteFault::Io(e)))
}
}
}
fn plain_socket_write<W: Write>(
stream: &mut W,
buf: &[u8],
) -> (usize, SocketResult, Option<PlainWriteFault>) {
let mut size = 0usize;
let mut counter = 0;
loop {
counter += 1;
if counter > MAX_LOOP_ITERATIONS {
incr!(names::socket::WRITE_INFINITE_LOOP_ERROR);
return (size, SocketResult::Error, Some(PlainWriteFault::LoopLimit));
}
debug_assert!(
size <= buf.len(),
"write cursor {size} overran buffer len {} (would slice out of bounds)",
buf.len()
);
if size == buf.len() {
return (size, SocketResult::Continue, None);
}
match stream.write(&buf[size..]) {
Ok(0) => return (size, SocketResult::Continue, None),
Ok(sz) => {
debug_assert!(
sz <= buf.len() - size,
"write reported {sz} bytes from a {}-byte remaining slice",
buf.len() - size
);
size += sz;
}
Err(e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => {
let (result, fault) = plain_write_error(e);
return (size, result, fault);
}
}
}
}
fn plain_socket_write_vectored<W: Write>(
stream: &mut W,
bufs: &[std::io::IoSlice],
) -> (usize, SocketResult, Option<PlainWriteFault>) {
let written = loop {
match stream.write_vectored(bufs) {
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
written => break written,
}
};
match written {
Ok(sz) => {
debug_assert!(
sz <= bufs.iter().map(|b| b.len()).sum::<usize>(),
"write_vectored reported {sz} bytes from {}-byte slices",
bufs.iter().map(|b| b.len()).sum::<usize>()
);
(sz, SocketResult::Continue, None)
}
Err(e) => {
let (result, fault) = plain_write_error(e);
(0, result, fault)
}
}
}
fn log_plain_write_fault(
stream: &TcpStream,
session_ulid: Option<Ulid>,
configured_peer: Option<SocketAddr>,
fault: PlainWriteFault,
) {
match fault {
PlainWriteFault::LoopLimit => error!(
"{} MAX_LOOP_ITERATION reached in TcpStream::socket_write",
log_socket_module_prefix(stream, session_ulid, configured_peer)
),
PlainWriteFault::Io(e) => match e.kind() {
ErrorKind::HostUnreachable
| ErrorKind::NetworkUnreachable
| ErrorKind::TimedOut
| ErrorKind::NotConnected => warn!(
"{} socket_write error={:?}",
log_socket_module_prefix(stream, session_ulid, configured_peer),
e
),
_ => error!(
"{} socket_write error={:?}",
log_socket_module_prefix(stream, session_ulid, configured_peer),
e
),
},
}
}
fn tcp_socket_write(
stream: &mut TcpStream,
buf: &[u8],
session_ulid: Option<Ulid>,
configured_peer: Option<SocketAddr>,
) -> (usize, SocketResult) {
let (size, result, fault) = plain_socket_write(stream, buf);
if let Some(fault) = fault {
log_plain_write_fault(stream, session_ulid, configured_peer, fault);
}
(size, result)
}
fn tcp_socket_write_vectored(
stream: &mut TcpStream,
bufs: &[std::io::IoSlice],
session_ulid: Option<Ulid>,
configured_peer: Option<SocketAddr>,
) -> (usize, SocketResult) {
let (size, result, fault) = plain_socket_write_vectored(stream, bufs);
if let Some(fault) = fault {
log_plain_write_fault(stream, session_ulid, configured_peer, fault);
}
(size, result)
}
impl SocketHandler for TcpStream {
fn socket_read(&mut self, buf: &mut [u8]) -> (usize, SocketResult) {
tcp_socket_read(self, buf, None, None)
}
fn socket_write(&mut self, buf: &[u8]) -> (usize, SocketResult) {
tcp_socket_write(self, buf, None, None)
}
fn socket_write_vectored(&mut self, bufs: &[std::io::IoSlice]) -> (usize, SocketResult) {
tcp_socket_write_vectored(self, bufs, None, None)
}
fn socket_ref(&self) -> &TcpStream {
self
}
fn socket_mut(&mut self) -> &mut TcpStream {
self
}
fn peer_addr(&self) -> Option<SocketAddr> {
TcpStream::peer_addr(self).ok()
}
fn protocol(&self) -> TransportProtocol {
TransportProtocol::Tcp
}
fn read_error(&self) {
incr!(names::tcp::READ_ERROR);
}
fn write_error(&self) {
incr!(names::tcp::WRITE_ERROR);
}
}
#[derive(Debug)]
pub struct SessionTcpStream {
pub stream: TcpStream,
pub session_ulid: Ulid,
pub configured_peer: Option<SocketAddr>,
}
impl SessionTcpStream {
pub fn new(stream: TcpStream, session_ulid: Ulid, configured_peer: Option<SocketAddr>) -> Self {
Self {
stream,
session_ulid,
configured_peer,
}
}
}
impl SocketHandler for SessionTcpStream {
fn socket_read(&mut self, buf: &mut [u8]) -> (usize, SocketResult) {
tcp_socket_read(
&mut self.stream,
buf,
Some(self.session_ulid),
self.configured_peer,
)
}
fn socket_write(&mut self, buf: &[u8]) -> (usize, SocketResult) {
tcp_socket_write(
&mut self.stream,
buf,
Some(self.session_ulid),
self.configured_peer,
)
}
fn socket_write_vectored(&mut self, bufs: &[std::io::IoSlice]) -> (usize, SocketResult) {
tcp_socket_write_vectored(
&mut self.stream,
bufs,
Some(self.session_ulid),
self.configured_peer,
)
}
fn socket_ref(&self) -> &TcpStream {
&self.stream
}
fn socket_mut(&mut self) -> &mut TcpStream {
&mut self.stream
}
fn peer_addr(&self) -> Option<SocketAddr> {
self.configured_peer
.or_else(|| self.stream.peer_addr().ok())
}
fn protocol(&self) -> TransportProtocol {
TransportProtocol::Tcp
}
fn read_error(&self) {
incr!(names::tcp::READ_ERROR);
}
fn write_error(&self) {
incr!(names::tcp::WRITE_ERROR);
}
fn session_ulid(&self) -> Option<Ulid> {
Some(self.session_ulid)
}
}
pub struct FrontRustls {
pub stream: TcpStream,
pub session: ServerConnection,
pub peer_disconnected: bool,
pub peer_reset: bool,
pub tls_fatal: bool,
pub session_ulid: Ulid,
pub configured_peer: Option<SocketAddr>,
pub recv_memory: RecvMemory,
}
impl FrontRustls {
pub fn readiness_delivered(&mut self, events: Ready) {
self.recv_memory.event_delivered(events);
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct RecvMemory {
drained: bool,
hup_seen: bool,
}
impl RecvMemory {
pub fn after_handshake(drained: bool) -> Self {
RecvMemory {
drained,
hup_seen: false,
}
}
fn event_delivered(&mut self, events: Ready) {
self.drained = false;
if events.is_hup() || events.is_error() {
self.hup_seen = true;
}
}
}
fn flush_tls(
session: &mut ServerConnection,
stream: &mut impl std::io::Write,
) -> std::io::Result<usize> {
#[cfg(test)]
if session.wants_write() {
TLS_WRITES.with(|writes| writes.set(writes.get() + 1));
}
let written = loop {
match session.write_tls(stream) {
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
written => break written,
}
};
if let Err(e) = &written
&& matches!(
e.kind(),
ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted
)
{
incr!(names::rustls::WRITE_RESET);
}
written
}
#[cfg(test)]
thread_local! {
static TLS_WRITES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
#[cfg(test)]
pub(crate) fn tls_writes() -> usize {
TLS_WRITES.with(std::cell::Cell::get)
}
impl std::fmt::Debug for FrontRustls {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FrontRustls")
.field("stream", &self.stream)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
enum RustlsReadFault {
LoopLimit,
ReadTls(std::io::Error),
ProcessPackets(rustls::Error),
Plaintext(std::io::Error),
}
pub(crate) struct ShortReadProbe<'a, R> {
inner: &'a mut R,
short: bool,
}
impl<'a, R> ShortReadProbe<'a, R> {
pub(crate) fn new(inner: &'a mut R) -> Self {
ShortReadProbe {
inner,
short: false,
}
}
pub(crate) fn was_short(&self) -> bool {
self.short
}
}
impl<R: Read> Read for ShortReadProbe<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let read = self.inner.read(buf)?;
self.short = 0 < read && read < buf.len();
Ok(read)
}
}
fn rustls_socket_read<R: Read>(
session: &mut ServerConnection,
stream: &mut R,
buf: &mut [u8],
peer_disconnected: &mut bool,
peer_reset: &mut bool,
tls_fatal: &mut bool,
memory: &mut RecvMemory,
) -> (usize, SocketResult, Option<RustlsReadFault>) {
if *tls_fatal {
return (0, SocketResult::Error, None);
}
let mut size = 0usize;
let mut can_read = true;
let mut drained = memory.drained && !memory.hup_seen;
let mut is_error = false;
let mut is_closed = false;
let mut fault = None;
let mut counter = 0;
loop {
counter += 1;
if counter > MAX_LOOP_ITERATIONS {
fault = Some(RustlsReadFault::LoopLimit);
is_error = true;
break;
}
while !session.wants_read() {
match session.reader().read(&mut buf[size..]) {
Ok(0) => break,
Ok(sz) => {
debug_assert!(
sz <= buf.len() - size,
"rustls reader returned {sz} bytes into a {}-byte remaining slice",
buf.len() - size
);
size += sz;
}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {
break;
}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
is_closed = true;
break;
}
_ => {
fault = Some(RustlsReadFault::Plaintext(e));
is_error = true;
break;
}
},
}
}
debug_assert!(
size <= buf.len(),
"rustls read cursor {size} overran buffer len {} (would slice out of bounds)",
buf.len()
);
if size == buf.len() {
break;
}
if !can_read | is_error | is_closed {
break;
}
if drained && session.wants_read() {
can_read = false;
break;
}
let mut probe = ShortReadProbe::new(&mut *stream);
let read = loop {
match session.read_tls(&mut probe) {
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
read => break read,
}
};
drained |= probe.was_short();
match read {
Ok(0) => {
can_read = false;
is_closed = true;
*peer_disconnected = true;
}
Ok(_sz) => {}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {
can_read = false;
drained = true;
}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
if e.kind() != ErrorKind::BrokenPipe {
incr!(names::rustls::READ_RESET);
}
is_closed = true;
*peer_disconnected = true;
*peer_reset = true;
}
ErrorKind::Other => {}
_ => {
fault = Some(RustlsReadFault::ReadTls(e));
is_error = true;
break;
}
},
}
if let Err(e) = session.process_new_packets() {
fault = Some(RustlsReadFault::ProcessPackets(e));
*tls_fatal = true;
is_error = true;
break;
}
}
debug_assert!(
size <= buf.len(),
"rustls socket_read returned {size} bytes for a {}-byte buffer",
buf.len()
);
debug_assert!(
!(is_error && is_closed),
"rustls socket_read cannot be both Error and Closed"
);
memory.drained = drained;
let result = if is_error {
SocketResult::Error
} else if is_closed {
SocketResult::Closed
} else if size == buf.len() {
SocketResult::Continue
} else if !can_read {
SocketResult::WouldBlock
} else {
SocketResult::Continue
};
(size, result, fault)
}
impl SocketHandler for FrontRustls {
fn socket_read(&mut self, buf: &mut [u8]) -> (usize, SocketResult) {
let (size, result, fault) = rustls_socket_read(
&mut self.session,
&mut self.stream,
buf,
&mut self.peer_disconnected,
&mut self.peer_reset,
&mut self.tls_fatal,
&mut self.recv_memory,
);
match fault {
None => {}
Some(RustlsReadFault::LoopLimit) => {
error!(
"{} MAX_LOOP_ITERATION reached in FrontRustls::socket_read",
log_socket_context!(self)
);
incr!(names::rustls::READ_INFINITE_LOOP_ERROR);
}
Some(RustlsReadFault::ReadTls(e)) => error!(
"{} could not read TLS stream from socket: {:?}",
log_socket_context!(self),
e
),
Some(RustlsReadFault::ProcessPackets(e)) => error!(
"{} could not process read TLS packets: {:?}",
log_socket_context!(self),
e
),
Some(RustlsReadFault::Plaintext(e)) => error!(
"{} could not read data from TLS stream: {:?}",
log_socket_context!(self),
e
),
}
(size, result)
}
fn socket_write(&mut self, buf: &[u8]) -> (usize, SocketResult) {
if self.peer_reset {
return (0, SocketResult::Closed);
}
let mut buffered_size = 0usize;
let mut can_write = true;
let mut is_error = false;
let mut is_closed = false;
let mut counter = 0;
loop {
counter += 1;
if counter > MAX_LOOP_ITERATIONS {
error!(
"{} MAX_LOOP_ITERATION reached in FrontRustls::socket_write",
log_socket_context!(self)
);
incr!(names::rustls::WRITE_INFINITE_LOOP_ERROR);
is_error = true;
break;
}
debug_assert!(
buffered_size <= buf.len(),
"rustls write cursor {buffered_size} overran buffer len {} (would slice out of bounds)",
buf.len()
);
if buffered_size == buf.len() {
break;
}
if !can_write | is_error | is_closed {
break;
}
match self.session.writer().write(&buf[buffered_size..]) {
Ok(0) => {} Ok(sz) => {
debug_assert!(
sz <= buf.len() - buffered_size,
"rustls writer absorbed {sz} bytes from a {}-byte remaining slice",
buf.len() - buffered_size
);
buffered_size += sz;
}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {
}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
incr!(names::rustls::WRITE_ERROR);
is_closed = true;
self.peer_reset = true;
break;
}
_ => {
error!(
"{} could not write data to TLS stream: {:?}",
log_socket_context!(self),
e
);
incr!(names::rustls::WRITE_ERROR);
is_error = true;
break;
}
},
}
loop {
match flush_tls(&mut self.session, &mut self.stream) {
Ok(0) => {
break;
}
Ok(_sz) => {}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {
can_write = false;
break;
}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
incr!(names::rustls::WRITE_ERROR);
is_closed = true;
self.peer_reset = true;
break;
}
_ => {
error!(
"{} could not write TLS stream to socket: {:?}",
log_socket_context!(self),
e
);
incr!(names::rustls::WRITE_ERROR);
is_error = true;
break;
}
},
}
}
}
if !is_error && !is_closed && can_write && self.session.wants_write() {
loop {
match flush_tls(&mut self.session, &mut self.stream) {
Ok(0) => break,
Ok(_) => {}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {
can_write = false;
break;
}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
incr!(names::rustls::WRITE_ERROR);
is_closed = true;
self.peer_reset = true;
break;
}
_ => {
error!(
"{} could not flush TLS stream to socket: {:?}",
log_socket_context!(self),
e
);
incr!(names::rustls::WRITE_ERROR);
is_error = true;
break;
}
},
}
}
}
debug_assert!(
buffered_size <= buf.len(),
"rustls socket_write reported {buffered_size} bytes for a {}-byte buffer",
buf.len()
);
debug_assert!(
!(is_error && is_closed),
"rustls socket_write cannot be both Error and Closed"
);
if is_error {
self.peer_reset = true;
(buffered_size, SocketResult::Error)
} else if is_closed {
(buffered_size, SocketResult::Closed)
} else if !can_write {
(buffered_size, SocketResult::WouldBlock)
} else {
(buffered_size, SocketResult::Continue)
}
}
fn socket_write_vectored(&mut self, bufs: &[std::io::IoSlice]) -> (usize, SocketResult) {
if self.peer_reset {
return (0, SocketResult::Closed);
}
let total_len: usize = bufs.iter().map(|b| b.len()).sum();
let mut buffered_size = 0usize;
let mut can_write = true;
let mut is_error = false;
let mut is_closed = false;
let mut counter = 0;
loop {
counter += 1;
if counter > MAX_LOOP_ITERATIONS {
error!(
"{} MAX_LOOP_ITERATION reached in FrontRustls::socket_write_vectored",
log_socket_context!(self)
);
incr!(names::rustls::WRITE_INFINITE_LOOP_ERROR);
is_error = true;
break;
}
debug_assert!(
buffered_size <= total_len,
"rustls vectored write cursor {buffered_size} overran total slice len {total_len}"
);
if buffered_size == total_len {
break;
}
if !can_write | is_error | is_closed {
break;
}
if buffered_size == 0 {
match self.session.writer().write_vectored(bufs) {
Ok(0) => {}
Ok(sz) => {
debug_assert!(
sz <= total_len,
"rustls writer absorbed {sz} bytes from {total_len}-byte slices"
);
buffered_size += sz;
}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
incr!(names::rustls::WRITE_ERROR);
is_closed = true;
self.peer_reset = true;
break;
}
_ => {
error!(
"{} could not write data to TLS stream: {:?}",
log_socket_context!(self),
e
);
incr!(names::rustls::WRITE_ERROR);
is_error = true;
break;
}
},
}
}
if buffered_size > 0 && buffered_size < total_len {
loop {
match flush_tls(&mut self.session, &mut self.stream) {
Ok(0) => break,
Ok(_) => {}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {
can_write = false;
break;
}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
incr!(names::rustls::WRITE_ERROR);
is_closed = true;
self.peer_reset = true;
break;
}
_ => {
error!(
"{} could not write TLS stream to socket: {:?}",
log_socket_context!(self),
e
);
incr!(names::rustls::WRITE_ERROR);
is_error = true;
break;
}
},
}
}
break;
}
loop {
match flush_tls(&mut self.session, &mut self.stream) {
Ok(0) => {
break;
}
Ok(_sz) => {}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {
can_write = false;
break;
}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
incr!(names::rustls::WRITE_ERROR);
is_closed = true;
self.peer_reset = true;
break;
}
_ => {
error!(
"{} could not write TLS stream to socket: {:?}",
log_socket_context!(self),
e
);
incr!(names::rustls::WRITE_ERROR);
is_error = true;
break;
}
},
}
}
}
if !is_error && !is_closed && can_write && self.session.wants_write() {
loop {
match flush_tls(&mut self.session, &mut self.stream) {
Ok(0) => break,
Ok(_) => {}
Err(e) => match e.kind() {
ErrorKind::WouldBlock => {
can_write = false;
break;
}
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe => {
incr!(names::rustls::WRITE_ERROR);
is_closed = true;
self.peer_reset = true;
break;
}
_ => {
error!(
"{} could not flush TLS stream to socket: {:?}",
log_socket_context!(self),
e
);
incr!(names::rustls::WRITE_ERROR);
is_error = true;
break;
}
},
}
}
}
debug_assert!(
buffered_size <= total_len,
"rustls socket_write_vectored reported {buffered_size} bytes for {total_len}-byte slices"
);
debug_assert!(
!(is_error && is_closed),
"rustls socket_write_vectored cannot be both Error and Closed"
);
if is_error {
self.peer_reset = true;
(buffered_size, SocketResult::Error)
} else if is_closed {
(buffered_size, SocketResult::Closed)
} else if !can_write {
(buffered_size, SocketResult::WouldBlock)
} else {
(buffered_size, SocketResult::Continue)
}
}
fn socket_close(&mut self) {
self.session.send_close_notify();
}
fn socket_write_then_close(&mut self, buf: &[u8]) -> (usize, SocketResult) {
if self.peer_reset {
return (0, SocketResult::Closed);
}
match self.session.writer().write(buf) {
Ok(absorbed) => {
if absorbed == buf.len() {
self.session.send_close_notify();
}
let (_flushed, status) = self.socket_write(&[]);
(absorbed, status)
}
Err(_) => self.socket_write(buf),
}
}
fn socket_wants_write(&self) -> bool {
!self.peer_reset && self.session.wants_write()
}
fn socket_ref(&self) -> &TcpStream {
&self.stream
}
fn socket_mut(&mut self) -> &mut TcpStream {
&mut self.stream
}
fn peer_addr(&self) -> Option<SocketAddr> {
self.configured_peer
.or_else(|| self.stream.peer_addr().ok())
}
fn protocol(&self) -> TransportProtocol {
self.session
.protocol_version()
.map(|version| match version {
ProtocolVersion::SSLv2 => TransportProtocol::Ssl2,
ProtocolVersion::SSLv3 => TransportProtocol::Ssl3,
ProtocolVersion::TLSv1_0 => TransportProtocol::Tls1_0,
ProtocolVersion::TLSv1_1 => TransportProtocol::Tls1_1,
ProtocolVersion::TLSv1_2 => TransportProtocol::Tls1_2,
ProtocolVersion::TLSv1_3 => TransportProtocol::Tls1_3,
_ => TransportProtocol::Tls1_3,
})
.unwrap_or(TransportProtocol::Tcp)
}
fn read_error(&self) {
incr!(names::rustls::READ_ERROR);
}
fn write_error(&self) {
incr!(names::rustls::WRITE_ERROR);
}
fn session_ulid(&self) -> Option<Ulid> {
Some(self.session_ulid)
}
}
fn bind_to_device(
sock: &Socket,
addr: SocketAddr,
interface: Option<&str>,
) -> Result<(), ServerBindError> {
let Some(interface) = interface else {
return Ok(());
};
#[cfg(target_os = "linux")]
{
sock.bind_device(Some(interface.as_bytes()))
.map_err(|error| ServerBindError::BindToDevice {
address: addr,
interface: interface.to_owned(),
error,
})?;
debug_assert!(
sock.device().map_or(true, |device| device.as_deref()
== Some(interface.as_bytes())),
"a socket bound to an interface must report that interface"
);
Ok(())
}
#[cfg(not(target_os = "linux"))]
{
let _ = sock;
Err(ServerBindError::BindToDeviceUnsupported {
address: addr,
interface: interface.to_owned(),
})
}
}
pub fn server_bind(
addr: SocketAddr,
interface: Option<&str>,
) -> Result<TcpListener, ServerBindError> {
let sock = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))
.map_err(ServerBindError::SocketCreationError)?;
bind_to_device(&sock, addr, interface)?;
if cfg!(unix) {
sock.set_reuse_address(true)
.map_err(ServerBindError::SetReuseAddress)?;
}
sock.set_reuse_port(true)
.map_err(ServerBindError::SetReusePort)?;
sock.bind(&addr.into())
.map_err(ServerBindError::BindError)?;
sock.set_nonblocking(true)
.map_err(ServerBindError::SetNonBlocking)?;
sock.listen(1024).map_err(ServerBindError::Listen)?;
if let Ok(nonblocking) = sock.nonblocking() {
debug_assert!(
nonblocking,
"server_bind must return a non-blocking socket (the worker event loop is edge-triggered)"
);
}
#[cfg(unix)]
if let Ok(reuse_port) = sock.reuse_port() {
debug_assert!(
reuse_port,
"server_bind must set SO_REUSEPORT so the listener survives a hot-upgrade re-bind"
);
}
#[cfg(unix)]
if let Ok(reuse_address) = sock.reuse_address() {
debug_assert!(
reuse_address,
"server_bind must set SO_REUSEADDR on unix (mirrors libstd)"
);
}
if let Ok(local) = sock.local_addr() {
debug_assert_eq!(
local.is_ipv4(),
addr.is_ipv4(),
"bound socket family must match the requested address family"
);
debug_assert_eq!(
local.is_ipv6(),
addr.is_ipv6(),
"bound socket family must match the requested address family"
);
}
Ok(TcpListener::from_std(sock.into()))
}
pub fn udp_bind(addr: SocketAddr, interface: Option<&str>) -> Result<UdpSocket, ServerBindError> {
let sock = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))
.map_err(ServerBindError::SocketCreationError)?;
bind_to_device(&sock, addr, interface)?;
if cfg!(unix) {
sock.set_reuse_address(true)
.map_err(ServerBindError::SetReuseAddress)?;
}
sock.set_reuse_port(true)
.map_err(ServerBindError::SetReusePort)?;
sock.bind(&addr.into())
.map_err(ServerBindError::BindError)?;
sock.set_nonblocking(true)
.map_err(ServerBindError::SetNonBlocking)?;
if let Ok(nonblocking) = sock.nonblocking() {
debug_assert!(
nonblocking,
"udp_bind must return a non-blocking socket (the worker event loop is edge-triggered)"
);
}
#[cfg(unix)]
if let Ok(reuse_port) = sock.reuse_port() {
debug_assert!(
reuse_port,
"udp_bind must set SO_REUSEPORT so the listener survives a hot-upgrade re-bind"
);
}
#[cfg(unix)]
if let Ok(reuse_address) = sock.reuse_address() {
debug_assert!(
reuse_address,
"udp_bind must set SO_REUSEADDR on unix (mirrors libstd / server_bind)"
);
}
if let Ok(local) = sock.local_addr() {
debug_assert_eq!(
local.is_ipv4(),
addr.is_ipv4(),
"bound UDP socket family must match the requested address family"
);
debug_assert_eq!(
local.is_ipv6(),
addr.is_ipv6(),
"bound UDP socket family must match the requested address family"
);
}
Ok(UdpSocket::from_std(sock.into()))
}
pub fn udp_connect(backend: SocketAddr) -> Result<UdpSocket, ServerBindError> {
let unspecified: SocketAddr = match backend {
SocketAddr::V4(_) => (std::net::Ipv4Addr::UNSPECIFIED, 0).into(),
SocketAddr::V6(_) => (std::net::Ipv6Addr::UNSPECIFIED, 0).into(),
};
debug_assert_eq!(
unspecified.is_ipv4(),
backend.is_ipv4(),
"ephemeral bind family must match the backend family"
);
debug_assert_eq!(
unspecified.port(),
0,
"ephemeral bind must use port 0 so the kernel picks the source port"
);
#[cfg(target_os = "linux")]
let ty = Type::DGRAM.nonblocking();
#[cfg(not(target_os = "linux"))]
let ty = Type::DGRAM;
let sock = Socket::new(Domain::for_address(backend), ty, Some(Protocol::UDP))
.map_err(ServerBindError::SocketCreationError)?;
sock.bind(&unspecified.into())
.map_err(ServerBindError::BindError)?;
#[cfg(not(target_os = "linux"))]
sock.set_nonblocking(true)
.map_err(ServerBindError::SetNonBlocking)?;
sock.connect(&backend.into())
.map_err(ServerBindError::BindError)?;
#[cfg(debug_assertions)]
{
if let Ok(nonblocking) = sock.nonblocking() {
debug_assert!(
nonblocking,
"udp_connect must return a non-blocking socket (the worker event loop is edge-triggered)"
);
}
if let Ok(local) = sock.local_addr() {
debug_assert_eq!(
local.is_ipv4(),
backend.is_ipv4(),
"connected UDP socket family must match the backend family"
);
if let Some(local) = local.as_socket() {
debug_assert_ne!(
local.port(),
0,
"connect must bind a concrete ephemeral source port (the return-demux key)"
);
}
}
if let Ok(peer) = sock.peer_addr()
&& let Some(peer) = peer.as_socket()
{
debug_assert_eq!(
peer, backend,
"connect must pin the peer to the requested backend (symmetric-NAT return-demux key)"
);
}
}
Ok(UdpSocket::from_std(sock.into()))
}
pub mod stats {
use std::{os::fd::AsRawFd, time::Duration};
use internal::{OPT_LEVEL, OPT_NAME, TcpInfo};
#[derive(Clone, Debug)]
pub struct TcpSnapshot {
pub rtt: Duration,
pub state: &'static str,
}
pub fn socket_rtt<A: AsRawFd>(socket: &A) -> Option<Duration> {
socket_info(socket.as_raw_fd()).map(|info| Duration::from_micros(info.rtt() as u64))
}
pub fn socket_snapshot<A: AsRawFd>(socket: &A) -> Option<TcpSnapshot> {
socket_info(socket.as_raw_fd()).map(|info| TcpSnapshot {
rtt: Duration::from_micros(info.rtt() as u64),
state: info.state(),
})
}
#[cfg(unix)]
pub fn socket_info(fd: libc::c_int) -> Option<TcpInfo> {
let mut tcp_info: TcpInfo = unsafe { std::mem::zeroed() };
let struct_len = std::mem::size_of::<TcpInfo>() as libc::socklen_t;
let mut len = struct_len;
let status = unsafe {
libc::getsockopt(
fd,
OPT_LEVEL,
OPT_NAME,
&mut tcp_info as *mut _ as *mut _,
&mut len,
)
};
if status != 0 {
None
} else {
debug_assert!(
len <= struct_len,
"getsockopt(TCP_INFO) wrote back len {len} > struct size {struct_len} (buffer overrun)"
);
Some(tcp_info)
}
}
#[cfg(not(unix))]
pub fn socketinfo(fd: libc::c_int) -> Option<TcpInfo> {
None
}
#[cfg(unix)]
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
mod internal {
#[cfg(target_os = "linux")]
pub const OPT_LEVEL: libc::c_int = libc::SOL_TCP;
#[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd"
))]
pub const OPT_LEVEL: libc::c_int = libc::IPPROTO_TCP;
pub const OPT_NAME: libc::c_int = libc::TCP_INFO;
#[derive(Clone, Debug)]
#[repr(C)]
pub struct TcpInfo {
tcpi_state: u8,
tcpi_ca_state: u8,
tcpi_retransmits: u8,
tcpi_probes: u8,
tcpi_backoff: u8,
tcpi_options: u8,
tcpi_snd_rcv_wscale: u8,
tcpi_rto: u32,
tcpi_ato: u32,
tcpi_snd_mss: u32,
tcpi_rcv_mss: u32,
tcpi_unacked: u32,
tcpi_sacked: u32,
tcpi_lost: u32,
tcpi_retrans: u32,
tcpi_fackets: u32,
tcpi_last_data_sent: u32,
tcpi_last_ack_sent: u32, tcpi_last_data_recv: u32,
tcpi_last_ack_recv: u32,
tcpi_pmtu: u32,
tcpi_rcv_ssthresh: u32,
tcpi_rtt: u32,
tcpi_rttvar: u32,
tcpi_snd_ssthresh: u32,
tcpi_snd_cwnd: u32,
tcpi_advmss: u32,
tcpi_reordering: u32,
}
impl TcpInfo {
pub fn rtt(&self) -> u32 {
self.tcpi_rtt
}
pub fn state(&self) -> &'static str {
match self.tcpi_state {
1 => "ESTABLISHED",
2 => "SYN_SENT",
3 => "SYN_RECV",
4 => "FIN_WAIT1",
5 => "FIN_WAIT2",
6 => "TIME_WAIT",
7 => "CLOSE",
8 => "CLOSE_WAIT",
9 => "LAST_ACK",
10 => "LISTEN",
11 => "CLOSING",
12 => "NEW_SYN_RECV",
_ => "UNKNOWN",
}
}
}
}
#[cfg(unix)]
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod internal {
pub const OPT_LEVEL: libc::c_int = libc::IPPROTO_TCP;
pub const OPT_NAME: libc::c_int = 0x106;
#[derive(Clone, Debug)]
#[repr(C)]
pub struct TcpInfo {
tcpi_state: u8,
tcpi_snd_wscale: u8,
tcpi_rcv_wscale: u8,
__pad1: u8,
tcpi_options: u32,
tcpi_flags: u32,
tcpi_rto: u32,
tcpi_maxseg: u32,
tcpi_snd_ssthresh: u32,
tcpi_snd_cwnd: u32,
tcpi_snd_wnd: u32,
tcpi_snd_sbbytes: u32,
tcpi_rcv_wnd: u32,
tcpi_rttcur: u32,
tcpi_srtt: u32,
tcpi_rttvar: u32,
tcpi_tfo: u32,
tcpi_txpackets: u64,
tcpi_txbytes: u64,
tcpi_txretransmitbytes: u64,
tcpi_rxpackets: u64,
tcpi_rxbytes: u64,
tcpi_rxoutoforderbytes: u64,
tcpi_txretransmitpackets: u64,
}
impl TcpInfo {
pub fn rtt(&self) -> u32 {
self.tcpi_srtt * 1000
}
pub fn state(&self) -> &'static str {
match self.tcpi_state {
0 => "CLOSED",
1 => "LISTEN",
2 => "SYN_SENT",
3 => "SYN_RECEIVED",
4 => "ESTABLISHED",
5 => "CLOSE_WAIT",
6 => "FIN_WAIT_1",
7 => "CLOSING",
8 => "LAST_ACK",
9 => "FIN_WAIT_2",
10 => "TIME_WAIT",
_ => "UNKNOWN",
}
}
}
}
#[cfg(not(unix))]
#[derive(Clone, Debug)]
struct TcpInfo {}
#[test]
#[serial_test::serial]
fn test_rtt() {
let sock = std::net::TcpStream::connect("google.com:80").unwrap();
let fd = sock.as_raw_fd();
let info = socket_info(fd);
assert!(info.is_some());
println!("{info:#?}");
println!(
"rtt: {}",
sozu_command::logging::LogDuration(socket_rtt(&sock))
);
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
#[cfg(target_os = "linux")]
fn lacks_bind_to_device_permission(error: &ServerBindError) -> bool {
matches!(
error,
ServerBindError::BindToDevice { error, .. }
if error.kind() == std::io::ErrorKind::PermissionDenied
)
}
#[cfg(target_os = "linux")]
#[test]
fn bind_sets_so_bindtodevice_to_the_listener_interface() {
use socket2::SockRef;
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let tcp = match server_bind(addr, Some("lo")) {
Ok(listener) => listener,
Err(error) if lacks_bind_to_device_permission(&error) => {
eprintln!(
"SKIPPED bind_sets_so_bindtodevice_to_the_listener_interface: this \
environment may not bind a socket to an interface (needs CAP_NET_RAW \
before Linux 5.7): {error}"
);
return;
}
Err(error) => panic!("binding a TCP listener to lo: {error}"),
};
assert_eq!(
SockRef::from(&tcp)
.device()
.expect("getsockopt(SO_BINDTODEVICE)"),
Some(b"lo".to_vec()),
"the TCP listener must be bound to lo"
);
let udp = udp_bind(addr, Some("lo")).expect("binding a UDP listener to lo");
assert_eq!(
SockRef::from(&udp)
.device()
.expect("getsockopt(SO_BINDTODEVICE)"),
Some(b"lo".to_vec()),
"the UDP listener must be bound to lo"
);
let plain = server_bind(addr, None).expect("binding a TCP listener");
assert_eq!(
SockRef::from(&plain)
.device()
.expect("getsockopt(SO_BINDTODEVICE)"),
None,
"a listener without interface stays bound to no interface"
);
}
#[cfg(target_os = "linux")]
#[test]
fn bind_to_a_missing_interface_names_the_interface_and_the_capability() {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
for result in [
server_bind(addr, Some("sozu-nosuch0")).map(|_| ()),
udp_bind(addr, Some("sozu-nosuch0")).map(|_| ()),
] {
let error = result.expect_err("binding to a missing interface must fail");
assert!(
matches!(&error, ServerBindError::BindToDevice { interface, .. } if interface == "sozu-nosuch0"),
"{error:?}"
);
let message = error.to_string();
for needle in [
"127.0.0.1:0",
"'sozu-nosuch0'",
"SO_BINDTODEVICE",
"must exist",
"CAP_NET_RAW",
"5.7",
] {
assert!(
message.contains(needle),
"{needle:?} missing from: {message}"
);
}
}
}
const CACHED_PEER: &str = "10.0.0.42:12345";
fn connected_loopback_stream() -> (std::net::TcpListener, TcpStream, SocketAddr) {
let listener = std::net::TcpListener::bind("127.0.0.1:0")
.expect("test listener must bind to a loopback port");
let live_peer = listener
.local_addr()
.expect("test listener must report its local address");
let stream =
std::net::TcpStream::connect(live_peer).expect("loopback connect must complete");
stream
.set_nonblocking(true)
.expect("mio requires a nonblocking stream");
(listener, TcpStream::from_std(stream), live_peer)
}
#[test]
fn session_tcp_stream_peer_addr_prefers_the_cached_address() {
let (_listener, stream, live_peer) = connected_loopback_stream();
let cached: SocketAddr = CACHED_PEER
.parse()
.expect("the cached peer literal must parse");
let handler = SessionTcpStream::new(stream, Ulid::generate(), Some(cached));
assert_eq!(
handler.socket_ref().peer_addr().ok(),
Some(live_peer),
"the test socket must be genuinely connected, so a live lookup succeeds"
);
assert_ne!(
cached, live_peer,
"the cached and live addresses must differ for this test to discriminate"
);
assert_eq!(
handler.peer_addr(),
Some(cached),
"a cached peer address must win over a live getpeername(2)"
);
}
#[test]
fn session_tcp_stream_peer_addr_falls_back_to_the_live_lookup() {
let (_listener, stream, live_peer) = connected_loopback_stream();
let handler = SessionTcpStream::new(stream, Ulid::generate(), None);
assert_eq!(
handler.peer_addr(),
Some(live_peer),
"with nothing cached, peer_addr must degrade to getpeername(2)"
);
}
#[test]
fn raw_tcp_stream_peer_addr_is_the_live_lookup() {
let (_listener, stream, live_peer) = connected_loopback_stream();
assert_eq!(
SocketHandler::peer_addr(&stream),
Some(live_peer),
"the contextless handler must report the kernel's answer"
);
}
#[test]
fn log_socket_context_renders_the_cached_peer_not_a_live_lookup() {
let (_listener, stream, live_peer) = connected_loopback_stream();
let cached: SocketAddr = CACHED_PEER
.parse()
.expect("the cached peer literal must parse");
let handler = SessionTcpStream::new(stream, Ulid::generate(), Some(cached));
assert_eq!(
handler.socket_ref().peer_addr().ok(),
Some(live_peer),
"the test socket must be genuinely connected, so a live lookup succeeds"
);
assert_ne!(
cached, live_peer,
"the cached and live addresses must differ for this test to discriminate"
);
let rendered = log_socket_context!(&handler);
assert!(
rendered.contains(&format!("peer=Some({cached})")),
"the SOCKET peer= slot must render the cached address; rendered: {rendered}"
);
assert!(
!rendered.contains(&format!("peer=Some({live_peer})")),
"the SOCKET peer= slot must not render a live getpeername(2); rendered: {rendered}"
);
assert!(
rendered.contains(&format!(
"local=Some({})",
handler
.socket_ref()
.local_addr()
.expect("a connected socket has a local address")
)),
"the SOCKET local= slot must stay getsockname(2); rendered: {rendered}"
);
}
fn unconnected_stream() -> TcpStream {
let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))
.expect("a test socket must be creatable");
socket
.set_nonblocking(true)
.expect("mio requires a nonblocking stream");
TcpStream::from_std(std::net::TcpStream::from(socket))
}
#[test]
fn log_socket_context_renders_the_cached_peer_when_the_live_lookup_fails() {
let stream = unconnected_stream();
let cached: SocketAddr = CACHED_PEER
.parse()
.expect("the cached peer literal must parse");
assert_eq!(
stream.peer_addr().ok(),
None,
"an unconnected socket must refuse getpeername(2)"
);
let handler = SessionTcpStream::new(stream, Ulid::generate(), Some(cached));
let rendered = log_socket_context!(&handler);
assert!(
rendered.contains(&format!("peer=Some({cached})")),
"the SOCKET peer= slot must survive a failed live lookup; rendered: {rendered}"
);
assert!(
!rendered.contains("peer=None"),
"the SOCKET peer= slot must not collapse to None while a cache exists; \
rendered: {rendered}"
);
}
#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
#[test]
fn udp_connect_is_born_nonblocking_and_skips_release_queries() {
use std::os::fd::{AsRawFd as _, FromRawFd as _, OwnedFd};
const WATCHED: [libc::c_long; 3] = [
libc::SYS_fcntl,
libc::SYS_getsockname,
libc::SYS_getpeername,
];
let backend_sock =
std::net::UdpSocket::bind("127.0.0.1:0").expect("the test backend must bind");
let backend = backend_sock
.local_addr()
.expect("the test backend must report its address");
let (listener_tx, listener_rx) = std::sync::mpsc::channel::<OwnedFd>();
let worker = std::thread::spawn(move || {
let stmt = |code: u32, k: u32| libc::sock_filter {
code: code as u16,
jt: 0,
jf: 0,
k,
};
let jeq = |k: libc::c_long, jt: u8| libc::sock_filter {
code: (libc::BPF_JMP | libc::BPF_JEQ | libc::BPF_K) as u16,
jt,
jf: 0,
k: k as u32,
};
let mut program = [
stmt(libc::BPF_LD | libc::BPF_W | libc::BPF_ABS, 0),
jeq(WATCHED[0], 3),
jeq(WATCHED[1], 2),
jeq(WATCHED[2], 1),
stmt(libc::BPF_RET | libc::BPF_K, libc::SECCOMP_RET_ALLOW),
stmt(libc::BPF_RET | libc::BPF_K, libc::SECCOMP_RET_USER_NOTIF),
];
let fprog = libc::sock_fprog {
len: program.len() as u16,
filter: program.as_mut_ptr(),
};
let listener = unsafe {
assert_eq!(
libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0),
0,
"PR_SET_NO_NEW_PRIVS: {}",
std::io::Error::last_os_error()
);
libc::syscall(
libc::SYS_seccomp,
libc::SECCOMP_SET_MODE_FILTER,
libc::SECCOMP_FILTER_FLAG_NEW_LISTENER,
&fprog as *const libc::sock_fprog,
)
};
assert!(
listener >= 0,
"installing the seccomp filter failed: {}",
std::io::Error::last_os_error()
);
let listener = unsafe { OwnedFd::from_raw_fd(listener as libc::c_int) };
listener_tx
.send(listener)
.expect("the counting thread must receive the listener");
udp_connect(backend)
});
let listener = listener_rx
.recv()
.expect("the worker must hand over the seccomp listener");
let mut counts = [0usize; 3];
loop {
let mut pollfd = libc::pollfd {
fd: listener.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
let ready = unsafe { libc::poll(&mut pollfd, 1, 10_000) };
assert!(ready > 0, "no seccomp notification or hang-up within 10 s");
if pollfd.revents & libc::POLLIN == 0 {
break;
}
let mut notif: libc::seccomp_notif = unsafe { std::mem::zeroed() };
if unsafe {
libc::ioctl(
listener.as_raw_fd(),
libc::SECCOMP_IOCTL_NOTIF_RECV,
&mut notif,
)
} != 0
{
let error = std::io::Error::last_os_error();
assert_eq!(
error.raw_os_error(),
Some(libc::ENOENT),
"SECCOMP_IOCTL_NOTIF_RECV: {error}"
);
continue;
}
let nr = libc::c_long::from(notif.data.nr);
let slot = WATCHED
.iter()
.position(|watched| *watched == nr)
.expect("the filter only reports watched syscalls");
counts[slot] += 1;
let mut resp: libc::seccomp_notif_resp = unsafe { std::mem::zeroed() };
resp.id = notif.id;
resp.flags = libc::SECCOMP_USER_NOTIF_FLAG_CONTINUE as u32;
let sent = unsafe {
libc::ioctl(
listener.as_raw_fd(),
libc::SECCOMP_IOCTL_NOTIF_SEND,
&mut resp,
)
};
assert_eq!(
sent,
0,
"SECCOMP_IOCTL_NOTIF_SEND: {}",
std::io::Error::last_os_error()
);
}
let sock = worker
.join()
.expect("the filtered thread must not panic")
.expect("udp_connect must succeed toward a bound loopback backend");
let expected = if cfg!(debug_assertions) {
[1, 1, 1]
} else {
[0, 0, 0]
};
assert_eq!(
counts,
expected,
"udp_connect (fcntl, getsockname, getpeername) calls, debug_assertions = {}",
cfg!(debug_assertions)
);
let fd = sock.as_raw_fd();
let (status, fd_flags) = unsafe {
(
libc::fcntl(fd, libc::F_GETFL),
libc::fcntl(fd, libc::F_GETFD),
)
};
assert!(status >= 0 && fd_flags >= 0, "fcntl on the returned socket");
assert_ne!(
status & libc::O_NONBLOCK,
0,
"the socket must be non-blocking"
);
assert_ne!(
fd_flags & libc::FD_CLOEXEC,
0,
"the socket must be close-on-exec"
);
let local = sock
.local_addr()
.expect("the socket must report its local address");
assert!(local.is_ipv4(), "the local family must match the backend's");
assert_ne!(local.port(), 0, "connect must pick a concrete source port");
assert_eq!(
sock.peer_addr().expect("the socket must be connected"),
backend,
"connect must pin the peer to the backend"
);
}
pub(crate) fn proxy_count(name: &str) -> i64 {
use sozu_command::proto::command::filtered_metrics::Inner;
crate::metrics::METRICS.with(|metrics| {
metrics
.borrow_mut()
.dump_local_proxy_metrics()
.get(name)
.and_then(|metric| match metric.inner {
Some(Inner::Count(value)) => Some(value),
_ => None,
})
.unwrap_or(0)
})
}
pub(crate) fn reset_stream() -> TcpStream {
let listener = std::net::TcpListener::bind("127.0.0.1:0")
.expect("test listener must bind to a loopback port");
let stream = std::net::TcpStream::connect(
listener
.local_addr()
.expect("test listener must report its local address"),
)
.expect("loopback connect must complete");
let (peer, _) = listener.accept().expect("loopback accept must complete");
socket2::SockRef::from(&peer)
.set_linger(Some(std::time::Duration::ZERO))
.expect("SO_LINGER must accept a zero timeout");
drop(peer);
let mut pollfd = libc::pollfd {
fd: std::os::unix::io::AsRawFd::as_raw_fd(&stream),
events: libc::POLLIN,
revents: 0,
};
let ready = unsafe { libc::poll(&mut pollfd, 1, 5_000) };
assert_eq!(ready, 1, "the peer's RST must arrive within 5 s");
assert_ne!(
pollfd.revents & libc::POLLERR,
0,
"premise: the peer must have reset the connection, not closed it"
);
stream
.set_nonblocking(true)
.expect("mio requires a nonblocking stream");
TcpStream::from_std(stream)
}
#[test]
fn a_plain_read_counts_a_peer_reset_once() {
let mut stream = reset_stream();
let before = proxy_count(names::tcp::READ_RESET);
let mut buf = [0u8; 64];
let (size, result) = stream.socket_read(&mut buf);
assert_eq!((size, result), (0, SocketResult::Closed));
assert_eq!(proxy_count(names::tcp::READ_RESET) - before, 1);
let (size, result) = stream.socket_read(&mut buf);
assert_eq!((size, result), (0, SocketResult::Closed));
assert_eq!(
proxy_count(names::tcp::READ_RESET) - before,
1,
"the EOF after the reset is not another reset"
);
}
#[test]
fn a_plain_write_counts_a_peer_reset_once() {
let mut stream = reset_stream();
let resets = proxy_count(names::tcp::WRITE_RESET);
let errors = proxy_count(names::tcp::WRITE_ERROR);
let (size, result) = stream.socket_write(b"after the reset");
assert_eq!((size, result), (0, SocketResult::Closed));
assert_eq!(proxy_count(names::tcp::WRITE_RESET) - resets, 1);
assert_eq!(proxy_count(names::tcp::WRITE_ERROR) - errors, 1);
let (size, result) = stream.socket_write(b"after the reset");
assert_eq!((size, result), (0, SocketResult::Closed));
assert_eq!(
proxy_count(names::tcp::WRITE_RESET) - resets,
1,
"the EPIPE after the reset is not another reset"
);
}
#[test]
fn a_plain_vectored_write_counts_a_peer_reset() {
let mut stream = reset_stream();
let before = proxy_count(names::tcp::WRITE_RESET);
let (size, result) =
stream.socket_write_vectored(&[std::io::IoSlice::new(b"after the reset")]);
assert_eq!((size, result), (0, SocketResult::Closed));
assert_eq!(proxy_count(names::tcp::WRITE_RESET) - before, 1);
}
}
#[cfg(test)]
pub(crate) mod rustls_read_tests {
use std::{collections::VecDeque, io::Write, sync::Arc};
use super::*;
pub(crate) const TEST_SNI: &str = "lolcatho.st";
const FRAME_HEADER_LEN: usize = 9;
fn client_frames() -> Vec<Vec<u8>> {
[6usize, 4, 37, 8]
.iter()
.enumerate()
.flat_map(|(index, &payload_len)| {
let mut header = vec![0u8; FRAME_HEADER_LEN];
header[..3].copy_from_slice(&(payload_len as u32).to_be_bytes()[1..]);
header[3] = index as u8;
let payload = (0..payload_len)
.map(|byte| (index * 64 + byte) as u8)
.collect();
[header, payload]
})
.collect()
}
#[derive(Default)]
pub(crate) struct CountingTransport {
pub(crate) wire: VecDeque<u8>,
pub(crate) eof: bool,
pub(crate) reset: bool,
pub(crate) reads: usize,
pub(crate) eagains: usize,
pub(crate) interrupts: usize,
}
impl Read for CountingTransport {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.reads += 1;
if self.interrupts > 0 {
self.interrupts -= 1;
return Err(ErrorKind::Interrupted.into());
}
if self.reset {
return Err(ErrorKind::ConnectionReset.into());
}
if self.wire.is_empty() {
return if self.eof {
Ok(0)
} else {
self.eagains += 1;
Err(ErrorKind::WouldBlock.into())
};
}
let len = buf.len().min(self.wire.len());
for (slot, byte) in buf.iter_mut().zip(self.wire.drain(..len)) {
*slot = byte;
}
Ok(len)
}
}
#[derive(Debug)]
struct AcceptAnyServerCertificate;
impl rustls::client::danger::ServerCertVerifier for AcceptAnyServerCertificate {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
crate::crypto::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}
fn pump_tls<A, B>(
from: &mut rustls::ConnectionCommon<A>,
to: &mut rustls::ConnectionCommon<B>,
) {
let mut wire = Vec::new();
while from.wants_write() {
from.write_tls(&mut wire)
.expect("a TLS flight must serialize into memory");
}
let mut cursor = std::io::Cursor::new(wire.as_slice());
while (cursor.position() as usize) < wire.len() {
to.read_tls(&mut cursor)
.expect("a TLS flight must be readable from memory");
to.process_new_packets()
.expect("a TLS flight must process cleanly");
}
}
pub(crate) fn fresh_pair(alpn: Vec<Vec<u8>>) -> (ServerConnection, rustls::ClientConnection) {
let provider = Arc::new(crate::crypto::default_provider());
let resolver = Arc::new(crate::tls::MutexCertificateResolver::default());
resolver
.0
.lock()
.expect("the test resolver lock must be available")
.add_certificate(&sozu_command::proto::command::AddCertificate {
address: sozu_command::proto::command::SocketAddress::new_v4(127, 0, 0, 1, 8443),
certificate: sozu_command::proto::command::CertificateAndKey {
certificate: include_str!("../assets/certificate.pem").to_owned(),
key: include_str!("../assets/key.pem").to_owned(),
names: vec![TEST_SNI.to_owned()],
..Default::default()
},
expired_at: None,
})
.expect("the test certificate must load into the resolver");
let mut server_config = rustls::ServerConfig::builder_with_provider(provider.clone())
.with_protocol_versions(&[&rustls::version::TLS13])
.expect("the test provider must support TLS 1.3")
.with_no_client_auth()
.with_cert_resolver(resolver);
server_config.send_tls13_tickets = 0;
let client_config = rustls::ClientConfig::builder_with_provider(provider)
.with_protocol_versions(&[&rustls::version::TLS13])
.expect("the test provider must support TLS 1.3")
.dangerous()
.with_custom_certificate_verifier(Arc::new(AcceptAnyServerCertificate))
.with_no_client_auth();
let server = ServerConnection::new(Arc::new(server_config))
.expect("the test server session must initialize");
let client = rustls::ClientConnection::new_with_alpn(
Arc::new(client_config),
rustls::pki_types::ServerName::try_from(TEST_SNI)
.expect("the test SNI must be a valid DNS name"),
alpn,
)
.expect("the test client session must initialize");
(server, client)
}
fn handshaken_pair() -> (ServerConnection, rustls::ClientConnection) {
let (mut server, mut client) = fresh_pair(Vec::new());
for _ in 0..16 {
if !client.is_handshaking() && !server.is_handshaking() && !client.wants_write() {
break;
}
pump_tls(&mut client, &mut server);
pump_tls(&mut server, &mut client);
}
assert!(
!server.is_handshaking() && !client.is_handshaking(),
"premise: the in-memory handshake must complete"
);
(server, client)
}
fn seal(client: &mut rustls::ClientConnection, plaintext: &[u8]) -> Vec<u8> {
client
.writer()
.write_all(plaintext)
.expect("the client must absorb the plaintext");
let mut wire = Vec::new();
while client.wants_write() {
client
.write_tls(&mut wire)
.expect("the client record must serialize");
}
wire
}
struct Front {
session: ServerConnection,
peer_disconnected: bool,
peer_reset: bool,
tls_fatal: bool,
memory: RecvMemory,
}
impl Front {
fn read(
&mut self,
transport: &mut CountingTransport,
len: usize,
) -> (Vec<u8>, SocketResult) {
let mut buf = vec![0u8; len];
let (size, result, fault) = rustls_socket_read(
&mut self.session,
transport,
&mut buf,
&mut self.peer_disconnected,
&mut self.peer_reset,
&mut self.tls_fatal,
&mut self.memory,
);
assert!(fault.is_none(), "unexpected read fault: {fault:?}");
buf.truncate(size);
(buf, result)
}
fn read_faulting(
&mut self,
transport: &mut CountingTransport,
len: usize,
) -> (Vec<u8>, SocketResult) {
let mut buf = vec![0u8; len];
let (size, result, _fault) = rustls_socket_read(
&mut self.session,
transport,
&mut buf,
&mut self.peer_disconnected,
&mut self.peer_reset,
&mut self.tls_fatal,
&mut self.memory,
);
buf.truncate(size);
(buf, result)
}
fn deliver(&mut self, events: Ready) {
self.memory.event_delivered(events);
}
}
fn front_and_client() -> (Front, rustls::ClientConnection) {
let (session, client) = handshaken_pair();
(
Front {
session,
peer_disconnected: false,
peer_reset: false,
tls_fatal: false,
memory: RecvMemory::default(),
},
client,
)
}
#[test]
fn frames_sharing_one_record_cost_one_recv() {
let (mut front, mut client) = front_and_client();
let frames = client_frames();
let record = seal(&mut client, &frames.concat());
let mut transport = CountingTransport {
wire: record.into(),
..Default::default()
};
for (index, block) in frames.iter().enumerate() {
let (read, result) = front.read(&mut transport, block.len());
assert_eq!(
&read, block,
"frame block {index} must arrive intact and in order"
);
assert_eq!(result, SocketResult::Continue, "frame block {index}");
}
let (read, result) = front.read(&mut transport, FRAME_HEADER_LEN);
assert!(read.is_empty(), "nothing is left after the last frame");
assert_eq!(result, SocketResult::WouldBlock);
assert_eq!(
(transport.reads, transport.eagains),
(1, 0),
"one recv for the record and no EAGAIN once the plaintext is drained, \
for {} frame blocks",
frames.len()
);
assert!(!front.peer_disconnected && !front.peer_reset);
}
#[test]
fn an_event_after_a_short_read_lets_the_next_call_read() {
let (mut front, mut client) = front_and_client();
let frames = client_frames();
let mut transport = CountingTransport {
wire: seal(&mut client, &frames.concat()).into(),
..Default::default()
};
for block in &frames {
assert_eq!(&front.read(&mut transport, block.len()).0, block);
}
assert_eq!(
front.read(&mut transport, FRAME_HEADER_LEN),
(Vec::new(), SocketResult::WouldBlock),
"no event since the short read: nothing is read"
);
transport.wire.extend(seal(&mut client, &frames[0]));
front.deliver(Ready::READABLE);
let (read, result) = front.read(&mut transport, frames[0].len());
assert_eq!(read, frames[0], "the new arrival is read");
assert_eq!(result, SocketResult::Continue);
assert_eq!((transport.reads, transport.eagains), (2, 0));
}
#[test]
fn a_hup_delivered_after_a_short_read_lets_the_next_call_read_the_eof() {
let (mut front, mut client) = front_and_client();
let frames = client_frames();
let mut transport = CountingTransport {
wire: seal(&mut client, &frames.concat()).into(),
..Default::default()
};
for block in &frames {
assert_eq!(&front.read(&mut transport, block.len()).0, block);
}
transport.eof = true;
front.deliver(Ready::HUP);
assert_eq!(
front.read(&mut transport, FRAME_HEADER_LEN),
(Vec::new(), SocketResult::Closed)
);
assert!(front.peer_disconnected);
assert_eq!((transport.reads, transport.eagains), (2, 0));
}
#[test]
fn a_large_buffer_drains_the_record_in_one_short_recv() {
let (mut front, mut client) = front_and_client();
let plaintext = client_frames().concat();
let mut transport = CountingTransport {
wire: seal(&mut client, &plaintext).into(),
..Default::default()
};
let (read, result) = front.read(&mut transport, 16 * 1024);
assert_eq!(read, plaintext);
assert_eq!(result, SocketResult::WouldBlock);
assert_eq!(
(transport.reads, transport.eagains),
(1, 0),
"one short recv for the record and no EAGAIN"
);
}
#[test]
fn close_notify_in_the_short_read_is_reported_in_the_same_call() {
let (mut front, mut client) = front_and_client();
let plaintext = client_frames().concat();
let mut wire = seal(&mut client, &plaintext);
client.send_close_notify();
while client.wants_write() {
client
.write_tls(&mut wire)
.expect("the close_notify alert must serialize");
}
let mut transport = CountingTransport {
wire: wire.into(),
..Default::default()
};
let (read, result) = front.read(&mut transport, 16 * 1024);
assert_eq!(read, plaintext, "the data precedes the close");
assert_eq!(result, SocketResult::Closed);
assert!(front.peer_disconnected && !front.peer_reset);
assert_eq!((transport.reads, transport.eagains), (1, 0));
}
#[test]
fn a_fin_behind_a_short_read_is_reported_by_the_next_call() {
let (mut front, mut client) = front_and_client();
let plaintext = client_frames().concat();
let mut transport = CountingTransport {
wire: seal(&mut client, &plaintext).into(),
eof: true,
..Default::default()
};
front.deliver(Ready::READABLE | Ready::HUP);
let (read, result) = front.read(&mut transport, 16 * 1024);
assert_eq!(read, plaintext);
assert_eq!(result, SocketResult::WouldBlock);
assert!(!front.peer_disconnected, "the FIN has not been read yet");
let (read, result) = front.read(&mut transport, 16 * 1024);
assert!(read.is_empty());
assert_eq!(result, SocketResult::Closed);
assert!(front.peer_disconnected && !front.peer_reset);
assert_eq!((transport.reads, transport.eagains), (2, 0));
}
#[test]
fn a_large_body_over_tls_arrives_intact() {
let (mut front, mut client) = front_and_client();
let body: Vec<u8> = (0..(64 * 1024 + 123)).map(|i| (i % 251) as u8).collect();
let mut transport = CountingTransport {
wire: body
.chunks(16 * 1024)
.flat_map(|chunk| seal(&mut client, chunk))
.collect(),
..Default::default()
};
let mut received = Vec::new();
loop {
let (read, result) = front.read(&mut transport, 16 * 1024);
received.extend_from_slice(&read);
match result {
SocketResult::Continue => assert_eq!(read.len(), 16 * 1024),
SocketResult::WouldBlock => break,
other => panic!("unexpected {other:?} after {} bytes", received.len()),
}
}
assert_eq!(received, body);
assert!(transport.wire.is_empty());
assert_eq!(
transport.eagains, 0,
"the call after the one that filled from the last short read reads nothing"
);
}
#[test]
fn a_fragmented_record_is_read_again_until_it_completes() {
let (mut front, mut client) = front_and_client();
let frames = client_frames();
let record = seal(&mut client, &frames.concat());
let (head, tail) = record.split_at(record.len() / 2);
let mut transport = CountingTransport {
wire: head.iter().copied().collect(),
..Default::default()
};
let (read, result) = front.read(&mut transport, FRAME_HEADER_LEN);
assert!(read.is_empty(), "half a record decrypts to nothing");
assert_eq!(result, SocketResult::WouldBlock);
assert_eq!(
(transport.reads, transport.eagains),
(1, 0),
"the partial record, in one short recv"
);
transport.wire.extend(tail);
front.deliver(Ready::READABLE);
for (index, block) in frames.iter().enumerate() {
let (read, result) = front.read(&mut transport, block.len());
assert_eq!(&read, block, "frame block {index}");
assert_eq!(result, SocketResult::Continue, "frame block {index}");
}
assert_eq!(transport.reads, 2, "the rest of the record costs one recv");
}
#[test]
fn close_notify_after_the_frames_reports_closed_once_they_are_read() {
let (mut front, mut client) = front_and_client();
let frames = client_frames();
let mut wire = seal(&mut client, &frames.concat());
client.send_close_notify();
while client.wants_write() {
client
.write_tls(&mut wire)
.expect("the close_notify alert must serialize");
}
let mut transport = CountingTransport {
wire: wire.into(),
..Default::default()
};
for (index, block) in frames.iter().enumerate() {
let (read, result) = front.read(&mut transport, block.len());
assert_eq!(&read, block, "frame block {index}");
assert_eq!(result, SocketResult::Continue, "frame block {index}");
}
let (read, result) = front.read(&mut transport, FRAME_HEADER_LEN);
assert!(read.is_empty());
assert_eq!(
result,
SocketResult::Closed,
"close_notify must close the read side"
);
assert!(
front.peer_disconnected,
"a clean close is a read-side disconnect"
);
assert!(!front.peer_reset, "a clean close is not a reset");
assert_eq!(
transport.reads, 1,
"close_notify needs no recv beyond the flight"
);
let (read, result) = front.read(&mut transport, FRAME_HEADER_LEN);
assert!(read.is_empty());
assert_eq!(
result,
SocketResult::Closed,
"a closed session stays closed"
);
}
#[test]
fn tcp_eof_without_close_notify_reports_closed_after_the_buffered_frames() {
let (mut front, mut client) = front_and_client();
let frames = client_frames();
let mut transport = CountingTransport {
wire: seal(&mut client, &frames.concat()).into(),
eof: true,
..Default::default()
};
front.deliver(Ready::READABLE | Ready::HUP);
for (index, block) in frames.iter().enumerate() {
let (read, result) = front.read(&mut transport, block.len());
assert_eq!(&read, block, "frame block {index}");
assert_eq!(result, SocketResult::Continue, "frame block {index}");
}
let (read, result) = front.read(&mut transport, FRAME_HEADER_LEN);
assert!(read.is_empty());
assert_eq!(
result,
SocketResult::Closed,
"a FIN must close the read side"
);
assert!(front.peer_disconnected && !front.peer_reset);
let (read, result) = front.read(&mut transport, FRAME_HEADER_LEN);
assert!(read.is_empty());
assert_eq!(result, SocketResult::Closed, "EOF stays EOF, not an error");
}
#[test]
fn a_fatal_tls_error_stays_an_error_and_serves_no_buffered_plaintext() {
let (mut front, mut client) = front_and_client();
let frames = client_frames();
let mut wire = seal(&mut client, &frames.concat());
let mut corrupted = seal(&mut client, b"after the valid record");
let last = corrupted.len() - 1;
corrupted[last] ^= 0xff;
wire.extend(corrupted);
let mut transport = CountingTransport {
wire: wire.into(),
..Default::default()
};
let (read, result) = front.read_faulting(&mut transport, FRAME_HEADER_LEN);
assert!(read.is_empty(), "the failing pass delivers nothing");
assert_eq!(result, SocketResult::Error);
for call in 0..2 * frames.len() {
transport.wire.extend([0u8; 16]);
let (read, result) = front.read_faulting(&mut transport, FRAME_HEADER_LEN);
assert_eq!(
(read.len(), result),
(0, SocketResult::Error),
"call {call} after a fatal TLS error must stay (0, Error)"
);
}
}
#[test]
fn plaintext_served_before_the_fatal_error_is_returned_with_it() {
let (mut front, mut client) = front_and_client();
let frames = client_frames();
let plaintext = frames.concat();
let mut transport = CountingTransport {
wire: seal(&mut client, &plaintext).into(),
..Default::default()
};
let (read, result) = front.read(&mut transport, FRAME_HEADER_LEN);
assert_eq!(read, plaintext[..FRAME_HEADER_LEN]);
assert_eq!(result, SocketResult::Continue);
let mut corrupted = seal(&mut client, b"after the valid record");
let last = corrupted.len() - 1;
corrupted[last] ^= 0xff;
transport.wire.extend(corrupted);
front.deliver(Ready::READABLE);
let (read, result) = front.read_faulting(&mut transport, 16 * 1024);
assert_eq!(
read,
plaintext[FRAME_HEADER_LEN..],
"the rest of the valid record"
);
assert_eq!(result, SocketResult::Error);
let (read, result) = front.read_faulting(&mut transport, 16 * 1024);
assert_eq!((read.len(), result), (0, SocketResult::Error));
}
#[test]
fn a_reset_reports_closed_and_marks_the_peer_reset() {
let (mut front, _client) = front_and_client();
let mut transport = CountingTransport {
reset: true,
..Default::default()
};
let (read, result) = front.read(&mut transport, FRAME_HEADER_LEN);
assert!(read.is_empty());
assert_eq!(result, SocketResult::Closed);
assert!(front.peer_disconnected && front.peer_reset);
assert_eq!(transport.reads, 1);
}
fn reset_front() -> FrontRustls {
let (session, _client) = handshaken_pair();
FrontRustls {
stream: super::tests::reset_stream(),
session,
peer_disconnected: false,
peer_reset: false,
tls_fatal: false,
session_ulid: Ulid::generate(),
configured_peer: None,
recv_memory: RecvMemory::default(),
}
}
#[test]
fn a_tls_read_counts_a_peer_reset_once() {
let mut front = reset_front();
let reads = super::tests::proxy_count(names::rustls::READ_RESET);
let writes = super::tests::proxy_count(names::rustls::WRITE_RESET);
let plain = super::tests::proxy_count(names::tcp::READ_RESET);
let mut buf = [0u8; 64];
let (size, result) = front.socket_read(&mut buf);
assert_eq!((size, result), (0, SocketResult::Closed));
assert!(front.peer_reset);
assert_eq!(
super::tests::proxy_count(names::rustls::READ_RESET) - reads,
1
);
let (_, result) = front.socket_write(b"after the reset");
assert_eq!(result, SocketResult::Closed);
assert_eq!(
super::tests::proxy_count(names::rustls::WRITE_RESET) - writes,
0,
"a write after a reset read never reaches the socket"
);
assert_eq!(super::tests::proxy_count(names::tcp::READ_RESET) - plain, 0);
}
#[test]
fn a_tls_write_counts_a_peer_reset_once() {
let mut front = reset_front();
let resets = super::tests::proxy_count(names::rustls::WRITE_RESET);
let errors = super::tests::proxy_count(names::rustls::WRITE_ERROR);
let (_, result) = front.socket_write(b"after the reset");
assert_eq!(result, SocketResult::Closed);
assert!(front.peer_reset);
assert_eq!(
super::tests::proxy_count(names::rustls::WRITE_RESET) - resets,
1
);
assert_eq!(
super::tests::proxy_count(names::rustls::WRITE_ERROR) - errors,
1
);
let (_, result) = front.socket_write_vectored(&[std::io::IoSlice::new(b"after the reset")]);
assert_eq!(result, SocketResult::Closed);
assert_eq!(
super::tests::proxy_count(names::rustls::WRITE_RESET) - resets,
1,
"the reset is counted once"
);
}
fn refused_front() -> FrontRustls {
let address = std::net::TcpListener::bind("127.0.0.1:0")
.expect("test listener must bind to a loopback port")
.local_addr()
.expect("test listener must report its local address");
let socket =
Socket::new(Domain::IPV4, Type::STREAM, None).expect("a TCP socket must be created");
socket
.set_nonblocking(true)
.expect("mio requires a nonblocking stream");
if let Err(e) = socket.connect(&address.into()) {
assert_eq!(
e.raw_os_error(),
Some(libc::EINPROGRESS),
"a nonblocking connect must be in progress, got {e}"
);
}
let mut pollfd = libc::pollfd {
fd: std::os::unix::io::AsRawFd::as_raw_fd(&socket),
events: libc::POLLOUT,
revents: 0,
};
let ready = unsafe { libc::poll(&mut pollfd, 1, 5_000) };
assert_eq!(ready, 1, "the refusal must arrive within 5 s");
assert_ne!(
pollfd.revents & libc::POLLERR,
0,
"premise: the kernel must have refused the connection"
);
let (session, _client) = handshaken_pair();
FrontRustls {
stream: TcpStream::from_std(std::net::TcpStream::from(socket)),
session,
peer_disconnected: false,
peer_reset: false,
tls_fatal: false,
session_ulid: Ulid::generate(),
configured_peer: None,
recv_memory: RecvMemory::default(),
}
}
#[test]
fn a_tls_write_that_meets_a_socket_error_stops_wanting_to_write() {
let mut front = refused_front();
let errors = super::tests::proxy_count(names::rustls::WRITE_ERROR);
let (_, result) = front.socket_write(b"refused by the kernel");
assert_eq!(result, SocketResult::Error);
assert_eq!(
super::tests::proxy_count(names::rustls::WRITE_ERROR) - errors,
1
);
assert!(
front.session.wants_write(),
"premise: rustls must still hold the record the kernel refused"
);
assert!(
!front.socket_wants_write(),
"a record no write can deliver must not read as a pending write"
);
let writes = tls_writes();
let (_, result) = front.socket_write_vectored(&[std::io::IoSlice::new(b"after the error")]);
assert_eq!(result, SocketResult::Closed);
assert_eq!(
tls_writes(),
writes,
"a write after the error must not reach the socket"
);
}
fn refused_front_holding_records() -> FrontRustls {
let mut front = refused_front();
front
.session
.writer()
.write_all(b"queued before the flush")
.expect("rustls must absorb the plaintext");
assert!(
front.session.wants_write(),
"premise: rustls must hold the record the flush will try to write"
);
front
}
fn assert_error_marks_the_channel_dead(front: &FrontRustls, result: SocketResult) {
assert_eq!(result, SocketResult::Error);
assert!(
front.session.wants_write(),
"premise: rustls must still hold the record the kernel refused"
);
assert!(
front.peer_reset && !front.socket_wants_write(),
"an Error write must mark the channel dead, or the record keeps the \
session waiting for a WRITABLE edge that never comes"
);
}
#[test]
fn an_empty_tls_flush_that_meets_a_socket_error_marks_the_channel_dead() {
let mut front = refused_front_holding_records();
let (_, result) = front.socket_write(&[]);
assert_error_marks_the_channel_dead(&front, result);
}
#[test]
fn a_vectored_tls_write_that_meets_a_socket_error_marks_the_channel_dead() {
let mut front = refused_front();
let (size, result) = front.socket_write_vectored(&[
std::io::IoSlice::new(b"refused "),
std::io::IoSlice::new(b"by the kernel"),
]);
assert_eq!(size, 21, "premise: rustls must absorb the whole write");
assert_error_marks_the_channel_dead(&front, result);
}
#[test]
fn a_partial_vectored_tls_write_that_meets_a_socket_error_marks_the_channel_dead() {
let mut front = refused_front();
front.session.set_buffer_limit(Some(64));
let payload = vec![b'x'; 4096];
let (size, result) = front.socket_write_vectored(&[std::io::IoSlice::new(&payload)]);
assert!(
size > 0 && size < payload.len(),
"premise: rustls must absorb part of the write, got {size}"
);
assert_error_marks_the_channel_dead(&front, result);
}
#[test]
fn an_empty_vectored_tls_flush_that_meets_a_socket_error_marks_the_channel_dead() {
let mut front = refused_front_holding_records();
let (_, result) = front.socket_write_vectored(&[]);
assert_error_marks_the_channel_dead(&front, result);
}
#[derive(Default)]
struct InterruptedOnce {
calls: usize,
written: Vec<u8>,
}
impl std::io::Write for InterruptedOnce {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.calls += 1;
if self.calls == 1 {
return Err(std::io::Error::from(ErrorKind::Interrupted));
}
self.written.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[test]
fn an_interrupted_tls_write_is_retried() {
let (mut session, _client) = handshaken_pair();
session
.writer()
.write_all(b"interrupted once")
.expect("rustls must absorb the plaintext");
let mut transport = InterruptedOnce::default();
let written = flush_tls(&mut session, &mut transport)
.expect("an interrupted write must be retried, not reported");
assert!(written > 0 && transport.written.len() == written);
assert_eq!(transport.calls, 2, "one interrupted write, one retry");
assert!(!session.wants_write(), "the retry must deliver the record");
}
#[test]
fn an_interrupted_plain_write_is_retried() {
let mut transport = InterruptedOnce::default();
let (size, result, fault) = plain_socket_write(&mut transport, b"interrupted once");
assert!(fault.is_none(), "unexpected write fault: {fault:?}");
assert_eq!((size, result), (16, SocketResult::Continue));
assert_eq!(transport.calls, 2, "one interrupted write, one retry");
assert_eq!(transport.written, b"interrupted once");
}
#[test]
fn an_interrupted_plain_vectored_write_is_retried() {
let mut transport = InterruptedOnce::default();
let bufs = [std::io::IoSlice::new(b"interrupted once")];
let (size, result, fault) = plain_socket_write_vectored(&mut transport, &bufs);
assert!(fault.is_none(), "unexpected write fault: {fault:?}");
assert_eq!((size, result), (16, SocketResult::Continue));
assert_eq!(transport.calls, 2, "one interrupted write, one retry");
assert_eq!(transport.written, b"interrupted once");
}
#[test]
fn an_interrupted_plain_read_is_retried() {
let mut transport = CountingTransport {
wire: b"interrupted once".iter().copied().collect(),
interrupts: 1,
..Default::default()
};
let mut buf = [0u8; 32];
let (size, result, fault) = plain_socket_read(&mut transport, &mut buf);
assert!(fault.is_none(), "unexpected read fault: {fault:?}");
assert_eq!((size, result), (16, SocketResult::WouldBlock));
assert_eq!(&buf[..size], b"interrupted once");
assert_eq!(transport.reads, 2, "one interrupted read, one retry");
}
#[test]
fn an_interrupted_tls_read_is_retried() {
let (mut front, mut client) = front_and_client();
let mut transport = CountingTransport {
wire: seal(&mut client, b"interrupted once").into(),
interrupts: 1,
..Default::default()
};
let (read, result) = front.read(&mut transport, 32);
assert_eq!(read, b"interrupted once");
assert_eq!(result, SocketResult::WouldBlock);
assert_eq!(transport.reads, 2, "one interrupted read, one retry");
}
fn plain_read_all(transport: &mut CountingTransport, len: usize) -> (Vec<u8>, SocketResult) {
let mut received = Vec::new();
loop {
let mut buf = vec![0u8; len];
let (size, result, fault) = plain_socket_read(transport, &mut buf);
assert!(fault.is_none(), "unexpected read fault: {fault:?}");
received.extend_from_slice(&buf[..size]);
if result != SocketResult::Continue {
return (received, result);
}
assert_eq!(size, len, "Continue means the buffer filled");
}
}
#[test]
fn a_short_plain_read_costs_one_recv_and_no_eagain() {
let request = b"GET /index.html HTTP/1.1\r\nHost: lolcatho.st\r\n\r\n".to_vec();
let mut transport = CountingTransport {
wire: request.iter().copied().collect(),
..Default::default()
};
let mut buf = vec![0u8; 16 * 1024];
let (size, result, fault) = plain_socket_read(&mut transport, &mut buf);
assert!(fault.is_none());
assert_eq!(&buf[..size], &request[..]);
assert_eq!(result, SocketResult::WouldBlock);
assert_eq!(
(transport.reads, transport.eagains),
(1, 0),
"one short recv and no EAGAIN"
);
}
#[test]
fn a_full_plain_buffer_is_not_a_short_read() {
let mut transport = CountingTransport {
wire: (0..250u8).collect(),
..Default::default()
};
let mut buf = vec![0u8; 100];
let (size, result, _) = plain_socket_read(&mut transport, &mut buf);
assert_eq!((size, result), (100, SocketResult::Continue));
let (size, result, _) = plain_socket_read(&mut transport, &mut buf);
assert_eq!((size, result), (100, SocketResult::Continue));
let (size, result, _) = plain_socket_read(&mut transport, &mut buf);
assert_eq!((size, result), (50, SocketResult::WouldBlock));
assert_eq!((transport.reads, transport.eagains), (3, 0));
}
#[test]
fn a_fin_behind_a_short_plain_read_is_closed_on_the_next_call() {
let mut transport = CountingTransport {
wire: (0..50u8).collect(),
eof: true,
..Default::default()
};
let mut buf = vec![0u8; 16 * 1024];
let (size, result, _) = plain_socket_read(&mut transport, &mut buf);
assert_eq!((size, result), (50, SocketResult::WouldBlock));
assert_eq!(buf[..size], (0..50u8).collect::<Vec<_>>()[..]);
let (size, result, _) = plain_socket_read(&mut transport, &mut buf);
assert_eq!((size, result), (0, SocketResult::Closed));
let (size, result, _) = plain_socket_read(&mut transport, &mut buf);
assert_eq!((size, result), (0, SocketResult::Closed), "EOF stays EOF");
assert_eq!(transport.eagains, 0);
}
#[test]
fn a_fragmented_plain_message_costs_one_recv_per_arrival() {
let message: Vec<u8> = (0..70u8).collect();
let mut transport = CountingTransport::default();
let mut received = Vec::new();
for chunk in message.chunks(30) {
transport.wire.extend(chunk);
let mut buf = vec![0u8; 16 * 1024];
let (size, result, _) = plain_socket_read(&mut transport, &mut buf);
assert_eq!((size, result), (chunk.len(), SocketResult::WouldBlock));
received.extend_from_slice(&buf[..size]);
}
assert_eq!(received, message);
assert_eq!((transport.reads, transport.eagains), (3, 0));
}
#[test]
fn a_large_plain_body_arrives_intact_without_eagain() {
let body: Vec<u8> = (0..(1024 * 1024 + 123)).map(|i| (i % 251) as u8).collect();
let mut transport = CountingTransport {
wire: body.iter().copied().collect(),
..Default::default()
};
let (received, result) = plain_read_all(&mut transport, 16 * 1024);
assert_eq!(result, SocketResult::WouldBlock);
assert_eq!(received, body);
assert_eq!(
(transport.reads, transport.eagains),
(65, 0),
"64 full reads and the short tail"
);
}
#[test]
fn a_plain_body_ending_on_a_full_buffer_still_needs_its_eagain() {
let body: Vec<u8> = (0..(4 * 16 * 1024)).map(|i| (i % 251) as u8).collect();
let mut transport = CountingTransport {
wire: body.iter().copied().collect(),
..Default::default()
};
let (received, result) = plain_read_all(&mut transport, 16 * 1024);
assert_eq!(result, SocketResult::WouldBlock);
assert_eq!(received, body);
assert_eq!((transport.reads, transport.eagains), (5, 1));
}
#[test]
fn after_a_short_read_new_bytes_raise_a_new_edge() {
use std::io::Write as _;
let token = mio::Token(7);
let mut poll = mio::Poll::new().expect("a poll instance must open");
let mut events = mio::Events::with_capacity(8);
let listener = std::net::TcpListener::bind("127.0.0.1:0")
.expect("test listener must bind to a loopback port");
let mut client = std::net::TcpStream::connect(
listener
.local_addr()
.expect("test listener must report its local address"),
)
.expect("loopback connect must complete");
let (accepted, _) = listener.accept().expect("the connection must be accepted");
accepted
.set_nonblocking(true)
.expect("mio requires a nonblocking stream");
let mut server = TcpStream::from_std(accepted);
poll.registry()
.register(&mut server, token, mio::Interest::READABLE)
.expect("the stream must register");
let wait_for_edge = |poll: &mut mio::Poll, events: &mut mio::Events| {
poll.poll(events, Some(std::time::Duration::from_secs(5)))
.expect("poll must succeed");
events
.iter()
.any(|event| event.token() == token && event.is_readable())
};
let mut buf = vec![0u8; 16 * 1024];
client.write_all(b"first").expect("the client must write");
assert!(wait_for_edge(&mut poll, &mut events), "the first arrival");
assert_eq!(
tcp_socket_read(&mut server, &mut buf, None, None),
(5, SocketResult::WouldBlock)
);
poll.poll(&mut events, Some(std::time::Duration::from_millis(50)))
.expect("poll must succeed");
assert!(events.is_empty(), "edge-triggered: nothing new, no event");
client.write_all(b"second").expect("the client must write");
assert!(
wait_for_edge(&mut poll, &mut events),
"bytes arriving after a short read must raise a new edge"
);
assert_eq!(
tcp_socket_read(&mut server, &mut buf, None, None),
(6, SocketResult::WouldBlock)
);
client
.shutdown(std::net::Shutdown::Write)
.expect("the client must half-close");
assert!(wait_for_edge(&mut poll, &mut events), "the FIN");
assert_eq!(
tcp_socket_read(&mut server, &mut buf, None, None),
(0, SocketResult::Closed)
);
}
}