Phase 8d: daemon binds a second QuicServer on the tailnet interface #66

Merged
osobh merged 1 commits from phase-8d-tailnet-server-bind into main 2026-07-14 19:00:11 +00:00
+38
View File
@@ -197,6 +197,16 @@ impl ClusterServices {
.rpc_lan() .rpc_lan()
.or_else(|| cluster.rpc_tailscale()) .or_else(|| cluster.rpc_tailscale())
.context("no RPC bind address (need bind_lan or bind_tailscale)")?; .context("no RPC bind address (need bind_lan or bind_tailscale)")?;
// Phase 8d (2026-07-14): when BOTH LAN and tailnet
// addresses are configured, we also bind a second
// QuicServer on the tailnet interface. The gossip
// layer already publishes `rpc_tailscale` so peers
// learn to dial it; without the second bind the
// advertised address just refuses connections.
let bind_tailnet = match (cluster.rpc_lan(), cluster.rpc_tailscale()) {
(Some(lan), Some(ts)) if lan != ts => Some(ts),
_ => None,
};
let outbound_client = crate::cluster::transport::QuicClient::new( let outbound_client = crate::cluster::transport::QuicClient::new(
"0.0.0.0:0".parse().expect("literal 0.0.0.0:0 parses"), "0.0.0.0:0".parse().expect("literal 0.0.0.0:0 parses"),
client_identity, client_identity,
@@ -228,6 +238,34 @@ impl ClusterServices {
let task = tokio::spawn(async move { let task = tokio::spawn(async move {
accept_forever(server, router_for_accept).await; accept_forever(server, router_for_accept).await;
}); });
// Phase 8d: second bind for the tailnet interface,
// when configured. Shares the same identity + router
// as the LAN listener — connections from either side
// hit the same handlers.
if let Some(ts_addr) = bind_tailnet {
let ts_identity =
NodeIdentity::from_pem_files(&tls.ca_cert, &tls.node_cert, &tls.node_key)
.context(
"loading node identity for tailnet QUIC server",
)?;
match QuicServer::bind(ts_addr, ts_identity) {
Ok(ts_server) => {
tracing::info!(
"cluster RPC server also listening on {} (tailnet)",
ts_addr
);
let router_for_ts = router.clone();
tokio::spawn(async move {
accept_forever(ts_server, router_for_ts).await;
});
}
Err(e) => tracing::warn!(
error = %e,
addr = %ts_addr,
"tailnet RPC bind failed; LAN listener still active"
),
}
}
(Some(router), Some(task)) (Some(router), Some(task))
} }
None => { None => {