Refactor agent logging and report active tcp-tls tunnels
Previous agent logging suffered from noisy repetitive '--- status ---' dumps to stdout every 15 seconds, intermingled with raw println! event lines and lacking structured log levels. Furthermore, the status report only inspected WireguardPlugin tunnels; when tcp-tls took precedence and established a link, the status falsely showed wg-quic as '0/1 tunnels established, waiting for a data link' without mentioning the active tcp-tls link. Key changes: - Adopt compact, clean tracing output: initialize tracing_subscriber with .with_target(false).compact() and default filter 'info,iroh=warn,quinn=warn,rustls=warn,boringtun=warn,mainline=warn' to keep standard agent events at INFO while silencing noisy internals. - Route agent events through structured tracing (log_event): emit INFO for peer connect/disconnect, data link up/down, and network activation; WARN for rejections and plugin errors; DEBUG for dials. - Disable periodic status dump by default (set status_interval default to 0), and re-route periodic ticks to tracing::debug! (log_status_summary). - Add active_peers() to GenericTunnelPlugin and update build_report to include active tcp-tls tunnels in OverlayReport alongside WireGuard peers. - Display enabled protocol priority on agent startup. Validation on Linux: - cargo fmt --all -- --check passed. - cargo clippy --locked --workspace --all-targets -- -D warnings passed. - cargo test --locked --workspace --all-targets passed (347 tests passed).
This commit is contained in:
1 parent
254a25ebd6
commit
41990c9934
2 files changed
+249
-170
No files matched your search
+236
-170
@@ -33,7 +33,12 @@ use tsunagi_wg_quic::{WireguardConfig, WireguardPlugin};
|
|||||||
#[command(name = "tsunagi", version, about, long_about = None)]
|
#[command(name = "tsunagi", version, about, long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// Log filter, for example `info` or `tsunagi=debug`.
|
/// Log filter, for example `info` or `tsunagi=debug`.
|
||||||
#[arg(long, global = true, env = "TSUNAGI_LOG", default_value = "warn")]
|
#[arg(
|
||||||
|
long,
|
||||||
|
global = true,
|
||||||
|
env = "TSUNAGI_LOG",
|
||||||
|
default_value = "info,iroh=warn,quinn=warn,rustls=warn,boringtun=warn,mainline=warn"
|
||||||
|
)]
|
||||||
log: String,
|
log: String,
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
@@ -469,8 +474,8 @@ struct UpArgs {
|
|||||||
#[arg(long, value_name = "PORT", help_heading = "System")]
|
#[arg(long, value_name = "PORT", help_heading = "System")]
|
||||||
dns_port: Option<u16>,
|
dns_port: Option<u16>,
|
||||||
|
|
||||||
/// How often to print a status summary, in seconds. Zero disables it.
|
/// How often to log a periodic status summary in debug mode, in seconds. Zero disables it.
|
||||||
#[arg(long, default_value_t = 15)]
|
#[arg(long, default_value_t = 0)]
|
||||||
status_interval: u64,
|
status_interval: u64,
|
||||||
|
|
||||||
/// Control socket to serve. Derived from the state directory by default.
|
/// Control socket to serve. Derived from the state directory by default.
|
||||||
@@ -693,6 +698,8 @@ fn main() -> std::process::ExitCode {
|
|||||||
|
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_env_filter(tracing_subscriber::EnvFilter::new(&cli.log))
|
.with_env_filter(tracing_subscriber::EnvFilter::new(&cli.log))
|
||||||
|
.with_target(false)
|
||||||
|
.compact()
|
||||||
.with_writer(std::io::stderr)
|
.with_writer(std::io::stderr)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
@@ -1270,6 +1277,7 @@ fn show_protocols() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
struct AgentControl {
|
struct AgentControl {
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
plugin: Option<Arc<WireguardPlugin>>,
|
plugin: Option<Arc<WireguardPlugin>>,
|
||||||
|
tcp_tls: Option<Arc<TcpTlsPlugin>>,
|
||||||
/// The resolver, which this owns so it can be switched while running.
|
/// The resolver, which this owns so it can be switched while running.
|
||||||
dns: Arc<tokio::sync::Mutex<Option<DnsService>>>,
|
dns: Arc<tokio::sync::Mutex<Option<DnsService>>>,
|
||||||
/// Where the setting is remembered, so it survives a restart.
|
/// Where the setting is remembered, so it survives a restart.
|
||||||
@@ -1280,7 +1288,13 @@ impl tsunagi::ipc::ReportSource for AgentControl {
|
|||||||
fn report(&self) -> tsunagi::BoxFuture<'_, tsunagi::ipc::StatusReport> {
|
fn report(&self) -> tsunagi::BoxFuture<'_, tsunagi::ipc::StatusReport> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let dns = self.dns_state().await;
|
let dns = self.dns_state().await;
|
||||||
build_report(&self.agent, self.plugin.as_deref(), dns).await
|
build_report(
|
||||||
|
&self.agent,
|
||||||
|
self.plugin.as_deref(),
|
||||||
|
self.tcp_tls.as_deref(),
|
||||||
|
dns,
|
||||||
|
)
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3460,6 +3474,7 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// was selected to carry traffic over it.
|
// was selected to carry traffic over it.
|
||||||
let mut wireguard = None;
|
let mut wireguard = None;
|
||||||
let mut tcp_tls_transport = None;
|
let mut tcp_tls_transport = None;
|
||||||
|
let mut tcp_tls_plugin = None;
|
||||||
if !wanted.is_empty() {
|
if !wanted.is_empty() {
|
||||||
let tun_factory: Arc<dyn TunFactory> = if args.no_tun {
|
let tun_factory: Arc<dyn TunFactory> = if args.no_tun {
|
||||||
Arc::new(MemoryTunFactory::new())
|
Arc::new(MemoryTunFactory::new())
|
||||||
@@ -3501,13 +3516,14 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let codec = TcpTlsCodec::new(identity.endpoint_id(), Arc::clone(&transport));
|
let codec = TcpTlsCodec::new(identity.endpoint_id(), Arc::clone(&transport));
|
||||||
let plugin = Arc::new(TcpTlsPlugin::new(codec));
|
let plugin = Arc::new(TcpTlsPlugin::new(codec));
|
||||||
config = config
|
config = config
|
||||||
.with_plugin(plugin as Arc<dyn IpPlugin>)
|
.with_plugin(plugin.clone() as Arc<dyn IpPlugin>)
|
||||||
.with_custom_transport(
|
.with_custom_transport(
|
||||||
TCP_TLS_PROTOCOL,
|
TCP_TLS_PROTOCOL,
|
||||||
Arc::clone(&transport)
|
Arc::clone(&transport)
|
||||||
as Arc<dyn tsunagi::dataplane::transport::PacketTransport>,
|
as Arc<dyn tsunagi::dataplane::transport::PacketTransport>,
|
||||||
);
|
);
|
||||||
tcp_tls_transport = Some(transport);
|
tcp_tls_transport = Some(transport);
|
||||||
|
tcp_tls_plugin = Some(plugin);
|
||||||
}
|
}
|
||||||
other => return Err(format!("`{other}` is listed but not built in").into()),
|
other => return Err(format!("`{other}` is listed but not built in").into()),
|
||||||
}
|
}
|
||||||
@@ -3561,9 +3577,15 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let protocol_names: Vec<&str> = wanted.iter().map(|p| p.name).collect();
|
||||||
|
|
||||||
println!("tsunagi is up");
|
println!("tsunagi is up");
|
||||||
println!(" endpoint id {}", agent.endpoint_id());
|
println!(" endpoint id {}", agent.endpoint_id());
|
||||||
println!(" hostname {}", agent.hostname());
|
println!(" hostname {}", agent.hostname());
|
||||||
|
println!(
|
||||||
|
" protocols {} (priority order)",
|
||||||
|
protocol_names.join(", ")
|
||||||
|
);
|
||||||
// What this device belongs to is a separate question from whether its
|
// What this device belongs to is a separate question from whether its
|
||||||
// agent is running, and `tsunagi join` answers it at any time.
|
// agent is running, and `tsunagi join` answers it at any time.
|
||||||
let configured = agent.list_networks().await.unwrap_or_default();
|
let configured = agent.list_networks().await.unwrap_or_default();
|
||||||
@@ -3591,10 +3613,12 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let control = {
|
let control = {
|
||||||
let agent = agent.clone();
|
let agent = agent.clone();
|
||||||
let plugin = wireguard.clone();
|
let plugin = wireguard.clone();
|
||||||
|
let tcp_tls = tcp_tls_plugin.clone();
|
||||||
let dns = Arc::clone(&dns);
|
let dns = Arc::clone(&dns);
|
||||||
let source: Arc<dyn tsunagi::ipc::ReportSource> = Arc::new(AgentControl {
|
let source: Arc<dyn tsunagi::ipc::ReportSource> = Arc::new(AgentControl {
|
||||||
agent,
|
agent,
|
||||||
plugin,
|
plugin,
|
||||||
|
tcp_tls,
|
||||||
dns,
|
dns,
|
||||||
paths: paths.clone(),
|
paths: paths.clone(),
|
||||||
});
|
});
|
||||||
@@ -3613,6 +3637,13 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
println!("\nPress Ctrl-C to stop.\n");
|
println!("\nPress Ctrl-C to stop.\n");
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
endpoint_id = %agent.endpoint_id(),
|
||||||
|
hostname = %agent.hostname(),
|
||||||
|
protocols = ?protocol_names,
|
||||||
|
"Agent started and listening"
|
||||||
|
);
|
||||||
|
|
||||||
let status_every =
|
let status_every =
|
||||||
(args.status_interval > 0).then(|| Duration::from_secs(args.status_interval));
|
(args.status_interval > 0).then(|| Duration::from_secs(args.status_interval));
|
||||||
let mut ticker = status_every.map(tokio::time::interval);
|
let mut ticker = status_every.map(tokio::time::interval);
|
||||||
@@ -3620,13 +3651,13 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
reason = stop_signal() => {
|
reason = stop_signal() => {
|
||||||
println!("\nstopping ({reason})...");
|
tracing::info!("Stopping agent ({reason})...");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
event = events.recv() => match event {
|
event = events.recv() => match event {
|
||||||
Ok(event) => print_event(&event),
|
Ok(event) => log_event(&event),
|
||||||
Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
|
Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
|
||||||
println!(" (missed {skipped} events)");
|
tracing::warn!("Event channel lagged, missed {skipped} events");
|
||||||
}
|
}
|
||||||
Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
|
Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
|
||||||
},
|
},
|
||||||
@@ -3636,7 +3667,7 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
None => std::future::pending::<()>().await,
|
None => std::future::pending::<()>().await,
|
||||||
}
|
}
|
||||||
}, if ticker.is_some() => {
|
}, if ticker.is_some() => {
|
||||||
print_status(&agent, None, wireguard.as_deref()).await;
|
log_status_summary(&agent, None).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3650,7 +3681,7 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
dns.shutdown().await;
|
dns.shutdown().await;
|
||||||
}
|
}
|
||||||
agent.shutdown().await;
|
agent.shutdown().await;
|
||||||
println!("stopped.");
|
tracing::info!("Agent stopped.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3660,6 +3691,7 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
async fn build_report(
|
async fn build_report(
|
||||||
agent: &Agent,
|
agent: &Agent,
|
||||||
wireguard: Option<&WireguardPlugin>,
|
wireguard: Option<&WireguardPlugin>,
|
||||||
|
tcp_tls: Option<&TcpTlsPlugin>,
|
||||||
dns: Option<DnsState>,
|
dns: Option<DnsState>,
|
||||||
) -> tsunagi::ipc::StatusReport {
|
) -> tsunagi::ipc::StatusReport {
|
||||||
use tsunagi::ipc::{
|
use tsunagi::ipc::{
|
||||||
@@ -3677,66 +3709,106 @@ async fn build_report(
|
|||||||
.networks
|
.networks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|network| {
|
.map(|network| {
|
||||||
let overlay = wireguard
|
let wg_view = wireguard.and_then(|plugin| plugin.overview(network.network_id));
|
||||||
.and_then(|plugin| plugin.overview(network.network_id))
|
let tcp_tls_peers = tcp_tls
|
||||||
.map(|view| OverlayReport {
|
.map(|p| p.active_peers(network.network_id))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let overlay = if let Some(view) = wg_view {
|
||||||
|
let mut peers = Vec::new();
|
||||||
|
for peer in &view.peers {
|
||||||
|
let active_tcp_tls_path = tcp_tls_peers
|
||||||
|
.iter()
|
||||||
|
.find(|(id, _)| *id == peer.endpoint_id)
|
||||||
|
.map(|(_, path)| path.clone());
|
||||||
|
|
||||||
|
let (path, handshake) = if let Some(path) = active_tcp_tls_path {
|
||||||
|
(
|
||||||
|
path,
|
||||||
|
peer.tunnel
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| t.health.since_handshake)
|
||||||
|
.map(|s| s.as_secs())
|
||||||
|
.or(Some(0)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
peer.tunnel
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.path.clone())
|
||||||
|
.unwrap_or_else(|| "no data link".into()),
|
||||||
|
peer.tunnel
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| t.health.since_handshake)
|
||||||
|
.map(|s| s.as_secs()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
peers.push(OverlayPeerReport {
|
||||||
|
endpoint_id: peer.endpoint_id.to_string(),
|
||||||
|
public_key: peer.public_key.to_string(),
|
||||||
|
address: peer.overlay_address_v4.map(|addr| addr.to_string()),
|
||||||
|
handshake_secs_ago: handshake,
|
||||||
|
tx_packets: peer.tunnel.as_ref().map_or(0, |t| t.stats.tx_packets),
|
||||||
|
rx_packets: peer.tunnel.as_ref().map_or(0, |t| t.stats.rx_packets),
|
||||||
|
dropped: peer.tunnel.as_ref().map_or(0, |t| {
|
||||||
|
t.stats.dropped_wrong_source + t.stats.dropped_oversize
|
||||||
|
}),
|
||||||
|
protocol_errors: peer
|
||||||
|
.tunnel
|
||||||
|
.as_ref()
|
||||||
|
.map_or(0, |t| t.stats.protocol_errors),
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(OverlayReport {
|
||||||
interface: overlay
|
interface: overlay
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or_else(String::new, |overlay| overlay.interface.clone()),
|
.map_or_else(String::new, |o| o.interface.clone()),
|
||||||
on_host: overlay.as_ref().is_some_and(|overlay| overlay.on_host),
|
on_host: overlay.as_ref().is_some_and(|o| o.on_host),
|
||||||
mtu: overlay.as_ref().map_or(0, |overlay| overlay.mtu),
|
mtu: overlay.as_ref().map_or(0, |o| o.mtu),
|
||||||
address: view.overlay_address_v4.map(|addr| addr.to_string()),
|
address: view.overlay_address_v4.map(|addr| addr.to_string()),
|
||||||
prefix_len: view.ipv4_range.map_or(0, |range| range.prefix_len),
|
prefix_len: view.ipv4_range.map_or(0, |r| r.prefix_len),
|
||||||
peers: view
|
peers,
|
||||||
.peers
|
unroutable_packets: overlay.as_ref().map_or(0, |o| o.counters.unroutable),
|
||||||
.iter()
|
multicast_packets: overlay.as_ref().map_or(0, |o| o.counters.multicast),
|
||||||
.map(|peer| OverlayPeerReport {
|
unroutable_sample: overlay
|
||||||
endpoint_id: peer.endpoint_id.to_string(),
|
|
||||||
public_key: peer.public_key.to_string(),
|
|
||||||
address: peer.overlay_address_v4.map(|addr| addr.to_string()),
|
|
||||||
handshake_secs_ago: peer
|
|
||||||
.tunnel
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|tunnel| tunnel.health.since_handshake)
|
|
||||||
.map(|since| since.as_secs()),
|
|
||||||
tx_packets: peer
|
|
||||||
.tunnel
|
|
||||||
.as_ref()
|
|
||||||
.map_or(0, |tunnel| tunnel.stats.tx_packets),
|
|
||||||
rx_packets: peer
|
|
||||||
.tunnel
|
|
||||||
.as_ref()
|
|
||||||
.map_or(0, |tunnel| tunnel.stats.rx_packets),
|
|
||||||
dropped: peer.tunnel.as_ref().map_or(0, |tunnel| {
|
|
||||||
tunnel.stats.dropped_wrong_source + tunnel.stats.dropped_oversize
|
|
||||||
}),
|
|
||||||
protocol_errors: peer
|
|
||||||
.tunnel
|
|
||||||
.as_ref()
|
|
||||||
.map_or(0, |tunnel| tunnel.stats.protocol_errors),
|
|
||||||
path: peer
|
|
||||||
.tunnel
|
|
||||||
.as_ref()
|
|
||||||
.map(|tunnel| tunnel.path.clone())
|
|
||||||
.unwrap_or_else(|| "no data link".into()),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
// The interface belongs to the agent, so the counters
|
|
||||||
// about it come from there and are the same for every
|
|
||||||
// network sharing it.
|
|
||||||
unroutable_packets: overlay
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |overlay| overlay.counters.unroutable),
|
.and_then(|o| o.counters.unroutable_sample.map(|a| a.to_string())),
|
||||||
multicast_packets: overlay
|
})
|
||||||
|
} else if overlay.is_some() && !tcp_tls_peers.is_empty() {
|
||||||
|
let peers = tcp_tls_peers
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, path)| OverlayPeerReport {
|
||||||
|
endpoint_id: id.to_string(),
|
||||||
|
public_key: String::new(),
|
||||||
|
address: None,
|
||||||
|
handshake_secs_ago: Some(0),
|
||||||
|
tx_packets: 0,
|
||||||
|
rx_packets: 0,
|
||||||
|
dropped: 0,
|
||||||
|
protocol_errors: 0,
|
||||||
|
path,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Some(OverlayReport {
|
||||||
|
interface: overlay
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |overlay| overlay.counters.multicast),
|
.map_or_else(String::new, |o| o.interface.clone()),
|
||||||
unroutable_sample: overlay.as_ref().and_then(|overlay| {
|
on_host: overlay.as_ref().is_some_and(|o| o.on_host),
|
||||||
overlay
|
mtu: overlay.as_ref().map_or(0, |o| o.mtu),
|
||||||
.counters
|
address: None,
|
||||||
.unroutable_sample
|
prefix_len: 0,
|
||||||
.map(|address| address.to_string())
|
peers,
|
||||||
}),
|
unroutable_packets: overlay.as_ref().map_or(0, |o| o.counters.unroutable),
|
||||||
});
|
multicast_packets: overlay.as_ref().map_or(0, |o| o.counters.multicast),
|
||||||
|
unroutable_sample: overlay
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|o| o.counters.unroutable_sample.map(|a| a.to_string())),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
NetworkReport {
|
NetworkReport {
|
||||||
broadcast: network.broadcast,
|
broadcast: network.broadcast,
|
||||||
@@ -3865,60 +3937,120 @@ fn system_tun_factory() -> Result<Arc<dyn TunFactory>, Box<dyn std::error::Error
|
|||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_event(event: &Event) {
|
fn log_event(event: &Event) {
|
||||||
match event {
|
match event {
|
||||||
|
Event::NetworkActivated { network } => {
|
||||||
|
tracing::info!(network = %network.fmt_short(), "Network activated");
|
||||||
|
}
|
||||||
|
Event::NetworkDeactivated { network } => {
|
||||||
|
tracing::info!(network = %network.fmt_short(), "Network deactivated");
|
||||||
|
}
|
||||||
Event::PeerConnected {
|
Event::PeerConnected {
|
||||||
|
network,
|
||||||
peer,
|
peer,
|
||||||
transport,
|
transport,
|
||||||
rtt,
|
rtt,
|
||||||
..
|
..
|
||||||
} => println!(
|
} => {
|
||||||
" + peer {} connected over {transport:?} rtt={rtt:?}",
|
let rtt_str = rtt
|
||||||
peer.fmt_short()
|
.map(|d| format!("{:.1}ms", d.as_secs_f64() * 1000.0))
|
||||||
),
|
.unwrap_or_else(|| "unknown".into());
|
||||||
Event::PeerDisconnected { peer, reason, .. } => {
|
tracing::info!(
|
||||||
println!(" - peer {} gone: {reason}", peer.fmt_short())
|
network = %network.fmt_short(),
|
||||||
|
peer = %peer.fmt_short(),
|
||||||
|
"Peer connected via {transport:?} (rtt: {rtt_str})"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::PeerDisconnected {
|
||||||
|
network,
|
||||||
|
peer,
|
||||||
|
reason,
|
||||||
|
} => {
|
||||||
|
tracing::info!(
|
||||||
|
network = %network.fmt_short(),
|
||||||
|
peer = %peer.fmt_short(),
|
||||||
|
"Peer disconnected: {reason}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Event::DataLinkUp {
|
Event::DataLinkUp {
|
||||||
|
network,
|
||||||
peer,
|
peer,
|
||||||
protocol,
|
protocol,
|
||||||
path,
|
path,
|
||||||
max_datagram,
|
max_datagram,
|
||||||
..
|
} => {
|
||||||
} => println!(
|
tracing::info!(
|
||||||
" + data link to {} for {protocol}: {path}, datagram {max_datagram}",
|
network = %network.fmt_short(),
|
||||||
peer.fmt_short()
|
peer = %peer.fmt_short(),
|
||||||
),
|
"Data link established for {protocol} ({path}, MTU datagram: {max_datagram}B)"
|
||||||
|
);
|
||||||
|
}
|
||||||
Event::DataLinkDown {
|
Event::DataLinkDown {
|
||||||
|
network,
|
||||||
peer,
|
peer,
|
||||||
protocol,
|
protocol,
|
||||||
reason,
|
reason,
|
||||||
..
|
} => {
|
||||||
} => println!(
|
tracing::info!(
|
||||||
" - data link to {} for {protocol}: {reason}",
|
network = %network.fmt_short(),
|
||||||
peer.fmt_short()
|
peer = %peer.fmt_short(),
|
||||||
),
|
"Data link closed for {protocol}: {reason}"
|
||||||
Event::HandshakeRejected { peer, reason, .. } => println!(
|
);
|
||||||
" ! rejected {}: {reason}",
|
}
|
||||||
peer.map(|peer| peer.fmt_short().to_string())
|
Event::DialFailed {
|
||||||
.unwrap_or_else(|| "a caller".into())
|
network,
|
||||||
),
|
peer,
|
||||||
|
reason,
|
||||||
|
} => {
|
||||||
|
tracing::debug!(
|
||||||
|
network = %network.fmt_short(),
|
||||||
|
peer = %peer.fmt_short(),
|
||||||
|
"Outbound dial failed: {reason}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::HandshakeRejected {
|
||||||
|
network,
|
||||||
|
peer,
|
||||||
|
reason,
|
||||||
|
} => {
|
||||||
|
let peer_str = peer
|
||||||
|
.map(|p| p.fmt_short().to_string())
|
||||||
|
.unwrap_or_else(|| "unknown".into());
|
||||||
|
tracing::warn!(
|
||||||
|
network = ?network.map(|n| n.fmt_short().to_string()),
|
||||||
|
peer = %peer_str,
|
||||||
|
"Handshake rejected: {reason}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::ProtocolViolation {
|
||||||
|
network,
|
||||||
|
peer,
|
||||||
|
reason,
|
||||||
|
} => {
|
||||||
|
let peer_str = peer
|
||||||
|
.map(|p| p.fmt_short().to_string())
|
||||||
|
.unwrap_or_else(|| "unknown".into());
|
||||||
|
tracing::warn!(
|
||||||
|
network = ?network.map(|n| n.fmt_short().to_string()),
|
||||||
|
peer = %peer_str,
|
||||||
|
"Protocol violation from {peer_str}: {reason}"
|
||||||
|
);
|
||||||
|
}
|
||||||
Event::PluginError {
|
Event::PluginError {
|
||||||
protocol, reason, ..
|
network,
|
||||||
} => println!(" ! {protocol}: {reason}"),
|
protocol,
|
||||||
Event::CacheReset { reason } => println!(" ! cache was reset: {reason}"),
|
reason,
|
||||||
|
} => {
|
||||||
|
tracing::warn!(network = %network.fmt_short(), %protocol, "Data plane plugin error: {reason}");
|
||||||
|
}
|
||||||
|
Event::CacheReset { reason } => {
|
||||||
|
tracing::warn!("Disposable cache was reset: {reason}");
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn print_status(
|
async fn log_status_summary(agent: &Agent, network: Option<NetworkId>) {
|
||||||
agent: &Agent,
|
|
||||||
network: Option<NetworkId>,
|
|
||||||
wireguard: Option<&WireguardPlugin>,
|
|
||||||
) {
|
|
||||||
// The network this command line named, or — when it named none — the
|
|
||||||
// first one the agent has, since there is no other candidate for "the"
|
|
||||||
// network and `tsunagi status` covers the whole picture anyway.
|
|
||||||
let network = match network {
|
let network = match network {
|
||||||
Some(network) => network,
|
Some(network) => network,
|
||||||
None => match agent.list_networks().await {
|
None => match agent.list_networks().await {
|
||||||
@@ -3929,80 +4061,14 @@ async fn print_status(
|
|||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let Ok(status) = agent.network_status(network).await else {
|
if let Ok(status) = agent.network_status(network).await {
|
||||||
return;
|
tracing::debug!(
|
||||||
};
|
peers = status.peers.len(),
|
||||||
println!("\n--- status ---");
|
dial_failures = status.metrics.dial_failures,
|
||||||
println!(
|
handshake_failures = status.metrics.handshake_failures,
|
||||||
"control: {} peer(s), {} dial failure(s), {} handshake failure(s)",
|
"Network status tick"
|
||||||
status.peers.len(),
|
|
||||||
status.metrics.dial_failures,
|
|
||||||
status.metrics.handshake_failures
|
|
||||||
);
|
|
||||||
for peer in &status.peers {
|
|
||||||
println!(
|
|
||||||
" {} {} {:?} rtt={:?}",
|
|
||||||
peer.endpoint_id.fmt_short(),
|
|
||||||
peer.hostname.as_deref().unwrap_or("?"),
|
|
||||||
peer.transport,
|
|
||||||
peer.rtt
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(plugin) = wireguard
|
|
||||||
&& let Some(view) = plugin.overview(network)
|
|
||||||
{
|
|
||||||
println!(
|
|
||||||
"{}: {} on {}/{} mtu {}, {}/{} tunnel(s) established",
|
|
||||||
plugin.protocol_id(),
|
|
||||||
agent
|
|
||||||
.overlay()
|
|
||||||
.map_or_else(|| "no interface".to_string(), |overlay| overlay.interface),
|
|
||||||
view.overlay_address_v4
|
|
||||||
.map_or_else(|| "no address yet".to_string(), |addr| addr.to_string()),
|
|
||||||
view.ipv4_range.map_or(0, |range| range.prefix_len),
|
|
||||||
view.mtu,
|
|
||||||
view.established_peers(),
|
|
||||||
view.peers.len()
|
|
||||||
);
|
|
||||||
for peer in &view.peers {
|
|
||||||
match &peer.tunnel {
|
|
||||||
Some(tunnel) => println!(
|
|
||||||
" {} {} {} tx={} rx={} dropped={} path={}",
|
|
||||||
peer.public_key.fmt_short(),
|
|
||||||
peer.overlay_address_v4
|
|
||||||
.map_or_else(|| "no address".to_string(), |addr| addr.to_string()),
|
|
||||||
match tunnel.health.since_handshake {
|
|
||||||
Some(since) => format!("handshake {}s ago", since.as_secs()),
|
|
||||||
None => "NOT HANDSHAKEN".to_string(),
|
|
||||||
},
|
|
||||||
tunnel.stats.tx_packets,
|
|
||||||
tunnel.stats.rx_packets,
|
|
||||||
tunnel.stats.dropped_wrong_source + tunnel.stats.dropped_oversize,
|
|
||||||
tunnel.path
|
|
||||||
),
|
|
||||||
None => println!(
|
|
||||||
" {} {} waiting for a data link",
|
|
||||||
peer.public_key.fmt_short(),
|
|
||||||
peer.overlay_address_v4
|
|
||||||
.map_or_else(|| "no address".to_string(), |addr| addr.to_string())
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(overlay) = agent.overlay()
|
|
||||||
&& overlay.counters.unroutable > 0
|
|
||||||
{
|
|
||||||
println!(
|
|
||||||
" {} packet(s) for unknown addresses{}",
|
|
||||||
overlay.counters.unroutable,
|
|
||||||
match overlay.counters.unroutable_sample {
|
|
||||||
Some(sample) => format!(" (for example {sample})"),
|
|
||||||
None => String::new(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -101,6 +101,19 @@ impl<C: TunnelCodec> GenericTunnelPlugin<C> {
|
|||||||
pub fn codec(&self) -> &C {
|
pub fn codec(&self) -> &C {
|
||||||
&self.codec
|
&self.codec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the active peers with established data links for a network.
|
||||||
|
pub fn active_peers(&self, network: NetworkId) -> Vec<(EndpointId, String)> {
|
||||||
|
let guard = match self.links.read() {
|
||||||
|
Ok(g) => g,
|
||||||
|
Err(p) => p.into_inner(),
|
||||||
|
};
|
||||||
|
guard
|
||||||
|
.iter()
|
||||||
|
.filter(|((net, _), _)| *net == network)
|
||||||
|
.map(|((_, peer), link)| (*peer, link.path_description()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: TunnelCodec> std::fmt::Debug for GenericTunnelPlugin<C> {
|
impl<C: TunnelCodec> std::fmt::Debug for GenericTunnelPlugin<C> {
|
||||||
|
|||||||
Reference in new issue
Block a user