use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::time::{Duration, Instant};
use rustc_hash::FxHashMap;
use melin_app::Application;
use melin_app::auth::{AuthorizedKeys, ClientRole, KeyRole, RoleId};
use melin_app::unix_epoch_nanos;
use melin_dpdk::transport::DpdkTransport;
use melin_pipeline::ring;
use melin_transport_core::pipeline::InputSlot;
use melin_transport_core::tick::TickSchedule;
#[cfg(feature = "latency-trace")]
use melin_transport_core::trace::mono_trace_ns;
use melin_wire_protocol::control::ConnectionId;
use melin_wire_protocol::control::TransportResponse;
use melin_wire_protocol::control_codec;
use melin_wire_protocol::framing::split_frame_limited;
use rand::RngExt;
use melin_dpdk::SocketHandle;
use tracing::{debug, warn};
use crate::dpdk_response::{ControlEvent, TxFrame};
use crate::halt::{HaltGate, RefusalSender};
use crate::policy_control::AckPolicyPublisher;
use crate::client_auth::MAX_AUTH_FRAME;
use crate::client_frames::MAX_FRAME_SIZE;
const AUTH_TIMEOUT: Duration = Duration::from_secs(5);
enum AuthState {
WaitingForResponse {
nonce: [u8; 32],
accepted_at: Instant,
},
Authenticated { role: ClientRole<RoleId> },
Rejected {
accepted_at: Instant,
},
}
struct ConnectionState {
connection_id: ConnectionId,
addr: SocketAddr,
handle: SocketHandle,
auth: AuthState,
key_hash: u64,
parse_buf: Vec<u8>,
last_activity: Instant,
}
#[allow(clippy::too_many_arguments)]
pub fn run_dpdk_poll<A: Application>(
mut transport: DpdkTransport,
mut producer: ring::Producer<InputSlot<A::Event>>,
decoder: crate::reader::RequestDecoderArc<A>,
halt: HaltGate,
mut refusals: RefusalSender<A::Report>,
mut ack_policy: AckPolicyPublisher,
control_tx: mpsc::Sender<ControlEvent>,
mut tx_rx: melin_pipeline::spsc::Consumer<TxFrame>,
shutdown: &AtomicBool,
authorized_keys: Arc<AuthorizedKeys>,
connection_timeout: Option<Duration>,
tick_cadence: Option<Duration>,
max_connections: u64,
active_connections: Arc<std::sync::atomic::AtomicU64>,
thread_id: u8,
repl_driver: Option<crate::replication::DpdkReplicationDriver<A>>,
repl_listen_port: u16,
) {
let mut repl_driver = repl_driver;
let mut connections: Vec<Option<ConnectionState>> =
(0..melin_dpdk::MAX_CONNECTIONS).map(|_| None).collect();
let mut id_to_handle: FxHashMap<u64, SocketHandle> =
FxHashMap::with_capacity_and_hasher(256, Default::default());
let mut next_connection_id: u64 = 1;
let mut connection_count: usize = 0;
let mut conn_range_end: usize = 0;
#[cfg(feature = "latency-trace")]
let mut publish_rec =
melin_transport_core::trace::register_stage("dpdk: publish (decode → disruptor publish)");
#[cfg(feature = "tick-to-trade")]
let mut ingest_rec =
melin_transport_core::trace::register_stage("reader: ingest (recv_ts → publish complete)");
let mut parse_buf_pool: Vec<Vec<u8>> = (0..256)
.map(|_| Vec::with_capacity(MAX_FRAME_SIZE + 4))
.collect();
let mut rng = rand::rng();
let tick_enabled = tick_cadence.is_some();
let cadence = tick_cadence.unwrap_or(Duration::ZERO);
let mut tick_schedule = TickSchedule::new(cadence, Instant::now());
let mut tick_check_counter: u32 = 0;
const TICK_CHECK_INTERVAL: u32 = 4096;
let mut slow_check_counter: u32 = 0;
const SLOW_CHECK_INTERVAL: u32 = 1024 * 1024;
if tick_enabled {
tracing::info!(
cadence_ms = cadence.as_millis() as u64,
thread_id,
"tick generator integrated into DPDK poll thread"
);
}
#[cfg(feature = "latency-trace")]
let mut poll_iter_rec = melin_transport_core::trace::register_stage(
"dpdk poll: outer iteration (work-iterations only)",
);
#[cfg(feature = "latency-trace")]
let mut poll_iter_start = mono_trace_ns();
#[cfg(feature = "latency-trace")]
let stats_flush_interval_ns =
melin_transport_core::trace::IDLE_FLUSH_INTERVAL.as_nanos() as u64;
#[cfg(feature = "latency-trace")]
let mut last_stats_flush_ns = poll_iter_start;
loop {
if shutdown.load(Ordering::Relaxed) {
if let Some(driver) = repl_driver.take() {
driver.close_all_links(&mut transport);
}
break;
}
#[cfg(feature = "latency-trace")]
let mut work_done_this_iter = false;
tick_check_counter = tick_check_counter.wrapping_add(1);
if tick_check_counter >= TICK_CHECK_INTERVAL {
tick_check_counter = 0;
ack_policy.poll(&mut producer, &halt);
if tick_enabled {
tick_schedule.publish_if_due(Instant::now(), unix_epoch_nanos, &mut producer);
}
}
macro_rules! drain_tx {
() => {
while let Some((_seq, frame)) = tx_rx.try_consume() {
if let Some(&handle) = id_to_handle.get(&frame.connection_id)
&& !transport.queue_send(handle, frame.as_bytes())
{
debug!(
connection_id = frame.connection_id,
"DPDK: TX queue overflow, dropping connection"
);
transport.close(handle);
let _ = control_tx.send(ControlEvent::Disconnected {
connection_id: frame.connection_id,
});
id_to_handle.remove(&frame.connection_id);
release_slot(
&mut connections,
handle.index(),
&mut parse_buf_pool,
&mut connection_count,
);
}
}
};
}
drain_tx!();
transport.poll();
for accepted in transport.take_accepted() {
if let Some(ref mut driver) = repl_driver
&& accepted.listen_port == repl_listen_port
{
driver.accept_connection(accepted.peer, accepted.handle, &mut transport);
continue;
}
if crate::connection_limit::connection_cap_reached(
connection_count as u64,
max_connections,
) {
warn!(
peer = %accepted.peer,
"DPDK: connection rejected: max_connections reached"
);
transport.close(accepted.handle);
continue;
}
let conn_id = ConnectionId((thread_id as u64) << 56 | next_connection_id);
next_connection_id += 1;
debug!(
connection_id = conn_id.0,
peer = %accepted.peer,
"DPDK: new connection, starting auth"
);
let nonce: [u8; 32] = rng.random();
let mut challenge_buf = [0u8; 128];
let written = control_codec::encode_transport_response(
&TransportResponse::Challenge { nonce },
&mut challenge_buf,
)
.expect("challenge encodes");
transport.queue_send(accepted.handle, &challenge_buf[..written]);
let accepted_idx = accepted.handle.index();
if accepted_idx + 1 > conn_range_end {
conn_range_end = accepted_idx + 1;
}
connections[accepted_idx] = Some(ConnectionState {
connection_id: conn_id,
addr: accepted.peer,
handle: accepted.handle,
auth: AuthState::WaitingForResponse {
nonce,
accepted_at: Instant::now(),
},
key_hash: 0,
parse_buf: parse_buf_pool
.pop()
.unwrap_or_else(|| Vec::with_capacity(MAX_FRAME_SIZE + 4)),
last_activity: Instant::now(),
});
connection_count += 1;
}
const POLL_EVERY_N_CONNS: usize = 4;
let mut batch_wall_ns: Option<u64> = None;
slow_check_counter = slow_check_counter.wrapping_add(1);
let do_slow_checks = slow_check_counter.is_multiple_of(SLOW_CHECK_INTERVAL);
let mut active_idx: usize = 0;
#[allow(clippy::needless_range_loop)]
for idx in 0..conn_range_end {
if connections[idx].is_none() {
continue;
}
if active_idx > 0 && active_idx.is_multiple_of(POLL_EVERY_N_CONNS) {
drain_tx!();
transport.poll();
if let Some(ref mut driver) = repl_driver {
driver.tick(&mut transport, shutdown);
}
if connections[idx].is_none() {
continue;
}
}
active_idx += 1;
let conn = match connections[idx].as_mut() {
Some(c) => c,
None => continue,
};
let conn_handle = conn.handle;
if do_slow_checks
&& let AuthState::WaitingForResponse { accepted_at, .. }
| AuthState::Rejected { accepted_at } = &conn.auth
&& accepted_at.elapsed() > AUTH_TIMEOUT
{
debug!(
connection_id = conn.connection_id.0,
addr = %conn.addr,
"DPDK: auth timeout, dropping connection"
);
transport.close(conn.handle);
release_slot(
&mut connections,
idx,
&mut parse_buf_pool,
&mut connection_count,
);
continue;
}
if matches!(conn.auth, AuthState::Rejected { .. }) && transport.tx_drained(conn.handle)
{
debug!(
connection_id = conn.connection_id.0,
addr = %conn.addr,
"DPDK: AuthFailed delivered, dropping connection"
);
transport.close(conn.handle);
release_slot(
&mut connections,
idx,
&mut parse_buf_pool,
&mut connection_count,
);
continue;
}
const MAX_PARSE_BUF: usize = 65536;
if conn.parse_buf.len() >= MAX_PARSE_BUF {
debug!(
connection_id = conn.connection_id.0,
buf_len = conn.parse_buf.len(),
"parse buffer exceeded limit, dropping connection"
);
transport.close(conn.handle);
let _ = control_tx.send(ControlEvent::Disconnected {
connection_id: conn.connection_id.0,
});
id_to_handle.remove(&conn.connection_id.0);
release_slot(
&mut connections,
idx,
&mut parse_buf_pool,
&mut connection_count,
);
continue;
}
let n = transport.recv_into_vec(conn.handle, &mut conn.parse_buf);
if n > 0 {
conn.last_activity = Instant::now();
#[cfg(feature = "latency-trace")]
{
work_done_this_iter = true;
}
}
if n == 0 {
if !transport.is_active(conn.handle) || transport.peer_closed(conn.handle) {
debug!(
connection_id = conn.connection_id.0,
addr = %conn.addr,
"DPDK: connection closed"
);
if matches!(conn.auth, AuthState::Authenticated { .. }) {
let _ = control_tx.send(ControlEvent::Disconnected {
connection_id: conn.connection_id.0,
});
}
transport.close(conn.handle);
id_to_handle.remove(&conn.connection_id.0);
release_slot(
&mut connections,
idx,
&mut parse_buf_pool,
&mut connection_count,
);
}
else if do_slow_checks
&& let Some(timeout) = connection_timeout
&& matches!(conn.auth, AuthState::Authenticated { .. })
&& conn.last_activity.elapsed() > timeout
{
debug!(
connection_id = conn.connection_id.0,
addr = %conn.addr,
"DPDK: idle timeout, dropping connection"
);
transport.close(conn.handle);
let _ = control_tx.send(ControlEvent::Disconnected {
connection_id: conn.connection_id.0,
});
id_to_handle.remove(&conn.connection_id.0);
release_slot(
&mut connections,
idx,
&mut parse_buf_pool,
&mut connection_count,
);
}
continue;
}
#[allow(clippy::let_unit_value)] let recv_ts = melin_transport_core::trace::mono_trace_ns();
let was_waiting = matches!(conn.auth, AuthState::WaitingForResponse { .. });
match &conn.auth {
AuthState::WaitingForResponse { .. } => {
process_auth_frame(
conn,
&mut transport,
&authorized_keys,
&control_tx,
&mut id_to_handle,
conn_handle,
);
}
AuthState::Rejected { .. } => conn.parse_buf.clear(),
AuthState::Authenticated { role } => {
use crate::client_frames::{FrameAction, process_client_frames};
let role = *role;
let action = process_client_frames::<A>(
&mut conn.parse_buf,
conn.connection_id.0,
conn.key_hash,
role,
&mut producer,
&*decoder,
&halt,
&mut refusals,
*batch_wall_ns.get_or_insert_with(unix_epoch_nanos),
recv_ts,
#[cfg(feature = "latency-trace")]
&mut publish_rec,
#[cfg(feature = "tick-to-trade")]
&mut ingest_rec,
);
if matches!(action, FrameAction::Disconnect | FrameAction::PipelineFull) {
if matches!(action, FrameAction::PipelineFull) {
debug!(
connection_id = conn.connection_id.0,
"DPDK: pipeline full, dropping connection"
);
}
transport.close(conn.handle);
let _ = control_tx.send(ControlEvent::Disconnected {
connection_id: conn.connection_id.0,
});
id_to_handle.remove(&conn.connection_id.0);
release_slot(
&mut connections,
idx,
&mut parse_buf_pool,
&mut connection_count,
);
continue;
}
}
}
if was_waiting && matches!(conn.auth, AuthState::Authenticated { .. }) {
active_connections.fetch_add(1, Ordering::Relaxed);
}
}
drain_tx!();
transport.poll();
if let Some(ref mut driver) = repl_driver {
driver.tick(&mut transport, shutdown);
}
#[cfg(feature = "latency-trace")]
{
let now = mono_trace_ns();
if work_done_this_iter && !shutdown.load(Ordering::Relaxed) {
poll_iter_rec.record_elapsed(poll_iter_start, now);
}
poll_iter_start = now;
if now.saturating_sub(last_stats_flush_ns) >= stats_flush_interval_ns {
last_stats_flush_ns = now;
poll_iter_rec.flush();
publish_rec.flush();
#[cfg(feature = "tick-to-trade")]
ingest_rec.flush();
}
}
}
}
#[derive(Debug)]
enum AuthVerdict {
Incomplete,
Rejected,
Admitted {
role: ClientRole<RoleId>,
public_key: [u8; 32],
},
}
fn evaluate_auth_frame(
parse_buf: &mut Vec<u8>,
nonce: &[u8; 32],
authorized_keys: &AuthorizedKeys,
connection_id: u64,
) -> AuthVerdict {
let (decoded, consumed) = match split_frame_limited(parse_buf, MAX_AUTH_FRAME) {
Ok(Some(frame)) => (
control_codec::decode_challenge_response(frame.payload),
frame.consumed(),
),
Ok(None) => return AuthVerdict::Incomplete,
Err(too_large) => {
debug!(
connection_id,
frame_len = too_large.declared,
"DPDK: auth frame too large"
);
return AuthVerdict::Rejected;
}
};
let remaining = parse_buf.len() - consumed;
parse_buf.copy_within(consumed.., 0);
parse_buf.truncate(remaining);
let cr = match decoded {
Ok(cr) => cr,
Err(e) => {
debug!(connection_id, error = %e, "DPDK: auth decode error");
return AuthVerdict::Rejected;
}
};
match crate::client_auth::verify_client(authorized_keys, nonce, &cr.public_key, &cr.signature) {
Ok(role) => AuthVerdict::Admitted {
role,
public_key: cr.public_key,
},
Err(e) => {
debug!(connection_id, error = %e, "DPDK: auth failed");
AuthVerdict::Rejected
}
}
}
fn process_auth_frame(
conn: &mut ConnectionState,
transport: &mut DpdkTransport,
authorized_keys: &AuthorizedKeys,
control_tx: &mpsc::Sender<ControlEvent>,
id_to_handle: &mut FxHashMap<u64, SocketHandle>,
handle: SocketHandle,
) {
let (nonce, accepted_at) = match &conn.auth {
AuthState::WaitingForResponse { nonce, accepted_at } => (*nonce, *accepted_at),
_ => unreachable!("process_auth_frame called in wrong state"),
};
let (role, public_key_bytes) = match evaluate_auth_frame(
&mut conn.parse_buf,
&nonce,
authorized_keys,
conn.connection_id.0,
) {
AuthVerdict::Incomplete => return,
AuthVerdict::Rejected => {
send_auth_failed(conn, transport);
conn.parse_buf.clear();
conn.auth = AuthState::Rejected { accepted_at };
return;
}
AuthVerdict::Admitted { role, public_key } => (role, public_key),
};
let mut buf = [0u8; 16];
let written =
control_codec::encode_transport_response(&TransportResponse::ServerReady, &mut buf)
.expect("ServerReady encodes");
transport.queue_send(conn.handle, &buf[..written]);
debug!(
connection_id = conn.connection_id.0,
addr = %conn.addr,
role = authorized_keys.token(KeyRole::Client(role)),
"DPDK: authenticated"
);
conn.key_hash = melin_app::key_hash(&public_key_bytes);
conn.auth = AuthState::Authenticated { role };
id_to_handle.insert(conn.connection_id.0, handle);
let _ = control_tx.send(ControlEvent::Connected {
connection_id: conn.connection_id.0,
});
}
#[inline]
fn release_slot(
connections: &mut [Option<ConnectionState>],
idx: usize,
parse_buf_pool: &mut Vec<Vec<u8>>,
connection_count: &mut usize,
) {
if let Some(mut removed) = connections[idx].take() {
removed.parse_buf.clear();
parse_buf_pool.push(removed.parse_buf);
*connection_count -= 1;
}
}
fn send_auth_failed(conn: &ConnectionState, transport: &mut DpdkTransport) {
let mut buf = [0u8; 16];
if let Ok(written) =
control_codec::encode_transport_response(&TransportResponse::AuthFailed, &mut buf)
{
transport.queue_send(conn.handle, &buf[..written]);
}
}
#[cfg(test)]
mod tests {
use super::*;
use counter_server::CounterEvent;
use melin_app::AppEvent;
use melin_wire_protocol::framing::Frame;
fn split(buf: &[u8]) -> Option<Frame<'_>> {
split_frame_limited(buf, MAX_FRAME_SIZE).expect("frame within MAX_FRAME_SIZE")
}
fn counter_frame(event: CounterEvent) -> Vec<u8> {
let mut body = vec![0u8; event.encoded_size()];
let n = event.encode(&mut body);
let mut frame = (n as u32).to_le_bytes().to_vec();
frame.extend_from_slice(&body[..n]);
frame
}
#[test]
fn wire_round_trip_increment() {
let wire = counter_frame(CounterEvent::Increment { amount: 42 });
let frame = split(&wire).expect("complete frame");
assert_eq!(frame.consumed(), wire.len());
let decoded = CounterEvent::decode(frame.payload).unwrap();
assert!(matches!(decoded, CounterEvent::Increment { amount: 42 }));
}
#[test]
fn wire_round_trip_get_value() {
let wire = counter_frame(CounterEvent::GetValue);
let frame = split(&wire).expect("complete frame");
let decoded = CounterEvent::decode(frame.payload).unwrap();
assert!(matches!(decoded, CounterEvent::GetValue));
}
#[test]
fn incremental_frame_accumulation() {
let wire = counter_frame(CounterEvent::Increment { amount: 7 });
let written = wire.len();
let mut parse_buf = Vec::new();
for (i, &byte) in wire.iter().enumerate() {
parse_buf.push(byte);
let result = split(&parse_buf);
if i < written - 1 {
assert_eq!(result, None, "byte {i}");
} else {
assert!(result.is_some(), "expected a frame at the final byte");
}
}
}
#[test]
fn multiple_frames_in_buffer() {
let frame1 = counter_frame(CounterEvent::Increment { amount: 1 });
let frame2 = counter_frame(CounterEvent::GetValue);
let mut combined = Vec::new();
combined.extend_from_slice(&frame1);
combined.extend_from_slice(&frame2);
let first = split(&combined).expect("first frame");
let decoded = CounterEvent::decode(first.payload).unwrap();
assert!(matches!(decoded, CounterEvent::Increment { amount: 1 }));
let second = split(&combined[first.consumed()..]).expect("second frame");
let decoded = CounterEvent::decode(second.payload).unwrap();
assert!(matches!(decoded, CounterEvent::GetValue));
assert_eq!(first.consumed() + second.consumed(), combined.len());
}
mod auth_verdict {
use super::*;
use base64::Engine;
use ed25519_dalek::{Signer, SigningKey};
use melin_wire_protocol::control::ChallengeResponse;
use crate::test_roles::desk_keys;
const NONCE: [u8; 32] = [0x5A; 32];
fn client() -> SigningKey {
SigningKey::from_bytes(&[0x11; 32])
}
fn keys() -> AuthorizedKeys {
let public = base64::engine::general_purpose::STANDARD
.encode(client().verifying_key().to_bytes());
desk_keys("trader", &public)
}
fn auth_frame(signer: &SigningKey, nonce: &[u8; 32]) -> Vec<u8> {
let response = ChallengeResponse {
signature: signer.sign(nonce).to_bytes(),
public_key: client().verifying_key().to_bytes(),
};
let mut payload = [0u8; 128];
let len = control_codec::encode_challenge_response(&response, &mut payload).unwrap();
let mut frame = (len as u32).to_le_bytes().to_vec();
frame.extend_from_slice(&payload[..len]);
frame
}
fn evaluate(buf: &mut Vec<u8>) -> AuthVerdict {
evaluate_auth_frame(buf, &NONCE, &keys(), 1)
}
#[test]
fn a_valid_response_is_admitted_and_pipelined_bytes_are_kept() {
let mut buf = auth_frame(&client(), &NONCE);
buf.extend_from_slice(b"next");
match evaluate(&mut buf) {
AuthVerdict::Admitted { public_key, .. } => {
assert_eq!(public_key, client().verifying_key().to_bytes());
}
other => panic!("expected Admitted, got {other:?}"),
}
assert_eq!(buf, b"next");
}
#[test]
fn a_partial_frame_is_incomplete_and_left_in_place() {
let frame = auth_frame(&client(), &NONCE);
for cut in 0..frame.len() {
let mut buf = frame[..cut].to_vec();
assert!(
matches!(evaluate(&mut buf), AuthVerdict::Incomplete),
"cut {cut}"
);
assert_eq!(buf, &frame[..cut]);
}
}
#[test]
fn a_signature_by_another_key_is_rejected() {
let impostor = SigningKey::from_bytes(&[0x33; 32]);
let mut buf = auth_frame(&impostor, &NONCE);
assert!(matches!(evaluate(&mut buf), AuthVerdict::Rejected));
}
#[test]
fn a_signature_over_another_nonce_is_rejected() {
let mut buf = auth_frame(&client(), &[0xA5; 32]);
assert!(matches!(evaluate(&mut buf), AuthVerdict::Rejected));
}
#[test]
fn an_undecodable_frame_is_rejected() {
let mut buf = auth_frame(&client(), &NONCE);
buf[4] ^= 0xFF; assert!(matches!(evaluate(&mut buf), AuthVerdict::Rejected));
}
#[test]
fn a_zero_length_frame_is_rejected() {
let mut buf = 0u32.to_le_bytes().to_vec();
assert!(matches!(evaluate(&mut buf), AuthVerdict::Rejected));
}
#[test]
fn an_oversized_frame_is_rejected_on_its_prefix() {
let mut buf = ((MAX_AUTH_FRAME + 1) as u32).to_le_bytes().to_vec();
assert!(matches!(evaluate(&mut buf), AuthVerdict::Rejected));
let mut at_cap = (MAX_AUTH_FRAME as u32).to_le_bytes().to_vec();
assert!(matches!(evaluate(&mut at_cap), AuthVerdict::Incomplete));
}
}
}