Enabled DNS by default

This commit is contained in:
ab
2026-09-22 16:08:06 +03:00
parent 9698f21d55
commit 686c74a3bd
4 changed files with 100 additions and 31 deletions
+15 -10
View File
@@ -422,7 +422,7 @@ struct UpArgs {
#[arg(long, value_name = "CIDR", help_heading = "System")]
ipv4_range: Option<String>,
/// Serve a local DNS zone for every network this device is in.
/// Enable local DNS for every network (on by default).
///
/// Each network becomes a zone named after it, and its members resolve
/// as `<hostname>.<network>` from signed state, so a member that is
@@ -430,10 +430,16 @@ struct UpArgs {
/// addresses; questions are taken on loopback of both families, over
/// UDP and TCP.
///
/// Remembered: once on it stays on, and `tsunagi dns off` turns it off.
#[arg(long, help_heading = "System")]
/// Overrides a remembered opt-out. Use --no-dns or `tsunagi dns off` to disable.
#[arg(long, conflicts_with = "no_dns", help_heading = "System")]
dns: bool,
/// Disable local DNS and remember this choice for future starts.
///
/// Use --dns or `tsunagi dns on` to enable it again.
#[arg(long, conflicts_with = "dns", help_heading = "System")]
no_dns: bool,
/// Port for the local DNS server, on loopback of both families.
///
/// Remembered with the setting, so it needs giving only when changing.
@@ -505,7 +511,7 @@ struct DnsSetting {
impl Default for DnsSetting {
fn default() -> Self {
Self {
enabled: false,
enabled: true,
port: DEFAULT_DNS_PORT,
}
}
@@ -517,9 +523,9 @@ fn dns_setting(paths: &StoragePaths) -> DnsSetting {
return DnsSetting::default();
};
DnsSetting {
enabled: matches!(
enabled: !matches!(
store.get_setting(DNS_ENABLED).ok().flatten().as_deref(),
Some("1")
Some("0")
),
port: store
.get_setting(DNS_PORT)
@@ -3256,11 +3262,10 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
let ipv4_range = resolve_ipv4_range(args.ipv4_range.as_ref())?;
// The resolver is a property of the device, not of this command line.
// `--dns` turns it on and it stays on; `tsunagi dns off` is what turns
// it off. Anything else means names work today and are gone tomorrow
// because a flag was not retyped.
// Enabled unless deliberately disabled. Explicit flags override the
// remembered choice, which otherwise survives subsequent restarts.
let stored_dns = dns_setting(&paths);
let serve_dns = args.dns || stored_dns.enabled;
let serve_dns = !args.no_dns && (args.dns || stored_dns.enabled);
let dns_port = args.dns_port.unwrap_or(stored_dns.port);
if serve_dns != stored_dns.enabled || dns_port != stored_dns.port {
// Written before the agent takes the directory, which it is about
+77 -12
View File
@@ -22,6 +22,7 @@ const PORT_REBIND: u16 = 15362;
const PORT_REFUSE: u16 = 15363;
const PORT_TWO_ZONES: u16 = 15364;
const PORT_SWITCH: u16 = 15365;
const PORT_FLAGS: u16 = 15366;
/// Asks, and returns the raw reply. Raw because a parsed packet borrows
/// from the bytes it came out of.
@@ -73,6 +74,33 @@ struct Running {
}
impl Running {
fn wait_ready(&mut self) {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let socket = tsunagi::ipc::control_socket_path(&self.dir.path().join("state"));
let deadline = Instant::now() + Duration::from_secs(20);
while !runtime.block_on(tsunagi::ipc::is_serving(&socket)) {
assert!(
self.child.try_wait().unwrap().is_none(),
"the agent exited before becoming ready"
);
assert!(
Instant::now() < deadline,
"the control socket never became ready"
);
std::thread::sleep(Duration::from_millis(25));
}
}
fn restart(&mut self, dns_flag: Option<&str>) {
self.child.kill().unwrap();
self.child.wait().unwrap();
self.child = spawn_child(&self.dir, None, dns_flag);
self.wait_ready();
}
/// Runs another `tsunagi` command against this agent's directory.
fn run(&self, args: &[&str]) -> std::process::Output {
std::process::Command::new(env!("CARGO_BIN_EXE_tsunagi"))
@@ -135,6 +163,17 @@ fn start_without_dns(network: &str) -> Running {
/// 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 = spawn_child(&dir, dns_port, dns_port.is_none().then_some("--no-dns"));
let mut agent = Running { child, dir };
agent.wait_ready();
agent
}
fn spawn_child(
dir: &TempDir,
dns_port: Option<u16>,
dns_flag: Option<&str>,
) -> std::process::Child {
let mut command = std::process::Command::new(env!("CARGO_BIN_EXE_tsunagi"));
command
.arg("up")
@@ -145,21 +184,21 @@ fn start_without_dns_at(_network: &str, dns_port: Option<u16>) -> Running {
// No real interface and no internet: this is about the wiring.
.args(["--reach", "local", "--no-tun"]);
if let Some(port) = dns_port {
command.arg("--dns").args(["--dns-port", &port.to_string()]);
command.args(["--dns-port", &port.to_string()]);
}
let child = command
if let Some(flag) = dns_flag {
command.arg(flag);
}
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 }
.expect("the agent binary starts")
}
#[test]
fn the_resolver_comes_up_even_with_no_overlay_interface_to_put_it_on() {
fn the_resolver_is_on_by_default_even_without_an_overlay_interface() {
// The promise is that the port is served whatever else fails: with
// `--no-tun` there is no interface to attach a resolver setting to,
// and the zone is answered on loopback all the same. Getting this
@@ -247,15 +286,11 @@ fn every_network_gets_a_zone_of_its_own() {
#[test]
fn the_resolver_can_be_switched_on_and_off_while_the_agent_runs() {
// Forgetting `--dns` on a command line should not be a decision that
// lasts until the next restart, and it is not: the setting belongs to
// the device, and turning it on takes effect at once.
// An intentional --no-dns can be reversed at runtime without restarting.
let agent = start_without_dns("switch.internal");
let server: SocketAddr = format!("127.0.0.1:{PORT_SWITCH}").parse().unwrap();
let host = hostname();
// Give the agent time to be up before asking it anything.
std::thread::sleep(Duration::from_secs(1));
assert!(
query(server, &format!("{host}.switch.internal"), TYPE::A).is_none(),
"nothing should be answering yet"
@@ -280,3 +315,33 @@ fn the_resolver_can_be_switched_on_and_off_while_the_agent_runs() {
std::thread::sleep(Duration::from_millis(200));
}
}
#[test]
fn explicit_dns_choices_override_the_saved_setting_and_survive_restart() {
let mut agent = start("flags.internal", PORT_FLAGS);
let server = format!("127.0.0.1:{PORT_FLAGS}").parse().unwrap();
let name = format!("{}.flags.internal", hostname());
wait_for_answer(server, &name);
for flag in [Some("--no-dns"), None] {
agent.restart(flag);
let state = agent.run(&["dns"]);
assert!(state.status.success());
assert!(String::from_utf8_lossy(&state.stdout).contains("not serving"));
assert!(query(server, &name, TYPE::A).is_none());
}
agent.restart(Some("--dns"));
wait_for_answer(server, &name);
agent.restart(None);
wait_for_answer(server, &name);
}
#[test]
fn opposing_dns_flags_are_rejected() {
let result = std::process::Command::new(env!("CARGO_BIN_EXE_tsunagi"))
.args(["up", "--dns", "--no-dns"])
.output()
.unwrap();
assert!(!result.status.success());
assert!(String::from_utf8_lossy(&result.stderr).contains("cannot be used with"));
}
+1 -1
View File
@@ -46,7 +46,7 @@ fn start_bare(port: u16) -> Running {
.arg(dir.path().join("state"))
.arg("--cache-dir")
.arg(dir.path().join("cache"))
.args(["--reach", "local", "--no-tun"])
.args(["--reach", "local", "--no-tun", "--no-dns"])
.arg("--bind")
.arg(format!("127.0.0.1:{port}"))
.args(["--log", "error", "--status-interval", "0"])