Filter overlay interface from candidates and align status member columns

Peers previously advertised overlay interface (tsun0) addresses as physical
candidate endpoints for both iroh discovery and tcp-tls announcements. When an
intermediate tunnel was active between two peers (e.g., between a host and a VM),
iroh probes to the remote peer's overlay address were routed through the existing
overlay, causing iroh to select the overlay IP itself as a 'direct' endpoint.
This caused recursive tunneling (QUIC over WireGuard over TLS) and confusing
status output showing peers connecting to their own overlay IP addresses.
Additionally, in tsunagi status, peer rows were displayed with unaligned
columns, mixing control plane status and data plane metrics in a way that was
difficult to scan.

Key improvements:
- In crates/tsunagi/src/net.rs, add an AddrFilter to the iroh EndpointBuilder
  to dynamically filter out any local addresses assigned to tsun* overlay
  interfaces before publishing to address discovery services (DHT/pkarr/STUN).
- In crates/tsunagi-tcp-tls/src/plugin.rs, exclude tsun* interfaces from
  local_ip_candidates() so overlay IPs are never advertised as physical
  TCP endpoints.
- In crates/tsunagi-cli/src/main.rs, exclude tsun* interfaces from
  netwatch_addresses() so the local addresses section reflects only actual
  physical/host network interfaces.
- In crates/tsunagi-cli/src/main.rs, update member_row() to format member
  details with aligned columnar layout: the overlay IP is placed first and
  padded, followed by control plane transport and latency, and line 2 pads
  tunnel metrics so that the data link endpoint (via ...) aligns vertically.

Validation on Linux:
- cargo fmt --all -- --check passed.
- cargo clippy --locked --workspace --all-targets -- -D warnings passed.
- cargo test --locked --workspace --all-targets --jobs 2 -- --test-threads=2 passed (349 tests passed).
This commit is contained in:
Aleksandr Bogomiakov committed 2026-10-01 00:24:53 +01:00
1 parent ac5a1596ef
commit 2ed447ae86
5 files changed
+73 -25

No files matched your search

+34 -22
View File
@@ -2810,7 +2810,7 @@ fn member_row(row: &MemberRow<'_>) -> report::Row {
// ordinary condition, so it is stated rather than flagged.
let mut detail = "offline".to_string();
if let Some(v4) = row.overlay_address_v4 {
detail.push_str(&format!(" · {v4} still reserved for it"));
detail = format!("{v4:<15} offline · {v4} still reserved for it");
}
let out = Row::new(Health::Info, row.label(), detail);
return if row.failed_dials > 0 {
@@ -2829,13 +2829,14 @@ fn member_row(row: &MemberRow<'_>) -> report::Row {
let tunnel_up = row.tunnel.is_some_and(|tunnel| tunnel.is_up());
let has_overlay = row.tunnel.is_some();
let mut detail = transport.to_lowercase();
let mut detail = String::new();
if let Some(v4) = row.overlay_address_v4 {
detail.push_str(&format!("{v4:<15} "));
}
detail.push_str(&transport.to_lowercase());
if let Some(rtt) = row.rtt_ms {
detail.push_str(&format!(" rtt {rtt}ms"));
}
if let Some(v4) = row.overlay_address_v4 {
detail.push_str(&format!(" · {v4}"));
}
let health = if !direct || (has_overlay && !tunnel_up) {
Health::Degraded
@@ -2851,22 +2852,24 @@ fn member_row(row: &MemberRow<'_>) -> report::Row {
&tunnel.protocol
};
out = out.with_note(match tunnel.handshake_secs_ago {
Some(secs) => format!(
"tunnel up ({proto}), {} tx {} rx {}{} · {}",
if proto == "tcp-tls" {
format!("uptime {secs}s,")
} else {
format!("handshake {secs}s ago,")
},
tunnel.tx_packets,
tunnel.rx_packets,
if tunnel.dropped > 0 {
format!(", {} dropped", tunnel.dropped)
} else {
String::new()
},
tunnel.path
),
Some(secs) => {
let stats = format!(
"tunnel up ({proto}), {} tx {} rx {}{}",
if proto == "tcp-tls" {
format!("uptime {secs}s,")
} else {
format!("handshake {secs}s ago,")
},
tunnel.tx_packets,
tunnel.rx_packets,
if tunnel.dropped > 0 {
format!(", {} dropped", tunnel.dropped)
} else {
String::new()
},
);
format!("{stats:<48} · {}", tunnel.path)
}
None => {
if proto == "wg-quic" {
"no WireGuard handshake yet; the tunnel cannot carry traffic".to_string()
@@ -3408,7 +3411,16 @@ fn program_path() -> String {
async fn netwatch_addresses() -> Vec<std::net::IpAddr> {
// Best effort; used for diagnostics only.
let state = netwatch::interfaces::State::new().await;
let mut addresses = state.local_addresses.regular;
let mut addresses = Vec::new();
for (name, iface) in &state.interfaces {
let name_lower = name.to_lowercase();
if name_lower.starts_with("tsun") || name_lower.contains("tsunagi") {
continue;
}
for prefix in iface.addrs() {
addresses.push(prefix.addr());
}
}
addresses.sort();
addresses.dedup();
addresses