//! **The netmap conversion against captured control-plane bytes.**
//!
//! # What these bytes are, and what they are not
//!
//! `tests/fixtures/map_response.json` and `node.json` are **real documents
//! recorded from Tailscale's coordination server**, vendored from
//! `tailscale/tailscale-rs` at commit
//! `ce8f3d91c56d8bc7739ced07dd6fbe5d98822a10` under BSD-3-Clause — see
//! `THIRD-PARTY.md`, where the attribution was added in the same commit as
//! these files, per this repository's rule.
//!
//! **No test in this crate has spoken to a coordination server.** Joining a
//! tailnet needs an auth key, and this lane holds none for any tailnet. So
//! every test here is named `captured_*`: it proves the conversion agrees with
//! bytes Tailscale's control plane really emitted, and it proves nothing about
//! a live session. That distinction is the reason for the naming convention
//! rather than a bare `it_works`.
//!
//! The one thing that would make these live — an auth key — is also the one
//! thing that would not help much, because [`tunnr_tailscale::Reachability`]
//! already says the resulting peer set cannot carry traffic without DERP.
use tunnr_tailscale::{
netmap::NodeFindingKind, peers_from_map_response, MissingPiece, TailnetPeerSet,
};
use ts_control_serde::MapResponse;
const MAP_RESPONSE: &str = include_str!("fixtures/map_response.json");
fn parsed() -> TailnetPeerSet {
let response: MapResponse<'_> =
serde_json::from_str(MAP_RESPONSE).expect("the captured MapResponse parses");
peers_from_map_response(&response)
}
/// The whole conversion, over bytes a real coordination server sent.
#[test]
fn captured_map_response_becomes_peers_the_data_plane_understands() {
let set = parsed();
assert_eq!(
set.peers.len(),
5,
"the captured response carries five peers in PeersChanged: {:?}",
set.findings
);
for peer in &set.peers {
// A key of all zeroes is what a missing field deserialises to. It is
// 32 valid-looking bytes and would build a peer nothing can hand-shake
// with, so no peer may carry one.
assert_ne!(peer.public_key, [0u8; 32], "no peer carries an identity key");
// Every peer routes something, or it could never be selected for an
// outbound packet.
assert!(!peer.allowed_ips.is_empty());
// Every route is in Tailscale's own 100.64.0.0/10 space.
for (net, prefix) in &peer.allowed_ips {
assert!(
net.octets()[0] == 100 && (64..=127).contains(&net.octets()[1]),
"{net}/{prefix} is not in Tailscale's 100.64.0.0/10"
);
}
// Tailscale does not use WireGuard's pre-shared key, and inventing one
// would be exactly the kind of guess this workspace does not make.
assert_eq!(peer.preshared_key, None);
assert_eq!(peer.persistent_keepalive, Some(25));
}
}
/// RED-WHEN-BROKEN, and the subtlest thing on this wire: `AllowedIPs: null`
/// means **the same as `Addresses`**, not "no routes".
///
/// Read as an empty list, every one of these peers would be routed nothing —
/// a tunnel that comes up, reports healthy and silently drops every packet
/// meant for that node. The captured bytes have `"AllowedIPs": null` on all
/// five peers, which is exactly why they are the fixture.
#[test]
fn captured_null_allowed_ips_defaults_to_the_nodes_addresses() {
assert!(
MAP_RESPONSE.contains(r#""AllowedIPs": null"#),
"the fixture must actually contain the null form, or this test proves nothing"
);
let set = parsed();
let defaulted = set
.findings
.iter()
.filter(|f| f.kind == NodeFindingKind::AllowedIpsDefaultedToAddresses)
.count();
assert_eq!(defaulted, 5, "all five peers hit the default: {:?}", set.findings);
// And the default actually produced routes, rather than being noted and
// then ignored — which would be a finding that reports a bug it did not fix.
for peer in &set.peers {
assert!(
!peer.allowed_ips.is_empty(),
"a defaulted AllowedIPs must yield the node's own address, not nothing"
);
}
}
/// RED-WHEN-BROKEN — the endpoint guess. Tailscale sends every candidate
/// because **disco** picks; with no disco this crate takes one and says so.
/// A silent choice here is a peer that never answers and no line anywhere
/// saying why.
#[test]
fn captured_multiple_endpoints_are_reported_as_a_guess_not_chosen_silently() {
let set = parsed();
let guesses: Vec<_> = set
.findings
.iter()
.filter(|f| matches!(f.kind, NodeFindingKind::EndpointGuessed { .. }))
.collect();
assert!(
!guesses.is_empty(),
"the captured peers list several endpoints each: {:?}",
set.findings
);
for f in &guesses {
let NodeFindingKind::EndpointGuessed { candidates, .. } = f.kind else {
unreachable!()
};
assert!(candidates > 1, "a single candidate is not a guess");
assert!(f.marker_line().contains("no disco"), "{}", f.marker_line());
}
}
/// The v6 half of every tailnet address is dropped, because the data plane
/// routes IPv4 only — and that must reach [`MissingPiece::Ipv6Routing`] rather
/// than staying a per-node note nobody aggregates.
#[test]
fn captured_ipv6_prefixes_are_dropped_and_that_reaches_the_reachability_verdict() {
let set = parsed();
assert!(
set.findings
.iter()
.any(|f| matches!(f.kind, NodeFindingKind::Ipv6PrefixesDropped { .. })),
"{:?}",
set.findings
);
assert!(
set.reachability.missing.contains(&MissingPiece::Ipv6Routing),
"the per-node drop must reach the verdict: {:?}",
set.reachability
);
// …and it is a WARNING, not a refusal: v4 still works.
assert!(!MissingPiece::Ipv6Routing.is_fatal());
}
/// Offline peers are still built — they may come back — and still reported.
#[test]
fn captured_offline_peers_are_built_and_named() {
let set = parsed();
let offline = set
.findings
.iter()
.filter(|f| f.kind == NodeFindingKind::Offline)
.count();
assert_eq!(offline, 2, "two peers are Online:false in the captured bytes");
// They are in the peer set anyway: a node that is offline now is not a
// node that will never return, and dropping it would make the peer set
// wrong the moment it does.
assert_eq!(set.peers.len(), 5);
}
/// **The refusal.** This is the crate's whole honest product: a peer set built
/// from a real netmap still cannot be turned into a tunnel that claims tailnet
/// membership, because a node with no DERP client and no disco is unreachable
/// rather than degraded.
///
/// RED-WHEN-BROKEN by construction — the day someone makes this return `Ok`,
/// an appliance starts reporting healthy while no peer will ever send to it.
#[test]
fn captured_a_real_netmap_still_refuses_to_become_a_tailnet_membership() {
let set = parsed();
assert!(!set.peers.is_empty(), "the refusal is about DERP, not about an empty netmap");
assert!(!set.reachability.is_reachable());
for fatal in [MissingPiece::DerpClient, MissingPiece::Disco, MissingPiece::ControlClient] {
assert!(set.reachability.missing.contains(&fatal), "{:?}", set.reachability);
}
let err = set
.into_tunnel_or_refuse([1u8; 32])
.expect_err("a peer set without DERP must not become a tunnel");
for expected in ["DERP is MANDATORY", "not a fallback", "disco", "carry nothing"] {
assert!(err.contains(expected), "the refusal names {expected}: {err}");
}
}
/// The identity guard for the test above: `into_tunnel_or_refuse` is not a
/// function that always says no. Given a reachable verdict it produces a
/// tunnel, so the refusal is about DERP and not about the function.
#[test]
fn the_refusal_is_about_derp_and_not_a_function_that_always_says_no() {
let mut set = parsed();
set.reachability.missing.retain(|m| !m.is_fatal());
assert!(set.reachability.is_reachable());
let peers = set.peers.len();
let tunnel = set
.into_tunnel_or_refuse([1u8; 32])
.expect("with nothing fatal missing, a tunnel is produced");
assert_eq!(tunnel.peers.len(), peers);
assert_eq!(tunnel.listen_port, Some(41641));
// …and an empty peer set is refused for its own reason, which is a
// different sentence from the DERP one.
let mut empty = parsed();
empty.reachability.missing.retain(|m| !m.is_fatal());
empty.peers.clear();
let err = empty.into_tunnel_or_refuse([1u8; 32]).expect_err("no peers");
assert!(err.contains("no usable peers"), "{err}");
}
/// A node whose key is the all-zero default must not become a peer. That key
/// is what a missing `Key` field deserialises to: 32 valid-looking bytes that
/// build a peer nothing can ever hand-shake with.
#[test]
fn captured_a_node_with_a_zero_key_is_refused_rather_than_built() {
let zeroed = MAP_RESPONSE.replacen(
"nodekey:a57413f6734a12fa6f50e600891517deef4d324ea63de8915da511550a20a23f",
"nodekey:0000000000000000000000000000000000000000000000000000000000000000",
1,
);
assert_ne!(zeroed, MAP_RESPONSE, "a key was actually zeroed");
let response: MapResponse<'_> = serde_json::from_str(&zeroed).expect("parses");
let set = peers_from_map_response(&response);
assert_eq!(set.peers.len(), 4, "the zero-keyed node is not built");
assert!(
set.findings.iter().any(|f| f.kind == NodeFindingKind::UnusableKey),
"{:?}",
set.findings
);
// The identity guard: unmodified bytes still give five.
assert_eq!(parsed().peers.len(), 5);
}
/// `Peers` takes precedence over `PeersChanged` when both are present — the
/// protocol says so, and reading the delta while the full list is there would
/// silently drop every peer that had not changed.
#[test]
fn captured_a_full_peers_list_takes_precedence_over_the_delta() {
let as_full = MAP_RESPONSE.replacen(r#""PeersChanged": ["#, r#""Peers": ["#, 1);
assert_ne!(as_full, MAP_RESPONSE);
let response: MapResponse<'_> = serde_json::from_str(&as_full).expect("parses");
let set = peers_from_map_response(&response);
assert_eq!(set.peers.len(), 5, "the full list is read");
// With NEITHER field, there are no peers at all — and that is reported as
// a tailnet of one rather than as a parse failure.
let empty: MapResponse<'_> = serde_json::from_str("{}").expect("an empty response parses");
let set = peers_from_map_response(&empty);
assert!(set.peers.is_empty());
assert!(set.reachability.missing.contains(&MissingPiece::Peers));
assert!(!MissingPiece::Peers.is_fatal(), "a tailnet of one is legitimate");
}