use std::net::{IpAddr, SocketAddr};
use std::os::fd::RawFd;
use std::sync::{Arc, OnceLock};
use crate::backend::{Backend, Config};
use crate::checks::{self, DevContext, HostReport};
use crate::clock::{monotonic_ns, rdtsc, ticks_per_us};
use crate::dns::{DnsConfig, Lookup, Resolver};
use crate::endpoint::sealed::RouteSlot;
use crate::endpoint::{ConnId, ConnMut, Dial, DriverStats, Endpoint, Handler, Receive};
use crate::error::{Error, Result};
use crate::placement::{self, CpuError};
use crate::route::{self, Flow, QuickAckCheck, QuickAckError, RouteCheck};
use crate::{RxMeta, sys};
use conn_checks::{ConnJob, RouteIn};
mod background;
mod conn_checks;
mod epoll;
#[cfg(test)]
mod placement_tests;
#[cfg(feature = "uring")]
mod uring;
struct Conns<E> {
slots: Vec<Option<E>>,
gens: Vec<u32>,
closing: Vec<bool>,
reserved: Vec<bool>,
closed: Vec<E>,
}
impl<E> Conns<E> {
const fn new() -> Self {
Self {
slots: Vec::new(),
gens: Vec::new(),
closing: Vec::new(),
reserved: Vec::new(),
closed: Vec::new(),
}
}
const fn len_slots(&self) -> usize {
self.slots.len()
}
fn id(&self, i: usize) -> ConnId {
ConnId::new(
u32::try_from(i).unwrap_or(u32::MAX),
self.gens.get(i).copied().unwrap_or(0),
)
}
fn at(&mut self, i: usize) -> Option<&mut E> {
self.slots.get_mut(i).and_then(Option::as_mut)
}
fn lookup(&self, id: ConnId) -> Option<usize> {
let i = id.index();
(self.gens.get(i) == Some(&id.generation())
&& self.closing.get(i) == Some(&false)
&& self.slots.get(i).is_some_and(Option::is_some))
.then_some(i)
}
fn is_open(&self, i: usize) -> bool {
self.closing.get(i) == Some(&false) && self.slots.get(i).is_some_and(Option::is_some)
}
fn reserve(&mut self) -> usize {
let i = (0..self.slots.len())
.find(|&i| self.slots[i].is_none() && !self.closing[i] && !self.reserved[i])
.unwrap_or_else(|| {
self.slots.push(None);
self.gens.push(1);
self.closing.push(false);
self.reserved.push(false);
self.slots.len() - 1
});
self.reserved[i] = true;
i
}
fn install(&mut self, i: usize, e: E) {
self.slots[i] = Some(e);
self.reserved[i] = false;
}
fn release(&mut self, i: usize) {
if self.reserved.get(i) == Some(&true) {
self.reserved[i] = false;
self.gens[i] = self.gens[i].wrapping_add(1);
}
}
fn lookup_reserved(&self, id: ConnId) -> Option<usize> {
let i = id.index();
(self.gens.get(i) == Some(&id.generation()) && self.reserved.get(i) == Some(&true))
.then_some(i)
}
#[cfg_attr(not(feature = "uring"), allow(dead_code))]
fn begin_close(&mut self, i: usize) {
if self.is_open(i) {
self.gens[i] = self.gens[i].wrapping_add(1);
self.closing[i] = true;
}
}
fn is_closing(&self, i: usize) -> bool {
self.closing.get(i) == Some(&true)
}
#[cfg_attr(not(feature = "uring"), allow(dead_code))]
fn finish_close(&mut self, i: usize) {
if self.is_closing(i) {
if let Some(e) = self.slots[i].take() {
self.closed.push(e);
}
self.closing[i] = false;
}
}
#[cfg_attr(not(feature = "uring"), allow(dead_code))]
fn leak(&mut self) {
std::mem::forget(std::mem::take(&mut self.slots));
}
fn remove(&mut self, i: usize) {
if self.is_closing(i) {
return;
}
if let Some(e) = self.slots.get_mut(i).and_then(Option::take) {
self.gens[i] = self.gens[i].wrapping_add(1);
self.closed.push(e);
}
}
}
#[allow(clippy::large_enum_variant)]
enum Imp {
Epoll(epoll::Epoll),
#[cfg(feature = "uring")]
Uring(uring::Uring),
}
const EXPIRY_CHECK_US: u32 = 250;
const EXPIRY_POLLS: u32 = 64;
const SETUP_STEP_US: u32 = 20;
#[derive(Clone)]
struct SockOpts {
timestamps: bool,
rcvbuf: Option<u32>,
busy_poll_us: u32,
prefer_busy_poll: bool,
busy_poll_budget: Option<u16>,
quickack: QuickAckCheck,
backend: Backend,
keepalive: (u32, u32, u32),
user_timeout_ms: u32,
ctx: Arc<DevContext>,
}
#[derive(Clone, Copy, Default)]
struct RouteCost {
lookups: u64,
ticks: u64,
}
impl SockOpts {
fn of(cfg: &Config, ctx: &Arc<DevContext>) -> Self {
Self {
timestamps: cfg.rx_timestamps,
rcvbuf: cfg.rcvbuf,
busy_poll_us: match cfg.backend {
Backend::EpollBusy | Backend::RecvBusy => cfg.busy_poll_us.max(1),
_ => 0,
},
prefer_busy_poll: cfg.backend == Backend::RecvBusy && cfg.prefer_busy_poll,
busy_poll_budget: cfg
.busy_poll_budget
.filter(|_| cfg.backend == Backend::RecvBusy),
quickack: cfg.quickack_check,
backend: cfg.backend,
keepalive: (
cfg.keepalive_idle_s,
cfg.keepalive_interval_s,
cfg.keepalive_count,
),
user_timeout_ms: cfg.tcp_user_timeout_ms,
ctx: Arc::clone(ctx),
}
}
fn apply(&self, fd: RawFd) -> Result<()> {
if self.timestamps {
sys::enable_rx_timestamps(fd)?;
}
if let Some(b) = self.rcvbuf {
sys::set_rcvbuf(fd, b)?;
}
sys::set_busy_poll(fd, self.busy_poll_us)?;
let (idle, interval, count) = self.keepalive;
if idle > 0 {
sys::set_tcp_keepalive(fd, idle, interval, count)?;
}
if self.user_timeout_ms > 0 {
sys::set_tcp_user_timeout(fd, self.user_timeout_ms)?;
}
let privileged = |what: &str, e: std::io::Error| {
if e.kind() == std::io::ErrorKind::PermissionDenied {
Error::Config(format!("{what} needs CAP_NET_ADMIN"))
} else {
Error::Io(e)
}
};
if self.prefer_busy_poll {
sys::set_prefer_busy_poll(fd, true)
.map_err(|e| privileged("prefer_busy_poll (SO_PREFER_BUSY_POLL)", e))?;
}
if let Some(b) = self.busy_poll_budget {
sys::set_busy_poll_budget(fd, b)
.map_err(|e| privileged("busy_poll_budget (SO_BUSY_POLL_BUDGET)", e))?;
}
Ok(())
}
fn prepare<E: Endpoint>(
&self,
ep: &mut E,
id: ConnId,
added: bool,
on_loop: bool,
) -> Result<RouteCost> {
let fd = ep.fd();
self.apply(fd)?;
if added && sys::tcp_nodelay_and_cork(fd)? == Some(true) {
crate::warn::emit(checks::cork_line(id));
}
if self.quickack == QuickAckCheck::Off {
return Ok(RouteCost::default());
}
let napi = if self.backend.polls_nic() {
sys::incoming_napi_id(fd).ok()
} else {
None
};
let t0 = rdtsc();
let mut lookups = 0;
let route = if ep.route_state().decided() {
RouteIn::Known(ep.route_state().get().clone())
} else if self.quickack == QuickAckCheck::Warn && on_loop {
let cell = Arc::new(OnceLock::new());
ep.set_route(RouteSlot::Pending(Arc::clone(&cell)));
RouteIn::Lookup(Flow::of_socket(fd), cell)
} else {
let found =
route::check_socket(self.quickack, fd).map_err(|e| Error::QuickAck(Box::new(e)))?;
lookups = 1;
ep.set_route(RouteSlot::Ready(found.clone()));
RouteIn::Known(found)
};
let local =
matches!(&route, RouteIn::Known(c) if c.route().is_none_or(route::Route::is_local));
if !local {
let job = ConnJob {
conn: id,
backend: self.backend,
ctx: Arc::clone(&self.ctx),
napi,
route,
};
crate::warn::run(move |st| job.run(st));
}
Ok(RouteCost {
lookups,
ticks: rdtsc().wrapping_sub(t0),
})
}
}
fn require_route(fd: RawFd) -> std::result::Result<RouteCheck, QuickAckError> {
route::check_socket(QuickAckCheck::Require, fd)
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss
)]
fn ticks_ns(ticks: u64, tpu: f64) -> u64 {
(ticks as f64 / tpu * 1000.0) as u64
}
struct Pending<E> {
slot: usize,
dial: Box<dyn Dial<Conn = E>>,
started: bool,
deadline_ns: i64,
}
pub struct Driver<E> {
cfg: Config,
imp: Imp,
conns: Conns<E>,
stats: DriverStats,
read_seq: u64,
buffered: bool,
pending: Vec<Pending<E>>,
unreported: Vec<(ConnId, Error)>,
resolver: Resolver,
ticks_per_us: f64,
next_expiry: u64,
expiry_ticks: u64,
expiry_countdown: u32,
expiry_polls: u32,
next_setup: u64,
setup_ticks: u64,
background: Option<background::Background<E>>,
dns: DnsConfig,
report: HostReport,
ctx: Arc<DevContext>,
_timestamp_keeper: Option<socket2::Socket>,
}
impl<E: Endpoint> Driver<E> {
pub fn new(cfg: Config) -> Result<Self> {
Self::with_dns(cfg, DnsConfig::system())
}
pub fn with_dns(cfg: Config, dns: DnsConfig) -> Result<Self> {
Self::on_host(cfg, dns, &ergon_tune::Host::system())
}
pub(crate) fn on_host(
mut cfg: Config,
dns: DnsConfig,
host: &ergon_tune::Host,
) -> Result<Self> {
cfg.validate()?;
let policy = ergon_tune::HostCheckPolicy::from_env()?;
let report = checks::host_report(host, &cfg);
let helpers = report.helpers.clone()?;
if cfg.backend == Backend::UringSqpoll {
cfg.sqpoll_cpu = Some(placement::sqpoll_cpu(&helpers, cfg.sqpoll_cpu)?);
}
crate::warn::prepare_on(
&helpers,
&placement::thread_cpus(&helpers, placement::sqpoll_of(&cfg)),
);
for l in report
.lines_under(policy)
.into_iter()
.chain(report.helper_line())
{
crate::warn::once(l);
}
if let Some(e) = report.refusal(policy) {
return Err(e.into());
}
if let Some(asked) = cfg.rcvbuf
&& let Ok(granted) = sys::granted_rcvbuf(asked)
&& let Some(l) = checks::rcvbuf_line(asked, granted)
{
crate::warn::once(l);
}
let ctx = Arc::new(DevContext::new(&cfg, report.hot.clone()));
let tpu = ticks_per_us();
let imp = match cfg.backend {
Backend::EpollSpin | Backend::EpollBusy | Backend::RecvBusy => {
Imp::Epoll(epoll::Epoll::new(&cfg)?)
}
#[cfg(feature = "uring")]
Backend::UringSpin | Backend::UringNapi | Backend::UringSqpoll => {
Imp::Uring(uring::Uring::new(&cfg)?)
}
#[cfg(not(feature = "uring"))]
Backend::UringSpin | Backend::UringNapi | Backend::UringSqpoll => {
return Err(Error::Unsupported(
"io_uring backends need the `uring` feature",
));
}
};
Ok(Self {
imp,
conns: Conns::new(),
stats: DriverStats::default(),
read_seq: 0,
buffered: false,
pending: Vec::new(),
unreported: Vec::new(),
resolver: Resolver::new(dns.clone()),
dns,
report,
ctx,
_timestamp_keeper: cfg.rx_timestamps.then(timestamp_keeper).flatten(),
ticks_per_us: tpu,
next_expiry: 0,
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
expiry_ticks: (f64::from(EXPIRY_CHECK_US) * tpu) as u64,
expiry_countdown: 1,
expiry_polls: if cfg.backend.may_sleep() {
1
} else {
EXPIRY_POLLS
},
next_setup: 0,
background: None,
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
setup_ticks: (f64::from(SETUP_STEP_US) * tpu) as u64,
cfg,
})
}
#[must_use]
pub const fn config(&self) -> &Config {
&self.cfg
}
#[must_use]
pub const fn stats(&self) -> &DriverStats {
&self.stats
}
#[must_use]
pub const fn host_report(&self) -> &HostReport {
&self.report
}
pub fn add(&mut self, ep: E) -> Result<ConnId> {
let i = self.conns.reserve();
let id = self.conns.id(i);
if let Err(e) = self.install(i, ep, true) {
self.conns.release(i);
return Err(e);
}
Ok(id)
}
pub fn connect<D: Dial<Conn = E> + 'static>(&mut self, dial: D) -> Result<ConnId> {
let i = self.conns.reserve();
let id = self.conns.id(i);
let mut p = Pending {
slot: i,
dial: Box::new(dial),
started: false,
deadline_ns: monotonic_ns() + i64::from(self.cfg.connect_timeout_ms) * 1_000_000,
};
if self.cfg.quickack_check == QuickAckCheck::Require {
p.dial.gate_route();
}
if let Some(addr) = p.dial.addr() {
if let Err(e) = p.dial.start(addr) {
self.conns.release(i);
return Err(e);
}
p.started = true;
}
self.pending.push(p);
Ok(id)
}
pub fn connect_background<D>(&mut self, dial: D) -> Result<ConnId>
where
D: Dial<Conn = E> + Send + 'static,
E: Send + 'static,
{
if self.background.is_none() {
match self.background_cpus()? {
Some(cpus) => {
let step = placement::dial_step(
&cpus,
placement::sqpoll_of(&self.cfg).and_then(|s| usize::try_from(s).ok()),
);
self.background = Some(background::Background::start(
&cpus,
self.dns.clone(),
step,
)?);
}
None => return self.connect(dial),
}
}
let i = self.conns.reserve();
let id = self.conns.id(i);
let timeout = std::time::Duration::from_millis(u64::from(self.cfg.connect_timeout_ms));
let opts = SockOpts::of(&self.cfg, &self.ctx);
let mut dial = dial;
if self.cfg.quickack_check == QuickAckCheck::Require {
dial.gate_route();
}
if let Some(bg) = self.background.as_mut()
&& let Err(e) = bg.submit(
i,
id.generation(),
Box::new(dial),
Box::new(move |c: &mut E, id: ConnId| {
opts.prepare(c, id, false, false).map(|_| ())
}),
timeout,
)
{
self.conns.release(i);
return Err(e);
}
Ok(id)
}
fn background_cpus(&self) -> Result<Option<Vec<usize>>> {
let cpus = match &self.report.helpers {
Ok(p) => placement::dial_cpus(p, &self.cfg),
Err(e) => Err(e.clone()),
};
match cpus {
Ok(cpus) => Ok(Some(cpus)),
Err(CpuError::NoSpareCpu { .. }) if self.cfg.inline_connect_fallback => Ok(None),
Err(e) => Err(e.into()),
}
}
pub(crate) fn background_ready(&self) -> Result<()> {
if self.background.is_none() {
self.background_cpus()?;
}
Ok(())
}
#[cold]
fn free_closed(&mut self) {
let Self {
conns, background, ..
} = self;
for c in conns.closed.drain(..) {
match background {
Some(b) => b.free(c),
None => drop(c),
}
}
}
#[cold]
fn take_background<H: Handler>(&mut self, handler: &mut H) {
while let Some(done) = self
.background
.as_mut()
.and_then(background::Background::try_take)
{
let id = ConnId::new(
u32::try_from(done.slot).unwrap_or(u32::MAX),
done.generation,
);
if self.conns.lookup_reserved(id).is_none() {
if let Ok(ep) = done.result {
self.conns.closed.push(ep);
}
continue;
}
match done
.result
.and_then(|ep| self.install_prepared(done.slot, ep))
{
Ok(()) => {
self.stats.connects += 1;
handler.on_open(id);
}
Err(e) => {
self.conns.release(done.slot);
self.stats.connect_failures += 1;
handler.on_closed(id, e);
}
}
}
}
pub fn prefetch(&mut self, host: &str) -> Result<()> {
self.resolver.prefetch(host, monotonic_ns())
}
#[must_use]
pub const fn connecting(&self) -> usize {
self.pending.len()
}
fn install(&mut self, i: usize, mut ep: E, added: bool) -> Result<()> {
let id = self.conns.id(i);
match SockOpts::of(&self.cfg, &self.ctx).prepare(&mut ep, id, added, true) {
Ok(cost) => self.count_route(cost),
Err(e) => {
self.conns.closed.push(ep);
return Err(e);
}
}
self.install_prepared(i, ep)
}
fn count_route(&mut self, cost: RouteCost) {
let ns = ticks_ns(cost.ticks, self.ticks_per_us);
self.stats.route_lookups += cost.lookups;
self.stats.route_check_ns += ns;
self.stats.route_check_max_ns = self.stats.route_check_max_ns.max(ns);
}
#[cfg_attr(not(feature = "uring"), allow(unused_mut))]
fn install_prepared(&mut self, i: usize, mut ep: E) -> Result<()> {
let fd = ep.fd();
let id = self.conns.id(i);
let added = match &mut self.imp {
Imp::Epoll(e) => e.add(fd, id),
#[cfg(feature = "uring")]
Imp::Uring(u) => u.add(&mut ep, i),
};
if let Err(e) = added {
self.conns.closed.push(ep);
return Err(e);
}
self.buffered |= ep.has_buffered_input();
self.conns.install(i, ep);
Ok(())
}
#[cold]
fn advance_pending<H: Handler>(&mut self, handler: &mut H) {
let now = monotonic_ns();
self.resolver.poll(now);
let mut k = 0;
while k < self.pending.len() {
let p = &mut self.pending[k];
let outcome = advance_one(
p,
&mut self.resolver,
&mut self.stats,
now,
self.ticks_per_us,
&require_route,
);
let ep = match outcome {
Ok(None) => {
k += 1;
continue;
}
Ok(Some(ep)) => Ok(ep),
Err(e) => Err(e),
};
let p = self.pending.swap_remove(k);
let id = self.conns.id(p.slot);
let installed = ep.and_then(|ep| self.install(p.slot, ep, false));
match installed {
Ok(()) => {
self.stats.connects += 1;
handler.on_open(id);
}
Err(e) => {
self.conns.release(p.slot);
self.stats.connect_failures += 1;
handler.on_closed(id, e);
}
}
}
}
#[must_use]
pub fn get(&self, id: ConnId) -> Option<&E> {
self.conns
.lookup(id)
.and_then(|i| self.conns.slots[i].as_ref())
}
pub fn get_mut(&mut self, id: ConnId) -> Option<ConnMut<'_, E>> {
self.conns
.lookup(id)
.and_then(|i| self.conns.at(i))
.map(ConnMut::new)
}
pub fn iter(&self) -> impl Iterator<Item = (ConnId, &E)> {
self.conns
.slots
.iter()
.enumerate()
.filter(|&(i, _)| !self.conns.is_closing(i))
.filter_map(|(i, c)| c.as_ref().map(|c| (self.conns.id(i), c)))
}
#[must_use]
pub fn len(&self) -> usize {
(0..self.conns.len_slots())
.filter(|&i| self.conns.is_open(i))
.count()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn close(&mut self, id: ConnId) {
if let Some(i) = self.conns.lookup(id) {
close_index(&mut self.imp, &mut self.conns, i);
self.free_closed();
} else if let Some(i) = self.conns.lookup_reserved(id) {
self.pending.retain(|p| p.slot != i);
self.conns.release(i);
}
}
#[must_use]
pub fn nic_polling_active(&self) -> bool {
self.cfg.backend.polls_nic()
&& self
.iter()
.all(|(_, c)| sys::incoming_napi_id(c.fd()).unwrap_or(0) != 0)
}
#[inline]
pub fn poll<H: Handler>(&mut self, handler: &mut H) -> Result<usize>
where
E: Receive<H>,
{
self.stats.polls += 1;
let before = self.stats.messages;
if !self.unreported.is_empty() {
for (id, e) in self.unreported.drain(..) {
handler.on_closed(id, e);
}
}
if self
.background
.as_ref()
.is_some_and(background::Background::has_outstanding)
{
self.take_background(handler);
}
if !self.pending.is_empty() || self.resolver.busy() {
let t = rdtsc();
if t >= self.next_setup {
self.next_setup = t.wrapping_add(self.setup_ticks);
self.advance_pending(handler);
}
}
if self.buffered {
self.buffered = false;
self.deliver_buffered(handler);
}
let mut r = match &mut self.imp {
Imp::Epoll(e) => e.poll(
&mut self.conns,
handler,
&mut self.stats,
&mut self.read_seq,
),
#[cfg(feature = "uring")]
Imp::Uring(u) => u.poll(
&mut self.conns,
handler,
&mut self.stats,
&mut self.read_seq,
),
};
if r.is_ok() && matches!(self.imp, Imp::Epoll(_)) {
r = self.send_queued(handler);
}
self.check_deadlines(handler);
if !self.conns.closed.is_empty() {
self.free_closed();
}
r?;
Ok(usize::try_from(self.stats.messages - before).unwrap_or(usize::MAX))
}
#[inline]
fn check_deadlines<H: Handler>(&mut self, handler: &mut H) {
if E::DEADLINES {
self.expiry_countdown -= 1;
if self.expiry_countdown == 0 {
self.expiry_countdown = self.expiry_polls;
let t = rdtsc();
if t >= self.next_expiry {
self.next_expiry = t.wrapping_add(self.expiry_ticks);
self.expire_all(handler);
}
}
}
}
#[cold]
fn expire_all<H: Handler>(&mut self, handler: &mut H) {
let now = monotonic_ns();
for i in 0..self.conns.len_slots() {
let id = self.conns.id(i);
if !self.conns.is_open(i) {
continue;
}
let Some(c) = self.conns.at(i) else {
continue;
};
if let Err(e) = c.expire(now) {
let e = c.close_reason(e);
close_index(&mut self.imp, &mut self.conns, i);
self.stats.closed += 1;
handler.on_closed(id, e);
}
}
}
#[cold]
fn deliver_buffered<H: Handler>(&mut self, handler: &mut H)
where
E: Receive<H>,
{
let meta = RxMeta::default();
for i in 0..self.conns.len_slots() {
let id = self.conns.id(i);
if !self.conns.is_open(i) {
continue;
}
let Some(c) = self.conns.at(i) else {
continue;
};
if !c.has_buffered_input() {
continue;
}
let r = c.on_received(id, 0, &meta, handler);
#[cfg(feature = "uring")]
if let (Ok(_), Imp::Uring(u)) = (&r, &mut self.imp) {
u.arm_initial(c, i);
}
match r {
Ok(n) => self.stats.messages += n as u64,
Err(e) => {
let e = c.close_reason(e);
close_index(&mut self.imp, &mut self.conns, i);
self.stats.closed += 1;
handler.on_closed(id, e);
}
}
}
}
pub fn flush(&mut self) -> Result<()> {
struct Defer(Vec<(ConnId, Error)>);
impl Handler for Defer {
fn on_closed(&mut self, id: ConnId, error: Error) {
self.0.push((id, error));
}
}
let mut d = Defer(std::mem::take(&mut self.unreported));
let r = self.flush_with(&mut d);
self.unreported = d.0;
r
}
#[inline]
pub fn flush_with<H: Handler>(&mut self, handler: &mut H) -> Result<()> {
let r = self.send_queued(handler);
if !self.conns.closed.is_empty() {
self.free_closed();
}
r
}
#[inline]
#[cfg_attr(not(feature = "uring"), allow(clippy::unnecessary_wraps))]
fn send_queued<H: Handler>(&mut self, handler: &mut H) -> Result<()> {
match &mut self.imp {
Imp::Epoll(e) => {
for i in 0..self.conns.len_slots() {
let Some(c) = self.conns.at(i) else {
continue;
};
if c.tx_queue().is_empty() {
continue;
}
self.stats.send_calls += 1;
self.stats.sends += 1;
let fd = c.fd();
match sys::send(fd, c.tx_queue()) {
Ok(n) => {
c.tx_queue().drain(..n);
c.on_handed(n);
}
Err(err) => {
let err = c.close_reason(err.into());
let id = self.conns.id(i);
e.remove(fd);
self.conns.remove(i);
self.stats.closed += 1;
handler.on_closed(id, err);
}
}
}
Ok(())
}
#[cfg(feature = "uring")]
Imp::Uring(u) => u.flush(&mut self.conns, &mut self.stats),
}
}
}
impl<E> Drop for Driver<E> {
fn drop(&mut self) {
#[cfg(feature = "uring")]
if let Imp::Uring(u) = &mut self.imp
&& !u.shutdown()
{
self.conns.leak();
}
}
}
fn timestamp_keeper() -> Option<socket2::Socket> {
use std::os::fd::AsRawFd;
let s = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::DGRAM, None).ok()?;
sys::enable_rx_timestamps(s.as_raw_fd()).ok()?;
Some(s)
}
fn close_index<E: Endpoint>(imp: &mut Imp, conns: &mut Conns<E>, i: usize) {
match imp {
Imp::Epoll(e) => {
if let Some(fd) = conns.at(i).map(|c| c.fd()) {
e.remove(fd);
conns.remove(i);
}
}
#[cfg(feature = "uring")]
Imp::Uring(u) => u.close(conns, i),
}
}
fn advance_one<E: Endpoint>(
p: &mut Pending<E>,
resolver: &mut Resolver,
stats: &mut DriverStats,
now: i64,
tpu: f64,
check: &dyn Fn(RawFd) -> std::result::Result<RouteCheck, QuickAckError>,
) -> Result<Option<E>> {
if now > p.deadline_ns {
return Err(Error::Timeout("connect"));
}
if !p.started {
match resolver.lookup(p.dial.host(), now)? {
Lookup::Ready(ip) => {
p.dial
.start(SocketAddr::new(IpAddr::V4(ip), p.dial.port()))?;
p.started = true;
}
Lookup::Pending => return Ok(None),
}
}
let t0 = rdtsc();
let r = p.dial.step();
let ns = ticks_ns(rdtsc().wrapping_sub(t0), tpu);
stats.connect_step_max_ns = stats.connect_step_max_ns.max(ns);
if let (Ok(None), Some(fd)) = (&r, p.dial.awaiting_route()) {
let t0 = rdtsc();
let found = check(fd);
let ns = ticks_ns(rdtsc().wrapping_sub(t0), tpu);
stats.route_lookups += 1;
stats.route_check_ns += ns;
stats.route_check_max_ns = stats.route_check_max_ns.max(ns);
p.dial
.route_checked(found.map_err(|e| Error::QuickAck(Box::new(e)))?);
}
r
}
fn recv_one<E, H>(
conn: &mut E,
id: ConnId,
slot: &mut sys::MsgSlot,
handler: &mut H,
stats: &mut DriverStats,
read_seq: &mut u64,
) -> Result<()>
where
E: Receive<H>,
H: Handler,
{
slot.arm(conn.rx_space());
stats.recvmsg_calls += 1;
match sys::recvmsg(conn.fd(), slot)? {
None => {
stats.recvmsg_eagain += 1;
Ok(())
}
Some(0) => Err(Error::Closed),
Some(n) => {
*read_seq += 1;
stats.completions += 1;
let meta = slot.meta(*read_seq);
stats.messages += conn.on_received(id, n, &meta, handler)? as u64;
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::io::Read;
use std::net::{TcpListener, TcpStream};
use std::time::{Duration, Instant};
use super::*;
use crate::endpoint::sealed::DialIo;
use crate::http::{HttpConn, HttpDial, HttpOptions};
use crate::net::Target;
use crate::route::RouteError;
type R<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
fn gated() -> R<(Pending<HttpConn>, TcpStream)> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let addr = listener.local_addr()?;
let target = Target::new("localhost", addr.port(), "/").with_addr(addr);
let tls = crate::tls::client_config(rustls::RootCertStore::empty())?;
let mut dial = HttpDial::new(target, tls, HttpOptions::default())?;
dial.gate_route();
dial.start(addr)?;
let p = Pending {
slot: 0,
dial: Box::new(dial),
started: true,
deadline_ns: monotonic_ns() + 5_000_000_000,
};
let (server, _) = listener.accept()?;
Ok((p, server))
}
fn step(
p: &mut Pending<HttpConn>,
stats: &mut DriverStats,
check: &dyn Fn(RawFd) -> std::result::Result<RouteCheck, QuickAckError>,
until: &dyn Fn(&DriverStats) -> bool,
) -> R<Result<Option<HttpConn>>> {
let mut resolver = Resolver::new(DnsConfig::system());
let tpu = ticks_per_us();
let end = Instant::now() + Duration::from_secs(5);
loop {
let r = advance_one(p, &mut resolver, stats, monotonic_ns(), tpu, check);
if !matches!(r, Ok(None)) || until(stats) {
return Ok(r);
}
if Instant::now() > end {
return Err(format!("no progress: {stats:?}").into());
}
}
}
#[test]
fn a_refused_route_costs_no_handshake() -> R {
let (mut p, mut server) = gated()?;
let mut stats = DriverStats::default();
let asked = Cell::new(0);
let refuse = |_: RawFd| {
asked.set(asked.get() + 1);
Err(QuickAckError::Lookup {
flow: None,
error: RouteError::Timeout,
})
};
let out = step(&mut p, &mut stats, &refuse, &|_| false)?;
assert!(matches!(out, Err(Error::QuickAck(_))), "{:?}", out.err());
assert_eq!(asked.get(), 1);
assert_eq!(stats.route_lookups, 1);
drop(p);
server.set_read_timeout(Some(Duration::from_secs(5)))?;
let mut b = [0u8; 64];
assert_eq!(server.read(&mut b)?, 0, "TLS bytes before the route check");
Ok(())
}
#[test]
fn the_handshake_starts_only_after_the_route_is_checked() -> R {
let (mut p, server) = gated()?;
server.set_nonblocking(true)?;
let mut stats = DriverStats::default();
let silent_then = Cell::new(None);
let pass = |_: RawFd| {
let mut b = [0u8; 1];
silent_then.set(Some(matches!(
server.peek(&mut b),
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock
)));
Ok(RouteCheck::NotChecked)
};
let out = step(&mut p, &mut stats, &pass, &|s| s.route_lookups > 0)?;
assert!(matches!(out, Ok(None)), "{:?}", out.err());
assert_eq!(silent_then.get(), Some(true), "TLS bytes before the check");
let mut b = [0u8; 1];
let end = Instant::now() + Duration::from_secs(5);
loop {
let _ = step(&mut p, &mut stats, &pass, &|_| true)?;
match server.peek(&mut b) {
Ok(1) => break,
_ if Instant::now() > end => return Err("no ClientHello after the check".into()),
_ => std::thread::sleep(Duration::from_millis(1)),
}
}
assert_eq!(b[0], 0x16, "not a TLS handshake record");
assert_eq!(stats.route_lookups, 1, "checked once");
Ok(())
}
}