pub mod tailscale;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::sync::broadcast;
pub trait NetworkProvider: Send + Sync {
fn start(&mut self) -> impl std::future::Future<Output = Result<(), NetworkError>> + Send;
fn stop(&mut self) -> impl std::future::Future<Output = Result<(), NetworkError>> + Send;
fn local_identity(&self) -> NodeIdentity;
fn local_addr(&self) -> PeerAddr;
fn peer_events(&self) -> broadcast::Receiver<NetworkPeerEvent>;
fn peers(&self) -> impl std::future::Future<Output = Vec<NetworkPeer>> + Send;
fn dial_tcp(&self, addr: &str, port: u16) -> impl std::future::Future<Output = Result<TcpStream, NetworkError>> + Send;
fn listen_tcp(
&self,
port: u16,
) -> impl std::future::Future<Output = Result<NetworkTcpListener, NetworkError>> + Send;
fn unlisten_tcp(&self, port: u16) -> impl std::future::Future<Output = Result<(), NetworkError>> + Send;
fn bind_udp(&self, port: u16) -> impl std::future::Future<Output = Result<NetworkUdpSocket, NetworkError>> + Send;
fn ping(&self, addr: &str) -> impl std::future::Future<Output = Result<PingResult, NetworkError>> + Send;
fn health(&self) -> impl std::future::Future<Output = HealthInfo> + Send;
}
#[derive(Debug, Clone)]
pub struct NetworkPeer {
pub id: String,
pub hostname: String,
pub ip: IpAddr,
pub online: bool,
pub cur_addr: Option<String>,
pub relay: Option<String>,
pub os: Option<String>,
pub last_seen: Option<String>,
pub key_expiry: Option<String>,
pub dns_name: Option<String>,
}
#[derive(Debug, Clone)]
pub enum NetworkPeerEvent {
Joined(NetworkPeer),
Left(String),
Updated(NetworkPeer),
AuthRequired {
url: String,
},
}
#[derive(Debug, Clone, Default)]
pub struct PeerAddr {
pub ip: Option<IpAddr>,
pub hostname: String,
pub dns_name: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct NodeIdentity {
pub id: String,
pub hostname: String,
pub name: String,
pub dns_name: Option<String>,
pub ip: Option<IpAddr>,
}
#[derive(Debug, Clone)]
pub struct PingResult {
pub latency: Duration,
pub connection: String,
pub peer_addr: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct HealthInfo {
pub state: String,
pub key_expiry: Option<String>,
pub warnings: Vec<String>,
pub healthy: bool,
}
pub struct NetworkTcpListener {
pub port: u16,
pub incoming: tokio::sync::mpsc::Receiver<IncomingConnection>,
}
#[derive(Debug)]
pub struct IncomingConnection {
pub stream: TcpStream,
pub remote_addr: String,
pub remote_identity: String,
pub port: u16,
}
pub struct NetworkUdpSocket {
inner: tokio::net::UdpSocket,
tsnet_port: u16,
}
const UDP_ADDR_HEADER_SIZE: usize = 6;
impl NetworkUdpSocket {
pub(crate) fn new(inner: tokio::net::UdpSocket, tsnet_port: u16) -> Self {
Self { inner, tsnet_port }
}
pub async fn send_to(&self, data: &[u8], addr: SocketAddr) -> Result<usize, NetworkError> {
let ip = match addr.ip() {
IpAddr::V4(v4) => v4,
IpAddr::V6(_) => {
return Err(NetworkError::Internal(
"NetworkUdpSocket: IPv6 not supported in relay framing".into(),
));
}
};
let port = addr.port();
let mut framed = Vec::with_capacity(UDP_ADDR_HEADER_SIZE + data.len());
framed.extend_from_slice(&ip.octets());
framed.extend_from_slice(&port.to_be_bytes());
framed.extend_from_slice(data);
tracing::debug!(
target_addr = %addr,
payload_len = data.len(),
framed_len = framed.len(),
relay_addr = ?self.inner.peer_addr().ok(),
"NetworkUdpSocket: sending framed datagram to relay"
);
let n = self
.inner
.send(&framed)
.await
.map_err(NetworkError::Io)?;
tracing::debug!(
bytes_sent = n,
"NetworkUdpSocket: framed datagram sent to relay"
);
Ok(n.saturating_sub(UDP_ADDR_HEADER_SIZE))
}
pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr), NetworkError> {
tracing::debug!("NetworkUdpSocket: waiting for inbound datagram from relay...");
let mut tmp = vec![0u8; UDP_ADDR_HEADER_SIZE + buf.len()];
let n = self
.inner
.recv(&mut tmp)
.await
.map_err(NetworkError::Io)?;
if n < UDP_ADDR_HEADER_SIZE {
return Err(NetworkError::Internal(
"NetworkUdpSocket: received packet too short for address header".into(),
));
}
let ip = Ipv4Addr::new(tmp[0], tmp[1], tmp[2], tmp[3]);
let port = u16::from_be_bytes([tmp[4], tmp[5]]);
let addr = SocketAddr::new(IpAddr::V4(ip), port);
let payload_len = n - UDP_ADDR_HEADER_SIZE;
buf[..payload_len].copy_from_slice(&tmp[UDP_ADDR_HEADER_SIZE..n]);
tracing::debug!(
raw_bytes = n,
payload_len = payload_len,
sender_addr = %addr,
"NetworkUdpSocket: received inbound datagram from relay"
);
Ok((payload_len, addr))
}
pub fn local_addr(&self) -> Result<SocketAddr, NetworkError> {
self.inner.local_addr().map_err(NetworkError::Io)
}
pub fn tsnet_port(&self) -> u16 {
self.tsnet_port
}
pub fn inner(&self) -> &tokio::net::UdpSocket {
&self.inner
}
}
impl std::fmt::Debug for NetworkUdpSocket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NetworkUdpSocket")
.field("tsnet_port", &self.tsnet_port)
.field("local_addr", &self.inner.local_addr().ok())
.finish()
}
}
#[derive(Debug, thiserror::Error)]
pub enum NetworkError {
#[error("network provider not running")]
NotRunning,
#[error("network provider already running")]
AlreadyRunning,
#[error("start failed: {0}")]
StartFailed(String),
#[error("stop failed: {0}")]
StopFailed(String),
#[error("authentication required: {url}")]
AuthRequired { url: String },
#[error("dial failed: {0}")]
DialFailed(String),
#[error("dial timed out after {0:?}")]
DialTimeout(Duration),
#[error("listen failed: {0}")]
ListenFailed(String),
#[error("ping failed: {0}")]
PingFailed(String),
#[error("sidecar error: {0}")]
SidecarError(String),
#[error("bridge error: {0}")]
BridgeError(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("serialization error: {0}")]
Serialize(#[from] serde_json::Error),
#[error("internal error: {0}")]
Internal(String),
}