use std::sync::atomic::Ordering;
use clap::{Args, Subcommand};
use net::adapter::net::traversal::blind_relay::{BlindRelay, RelayConfig};
use serde_json::json;
use crate::error::{generic, CliError};
use crate::prelude::{emit_stream_row, OutputFormat};
#[derive(Subcommand, Debug)]
pub enum RelayCommand {
Serve(ServeArgs),
}
#[derive(Args, Debug)]
pub struct ServeArgs {
#[arg(long, value_name = "ADDR")]
pub bind: String,
#[arg(long, default_value_t = 100_000)]
pub max_registrations: usize,
#[arg(long, default_value_t = 8_192)]
pub max_channels_per_registration: usize,
}
pub async fn run(cmd: RelayCommand, output: Option<OutputFormat>) -> Result<(), CliError> {
match cmd {
RelayCommand::Serve(args) => serve(args, output).await,
}
}
async fn serve(args: ServeArgs, output: Option<OutputFormat>) -> Result<(), CliError> {
let bind = crate::context::parse_bind_literal(&args.bind)?;
let fmt = OutputFormat::resolve_stream(output);
let config = RelayConfig {
max_registrations: args.max_registrations,
max_channels_per_registration: args.max_channels_per_registration,
..RelayConfig::default()
};
let relay = BlindRelay::bind(bind, config)
.await
.map_err(|e| crate::error::connection_failure(format!("relay bind {bind}: {e}")))?;
let local = relay
.local_addr()
.map_err(|e| generic(format!("relay socket: {e}")))?;
let core = relay.core().clone();
#[cfg(feature = "fixtures")]
if std::env::var_os("NET_MESH_FIXTURE_RELAY_BLOCK_UDP").is_some() {
relay.block_udp_for_test(true);
}
emit_stream_row(
fmt,
&json!({ "event": "ready", "relay": local.to_string() }),
)
.map_err(|e| generic(format!("write readiness: {e}")))?;
tokio::select! {
_ = relay.run() => {}
_ = shutdown_signal() => {}
}
let (registrations, channels) = core.sizes();
let stats = core.stats();
emit_stream_row(
fmt,
&json!({
"event": "stopped",
"relay": local.to_string(),
"registrations": registrations,
"channels": channels,
"forwarded_packets": stats.forwarded_packets.load(Ordering::Relaxed),
"forwarded_bytes": stats.forwarded_bytes.load(Ordering::Relaxed),
"dropped": stats.dropped.load(Ordering::Relaxed),
"refused": stats.refused.load(Ordering::Relaxed),
"registrations_accepted": stats.registrations.load(Ordering::Relaxed),
"splices": stats.splices_opened.load(Ordering::Relaxed),
"splice_bytes": stats.splice_bytes.load(Ordering::Relaxed),
"tunnels": stats.tunnels_opened.load(Ordering::Relaxed),
}),
)
.map_err(|e| generic(format!("write stop event: {e}")))
}
async fn shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{signal, SignalKind};
if let Ok(mut term) = signal(SignalKind::terminate()) {
tokio::select! {
_ = tokio::signal::ctrl_c() => {}
_ = term.recv() => {}
}
return;
}
}
let _ = tokio::signal::ctrl_c().await;
}