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:
1 parent
ac5a1596ef
commit
2ed447ae86
5 files changed
+73
-25
No files matched your search
Generated
+1
-1
@@ -4212,7 +4212,7 @@ dependencies = [
|
||||
"hmac 0.13.0",
|
||||
"iroh",
|
||||
"mainline",
|
||||
"netdev 0.45.1",
|
||||
"netdev 0.46.3",
|
||||
"netwatch",
|
||||
"postcard",
|
||||
"rand 0.10.3",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -66,6 +66,10 @@ pub fn local_ip_candidates() -> Vec<std::net::IpAddr> {
|
||||
let mut ips = Vec::new();
|
||||
for iface in netdev::get_interfaces() {
|
||||
if iface.is_up() && !iface.is_loopback() {
|
||||
let name_lower = iface.name.to_lowercase();
|
||||
if name_lower.starts_with("tsun") || name_lower.contains("tsunagi") {
|
||||
continue;
|
||||
}
|
||||
for ip in iface.ipv4 {
|
||||
let addr = ip.addr();
|
||||
if !addr.is_loopback() && !addr.is_unspecified() {
|
||||
|
||||
@@ -22,7 +22,6 @@ tun-device = [
|
||||
"dep:rtnetlink",
|
||||
"dep:caps",
|
||||
"dep:futures-util",
|
||||
"dep:netdev",
|
||||
]
|
||||
# Telling the operating system where to send its DNS questions. systemd-resolved
|
||||
# on Linux, the Name Resolution Policy Table on Windows; the zone and the
|
||||
@@ -67,6 +66,7 @@ directories = "6.0"
|
||||
# forbidden `unsafe` and will not make one.
|
||||
gethostname = "1.1"
|
||||
netwatch = "0.19.3"
|
||||
netdev = "0.46"
|
||||
mainline = "8.0"
|
||||
futures-lite = "2.6"
|
||||
tun = { version = "0.8", features = ["async"], optional = true }
|
||||
@@ -93,7 +93,6 @@ futures-util = { version = "0.3", default-features = false, optional = true }
|
||||
# before deciding what to change. Creating an adapter needs the process to be
|
||||
# elevated and `wintun.dll` to be reachable at run time.
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
netdev = { version = "0.45", optional = true }
|
||||
windows-sys = { version = "0.61", features = [
|
||||
"Win32_Security",
|
||||
"Win32_Security_Authorization",
|
||||
|
||||
@@ -240,6 +240,39 @@ impl EndpointAdapter {
|
||||
TransportPolicy::N0Defaults => builder.preset(presets::N0),
|
||||
};
|
||||
|
||||
if matches!(
|
||||
config.transport,
|
||||
TransportPolicy::N0Defaults | TransportPolicy::DirectOnly
|
||||
) {
|
||||
let filter = iroh::address_lookup::AddrFilter::new(|addrs| {
|
||||
let mut overlay_ips = std::collections::HashSet::new();
|
||||
for iface in netdev::get_interfaces() {
|
||||
let name = iface.name.to_lowercase();
|
||||
if name.starts_with("tsun") || name.contains("tsunagi") {
|
||||
for ip in iface.ipv4 {
|
||||
overlay_ips.insert(std::net::IpAddr::V4(ip.addr()));
|
||||
}
|
||||
for ip in iface.ipv6 {
|
||||
overlay_ips.insert(std::net::IpAddr::V6(ip.addr()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if overlay_ips.is_empty() {
|
||||
return std::borrow::Cow::Borrowed(addrs);
|
||||
}
|
||||
let filtered: Vec<iroh::TransportAddr> = addrs
|
||||
.iter()
|
||||
.filter(|addr| match addr {
|
||||
iroh::TransportAddr::Ip(sock) => !overlay_ips.contains(&sock.ip()),
|
||||
_ => true,
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
std::borrow::Cow::Owned(filtered)
|
||||
});
|
||||
builder = builder.addr_filter(filter);
|
||||
}
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
if let Some(transport) = &config.test_quic_transport {
|
||||
builder = builder.transport_config(transport.clone());
|
||||
|
||||
Reference in new issue
Block a user