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,3 +1,4 @@
1* Threaded: rename Pool to Threaded
1* Threaded: finish linux impl (all tests passing)2* Threaded: finish linux impl (all tests passing)
2* Threaded: finish macos impl 3* Threaded: finish macos impl
3* Threaded: finish windows impl 4* Threaded: finish windows impl
...@@ -14,4 +15,6 @@...@@ -14,4 +15,6 @@
14* move fs.File.Writer to Io15* move fs.File.Writer to Io
15* add non-blocking flag to net and fs operations, handle EAGAIN16* add non-blocking flag to net and fs operations, handle EAGAIN
16* finish moving std.fs to Io17* 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)
17* finish moving all of std.posix into Threaded20* finish moving all of std.posix into Threaded
lib/std/Io.zig+65-44
...@@ -649,9 +649,11 @@ pub const VTable = struct {...@@ -649,9 +649,11 @@ pub const VTable = struct {
649 select: *const fn (?*anyopaque, futures: []const *AnyFuture) usize,649 select: *const fn (?*anyopaque, futures: []const *AnyFuture) usize,
650650
651 mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void,651 mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void,
652 mutexLockUncancelable: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,
652 mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,653 mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,
653654
654 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,655 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
656 conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void,
655 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,657 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
656658
657 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,659 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
...@@ -686,6 +688,7 @@ pub const VTable = struct {...@@ -686,6 +688,7 @@ pub const VTable = struct {
686 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,688 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
687 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,689 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
688 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,690 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,
689};692};
690693
691pub const Cancelable = error{694pub const Cancelable = error{
...@@ -1030,7 +1033,7 @@ pub const Group = struct {...@@ -1030,7 +1033,7 @@ pub const Group = struct {
1030 }1033 }
1031};1034};
10321035
1033pub const Mutex = if (true) struct {1036pub const Mutex = struct {
1034 state: State,1037 state: State,
10351038
1036 pub const State = enum(usize) {1039 pub const State = enum(usize) {
...@@ -1073,54 +1076,32 @@ pub const Mutex = if (true) struct {...@@ -1073,54 +1076,32 @@ pub const Mutex = if (true) struct {
1073 return io.vtable.mutexLock(io.userdata, prev_state, mutex);1076 return io.vtable.mutexLock(io.userdata, prev_state, mutex);
1074 }1077 }
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
1076 pub fn unlock(mutex: *Mutex, io: std.Io) void {1095 pub fn unlock(mutex: *Mutex, io: std.Io) void {
1077 const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse {1096 const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse {
1078 @branchHint(.likely);1097 @branchHint(.likely);
1079 return;1098 return;
1080 };1099 };
1081 std.debug.assert(prev_state != .unlocked); // mutex not locked1100 assert(prev_state != .unlocked); // mutex not locked
1082 return io.vtable.mutexUnlock(io.userdata, prev_state, mutex);1101 return io.vtable.mutexUnlock(io.userdata, prev_state, mutex);
1083 }1102 }
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 }
1120};1103};
11211104
1122/// Supports exactly 1 waiter. More than 1 simultaneous wait on the same
1123/// condition is illegal.
1124pub const Condition = struct {1105pub const Condition = struct {
1125 state: u64 = 0,1106 state: u64 = 0,
11261107
...@@ -1128,6 +1109,10 @@ pub const Condition = struct {...@@ -1128,6 +1109,10 @@ pub const Condition = struct {
1128 return io.vtable.conditionWait(io.userdata, cond, mutex);1109 return io.vtable.conditionWait(io.userdata, cond, mutex);
1129 }1110 }
11301111
1112 pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void {
1113 return io.vtable.conditionWaitUncancelable(io.userdata, cond, mutex);
1114 }
1115
1131 pub fn signal(cond: *Condition, io: Io) void {1116 pub fn signal(cond: *Condition, io: Io) void {
1132 io.vtable.conditionWake(io.userdata, cond, .one);1117 io.vtable.conditionWake(io.userdata, cond, .one);
1133 }1118 }
...@@ -1137,9 +1122,9 @@ pub const Condition = struct {...@@ -1137,9 +1122,9 @@ pub const Condition = struct {
1137 }1122 }
11381123
1139 pub const Wake = enum {1124 pub const Wake = enum {
1140 /// wake up only one thread1125 /// Wake up only one thread.
1141 one,1126 one,
1142 /// wake up all thread1127 /// Wake up all threads.
1143 all,1128 all,
1144 };1129 };
1145};1130};
...@@ -1180,10 +1165,24 @@ pub const TypeErasedQueue = struct {...@@ -1180,10 +1165,24 @@ pub const TypeErasedQueue = struct {
11801165
1181 pub fn put(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) Cancelable!usize {1166 pub fn put(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) Cancelable!usize {
1182 assert(elements.len >= min);1167 assert(elements.len >= min);
11831168 if (elements.len == 0) return 0;
1184 try q.mutex.lock(io);1169 try q.mutex.lock(io);
1185 defer q.mutex.unlock(io);1170 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 {
1187 // Getters have first priority on the data, and only when the getters1186 // Getters have first priority on the data, and only when the getters
1188 // queue is empty do we start populating the buffer.1187 // queue is empty do we start populating the buffer.
11891188
...@@ -1226,7 +1225,10 @@ pub const TypeErasedQueue = struct {...@@ -1226,7 +1225,10 @@ pub const TypeErasedQueue = struct {
12261225
1227 var pending: Put = .{ .remaining = remaining, .condition = .{}, .node = .{} };1226 var pending: Put = .{ .remaining = remaining, .condition = .{}, .node = .{} };
1228 q.putters.append(&pending.node);1227 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);
1230 remaining = pending.remaining;1232 remaining = pending.remaining;
1231 }1233 }
1232 }1234 }
...@@ -1347,6 +1349,16 @@ pub fn Queue(Elem: type) type {...@@ -1347,6 +1349,16 @@ pub fn Queue(Elem: type) type {
1347 return @divExact(try q.type_erased.put(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem));1349 return @divExact(try q.type_erased.put(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem));
1348 }1350 }
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
1350 /// Receives elements from the beginning of the queue. The function1362 /// Receives elements from the beginning of the queue. The function
1351 /// returns when at least `min` elements have been populated inside1363 /// returns when at least `min` elements have been populated inside
1352 /// `buffer`.1364 /// `buffer`.
...@@ -1362,11 +1374,20 @@ pub fn Queue(Elem: type) type {...@@ -1362,11 +1374,20 @@ pub fn Queue(Elem: type) type {
1362 assert(try q.put(io, &.{item}, 1) == 1);1374 assert(try q.put(io, &.{item}, 1) == 1);
1363 }1375 }
13641376
1377 pub fn putOneUncancelable(q: *@This(), io: Io, item: Elem) void {
1378 assert(q.putUncancelable(io, &.{item}, 1) == 1);
1379 }
1380
1365 pub fn getOne(q: *@This(), io: Io) Cancelable!Elem {1381 pub fn getOne(q: *@This(), io: Io) Cancelable!Elem {
1366 var buf: [1]Elem = undefined;1382 var buf: [1]Elem = undefined;
1367 assert(try q.get(io, &buf, 1) == 1);1383 assert(try q.get(io, &buf, 1) == 1);
1368 return buf[0];1384 return buf[0];
1369 }1385 }
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 }
1370 };1391 };
1371}1392}
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...@@ -1410,7 +1410,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o
1410 .NOMEM => return error.SystemResources,1410 .NOMEM => return error.SystemResources,
1411 .NOTCONN => return error.SocketUnconnected,1411 .NOTCONN => return error.SocketUnconnected,
1412 .CONNRESET => return error.ConnectionResetByPeer,1412 .CONNRESET => return error.ConnectionResetByPeer,
1413 .TIMEDOUT => return error.ConnectionTimedOut,1413 .TIMEDOUT => return error.Timeout,
1414 .NXIO => return error.Unseekable,1414 .NXIO => return error.Unseekable,
1415 .SPIPE => return error.Unseekable,1415 .SPIPE => return error.Unseekable,
1416 .OVERFLOW => return error.Unseekable,1416 .OVERFLOW => return error.Unseekable,
lib/std/Io/File.zig+1-1
...@@ -153,7 +153,7 @@ pub const ReadStreamingError = error{...@@ -153,7 +153,7 @@ pub const ReadStreamingError = error{
153 IsDir,153 IsDir,
154 BrokenPipe,154 BrokenPipe,
155 ConnectionResetByPeer,155 ConnectionResetByPeer,
156 ConnectionTimedOut,156 Timeout,
157 NotOpenForReading,157 NotOpenForReading,
158 SocketUnconnected,158 SocketUnconnected,
159 /// This error occurs when no global event loop is configured,159 /// 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;...@@ -8,6 +8,8 @@ const windows = std.os.windows;
8const std = @import("../std.zig");8const std = @import("../std.zig");
9const Io = std.Io;9const Io = std.Io;
10const net = std.Io.net;10const net = std.Io.net;
11const HostName = std.Io.net.HostName;
12const IpAddress = std.Io.net.IpAddress;
11const Allocator = std.mem.Allocator;13const Allocator = std.mem.Allocator;
12const assert = std.debug.assert;14const assert = std.debug.assert;
13const posix = std.posix;15const posix = std.posix;
...@@ -156,9 +158,11 @@ pub fn io(pool: *Pool) Io {...@@ -156,9 +158,11 @@ pub fn io(pool: *Pool) Io {
156 .groupCancel = groupCancel,158 .groupCancel = groupCancel,
157159
158 .mutexLock = mutexLock,160 .mutexLock = mutexLock,
161 .mutexLockUncancelable = mutexLockUncancelable,
159 .mutexUnlock = mutexUnlock,162 .mutexUnlock = mutexUnlock,
160163
161 .conditionWait = conditionWait,164 .conditionWait = conditionWait,
165 .conditionWaitUncancelable = conditionWaitUncancelable,
162 .conditionWake = conditionWake,166 .conditionWake = conditionWake,
163167
164 .dirMake = switch (builtin.os.tag) {168 .dirMake = switch (builtin.os.tag) {
...@@ -235,6 +239,7 @@ pub fn io(pool: *Pool) Io {...@@ -235,6 +239,7 @@ pub fn io(pool: *Pool) Io {
235 .netReceive = netReceive,239 .netReceive = netReceive,
236 .netInterfaceNameResolve = netInterfaceNameResolve,240 .netInterfaceNameResolve = netInterfaceNameResolve,
237 .netInterfaceName = netInterfaceName,241 .netInterfaceName = netInterfaceName,
242 .netLookup = netLookup,
238 },243 },
239 };244 };
240}245}
...@@ -653,26 +658,63 @@ fn checkCancel(pool: *Pool) error{Canceled}!void {...@@ -653,26 +658,63 @@ fn checkCancel(pool: *Pool) error{Canceled}!void {
653 if (cancelRequested(pool)) return error.Canceled;658 if (cancelRequested(pool)) return error.Canceled;
654}659}
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 {
657 _ = userdata;674 _ = userdata;
658 if (prev_state == .contended) {675 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));
660 }677 }
661 while (@atomicRmw(678 while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) {
662 Io.Mutex.State,679 futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
663 &mutex.state,
664 .Xchg,
665 .contended,
666 .acquire,
667 ) != .unlocked) {
668 std.Thread.Futex.wait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
669 }680 }
670}681}
682
671fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {683fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
672 _ = userdata;684 _ = userdata;
673 _ = prev_state;685 _ = prev_state;
674 if (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .unlocked, .release) == .contended) {686 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 }
676 }718 }
677}719}
678720
...@@ -702,20 +744,18 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I...@@ -702,20 +744,18 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
702 state += one_waiter;744 state += one_waiter;
703745
704 mutex.unlock(pool.io());746 mutex.unlock(pool.io());
705 defer mutex.lock(pool.io()) catch @panic("TODO");747 defer mutex.lockUncancelable(pool.io());
706
707 var futex_deadline = std.Thread.Futex.Deadline.init(null);
708748
709 while (true) {749 while (true) {
710 futex_deadline.wait(cond_epoch, epoch) catch |err| switch (err) {750 try pool.checkCancel();
711 error.Timeout => unreachable,751 futexWait(cond_epoch, epoch);
712 };
713752
714 epoch = cond_epoch.load(.acquire);753 epoch = cond_epoch.load(.acquire);
715 state = cond_state.load(.monotonic);754 state = cond_state.load(.monotonic);
716755
717 // Try to wake up by consuming a signal and decremented the waiter we added previously.756 // Try to wake up by consuming a signal and decremented the waiter we
718 // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return.757 // added previously. Acquire barrier ensures code before the wake()
758 // which added the signal happens before we decrement it and return.
719 while (state & signal_mask != 0) {759 while (state & signal_mask != 0) {
720 const new_state = state - one_waiter - one_signal;760 const new_state = state - one_waiter - one_signal;
721 state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return;761 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....@@ -740,8 +780,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
740 const signals = (state & signal_mask) / one_signal;780 const signals = (state & signal_mask) / one_signal;
741781
742 // Reserves which waiters to wake up by incrementing the signals count.782 // 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.783 // Therefore, the signals count is always less than or equal to the
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.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.
745 const wakeable = waiters - signals;787 const wakeable = waiters - signals;
746 if (wakeable == 0) {788 if (wakeable == 0) {
747 return;789 return;
...@@ -752,16 +794,23 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition....@@ -752,16 +794,23 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
752 .all => wakeable,794 .all => wakeable,
753 };795 };
754796
755 // Reserve the amount of waiters to wake by incrementing the signals count.797 // Reserve the amount of waiters to wake by incrementing the signals
756 // Release barrier ensures code before the wake() happens before the signal it posted and consumed by the wait() threads.798 // count. Release barrier ensures code before the wake() happens before
799 // the signal it posted and consumed by the wait() threads.
757 const new_state = state + (one_signal * to_wake);800 const new_state = state + (one_signal * to_wake);
758 state = cond_state.cmpxchgWeak(state, new_state, .release, .monotonic) orelse {801 state = cond_state.cmpxchgWeak(state, new_state, .release, .monotonic) orelse {
759 // Wake up the waiting threads we reserved above by changing the epoch value.802 // 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.
762 //803 //
763 // Release barrier ensures the signal being added to the state happens before the epoch is changed.804 // A waiting thread could miss a wake up if *exactly* ((1<<32)-1)
764 // If not, the waiting thread could potentially deadlock from missing both the state and epoch change: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:
765 //814 //
766 // - T2: UPDATE(&epoch, 1) (reordered before the state change)815 // - T2: UPDATE(&epoch, 1) (reordered before the state change)
767 // - T1: e = LOAD(&epoch)816 // - T1: e = LOAD(&epoch)
...@@ -769,7 +818,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition....@@ -769,7 +818,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
769 // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch)818 // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch)
770 // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change)819 // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change)
771 _ = cond_epoch.fetchAdd(1, .release);820 _ = cond_epoch.fetchAdd(1, .release);
772 std.Thread.Futex.wake(cond_epoch, to_wake);821 futexWake(cond_epoch, to_wake);
773 return;822 return;
774 };823 };
775 }824 }
...@@ -1298,7 +1347,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File...@@ -1298,7 +1347,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
1298 .NOMEM => return error.SystemResources,1347 .NOMEM => return error.SystemResources,
1299 .NOTCONN => return error.SocketUnconnected,1348 .NOTCONN => return error.SocketUnconnected,
1300 .CONNRESET => return error.ConnectionResetByPeer,1349 .CONNRESET => return error.ConnectionResetByPeer,
1301 .TIMEDOUT => return error.ConnectionTimedOut,1350 .TIMEDOUT => return error.Timeout,
1302 .NOTCAPABLE => return error.AccessDenied,1351 .NOTCAPABLE => return error.AccessDenied,
1303 else => |err| return posix.unexpectedErrno(err),1352 else => |err| return posix.unexpectedErrno(err),
1304 }1353 }
...@@ -1321,7 +1370,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File...@@ -1321,7 +1370,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
1321 .NOMEM => return error.SystemResources,1370 .NOMEM => return error.SystemResources,
1322 .NOTCONN => return error.SocketUnconnected,1371 .NOTCONN => return error.SocketUnconnected,
1323 .CONNRESET => return error.ConnectionResetByPeer,1372 .CONNRESET => return error.ConnectionResetByPeer,
1324 .TIMEDOUT => return error.ConnectionTimedOut,1373 .TIMEDOUT => return error.Timeout,
1325 else => |err| return posix.unexpectedErrno(err),1374 else => |err| return posix.unexpectedErrno(err),
1326 }1375 }
1327 }1376 }
...@@ -1420,7 +1469,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset...@@ -1420,7 +1469,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
1420 .NOMEM => return error.SystemResources,1469 .NOMEM => return error.SystemResources,
1421 .NOTCONN => return error.SocketUnconnected,1470 .NOTCONN => return error.SocketUnconnected,
1422 .CONNRESET => return error.ConnectionResetByPeer,1471 .CONNRESET => return error.ConnectionResetByPeer,
1423 .TIMEDOUT => return error.ConnectionTimedOut,1472 .TIMEDOUT => return error.Timeout,
1424 .NXIO => return error.Unseekable,1473 .NXIO => return error.Unseekable,
1425 .SPIPE => return error.Unseekable,1474 .SPIPE => return error.Unseekable,
1426 .OVERFLOW => return error.Unseekable,1475 .OVERFLOW => return error.Unseekable,
...@@ -1446,7 +1495,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset...@@ -1446,7 +1495,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
1446 .NOMEM => return error.SystemResources,1495 .NOMEM => return error.SystemResources,
1447 .NOTCONN => return error.SocketUnconnected,1496 .NOTCONN => return error.SocketUnconnected,
1448 .CONNRESET => return error.ConnectionResetByPeer,1497 .CONNRESET => return error.ConnectionResetByPeer,
1449 .TIMEDOUT => return error.ConnectionTimedOut,1498 .TIMEDOUT => return error.Timeout,
1450 .NXIO => return error.Unseekable,1499 .NXIO => return error.Unseekable,
1451 .SPIPE => return error.Unseekable,1500 .SPIPE => return error.Unseekable,
1452 .OVERFLOW => return error.Unseekable,1501 .OVERFLOW => return error.Unseekable,
...@@ -1693,9 +1742,9 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {...@@ -1693,9 +1742,9 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
16931742
1694fn netListenIpPosix(1743fn netListenIpPosix(
1695 userdata: ?*anyopaque,1744 userdata: ?*anyopaque,
1696 address: net.IpAddress,1745 address: IpAddress,
1697 options: net.IpAddress.ListenOptions,1746 options: IpAddress.ListenOptions,
1698) net.IpAddress.ListenError!net.Server {1747) IpAddress.ListenError!net.Server {
1699 const pool: *Pool = @ptrCast(@alignCast(userdata));1748 const pool: *Pool = @ptrCast(@alignCast(userdata));
1700 const family = posixAddressFamily(&address);1749 const family = posixAddressFamily(&address);
1701 const socket_fd = try openSocketPosix(pool, family, .{1750 const socket_fd = try openSocketPosix(pool, family, .{
...@@ -1831,7 +1880,7 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka...@@ -1831,7 +1880,7 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka
1831 .NETUNREACH => return error.NetworkUnreachable,1880 .NETUNREACH => return error.NetworkUnreachable,
1832 .NOTSOCK => |err| return errnoBug(err),1881 .NOTSOCK => |err| return errnoBug(err),
1833 .PROTOTYPE => |err| return errnoBug(err),1882 .PROTOTYPE => |err| return errnoBug(err),
1834 .TIMEDOUT => return error.ConnectionTimedOut,1883 .TIMEDOUT => return error.Timeout,
1835 .CONNABORTED => |err| return errnoBug(err),1884 .CONNABORTED => |err| return errnoBug(err),
1836 .ACCES => return error.AccessDenied,1885 .ACCES => return error.AccessDenied,
1837 .PERM => |err| return errnoBug(err),1886 .PERM => |err| return errnoBug(err),
...@@ -1904,9 +1953,9 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio...@@ -1904,9 +1953,9 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio
19041953
1905fn netConnectIpPosix(1954fn netConnectIpPosix(
1906 userdata: ?*anyopaque,1955 userdata: ?*anyopaque,
1907 address: *const net.IpAddress,1956 address: *const IpAddress,
1908 options: net.IpAddress.ConnectOptions,1957 options: IpAddress.ConnectOptions,
1909) net.IpAddress.ConnectError!net.Stream {1958) IpAddress.ConnectError!net.Stream {
1910 if (options.timeout != .none) @panic("TODO");1959 if (options.timeout != .none) @panic("TODO");
1911 const pool: *Pool = @ptrCast(@alignCast(userdata));1960 const pool: *Pool = @ptrCast(@alignCast(userdata));
1912 const family = posixAddressFamily(address);1961 const family = posixAddressFamily(address);
...@@ -1941,9 +1990,9 @@ fn netConnectUnix(...@@ -1941,9 +1990,9 @@ fn netConnectUnix(
19411990
1942fn netBindIpPosix(1991fn netBindIpPosix(
1943 userdata: ?*anyopaque,1992 userdata: ?*anyopaque,
1944 address: *const net.IpAddress,1993 address: *const IpAddress,
1945 options: net.IpAddress.BindOptions,1994 options: IpAddress.BindOptions,
1946) net.IpAddress.BindError!net.Socket {1995) IpAddress.BindError!net.Socket {
1947 const pool: *Pool = @ptrCast(@alignCast(userdata));1996 const pool: *Pool = @ptrCast(@alignCast(userdata));
1948 const family = posixAddressFamily(address);1997 const family = posixAddressFamily(address);
1949 const socket_fd = try openSocketPosix(pool, family, options);1998 const socket_fd = try openSocketPosix(pool, family, options);
...@@ -1958,7 +2007,7 @@ fn netBindIpPosix(...@@ -1958,7 +2007,7 @@ fn netBindIpPosix(
1958 };2007 };
1959}2008}
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 {
1962 const mode = posixSocketMode(options.mode);2011 const mode = posixSocketMode(options.mode);
1963 const protocol = posixProtocol(options.protocol);2012 const protocol = posixProtocol(options.protocol);
1964 const socket_fd = while (true) {2013 const socket_fd = while (true) {
...@@ -2081,7 +2130,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net....@@ -2081,7 +2130,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
2081 .NOMEM => return error.SystemResources,2130 .NOMEM => return error.SystemResources,
2082 .NOTCONN => return error.SocketUnconnected,2131 .NOTCONN => return error.SocketUnconnected,
2083 .CONNRESET => return error.ConnectionResetByPeer,2132 .CONNRESET => return error.ConnectionResetByPeer,
2084 .TIMEDOUT => return error.ConnectionTimedOut,2133 .TIMEDOUT => return error.Timeout,
2085 .NOTCAPABLE => return error.AccessDenied,2134 .NOTCAPABLE => return error.AccessDenied,
2086 else => |err| return posix.unexpectedErrno(err),2135 else => |err| return posix.unexpectedErrno(err),
2087 }2136 }
...@@ -2102,7 +2151,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net....@@ -2102,7 +2151,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
2102 .NOMEM => return error.SystemResources,2151 .NOMEM => return error.SystemResources,
2103 .NOTCONN => return error.SocketUnconnected,2152 .NOTCONN => return error.SocketUnconnected,
2104 .CONNRESET => return error.ConnectionResetByPeer,2153 .CONNRESET => return error.ConnectionResetByPeer,
2105 .TIMEDOUT => return error.ConnectionTimedOut,2154 .TIMEDOUT => return error.Timeout,
2106 .PIPE => return error.BrokenPipe,2155 .PIPE => return error.BrokenPipe,
2107 .NETDOWN => return error.NetworkDown,2156 .NETDOWN => return error.NetworkDown,
2108 else => |err| return posix.unexpectedErrno(err),2157 else => |err| return posix.unexpectedErrno(err),
...@@ -2563,6 +2612,118 @@ fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interfa...@@ -2563,6 +2612,118 @@ fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interfa
2563 @panic("unimplemented");2612 @panic("unimplemented");
2564}2613}
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
2566const PosixAddress = extern union {2727const PosixAddress = extern union {
2567 any: posix.sockaddr,2728 any: posix.sockaddr,
2568 in: posix.sockaddr.in,2729 in: posix.sockaddr.in,
...@@ -2574,14 +2735,14 @@ const UnixAddress = extern union {...@@ -2574,14 +2735,14 @@ const UnixAddress = extern union {
2574 un: posix.sockaddr.un,2735 un: posix.sockaddr.un,
2575};2736};
25762737
2577fn posixAddressFamily(a: *const net.IpAddress) posix.sa_family_t {2738fn posixAddressFamily(a: *const IpAddress) posix.sa_family_t {
2578 return switch (a.*) {2739 return switch (a.*) {
2579 .ip4 => posix.AF.INET,2740 .ip4 => posix.AF.INET,
2580 .ip6 => posix.AF.INET6,2741 .ip6 => posix.AF.INET6,
2581 };2742 };
2582}2743}
25832744
2584fn addressFromPosix(posix_address: *PosixAddress) net.IpAddress {2745fn addressFromPosix(posix_address: *PosixAddress) IpAddress {
2585 return switch (posix_address.any.family) {2746 return switch (posix_address.any.family) {
2586 posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) },2747 posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) },
2587 posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) },2748 posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) },
...@@ -2589,7 +2750,7 @@ fn addressFromPosix(posix_address: *PosixAddress) net.IpAddress {...@@ -2589,7 +2750,7 @@ fn addressFromPosix(posix_address: *PosixAddress) net.IpAddress {
2589 };2750 };
2590}2751}
25912752
2592fn addressToPosix(a: *const net.IpAddress, storage: *PosixAddress) posix.socklen_t {2753fn addressToPosix(a: *const IpAddress, storage: *PosixAddress) posix.socklen_t {
2593 return switch (a.*) {2754 return switch (a.*) {
2594 .ip4 => |ip4| {2755 .ip4 => |ip4| {
2595 storage.in = address4ToPosix(ip4);2756 storage.in = address4ToPosix(ip4);
...@@ -2789,3 +2950,436 @@ fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNa...@@ -2789,3 +2950,436 @@ fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNa
2789 buffer[file_path.len] = 0;2950 buffer[file_path.len] = 0;
2790 return buffer[0..file_path.len :0];2951 return buffer[0..file_path.len :0];
2791}2952}
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) {...@@ -281,7 +281,6 @@ pub const IpAddress = union(enum) {
281 }281 }
282282
283 pub const ConnectError = error{283 pub const ConnectError = error{
284 AddressInUse,
285 AddressUnavailable,284 AddressUnavailable,
286 AddressFamilyUnsupported,285 AddressFamilyUnsupported,
287 /// Insufficient memory or other resource internal to the operating system.286 /// Insufficient memory or other resource internal to the operating system.
...@@ -291,7 +290,7 @@ pub const IpAddress = union(enum) {...@@ -291,7 +290,7 @@ pub const IpAddress = union(enum) {
291 ConnectionResetByPeer,290 ConnectionResetByPeer,
292 HostUnreachable,291 HostUnreachable,
293 NetworkUnreachable,292 NetworkUnreachable,
294 ConnectionTimedOut,293 Timeout,
295 /// One of the `ConnectOptions` is not supported by the Io294 /// One of the `ConnectOptions` is not supported by the Io
296 /// implementation.295 /// implementation.
297 OptionUnsupported,296 OptionUnsupported,
...@@ -1165,7 +1164,7 @@ pub const Stream = struct {...@@ -1165,7 +1164,7 @@ pub const Stream = struct {
1165 SystemResources,1164 SystemResources,
1166 BrokenPipe,1165 BrokenPipe,
1167 ConnectionResetByPeer,1166 ConnectionResetByPeer,
1168 ConnectionTimedOut,1167 Timeout,
1169 SocketUnconnected,1168 SocketUnconnected,
1170 /// The file descriptor does not hold the required rights to read1169 /// The file descriptor does not hold the required rights to read
1171 /// from it.1170 /// from it.
lib/std/Io/net/HostName.zig+48-504
...@@ -63,8 +63,6 @@ pub fn eql(a: HostName, b: HostName) bool {...@@ -63,8 +63,6 @@ pub fn eql(a: HostName, b: HostName) bool {
6363
64pub const LookupOptions = struct {64pub const LookupOptions = struct {
65 port: u16,65 port: u16,
66 /// Must have at least length 2.
67 addresses_buffer: []IpAddress,
68 canonical_name_buffer: *[max_len]u8,66 canonical_name_buffer: *[max_len]u8,
69 /// `null` means either.67 /// `null` means either.
70 family: ?IpAddress.Family = null,68 family: ?IpAddress.Family = null,
...@@ -81,487 +79,23 @@ pub const LookupError = error{...@@ -81,487 +79,23 @@ pub const LookupError = error{
81 DetectingNetworkConfigurationFailed,79 DetectingNetworkConfigurationFailed,
82} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable;80} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable;
8381
84pub const LookupResult = struct {82pub const LookupResult = union(enum) {
85 /// How many `LookupOptions.addresses_buffer` elements are populated.83 address: IpAddress,
86 addresses_len: usize,
87 canonical_name: HostName,84 canonical_name: HostName,
8885 end: LookupError!void,
89 pub const empty: LookupResult = .{
90 .addresses_len = 0,
91 .canonical_name = undefined,
92 };
93};86};
9487
95pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {88/// Adds any number of `IpAddress` into resolved, exactly one canonical_name,
96 const name = host_name.bytes;89/// and then always finishes by adding one `LookupResult.end` entry.
97 assert(name.len <= max_len);90///
98 assert(options.addresses_buffer.len >= 2);91/// Guaranteed not to block if provided queue has capacity at least 8.
9992pub fn lookup(
100 if (native_os == .windows) @compileError("TODO");93 host_name: HostName,
101 if (builtin.link_libc) @compileError("TODO");94 io: Io,
102 if (native_os == .linux) {95 resolved: *Io.Queue(LookupResult),
103 if (options.family != .ip6) {96 options: LookupOptions,
104 if (IpAddress.parseIp4(name, options.port)) |addr| {97) void {
105 options.addresses_buffer[0] = addr;98 return io.vtable.netLookup(io.userdata, host_name, resolved, options);
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;
565}99}
566100
567pub const ExpandError = error{InvalidDnsPacket} || ValidateError;101pub const ExpandError = error{InvalidDnsPacket} || ValidateError;
...@@ -672,33 +206,43 @@ pub fn connect(...@@ -672,33 +206,43 @@ pub fn connect(
672 port: u16,206 port: u16,
673 options: IpAddress.ConnectOptions,207 options: IpAddress.ConnectOptions,
674) ConnectError!Stream {208) ConnectError!Stream {
675 var addresses_buffer: [32]IpAddress = undefined;209 var canonical_name_buffer: [max_len]u8 = undefined;
676 var canonical_name_buffer: [HostName.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, .{
679 .port = port,214 .port = port,
680 .addresses_buffer = &addresses_buffer,
681 .canonical_name_buffer = &canonical_name_buffer,215 .canonical_name_buffer = &canonical_name_buffer,
682 });216 } });
683 const addresses = addresses_buffer[0..results.addresses_len];217 defer lookup_task.cancel(io);
684218
685 if (addresses.len == 0) return error.UnknownHostName;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 out231 var aggregate_error: ConnectError = error.UnknownHostName;
688 // the connections simultaneously and then keep the first
689 // successful one, canceling the rest.
690232
691 // TODO On Linux this should additionally use an Io.Queue based233 while (select.remaining != 0) switch (select.wait(io)) {
692 // DNS resolution API in order to send out a connection after234 .ip_connect => |ip_connect| if (ip_connect) |stream| return stream else |err| switch (err) {
693 // each DNS response before waiting for the rest of them.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| {245 return aggregate_error;
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;
702}246}
703247
704pub const ResolvConf = struct {248pub const ResolvConf = struct {
...@@ -713,7 +257,7 @@ pub const ResolvConf = struct {...@@ -713,7 +257,7 @@ pub const ResolvConf = struct {
713 pub const max_nameservers = 3;257 pub const max_nameservers = 3;
714258
715 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.259 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
716 fn init(io: Io) !ResolvConf {260 pub fn init(io: Io) !ResolvConf {
717 var rc: ResolvConf = .{261 var rc: ResolvConf = .{
718 .nameservers_buffer = undefined,262 .nameservers_buffer = undefined,
719 .nameservers_len = 0,263 .nameservers_len = 0,
...@@ -749,7 +293,7 @@ pub const ResolvConf = struct {...@@ -749,7 +293,7 @@ pub const ResolvConf = struct {
749 const Directive = enum { options, nameserver, domain, search };293 const Directive = enum { options, nameserver, domain, search };
750 const Option = enum { ndots, attempts, timeout };294 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 {
753 while (reader.takeSentinel('\n')) |line_with_comment| {297 while (reader.takeSentinel('\n')) |line_with_comment| {
754 const line = line: {298 const line = line: {
755 var split = std.mem.splitScalar(u8, line_with_comment, '#');299 var split = std.mem.splitScalar(u8, line_with_comment, '#');
...@@ -799,7 +343,7 @@ pub const ResolvConf = struct {...@@ -799,7 +343,7 @@ pub const ResolvConf = struct {
799 rc.nameservers_len += 1;343 rc.nameservers_len += 1;
800 }344 }
801345
802 fn nameservers(rc: *const ResolvConf) []const IpAddress {346 pub fn nameservers(rc: *const ResolvConf) []const IpAddress {
803 return rc.nameservers_buffer[0..rc.nameservers_len];347 return rc.nameservers_buffer[0..rc.nameservers_len];
804 }348 }
805};349};
lib/std/posix.zig+15-15
...@@ -845,7 +845,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -845,7 +845,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
845 .NOMEM => return error.SystemResources,845 .NOMEM => return error.SystemResources,
846 .NOTCONN => return error.SocketUnconnected,846 .NOTCONN => return error.SocketUnconnected,
847 .CONNRESET => return error.ConnectionResetByPeer,847 .CONNRESET => return error.ConnectionResetByPeer,
848 .TIMEDOUT => return error.ConnectionTimedOut,848 .TIMEDOUT => return error.Timeout,
849 .NOTCAPABLE => return error.AccessDenied,849 .NOTCAPABLE => return error.AccessDenied,
850 else => |err| return unexpectedErrno(err),850 else => |err| return unexpectedErrno(err),
851 }851 }
...@@ -874,7 +874,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -874,7 +874,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
874 .NOMEM => return error.SystemResources,874 .NOMEM => return error.SystemResources,
875 .NOTCONN => return error.SocketUnconnected,875 .NOTCONN => return error.SocketUnconnected,
876 .CONNRESET => return error.ConnectionResetByPeer,876 .CONNRESET => return error.ConnectionResetByPeer,
877 .TIMEDOUT => return error.ConnectionTimedOut,877 .TIMEDOUT => return error.Timeout,
878 else => |err| return unexpectedErrno(err),878 else => |err| return unexpectedErrno(err),
879 }879 }
880 }880 }
...@@ -914,7 +914,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -914,7 +914,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
914 .NOMEM => return error.SystemResources,914 .NOMEM => return error.SystemResources,
915 .NOTCONN => return error.SocketUnconnected,915 .NOTCONN => return error.SocketUnconnected,
916 .CONNRESET => return error.ConnectionResetByPeer,916 .CONNRESET => return error.ConnectionResetByPeer,
917 .TIMEDOUT => return error.ConnectionTimedOut,917 .TIMEDOUT => return error.Timeout,
918 .NOTCAPABLE => return error.AccessDenied,918 .NOTCAPABLE => return error.AccessDenied,
919 else => |err| return unexpectedErrno(err),919 else => |err| return unexpectedErrno(err),
920 }920 }
...@@ -936,7 +936,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -936,7 +936,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
936 .NOMEM => return error.SystemResources,936 .NOMEM => return error.SystemResources,
937 .NOTCONN => return error.SocketUnconnected,937 .NOTCONN => return error.SocketUnconnected,
938 .CONNRESET => return error.ConnectionResetByPeer,938 .CONNRESET => return error.ConnectionResetByPeer,
939 .TIMEDOUT => return error.ConnectionTimedOut,939 .TIMEDOUT => return error.Timeout,
940 else => |err| return unexpectedErrno(err),940 else => |err| return unexpectedErrno(err),
941 }941 }
942 }942 }
...@@ -983,7 +983,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {...@@ -983,7 +983,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
983 .NOMEM => return error.SystemResources,983 .NOMEM => return error.SystemResources,
984 .NOTCONN => return error.SocketUnconnected,984 .NOTCONN => return error.SocketUnconnected,
985 .CONNRESET => return error.ConnectionResetByPeer,985 .CONNRESET => return error.ConnectionResetByPeer,
986 .TIMEDOUT => return error.ConnectionTimedOut,986 .TIMEDOUT => return error.Timeout,
987 .NXIO => return error.Unseekable,987 .NXIO => return error.Unseekable,
988 .SPIPE => return error.Unseekable,988 .SPIPE => return error.Unseekable,
989 .OVERFLOW => return error.Unseekable,989 .OVERFLOW => return error.Unseekable,
...@@ -1016,7 +1016,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {...@@ -1016,7 +1016,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
1016 .NOMEM => return error.SystemResources,1016 .NOMEM => return error.SystemResources,
1017 .NOTCONN => return error.SocketUnconnected,1017 .NOTCONN => return error.SocketUnconnected,
1018 .CONNRESET => return error.ConnectionResetByPeer,1018 .CONNRESET => return error.ConnectionResetByPeer,
1019 .TIMEDOUT => return error.ConnectionTimedOut,1019 .TIMEDOUT => return error.Timeout,
1020 .NXIO => return error.Unseekable,1020 .NXIO => return error.Unseekable,
1021 .SPIPE => return error.Unseekable,1021 .SPIPE => return error.Unseekable,
1022 .OVERFLOW => return error.Unseekable,1022 .OVERFLOW => return error.Unseekable,
...@@ -1134,7 +1134,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {...@@ -1134,7 +1134,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
1134 .NOMEM => return error.SystemResources,1134 .NOMEM => return error.SystemResources,
1135 .NOTCONN => return error.SocketUnconnected,1135 .NOTCONN => return error.SocketUnconnected,
1136 .CONNRESET => return error.ConnectionResetByPeer,1136 .CONNRESET => return error.ConnectionResetByPeer,
1137 .TIMEDOUT => return error.ConnectionTimedOut,1137 .TIMEDOUT => return error.Timeout,
1138 .NXIO => return error.Unseekable,1138 .NXIO => return error.Unseekable,
1139 .SPIPE => return error.Unseekable,1139 .SPIPE => return error.Unseekable,
1140 .OVERFLOW => return error.Unseekable,1140 .OVERFLOW => return error.Unseekable,
...@@ -1160,7 +1160,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {...@@ -1160,7 +1160,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
1160 .NOMEM => return error.SystemResources,1160 .NOMEM => return error.SystemResources,
1161 .NOTCONN => return error.SocketUnconnected,1161 .NOTCONN => return error.SocketUnconnected,
1162 .CONNRESET => return error.ConnectionResetByPeer,1162 .CONNRESET => return error.ConnectionResetByPeer,
1163 .TIMEDOUT => return error.ConnectionTimedOut,1163 .TIMEDOUT => return error.Timeout,
1164 .NXIO => return error.Unseekable,1164 .NXIO => return error.Unseekable,
1165 .SPIPE => return error.Unseekable,1165 .SPIPE => return error.Unseekable,
1166 .OVERFLOW => return error.Unseekable,1166 .OVERFLOW => return error.Unseekable,
...@@ -4205,7 +4205,7 @@ pub const ConnectError = error{...@@ -4205,7 +4205,7 @@ pub const ConnectError = error{
42054205
4206 /// Timeout while attempting connection. The server may be too busy to accept new connections. Note4206 /// Timeout while attempting connection. The server may be too busy to accept new connections. Note
4207 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.4207 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
4208 ConnectionTimedOut,4208 Timeout,
42094209
4210 /// This error occurs when no global event loop is configured,4210 /// This error occurs when no global event loop is configured,
4211 /// and connecting to the socket would block.4211 /// 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...@@ -4236,7 +4236,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
4236 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,4236 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
4237 .WSAECONNREFUSED => return error.ConnectionRefused,4237 .WSAECONNREFUSED => return error.ConnectionRefused,
4238 .WSAECONNRESET => return error.ConnectionResetByPeer,4238 .WSAECONNRESET => return error.ConnectionResetByPeer,
4239 .WSAETIMEDOUT => return error.ConnectionTimedOut,4239 .WSAETIMEDOUT => return error.Timeout,
4240 .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well?4240 .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well?
4241 .WSAENETUNREACH,4241 .WSAENETUNREACH,
4242 => return error.NetworkUnreachable,4242 => return error.NetworkUnreachable,
...@@ -4273,7 +4273,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne...@@ -4273,7 +4273,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
4273 .NETUNREACH => return error.NetworkUnreachable,4273 .NETUNREACH => return error.NetworkUnreachable,
4274 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.4274 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
4275 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.4275 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
4276 .TIMEDOUT => return error.ConnectionTimedOut,4276 .TIMEDOUT => return error.Timeout,
4277 .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist.4277 .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist.
4278 .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused.4278 .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused.
4279 else => |err| return unexpectedErrno(err),4279 else => |err| return unexpectedErrno(err),
...@@ -4333,7 +4333,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {...@@ -4333,7 +4333,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
4333 .NETUNREACH => return error.NetworkUnreachable,4333 .NETUNREACH => return error.NetworkUnreachable,
4334 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.4334 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
4335 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.4335 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
4336 .TIMEDOUT => return error.ConnectionTimedOut,4336 .TIMEDOUT => return error.Timeout,
4337 .CONNRESET => return error.ConnectionResetByPeer,4337 .CONNRESET => return error.ConnectionResetByPeer,
4338 else => |err| return unexpectedErrno(err),4338 else => |err| return unexpectedErrno(err),
4339 },4339 },
...@@ -6465,7 +6465,7 @@ pub const RecvFromError = error{...@@ -6465,7 +6465,7 @@ pub const RecvFromError = error{
6465 SystemResources,6465 SystemResources,
64666466
6467 ConnectionResetByPeer,6467 ConnectionResetByPeer,
6468 ConnectionTimedOut,6468 Timeout,
64696469
6470 /// The socket has not been bound.6470 /// The socket has not been bound.
6471 SocketNotBound,6471 SocketNotBound,
...@@ -6508,7 +6508,7 @@ pub fn recvfrom(...@@ -6508,7 +6508,7 @@ pub fn recvfrom(
6508 .WSAENETDOWN => return error.NetworkDown,6508 .WSAENETDOWN => return error.NetworkDown,
6509 .WSAENOTCONN => return error.SocketUnconnected,6509 .WSAENOTCONN => return error.SocketUnconnected,
6510 .WSAEWOULDBLOCK => return error.WouldBlock,6510 .WSAEWOULDBLOCK => return error.WouldBlock,
6511 .WSAETIMEDOUT => return error.ConnectionTimedOut,6511 .WSAETIMEDOUT => return error.Timeout,
6512 // TODO: handle more errors6512 // TODO: handle more errors
6513 else => |err| return windows.unexpectedWSAError(err),6513 else => |err| return windows.unexpectedWSAError(err),
6514 }6514 }
...@@ -6528,7 +6528,7 @@ pub fn recvfrom(...@@ -6528,7 +6528,7 @@ pub fn recvfrom(
6528 .NOMEM => return error.SystemResources,6528 .NOMEM => return error.SystemResources,
6529 .CONNREFUSED => return error.ConnectionRefused,6529 .CONNREFUSED => return error.ConnectionRefused,
6530 .CONNRESET => return error.ConnectionResetByPeer,6530 .CONNRESET => return error.ConnectionResetByPeer,
6531 .TIMEDOUT => return error.ConnectionTimedOut,6531 .TIMEDOUT => return error.Timeout,
6532 .PIPE => return error.BrokenPipe,6532 .PIPE => return error.BrokenPipe,
6533 else => |err| return unexpectedErrno(err),6533 else => |err| return unexpectedErrno(err),
6534 }6534 }
lib/std/zig/system.zig+1-1
...@@ -428,7 +428,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {...@@ -428,7 +428,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
428 error.WouldBlock => return error.Unexpected,428 error.WouldBlock => return error.Unexpected,
429 error.BrokenPipe => return error.Unexpected,429 error.BrokenPipe => return error.Unexpected,
430 error.ConnectionResetByPeer => return error.Unexpected,430 error.ConnectionResetByPeer => return error.Unexpected,
431 error.ConnectionTimedOut => return error.Unexpected,431 error.Timeout => return error.Unexpected,
432 error.NotOpenForReading => return error.Unexpected,432 error.NotOpenForReading => return error.Unexpected,
433 error.SocketUnconnected => return error.Unexpected,433 error.SocketUnconnected => return error.Unexpected,
434434