milestone 5: blocklist filtering, local records and conditional forwarding
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -20,6 +20,21 @@ const name = @import("../dns/name.zig");
|
||||
/// RFC 1035 §4.2.2: the TCP length prefix is 16-bit, so no DNS message can be
|
||||
/// larger than this on any transport nxdns speaks.
|
||||
pub const max_message_len = 65535;
|
||||
|
||||
/// RFC 1035 §4.2.2 two-byte big-endian length prefix, shared by every
|
||||
/// stream transport (DoT, plain TCP server, forward-zone TCP fallback).
|
||||
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 doh_default_port = 443;
|
||||
pub const dot_default_port = 853; // RFC 7858 §3.1
|
||||
pub const doh_default_path = "/dns-query"; // RFC 8484 §4.1 well-known template
|
||||
@@ -264,6 +279,30 @@ pub fn validateResponse(query: []const u8, response: []const u8) ValidateError!v
|
||||
|
||||
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" {
|
||||
for ([_]u16{ 0, 1, 12, 512, 4096, 65534, 65535 }) |len| {
|
||||
try testing.expectEqual(len, parsePrefix(framePrefix(len)));
|
||||
}
|
||||
}
|
||||
|
||||
test "the prefix ceiling is the message ceiling" {
|
||||
try testing.expectEqual(@as(u16, max_message_len), parsePrefix(.{ 0xff, 0xff }));
|
||||
}
|
||||
|
||||
test "parse a DoH url with an explicit path" {
|
||||
const e = try Endpoint.parse("https://cloudflare-dns.com/dns-query");
|
||||
try testing.expectEqual(Scheme.doh, e.scheme);
|
||||
|
||||
Reference in New Issue
Block a user