Print the allocated IPv4 address in tun-setup
The overlay IPv4 address is not derived from the keys: it is allocated at run time and signed, so on a fresh state directory there is nothing for tun-setup to print. Once the agent has run, the claim is in state.sqlite, and reading it back takes no directory lock, so tun-setup can show the `ip address add` line while the agent is running. Records are verified on the way out; the database is not a trust boundary. The line needs no keep_addr_on_down and no nodad, unlike its IPv6 counterpart: Linux keeps IPv4 addresses on an interface that has lost carrier, and IPv4 has no duplicate address detection to stall. Also fix a race in the four-agent test. A peer counts as connected once its session authenticates, which can precede the announcement carrying its hostname, so reading the hostnames straight away occasionally saw only two. It now waits for them like every other success condition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+48
-7
@@ -344,6 +344,31 @@ async fn run(command: Command) -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The IPv4 address this agent has already been allocated, if any.
|
||||
///
|
||||
/// Read straight from the mandatory state store. Opening it for reading does
|
||||
/// not take the directory lock, so this works while the agent is running.
|
||||
/// Records are verified here too: the database is not a trust boundary.
|
||||
fn allocated_ipv4(
|
||||
paths: &StoragePaths,
|
||||
network: tsunagi::NetworkId,
|
||||
) -> Result<Option<(std::net::Ipv4Addr, u8)>, Box<dyn std::error::Error>> {
|
||||
use tsunagi::state::RecordBody;
|
||||
use tsunagi::storage::StateStore;
|
||||
|
||||
let store = StateStore::open(paths.state_db())?;
|
||||
let author = store.load_or_create_device_identity()?.endpoint_id();
|
||||
for record in store.signed_records(network)? {
|
||||
if record.author != *author.as_bytes() || record.verify(network).is_err() {
|
||||
continue;
|
||||
}
|
||||
if let RecordBody::Ipv4Claim { address, range } = record.body {
|
||||
return Ok(Some((address, range.prefix_len)));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Path of the local control socket for a state directory.
|
||||
fn control_socket(paths: &StoragePaths, override_path: Option<&PathBuf>) -> PathBuf {
|
||||
match override_path {
|
||||
@@ -406,12 +431,23 @@ async fn tun_setup(args: TunSetupArgs) -> Result<(), Box<dyn std::error::Error>>
|
||||
|
||||
println!("# Network {name} ({network})");
|
||||
println!("# Interface {interface}, address {address}/{OVERLAY_PREFIX_LEN}, mtu {mtu}");
|
||||
if ipv4_range.is_some() {
|
||||
println!(
|
||||
"# IPv4 is allocated once the agent runs and agrees with its peers, so it\n\
|
||||
# cannot be printed here. Start `tsunagi up`; it prints the exact\n\
|
||||
# `ip address add` command for the address it was given."
|
||||
);
|
||||
// The IPv4 address is allocated at run time, so it can only be shown once
|
||||
// the agent has one. Reading the state store does not disturb a running
|
||||
// agent: the directory lock belongs to the agent, not to this reader.
|
||||
let allocated_v4 = ipv4_range.and_then(|_| allocated_ipv4(&paths, network).ok().flatten());
|
||||
match (ipv4_range, allocated_v4) {
|
||||
(Some(_), Some((address, prefix_len))) => {
|
||||
println!("# IPv4 overlay address {address}/{prefix_len}, allocated and signed");
|
||||
}
|
||||
(Some(_), None) => {
|
||||
println!(
|
||||
"# IPv4 is allocated once the agent runs and agrees with its peers, so\n\
|
||||
# there is nothing to print yet. Start `tsunagi up`: it prints the exact\n\
|
||||
# `ip address add` command for the address it was given, and this\n\
|
||||
# command will include it from then on."
|
||||
);
|
||||
}
|
||||
(None, _) => {}
|
||||
}
|
||||
println!("# Run once as root; then run `tsunagi up` as {user}.");
|
||||
println!(
|
||||
@@ -426,9 +462,14 @@ async fn tun_setup(args: TunSetupArgs) -> Result<(), Box<dyn std::error::Error>>
|
||||
println!("sudo ip link set dev {interface} mtu {mtu} up");
|
||||
println!("sudo sysctl -qw net.ipv6.conf.{interface}.keep_addr_on_down=1");
|
||||
println!("sudo ip -6 address add {address}/{OVERLAY_PREFIX_LEN} dev {interface} nodad");
|
||||
if let Some((address, prefix_len)) = allocated_v4 {
|
||||
// IPv4 addresses are not flushed when an interface loses carrier, so
|
||||
// this one needs none of the treatment IPv6 does.
|
||||
println!("sudo ip address add {address}/{prefix_len} dev {interface}");
|
||||
}
|
||||
|
||||
println!("\n# To check it afterwards:");
|
||||
println!("ip -6 addr show dev {interface}");
|
||||
println!("ip addr show dev {interface}");
|
||||
println!("\n# To remove it again:");
|
||||
println!("sudo ip link del dev {interface}");
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user