milestone 11: systemd and docker packaging, operator and architecture docs, config and api reference, docs drift guards

This commit is contained in:
2026-08-02 15:24:10 +02:00
parent a589df7515
commit bdb6ffab7a
29 changed files with 1936 additions and 94 deletions
+59 -4
View File
@@ -458,8 +458,8 @@ fn serve(r: cli.Runner, args: cli.RunArgs) !u8 {
.reload_fn = reloadManager,
};
const v6_bind = parseBind(r, cfg.dns.bind_ipv6, cfg.dns.port, "dns.bind_ipv6") catch |err| return err;
const v4_bind = parseBind(r, cfg.dns.bind_ipv4, cfg.dns.port, "dns.bind_ipv4") catch |err| return err;
const v6_bind = parseBind(r, cfg.dns.bind_ipv6, cfg.dns.port, "dns.bind_ipv6", .ip6) catch |err| return err;
const v4_bind = parseBind(r, cfg.dns.bind_ipv4, cfg.dns.port, "dns.bind_ipv4", .ip4) catch |err| return err;
// IPv6 first, and the order is load-bearing — see `Listeners`.
var udp6: ?udp_server.UdpServer = udp_server.UdpServer.bind(gpa, io, v6_bind, &h, .{}) catch |err| bound: {
@@ -826,11 +826,66 @@ const Listeners = struct {
tcp4: ?net.IpAddress,
};
fn parseBind(r: cli.Runner, text: []const u8, port: u16, field: []const u8) !net.IpAddress {
return net.IpAddress.parse(text, port) catch {
const BindFamily = enum { ip4, ip6 };
/// `config/validate.checkBind` enforces the same family rule on import, check
/// and settings PUT — but not on a config.db written before the rule existed,
/// and `serve` loads that DB without re-validating. Boot is the last seam: a
/// cross-family literal here would bind the wrong family's socket and make the
/// real one fail with AddressInUse, silently losing a family.
fn parseBind(
r: cli.Runner,
text: []const u8,
port: u16,
field: []const u8,
family: BindFamily,
) !net.IpAddress {
const addr = net.IpAddress.parse(text, port) catch {
r.err.print("{s}: '{s}' is not an IP address\n", .{ field, text }) catch {};
return error.BadBindAddress;
};
const matches = switch (addr) {
.ip4 => family == .ip4,
.ip6 => family == .ip6,
};
if (!matches) {
const digit: u8 = if (family == .ip4) '4' else '6';
r.err.print(
"{s}: '{s}' is not an IPv{c} address; re-import the configuration or correct it with a settings PUT\n",
.{ field, text, digit },
) catch {};
return error.BadBindAddress;
}
return addr;
}
test "parseBind refuses a bind address of the wrong family" {
var out_buf: [8]u8 = undefined;
var err_buf: [256]u8 = undefined;
var out: Writer = .fixed(&out_buf);
var err_writer: Writer = .fixed(&err_buf);
const r: cli.Runner = .{
.io = std.testing.io,
.gpa = std.testing.allocator,
.out = &out,
.err = &err_writer,
};
_ = try parseBind(r, "0.0.0.0", 53, "dns.bind_ipv4", .ip4);
_ = try parseBind(r, "::", 53, "dns.bind_ipv6", .ip6);
try std.testing.expectError(
error.BadBindAddress,
parseBind(r, "0.0.0.0", 53, "dns.bind_ipv6", .ip6),
);
try std.testing.expectError(
error.BadBindAddress,
parseBind(r, "::", 53, "dns.bind_ipv4", .ip4),
);
const printed = err_writer.buffered();
try std.testing.expect(std.mem.containsAtLeast(u8, printed, 1, "is not an IPv6 address"));
try std.testing.expect(std.mem.containsAtLeast(u8, printed, 1, "is not an IPv4 address"));
}
fn reportBind(r: cli.Runner, which: []const u8, addr: net.IpAddress, err: anyerror) anyerror {