use crate::error::{NetworkError, NetworkResult};
use crate::identity::MachineId;
pub const STREAM_ACCEPTOR_CAPACITY: usize = 64;
pub(crate) struct StreamAccept {
tx: tokio::sync::mpsc::Sender<PeerStream>,
rx: std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<PeerStream>>>,
acceptors:
std::sync::Mutex<std::collections::HashMap<u8, tokio::sync::mpsc::Sender<PeerStream>>>,
started: std::sync::atomic::AtomicBool,
}
impl StreamAccept {
pub(crate) fn new(capacity: usize) -> Self {
let (tx, rx) = tokio::sync::mpsc::channel(capacity);
Self {
tx,
rx: std::sync::Arc::new(tokio::sync::Mutex::new(rx)),
acceptors: std::sync::Mutex::new(std::collections::HashMap::new()),
started: std::sync::atomic::AtomicBool::new(false),
}
}
#[cfg(test)]
pub(crate) fn sender(&self) -> &tokio::sync::mpsc::Sender<PeerStream> {
&self.tx
}
pub(crate) fn receiver(
&self,
) -> &std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<PeerStream>>> {
&self.rx
}
pub(crate) fn sender_for(
&self,
protocol: StreamProtocol,
) -> tokio::sync::mpsc::Sender<PeerStream> {
let acceptors = self
.acceptors
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
acceptors
.get(&protocol.as_u8())
.cloned()
.unwrap_or_else(|| self.tx.clone())
}
pub(crate) fn register(
self: &std::sync::Arc<Self>,
protocol: StreamProtocol,
) -> NetworkResult<StreamAcceptor> {
let (tx, rx) = tokio::sync::mpsc::channel(STREAM_ACCEPTOR_CAPACITY);
let mut acceptors = self
.acceptors
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if acceptors.contains_key(&protocol.as_u8()) {
return Err(NetworkError::StreamAcceptorConflict {
protocol_byte: protocol.as_u8(),
});
}
acceptors.insert(protocol.as_u8(), tx.clone());
Ok(StreamAcceptor {
protocol,
rx,
tx,
registry: std::sync::Arc::clone(self),
})
}
fn deregister(&self, protocol: StreamProtocol, tx: &tokio::sync::mpsc::Sender<PeerStream>) {
let mut acceptors = self
.acceptors
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if acceptors
.get(&protocol.as_u8())
.is_some_and(|live| live.same_channel(tx))
{
acceptors.remove(&protocol.as_u8());
}
}
pub(crate) fn start_once(&self) -> bool {
!self.started.swap(true, std::sync::atomic::Ordering::AcqRel)
}
}
pub struct StreamAcceptor {
protocol: StreamProtocol,
rx: tokio::sync::mpsc::Receiver<PeerStream>,
tx: tokio::sync::mpsc::Sender<PeerStream>,
registry: std::sync::Arc<StreamAccept>,
}
impl StreamAcceptor {
#[must_use]
pub fn protocol(&self) -> StreamProtocol {
self.protocol
}
pub async fn next(&mut self) -> Option<PeerStream> {
self.rx.recv().await
}
#[must_use]
pub fn queued(&self) -> usize {
self.rx.len()
}
#[must_use]
pub fn try_next(&mut self) -> Option<PeerStream> {
self.rx.try_recv().ok()
}
}
impl Drop for StreamAcceptor {
fn drop(&mut self) {
self.registry.deregister(self.protocol, &self.tx);
}
}
pub(crate) fn stream_gate(
agent_id: &crate::identity::AgentId,
trust_decision: Option<crate::trust::TrustDecision>,
revoked_agent: bool,
revoked_machine: bool,
expired: bool,
) -> NetworkResult<()> {
use crate::trust::TrustDecision;
if revoked_agent || revoked_machine {
return Err(NetworkError::PeerRevoked {
agent_id: agent_id.0,
});
}
if expired {
return Err(NetworkError::PeerNotVerified {
agent_id: agent_id.0,
});
}
if trust_decision != Some(TrustDecision::Accept) {
return Err(NetworkError::PeerTrustRejected {
agent_id: agent_id.0,
});
}
Ok(())
}
pub(crate) fn stream_acl_gate(
policy: &crate::connect::ConnectPolicy,
agents: &[crate::identity::AgentId],
machine_id: &MachineId,
) -> NetworkResult<()> {
let crate::connect::ConnectPolicy::Enabled(acl) = policy else {
return Ok(());
};
for agent_id in agents {
if acl.entry_for(agent_id, machine_id).is_none() {
return Err(NetworkError::PeerNotInConnectAcl {
agent_id: agent_id.0,
});
}
}
Ok(())
}
pub const PREFIX_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamProtocol {
ForwardV1 = 0x01,
SocksV1 = 0x02,
ForwardV2 = 0x03,
}
impl StreamProtocol {
#[must_use]
pub fn from_u8(byte: u8) -> Option<Self> {
match byte {
0x01 => Some(Self::ForwardV1),
0x02 => Some(Self::SocksV1),
0x03 => Some(Self::ForwardV2),
_ => None,
}
}
#[must_use]
pub const fn as_u8(self) -> u8 {
self as u8
}
}
pub struct PeerStream {
agents: Vec<crate::identity::AgentId>,
peer: MachineId,
protocol: StreamProtocol,
send: ant_quic::HighLevelSendStream,
recv: ant_quic::HighLevelRecvStream,
}
impl PeerStream {
pub(crate) fn new(
agents: Vec<crate::identity::AgentId>,
peer: MachineId,
protocol: StreamProtocol,
send: ant_quic::HighLevelSendStream,
recv: ant_quic::HighLevelRecvStream,
) -> Self {
Self {
agents,
peer,
protocol,
send,
recv,
}
}
#[must_use]
pub fn agent(&self) -> crate::identity::AgentId {
self.agents[0]
}
#[must_use]
pub fn peer_agents(&self) -> &[crate::identity::AgentId] {
&self.agents
}
#[must_use]
pub fn peer(&self) -> MachineId {
self.peer
}
#[must_use]
pub fn protocol(&self) -> StreamProtocol {
self.protocol
}
pub fn send_mut(&mut self) -> &mut ant_quic::HighLevelSendStream {
&mut self.send
}
pub fn recv_mut(&mut self) -> &mut ant_quic::HighLevelRecvStream {
&mut self.recv
}
pub fn into_split(self) -> (ant_quic::HighLevelSendStream, ant_quic::HighLevelRecvStream) {
(self.send, self.recv)
}
}
pub(crate) async fn write_protocol_prefix(
send: &mut ant_quic::HighLevelSendStream,
protocol: StreamProtocol,
) -> NetworkResult<()> {
send.write_all(&[protocol.as_u8()])
.await
.map_err(|e| NetworkError::StreamError(format!("write protocol prefix: {e}")))
}
pub(crate) async fn read_protocol_prefix(
recv: &mut ant_quic::HighLevelRecvStream,
) -> NetworkResult<StreamProtocol> {
let mut buf = [0u8; 1];
recv.read_exact(&mut buf)
.await
.map_err(|e| NetworkError::StreamError(format!("read protocol prefix: {e}")))?;
StreamProtocol::from_u8(buf[0]).ok_or(NetworkError::StreamProtocolUnknown {
protocol_byte: buf[0],
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn protocol_prefix_round_trips() {
for p in [
StreamProtocol::ForwardV1,
StreamProtocol::SocksV1,
StreamProtocol::ForwardV2,
] {
assert_eq!(StreamProtocol::from_u8(p.as_u8()), Some(p));
}
}
#[test]
fn reserved_and_unassigned_bytes_are_unknown() {
for byte in 0x00u8..=0xFF {
let parsed = StreamProtocol::from_u8(byte);
match byte {
0x01..=0x03 => {
assert!(parsed.is_some(), "byte {byte:#x} should parse")
}
_ => assert_eq!(parsed, None, "byte {byte:#x} must be unknown"),
}
}
}
#[test]
fn stream_gate_matrix() {
use crate::error::NetworkError;
use crate::identity::AgentId;
use crate::trust::TrustDecision;
let agent = AgentId([9u8; 32]);
let accept = Some(TrustDecision::Accept);
let accept_with_flag = Some(TrustDecision::AcceptWithFlag);
let reject = Some(TrustDecision::RejectBlocked);
assert!(stream_gate(&agent, accept, false, false, false).is_ok());
assert!(matches!(
stream_gate(&agent, accept, true, false, false),
Err(NetworkError::PeerRevoked { agent_id }) if agent_id == agent.0
));
assert!(matches!(
stream_gate(&agent, accept, false, true, false),
Err(NetworkError::PeerRevoked { .. })
));
assert!(matches!(
stream_gate(&agent, reject, true, false, false),
Err(NetworkError::PeerRevoked { .. })
));
for decision in [accept_with_flag, reject, None] {
assert!(
matches!(
stream_gate(&agent, decision, false, false, false),
Err(NetworkError::PeerTrustRejected { agent_id }) if agent_id == agent.0
),
"decision {decision:?} must deny (only plain Accept passes)"
);
}
}
#[test]
fn stream_gate_rejects_expired_cert() {
use crate::error::NetworkError;
use crate::identity::AgentId;
use crate::trust::TrustDecision;
let agent = AgentId([9u8; 32]);
let accept = Some(TrustDecision::Accept);
assert!(matches!(
stream_gate(&agent, accept, false, false, true),
Err(NetworkError::PeerNotVerified { agent_id }) if agent_id == agent.0
));
assert!(stream_gate(&agent, accept, false, false, false).is_ok());
assert!(matches!(
stream_gate(&agent, accept, true, false, true),
Err(NetworkError::PeerRevoked { agent_id }) if agent_id == agent.0
));
}
fn enabled_policy_listing(
pairs: &[(crate::identity::AgentId, MachineId)],
) -> crate::connect::ConnectPolicy {
let allow = pairs
.iter()
.map(|(agent_id, machine_id)| crate::connect::ConnectAllowEntry {
description: None,
agent_id: *agent_id,
machine_id: *machine_id,
targets: vec!["127.0.0.1:22".parse().expect("loopback literal")],
})
.collect();
crate::connect::ConnectPolicy::Enabled(crate::connect::ConnectAcl {
loaded_from: std::path::Path::new("/test").to_path_buf(),
loaded_at_unix_ms: 0,
allow,
})
}
#[test]
fn stream_acl_gate_matrix() {
use crate::error::NetworkError;
use crate::identity::AgentId;
let machine = MachineId([7u8; 32]);
let listed = AgentId([1u8; 32]);
let also_listed = AgentId([2u8; 32]);
let unlisted = AgentId([3u8; 32]);
let disabled = crate::connect::ConnectPolicy::default();
assert!(stream_acl_gate(&disabled, &[unlisted], &machine).is_ok());
let policy = enabled_policy_listing(&[(listed, machine), (also_listed, machine)]);
assert!(stream_acl_gate(&policy, &[listed], &machine).is_ok());
assert!(stream_acl_gate(&policy, &[listed, also_listed], &machine).is_ok());
assert!(matches!(
stream_acl_gate(&policy, &[unlisted], &machine),
Err(NetworkError::PeerNotInConnectAcl { agent_id }) if agent_id == unlisted.0
));
assert!(matches!(
stream_acl_gate(&policy, &[listed, unlisted], &machine),
Err(NetworkError::PeerNotInConnectAcl { agent_id }) if agent_id == unlisted.0
));
let other_machine = MachineId([8u8; 32]);
assert!(matches!(
stream_acl_gate(&policy, &[listed], &other_machine),
Err(NetworkError::PeerNotInConnectAcl { agent_id }) if agent_id == listed.0
));
}
#[test]
fn acceptor_registration_lifecycle() {
let accept = std::sync::Arc::new(StreamAccept::new(8));
let first = accept.register(StreamProtocol::SocksV1).expect("register");
assert_eq!(first.tx.capacity(), STREAM_ACCEPTOR_CAPACITY);
assert_eq!(first.protocol(), StreamProtocol::SocksV1);
let dup = accept.register(StreamProtocol::SocksV1);
assert!(matches!(
dup,
Err(NetworkError::StreamAcceptorConflict {
protocol_byte: 0x02
})
));
assert!(accept
.sender_for(StreamProtocol::SocksV1)
.same_channel(&first.tx));
assert!(accept
.sender_for(StreamProtocol::ForwardV1)
.same_channel(accept.sender()));
let stale_tx = first.tx.clone();
drop(first);
let second = accept
.register(StreamProtocol::SocksV1)
.expect("re-register after drop");
assert!(accept
.sender_for(StreamProtocol::SocksV1)
.same_channel(&second.tx));
accept.deregister(StreamProtocol::SocksV1, &stale_tx);
assert!(accept
.sender_for(StreamProtocol::SocksV1)
.same_channel(&second.tx));
drop(second);
assert!(accept
.sender_for(StreamProtocol::SocksV1)
.same_channel(accept.sender()));
}
}