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 {...@@ -641,8 +641,8 @@ pub const VTable = struct {
641 context_alignment: std.mem.Alignment,641 context_alignment: std.mem.Alignment,
642 start: *const fn (context: *const anyopaque) void,642 start: *const fn (context: *const anyopaque) void,
643 ) void,643 ) void,
644 groupWait: *const fn (?*anyopaque, *Group) void,644 groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
645 groupCancel: *const fn (?*anyopaque, *Group) void,645 groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
646646
647 /// Blocks until one of the futures from the list has a result ready, such647 /// Blocks until one of the futures from the list has a result ready, such
648 /// that awaiting it will not block. Returns that index.648 /// that awaiting it will not block. Returns that index.
...@@ -665,14 +665,14 @@ pub const VTable = struct {...@@ -665,14 +665,14 @@ pub const VTable = struct {
665 fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,665 fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,
666 fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,666 fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,
667667
668 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,668 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) NowError!Timestamp,
669 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,669 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, timeout: Timeout) SleepError!void,
670670
671 listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,671 listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
672 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream,672 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream,
673 ipBind: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,673 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,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, address: net.IpAddress, buffer: []u8) net.Socket.ReceiveError!void,675 netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, buffer: []u8, timeout: Timeout) net.Socket.ReceiveTimeoutError!net.ReceivedMessage,
676 netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,676 netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,
677 netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,677 netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
678 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,678 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
...@@ -710,6 +710,15 @@ pub const Timestamp = enum(i96) {...@@ -710,6 +710,15 @@ pub const Timestamp = enum(i96) {
710 pub fn addDuration(from: Timestamp, duration: Duration) Timestamp {710 pub fn addDuration(from: Timestamp, duration: Duration) Timestamp {
711 return @enumFromInt(@intFromEnum(from) + duration.nanoseconds);711 return @enumFromInt(@intFromEnum(from) + duration.nanoseconds);
712 }712 }
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 }
713};722};
714pub const Duration = struct {723pub const Duration = struct {
715 nanoseconds: i96,724 nanoseconds: i96,
...@@ -722,11 +731,14 @@ pub const Duration = struct {...@@ -722,11 +731,14 @@ pub const Duration = struct {
722 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_s };731 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_s };
723 }732 }
724};733};
725pub const Deadline = union(enum) {734pub const Timeout = union(enum) {
735 none,
726 duration: Duration,736 duration: Duration,
727 timestamp: Timestamp,737 deadline: Timestamp,
738
739 pub const Error = error{Timeout};
728};740};
729pub const ClockGetTimeError = std.posix.ClockGetTimeError || Cancelable;741pub const NowError = std.posix.ClockGetTimeError || Cancelable;
730pub const SleepError = error{ UnsupportedClock, Unexpected, Canceled };742pub const SleepError = error{ UnsupportedClock, Unexpected, Canceled };
731743
732pub const AnyFuture = opaque {};744pub const AnyFuture = opaque {};
...@@ -768,8 +780,10 @@ pub const Group = struct {...@@ -768,8 +780,10 @@ pub const Group = struct {
768 ///780 ///
769 /// `function` *may* be called immediately, before `async` returns.781 /// `function` *may* be called immediately, before `async` returns.
770 ///782 ///
771 /// After this is called, `wait` must be called before the group is783 /// After this is called, `wait` or `cancel` must be called before the
772 /// deinitialized.784 /// group is deinitialized.
785 ///
786 /// Threadsafe.
773 ///787 ///
774 /// See also:788 /// See also:
775 /// * `Io.async`789 /// * `Io.async`
...@@ -789,7 +803,9 @@ pub const Group = struct {...@@ -789,7 +803,9 @@ pub const Group = struct {
789 ///803 ///
790 /// Idempotent. Not threadsafe.804 /// Idempotent. Not threadsafe.
791 pub fn wait(g: *Group, io: Io) void {805 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);
793 }809 }
794810
795 /// Equivalent to `wait` but requests cancellation on all tasks owned by811 /// Equivalent to `wait` but requests cancellation on all tasks owned by
...@@ -797,9 +813,9 @@ pub const Group = struct {...@@ -797,9 +813,9 @@ pub const Group = struct {
797 ///813 ///
798 /// Idempotent. Not threadsafe.814 /// Idempotent. Not threadsafe.
799 pub fn cancel(g: *Group, io: Io) void {815 pub fn cancel(g: *Group, io: Io) void {
800 if (g.token == null) return;816 const token = g.token orelse return;
801 io.vtable.groupCancel(io.userdata, g);817 g.token = null;
802 assert(g.token == null);818 io.vtable.groupCancel(io.userdata, g, token);
803 }819 }
804};820};
805821
...@@ -1215,12 +1231,12 @@ pub fn cancelRequested(io: Io) bool {...@@ -1215,12 +1231,12 @@ pub fn cancelRequested(io: Io) bool {
1215 return io.vtable.cancelRequested(io.userdata);1231 return io.vtable.cancelRequested(io.userdata);
1216}1232}
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 {
1219 return io.vtable.now(io.userdata, clockid);1235 return io.vtable.now(io.userdata, clockid);
1220}1236}
12211237
1222pub fn sleep(io: Io, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void {1238pub fn sleep(io: Io, clockid: std.posix.clockid_t, timeout: Timeout) SleepError!void {
1223 return io.vtable.sleep(io.userdata, clockid, deadline);1239 return io.vtable.sleep(io.userdata, clockid, timeout);
1224}1240}
12251241
1226pub fn sleepDuration(io: Io, duration: Duration) SleepError!void {1242pub fn sleepDuration(io: Io, duration: Duration) SleepError!void {
lib/std/Io/Threaded.zig+58-27
...@@ -463,16 +463,19 @@ fn groupAsync(...@@ -463,16 +463,19 @@ fn groupAsync(
463 },463 },
464 .pool = pool,464 .pool = pool,
465 .group = group,465 .group = group,
466 .node = .{ .next = @ptrCast(@alignCast(group.token)) },466 .node = undefined,
467 .func = start,467 .func = start,
468 .context_alignment = context_alignment,468 .context_alignment = context_alignment,
469 .context_len = context.len,469 .context_len = context.len,
470 };470 };
471 group.token = &gc.node;
472 @memcpy(gc.contextPointer()[0..context.len], context);471 @memcpy(gc.contextPointer()[0..context.len], context);
473472
474 pool.mutex.lock();473 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
476 const thread_capacity = cpu_count - 1 + pool.concurrent_count;479 const thread_capacity = cpu_count - 1 + pool.concurrent_count;
477480
478 pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch {481 pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch {
...@@ -493,6 +496,8 @@ fn groupAsync(...@@ -493,6 +496,8 @@ fn groupAsync(
493 pool.threads.appendAssumeCapacity(thread);496 pool.threads.appendAssumeCapacity(thread);
494 }497 }
495498
499 // This needs to be done before unlocking the mutex to avoid a race with
500 // the associated task finishing.
496 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);501 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
497 std.Thread.WaitGroup.startStateless(group_state);502 std.Thread.WaitGroup.startStateless(group_state);
498503
...@@ -500,21 +505,16 @@ fn groupAsync(...@@ -500,21 +505,16 @@ fn groupAsync(
500 pool.cond.signal();505 pool.cond.signal();
501}506}
502507
503fn groupWait(userdata: ?*anyopaque, group: *Io.Group) void {508fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
504 if (builtin.single_threaded) return;
505 const pool: *Pool = @ptrCast(@alignCast(userdata));509 const pool: *Pool = @ptrCast(@alignCast(userdata));
506 _ = pool;510 _ = pool;
511
512 if (builtin.single_threaded) return;
513
507 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);514 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
508 const reset_event: *ResetEvent = @ptrCast(&group.context);515 const reset_event: *ResetEvent = @ptrCast(&group.context);
509 std.Thread.WaitGroup.waitStateless(group_state, reset_event);516 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;
518 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));518 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
519 while (true) {519 while (true) {
520 const gc: *GroupClosure = @fieldParentPtr("node", node);520 const gc: *GroupClosure = @fieldParentPtr("node", node);
...@@ -523,6 +523,36 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void {...@@ -523,6 +523,36 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void {
523 }523 }
524}524}
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
526fn await(556fn await(
527 userdata: ?*anyopaque,557 userdata: ?*anyopaque,
528 any_future: *Io.AnyFuture,558 any_future: *Io.AnyFuture,
...@@ -774,7 +804,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File...@@ -774,7 +804,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
774 .SUCCESS => return nread,804 .SUCCESS => return nread,
775 .INTR => unreachable,805 .INTR => unreachable,
776 .INVAL => unreachable,806 .INVAL => unreachable,
777 .FAULT => unreachable,807 .FAULT => |err| return errnoBug(err),
778 .AGAIN => unreachable, // currently not support in WASI808 .AGAIN => unreachable, // currently not support in WASI
779 .BADF => return error.NotOpenForReading, // can be a race condition809 .BADF => return error.NotOpenForReading, // can be a race condition
780 .IO => return error.InputOutput,810 .IO => return error.InputOutput,
...@@ -796,7 +826,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File...@@ -796,7 +826,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
796 .SUCCESS => return @intCast(rc),826 .SUCCESS => return @intCast(rc),
797 .INTR => continue,827 .INTR => continue,
798 .INVAL => unreachable,828 .INVAL => unreachable,
799 .FAULT => unreachable,829 .FAULT => |err| return errnoBug(err),
800 .SRCH => return error.ProcessNotFound,830 .SRCH => return error.ProcessNotFound,
801 .AGAIN => return error.WouldBlock,831 .AGAIN => return error.WouldBlock,
802 .BADF => return error.NotOpenForReading, // can be a race condition832 .BADF => return error.NotOpenForReading, // can be a race condition
...@@ -896,7 +926,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset...@@ -896,7 +926,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
896 .SUCCESS => return nread,926 .SUCCESS => return nread,
897 .INTR => unreachable,927 .INTR => unreachable,
898 .INVAL => unreachable,928 .INVAL => unreachable,
899 .FAULT => unreachable,929 .FAULT => |err| return errnoBug(err),
900 .AGAIN => unreachable,930 .AGAIN => unreachable,
901 .BADF => return error.NotOpenForReading, // can be a race condition931 .BADF => return error.NotOpenForReading, // can be a race condition
902 .IO => return error.InputOutput,932 .IO => return error.InputOutput,
...@@ -922,7 +952,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset...@@ -922,7 +952,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
922 .SUCCESS => return @bitCast(rc),952 .SUCCESS => return @bitCast(rc),
923 .INTR => continue,953 .INTR => continue,
924 .INVAL => unreachable,954 .INVAL => unreachable,
925 .FAULT => unreachable,955 .FAULT => |err| return errnoBug(err),
926 .SRCH => return error.ProcessNotFound,956 .SRCH => return error.ProcessNotFound,
927 .AGAIN => return error.WouldBlock,957 .AGAIN => return error.WouldBlock,
928 .BADF => return error.NotOpenForReading, // can be a race condition958 .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...@@ -969,18 +999,19 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posi
969 };999 };
970}1000}
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 {
973 const pool: *Pool = @ptrCast(@alignCast(userdata));1003 const pool: *Pool = @ptrCast(@alignCast(userdata));
974 try pool.checkCancel();1004 try pool.checkCancel();
975 const timespec = try posix.clock_gettime(clockid);1005 const timespec = try posix.clock_gettime(clockid);
976 return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);1006 return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);
977}1007}
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 {
980 const pool: *Pool = @ptrCast(@alignCast(userdata));1010 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),
982 .duration => |duration| duration.nanoseconds,1013 .duration => |duration| duration.nanoseconds,
983 .timestamp => |timestamp| @intFromEnum(timestamp),1014 .deadline => |deadline| @intFromEnum(deadline),
984 };1015 };
985 var timespec: posix.timespec = .{1016 var timespec: posix.timespec = .{
986 .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)),1017 .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)...@@ -988,12 +1019,12 @@ fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline)
988 };1019 };
989 while (true) {1020 while (true) {
990 try pool.checkCancel();1021 try pool.checkCancel();
991 switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clockid, .{ .ABSTIME = switch (deadline) {1022 switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clockid, .{ .ABSTIME = switch (timeout) {
992 .duration => false,1023 .none, .duration => false,
993 .timestamp => true,1024 .deadline => true,
994 } }, &timespec, &timespec))) {1025 } }, &timespec, &timespec))) {
995 .SUCCESS => return,1026 .SUCCESS => return,
996 .FAULT => unreachable,1027 .FAULT => |err| return errnoBug(err),
997 .INTR => {},1028 .INTR => {},
998 .INVAL => return error.UnsupportedClock,1029 .INVAL => return error.UnsupportedClock,
999 else => |err| return posix.unexpectedErrno(err),1030 else => |err| return posix.unexpectedErrno(err),
...@@ -1278,7 +1309,7 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n...@@ -1278,7 +1309,7 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n
1278fn netSend(1309fn netSend(
1279 userdata: ?*anyopaque,1310 userdata: ?*anyopaque,
1280 handle: Io.net.Socket.Handle,1311 handle: Io.net.Socket.Handle,
1281 address: Io.net.IpAddress,1312 address: *const Io.net.IpAddress,
1282 data: []const u8,1313 data: []const u8,
1283) Io.net.Socket.SendError!void {1314) Io.net.Socket.SendError!void {
1284 const pool: *Pool = @ptrCast(@alignCast(userdata));1315 const pool: *Pool = @ptrCast(@alignCast(userdata));
...@@ -1293,15 +1324,15 @@ fn netSend(...@@ -1293,15 +1324,15 @@ fn netSend(
1293fn netReceive(1324fn netReceive(
1294 userdata: ?*anyopaque,1325 userdata: ?*anyopaque,
1295 handle: Io.net.Socket.Handle,1326 handle: Io.net.Socket.Handle,
1296 address: Io.net.IpAddress,
1297 buffer: []u8,1327 buffer: []u8,
1298) Io.net.Socket.ReceiveError!void {1328 timeout: Io.Timeout,
1329) Io.net.Socket.ReceiveTimeoutError!Io.net.ReceivedMessage {
1299 const pool: *Pool = @ptrCast(@alignCast(userdata));1330 const pool: *Pool = @ptrCast(@alignCast(userdata));
1300 try pool.checkCancel();1331 try pool.checkCancel();
13011332
1302 _ = handle;1333 _ = handle;
1303 _ = address;
1304 _ = buffer;1334 _ = buffer;
1335 _ = timeout;
1305 @panic("TODO");1336 @panic("TODO");
1306}1337}
13071338
lib/std/Io/net.zig+32-10
...@@ -134,13 +134,13 @@ pub const IpAddress = union(enum) {...@@ -134,13 +134,13 @@ pub const IpAddress = union(enum) {
134 }134 }
135 }135 }
136136
137 pub fn eql(a: IpAddress, b: IpAddress) bool {137 pub fn eql(a: *const IpAddress, b: *const IpAddress) bool {
138 return switch (a) {138 return switch (a.*) {
139 .ip4 => |a_ip4| switch (b) {139 .ip4 => |a_ip4| switch (b.*) {
140 .ip4 => |b_ip4| a_ip4.eql(b_ip4),140 .ip4 => |b_ip4| a_ip4.eql(b_ip4),
141 else => false,141 else => false,
142 },142 },
143 .ip6 => |a_ip6| switch (b) {143 .ip6 => |a_ip6| switch (b.*) {
144 .ip6 => |b_ip6| a_ip6.eql(b_ip6),144 .ip6 => |b_ip6| a_ip6.eql(b_ip6),
145 else => false,145 else => false,
146 },146 },
...@@ -695,6 +695,11 @@ pub const Ip6Address = struct {...@@ -695,6 +695,11 @@ pub const Ip6Address = struct {
695 };695 };
696};696};
697697
698pub const ReceivedMessage = struct {
699 from: IpAddress,
700 len: usize,
701};
702
698pub const Interface = struct {703pub const Interface = struct {
699 /// Value 0 indicates `none`.704 /// Value 0 indicates `none`.
700 index: u32,705 index: u32,
...@@ -816,14 +821,31 @@ pub const Socket = struct {...@@ -816,14 +821,31 @@ pub const Socket = struct {
816 return io.vtable.netSend(io.userdata, s.handle, dest, data);821 return io.vtable.netSend(io.userdata, s.handle, dest, data);
817 }822 }
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.
822 ///827 ///
823 /// Returned slice has same pointer as `buffer` with possibly shorter length.828 /// See also:
824 pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError![]u8 {829 /// * `receiveTimeout`
825 const n = try io.vtable.netReceive(io.userdata, s.handle, source, buffer);830 pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError!ReceivedMessage {
826 return buffer[0..n];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);
827 }849 }
828};850};
829851
lib/std/Io/net/HostName.zig+113-93
...@@ -46,15 +46,13 @@ pub const LookupOptions = struct {...@@ -46,15 +46,13 @@ pub const LookupOptions = struct {
46 family: ?IpAddress.Family = null,46 family: ?IpAddress.Family = null,
47};47};
4848
49pub const LookupError = Io.Cancelable || Io.File.OpenError || Io.File.Reader.Error || error{49pub const LookupError = error{
50 UnknownHostName,50 UnknownHostName,
51 ResolvConfParseFailed,51 ResolvConfParseFailed,
52 // TODO remove from error set; retry a few times then report a different error
53 TemporaryNameServerFailure,
54 InvalidDnsARecord,52 InvalidDnsARecord,
55 InvalidDnsAAAARecord,53 InvalidDnsAAAARecord,
56 NameServerFailure,54 NameServerFailure,
57};55} || Io.NowError || IpAddress.BindError || Io.File.OpenError || Io.File.Reader.Error || Io.Cancelable;
5856
59pub const LookupResult = struct {57pub const LookupResult = struct {
60 /// How many `LookupOptions.addresses_buffer` elements are populated.58 /// How many `LookupOptions.addresses_buffer` elements are populated.
...@@ -185,7 +183,7 @@ fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult...@@ -185,7 +183,7 @@ fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult
185 return result;183 return result;
186}184}
187185
188fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {186fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
189 const rc = ResolvConf.init(io) catch return error.ResolvConfParseFailed;187 const rc = ResolvConf.init(io) catch return error.ResolvConfParseFailed;
190188
191 // Count dots, suppress search when >=ndots or name ends in189 // Count dots, suppress search when >=ndots or name ends in
...@@ -218,19 +216,17 @@ fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) !LookupR...@@ -218,19 +216,17 @@ fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) !LookupR
218 return lookupDns(io, lookup_canon_name, &rc, options);216 return lookupDns(io, lookup_canon_name, &rc, options);
219}217}
220218
221const DnsReply = struct {219fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) LookupError!LookupResult {
222 buf: [512]u8,
223 len: usize,
224};
225
226fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) !LookupResult {
227 const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{220 const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{
228 .{ .af = .ip6, .rr = std.posix.RR.A },221 .{ .af = .ip6, .rr = std.posix.RR.A },
229 .{ .af = .ip4, .rr = std.posix.RR.AAAA },222 .{ .af = .ip4, .rr = std.posix.RR.AAAA },
230 };223 };
231 var query_buffers: [2][280]u8 = undefined;224 var query_buffers: [2][280]u8 = undefined;
225 var answer_buffers: [2][512]u8 = undefined;
232 var queries_buffer: [2][]const u8 = undefined;226 var queries_buffer: [2][]const u8 = undefined;
227 var answers_buffer: [2][]const u8 = undefined;
233 var nq: usize = 0;228 var nq: usize = 0;
229 var next_answer_buffer: usize = 0;
234230
235 for (family_records) |fr| {231 for (family_records) |fr| {
236 if (options.family != fr.af) {232 if (options.family != fr.af) {
...@@ -241,41 +237,123 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio...@@ -241,41 +237,123 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
241 }237 }
242 }238 }
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();
244 const queries = queries_buffer[0..nq];263 const queries = queries_buffer[0..nq];
245 var replies_buffer: [2]DnsReply = undefined;264 const answers = answers_buffer[0..queries.len];
246 var replies: Io.Queue(DnsReply) = .init(&replies_buffer);265 for (answers) |*answer| answer.len = 0;
247 try rc.sendMessage(io, queries, &replies);266
248267 var now_ts = try io.now(.MONOTONIC);
249 for (replies) |reply| {268 const final_ts = now_ts.addDuration(.seconds(rc.timeout_seconds));
250 if (reply.len < 4 or (reply[3] & 15) == 2) return error.TemporaryNameServerFailure;269 const attempt_duration: Io.Duration = .{
251 if ((reply[3] & 15) == 3) return .empty;270 .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts,
252 if ((reply[3] & 15) != 0) return error.UnknownHostName;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;
253 }331 }
254332
255 var addresses_len: usize = 0;333 var addresses_len: usize = 0;
256 var canonical_name: ?HostName = null;334 var canonical_name: ?HostName = null;
257335
258 for (replies) |reply| {336 for (answers) |answer| {
259 var it = DnsResponse.init(reply) catch {337 var it = DnsResponse.init(answer) catch {
260 // TODO accept a diagnostics struct and append warnings338 // TODO accept a diagnostics struct and append warnings
261 continue;339 continue;
262 };340 };
263 while (it.next() catch {341 while (it.next() catch {
264 // TODO accept a diagnostics struct and append warnings342 // TODO accept a diagnostics struct and append warnings
265 continue;343 continue;
266 }) |answer| switch (answer.rr) {344 }) |record| switch (record.rr) {
267 std.posix.RR.A => {345 std.posix.RR.A => {
268 if (answer.data.len != 4) return error.InvalidDnsARecord;346 if (record.data.len != 4) return error.InvalidDnsARecord;
269 options.addresses_buffer[addresses_len] = .{ .ip4 = .{347 options.addresses_buffer[addresses_len] = .{ .ip4 = .{
270 .bytes = answer.data[0..4].*,348 .bytes = record.data[0..4].*,
271 .port = options.port,349 .port = options.port,
272 } };350 } };
273 addresses_len += 1;351 addresses_len += 1;
274 },352 },
275 std.posix.RR.AAAA => {353 std.posix.RR.AAAA => {
276 if (answer.data.len != 16) return error.InvalidDnsAAAARecord;354 if (record.data.len != 16) return error.InvalidDnsAAAARecord;
277 options.addresses_buffer[addresses_len] = .{ .ip6 = .{355 options.addresses_buffer[addresses_len] = .{ .ip6 = .{
278 .bytes = answer.data[0..16].*,356 .bytes = record.data[0..16].*,
279 .port = options.port,357 .port = options.port,
280 } };358 } };
281 addresses_len += 1;359 addresses_len += 1;
...@@ -285,7 +363,7 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio...@@ -285,7 +363,7 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
285 @panic("TODO");363 @panic("TODO");
286 //var tmp: [256]u8 = undefined;364 //var tmp: [256]u8 = undefined;
287 //// Returns len of compressed name. strlen to get canon name.365 //// 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);
289 //const canon_name = mem.sliceTo(&tmp, 0);367 //const canon_name = mem.sliceTo(&tmp, 0);
290 //if (isValidHostName(canon_name)) {368 //if (isValidHostName(canon_name)) {
291 // ctx.canon.items.len = 0;369 // ctx.canon.items.len = 0;
...@@ -304,6 +382,10 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio...@@ -304,6 +382,10 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
304 return error.NameServerFailure;382 return error.NameServerFailure;
305}383}
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
307fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {389fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {
308 const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) {390 const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) {
309 error.FileNotFound,391 error.FileNotFound,
...@@ -523,7 +605,7 @@ pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream...@@ -523,7 +605,7 @@ pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream
523pub const ResolvConf = struct {605pub const ResolvConf = struct {
524 attempts: u32,606 attempts: u32,
525 ndots: u32,607 ndots: u32,
526 timeout: Io.Duration,608 timeout_seconds: u32,
527 nameservers_buffer: [max_nameservers]IpAddress,609 nameservers_buffer: [max_nameservers]IpAddress,
528 nameservers_len: usize,610 nameservers_len: usize,
529 search_buffer: [max_len]u8,611 search_buffer: [max_len]u8,
...@@ -539,7 +621,7 @@ pub const ResolvConf = struct {...@@ -539,7 +621,7 @@ pub const ResolvConf = struct {
539 .search_buffer = undefined,621 .search_buffer = undefined,
540 .search_len = 0,622 .search_len = 0,
541 .ndots = 1,623 .ndots = 1,
542 .timeout = .seconds(5),624 .timeout_seconds = 5,
543 .attempts = 2,625 .attempts = 2,
544 };626 };
545627
...@@ -589,7 +671,7 @@ pub const ResolvConf = struct {...@@ -589,7 +671,7 @@ pub const ResolvConf = struct {
589 switch (std.meta.stringToEnum(Option, name) orelse continue) {671 switch (std.meta.stringToEnum(Option, name) orelse continue) {
590 .ndots => rc.ndots = @min(value, 15),672 .ndots => rc.ndots = @min(value, 15),
591 .attempts => rc.attempts = @min(value, 10),673 .attempts => rc.attempts = @min(value, 10),
592 .timeout => rc.timeout = .seconds(@min(value, 60)),674 .timeout => rc.timeout_seconds = @min(value, 60),
593 }675 }
594 },676 },
595 .nameserver => {677 .nameserver => {
...@@ -621,68 +703,6 @@ pub const ResolvConf = struct {...@@ -621,68 +703,6 @@ pub const ResolvConf = struct {
621 fn nameservers(rc: *const ResolvConf) []const IpAddress {703 fn nameservers(rc: *const ResolvConf) []const IpAddress {
622 return rc.nameservers_buffer[0..rc.nameservers_len];704 return rc.nameservers_buffer[0..rc.nameservers_len];
623 }705 }
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 }
686};706};
687707
688test ResolvConf {708test ResolvConf {
...@@ -702,7 +722,7 @@ test ResolvConf {...@@ -702,7 +722,7 @@ test ResolvConf {
702 .search_buffer = undefined,722 .search_buffer = undefined,
703 .search_len = 0,723 .search_len = 0,
704 .ndots = 1,724 .ndots = 1,
705 .timeout = .seconds(5),725 .timeout_seconds = 5,
706 .attempts = 2,726 .attempts = 2,
707 };727 };
708728