use std::{
collections::{HashMap, HashSet, hash_map::Entry},
io,
net::{IpAddr, SocketAddr},
ops::Not,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
use parking_lot::{Mutex, RwLock};
use tokio::{
io::{AsyncRead, AsyncWrite},
net::TcpStream,
sync::{Notify, oneshot},
task::JoinHandle,
};
use tracing::*;
use crate::Stats;
#[cfg(doc)]
use crate::{
Node,
protocols::{Handshake, OnConnect, OnDisconnect, Reading, Writing},
};
pub(crate) struct Connections {
pub(crate) active: RwLock<HashMap<SocketAddr, Connection>>,
pub(crate) limits: Mutex<ConnectionLimits>,
shutting_down: Arc<AtomicBool>,
pub(crate) drain_notify: Notify,
}
impl Connections {
pub(crate) fn new(shutting_down: Arc<AtomicBool>) -> Self {
Self {
active: Default::default(),
limits: Default::default(),
shutting_down,
drain_notify: Notify::new(),
}
}
pub(crate) fn add(&self, conn: Connection, mut guard: ConnectionGuard<'_>) -> io::Result<()> {
let mut active = self.active.write();
if self.shutting_down.load(Ordering::Acquire) {
return Err(io::Error::other("shutting down"));
}
active.insert(conn.addr(), conn);
guard.completed = true;
Ok(())
}
pub(crate) fn get_info(&self, addr: SocketAddr) -> Option<ConnectionInfo> {
self.active.read().get(&addr).map(|conn| conn.info.clone())
}
pub(crate) fn infos(&self) -> HashMap<SocketAddr, ConnectionInfo> {
self.active
.read()
.iter()
.map(|(addr, conn)| (*addr, conn.info.clone()))
.collect()
}
pub(crate) fn is_connected(&self, addr: SocketAddr) -> bool {
self.active.read().contains_key(&addr)
}
pub(crate) fn remove(&self, addr: SocketAddr) -> Option<Connection> {
let removed = self.active.write().remove(&addr);
if removed.is_some() {
self.drain_notify.notify_waiters();
}
removed
}
pub(crate) fn num_connected(&self) -> usize {
self.active.read().len()
}
pub(crate) fn addrs(&self) -> Vec<SocketAddr> {
self.active.read().keys().copied().collect()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConnectionSide {
Initiator,
Responder,
}
impl Not for ConnectionSide {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Self::Initiator => Self::Responder,
Self::Responder => Self::Initiator,
}
}
}
pub(crate) trait AR: AsyncRead + Unpin + Send + Sync {}
impl<T: AsyncRead + Unpin + Send + Sync> AR for T {}
pub(crate) trait AW: AsyncWrite + Unpin + Send + Sync {}
impl<T: AsyncWrite + Unpin + Send + Sync> AW for T {}
#[derive(Clone)]
pub struct ConnectionInfo {
span: Span,
addr: SocketAddr,
side: ConnectionSide,
stats: Arc<Stats>,
}
impl ConnectionInfo {
#[inline]
pub const fn addr(&self) -> SocketAddr {
self.addr
}
#[inline]
pub const fn side(&self) -> ConnectionSide {
self.side
}
#[inline]
pub const fn stats(&self) -> &Arc<Stats> {
&self.stats
}
#[inline]
pub const fn span(&self) -> &Span {
&self.span
}
}
pub struct Connection {
pub(crate) info: ConnectionInfo,
pub(crate) stream: Option<TcpStream>,
pub(crate) reader: Option<Box<dyn AR>>,
pub(crate) writer: Option<Box<dyn AW>>,
pub(crate) readiness_notifier: Option<oneshot::Sender<()>>,
pub(crate) disconnecting: AtomicBool,
pub(crate) tasks: Vec<JoinHandle<()>>,
}
impl Connection {
pub(crate) fn new(
addr: SocketAddr,
stream: TcpStream,
side: ConnectionSide,
span: Span,
) -> Self {
Self {
info: ConnectionInfo {
span,
addr,
side,
stats: Default::default(),
},
stream: Some(stream),
reader: None,
writer: None,
readiness_notifier: None,
disconnecting: Default::default(),
tasks: Default::default(),
}
}
#[inline]
pub const fn info(&self) -> &ConnectionInfo {
&self.info
}
#[inline]
pub const fn addr(&self) -> SocketAddr {
self.info.addr()
}
#[inline]
pub const fn side(&self) -> ConnectionSide {
self.info.side()
}
#[inline]
pub const fn stats(&self) -> &Arc<Stats> {
self.info.stats()
}
#[inline]
pub const fn span(&self) -> &Span {
&self.info.span
}
}
impl Drop for Connection {
fn drop(&mut self) {
for task in self.tasks.iter().rev() {
task.abort();
}
}
}
#[derive(Default)]
pub(crate) struct ConnectionLimits {
pub(crate) connecting: HashSet<SocketAddr>,
pub(crate) ip_counts: HashMap<IpAddr, usize>,
}
impl ConnectionLimits {
pub(crate) fn reserve(&mut self, addr: SocketAddr) {
self.connecting.insert(addr);
*self.ip_counts.entry(canonical_ip(addr)).or_insert(0) += 1;
}
pub(crate) fn ip_count(&self, addr: SocketAddr) -> usize {
self.ip_counts
.get(&canonical_ip(addr))
.copied()
.unwrap_or(0)
}
pub(crate) fn release_ip(&mut self, addr: SocketAddr) {
let ip = canonical_ip(addr);
debug_assert!(
self.ip_counts.get(&ip).copied().unwrap_or(0) >= 1,
"ip_count for {ip} underflowing: release with no live reservation",
);
if let Entry::Occupied(mut e) = self.ip_counts.entry(ip) {
if *e.get() > 1 {
*e.get_mut() -= 1;
} else {
e.remove();
}
}
}
}
const fn canonical_ip(addr: SocketAddr) -> IpAddr {
addr.ip().to_canonical()
}
pub(crate) struct ConnectionGuard<'a> {
pub(crate) addr: SocketAddr,
pub(crate) connections: &'a Connections,
pub(crate) completed: bool,
}
impl<'a> Drop for ConnectionGuard<'a> {
fn drop(&mut self) {
let mut limits = self.connections.limits.lock();
limits.connecting.remove(&self.addr);
if !self.completed {
limits.release_ip(self.addr);
}
}
}
pub(crate) fn create_connection_span(addr: SocketAddr, parent: &Span) -> Span {
macro_rules! try_span {
($lvl:expr) => {
let s = span!(parent: parent, $lvl, "conn", addr = %addr);
if !s.is_disabled() {
return s;
}
};
}
try_span!(Level::TRACE);
try_span!(Level::DEBUG);
try_span!(Level::INFO);
try_span!(Level::WARN);
error_span!(parent: parent, "conn", addr = %addr)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DisconnectOrigin {
OnConnectAbort,
Reading,
Shutdown,
User,
Writing,
}
#[cfg(test)]
mod ip_count_tests {
use super::*;
use std::sync::atomic::AtomicBool;
fn new_conns() -> Connections {
Connections::new(Arc::new(AtomicBool::new(false)))
}
fn reserve(conns: &Connections, addr: SocketAddr) -> ConnectionGuard<'_> {
conns.limits.lock().reserve(addr);
ConnectionGuard {
addr,
connections: conns,
completed: false,
}
}
fn disconnect_decrement(conns: &Connections, addr: SocketAddr) {
conns.limits.lock().release_ip(addr);
}
fn ip_count(conns: &Connections, addr: SocketAddr) -> Option<usize> {
conns
.limits
.lock()
.ip_counts
.get(&canonical_ip(addr))
.copied()
}
#[test]
fn completed_guard_drop_after_concurrent_decrement_is_a_noop() {
let conns = new_conns();
let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap();
let mut guard = reserve(&conns, addr);
guard.completed = true;
disconnect_decrement(&conns, addr);
assert_eq!(ip_count(&conns, addr), None);
drop(guard);
assert_eq!(ip_count(&conns, addr), None);
assert!(!conns.limits.lock().connecting.contains(&addr)); }
#[test]
fn failed_guard_drop_decrements() {
let conns = new_conns();
let addr: SocketAddr = "127.0.0.1:9001".parse().unwrap();
let guard = reserve(&conns, addr); assert_eq!(ip_count(&conns, addr), Some(1));
drop(guard);
assert_eq!(ip_count(&conns, addr), None);
assert!(!conns.limits.lock().connecting.contains(&addr));
}
#[test]
fn per_ip_count_tracks_multiple_addrs() {
let conns = new_conns();
let a1: SocketAddr = "127.0.0.1:1".parse().unwrap();
let a2: SocketAddr = "127.0.0.1:2".parse().unwrap();
let g1 = reserve(&conns, a1);
let g2 = reserve(&conns, a2);
assert_eq!(ip_count(&conns, a1), Some(2));
drop(g1); assert_eq!(ip_count(&conns, a1), Some(1));
drop(g2); assert_eq!(ip_count(&conns, a1), None);
}
#[test]
fn ipv4_mapped_v6_shares_the_native_v4_bucket() {
let conns = new_conns();
let v4: SocketAddr = "127.0.0.1:8000".parse().unwrap();
let mapped: SocketAddr = "[::ffff:127.0.0.1]:8001".parse().unwrap();
assert_ne!(v4.ip(), mapped.ip());
assert_eq!(canonical_ip(v4), canonical_ip(mapped));
let g1 = reserve(&conns, v4);
let g2 = reserve(&conns, mapped);
assert_eq!(ip_count(&conns, v4), Some(2));
assert_eq!(ip_count(&conns, mapped), Some(2));
drop(g1); assert_eq!(ip_count(&conns, v4), Some(1));
drop(g2); assert_eq!(ip_count(&conns, mapped), None);
}
}