663 lines
25 KiB
Rust
663 lines
25 KiB
Rust
//! Authenticated, opaque-datagram direct UDP transport.
|
|
|
|
use std::{
|
|
collections::HashMap,
|
|
net::SocketAddr,
|
|
sync::{
|
|
atomic::{AtomicU32, Ordering},
|
|
Arc,
|
|
},
|
|
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
|
};
|
|
|
|
use anyhow::{anyhow, Context, Result};
|
|
use bytes::Bytes;
|
|
use rand::{rngs::OsRng, RngCore};
|
|
use tokio::{
|
|
net::{lookup_host, UdpSocket},
|
|
sync::Mutex,
|
|
task::JoinSet,
|
|
time::{self, timeout},
|
|
};
|
|
use tokio_util::sync::CancellationToken;
|
|
use tracing::{debug, info, warn};
|
|
|
|
use crate::{
|
|
config::{ClientConfig, ServerConfig},
|
|
protocol::{
|
|
self, auth,
|
|
direct::{
|
|
AcceptPacket, DataPacket, HelloPacket, Packet, PacketType, RejectPacket, RejectReason,
|
|
SessionPacket,
|
|
},
|
|
replay::{NonceReplayCache, ReplayWindow},
|
|
},
|
|
stats::Stats,
|
|
};
|
|
|
|
use super::{TransportConnector, TransportError, TransportFuture, TransportSession};
|
|
|
|
const DEFAULT_MAX_DATAGRAM: usize = 1200;
|
|
const MAX_UDP_DATAGRAM: usize = u16::MAX as usize;
|
|
|
|
fn unix_timestamp() -> u64 {
|
|
SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs()
|
|
}
|
|
|
|
async fn resolve_server(server: &str, port: u16) -> Result<SocketAddr> {
|
|
if let Ok(addr) = server.parse::<SocketAddr>() {
|
|
return Ok(addr);
|
|
}
|
|
let endpoint = format!("{server}:{port}");
|
|
lookup_host(endpoint.clone())
|
|
.await?
|
|
.next()
|
|
.ok_or_else(|| anyhow!("could not resolve {endpoint}"))
|
|
}
|
|
|
|
fn direct_bind_addr(listen: &str, port: u16) -> Result<String> {
|
|
if listen.parse::<SocketAddr>().is_ok() {
|
|
Ok(listen.to_owned())
|
|
} else {
|
|
Ok(format!("{listen}:{port}"))
|
|
}
|
|
}
|
|
|
|
/// Established client-side direct UDP session.
|
|
pub struct DirectUdpSession {
|
|
socket: Arc<UdpSocket>,
|
|
peer: Mutex<SocketAddr>,
|
|
session_id: u64,
|
|
session_key: [u8; auth::SESSION_KEY_LEN],
|
|
send_sequence: AtomicU32,
|
|
receive_window: Mutex<ReplayWindow>,
|
|
max_datagram: usize,
|
|
closed: CancellationToken,
|
|
}
|
|
|
|
impl DirectUdpSession {
|
|
pub fn session_id(&self) -> u64 {
|
|
self.session_id
|
|
}
|
|
pub fn peer(&self) -> TransportFuture<'_, SocketAddr> {
|
|
Box::pin(async move { Ok(*self.peer.lock().await) })
|
|
}
|
|
|
|
pub async fn connect(config: &ClientConfig) -> Result<Self, TransportError> {
|
|
let secret = config.client.secret.load_bytes("client").map_err(|e| {
|
|
TransportError::Io(std::io::Error::new(
|
|
std::io::ErrorKind::InvalidInput,
|
|
e.to_string(),
|
|
))
|
|
})?;
|
|
let peer = resolve_server(&config.client.server, config.transport.direct_udp.port)
|
|
.await
|
|
.map_err(|e| {
|
|
TransportError::Io(std::io::Error::new(
|
|
std::io::ErrorKind::NotFound,
|
|
e.to_string(),
|
|
))
|
|
})?;
|
|
let socket = UdpSocket::bind("0.0.0.0:0").await?;
|
|
let socket = Arc::new(socket);
|
|
let mut nonce = [0u8; 16];
|
|
OsRng.fill_bytes(&mut nonce);
|
|
let hello = HelloPacket::authenticated(
|
|
&secret,
|
|
Bytes::copy_from_slice(config.client.id.as_bytes()),
|
|
unix_timestamp(),
|
|
nonce,
|
|
)?;
|
|
let encoded = hello.encode()?;
|
|
let handshake_timeout =
|
|
Duration::from_millis(config.transport.auto.probe_timeout_ms.max(1))
|
|
.min(Duration::from_secs(30));
|
|
for _attempt in 0..config.transport.auto.retries.max(1) {
|
|
socket.send_to(&encoded, peer).await?;
|
|
let mut buf = vec![0u8; MAX_UDP_DATAGRAM];
|
|
match timeout(handshake_timeout, socket.recv_from(&mut buf)).await {
|
|
Ok(Ok((len, source))) => {
|
|
let packet = match protocol::parse_packet(&buf[..len]) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
debug!(error=%e, "ignoring malformed handshake response");
|
|
continue;
|
|
}
|
|
};
|
|
match packet {
|
|
Packet::Accept(accept) => {
|
|
if source.ip() != peer.ip()
|
|
|| !accept.verify_authenticator(&secret, &nonce)
|
|
{
|
|
continue;
|
|
}
|
|
let max_datagram = (config.network.max_datagram_size as usize)
|
|
.min(accept.max_datagram as usize);
|
|
let session_key = auth::derive_session_key(
|
|
&secret,
|
|
&nonce,
|
|
&accept.server_nonce,
|
|
accept.session_id,
|
|
);
|
|
info!(session_id=accept.session_id, remote_addr=%source, max_datagram, "direct UDP session authenticated");
|
|
return Ok(Self {
|
|
socket,
|
|
peer: Mutex::new(source),
|
|
session_id: accept.session_id,
|
|
session_key,
|
|
send_sequence: AtomicU32::new(auth::generate_sequence_number()),
|
|
receive_window: Mutex::new(ReplayWindow::new()),
|
|
max_datagram,
|
|
closed: CancellationToken::new(),
|
|
});
|
|
}
|
|
Packet::Reject(reject) => {
|
|
return Err(TransportError::AuthenticationRejected(reject.reason))
|
|
}
|
|
_ => continue,
|
|
}
|
|
}
|
|
Ok(Err(e)) => return Err(e.into()),
|
|
Err(_) => continue,
|
|
}
|
|
}
|
|
Err(TransportError::HandshakeTimeout)
|
|
}
|
|
|
|
async fn send_packet(&self, bytes: &[u8]) -> Result<(), TransportError> {
|
|
if self.closed.is_cancelled() {
|
|
return Err(TransportError::Closed);
|
|
}
|
|
let peer = *self.peer.lock().await;
|
|
self.socket.send_to(bytes, peer).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Send an authenticated control keepalive. It intentionally shares the
|
|
/// outgoing DATA sequence space only within this direction.
|
|
pub async fn send_keepalive(&self) -> Result<(), TransportError> {
|
|
let sequence = self.send_sequence.fetch_add(1, Ordering::Relaxed);
|
|
let packet = SessionPacket::authenticated(
|
|
PacketType::Keepalive,
|
|
&self.session_key,
|
|
self.session_id,
|
|
sequence,
|
|
);
|
|
self.send_packet(&packet.encode()?).await
|
|
}
|
|
|
|
async fn receive_valid(&self) -> Result<(Packet, SocketAddr), TransportError> {
|
|
let mut buf = vec![0u8; MAX_UDP_DATAGRAM];
|
|
loop {
|
|
let (len, source) = self.socket.recv_from(&mut buf).await?;
|
|
let packet = match protocol::parse_packet(&buf[..len]) {
|
|
Ok(p) => p,
|
|
Err(e) => {
|
|
debug!(error=%e, "dropping malformed direct UDP packet");
|
|
continue;
|
|
}
|
|
};
|
|
let valid = match &packet {
|
|
Packet::Data(p) => {
|
|
p.session_id == self.session_id && p.verify_authenticator(&self.session_key)
|
|
}
|
|
Packet::Keepalive(p) | Packet::Close(p) => {
|
|
p.session_id == self.session_id && p.verify_authenticator(&self.session_key)
|
|
}
|
|
_ => false,
|
|
};
|
|
if !valid {
|
|
continue;
|
|
}
|
|
let sequence = match &packet {
|
|
Packet::Data(p) => p.sequence,
|
|
Packet::Keepalive(p) | Packet::Close(p) => p.sequence,
|
|
_ => unreachable!(),
|
|
};
|
|
if !self.receive_window.lock().await.accept(sequence) {
|
|
continue;
|
|
}
|
|
*self.peer.lock().await = source;
|
|
return Ok((packet, source));
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TransportSession for DirectUdpSession {
|
|
fn send_datagram<'a>(&'a self, data: Bytes) -> TransportFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
if data.len() > self.max_datagram {
|
|
return Err(TransportError::Oversized {
|
|
maximum: self.max_datagram,
|
|
});
|
|
}
|
|
let sequence = self.send_sequence.fetch_add(1, Ordering::Relaxed);
|
|
let packet =
|
|
DataPacket::authenticated(&self.session_key, self.session_id, sequence, data);
|
|
self.send_packet(&packet.encode()?).await
|
|
})
|
|
}
|
|
fn recv_datagram<'a>(&'a self) -> TransportFuture<'a, Bytes> {
|
|
Box::pin(async move {
|
|
loop {
|
|
match self.receive_valid().await? {
|
|
(Packet::Data(packet), _) => return Ok(packet.payload),
|
|
(Packet::Keepalive(_), _) => continue,
|
|
(Packet::Close(_), _) => {
|
|
self.closed.cancel();
|
|
return Err(TransportError::Closed);
|
|
}
|
|
_ => continue,
|
|
}
|
|
}
|
|
})
|
|
}
|
|
fn close<'a>(&'a self) -> TransportFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
if self.closed.is_cancelled() {
|
|
return Ok(());
|
|
}
|
|
let sequence = self.send_sequence.fetch_add(1, Ordering::Relaxed);
|
|
let packet = SessionPacket::authenticated(
|
|
PacketType::Close,
|
|
&self.session_key,
|
|
self.session_id,
|
|
sequence,
|
|
);
|
|
let _ = self.send_packet(&packet.encode()?).await;
|
|
self.closed.cancel();
|
|
Ok(())
|
|
})
|
|
}
|
|
fn max_datagram_size(&self) -> usize {
|
|
self.max_datagram
|
|
}
|
|
}
|
|
|
|
pub struct DirectUdpConnector {
|
|
config: ClientConfig,
|
|
}
|
|
impl DirectUdpConnector {
|
|
pub fn new(config: ClientConfig) -> Self {
|
|
Self { config }
|
|
}
|
|
}
|
|
impl TransportConnector for DirectUdpConnector {
|
|
fn connect<'a>(&'a self) -> TransportFuture<'a, Box<dyn TransportSession>> {
|
|
Box::pin(async move {
|
|
Ok(Box::new(DirectUdpSession::connect(&self.config).await?)
|
|
as Box<dyn TransportSession>)
|
|
})
|
|
}
|
|
}
|
|
|
|
struct ServerSession {
|
|
id: u64,
|
|
client_id: String,
|
|
key: [u8; auth::SESSION_KEY_LEN],
|
|
peer: Mutex<SocketAddr>,
|
|
backend: Arc<UdpSocket>,
|
|
receive_window: Mutex<ReplayWindow>,
|
|
send_sequence: AtomicU32,
|
|
last_activity: Mutex<Instant>,
|
|
max_datagram: usize,
|
|
cancel: CancellationToken,
|
|
}
|
|
|
|
/// Multi-client direct UDP server. Each authenticated session gets its own
|
|
/// connected backend socket and therefore natural response demultiplexing.
|
|
pub struct DirectUdpServer {
|
|
socket: Arc<UdpSocket>,
|
|
backend_target: SocketAddr,
|
|
clients: HashMap<String, Vec<u8>>,
|
|
sessions: Arc<Mutex<HashMap<u64, Arc<ServerSession>>>>,
|
|
nonces: Arc<Mutex<NonceReplayCache>>,
|
|
pending: Arc<Mutex<HashMap<SocketAddr, Instant>>>,
|
|
config: ServerConfig,
|
|
stats: Arc<Stats>,
|
|
}
|
|
|
|
impl DirectUdpServer {
|
|
pub async fn bind(config: ServerConfig) -> Result<Self> {
|
|
let bind = direct_bind_addr(&config.server.listen, config.transports.direct_udp.port)?;
|
|
let socket = Arc::new(
|
|
UdpSocket::bind(&bind)
|
|
.await
|
|
.context("bind direct UDP listener")?,
|
|
);
|
|
let backend_target = config
|
|
.backend
|
|
.target
|
|
.parse::<SocketAddr>()
|
|
.context("invalid backend target")?;
|
|
let mut clients = HashMap::new();
|
|
for client in &config.auth.clients {
|
|
clients.insert(
|
|
client.id.clone(),
|
|
client
|
|
.secret
|
|
.load_bytes(&format!("auth client {}", client.id))?,
|
|
);
|
|
}
|
|
Ok(Self {
|
|
socket,
|
|
backend_target,
|
|
clients,
|
|
sessions: Arc::new(Mutex::new(HashMap::new())),
|
|
nonces: Arc::new(Mutex::new(NonceReplayCache::new(Duration::from_secs(120)))),
|
|
pending: Arc::new(Mutex::new(HashMap::new())),
|
|
config,
|
|
stats: Arc::new(Stats::default()),
|
|
})
|
|
}
|
|
pub fn local_addr(&self) -> Result<SocketAddr> {
|
|
Ok(self.socket.local_addr()?)
|
|
}
|
|
pub fn stats(&self) -> Arc<Stats> {
|
|
self.stats.clone()
|
|
}
|
|
pub async fn active_sessions(&self) -> usize {
|
|
self.sessions.lock().await.len()
|
|
}
|
|
|
|
async fn send_reject(&self, peer: SocketAddr, reason: RejectReason) {
|
|
if let Ok(bytes) = RejectPacket::new(reason).encode() {
|
|
if self.socket.send_to(&bytes, peer).await.is_ok() {
|
|
self.stats.add_sent(bytes.len());
|
|
}
|
|
}
|
|
self.stats
|
|
.authentication_rejections
|
|
.fetch_add(1, Ordering::Relaxed);
|
|
self.stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
async fn handle_hello(
|
|
&self,
|
|
hello: protocol::HelloPacket,
|
|
peer: SocketAddr,
|
|
) -> Result<Option<Arc<ServerSession>>> {
|
|
self.stats
|
|
.handshake_attempts
|
|
.fetch_add(1, Ordering::Relaxed);
|
|
let now = Instant::now();
|
|
{
|
|
let mut pending = self.pending.lock().await;
|
|
let expiry = Duration::from_millis(self.config.limits.handshake_timeout_ms.max(1));
|
|
pending.retain(|_, seen| now.duration_since(*seen) < expiry);
|
|
if !pending.contains_key(&peer)
|
|
&& pending.len() >= self.config.limits.max_pending_handshakes
|
|
{
|
|
self.stats
|
|
.rate_limited_attempts
|
|
.fetch_add(1, Ordering::Relaxed);
|
|
drop(pending);
|
|
self.send_reject(peer, RejectReason::RateLimited).await;
|
|
return Ok(None);
|
|
}
|
|
pending.insert(peer, now);
|
|
}
|
|
let source_ip = peer.ip();
|
|
let active = self.sessions.lock().await;
|
|
let sessions_for_ip = {
|
|
let mut count = 0;
|
|
for session in active.values() {
|
|
if session.peer.lock().await.ip() == source_ip {
|
|
count += 1;
|
|
}
|
|
}
|
|
count
|
|
};
|
|
if active.len() >= self.config.limits.max_sessions
|
|
|| sessions_for_ip >= self.config.limits.max_sessions_per_ip
|
|
{
|
|
drop(active);
|
|
self.pending.lock().await.remove(&peer);
|
|
self.send_reject(peer, RejectReason::ServerBusy).await;
|
|
return Ok(None);
|
|
}
|
|
drop(active);
|
|
let Some(secret) = self
|
|
.clients
|
|
.get(std::str::from_utf8(&hello.client_id).unwrap_or(""))
|
|
else {
|
|
self.pending.lock().await.remove(&peer);
|
|
self.send_reject(peer, RejectReason::AuthenticationFailed)
|
|
.await;
|
|
return Ok(None);
|
|
};
|
|
if !hello.is_fresh(SystemTime::now(), Duration::from_secs(60))
|
|
|| !hello.verify_authenticator(secret)
|
|
{
|
|
self.stats
|
|
.authentication_failures
|
|
.fetch_add(1, Ordering::Relaxed);
|
|
self.pending.lock().await.remove(&peer);
|
|
self.send_reject(peer, RejectReason::AuthenticationFailed)
|
|
.await;
|
|
return Ok(None);
|
|
}
|
|
if !self.nonces.lock().await.check_and_record(
|
|
&hello.client_id,
|
|
hello.client_nonce,
|
|
Instant::now(),
|
|
) {
|
|
self.pending.lock().await.remove(&peer);
|
|
self.send_reject(peer, RejectReason::ReplayDetected).await;
|
|
return Ok(None);
|
|
}
|
|
let backend = Arc::new(UdpSocket::bind("0.0.0.0:0").await?);
|
|
backend.connect(self.backend_target).await?;
|
|
let mut session_id = auth::generate_session_id();
|
|
let mut sessions = self.sessions.lock().await;
|
|
while sessions.contains_key(&session_id) {
|
|
session_id = auth::generate_session_id();
|
|
}
|
|
let mut server_nonce = [0u8; 16];
|
|
OsRng.fill_bytes(&mut server_nonce);
|
|
let idle = self.config.session.idle_timeout_secs.min(u32::MAX as u64) as u32;
|
|
let max_datagram = DEFAULT_MAX_DATAGRAM as u16;
|
|
let key = auth::derive_session_key(secret, &hello.client_nonce, &server_nonce, session_id);
|
|
let session = Arc::new(ServerSession {
|
|
id: session_id,
|
|
client_id: String::from_utf8_lossy(&hello.client_id).into_owned(),
|
|
key,
|
|
peer: Mutex::new(peer),
|
|
backend,
|
|
receive_window: Mutex::new(ReplayWindow::new()),
|
|
send_sequence: AtomicU32::new(auth::generate_sequence_number()),
|
|
last_activity: Mutex::new(Instant::now()),
|
|
max_datagram: max_datagram as usize,
|
|
cancel: CancellationToken::new(),
|
|
});
|
|
sessions.insert(session_id, session.clone());
|
|
drop(sessions);
|
|
self.pending.lock().await.remove(&peer);
|
|
let accept = AcceptPacket::authenticated(
|
|
secret,
|
|
session_id,
|
|
hello.client_nonce,
|
|
server_nonce,
|
|
idle,
|
|
max_datagram,
|
|
);
|
|
let accept_bytes = accept.encode()?;
|
|
self.socket.send_to(&accept_bytes, peer).await?;
|
|
self.stats.add_sent(accept_bytes.len());
|
|
info!(session_id, client_id=%session.client_id, remote_addr=%peer, "direct UDP client authenticated");
|
|
Ok(Some(session))
|
|
}
|
|
|
|
async fn remove_session(&self, id: u64, expired: bool) {
|
|
if let Some(session) = self.sessions.lock().await.remove(&id) {
|
|
session.cancel.cancel();
|
|
if expired {
|
|
self.stats.expired_sessions.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn run(self) -> Result<()> {
|
|
let mut tasks = JoinSet::new();
|
|
let mut buf = vec![0u8; MAX_UDP_DATAGRAM];
|
|
let mut cleanup = time::interval(Duration::from_secs(1));
|
|
let signal = shutdown_signal();
|
|
tokio::pin!(signal);
|
|
loop {
|
|
tokio::select! {
|
|
_ = &mut signal => { info!("direct UDP server shutting down"); break; }
|
|
Some(done) = tasks.join_next() => { if let Ok(id) = done { self.remove_session(id, false).await; } }
|
|
_ = cleanup.tick() => {
|
|
let now = Instant::now();
|
|
let expired: Vec<u64> = self.sessions.lock().await.iter().filter_map(|(&id,s)| async_ready_expired(s, now, Duration::from_secs(self.config.session.idle_timeout_secs)).map(|_| id)).collect();
|
|
for id in expired { self.remove_session(id, true).await; }
|
|
}
|
|
received = self.socket.recv_from(&mut buf) => {
|
|
let (len, peer) = match received { Ok(v) => v, Err(e) => { warn!(error=%e, "direct UDP receive failed"); continue; } };
|
|
self.stats.add_received(len);
|
|
let packet = match protocol::parse_packet(&buf[..len]) { Ok(p) => p, Err(e) => { self.stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed); debug!(remote_addr=%peer,error=%e,"dropping malformed direct UDP packet"); continue; } };
|
|
match packet {
|
|
Packet::Hello(hello) => match self.handle_hello(hello, peer).await {
|
|
Ok(Some(session)) => {
|
|
let listener = self.socket.clone(); let backend = session.backend.clone(); let session_clone = session.clone(); let stats = self.stats.clone();
|
|
tasks.spawn(async move { backend_loop(listener, backend, session_clone, stats).await });
|
|
}
|
|
Ok(None) => {}
|
|
Err(error) => warn!(remote_addr=%peer, error=%error, "failed to establish direct UDP session"),
|
|
},
|
|
Packet::Data(data) => if let Err(error) = self.handle_session_packet(PacketType::Data, data.session_id, data.sequence, data.auth_tag, data.payload, peer).await { self.stats.backend_errors.fetch_add(1, Ordering::Relaxed); self.stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed); warn!(session_id=data.session_id, error=%error, "backend forwarding failed"); },
|
|
Packet::Keepalive(p) => if let Err(error) = self.handle_session_packet(p.packet_type, p.session_id, p.sequence, p.auth_tag, Bytes::new(), peer).await { warn!(session_id=p.session_id, error=%error, "keepalive handling failed"); },
|
|
Packet::Close(p) => { if let Err(error) = self.handle_session_packet(p.packet_type, p.session_id, p.sequence, p.auth_tag, Bytes::new(), peer).await { warn!(session_id=p.session_id, error=%error, "close handling failed"); } self.remove_session(p.session_id, false).await; },
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let ids: Vec<u64> = self.sessions.lock().await.keys().copied().collect();
|
|
for id in ids {
|
|
self.remove_session(id, false).await;
|
|
}
|
|
while tasks.join_next().await.is_some() {}
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_session_packet(
|
|
&self,
|
|
packet_type: PacketType,
|
|
id: u64,
|
|
sequence: u32,
|
|
tag: [u8; 16],
|
|
payload: Bytes,
|
|
peer: SocketAddr,
|
|
) -> Result<()> {
|
|
let session = match self.sessions.lock().await.get(&id).cloned() {
|
|
Some(s) => s,
|
|
None => {
|
|
self.stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed);
|
|
return Ok(());
|
|
}
|
|
};
|
|
if payload.len() > session.max_datagram {
|
|
self.stats
|
|
.oversized_datagrams
|
|
.fetch_add(1, Ordering::Relaxed);
|
|
self.stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed);
|
|
return Ok(());
|
|
}
|
|
if !auth::verify_data_auth_tag(
|
|
&session.key,
|
|
protocol::PROTOCOL_VERSION,
|
|
packet_type,
|
|
0,
|
|
id,
|
|
sequence,
|
|
&payload,
|
|
&tag,
|
|
) {
|
|
self.stats
|
|
.authentication_failures
|
|
.fetch_add(1, Ordering::Relaxed);
|
|
self.stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed);
|
|
return Ok(());
|
|
}
|
|
if !session.receive_window.lock().await.accept(sequence) {
|
|
self.stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed);
|
|
return Ok(());
|
|
}
|
|
*session.peer.lock().await = peer;
|
|
*session.last_activity.lock().await = Instant::now();
|
|
match packet_type {
|
|
PacketType::Data => {
|
|
session.backend.send(&payload).await?;
|
|
}
|
|
PacketType::Close => session.cancel.cancel(),
|
|
PacketType::Keepalive => {}
|
|
_ => {}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
async fn backend_loop(
|
|
listener: Arc<UdpSocket>,
|
|
backend: Arc<UdpSocket>,
|
|
session: Arc<ServerSession>,
|
|
stats: Arc<Stats>,
|
|
) -> u64 {
|
|
let mut buf = vec![0u8; MAX_UDP_DATAGRAM];
|
|
loop {
|
|
tokio::select! {
|
|
_ = session.cancel.cancelled() => break,
|
|
received = backend.recv(&mut buf) => {
|
|
let len = match received { Ok(len) => len, Err(_) => break };
|
|
if len > session.max_datagram {
|
|
stats.oversized_datagrams.fetch_add(1, Ordering::Relaxed);
|
|
stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed);
|
|
continue;
|
|
}
|
|
let sequence = session.send_sequence.fetch_add(1, Ordering::Relaxed);
|
|
let packet = DataPacket::authenticated(&session.key, session.id, sequence, Bytes::copy_from_slice(&buf[..len]));
|
|
let bytes = match packet.encode() { Ok(v) => v, Err(_) => continue };
|
|
let peer = *session.peer.lock().await;
|
|
if listener.send_to(&bytes, peer).await.is_err() {
|
|
stats.backend_errors.fetch_add(1, Ordering::Relaxed);
|
|
stats.datagrams_dropped.fetch_add(1, Ordering::Relaxed);
|
|
break;
|
|
}
|
|
stats.add_sent(bytes.len());
|
|
}
|
|
}
|
|
}
|
|
session.id
|
|
}
|
|
|
|
fn async_ready_expired(
|
|
session: &Arc<ServerSession>,
|
|
now: Instant,
|
|
timeout: Duration,
|
|
) -> Option<u64> {
|
|
session.last_activity.try_lock().ok().and_then(|last| {
|
|
if now.duration_since(*last) >= timeout {
|
|
Some(session.id)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
async fn shutdown_signal() {
|
|
#[cfg(unix)]
|
|
{
|
|
use tokio::signal::unix::{signal, SignalKind};
|
|
let mut term = signal(SignalKind::terminate()).expect("install SIGTERM handler");
|
|
tokio::select! { _ = tokio::signal::ctrl_c() => {}, _ = term.recv() => {} }
|
|
}
|
|
#[cfg(not(unix))]
|
|
{
|
|
let _ = tokio::signal::ctrl_c().await;
|
|
}
|
|
}
|