milestone 5: blocklist filtering, local records and conditional forwarding

This commit is contained in:
2026-08-01 16:43:55 +02:00
parent 3baf5d6581
commit 59d94df722
29 changed files with 10257 additions and 81 deletions
+3 -37
View File
@@ -21,19 +21,6 @@ const tls_client = @import("../platform/tls_client.zig");
const log = std.log.scoped(.dot_client);
/// RFC 1035 §4.2.2 length prefix, shared by DNS over TCP and DNS over TLS.
pub const prefix_len = 2;
pub fn framePrefix(len: u16) [prefix_len]u8 {
var out: [prefix_len]u8 = undefined;
std.mem.writeInt(u16, &out, len, .big);
return out;
}
pub fn parsePrefix(bytes: [prefix_len]u8) u16 {
return std.mem.readInt(u16, &bytes, .big);
}
pub const ResolveError = error{ConnectFailed};
/// DoT endpoints take IP literals. Name resolution for upstreams is out of
@@ -179,7 +166,7 @@ pub const DotClient = struct {
defer closeTls(io, &tls_stream);
const writer = tls_stream.writer();
const prefix = framePrefix(@intCast(query.len));
const prefix = transport.framePrefix(@intCast(query.len));
writer.writeAll(&prefix) catch |err| return sendFailure(&tls_stream, err);
writer.writeAll(query) catch |err| return sendFailure(&tls_stream, err);
// `TlsStream.flush`, not `writer.flush`: the latter leaves the encrypted
@@ -188,10 +175,10 @@ pub const DotClient = struct {
tls_stream.flush() catch |err| return sendFailure(&tls_stream, err);
const reader = tls_stream.reader();
var prefix_bytes: [prefix_len]u8 = undefined;
var prefix_bytes: [transport.prefix_len]u8 = undefined;
reader.readSliceAll(&prefix_bytes) catch |err| return receiveFailure(&tls_stream, err);
const len = parsePrefix(prefix_bytes);
const len = transport.parsePrefix(prefix_bytes);
if (len == 0) return error.BadResponse;
if (len > response_buf.len) return error.ResponseTooLarge;
reader.readSliceAll(response_buf[0..len]) catch |err|
@@ -299,27 +286,6 @@ fn receiveFailure(stream: *tls_client.TlsStream, err: anyerror) transport.Exchan
const testing = std.testing;
test "framePrefix writes the length big-endian" {
try testing.expectEqualSlices(u8, &.{ 0x00, 0x00 }, &framePrefix(0));
try testing.expectEqualSlices(u8, &.{ 0x00, 0x1d }, &framePrefix(29));
try testing.expectEqualSlices(u8, &.{ 0x01, 0x00 }, &framePrefix(256));
try testing.expectEqualSlices(u8, &.{ 0xff, 0xff }, &framePrefix(65535));
}
test "parsePrefix reads the length big-endian" {
try testing.expectEqual(@as(u16, 0), parsePrefix(.{ 0x00, 0x00 }));
try testing.expectEqual(@as(u16, 29), parsePrefix(.{ 0x00, 0x1d }));
try testing.expectEqual(@as(u16, 256), parsePrefix(.{ 0x01, 0x00 }));
try testing.expectEqual(@as(u16, 65535), parsePrefix(.{ 0xff, 0xff }));
}
test "framePrefix and parsePrefix round-trip" {
const cases = [_]u16{ 0, 1, 12, 512, 4096, 65534, 65535 };
for (cases) |len| {
try testing.expectEqual(len, parsePrefix(framePrefix(len)));
}
}
test "resolveAddress accepts IP literals" {
const v4 = try resolveAddress(try .parse("tls://1.1.1.1:853"));
try testing.expectEqual(@as(u16, 853), v4.ip4.port);