use std::fmt;
use std::net::SocketAddr;
#[cfg(unix)]
use std::path::Path;
use std::sync::Arc;
use std::time::Instant;
use crate::addr::{PrefixKey, PrefixPolicy};
use crate::id::{ConnectionId, ListenerId, WorkerId};
use crate::proxy::ProxyFacts;
use crate::tls::TlsInfo;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct Lane(pub u8);
impl Lane {
pub const MOST_IMPORTANT: Lane = Lane(0);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct GateTag(pub u64);
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Transport {
Tcp,
Udp,
Unix,
Pipe,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Origin {
Accepted,
Dialed,
Adopted,
Inherited,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct UnixPath {
bytes: [u8; 108],
len: u8,
}
impl UnixPath {
pub const MAX: usize = 108;
#[must_use]
pub fn new(bytes: &[u8]) -> Option<UnixPath> {
if bytes.len() > Self::MAX {
return None;
}
let mut out = [0u8; 108];
out[..bytes.len()].copy_from_slice(bytes);
Some(UnixPath {
bytes: out,
len: u8::try_from(bytes.len()).ok()?,
})
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..usize::from(self.len)]
}
#[cfg(unix)]
#[must_use]
pub fn as_path(&self) -> &Path {
use std::os::unix::ffi::OsStrExt;
Path::new(std::ffi::OsStr::from_bytes(self.as_bytes()))
}
}
impl std::fmt::Debug for UnixPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"UnixPath({:?})",
String::from_utf8_lossy(self.as_bytes())
)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PipeName {
units: [u16; 256],
len: u16,
}
impl PipeName {
pub const MAX: usize = 256;
#[must_use]
pub fn new(name: &str) -> Option<PipeName> {
let mut units = [0u16; 256];
let mut len = 0usize;
for u in name.encode_utf16() {
if len == Self::MAX {
return None;
}
units[len] = u;
len += 1;
}
Some(PipeName {
units,
len: u16::try_from(len).ok()?,
})
}
#[must_use]
pub fn as_wide(&self) -> &[u16] {
&self.units[..usize::from(self.len)]
}
#[must_use]
pub fn to_string_lossy(&self) -> String {
String::from_utf16_lossy(self.as_wide())
}
}
impl std::fmt::Debug for PipeName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "PipeName({:?})", self.to_string_lossy())
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Endpoint {
Ip(SocketAddr),
Unix(Arc<UnixPath>),
Pipe(Arc<PipeName>),
Unnamed,
}
impl fmt::Display for Endpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Endpoint::Ip(a) => write!(f, "{a}"),
Endpoint::Unix(p) => write!(f, "{}", String::from_utf8_lossy(p.as_bytes())),
Endpoint::Pipe(p) => {
for c in char::decode_utf16(p.as_wide().iter().copied()) {
write!(f, "{}", c.unwrap_or(char::REPLACEMENT_CHARACTER))?;
}
Ok(())
}
Endpoint::Unnamed => f.write_str("unnamed"),
}
}
}
impl Endpoint {
#[must_use]
pub fn socket_addr(&self) -> Option<SocketAddr> {
match self {
Endpoint::Ip(a) => Some(*a),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PeerCredentials {
pub pid: Option<u32>,
pub uid: Option<u32>,
pub gid: Option<u32>,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct ConnectionInfo {
pub id: ConnectionId,
pub listener: Option<ListenerId>,
pub worker: WorkerId,
pub transport: Transport,
pub origin: Origin,
pub local: Endpoint,
pub peer: Endpoint,
pub real_peer: Option<SocketAddr>,
pub proxy: Option<Arc<ProxyFacts>>,
pub peer_credentials: Option<PeerCredentials>,
pub tls: Option<Arc<TlsInfo>>,
pub lane: Lane,
pub gate: GateTag,
pub accepted_at: Instant,
}
impl ConnectionInfo {
#[inline]
#[must_use]
pub fn client_addr(&self) -> Option<SocketAddr> {
self.real_peer.or_else(|| self.peer.socket_addr())
}
#[must_use]
pub fn client_key(&self, policy: PrefixPolicy) -> Option<PrefixKey> {
self.client_addr().map(|a| PrefixKey::of(a.ip(), policy))
}
#[must_use]
pub fn socket_peer(&self) -> Option<SocketAddr> {
self.peer.socket_addr()
}
#[must_use]
pub fn for_testing(peer: SocketAddr) -> ConnectionInfo {
let local = if peer.is_ipv4() {
SocketAddr::from(([127, 0, 0, 1], 0))
} else {
SocketAddr::from(([0u16, 0, 0, 0, 0, 0, 0, 1], 0))
};
ConnectionInfo::new_tcp(
crate::id::issue_single(),
None,
WorkerId(0),
Origin::Accepted,
local,
crate::addr::normalize_socket(peer),
Instant::now(),
)
}
#[cfg(feature = "local")]
pub(crate) fn new_unix(
id: ConnectionId,
listener: Option<ListenerId>,
origin: Origin,
local: Endpoint,
peer: Endpoint,
credentials: Option<PeerCredentials>,
at: Instant,
) -> ConnectionInfo {
ConnectionInfo {
id,
listener,
worker: WorkerId(0),
transport: Transport::Unix,
origin,
local,
peer,
real_peer: None,
proxy: None,
peer_credentials: credentials,
tls: None,
lane: Lane::MOST_IMPORTANT,
gate: GateTag(0),
accepted_at: at,
}
}
pub(crate) fn new_tcp(
id: ConnectionId,
listener: Option<ListenerId>,
worker: WorkerId,
origin: Origin,
local: SocketAddr,
peer: SocketAddr,
at: Instant,
) -> ConnectionInfo {
ConnectionInfo {
id,
listener,
worker,
transport: Transport::Tcp,
origin,
local: Endpoint::Ip(local),
peer: Endpoint::Ip(peer),
real_peer: None,
proxy: None,
peer_credentials: None,
tls: None,
lane: Lane::MOST_IMPORTANT,
gate: GateTag(0),
accepted_at: at,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_addr_prefers_real_peer() {
let mut info = ConnectionInfo::for_testing("10.0.0.1:1".parse().unwrap());
assert_eq!(info.client_addr().unwrap().ip().to_string(), "10.0.0.1");
info.real_peer = Some("203.0.113.5:9".parse().unwrap());
assert_eq!(info.client_addr().unwrap().ip().to_string(), "203.0.113.5");
assert_eq!(info.socket_peer().unwrap().ip().to_string(), "10.0.0.1");
}
#[test]
fn test_for_testing_normalizes_mapped_peer() {
let info = ConnectionInfo::for_testing("[::ffff:1.2.3.4]:80".parse().unwrap());
assert_eq!(info.socket_peer().unwrap().to_string(), "1.2.3.4:80");
}
#[test]
fn test_unix_path_bounds() {
assert!(UnixPath::new(&[b'a'; 108]).is_some());
assert!(UnixPath::new(&[b'a'; 109]).is_none());
}
#[test]
fn test_pipe_name_bounds() {
assert!(PipeName::new(&"a".repeat(256)).is_some());
assert!(PipeName::new(&"a".repeat(257)).is_none());
}
#[test]
fn test_connection_info_size_fits_idle_budget() {
assert!(
std::mem::size_of::<ConnectionInfo>() <= 256,
"{}",
std::mem::size_of::<ConnectionInfo>()
);
}
}