authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-29 23:27:00-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
log46e5068e48fdc7ecdc6121b978c87fad680cbe40
tree672e811bd1a3c6dd2ecb5c678d0bcf2702999dc8
parenta37c0bca2248e5d2e18c4855ee8d8d17bf26aa26

std.Io.net.HostName: finish implementing DNS lookup


4 files changed, 237 insertions(+), 148 deletions(-)

lib/std/Io.zig+34-18
......@@ -641,8 +641,8 @@ pub const VTable = struct {
641641 context_alignment: std.mem.Alignment,
642642 start: *const fn (context: *const anyopaque) void,
643643 ) void,
644 groupWait: *const fn (?*anyopaque, *Group) void,
645 groupCancel: *const fn (?*anyopaque, *Group) void,
644 groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
645 groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
646646
647647 /// Blocks until one of the futures from the list has a result ready, such
648648 /// that awaiting it will not block. Returns that index.
......@@ -665,14 +665,14 @@ pub const VTable = struct {
665665 fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,
666666 fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,
667667
668 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,
669 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,
668 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) NowError!Timestamp,
669 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, timeout: Timeout) SleepError!void,
670670
671671 listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
672672 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream,
673673 ipBind: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
674 netSend: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, data: []const u8) net.Socket.SendError!void,
675 netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, buffer: []u8) net.Socket.ReceiveError!void,
674 netSend: *const fn (?*anyopaque, handle: net.Socket.Handle, address: *const net.IpAddress, data: []const u8) net.Socket.SendError!void,
675 netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, buffer: []u8, timeout: Timeout) net.Socket.ReceiveTimeoutError!net.ReceivedMessage,
676676 netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,
677677 netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
678678 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
......@@ -710,6 +710,15 @@ pub const Timestamp = enum(i96) {
710710 pub fn addDuration(from: Timestamp, duration: Duration) Timestamp {
711711 return @enumFromInt(@intFromEnum(from) + duration.nanoseconds);
712712 }
713
714 pub fn fromNow(io: Io, clockid: std.posix.clockid_t, duration: Duration) NowError!Timestamp {
715 const now_ts = try now(io, clockid);
716 return addDuration(now_ts, duration);
717 }
718
719 pub fn compare(lhs: Timestamp, op: std.math.CompareOperator, rhs: Timestamp) bool {
720 return std.math.compare(@intFromEnum(lhs), op, @intFromEnum(rhs));
721 }
713722};
714723pub const Duration = struct {
715724 nanoseconds: i96,
......@@ -722,11 +731,14 @@ pub const Duration = struct {
722731 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_s };
723732 }
724733};
725pub const Deadline = union(enum) {
734pub const Timeout = union(enum) {
735 none,
726736 duration: Duration,
727 timestamp: Timestamp,
737 deadline: Timestamp,
738
739 pub const Error = error{Timeout};
728740};
729pub const ClockGetTimeError = std.posix.ClockGetTimeError || Cancelable;
741pub const NowError = std.posix.ClockGetTimeError || Cancelable;
730742pub const SleepError = error{ UnsupportedClock, Unexpected, Canceled };
731743
732744pub const AnyFuture = opaque {};
......@@ -768,8 +780,10 @@ pub const Group = struct {
768780 ///
769781 /// `function` *may* be called immediately, before `async` returns.
770782 ///
771 /// After this is called, `wait` must be called before the group is
772 /// deinitialized.
783 /// After this is called, `wait` or `cancel` must be called before the
784 /// group is deinitialized.
785 ///
786 /// Threadsafe.
773787 ///
774788 /// See also:
775789 /// * `Io.async`
......@@ -789,7 +803,9 @@ pub const Group = struct {
789803 ///
790804 /// Idempotent. Not threadsafe.
791805 pub fn wait(g: *Group, io: Io) void {
792 io.vtable.groupWait(io.userdata, g);
806 const token = g.token orelse return;
807 g.token = null;
808 io.vtable.groupWait(io.userdata, g, token);
793809 }
794810
795811 /// Equivalent to `wait` but requests cancellation on all tasks owned by
......@@ -797,9 +813,9 @@ pub const Group = struct {
797813 ///
798814 /// Idempotent. Not threadsafe.
799815 pub fn cancel(g: *Group, io: Io) void {
800 if (g.token == null) return;
801 io.vtable.groupCancel(io.userdata, g);
802 assert(g.token == null);
816 const token = g.token orelse return;
817 g.token = null;
818 io.vtable.groupCancel(io.userdata, g, token);
803819 }
804820};
805821
......@@ -1215,12 +1231,12 @@ pub fn cancelRequested(io: Io) bool {
12151231 return io.vtable.cancelRequested(io.userdata);
12161232}
12171233
1218pub fn now(io: Io, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp {
1234pub fn now(io: Io, clockid: std.posix.clockid_t) NowError!Timestamp {
12191235 return io.vtable.now(io.userdata, clockid);
12201236}
12211237
1222pub fn sleep(io: Io, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void {
1223 return io.vtable.sleep(io.userdata, clockid, deadline);
1238pub fn sleep(io: Io, clockid: std.posix.clockid_t, timeout: Timeout) SleepError!void {
1239 return io.vtable.sleep(io.userdata, clockid, timeout);
12241240}
12251241
12261242pub fn sleepDuration(io: Io, duration: Duration) SleepError!void {
lib/std/Io/Threaded.zig+58-27
......@@ -463,16 +463,19 @@ fn groupAsync(
463463 },
464464 .pool = pool,
465465 .group = group,
466 .node = .{ .next = @ptrCast(@alignCast(group.token)) },
466 .node = undefined,
467467 .func = start,
468468 .context_alignment = context_alignment,
469469 .context_len = context.len,
470470 };
471 group.token = &gc.node;
472471 @memcpy(gc.contextPointer()[0..context.len], context);
473472
474473 pool.mutex.lock();
475474
475 // Append to the group linked list inside the mutex to make `Io.Group.async` thread-safe.
476 gc.node = .{ .next = @ptrCast(@alignCast(group.token)) };
477 group.token = &gc.node;
478
476479 const thread_capacity = cpu_count - 1 + pool.concurrent_count;
477480
478481 pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch {
......@@ -493,6 +496,8 @@ fn groupAsync(
493496 pool.threads.appendAssumeCapacity(thread);
494497 }
495498
499 // This needs to be done before unlocking the mutex to avoid a race with
500 // the associated task finishing.
496501 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
497502 std.Thread.WaitGroup.startStateless(group_state);
498503
......@@ -500,21 +505,16 @@ fn groupAsync(
500505 pool.cond.signal();
501506}
502507
503fn groupWait(userdata: ?*anyopaque, group: *Io.Group) void {
504 if (builtin.single_threaded) return;
508fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
505509 const pool: *Pool = @ptrCast(@alignCast(userdata));
506510 _ = pool;
511
512 if (builtin.single_threaded) return;
513
507514 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
508515 const reset_event: *ResetEvent = @ptrCast(&group.context);
509516 std.Thread.WaitGroup.waitStateless(group_state, reset_event);
510}
511517
512fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void {
513 if (builtin.single_threaded) return;
514 const pool: *Pool = @ptrCast(@alignCast(userdata));
515 _ = pool;
516 const token = group.token.?;
517 group.token = null;
518518 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
519519 while (true) {
520520 const gc: *GroupClosure = @fieldParentPtr("node", node);
......@@ -523,6 +523,36 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void {
523523 }
524524}
525525
526fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
527 const pool: *Pool = @ptrCast(@alignCast(userdata));
528 const gpa = pool.allocator;
529
530 if (builtin.single_threaded) return;
531
532 {
533 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
534 while (true) {
535 const gc: *GroupClosure = @fieldParentPtr("node", node);
536 gc.closure.requestCancel();
537 node = node.next orelse break;
538 }
539 }
540
541 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
542 const reset_event: *ResetEvent = @ptrCast(&group.context);
543 std.Thread.WaitGroup.waitStateless(group_state, reset_event);
544
545 {
546 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
547 while (true) {
548 const gc: *GroupClosure = @fieldParentPtr("node", node);
549 const node_next = node.next;
550 gc.free(gpa);
551 node = node_next orelse break;
552 }
553 }
554}
555
526556fn await(
527557 userdata: ?*anyopaque,
528558 any_future: *Io.AnyFuture,
......@@ -774,7 +804,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
774804 .SUCCESS => return nread,
775805 .INTR => unreachable,
776806 .INVAL => unreachable,
777 .FAULT => unreachable,
807 .FAULT => |err| return errnoBug(err),
778808 .AGAIN => unreachable, // currently not support in WASI
779809 .BADF => return error.NotOpenForReading, // can be a race condition
780810 .IO => return error.InputOutput,
......@@ -796,7 +826,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
796826 .SUCCESS => return @intCast(rc),
797827 .INTR => continue,
798828 .INVAL => unreachable,
799 .FAULT => unreachable,
829 .FAULT => |err| return errnoBug(err),
800830 .SRCH => return error.ProcessNotFound,
801831 .AGAIN => return error.WouldBlock,
802832 .BADF => return error.NotOpenForReading, // can be a race condition
......@@ -896,7 +926,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
896926 .SUCCESS => return nread,
897927 .INTR => unreachable,
898928 .INVAL => unreachable,
899 .FAULT => unreachable,
929 .FAULT => |err| return errnoBug(err),
900930 .AGAIN => unreachable,
901931 .BADF => return error.NotOpenForReading, // can be a race condition
902932 .IO => return error.InputOutput,
......@@ -922,7 +952,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
922952 .SUCCESS => return @bitCast(rc),
923953 .INTR => continue,
924954 .INVAL => unreachable,
925 .FAULT => unreachable,
955 .FAULT => |err| return errnoBug(err),
926956 .SRCH => return error.ProcessNotFound,
927957 .AGAIN => return error.WouldBlock,
928958 .BADF => return error.NotOpenForReading, // can be a race condition
......@@ -969,18 +999,19 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posi
969999 };
9701000}
9711001
972fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp {
1002fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.NowError!Io.Timestamp {
9731003 const pool: *Pool = @ptrCast(@alignCast(userdata));
9741004 try pool.checkCancel();
9751005 const timespec = try posix.clock_gettime(clockid);
9761006 return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);
9771007}
9781008
979fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {
1009fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, timeout: Io.Timeout) Io.SleepError!void {
9801010 const pool: *Pool = @ptrCast(@alignCast(userdata));
981 const deadline_nanoseconds: i96 = switch (deadline) {
1011 const deadline_nanoseconds: i96 = switch (timeout) {
1012 .none => std.math.maxInt(i96),
9821013 .duration => |duration| duration.nanoseconds,
983 .timestamp => |timestamp| @intFromEnum(timestamp),
1014 .deadline => |deadline| @intFromEnum(deadline),
9841015 };
9851016 var timespec: posix.timespec = .{
9861017 .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)),
......@@ -988,12 +1019,12 @@ fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline)
9881019 };
9891020 while (true) {
9901021 try pool.checkCancel();
991 switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clockid, .{ .ABSTIME = switch (deadline) {
992 .duration => false,
993 .timestamp => true,
1022 switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clockid, .{ .ABSTIME = switch (timeout) {
1023 .none, .duration => false,
1024 .deadline => true,
9941025 } }, &timespec, &timespec))) {
9951026 .SUCCESS => return,
996 .FAULT => unreachable,
1027 .FAULT => |err| return errnoBug(err),
9971028 .INTR => {},
9981029 .INVAL => return error.UnsupportedClock,
9991030 else => |err| return posix.unexpectedErrno(err),
......@@ -1278,7 +1309,7 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n
12781309fn netSend(
12791310 userdata: ?*anyopaque,
12801311 handle: Io.net.Socket.Handle,
1281 address: Io.net.IpAddress,
1312 address: *const Io.net.IpAddress,
12821313 data: []const u8,
12831314) Io.net.Socket.SendError!void {
12841315 const pool: *Pool = @ptrCast(@alignCast(userdata));
......@@ -1293,15 +1324,15 @@ fn netSend(
12931324fn netReceive(
12941325 userdata: ?*anyopaque,
12951326 handle: Io.net.Socket.Handle,
1296 address: Io.net.IpAddress,
12971327 buffer: []u8,
1298) Io.net.Socket.ReceiveError!void {
1328 timeout: Io.Timeout,
1329) Io.net.Socket.ReceiveTimeoutError!Io.net.ReceivedMessage {
12991330 const pool: *Pool = @ptrCast(@alignCast(userdata));
13001331 try pool.checkCancel();
13011332
13021333 _ = handle;
1303 _ = address;
13041334 _ = buffer;
1335 _ = timeout;
13051336 @panic("TODO");
13061337}
13071338
lib/std/Io/net.zig+32-10
......@@ -134,13 +134,13 @@ pub const IpAddress = union(enum) {
134134 }
135135 }
136136
137 pub fn eql(a: IpAddress, b: IpAddress) bool {
138 return switch (a) {
139 .ip4 => |a_ip4| switch (b) {
137 pub fn eql(a: *const IpAddress, b: *const IpAddress) bool {
138 return switch (a.*) {
139 .ip4 => |a_ip4| switch (b.*) {
140140 .ip4 => |b_ip4| a_ip4.eql(b_ip4),
141141 else => false,
142142 },
143 .ip6 => |a_ip6| switch (b) {
143 .ip6 => |a_ip6| switch (b.*) {
144144 .ip6 => |b_ip6| a_ip6.eql(b_ip6),
145145 else => false,
146146 },
......@@ -695,6 +695,11 @@ pub const Ip6Address = struct {
695695 };
696696};
697697
698pub const ReceivedMessage = struct {
699 from: IpAddress,
700 len: usize,
701};
702
698703pub const Interface = struct {
699704 /// Value 0 indicates `none`.
700705 index: u32,
......@@ -816,14 +821,31 @@ pub const Socket = struct {
816821 return io.vtable.netSend(io.userdata, s.handle, dest, data);
817822 }
818823
819 pub const ReceiveError = error{} || Io.Cancelable;
824 pub const ReceiveError = error{} || Io.UnexpectedError || Io.Cancelable;
820825
821 /// Transfers `data` from `source`, connectionless.
826 /// Waits for data. Connectionless.
822827 ///
823 /// Returned slice has same pointer as `buffer` with possibly shorter length.
824 pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError![]u8 {
825 const n = try io.vtable.netReceive(io.userdata, s.handle, source, buffer);
826 return buffer[0..n];
828 /// See also:
829 /// * `receiveTimeout`
830 pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError!ReceivedMessage {
831 return io.vtable.netReceive(io.userdata, s.handle, source, buffer, .none);
832 }
833
834 pub const ReceiveTimeoutError = ReceiveError || Io.Timeout.Error;
835
836 /// Waits for data. Connectionless.
837 ///
838 /// Returns `error.Timeout` if no message arrives early enough.
839 ///
840 /// See also:
841 /// * `receive`
842 pub fn receiveTimeout(
843 s: *const Socket,
844 io: Io,
845 buffer: []u8,
846 timeout: Io.Timeout,
847 ) ReceiveTimeoutError!ReceivedMessage {
848 return io.vtable.netReceive(io.userdata, s.handle, buffer, timeout);
827849 }
828850};
829851
lib/std/Io/net/HostName.zig+113-93
......@@ -46,15 +46,13 @@ pub const LookupOptions = struct {
4646 family: ?IpAddress.Family = null,
4747};
4848
49pub const LookupError = Io.Cancelable || Io.File.OpenError || Io.File.Reader.Error || error{
49pub const LookupError = error{
5050 UnknownHostName,
5151 ResolvConfParseFailed,
52 // TODO remove from error set; retry a few times then report a different error
53 TemporaryNameServerFailure,
5452 InvalidDnsARecord,
5553 InvalidDnsAAAARecord,
5654 NameServerFailure,
57};
55} || Io.NowError || IpAddress.BindError || Io.File.OpenError || Io.File.Reader.Error || Io.Cancelable;
5856
5957pub const LookupResult = struct {
6058 /// How many `LookupOptions.addresses_buffer` elements are populated.
......@@ -185,7 +183,7 @@ fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult
185183 return result;
186184}
187185
188fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {
186fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
189187 const rc = ResolvConf.init(io) catch return error.ResolvConfParseFailed;
190188
191189 // Count dots, suppress search when >=ndots or name ends in
......@@ -218,19 +216,17 @@ fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) !LookupR
218216 return lookupDns(io, lookup_canon_name, &rc, options);
219217}
220218
221const DnsReply = struct {
222 buf: [512]u8,
223 len: usize,
224};
225
226fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) !LookupResult {
219fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) LookupError!LookupResult {
227220 const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{
228221 .{ .af = .ip6, .rr = std.posix.RR.A },
229222 .{ .af = .ip4, .rr = std.posix.RR.AAAA },
230223 };
231224 var query_buffers: [2][280]u8 = undefined;
225 var answer_buffers: [2][512]u8 = undefined;
232226 var queries_buffer: [2][]const u8 = undefined;
227 var answers_buffer: [2][]const u8 = undefined;
233228 var nq: usize = 0;
229 var next_answer_buffer: usize = 0;
234230
235231 for (family_records) |fr| {
236232 if (options.family != fr.af) {
......@@ -241,41 +237,123 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
241237 }
242238 }
243239
240 var ip4_mapped: [ResolvConf.max_nameservers]IpAddress = undefined;
241 var any_ip6 = false;
242 for (rc.nameservers(), &ip4_mapped) |*ns, *m| {
243 m.* = .{ .ip6 = .fromAny(ns.*) };
244 any_ip6 = any_ip6 or ns.* == .ip6;
245 }
246 var socket = s: {
247 if (any_ip6) ip6: {
248 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };
249 const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) {
250 error.AddressFamilyUnsupported => break :ip6,
251 else => |e| return e,
252 };
253 break :s socket;
254 }
255 any_ip6 = false;
256 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };
257 const socket = try ip4_addr.bind(io, .{ .mode = .dgram });
258 break :s socket;
259 };
260 defer socket.close(io);
261
262 const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers();
244263 const queries = queries_buffer[0..nq];
245 var replies_buffer: [2]DnsReply = undefined;
246 var replies: Io.Queue(DnsReply) = .init(&replies_buffer);
247 try rc.sendMessage(io, queries, &replies);
248
249 for (replies) |reply| {
250 if (reply.len < 4 or (reply[3] & 15) == 2) return error.TemporaryNameServerFailure;
251 if ((reply[3] & 15) == 3) return .empty;
252 if ((reply[3] & 15) != 0) return error.UnknownHostName;
264 const answers = answers_buffer[0..queries.len];
265 for (answers) |*answer| answer.len = 0;
266
267 var now_ts = try io.now(.MONOTONIC);
268 const final_ts = now_ts.addDuration(.seconds(rc.timeout_seconds));
269 const attempt_duration: Io.Duration = .{
270 .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts,
271 };
272
273 send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try io.now(.MONOTONIC)) {
274 var group: Io.Group = .init;
275 defer group.cancel(io);
276
277 for (queries, answers) |query, *answer| {
278 if (answer.len != 0) continue;
279 for (mapped_nameservers) |*ns| {
280 group.async(io, sendIgnoringResult, .{ io, socket.handle, ns, query });
281 }
282 }
283
284 const timeout: Io.Timeout = .{ .deadline = now_ts.addDuration(attempt_duration) };
285
286 while (true) {
287 const buf = &answer_buffers[next_answer_buffer];
288 const reply = socket.receiveTimeout(io, buf, timeout) catch |err| switch (err) {
289 error.Canceled => return error.Canceled,
290 error.Timeout => continue :send,
291 else => continue,
292 };
293
294 // Ignore non-identifiable packets.
295 if (reply.len < 4) continue;
296
297 // Ignore replies from addresses we didn't send to.
298 const ns = for (mapped_nameservers) |*ns| {
299 if (reply.from.eql(ns)) break ns;
300 } else {
301 continue;
302 };
303
304 const reply_msg = buf[0..reply.len];
305
306 // Find which query this answer goes with, if any.
307 const query, const answer = for (queries, answers) |query, *answer| {
308 if (reply_msg[0] == query[0] and reply_msg[1] == query[1]) break .{ query, answer };
309 } else {
310 continue;
311 };
312 if (answer.len != 0) continue;
313
314 // Only accept positive or negative responses; retry immediately on
315 // server failure, and ignore all other codes such as refusal.
316 switch (reply_msg[3] & 15) {
317 0, 3 => {
318 answer.* = reply_msg;
319 next_answer_buffer += 1;
320 if (next_answer_buffer == answers.len) break :send;
321 },
322 2 => {
323 group.async(io, sendIgnoringResult, .{ io, socket.handle, ns, query });
324 continue;
325 },
326 else => continue,
327 }
328 }
329 } else {
330 return error.NameServerFailure;
253331 }
254332
255333 var addresses_len: usize = 0;
256334 var canonical_name: ?HostName = null;
257335
258 for (replies) |reply| {
259 var it = DnsResponse.init(reply) catch {
336 for (answers) |answer| {
337 var it = DnsResponse.init(answer) catch {
260338 // TODO accept a diagnostics struct and append warnings
261339 continue;
262340 };
263341 while (it.next() catch {
264342 // TODO accept a diagnostics struct and append warnings
265343 continue;
266 }) |answer| switch (answer.rr) {
344 }) |record| switch (record.rr) {
267345 std.posix.RR.A => {
268 if (answer.data.len != 4) return error.InvalidDnsARecord;
346 if (record.data.len != 4) return error.InvalidDnsARecord;
269347 options.addresses_buffer[addresses_len] = .{ .ip4 = .{
270 .bytes = answer.data[0..4].*,
348 .bytes = record.data[0..4].*,
271349 .port = options.port,
272350 } };
273351 addresses_len += 1;
274352 },
275353 std.posix.RR.AAAA => {
276 if (answer.data.len != 16) return error.InvalidDnsAAAARecord;
354 if (record.data.len != 16) return error.InvalidDnsAAAARecord;
277355 options.addresses_buffer[addresses_len] = .{ .ip6 = .{
278 .bytes = answer.data[0..16].*,
356 .bytes = record.data[0..16].*,
279357 .port = options.port,
280358 } };
281359 addresses_len += 1;
......@@ -285,7 +363,7 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
285363 @panic("TODO");
286364 //var tmp: [256]u8 = undefined;
287365 //// Returns len of compressed name. strlen to get canon name.
288 //_ = try posix.dn_expand(packet, answer.data, &tmp);
366 //_ = try posix.dn_expand(packet, record.data, &tmp);
289367 //const canon_name = mem.sliceTo(&tmp, 0);
290368 //if (isValidHostName(canon_name)) {
291369 // ctx.canon.items.len = 0;
......@@ -304,6 +382,10 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
304382 return error.NameServerFailure;
305383}
306384
385fn sendIgnoringResult(io: Io, socket_handle: Io.net.Socket.Handle, dest: *const IpAddress, msg: []const u8) void {
386 _ = io.vtable.netSend(io.userdata, socket_handle, dest, msg) catch {};
387}
388
307389fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {
308390 const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) {
309391 error.FileNotFound,
......@@ -523,7 +605,7 @@ pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream
523605pub const ResolvConf = struct {
524606 attempts: u32,
525607 ndots: u32,
526 timeout: Io.Duration,
608 timeout_seconds: u32,
527609 nameservers_buffer: [max_nameservers]IpAddress,
528610 nameservers_len: usize,
529611 search_buffer: [max_len]u8,
......@@ -539,7 +621,7 @@ pub const ResolvConf = struct {
539621 .search_buffer = undefined,
540622 .search_len = 0,
541623 .ndots = 1,
542 .timeout = .seconds(5),
624 .timeout_seconds = 5,
543625 .attempts = 2,
544626 };
545627
......@@ -589,7 +671,7 @@ pub const ResolvConf = struct {
589671 switch (std.meta.stringToEnum(Option, name) orelse continue) {
590672 .ndots => rc.ndots = @min(value, 15),
591673 .attempts => rc.attempts = @min(value, 10),
592 .timeout => rc.timeout = .seconds(@min(value, 60)),
674 .timeout => rc.timeout_seconds = @min(value, 60),
593675 }
594676 },
595677 .nameserver => {
......@@ -621,68 +703,6 @@ pub const ResolvConf = struct {
621703 fn nameservers(rc: *const ResolvConf) []const IpAddress {
622704 return rc.nameservers_buffer[0..rc.nameservers_len];
623705 }
624
625 fn sendMessage(
626 rc: *const ResolvConf,
627 io: Io,
628 queries: []const []const u8,
629 replies: *Io.Queue(DnsReply),
630 ) !void {
631 var ip4_mapped: [ResolvConf.max_nameservers]IpAddress = undefined;
632 var any_ip6 = false;
633 for (rc.nameservers(), &ip4_mapped) |*ns, *m| {
634 m.* = .{ .ip6 = .fromAny(ns.*) };
635 any_ip6 = any_ip6 or ns.* == .ip6;
636 }
637
638 const socket = s: {
639 if (any_ip6) ip6: {
640 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };
641 const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) {
642 error.AddressFamilyUnsupported => break :ip6,
643 else => |e| return e,
644 };
645 break :s socket;
646 }
647 any_ip6 = false;
648 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };
649 const socket = try ip4_addr.bind(io, .{ .mode = .dgram });
650 break :s socket;
651 };
652 defer socket.close();
653
654 const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers();
655
656 var group: Io.Group = .init;
657 defer group.cancel();
658
659 for (queries) |query| {
660 for (mapped_nameservers) |*ns| {
661 group.async(sendOneMessage, .{ io, query, ns });
662 }
663 }
664
665 const deadline: Io.Deadline = .fromDuration(rc.timeout);
666
667 for (0..queries.len) |_| {
668 const msg = socket.receiveDeadline(deadline) catch |err| switch (err) {
669 error.Timeout => return error.Timeout,
670 error.Canceled => return error.Canceled,
671 else => continue,
672 };
673 _ = msg;
674 _ = replies;
675 @panic("TODO check msg for dns reply and put into replies queue");
676 }
677 }
678
679 fn sendOneMessage(
680 io: Io,
681 query: []const u8,
682 ns: *const IpAddress,
683 ) void {
684 io.vtable.netSend(io.userdata, ns.*, &.{query}) catch |err| switch (err) {};
685 }
686706};
687707
688708test ResolvConf {
......@@ -702,7 +722,7 @@ test ResolvConf {
702722 .search_buffer = undefined,
703723 .search_len = 0,
704724 .ndots = 1,
705 .timeout = .seconds(5),
725 .timeout_seconds = 5,
706726 .attempts = 2,
707727 };
708728