Put every network's address on the interface, and split up from join
Two networks on one agent, and only one of them worked. The interface plan is exhaustive by contract — it is what the interface should carry and nothing else — but the request that built it held a single address, so the provisioner was told about the first network and took the second one's address off, or never put it on. On the host that is a network whose address the operating system has never heard of: the tunnel is up, the status says all is well, and nothing routes. The request now carries every address, which is also what takes one off when a network is left or stopped. The other half is the command line. `up --network X --secret Y` and `network join` were two ways to do the same thing, and the one on `up` could only be undone by restarting — which is how a network somebody left came back, and how an invite line told the other side to start their agent with a network baked into it. So they are one thing now, split the way the system is: **`up` runs the agent** — the device's one process, serving whatever it has joined, answering `status`, taking instructions — and **`join` decides what it belongs to**, at any time, while it runs. `join` is at the top level because it is what gets typed; `network join` is the same command for anyone who likes the long form. Every line that told somebody to type the old form is gone with it: the invite after making a network, the lock error from a second `up`, the empty-network hint in `id`, the README walkthrough and the WireGuard document. The invite now prints the `join` line for the other machine and, separately, the `up --peer` line for an agent that is not running yet — two commands, because they really are two, and no amount of wording makes starting an agent the same thing as joining a network. Joining says how the network stood before: new, already here, or stopped and now running again. That last one matters — joining is an instruction to run it, so it undoes a stop, and a pause that ends without a word is a pause nobody can rely on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,51 +98,63 @@ impl Drop for Running {
|
||||
/// There is no separate zone setting: an agent serves a zone per network,
|
||||
/// named after it, so the network name is the zone name.
|
||||
fn start(zone: &str, port: u16) -> Running {
|
||||
let agent = start_without_dns_at(zone, Some(port));
|
||||
let joined = agent.run(&[
|
||||
"join",
|
||||
"--network",
|
||||
zone,
|
||||
"--secret",
|
||||
"a-secret-for-the-dns-test",
|
||||
]);
|
||||
assert!(
|
||||
joined.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&joined.stderr)
|
||||
);
|
||||
agent
|
||||
}
|
||||
|
||||
/// Starts an agent in one network with the resolver off, to switch on later.
|
||||
fn start_without_dns(network: &str) -> Running {
|
||||
let agent = start_without_dns_at(network, None);
|
||||
let joined = agent.run(&[
|
||||
"join",
|
||||
"--network",
|
||||
network,
|
||||
"--secret",
|
||||
"a-secret-for-the-dns-test",
|
||||
]);
|
||||
assert!(
|
||||
joined.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&joined.stderr)
|
||||
);
|
||||
agent
|
||||
}
|
||||
|
||||
/// The agent alone: `up` runs it, and what it belongs to is decided after.
|
||||
fn start_without_dns_at(_network: &str, dns_port: Option<u16>) -> Running {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let child = std::process::Command::new(env!("CARGO_BIN_EXE_tsunagi"))
|
||||
.args([
|
||||
"up",
|
||||
"--network",
|
||||
zone,
|
||||
"--secret",
|
||||
"a-secret-for-the-dns-test",
|
||||
])
|
||||
let mut command = std::process::Command::new(env!("CARGO_BIN_EXE_tsunagi"));
|
||||
command
|
||||
.arg("up")
|
||||
.arg("--state-dir")
|
||||
.arg(dir.path().join("state"))
|
||||
.arg("--cache-dir")
|
||||
.arg(dir.path().join("cache"))
|
||||
// No real interface and no internet: this is about the wiring.
|
||||
.args(["--reach", "local", "--no-tun", "--dns"])
|
||||
.args(["--dns-port", &port.to_string()])
|
||||
.args(["--log", "error", "--status-interval", "0"])
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()
|
||||
.expect("the agent binary starts");
|
||||
Running { child, dir }
|
||||
}
|
||||
|
||||
/// Starts an agent with the resolver off, to be switched on later.
|
||||
fn start_without_dns(network: &str) -> Running {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let child = std::process::Command::new(env!("CARGO_BIN_EXE_tsunagi"))
|
||||
.args([
|
||||
"up",
|
||||
"--network",
|
||||
network,
|
||||
"--secret",
|
||||
"a-secret-for-the-dns-test",
|
||||
])
|
||||
.arg("--state-dir")
|
||||
.arg(dir.path().join("state"))
|
||||
.arg("--cache-dir")
|
||||
.arg(dir.path().join("cache"))
|
||||
.args(["--reach", "local", "--no-tun"])
|
||||
.args(["--reach", "local", "--no-tun"]);
|
||||
if let Some(port) = dns_port {
|
||||
command.arg("--dns").args(["--dns-port", &port.to_string()]);
|
||||
}
|
||||
let child = command
|
||||
.args(["--log", "error", "--status-interval", "0"])
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()
|
||||
.expect("the agent binary starts");
|
||||
// Long enough for the control socket to be there to talk to.
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
Running { child, dir }
|
||||
}
|
||||
|
||||
@@ -213,7 +225,6 @@ fn every_network_gets_a_zone_of_its_own() {
|
||||
wait_for_answer(server, &format!("{host}.first.internal"));
|
||||
|
||||
let joined = agent.run(&[
|
||||
"network",
|
||||
"join",
|
||||
"--network",
|
||||
"second.internal",
|
||||
|
||||
Reference in New Issue
Block a user