authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-14 20:59:16-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:49-07:00
log35ce907c06d5758adab276927ad8dbe730d6130d
treec7d3839b953c4c0a7e5beee7090713d28fff04b3
parent1382e4122603fd2b57e4feb0eff76ba2d73a913a

std.Io.net.HostName: move lookup to the interface

Unfortunately this can't be implemented "above the vtable" because various operating systems don't provide low level DNS resolution primitives such as just putting the list of nameservers in a file. Without libc on Linux it works great though! Anyway this also changes the API to be based on Io.Queue. By using a large enough buffer, reusable code can be written that does not require concurrent, yet takes advantage of responding to DNS queries as they come in. I sketched out a new implementation of `HostName.connect` to demonstrate this, but it will require an additional API (`Io.Select`) to be implemented in a future commit. This commit also introduces "uncancelable" variants for mutex locking, waiting on a condition, and putting items into a queue.

9 files changed, 778 insertions(+), 617 deletions(-)

BRANCH_TODO+3
......@@ -1,3 +1,4 @@
1* Threaded: rename Pool to Threaded
12* Threaded: finish linux impl (all tests passing)
23* Threaded: finish macos impl
34* Threaded: finish windows impl
......@@ -14,4 +15,6 @@
1415* move fs.File.Writer to Io
1516* add non-blocking flag to net and fs operations, handle EAGAIN
1617* finish moving std.fs to Io
18* migrate child process into std.Io
19* eliminate std.Io.poll (it should be replaced by "select" functionality)
1720* finish moving all of std.posix into Threaded
lib/std/Io.zig+65-44
......@@ -649,9 +649,11 @@ pub const VTable = struct {
649649 select: *const fn (?*anyopaque, futures: []const *AnyFuture) usize,
650650
651651 mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void,
652 mutexLockUncancelable: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,
652653 mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,
653654
654655 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
656 conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void,
655657 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
656658
657659 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
......@@ -686,6 +688,7 @@ pub const VTable = struct {
686688 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
687689 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
688690 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
691 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) void,
689692};
690693
691694pub const Cancelable = error{
......@@ -1030,7 +1033,7 @@ pub const Group = struct {
10301033 }
10311034};
10321035
1033pub const Mutex = if (true) struct {
1036pub const Mutex = struct {
10341037 state: State,
10351038
10361039 pub const State = enum(usize) {
......@@ -1073,54 +1076,32 @@ pub const Mutex = if (true) struct {
10731076 return io.vtable.mutexLock(io.userdata, prev_state, mutex);
10741077 }
10751078
1079 /// Same as `lock` but cannot be canceled.
1080 pub fn lockUncancelable(mutex: *Mutex, io: std.Io) void {
1081 const prev_state: State = @enumFromInt(@atomicRmw(
1082 usize,
1083 @as(*usize, @ptrCast(&mutex.state)),
1084 .And,
1085 ~@intFromEnum(State.unlocked),
1086 .acquire,
1087 ));
1088 if (prev_state.isUnlocked()) {
1089 @branchHint(.likely);
1090 return;
1091 }
1092 return io.vtable.mutexLockUncancelable(io.userdata, prev_state, mutex);
1093 }
1094
10761095 pub fn unlock(mutex: *Mutex, io: std.Io) void {
10771096 const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse {
10781097 @branchHint(.likely);
10791098 return;
10801099 };
1081 std.debug.assert(prev_state != .unlocked); // mutex not locked
1100 assert(prev_state != .unlocked); // mutex not locked
10821101 return io.vtable.mutexUnlock(io.userdata, prev_state, mutex);
10831102 }
1084} else struct {
1085 state: std.atomic.Value(u32),
1086
1087 pub const State = void;
1088
1089 pub const init: Mutex = .{ .state = .init(unlocked) };
1090
1091 pub const unlocked: u32 = 0b00;
1092 pub const locked: u32 = 0b01;
1093 pub const contended: u32 = 0b11; // must contain the `locked` bit for x86 optimization below
1094
1095 pub fn tryLock(m: *Mutex) bool {
1096 // On x86, use `lock bts` instead of `lock cmpxchg` as:
1097 // - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048
1098 // - `lock bts` is smaller instruction-wise which makes it better for inlining
1099 if (builtin.target.cpu.arch.isX86()) {
1100 const locked_bit = @ctz(locked);
1101 return m.state.bitSet(locked_bit, .acquire) == 0;
1102 }
1103
1104 // Acquire barrier ensures grabbing the lock happens before the critical section
1105 // and that the previous lock holder's critical section happens before we grab the lock.
1106 return m.state.cmpxchgWeak(unlocked, locked, .acquire, .monotonic) == null;
1107 }
1108
1109 /// Avoids the vtable for uncontended locks.
1110 pub fn lock(m: *Mutex, io: Io) Cancelable!void {
1111 if (!m.tryLock()) {
1112 @branchHint(.unlikely);
1113 try io.vtable.mutexLock(io.userdata, {}, m);
1114 }
1115 }
1116
1117 pub fn unlock(m: *Mutex, io: Io) void {
1118 io.vtable.mutexUnlock(io.userdata, {}, m);
1119 }
11201103};
11211104
1122/// Supports exactly 1 waiter. More than 1 simultaneous wait on the same
1123/// condition is illegal.
11241105pub const Condition = struct {
11251106 state: u64 = 0,
11261107
......@@ -1128,6 +1109,10 @@ pub const Condition = struct {
11281109 return io.vtable.conditionWait(io.userdata, cond, mutex);
11291110 }
11301111
1112 pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void {
1113 return io.vtable.conditionWaitUncancelable(io.userdata, cond, mutex);
1114 }
1115
11311116 pub fn signal(cond: *Condition, io: Io) void {
11321117 io.vtable.conditionWake(io.userdata, cond, .one);
11331118 }
......@@ -1137,9 +1122,9 @@ pub const Condition = struct {
11371122 }
11381123
11391124 pub const Wake = enum {
1140 /// wake up only one thread
1125 /// Wake up only one thread.
11411126 one,
1142 /// wake up all thread
1127 /// Wake up all threads.
11431128 all,
11441129 };
11451130};
......@@ -1180,10 +1165,24 @@ pub const TypeErasedQueue = struct {
11801165
11811166 pub fn put(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) Cancelable!usize {
11821167 assert(elements.len >= min);
1183
1168 if (elements.len == 0) return 0;
11841169 try q.mutex.lock(io);
11851170 defer q.mutex.unlock(io);
1171 return putLocked(q, io, elements, min, false);
1172 }
1173
1174 /// Same as `put` but cannot be canceled.
1175 pub fn putUncancelable(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) usize {
1176 assert(elements.len >= min);
1177 if (elements.len == 0) return 0;
1178 q.mutex.lockUncancelable(io);
1179 defer q.mutex.unlock(io);
1180 return putLocked(q, io, elements, min, true) catch |err| switch (err) {
1181 error.Canceled => unreachable,
1182 };
1183 }
11861184
1185 fn putLocked(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize, uncancelable: bool) Cancelable!usize {
11871186 // Getters have first priority on the data, and only when the getters
11881187 // queue is empty do we start populating the buffer.
11891188
......@@ -1226,7 +1225,10 @@ pub const TypeErasedQueue = struct {
12261225
12271226 var pending: Put = .{ .remaining = remaining, .condition = .{}, .node = .{} };
12281227 q.putters.append(&pending.node);
1229 try pending.condition.wait(io, &q.mutex);
1228 if (uncancelable)
1229 pending.condition.waitUncancelable(io, &q.mutex)
1230 else
1231 try pending.condition.wait(io, &q.mutex);
12301232 remaining = pending.remaining;
12311233 }
12321234 }
......@@ -1347,6 +1349,16 @@ pub fn Queue(Elem: type) type {
13471349 return @divExact(try q.type_erased.put(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem));
13481350 }
13491351
1352 /// Same as `put` but blocks until all elements have been added to the queue.
1353 pub fn putAll(q: *@This(), io: Io, elements: []const Elem) Cancelable!void {
1354 assert(try q.put(io, elements, elements.len) == elements.len);
1355 }
1356
1357 /// Same as `put` but cannot be interrupted.
1358 pub fn putUncancelable(q: *@This(), io: Io, elements: []const Elem, min: usize) usize {
1359 return @divExact(q.type_erased.putUncancelable(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem));
1360 }
1361
13501362 /// Receives elements from the beginning of the queue. The function
13511363 /// returns when at least `min` elements have been populated inside
13521364 /// `buffer`.
......@@ -1362,11 +1374,20 @@ pub fn Queue(Elem: type) type {
13621374 assert(try q.put(io, &.{item}, 1) == 1);
13631375 }
13641376
1377 pub fn putOneUncancelable(q: *@This(), io: Io, item: Elem) void {
1378 assert(q.putUncancelable(io, &.{item}, 1) == 1);
1379 }
1380
13651381 pub fn getOne(q: *@This(), io: Io) Cancelable!Elem {
13661382 var buf: [1]Elem = undefined;
13671383 assert(try q.get(io, &buf, 1) == 1);
13681384 return buf[0];
13691385 }
1386
1387 /// Returns buffer length in `Elem` units.
1388 pub fn capacity(q: *const @This()) usize {
1389 return @divExact(q.type_erased.buffer.len, @sizeOf(Elem));
1390 }
13701391 };
13711392}
13721393
lib/std/Io/EventLoop.zig+1-1
......@@ -1410,7 +1410,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o
14101410 .NOMEM => return error.SystemResources,
14111411 .NOTCONN => return error.SocketUnconnected,
14121412 .CONNRESET => return error.ConnectionResetByPeer,
1413 .TIMEDOUT => return error.ConnectionTimedOut,
1413 .TIMEDOUT => return error.Timeout,
14141414 .NXIO => return error.Unseekable,
14151415 .SPIPE => return error.Unseekable,
14161416 .OVERFLOW => return error.Unseekable,
lib/std/Io/File.zig+1-1
......@@ -153,7 +153,7 @@ pub const ReadStreamingError = error{
153153 IsDir,
154154 BrokenPipe,
155155 ConnectionResetByPeer,
156 ConnectionTimedOut,
156 Timeout,
157157 NotOpenForReading,
158158 SocketUnconnected,
159159 /// This error occurs when no global event loop is configured,
lib/std/Io/Threaded.zig+642-48
......@@ -8,6 +8,8 @@ const windows = std.os.windows;
88const std = @import("../std.zig");
99const Io = std.Io;
1010const net = std.Io.net;
11const HostName = std.Io.net.HostName;
12const IpAddress = std.Io.net.IpAddress;
1113const Allocator = std.mem.Allocator;
1214const assert = std.debug.assert;
1315const posix = std.posix;
......@@ -156,9 +158,11 @@ pub fn io(pool: *Pool) Io {
156158 .groupCancel = groupCancel,
157159
158160 .mutexLock = mutexLock,
161 .mutexLockUncancelable = mutexLockUncancelable,
159162 .mutexUnlock = mutexUnlock,
160163
161164 .conditionWait = conditionWait,
165 .conditionWaitUncancelable = conditionWaitUncancelable,
162166 .conditionWake = conditionWake,
163167
164168 .dirMake = switch (builtin.os.tag) {
......@@ -235,6 +239,7 @@ pub fn io(pool: *Pool) Io {
235239 .netReceive = netReceive,
236240 .netInterfaceNameResolve = netInterfaceNameResolve,
237241 .netInterfaceName = netInterfaceName,
242 .netLookup = netLookup,
238243 },
239244 };
240245}
......@@ -653,26 +658,63 @@ fn checkCancel(pool: *Pool) error{Canceled}!void {
653658 if (cancelRequested(pool)) return error.Canceled;
654659}
655660
656fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void {
661fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void {
662 const pool: *Pool = @ptrCast(@alignCast(userdata));
663 if (prev_state == .contended) {
664 try pool.checkCancel();
665 futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
666 }
667 while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) {
668 try pool.checkCancel();
669 futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
670 }
671}
672
673fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
657674 _ = userdata;
658675 if (prev_state == .contended) {
659 std.Thread.Futex.wait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
676 futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
660677 }
661 while (@atomicRmw(
662 Io.Mutex.State,
663 &mutex.state,
664 .Xchg,
665 .contended,
666 .acquire,
667 ) != .unlocked) {
668 std.Thread.Futex.wait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
678 while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) {
679 futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
669680 }
670681}
682
671683fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
672684 _ = userdata;
673685 _ = prev_state;
674686 if (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .unlocked, .release) == .contended) {
675 std.Thread.Futex.wake(@ptrCast(&mutex.state), 1);
687 futexWake(@ptrCast(&mutex.state), 1);
688 }
689}
690
691fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void {
692 const pool: *Pool = @ptrCast(@alignCast(userdata));
693 const pool_io = pool.io();
694 comptime assert(@TypeOf(cond.state) == u64);
695 const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
696 const cond_state = &ints[0];
697 const cond_epoch = &ints[1];
698 const one_waiter = 1;
699 const waiter_mask = 0xffff;
700 const one_signal = 1 << 16;
701 const signal_mask = 0xffff << 16;
702 var epoch = cond_epoch.load(.acquire);
703 var state = cond_state.fetchAdd(one_waiter, .monotonic);
704 assert(state & waiter_mask != waiter_mask);
705 state += one_waiter;
706
707 mutex.unlock(pool_io);
708 defer mutex.lockUncancelable(pool_io);
709
710 while (true) {
711 futexWait(cond_epoch, epoch);
712 epoch = cond_epoch.load(.acquire);
713 state = cond_state.load(.monotonic);
714 while (state & signal_mask != 0) {
715 const new_state = state - one_waiter - one_signal;
716 state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return;
717 }
676718 }
677719}
678720
......@@ -702,20 +744,18 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
702744 state += one_waiter;
703745
704746 mutex.unlock(pool.io());
705 defer mutex.lock(pool.io()) catch @panic("TODO");
706
707 var futex_deadline = std.Thread.Futex.Deadline.init(null);
747 defer mutex.lockUncancelable(pool.io());
708748
709749 while (true) {
710 futex_deadline.wait(cond_epoch, epoch) catch |err| switch (err) {
711 error.Timeout => unreachable,
712 };
750 try pool.checkCancel();
751 futexWait(cond_epoch, epoch);
713752
714753 epoch = cond_epoch.load(.acquire);
715754 state = cond_state.load(.monotonic);
716755
717 // Try to wake up by consuming a signal and decremented the waiter we added previously.
718 // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return.
756 // Try to wake up by consuming a signal and decremented the waiter we
757 // added previously. Acquire barrier ensures code before the wake()
758 // which added the signal happens before we decrement it and return.
719759 while (state & signal_mask != 0) {
720760 const new_state = state - one_waiter - one_signal;
721761 state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return;
......@@ -740,8 +780,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
740780 const signals = (state & signal_mask) / one_signal;
741781
742782 // Reserves which waiters to wake up by incrementing the signals count.
743 // Therefore, the signals count is always less than or equal to the waiters count.
744 // We don't need to Futex.wake if there's nothing to wake up or if other wake() threads have reserved to wake up the current waiters.
783 // Therefore, the signals count is always less than or equal to the
784 // waiters count. We don't need to Futex.wake if there's nothing to
785 // wake up or if other wake() threads have reserved to wake up the
786 // current waiters.
745787 const wakeable = waiters - signals;
746788 if (wakeable == 0) {
747789 return;
......@@ -752,16 +794,23 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
752794 .all => wakeable,
753795 };
754796
755 // Reserve the amount of waiters to wake by incrementing the signals count.
756 // Release barrier ensures code before the wake() happens before the signal it posted and consumed by the wait() threads.
797 // Reserve the amount of waiters to wake by incrementing the signals
798 // count. Release barrier ensures code before the wake() happens before
799 // the signal it posted and consumed by the wait() threads.
757800 const new_state = state + (one_signal * to_wake);
758801 state = cond_state.cmpxchgWeak(state, new_state, .release, .monotonic) orelse {
759802 // Wake up the waiting threads we reserved above by changing the epoch value.
760 // NOTE: a waiting thread could miss a wake up if *exactly* ((1<<32)-1) wake()s happen between it observing the epoch and sleeping on it.
761 // This is very unlikely due to how many precise amount of Futex.wake() calls that would be between the waiting thread's potential preemption.
762803 //
763 // Release barrier ensures the signal being added to the state happens before the epoch is changed.
764 // If not, the waiting thread could potentially deadlock from missing both the state and epoch change:
804 // A waiting thread could miss a wake up if *exactly* ((1<<32)-1)
805 // wake()s happen between it observing the epoch and sleeping on
806 // it. This is very unlikely due to how many precise amount of
807 // Futex.wake() calls that would be between the waiting thread's
808 // potential preemption.
809 //
810 // Release barrier ensures the signal being added to the state
811 // happens before the epoch is changed. If not, the waiting thread
812 // could potentially deadlock from missing both the state and epoch
813 // change:
765814 //
766815 // - T2: UPDATE(&epoch, 1) (reordered before the state change)
767816 // - T1: e = LOAD(&epoch)
......@@ -769,7 +818,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
769818 // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch)
770819 // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change)
771820 _ = cond_epoch.fetchAdd(1, .release);
772 std.Thread.Futex.wake(cond_epoch, to_wake);
821 futexWake(cond_epoch, to_wake);
773822 return;
774823 };
775824 }
......@@ -1298,7 +1347,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
12981347 .NOMEM => return error.SystemResources,
12991348 .NOTCONN => return error.SocketUnconnected,
13001349 .CONNRESET => return error.ConnectionResetByPeer,
1301 .TIMEDOUT => return error.ConnectionTimedOut,
1350 .TIMEDOUT => return error.Timeout,
13021351 .NOTCAPABLE => return error.AccessDenied,
13031352 else => |err| return posix.unexpectedErrno(err),
13041353 }
......@@ -1321,7 +1370,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
13211370 .NOMEM => return error.SystemResources,
13221371 .NOTCONN => return error.SocketUnconnected,
13231372 .CONNRESET => return error.ConnectionResetByPeer,
1324 .TIMEDOUT => return error.ConnectionTimedOut,
1373 .TIMEDOUT => return error.Timeout,
13251374 else => |err| return posix.unexpectedErrno(err),
13261375 }
13271376 }
......@@ -1420,7 +1469,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
14201469 .NOMEM => return error.SystemResources,
14211470 .NOTCONN => return error.SocketUnconnected,
14221471 .CONNRESET => return error.ConnectionResetByPeer,
1423 .TIMEDOUT => return error.ConnectionTimedOut,
1472 .TIMEDOUT => return error.Timeout,
14241473 .NXIO => return error.Unseekable,
14251474 .SPIPE => return error.Unseekable,
14261475 .OVERFLOW => return error.Unseekable,
......@@ -1446,7 +1495,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
14461495 .NOMEM => return error.SystemResources,
14471496 .NOTCONN => return error.SocketUnconnected,
14481497 .CONNRESET => return error.ConnectionResetByPeer,
1449 .TIMEDOUT => return error.ConnectionTimedOut,
1498 .TIMEDOUT => return error.Timeout,
14501499 .NXIO => return error.Unseekable,
14511500 .SPIPE => return error.Unseekable,
14521501 .OVERFLOW => return error.Unseekable,
......@@ -1693,9 +1742,9 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
16931742
16941743fn netListenIpPosix(
16951744 userdata: ?*anyopaque,
1696 address: net.IpAddress,
1697 options: net.IpAddress.ListenOptions,
1698) net.IpAddress.ListenError!net.Server {
1745 address: IpAddress,
1746 options: IpAddress.ListenOptions,
1747) IpAddress.ListenError!net.Server {
16991748 const pool: *Pool = @ptrCast(@alignCast(userdata));
17001749 const family = posixAddressFamily(&address);
17011750 const socket_fd = try openSocketPosix(pool, family, .{
......@@ -1831,7 +1880,7 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka
18311880 .NETUNREACH => return error.NetworkUnreachable,
18321881 .NOTSOCK => |err| return errnoBug(err),
18331882 .PROTOTYPE => |err| return errnoBug(err),
1834 .TIMEDOUT => return error.ConnectionTimedOut,
1883 .TIMEDOUT => return error.Timeout,
18351884 .CONNABORTED => |err| return errnoBug(err),
18361885 .ACCES => return error.AccessDenied,
18371886 .PERM => |err| return errnoBug(err),
......@@ -1904,9 +1953,9 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio
19041953
19051954fn netConnectIpPosix(
19061955 userdata: ?*anyopaque,
1907 address: *const net.IpAddress,
1908 options: net.IpAddress.ConnectOptions,
1909) net.IpAddress.ConnectError!net.Stream {
1956 address: *const IpAddress,
1957 options: IpAddress.ConnectOptions,
1958) IpAddress.ConnectError!net.Stream {
19101959 if (options.timeout != .none) @panic("TODO");
19111960 const pool: *Pool = @ptrCast(@alignCast(userdata));
19121961 const family = posixAddressFamily(address);
......@@ -1941,9 +1990,9 @@ fn netConnectUnix(
19411990
19421991fn netBindIpPosix(
19431992 userdata: ?*anyopaque,
1944 address: *const net.IpAddress,
1945 options: net.IpAddress.BindOptions,
1946) net.IpAddress.BindError!net.Socket {
1993 address: *const IpAddress,
1994 options: IpAddress.BindOptions,
1995) IpAddress.BindError!net.Socket {
19471996 const pool: *Pool = @ptrCast(@alignCast(userdata));
19481997 const family = posixAddressFamily(address);
19491998 const socket_fd = try openSocketPosix(pool, family, options);
......@@ -1958,7 +2007,7 @@ fn netBindIpPosix(
19582007 };
19592008}
19602009
1961fn openSocketPosix(pool: *Pool, family: posix.sa_family_t, options: net.IpAddress.BindOptions) !posix.socket_t {
2010fn openSocketPosix(pool: *Pool, family: posix.sa_family_t, options: IpAddress.BindOptions) !posix.socket_t {
19622011 const mode = posixSocketMode(options.mode);
19632012 const protocol = posixProtocol(options.protocol);
19642013 const socket_fd = while (true) {
......@@ -2081,7 +2130,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
20812130 .NOMEM => return error.SystemResources,
20822131 .NOTCONN => return error.SocketUnconnected,
20832132 .CONNRESET => return error.ConnectionResetByPeer,
2084 .TIMEDOUT => return error.ConnectionTimedOut,
2133 .TIMEDOUT => return error.Timeout,
20852134 .NOTCAPABLE => return error.AccessDenied,
20862135 else => |err| return posix.unexpectedErrno(err),
20872136 }
......@@ -2102,7 +2151,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
21022151 .NOMEM => return error.SystemResources,
21032152 .NOTCONN => return error.SocketUnconnected,
21042153 .CONNRESET => return error.ConnectionResetByPeer,
2105 .TIMEDOUT => return error.ConnectionTimedOut,
2154 .TIMEDOUT => return error.Timeout,
21062155 .PIPE => return error.BrokenPipe,
21072156 .NETDOWN => return error.NetworkDown,
21082157 else => |err| return posix.unexpectedErrno(err),
......@@ -2563,6 +2612,118 @@ fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interfa
25632612 @panic("unimplemented");
25642613}
25652614
2615fn netLookup(
2616 userdata: ?*anyopaque,
2617 host_name: HostName,
2618 resolved: *Io.Queue(HostName.LookupResult),
2619 options: HostName.LookupOptions,
2620) void {
2621 const pool: *Pool = @ptrCast(@alignCast(userdata));
2622 const pool_io = pool.io();
2623 resolved.putOneUncancelable(pool_io, .{ .end = netLookupFallible(pool, host_name, resolved, options) });
2624}
2625
2626fn netLookupFallible(
2627 pool: *Pool,
2628 host_name: HostName,
2629 resolved: *Io.Queue(HostName.LookupResult),
2630 options: HostName.LookupOptions,
2631) !void {
2632 const pool_io = pool.io();
2633 const name = host_name.bytes;
2634 assert(name.len <= HostName.max_len);
2635
2636 if (is_windows) {
2637 // TODO use GetAddrInfoExW / GetAddrInfoExCancel
2638 @compileError("TODO");
2639 }
2640
2641 // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics.
2642 // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it.
2643
2644 if (builtin.target.isGnuLibC()) {
2645 // TODO use getaddrinfo_a / gai_cancel
2646 }
2647
2648 if (native_os == .linux) {
2649 if (options.family != .ip4) {
2650 if (IpAddress.parseIp6(name, options.port)) |addr| {
2651 try resolved.putAll(pool_io, &.{
2652 .{ .address = addr },
2653 .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) },
2654 });
2655 return;
2656 } else |_| {}
2657 }
2658
2659 if (options.family != .ip6) {
2660 if (IpAddress.parseIp4(name, options.port)) |addr| {
2661 try resolved.putAll(pool_io, &.{
2662 .{ .address = addr },
2663 .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) },
2664 });
2665 } else |_| {}
2666 }
2667
2668 lookupHosts(pool, host_name, resolved, options) catch |err| switch (err) {
2669 error.UnknownHostName => {},
2670 else => |e| return e,
2671 };
2672
2673 // RFC 6761 Section 6.3.3
2674 // Name resolution APIs and libraries SHOULD recognize
2675 // localhost names as special and SHOULD always return the IP
2676 // loopback address for address queries and negative responses
2677 // for all other query types.
2678
2679 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
2680 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
2681 if (std.mem.endsWith(u8, name, localhost) and
2682 (name.len == localhost.len or name[name.len - localhost.len] == '.'))
2683 {
2684 var results_buffer: [3]HostName.LookupResult = undefined;
2685 var results_index: usize = 0;
2686 if (options.family != .ip4) {
2687 results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } };
2688 results_index += 1;
2689 }
2690 if (options.family != .ip6) {
2691 results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } };
2692 results_index += 1;
2693 }
2694 const canon_name = "localhost";
2695 const canon_name_dest = options.canonical_name_buffer[0..canon_name.len];
2696 canon_name_dest.* = canon_name.*;
2697 results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } };
2698 results_index += 1;
2699 try resolved.putAll(pool_io, results_buffer[0..results_index]);
2700 return;
2701 }
2702
2703 return lookupDnsSearch(pool, host_name, resolved, options);
2704 }
2705
2706 if (native_os == .openbsd) {
2707 // TODO use getaddrinfo_async / asr_abort
2708 }
2709
2710 if (native_os == .freebsd) {
2711 // TODO use dnsres_getaddrinfo
2712 }
2713
2714 if (native_os.isDarwin()) {
2715 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
2716 }
2717
2718 if (builtin.link_libc) {
2719 // This operating system lacks a way to resolve asynchronously. We are
2720 // stuck with getaddrinfo.
2721 @compileError("TODO");
2722 }
2723
2724 return error.OptionUnsupported;
2725}
2726
25662727const PosixAddress = extern union {
25672728 any: posix.sockaddr,
25682729 in: posix.sockaddr.in,
......@@ -2574,14 +2735,14 @@ const UnixAddress = extern union {
25742735 un: posix.sockaddr.un,
25752736};
25762737
2577fn posixAddressFamily(a: *const net.IpAddress) posix.sa_family_t {
2738fn posixAddressFamily(a: *const IpAddress) posix.sa_family_t {
25782739 return switch (a.*) {
25792740 .ip4 => posix.AF.INET,
25802741 .ip6 => posix.AF.INET6,
25812742 };
25822743}
25832744
2584fn addressFromPosix(posix_address: *PosixAddress) net.IpAddress {
2745fn addressFromPosix(posix_address: *PosixAddress) IpAddress {
25852746 return switch (posix_address.any.family) {
25862747 posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) },
25872748 posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) },
......@@ -2589,7 +2750,7 @@ fn addressFromPosix(posix_address: *PosixAddress) net.IpAddress {
25892750 };
25902751}
25912752
2592fn addressToPosix(a: *const net.IpAddress, storage: *PosixAddress) posix.socklen_t {
2753fn addressToPosix(a: *const IpAddress, storage: *PosixAddress) posix.socklen_t {
25932754 return switch (a.*) {
25942755 .ip4 => |ip4| {
25952756 storage.in = address4ToPosix(ip4);
......@@ -2789,3 +2950,436 @@ fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNa
27892950 buffer[file_path.len] = 0;
27902951 return buffer[0..file_path.len :0];
27912952}
2953
2954fn lookupDnsSearch(
2955 pool: *Pool,
2956 host_name: HostName,
2957 resolved: *Io.Queue(HostName.LookupResult),
2958 options: HostName.LookupOptions,
2959) HostName.LookupError!void {
2960 const pool_io = pool.io();
2961 const rc = HostName.ResolvConf.init(pool_io) catch return error.ResolvConfParseFailed;
2962
2963 // Count dots, suppress search when >=ndots or name ends in
2964 // a dot, which is an explicit request for global scope.
2965 const dots = std.mem.countScalar(u8, host_name.bytes, '.');
2966 const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len;
2967 const search = rc.search_buffer[0..search_len];
2968
2969 var canon_name = host_name.bytes;
2970
2971 // Strip final dot for canon, fail if multiple trailing dots.
2972 if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
2973 if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
2974
2975 // Name with search domain appended is set up in `canon_name`. This
2976 // both provides the desired default canonical name (if the requested
2977 // name is not a CNAME record) and serves as a buffer for passing the
2978 // full requested name to `lookupDns`.
2979 @memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name);
2980 options.canonical_name_buffer[canon_name.len] = '.';
2981 var it = std.mem.tokenizeAny(u8, search, " \t");
2982 while (it.next()) |token| {
2983 @memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token);
2984 const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len];
2985 if (lookupDns(pool, lookup_canon_name, &rc, resolved, options)) |result| {
2986 return result;
2987 } else |err| switch (err) {
2988 error.UnknownHostName => continue,
2989 else => |e| return e,
2990 }
2991 }
2992
2993 const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len];
2994 return lookupDns(pool, lookup_canon_name, &rc, resolved, options);
2995}
2996
2997fn lookupDns(
2998 pool: *Pool,
2999 lookup_canon_name: []const u8,
3000 rc: *const HostName.ResolvConf,
3001 resolved: *Io.Queue(HostName.LookupResult),
3002 options: HostName.LookupOptions,
3003) HostName.LookupError!void {
3004 const pool_io = pool.io();
3005 const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{
3006 .{ .af = .ip6, .rr = std.posix.RR.A },
3007 .{ .af = .ip4, .rr = std.posix.RR.AAAA },
3008 };
3009 var query_buffers: [2][280]u8 = undefined;
3010 var answer_buffer: [2 * 512]u8 = undefined;
3011 var queries_buffer: [2][]const u8 = undefined;
3012 var answers_buffer: [2][]const u8 = undefined;
3013 var nq: usize = 0;
3014 var answer_buffer_i: usize = 0;
3015
3016 for (family_records) |fr| {
3017 if (options.family != fr.af) {
3018 const entropy = std.crypto.random.array(u8, 2);
3019 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
3020 queries_buffer[nq] = query_buffers[nq][0..len];
3021 nq += 1;
3022 }
3023 }
3024
3025 var ip4_mapped: [HostName.ResolvConf.max_nameservers]IpAddress = undefined;
3026 var any_ip6 = false;
3027 for (rc.nameservers(), &ip4_mapped) |*ns, *m| {
3028 m.* = .{ .ip6 = .fromAny(ns.*) };
3029 any_ip6 = any_ip6 or ns.* == .ip6;
3030 }
3031 var socket = s: {
3032 if (any_ip6) ip6: {
3033 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };
3034 const socket = ip6_addr.bind(pool_io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) {
3035 error.AddressFamilyUnsupported => break :ip6,
3036 else => |e| return e,
3037 };
3038 break :s socket;
3039 }
3040 any_ip6 = false;
3041 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };
3042 const socket = try ip4_addr.bind(pool_io, .{ .mode = .dgram });
3043 break :s socket;
3044 };
3045 defer socket.close(pool_io);
3046
3047 const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers();
3048 const queries = queries_buffer[0..nq];
3049 const answers = answers_buffer[0..queries.len];
3050 var answers_remaining = answers.len;
3051 for (answers) |*answer| answer.len = 0;
3052
3053 // boot clock is chosen because time the computer is suspended should count
3054 // against time spent waiting for external messages to arrive.
3055 const clock: Io.Clock = .boot;
3056 var now_ts = try clock.now(pool_io);
3057 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
3058 const attempt_duration: Io.Duration = .{
3059 .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts,
3060 };
3061
3062 send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(pool_io)) {
3063 const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers;
3064 {
3065 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;
3066 var message_i: usize = 0;
3067 for (queries, answers) |query, *answer| {
3068 if (answer.len != 0) continue;
3069 for (mapped_nameservers) |*ns| {
3070 message_buffer[message_i] = .{
3071 .address = ns,
3072 .data_ptr = query.ptr,
3073 .data_len = query.len,
3074 };
3075 message_i += 1;
3076 }
3077 }
3078 _ = netSend(pool, socket.handle, message_buffer[0..message_i], .{});
3079 }
3080
3081 const timeout: Io.Timeout = .{ .deadline = .{
3082 .raw = now_ts.addDuration(attempt_duration),
3083 .clock = clock,
3084 } };
3085
3086 while (true) {
3087 var message_buffer: [max_messages]Io.net.IncomingMessage = undefined;
3088 const buf = answer_buffer[answer_buffer_i..];
3089 const recv_err, const recv_n = socket.receiveManyTimeout(pool_io, &message_buffer, buf, .{}, timeout);
3090 for (message_buffer[0..recv_n]) |*received_message| {
3091 const reply = received_message.data;
3092 // Ignore non-identifiable packets.
3093 if (reply.len < 4) continue;
3094
3095 // Ignore replies from addresses we didn't send to.
3096 const ns = for (mapped_nameservers) |*ns| {
3097 if (received_message.from.eql(ns)) break ns;
3098 } else {
3099 continue;
3100 };
3101
3102 // Find which query this answer goes with, if any.
3103 const query, const answer = for (queries, answers) |query, *answer| {
3104 if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer };
3105 } else {
3106 continue;
3107 };
3108 if (answer.len != 0) continue;
3109
3110 // Only accept positive or negative responses; retry immediately on
3111 // server failure, and ignore all other codes such as refusal.
3112 switch (reply[3] & 15) {
3113 0, 3 => {
3114 answer.* = reply;
3115 answer_buffer_i += reply.len;
3116 answers_remaining -= 1;
3117 if (answer_buffer.len - answer_buffer_i == 0) break :send;
3118 if (answers_remaining == 0) break :send;
3119 },
3120 2 => {
3121 var retry_message: Io.net.OutgoingMessage = .{
3122 .address = ns,
3123 .data_ptr = query.ptr,
3124 .data_len = query.len,
3125 };
3126 _ = netSend(pool, socket.handle, (&retry_message)[0..1], .{});
3127 continue;
3128 },
3129 else => continue,
3130 }
3131 }
3132 if (recv_err) |err| switch (err) {
3133 error.Canceled => return error.Canceled,
3134 error.Timeout => continue :send,
3135 else => continue,
3136 };
3137 }
3138 } else {
3139 return error.NameServerFailure;
3140 }
3141
3142 var addresses_len: usize = 0;
3143 var canonical_name: ?HostName = null;
3144
3145 for (answers) |answer| {
3146 var it = HostName.DnsResponse.init(answer) catch {
3147 // TODO accept a diagnostics struct and append warnings
3148 continue;
3149 };
3150 while (it.next() catch {
3151 // TODO accept a diagnostics struct and append warnings
3152 continue;
3153 }) |record| switch (record.rr) {
3154 std.posix.RR.A => {
3155 const data = record.packet[record.data_off..][0..record.data_len];
3156 if (data.len != 4) return error.InvalidDnsARecord;
3157 try resolved.putOne(pool_io, .{ .address = .{ .ip4 = .{
3158 .bytes = data[0..4].*,
3159 .port = options.port,
3160 } } });
3161 addresses_len += 1;
3162 },
3163 std.posix.RR.AAAA => {
3164 const data = record.packet[record.data_off..][0..record.data_len];
3165 if (data.len != 16) return error.InvalidDnsAAAARecord;
3166 try resolved.putOne(pool_io, .{ .address = .{ .ip6 = .{
3167 .bytes = data[0..16].*,
3168 .port = options.port,
3169 } } });
3170 addresses_len += 1;
3171 },
3172 std.posix.RR.CNAME => {
3173 _, canonical_name = HostName.expand(record.packet, record.data_off, options.canonical_name_buffer) catch
3174 return error.InvalidDnsCnameRecord;
3175 },
3176 else => continue,
3177 };
3178 }
3179
3180 try resolved.putOne(pool_io, .{ .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name } });
3181 if (addresses_len == 0) return error.NameServerFailure;
3182}
3183
3184fn lookupHosts(
3185 pool: *Pool,
3186 host_name: HostName,
3187 resolved: *Io.Queue(HostName.LookupResult),
3188 options: HostName.LookupOptions,
3189) !void {
3190 const pool_io = pool.io();
3191 const file = Io.File.openAbsolute(pool_io, "/etc/hosts", .{}) catch |err| switch (err) {
3192 error.FileNotFound,
3193 error.NotDir,
3194 error.AccessDenied,
3195 => return error.UnknownHostName,
3196
3197 error.Canceled => |e| return e,
3198
3199 else => {
3200 // TODO populate optional diagnostic struct
3201 return error.DetectingNetworkConfigurationFailed;
3202 },
3203 };
3204 defer file.close(pool_io);
3205
3206 var line_buf: [512]u8 = undefined;
3207 var file_reader = file.reader(pool_io, &line_buf);
3208 return lookupHostsReader(pool, host_name, resolved, options, &file_reader.interface) catch |err| switch (err) {
3209 error.ReadFailed => switch (file_reader.err.?) {
3210 error.Canceled => |e| return e,
3211 else => {
3212 // TODO populate optional diagnostic struct
3213 return error.DetectingNetworkConfigurationFailed;
3214 },
3215 },
3216 error.Canceled => |e| return e,
3217 error.UnknownHostName => |e| return e,
3218 };
3219}
3220
3221fn lookupHostsReader(
3222 pool: *Pool,
3223 host_name: HostName,
3224 resolved: *Io.Queue(HostName.LookupResult),
3225 options: HostName.LookupOptions,
3226 reader: *Io.Reader,
3227) error{ ReadFailed, Canceled, UnknownHostName }!void {
3228 const pool_io = pool.io();
3229 var addresses_len: usize = 0;
3230 var canonical_name: ?HostName = null;
3231 while (true) {
3232 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
3233 error.StreamTooLong => {
3234 // Skip lines that are too long.
3235 _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) {
3236 error.EndOfStream => break,
3237 error.ReadFailed => return error.ReadFailed,
3238 };
3239 continue;
3240 },
3241 error.ReadFailed => return error.ReadFailed,
3242 error.EndOfStream => break,
3243 };
3244 reader.toss(1);
3245 var split_it = std.mem.splitScalar(u8, line, '#');
3246 const no_comment_line = split_it.first();
3247
3248 var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t");
3249 const ip_text = line_it.next() orelse continue;
3250 var first_name_text: ?[]const u8 = null;
3251 while (line_it.next()) |name_text| {
3252 if (std.mem.eql(u8, name_text, host_name.bytes)) {
3253 if (first_name_text == null) first_name_text = name_text;
3254 break;
3255 }
3256 } else continue;
3257
3258 if (canonical_name == null) {
3259 if (HostName.init(first_name_text.?)) |name_text| {
3260 if (name_text.bytes.len <= options.canonical_name_buffer.len) {
3261 const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len];
3262 @memcpy(canonical_name_dest, name_text.bytes);
3263 canonical_name = .{ .bytes = canonical_name_dest };
3264 }
3265 } else |_| {}
3266 }
3267
3268 if (options.family != .ip6) {
3269 if (IpAddress.parseIp4(ip_text, options.port)) |addr| {
3270 try resolved.putOne(pool_io, .{ .address = addr });
3271 addresses_len += 1;
3272 } else |_| {}
3273 }
3274 if (options.family != .ip4) {
3275 if (IpAddress.parseIp6(ip_text, options.port)) |addr| {
3276 try resolved.putOne(pool_io, .{ .address = addr });
3277 addresses_len += 1;
3278 } else |_| {}
3279 }
3280 }
3281
3282 if (canonical_name) |canon_name| try resolved.putOne(pool_io, .{ .canonical_name = canon_name });
3283 if (addresses_len == 0) return error.UnknownHostName;
3284}
3285
3286/// Writes DNS resolution query packet data to `w`; at most 280 bytes.
3287fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u8, entropy: [2]u8) usize {
3288 // This implementation is ported from musl libc.
3289 // A more idiomatic "ziggy" implementation would be welcome.
3290 var name = dname;
3291 if (std.mem.endsWith(u8, name, ".")) name.len -= 1;
3292 assert(name.len <= 253);
3293 const n = 17 + name.len + @intFromBool(name.len != 0);
3294
3295 // Construct query template - ID will be filled later
3296 q[0..2].* = entropy;
3297 @memset(q[2..n], 0);
3298 q[2] = @as(u8, op) * 8 + 1;
3299 q[5] = 1;
3300 @memcpy(q[13..][0..name.len], name);
3301 var i: usize = 13;
3302 var j: usize = undefined;
3303 while (q[i] != 0) : (i = j + 1) {
3304 j = i;
3305 while (q[j] != 0 and q[j] != '.') : (j += 1) {}
3306 // TODO determine the circumstances for this and whether or
3307 // not this should be an error.
3308 if (j - i - 1 > 62) unreachable;
3309 q[i - 1] = @intCast(j - i);
3310 }
3311 q[i + 1] = ty;
3312 q[i + 3] = class;
3313 return n;
3314}
3315
3316fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) HostName {
3317 const dest = canonical_name_buffer[0..name.len];
3318 @memcpy(dest, name);
3319 return .{ .bytes = dest };
3320}
3321
3322pub fn futexWait(ptr: *const std.atomic.Value(u32), expect: u32) void {
3323 @branchHint(.cold);
3324
3325 if (native_os == .linux) {
3326 const linux = std.os.linux;
3327 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null);
3328 if (builtin.mode == .Debug) switch (linux.E.init(rc)) {
3329 .SUCCESS => {}, // notified by `wake()`
3330 .INTR => {}, // gives caller a chance to check cancellation
3331 .AGAIN => {}, // ptr.* != expect
3332 .INVAL => {}, // possibly timeout overflow
3333 .TIMEDOUT => unreachable,
3334 .FAULT => unreachable, // ptr was invalid
3335 else => unreachable,
3336 };
3337 return;
3338 }
3339
3340 @compileError("TODO");
3341}
3342
3343pub fn futexWaitDuration(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void {
3344 @branchHint(.cold);
3345
3346 if (native_os == .linux) {
3347 const linux = std.os.linux;
3348 var ts = timestampToPosix(timeout.toNanoseconds());
3349 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, &ts);
3350 if (builtin.mode == .Debug) switch (linux.E.init(rc)) {
3351 .SUCCESS => {}, // notified by `wake()`
3352 .INTR => {}, // gives caller a chance to check cancellation
3353 .AGAIN => {}, // ptr.* != expect
3354 .TIMEDOUT => {},
3355 .INVAL => {}, // possibly timeout overflow
3356 .FAULT => unreachable, // ptr was invalid
3357 else => unreachable,
3358 };
3359 return;
3360 }
3361
3362 @compileError("TODO");
3363}
3364
3365pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void {
3366 @branchHint(.cold);
3367
3368 if (native_os == .linux) {
3369 const linux = std.os.linux;
3370 const rc = linux.futex_3arg(
3371 &ptr.raw,
3372 .{ .cmd = .WAKE, .private = true },
3373 @min(max_waiters, std.math.maxInt(i32)),
3374 );
3375 if (builtin.mode == .Debug) switch (linux.E.init(rc)) {
3376 .SUCCESS => {}, // successful wake up
3377 .INVAL => {}, // invalid futex_wait() on ptr done elsewhere
3378 .FAULT => {}, // pointer became invalid while doing the wake
3379 else => unreachable,
3380 };
3381 return;
3382 }
3383
3384 @compileError("TODO");
3385}
lib/std/Io/net.zig+2-3
......@@ -281,7 +281,6 @@ pub const IpAddress = union(enum) {
281281 }
282282
283283 pub const ConnectError = error{
284 AddressInUse,
285284 AddressUnavailable,
286285 AddressFamilyUnsupported,
287286 /// Insufficient memory or other resource internal to the operating system.
......@@ -291,7 +290,7 @@ pub const IpAddress = union(enum) {
291290 ConnectionResetByPeer,
292291 HostUnreachable,
293292 NetworkUnreachable,
294 ConnectionTimedOut,
293 Timeout,
295294 /// One of the `ConnectOptions` is not supported by the Io
296295 /// implementation.
297296 OptionUnsupported,
......@@ -1165,7 +1164,7 @@ pub const Stream = struct {
11651164 SystemResources,
11661165 BrokenPipe,
11671166 ConnectionResetByPeer,
1168 ConnectionTimedOut,
1167 Timeout,
11691168 SocketUnconnected,
11701169 /// The file descriptor does not hold the required rights to read
11711170 /// from it.
lib/std/Io/net/HostName.zig+48-504
......@@ -63,8 +63,6 @@ pub fn eql(a: HostName, b: HostName) bool {
6363
6464pub const LookupOptions = struct {
6565 port: u16,
66 /// Must have at least length 2.
67 addresses_buffer: []IpAddress,
6866 canonical_name_buffer: *[max_len]u8,
6967 /// `null` means either.
7068 family: ?IpAddress.Family = null,
......@@ -81,487 +79,23 @@ pub const LookupError = error{
8179 DetectingNetworkConfigurationFailed,
8280} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable;
8381
84pub const LookupResult = struct {
85 /// How many `LookupOptions.addresses_buffer` elements are populated.
86 addresses_len: usize,
82pub const LookupResult = union(enum) {
83 address: IpAddress,
8784 canonical_name: HostName,
88
89 pub const empty: LookupResult = .{
90 .addresses_len = 0,
91 .canonical_name = undefined,
92 };
85 end: LookupError!void,
9386};
9487
95pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
96 const name = host_name.bytes;
97 assert(name.len <= max_len);
98 assert(options.addresses_buffer.len >= 2);
99
100 if (native_os == .windows) @compileError("TODO");
101 if (builtin.link_libc) @compileError("TODO");
102 if (native_os == .linux) {
103 if (options.family != .ip6) {
104 if (IpAddress.parseIp4(name, options.port)) |addr| {
105 options.addresses_buffer[0] = addr;
106 return .{ .addresses_len = 1, .canonical_name = copyCanon(options.canonical_name_buffer, name) };
107 } else |_| {}
108 }
109 if (options.family != .ip4) {
110 if (IpAddress.parseIp6(name, options.port)) |addr| {
111 options.addresses_buffer[0] = addr;
112 return .{ .addresses_len = 1, .canonical_name = copyCanon(options.canonical_name_buffer, name) };
113 } else |_| {}
114 }
115 {
116 const result = try lookupHosts(host_name, io, options);
117 if (result.addresses_len > 0) return sortLookupResults(options, result);
118 }
119 {
120 // RFC 6761 Section 6.3.3
121 // Name resolution APIs and libraries SHOULD recognize
122 // localhost names as special and SHOULD always return the IP
123 // loopback address for address queries and negative responses
124 // for all other query types.
125
126 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
127 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
128 if (std.mem.endsWith(u8, name, localhost) and
129 (name.len == localhost.len or name[name.len - localhost.len] == '.'))
130 {
131 var i: usize = 0;
132 if (options.family != .ip6) {
133 options.addresses_buffer[i] = .{ .ip4 = .loopback(options.port) };
134 i += 1;
135 }
136 if (options.family != .ip4) {
137 options.addresses_buffer[i] = .{ .ip6 = .loopback(options.port) };
138 i += 1;
139 }
140 const canon_name = "localhost";
141 const canon_name_dest = options.canonical_name_buffer[0..canon_name.len];
142 canon_name_dest.* = canon_name.*;
143 return sortLookupResults(options, .{
144 .addresses_len = i,
145 .canonical_name = .{ .bytes = canon_name_dest },
146 });
147 }
148 }
149 {
150 const result = try lookupDnsSearch(host_name, io, options);
151 if (result.addresses_len > 0) return sortLookupResults(options, result);
152 }
153 return error.UnknownHostName;
154 }
155 @compileError("unimplemented");
156}
157
158fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult {
159 const addresses = options.addresses_buffer[0..result.addresses_len];
160 // No further processing is needed if there are fewer than 2 results or
161 // if there are only IPv4 results.
162 if (addresses.len < 2) return result;
163 const all_ip4 = for (addresses) |a| switch (a) {
164 .ip4 => continue,
165 .ip6 => break false,
166 } else true;
167 if (all_ip4) return result;
168
169 // RFC 3484/6724 describes how destination address selection is
170 // supposed to work. However, to implement it requires making a bunch
171 // of networking syscalls, which is unnecessarily high latency,
172 // especially if implemented serially. Furthermore, rules 3, 4, and 7
173 // have excessive runtime and code size cost and dubious benefit.
174 //
175 // Therefore, this logic sorts only using values available without
176 // doing any syscalls, relying on the calling code to have a
177 // meta-strategy such as attempting connection to multiple results at
178 // once and keeping the fastest response while canceling the others.
179
180 const S = struct {
181 pub fn lessThan(s: @This(), lhs: IpAddress, rhs: IpAddress) bool {
182 return sortKey(s, lhs) < sortKey(s, rhs);
183 }
184
185 fn sortKey(s: @This(), a: IpAddress) i32 {
186 _ = s;
187 var da6: Ip6Address = .{
188 .port = 65535,
189 .bytes = undefined,
190 };
191 switch (a) {
192 .ip6 => |ip6| {
193 da6.bytes = ip6.bytes;
194 da6.interface = ip6.interface;
195 },
196 .ip4 => |ip4| {
197 da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
198 da6.bytes[12..].* = ip4.bytes;
199 },
200 }
201 const da6_scope: i32 = da6.scope();
202 const da6_prec: i32 = da6.policy().prec;
203 var key: i32 = 0;
204 key |= da6_prec << 20;
205 key |= (15 - da6_scope) << 16;
206 return key;
207 }
208 };
209 std.mem.sort(IpAddress, addresses, @as(S, .{}), S.lessThan);
210 return result;
211}
212
213fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
214 const rc = ResolvConf.init(io) catch return error.ResolvConfParseFailed;
215
216 // Count dots, suppress search when >=ndots or name ends in
217 // a dot, which is an explicit request for global scope.
218 const dots = std.mem.countScalar(u8, host_name.bytes, '.');
219 const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len;
220 const search = rc.search_buffer[0..search_len];
221
222 var canon_name = host_name.bytes;
223
224 // Strip final dot for canon, fail if multiple trailing dots.
225 if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
226 if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
227
228 // Name with search domain appended is set up in `canon_name`. This
229 // both provides the desired default canonical name (if the requested
230 // name is not a CNAME record) and serves as a buffer for passing the
231 // full requested name to `lookupDns`.
232 @memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name);
233 options.canonical_name_buffer[canon_name.len] = '.';
234 var it = std.mem.tokenizeAny(u8, search, " \t");
235 while (it.next()) |token| {
236 @memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token);
237 const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len];
238 const result = try lookupDns(io, lookup_canon_name, &rc, options);
239 if (result.addresses_len > 0) return sortLookupResults(options, result);
240 }
241
242 const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len];
243 return lookupDns(io, lookup_canon_name, &rc, options);
244}
245
246fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) LookupError!LookupResult {
247 const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{
248 .{ .af = .ip6, .rr = std.posix.RR.A },
249 .{ .af = .ip4, .rr = std.posix.RR.AAAA },
250 };
251 var query_buffers: [2][280]u8 = undefined;
252 var answer_buffer: [2 * 512]u8 = undefined;
253 var queries_buffer: [2][]const u8 = undefined;
254 var answers_buffer: [2][]const u8 = undefined;
255 var nq: usize = 0;
256 var answer_buffer_i: usize = 0;
257
258 for (family_records) |fr| {
259 if (options.family != fr.af) {
260 const entropy = std.crypto.random.array(u8, 2);
261 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
262 queries_buffer[nq] = query_buffers[nq][0..len];
263 nq += 1;
264 }
265 }
266
267 var ip4_mapped: [ResolvConf.max_nameservers]IpAddress = undefined;
268 var any_ip6 = false;
269 for (rc.nameservers(), &ip4_mapped) |*ns, *m| {
270 m.* = .{ .ip6 = .fromAny(ns.*) };
271 any_ip6 = any_ip6 or ns.* == .ip6;
272 }
273 var socket = s: {
274 if (any_ip6) ip6: {
275 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };
276 const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) {
277 error.AddressFamilyUnsupported => break :ip6,
278 else => |e| return e,
279 };
280 break :s socket;
281 }
282 any_ip6 = false;
283 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };
284 const socket = try ip4_addr.bind(io, .{ .mode = .dgram });
285 break :s socket;
286 };
287 defer socket.close(io);
288
289 const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers();
290 const queries = queries_buffer[0..nq];
291 const answers = answers_buffer[0..queries.len];
292 var answers_remaining = answers.len;
293 for (answers) |*answer| answer.len = 0;
294
295 // boot clock is chosen because time the computer is suspended should count
296 // against time spent waiting for external messages to arrive.
297 const clock: Io.Clock = .boot;
298 var now_ts = try clock.now(io);
299 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
300 const attempt_duration: Io.Duration = .{
301 .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts,
302 };
303
304 send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(io)) {
305 const max_messages = queries_buffer.len * ResolvConf.max_nameservers;
306 {
307 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;
308 var message_i: usize = 0;
309 for (queries, answers) |query, *answer| {
310 if (answer.len != 0) continue;
311 for (mapped_nameservers) |*ns| {
312 message_buffer[message_i] = .{
313 .address = ns,
314 .data_ptr = query.ptr,
315 .data_len = query.len,
316 };
317 message_i += 1;
318 }
319 }
320 _ = io.vtable.netSend(io.userdata, socket.handle, message_buffer[0..message_i], .{});
321 }
322
323 const timeout: Io.Timeout = .{ .deadline = .{
324 .raw = now_ts.addDuration(attempt_duration),
325 .clock = clock,
326 } };
327
328 while (true) {
329 var message_buffer: [max_messages]Io.net.IncomingMessage = undefined;
330 const buf = answer_buffer[answer_buffer_i..];
331 const recv_err, const recv_n = socket.receiveManyTimeout(io, &message_buffer, buf, .{}, timeout);
332 for (message_buffer[0..recv_n]) |*received_message| {
333 const reply = received_message.data;
334 // Ignore non-identifiable packets.
335 if (reply.len < 4) continue;
336
337 // Ignore replies from addresses we didn't send to.
338 const ns = for (mapped_nameservers) |*ns| {
339 if (received_message.from.eql(ns)) break ns;
340 } else {
341 continue;
342 };
343
344 // Find which query this answer goes with, if any.
345 const query, const answer = for (queries, answers) |query, *answer| {
346 if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer };
347 } else {
348 continue;
349 };
350 if (answer.len != 0) continue;
351
352 // Only accept positive or negative responses; retry immediately on
353 // server failure, and ignore all other codes such as refusal.
354 switch (reply[3] & 15) {
355 0, 3 => {
356 answer.* = reply;
357 answer_buffer_i += reply.len;
358 answers_remaining -= 1;
359 if (answer_buffer.len - answer_buffer_i == 0) break :send;
360 if (answers_remaining == 0) break :send;
361 },
362 2 => {
363 var retry_message: Io.net.OutgoingMessage = .{
364 .address = ns,
365 .data_ptr = query.ptr,
366 .data_len = query.len,
367 };
368 _ = io.vtable.netSend(io.userdata, socket.handle, (&retry_message)[0..1], .{});
369 continue;
370 },
371 else => continue,
372 }
373 }
374 if (recv_err) |err| switch (err) {
375 error.Canceled => return error.Canceled,
376 error.Timeout => continue :send,
377 else => continue,
378 };
379 }
380 } else {
381 return error.NameServerFailure;
382 }
383
384 var addresses_len: usize = 0;
385 var canonical_name: ?HostName = null;
386
387 for (answers) |answer| {
388 var it = DnsResponse.init(answer) catch {
389 // TODO accept a diagnostics struct and append warnings
390 continue;
391 };
392 while (it.next() catch {
393 // TODO accept a diagnostics struct and append warnings
394 continue;
395 }) |record| switch (record.rr) {
396 std.posix.RR.A => {
397 const data = record.packet[record.data_off..][0..record.data_len];
398 if (data.len != 4) return error.InvalidDnsARecord;
399 if (addresses_len < options.addresses_buffer.len) {
400 options.addresses_buffer[addresses_len] = .{ .ip4 = .{
401 .bytes = data[0..4].*,
402 .port = options.port,
403 } };
404 addresses_len += 1;
405 }
406 },
407 std.posix.RR.AAAA => {
408 const data = record.packet[record.data_off..][0..record.data_len];
409 if (data.len != 16) return error.InvalidDnsAAAARecord;
410 if (addresses_len < options.addresses_buffer.len) {
411 options.addresses_buffer[addresses_len] = .{ .ip6 = .{
412 .bytes = data[0..16].*,
413 .port = options.port,
414 } };
415 addresses_len += 1;
416 }
417 },
418 std.posix.RR.CNAME => {
419 _, canonical_name = expand(record.packet, record.data_off, options.canonical_name_buffer) catch
420 return error.InvalidDnsCnameRecord;
421 },
422 else => continue,
423 };
424 }
425
426 if (addresses_len != 0) return .{
427 .addresses_len = addresses_len,
428 .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name },
429 };
430
431 return error.NameServerFailure;
432}
433
434fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {
435 const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) {
436 error.FileNotFound,
437 error.NotDir,
438 error.AccessDenied,
439 => return .empty,
440
441 error.Canceled => |e| return e,
442
443 else => {
444 // TODO populate optional diagnostic struct
445 return error.DetectingNetworkConfigurationFailed;
446 },
447 };
448 defer file.close(io);
449
450 var line_buf: [512]u8 = undefined;
451 var file_reader = file.reader(io, &line_buf);
452 return lookupHostsReader(host_name, options, &file_reader.interface) catch |err| switch (err) {
453 error.ReadFailed => switch (file_reader.err.?) {
454 error.Canceled => |e| return e,
455 else => {
456 // TODO populate optional diagnostic struct
457 return error.DetectingNetworkConfigurationFailed;
458 },
459 },
460 };
461}
462
463fn lookupHostsReader(host_name: HostName, options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult {
464 var addresses_len: usize = 0;
465 var canonical_name: ?HostName = null;
466 while (true) {
467 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
468 error.StreamTooLong => {
469 // Skip lines that are too long.
470 _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) {
471 error.EndOfStream => break,
472 error.ReadFailed => return error.ReadFailed,
473 };
474 continue;
475 },
476 error.ReadFailed => return error.ReadFailed,
477 error.EndOfStream => break,
478 };
479 reader.toss(1);
480 var split_it = std.mem.splitScalar(u8, line, '#');
481 const no_comment_line = split_it.first();
482
483 var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t");
484 const ip_text = line_it.next() orelse continue;
485 var first_name_text: ?[]const u8 = null;
486 while (line_it.next()) |name_text| {
487 if (std.mem.eql(u8, name_text, host_name.bytes)) {
488 if (first_name_text == null) first_name_text = name_text;
489 break;
490 }
491 } else continue;
492
493 if (canonical_name == null) {
494 if (HostName.init(first_name_text.?)) |name_text| {
495 if (name_text.bytes.len <= options.canonical_name_buffer.len) {
496 const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len];
497 @memcpy(canonical_name_dest, name_text.bytes);
498 canonical_name = .{ .bytes = canonical_name_dest };
499 }
500 } else |_| {}
501 }
502
503 if (options.family != .ip6) {
504 if (IpAddress.parseIp4(ip_text, options.port)) |addr| {
505 options.addresses_buffer[addresses_len] = addr;
506 addresses_len += 1;
507 if (options.addresses_buffer.len - addresses_len == 0) return .{
508 .addresses_len = addresses_len,
509 .canonical_name = canonical_name orelse copyCanon(options.canonical_name_buffer, ip_text),
510 };
511 } else |_| {}
512 }
513 if (options.family != .ip4) {
514 if (IpAddress.parseIp6(ip_text, options.port)) |addr| {
515 options.addresses_buffer[addresses_len] = addr;
516 addresses_len += 1;
517 if (options.addresses_buffer.len - addresses_len == 0) return .{
518 .addresses_len = addresses_len,
519 .canonical_name = canonical_name orelse copyCanon(options.canonical_name_buffer, ip_text),
520 };
521 } else |_| {}
522 }
523 }
524 if (canonical_name == null) assert(addresses_len == 0);
525 return .{
526 .addresses_len = addresses_len,
527 .canonical_name = canonical_name orelse undefined,
528 };
529}
530
531fn copyCanon(canonical_name_buffer: *[max_len]u8, name: []const u8) HostName {
532 const dest = canonical_name_buffer[0..name.len];
533 @memcpy(dest, name);
534 return .{ .bytes = dest };
535}
536
537/// Writes DNS resolution query packet data to `w`; at most 280 bytes.
538fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u8, entropy: [2]u8) usize {
539 // This implementation is ported from musl libc.
540 // A more idiomatic "ziggy" implementation would be welcome.
541 var name = dname;
542 if (std.mem.endsWith(u8, name, ".")) name.len -= 1;
543 assert(name.len <= 253);
544 const n = 17 + name.len + @intFromBool(name.len != 0);
545
546 // Construct query template - ID will be filled later
547 q[0..2].* = entropy;
548 @memset(q[2..n], 0);
549 q[2] = @as(u8, op) * 8 + 1;
550 q[5] = 1;
551 @memcpy(q[13..][0..name.len], name);
552 var i: usize = 13;
553 var j: usize = undefined;
554 while (q[i] != 0) : (i = j + 1) {
555 j = i;
556 while (q[j] != 0 and q[j] != '.') : (j += 1) {}
557 // TODO determine the circumstances for this and whether or
558 // not this should be an error.
559 if (j - i - 1 > 62) unreachable;
560 q[i - 1] = @intCast(j - i);
561 }
562 q[i + 1] = ty;
563 q[i + 3] = class;
564 return n;
88/// Adds any number of `IpAddress` into resolved, exactly one canonical_name,
89/// and then always finishes by adding one `LookupResult.end` entry.
90///
91/// Guaranteed not to block if provided queue has capacity at least 8.
92pub fn lookup(
93 host_name: HostName,
94 io: Io,
95 resolved: *Io.Queue(LookupResult),
96 options: LookupOptions,
97) void {
98 return io.vtable.netLookup(io.userdata, host_name, resolved, options);
56599}
566100
567101pub const ExpandError = error{InvalidDnsPacket} || ValidateError;
......@@ -672,33 +206,43 @@ pub fn connect(
672206 port: u16,
673207 options: IpAddress.ConnectOptions,
674208) ConnectError!Stream {
675 var addresses_buffer: [32]IpAddress = undefined;
676 var canonical_name_buffer: [HostName.max_len]u8 = undefined;
209 var canonical_name_buffer: [max_len]u8 = undefined;
210 var results_buffer: [32]HostName.LookupResult = undefined;
211 var results: Io.Queue(LookupResult) = .init(&results_buffer);
677212
678 const results = try lookup(host_name, io, .{
213 var lookup_task = io.async(HostName.lookup, .{ host_name, io, &results, .{
679214 .port = port,
680 .addresses_buffer = &addresses_buffer,
681215 .canonical_name_buffer = &canonical_name_buffer,
682 });
683 const addresses = addresses_buffer[0..results.addresses_len];
684
685 if (addresses.len == 0) return error.UnknownHostName;
216 } });
217 defer lookup_task.cancel(io);
218
219 var select: Io.Select(union(enum) { ip_connect: IpAddress.ConnectError!Stream }) = .init;
220 defer select.cancel(io);
221
222 while (results.getOne(io)) |result| switch (result) {
223 .address => |address| select.async(io, .ip_connect, IpAddress.connect, .{ address, io, options }),
224 .canonical_name => continue,
225 .end => |lookup_result| {
226 try lookup_result;
227 break;
228 },
229 } else |err| return err;
686230
687 // TODO instead of serially, use a Select API to send out
688 // the connections simultaneously and then keep the first
689 // successful one, canceling the rest.
231 var aggregate_error: ConnectError = error.UnknownHostName;
690232
691 // TODO On Linux this should additionally use an Io.Queue based
692 // DNS resolution API in order to send out a connection after
693 // each DNS response before waiting for the rest of them.
233 while (select.remaining != 0) switch (select.wait(io)) {
234 .ip_connect => |ip_connect| if (ip_connect) |stream| return stream else |err| switch (err) {
235 error.SystemResources => |e| return e,
236 error.OptionUnsupported => |e| return e,
237 error.ProcessFdQuotaExceeded => |e| return e,
238 error.SystemFdQuotaExceeded => |e| return e,
239 error.Canceled => |e| return e,
240 error.WouldBlock => return error.Unexpected,
241 else => |e| aggregate_error = e,
242 },
243 };
694244
695 for (addresses) |*addr| {
696 return addr.connect(io, options) catch |err| switch (err) {
697 error.ConnectionRefused => continue,
698 else => |e| return e,
699 };
700 }
701 return error.ConnectionRefused;
245 return aggregate_error;
702246}
703247
704248pub const ResolvConf = struct {
......@@ -713,7 +257,7 @@ pub const ResolvConf = struct {
713257 pub const max_nameservers = 3;
714258
715259 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
716 fn init(io: Io) !ResolvConf {
260 pub fn init(io: Io) !ResolvConf {
717261 var rc: ResolvConf = .{
718262 .nameservers_buffer = undefined,
719263 .nameservers_len = 0,
......@@ -749,7 +293,7 @@ pub const ResolvConf = struct {
749293 const Directive = enum { options, nameserver, domain, search };
750294 const Option = enum { ndots, attempts, timeout };
751295
752 fn parse(rc: *ResolvConf, io: Io, reader: *Io.Reader) !void {
296 pub fn parse(rc: *ResolvConf, io: Io, reader: *Io.Reader) !void {
753297 while (reader.takeSentinel('\n')) |line_with_comment| {
754298 const line = line: {
755299 var split = std.mem.splitScalar(u8, line_with_comment, '#');
......@@ -799,7 +343,7 @@ pub const ResolvConf = struct {
799343 rc.nameservers_len += 1;
800344 }
801345
802 fn nameservers(rc: *const ResolvConf) []const IpAddress {
346 pub fn nameservers(rc: *const ResolvConf) []const IpAddress {
803347 return rc.nameservers_buffer[0..rc.nameservers_len];
804348 }
805349};
lib/std/posix.zig+15-15
......@@ -845,7 +845,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
845845 .NOMEM => return error.SystemResources,
846846 .NOTCONN => return error.SocketUnconnected,
847847 .CONNRESET => return error.ConnectionResetByPeer,
848 .TIMEDOUT => return error.ConnectionTimedOut,
848 .TIMEDOUT => return error.Timeout,
849849 .NOTCAPABLE => return error.AccessDenied,
850850 else => |err| return unexpectedErrno(err),
851851 }
......@@ -874,7 +874,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
874874 .NOMEM => return error.SystemResources,
875875 .NOTCONN => return error.SocketUnconnected,
876876 .CONNRESET => return error.ConnectionResetByPeer,
877 .TIMEDOUT => return error.ConnectionTimedOut,
877 .TIMEDOUT => return error.Timeout,
878878 else => |err| return unexpectedErrno(err),
879879 }
880880 }
......@@ -914,7 +914,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
914914 .NOMEM => return error.SystemResources,
915915 .NOTCONN => return error.SocketUnconnected,
916916 .CONNRESET => return error.ConnectionResetByPeer,
917 .TIMEDOUT => return error.ConnectionTimedOut,
917 .TIMEDOUT => return error.Timeout,
918918 .NOTCAPABLE => return error.AccessDenied,
919919 else => |err| return unexpectedErrno(err),
920920 }
......@@ -936,7 +936,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
936936 .NOMEM => return error.SystemResources,
937937 .NOTCONN => return error.SocketUnconnected,
938938 .CONNRESET => return error.ConnectionResetByPeer,
939 .TIMEDOUT => return error.ConnectionTimedOut,
939 .TIMEDOUT => return error.Timeout,
940940 else => |err| return unexpectedErrno(err),
941941 }
942942 }
......@@ -983,7 +983,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
983983 .NOMEM => return error.SystemResources,
984984 .NOTCONN => return error.SocketUnconnected,
985985 .CONNRESET => return error.ConnectionResetByPeer,
986 .TIMEDOUT => return error.ConnectionTimedOut,
986 .TIMEDOUT => return error.Timeout,
987987 .NXIO => return error.Unseekable,
988988 .SPIPE => return error.Unseekable,
989989 .OVERFLOW => return error.Unseekable,
......@@ -1016,7 +1016,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
10161016 .NOMEM => return error.SystemResources,
10171017 .NOTCONN => return error.SocketUnconnected,
10181018 .CONNRESET => return error.ConnectionResetByPeer,
1019 .TIMEDOUT => return error.ConnectionTimedOut,
1019 .TIMEDOUT => return error.Timeout,
10201020 .NXIO => return error.Unseekable,
10211021 .SPIPE => return error.Unseekable,
10221022 .OVERFLOW => return error.Unseekable,
......@@ -1134,7 +1134,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
11341134 .NOMEM => return error.SystemResources,
11351135 .NOTCONN => return error.SocketUnconnected,
11361136 .CONNRESET => return error.ConnectionResetByPeer,
1137 .TIMEDOUT => return error.ConnectionTimedOut,
1137 .TIMEDOUT => return error.Timeout,
11381138 .NXIO => return error.Unseekable,
11391139 .SPIPE => return error.Unseekable,
11401140 .OVERFLOW => return error.Unseekable,
......@@ -1160,7 +1160,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
11601160 .NOMEM => return error.SystemResources,
11611161 .NOTCONN => return error.SocketUnconnected,
11621162 .CONNRESET => return error.ConnectionResetByPeer,
1163 .TIMEDOUT => return error.ConnectionTimedOut,
1163 .TIMEDOUT => return error.Timeout,
11641164 .NXIO => return error.Unseekable,
11651165 .SPIPE => return error.Unseekable,
11661166 .OVERFLOW => return error.Unseekable,
......@@ -4205,7 +4205,7 @@ pub const ConnectError = error{
42054205
42064206 /// Timeout while attempting connection. The server may be too busy to accept new connections. Note
42074207 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
4208 ConnectionTimedOut,
4208 Timeout,
42094209
42104210 /// This error occurs when no global event loop is configured,
42114211 /// and connecting to the socket would block.
......@@ -4236,7 +4236,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
42364236 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
42374237 .WSAECONNREFUSED => return error.ConnectionRefused,
42384238 .WSAECONNRESET => return error.ConnectionResetByPeer,
4239 .WSAETIMEDOUT => return error.ConnectionTimedOut,
4239 .WSAETIMEDOUT => return error.Timeout,
42404240 .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well?
42414241 .WSAENETUNREACH,
42424242 => return error.NetworkUnreachable,
......@@ -4273,7 +4273,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
42734273 .NETUNREACH => return error.NetworkUnreachable,
42744274 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
42754275 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
4276 .TIMEDOUT => return error.ConnectionTimedOut,
4276 .TIMEDOUT => return error.Timeout,
42774277 .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist.
42784278 .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused.
42794279 else => |err| return unexpectedErrno(err),
......@@ -4333,7 +4333,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
43334333 .NETUNREACH => return error.NetworkUnreachable,
43344334 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
43354335 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
4336 .TIMEDOUT => return error.ConnectionTimedOut,
4336 .TIMEDOUT => return error.Timeout,
43374337 .CONNRESET => return error.ConnectionResetByPeer,
43384338 else => |err| return unexpectedErrno(err),
43394339 },
......@@ -6465,7 +6465,7 @@ pub const RecvFromError = error{
64656465 SystemResources,
64666466
64676467 ConnectionResetByPeer,
6468 ConnectionTimedOut,
6468 Timeout,
64696469
64706470 /// The socket has not been bound.
64716471 SocketNotBound,
......@@ -6508,7 +6508,7 @@ pub fn recvfrom(
65086508 .WSAENETDOWN => return error.NetworkDown,
65096509 .WSAENOTCONN => return error.SocketUnconnected,
65106510 .WSAEWOULDBLOCK => return error.WouldBlock,
6511 .WSAETIMEDOUT => return error.ConnectionTimedOut,
6511 .WSAETIMEDOUT => return error.Timeout,
65126512 // TODO: handle more errors
65136513 else => |err| return windows.unexpectedWSAError(err),
65146514 }
......@@ -6528,7 +6528,7 @@ pub fn recvfrom(
65286528 .NOMEM => return error.SystemResources,
65296529 .CONNREFUSED => return error.ConnectionRefused,
65306530 .CONNRESET => return error.ConnectionResetByPeer,
6531 .TIMEDOUT => return error.ConnectionTimedOut,
6531 .TIMEDOUT => return error.Timeout,
65326532 .PIPE => return error.BrokenPipe,
65336533 else => |err| return unexpectedErrno(err),
65346534 }
lib/std/zig/system.zig+1-1
......@@ -428,7 +428,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
428428 error.WouldBlock => return error.Unexpected,
429429 error.BrokenPipe => return error.Unexpected,
430430 error.ConnectionResetByPeer => return error.Unexpected,
431 error.ConnectionTimedOut => return error.Unexpected,
431 error.Timeout => return error.Unexpected,
432432 error.NotOpenForReading => return error.Unexpected,
433433 error.SocketUnconnected => return error.Unexpected,
434434