lib/sys/src/net.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const capabilities = @import("capabilities.zig");
4 const fd = @import("fd.zig");
5 const random = @import("random.zig");
6
7 const linux = std.os.linux;
8 const posix = std.posix;
9 const system = posix.system;
10 const native_os = builtin.os.tag;
11
12 pub const required_capabilities = switch (native_os) {
13 .linux => capabilities.noLibc(&.{ .networking, .descriptors, .process, .random, .time }),
14 else => capabilities.host(&.{ .networking, .descriptors, .process, .random, .time }),
15 };
16
17 pub const Socket = posix.socket_t;
18 pub const Ip4Address = posix.sockaddr.in;
19 pub const Ip6Address = posix.sockaddr.in6;
20 pub const IpAddress = union(enum) {
21 ip4: Ip4Address,
22 ip6: Ip6Address,
23 };
24 pub const SocketAddress = posix.sockaddr;
25 pub const SocketAddressLength = posix.socklen_t;
26 pub const unix_path_capacity = @sizeOf(@FieldType(posix.sockaddr.un, "path"));
27 pub const readiness_max: usize = 64;
28
29 const readable_mask = posix.POLL.IN | posix.POLL.HUP | posix.POLL.ERR;
30 const writable_mask = posix.POLL.OUT | posix.POLL.HUP | posix.POLL.ERR;
31
32 pub const PollInterest = enum {
33 read,
34 write,
35 };
36
37 const AddressStorage = extern union {
38 storage: posix.sockaddr.storage,
39 any: SocketAddress,
40 ip4: Ip4Address,
41 ip6: Ip6Address,
42 unix: posix.sockaddr.un,
43 };
44
45 pub const SocketOptions = struct {
46 nonblocking: bool = false,
47 close_on_exec: bool = false,
48 };
49
50 pub const SocketPolicy = enum {
51 unsupported,
52 linux_syscall,
53 posix_host,
54 };
55
56 pub const PeerCredentialsPolicy = enum {
57 unsupported,
58 linux_so_peercred,
59 };
60
61 pub const PeerCredentials = extern struct {
62 process_id: linux.pid_t,
63 user_id: linux.uid_t,
64 group_id: linux.gid_t,
65 };
66
67 pub const SocketError = error{
68 UnsupportedPlatform,
69 AddressFamilyUnsupported,
70 ProtocolUnsupportedBySystem,
71 ProcessFdQuotaExceeded,
72 SystemFdQuotaExceeded,
73 SystemResources,
74 ProtocolUnsupportedByAddressFamily,
75 SocketModeUnsupported,
76 SocketFailed,
77 };
78
79 pub const AddressError = error{InvalidAddress};
80 pub const UnixAddressError = error{InvalidPath};
81
82 pub const Address = struct {
83 storage: AddressStorage,
84 len: SocketAddressLength,
85
86 pub const ListenOptions = struct {
87 backlog: u32 = 128,
88 reuse_address: bool = false,
89 nonblocking: bool = false,
90 close_on_exec: bool = true,
91 };
92
93 pub const ListenAddressError = SocketError || SetOptionError || BindError || ListenError || SocketNameError;
94
95 pub fn empty() Address {
96 return .{
97 .storage = undefined,
98 .len = @intCast(@sizeOf(posix.sockaddr.storage)),
99 };
100 }
101
102 pub fn parseIp(host: []const u8, port: u16) AddressError!Address {
103 return fromIpAddress(try ipAddressForHost(host, port));
104 }
105
106 pub fn parseIp4(host: []const u8, port: u16) AddressError!Address {
107 return fromIp4Address(try ip4AddressForHost(host, port));
108 }
109
110 pub fn fromIpAddress(address: IpAddress) Address {
111 return switch (address) {
112 .ip4 => |ip4| fromIp4Address(ip4),
113 .ip6 => |ip6| fromIp6Address(ip6),
114 };
115 }
116
117 pub fn fromIp4Address(address: Ip4Address) Address {
118 var result = Address{
119 .storage = undefined,
120 .len = @intCast(@sizeOf(Ip4Address)),
121 };
122 result.storage.ip4 = address;
123 return result;
124 }
125
126 pub fn fromIp6Address(address: Ip6Address) Address {
127 var result = Address{
128 .storage = undefined,
129 .len = @intCast(@sizeOf(Ip6Address)),
130 };
131 result.storage.ip6 = address;
132 return result;
133 }
134
135 pub fn initUnix(path: []const u8) UnixAddressError!Address {
136 var result = Address{
137 .storage = undefined,
138 .len = undefined,
139 };
140 if (path.len > result.storage.unix.path.len) return error.InvalidPath;
141 result.storage.unix.family = posix.AF.UNIX;
142 @memset(&result.storage.unix.path, 0);
143 @memcpy(result.storage.unix.path[0..path.len], path);
144 const path_len = if (path.len < result.storage.unix.path.len) path.len + 1 else path.len;
145 result.len = @intCast(@offsetOf(posix.sockaddr.un, "path") + path_len);
146 return result;
147 }
148
149 pub fn initUnixAbstract(name: []const u8) UnixAddressError!Address {
150 var result = Address{
151 .storage = undefined,
152 .len = undefined,
153 };
154 if (name.len + 1 > result.storage.unix.path.len) return error.InvalidPath;
155 result.storage.unix.family = posix.AF.UNIX;
156 @memset(&result.storage.unix.path, 0);
157 @memcpy(result.storage.unix.path[1 .. 1 + name.len], name);
158 result.len = @intCast(@offsetOf(posix.sockaddr.un, "path") + 1 + name.len);
159 return result;
160 }
161
162 pub fn family(self: *const Address) u32 {
163 return @intCast(self.storage.any.family);
164 }
165
166 pub fn socketAddress(self: *const Address) *const SocketAddress {
167 return &self.storage.any;
168 }
169
170 pub fn mutableSocketAddress(self: *Address) *SocketAddress {
171 return &self.storage.any;
172 }
173
174 pub fn socketAddressLen(self: *const Address) SocketAddressLength {
175 return self.len;
176 }
177
178 pub fn ipAddress(self: *const Address) ?IpAddress {
179 return socketAddressToIpAddress(&self.storage.storage, self.len);
180 }
181
182 pub fn ip4Bytes(self: *const Address) ?[4]u8 {
183 return switch (self.ipAddress() orelse return null) {
184 .ip4 => |ip4| @bitCast(ip4.addr),
185 .ip6 => null,
186 };
187 }
188
189 pub fn getPort(self: *const Address) u16 {
190 return ipAddressPort(self.ipAddress() orelse return 0);
191 }
192
193 pub fn listen(address: Address, options: ListenOptions) ListenAddressError!Server {
194 const socket_fd = try streamSocket(address.family(), .{
195 .close_on_exec = options.close_on_exec,
196 .nonblocking = options.nonblocking,
197 });
198 errdefer close(socket_fd);
199
200 if (options.reuse_address and isInetFamily(address.family())) {
201 try setReuseAddress(socket_fd);
202 }
203
204 try bind(socket_fd, address.socketAddress(), address.socketAddressLen());
205 try startListening(socket_fd, options.backlog);
206
207 var listen_address = Address.empty();
208 try readSocketAddress(socket_fd, &listen_address);
209
210 return .{
211 .socket = socket_fd,
212 .listen_address = listen_address,
213 };
214 }
215 };
216
217 pub const Stream = struct {
218 handle: Socket,
219
220 pub fn initFd(socket_fd: Socket) Stream {
221 return .{ .handle = socket_fd };
222 }
223
224 pub fn read(self: Stream, buffer: []u8) RecvError!usize {
225 return recv(self.handle, buffer, 0);
226 }
227
228 pub fn write(self: Stream, bytes: []const u8) SendError!usize {
229 const written = try sendNoSignal(self.handle, bytes);
230 if (bytes.len != 0 and written == 0) return error.BrokenPipe;
231 return written;
232 }
233
234 pub fn writeAll(self: Stream, bytes: []const u8) SendError!void {
235 var written: usize = 0;
236 while (written < bytes.len) {
237 written += try self.write(bytes[written..]);
238 }
239 }
240
241 pub fn close(self: Stream) void {
242 fd.close(self.handle);
243 }
244 };
245
246 pub const AcceptedStream = struct {
247 stream: Stream,
248 address: Address,
249 };
250
251 pub const Server = struct {
252 socket: Socket,
253 listen_address: Address,
254
255 pub fn accept(self: *Server) AcceptError!AcceptedStream {
256 var address = Address.empty();
257 const accepted = try acceptSocket(
258 self.socket,
259 address.mutableSocketAddress(),
260 &address.len,
261 posix.SOCK.CLOEXEC,
262 );
263 return .{
264 .stream = Stream.initFd(accepted),
265 .address = address,
266 };
267 }
268
269 pub fn deinit(self: *Server) void {
270 fd.close(self.socket);
271 self.* = undefined;
272 }
273 };
274
275 pub const StreamConnectError = SocketError || ConnectError;
276
277 pub const BindError = error{
278 UnsupportedPlatform,
279 AddressInUse,
280 BindFailed,
281 };
282
283 pub const ListenError = error{
284 UnsupportedPlatform,
285 ListenFailed,
286 };
287
288 pub const AcceptError = error{
289 UnsupportedPlatform,
290 WouldBlock,
291 ConnectionAborted,
292 ConnectionResetByPeer,
293 FileDescriptorNotASocket,
294 SocketNotListening,
295 AcceptFailed,
296 };
297
298 pub const ConnectError = error{
299 UnsupportedPlatform,
300 WouldBlock,
301 ConnectionRefused,
302 ConnectionTimedOut,
303 HostUnreachable,
304 NetworkUnreachable,
305 ConnectFailed,
306 };
307
308 pub const SendError = error{
309 UnsupportedPlatform,
310 WouldBlock,
311 BrokenPipe,
312 BadFileDescriptor,
313 ConnectionResetByPeer,
314 MessageTooLarge,
315 SendFailed,
316 };
317
318 pub const RecvError = error{
319 UnsupportedPlatform,
320 WouldBlock,
321 BadFileDescriptor,
322 ConnectionResetByPeer,
323 ConnectionTimedOut,
324 ReceiveFailed,
325 };
326
327 pub const SetOptionError = error{
328 UnsupportedPlatform,
329 InvalidArgument,
330 SetOptionFailed,
331 };
332
333 pub const PeerCredentialsError = error{
334 UnsupportedPlatform,
335 BadFileDescriptor,
336 FileDescriptorNotASocket,
337 SocketNotConnected,
338 PeerCredentialsUnavailable,
339 };
340
341 pub const SocketNameError = error{
342 UnsupportedPlatform,
343 SocketNameFailed,
344 };
345
346 pub const PollError = error{
347 UnsupportedPlatform,
348 PollFailed,
349 };
350
351 pub const ReceivedDatagram = struct {
352 address: IpAddress,
353 bytes: usize,
354 truncated: bool,
355 };
356
357 /// Runs one worker's socket operations for one request under a deadline, a
358 /// cancellation flag, and a byte budget, so another thread can stop the work
359 /// and the work ends at a time the caller picked. The caller keeps the control
360 /// pinned and its deadline unchanged until the worker returns, and `cancel` may
361 /// be called from another thread. Because one worker owns every descriptor the
362 /// control touches, cancellation ends the wait and leaves the descriptor open,
363 /// so a descriptor another thread has since reused is never closed under it.
364 /// `check` fails with `Canceled` after a cancel and with `DeadlineExceeded`
365 /// past the deadline, and keeps the first failure in `failure`, while a send or
366 /// a receive past the byte budget fails with `TransferCapacityExceeded`.
367 pub const Control = struct {
368 deadline_ns: u64,
369 cancelled: std.atomic.Value(bool) = .init(false),
370 bytes_remaining: u64,
371 failure: ?Failure = null,
372
373 pub const Failure = error{ Canceled, DeadlineExceeded, TransferCapacityExceeded };
374 pub const poll_quantum_ms = 10;
375 pub const DnsFamily = enum { ip4, ip6 };
376
377 pub fn cancel(self: *Control) void {
378 self.cancelled.store(true, .release);
379 }
380
381 pub fn check(self: *Control) !void {
382 if (self.cancelled.load(.acquire)) return self.fail(error.Canceled);
383 if (try clock() >= self.deadline_ns) return self.fail(error.DeadlineExceeded);
384 }
385
386 pub fn clock() !u64 {
387 return (try @import("root.zig").time.awakeNow()).asNanoseconds();
388 }
389
390 fn fail(self: *Control, failure: Failure) Failure {
391 self.failure = failure;
392 return failure;
393 }
394
395 fn window(self: *Control, length: usize) !usize {
396 try self.check();
397 if (self.bytes_remaining == 0) return self.fail(error.TransferCapacityExceeded);
398 return @intCast(@min(length, self.bytes_remaining));
399 }
400
401 /// Keeps cancellation and the deadline responsive during every blocking
402 /// step of a controlled operation by polling the socket for one quantum at
403 /// a time, and returns to the cancellation and deadline checks after each
404 /// slice and after an interruption. A quantum runs ten milliseconds,
405 /// shortened when less time than that remains before the deadline.
406 fn wait(self: *Control, socket_fd: Socket, writable: bool) !void {
407 if (comptime !supported()) return error.UnsupportedPlatform;
408 while (true) {
409 try self.check();
410 const remaining = self.deadline_ns -| try clock();
411 const millis: i32 = @intCast(@min(poll_quantum_ms, remaining / 1000000 + @intFromBool(remaining % 1000000 != 0)));
412 var descriptor = posix.pollfd{
413 .fd = socket_fd,
414 .events = if (writable) posix.POLL.OUT else posix.POLL.IN,
415 .revents = 0,
416 };
417 const rc = linux.poll(@ptrCast(&descriptor), 1, millis);
418 const ready = switch (linux.errno(rc)) {
419 .SUCCESS => rc != 0,
420 .INTR => false,
421 else => return error.PollFailed,
422 };
423 try self.check();
424 if (ready) return;
425 }
426 }
427
428 /// Reports whether this host carries the controlled descriptor and bounded
429 /// entropy implementation, which is the native Linux one. Where this is
430 /// false, every operation on a control returns `UnsupportedPlatform`.
431 pub fn supported() bool {
432 return native_os == .linux;
433 }
434
435 /// Draws random bytes for the resolver's query identifier under the same
436 /// deadline as the rest of the request, filling `output` with kernel random
437 /// bytes and rechecking cancellation and the deadline between reads. The
438 /// read asks with `GRND_NONBLOCK`, so boot entropy that the kernel has yet
439 /// to gather fails with `EntropyUnavailable` and the call returns inside
440 /// the deadline.
441 pub fn entropy(self: *Control, output: []u8) !void {
442 if (comptime !supported()) return error.UnsupportedPlatform;
443 var offset: usize = 0;
444 while (offset < output.len) {
445 try self.check();
446 const rc = linux.getrandom(output[offset..].ptr, output.len - offset, 1);
447 switch (linux.errno(rc)) {
448 .SUCCESS => {
449 if (rc == 0) return error.EntropyUnavailable;
450 offset += rc;
451 },
452 .INTR => {},
453 else => return error.EntropyUnavailable,
454 }
455 }
456 try self.check();
457 }
458
459 /// Opens the request's socket so that the open itself sits under the
460 /// deadline, creating a TCP socket for the address family of `address`,
461 /// close-on-exec and nonblocking. The descriptor belongs to the operation
462 /// that asked for it, and the caller closes it.
463 pub fn open(self: *Control, address: IpAddress) !Socket {
464 if (comptime !supported()) return error.UnsupportedPlatform;
465 while (true) {
466 try self.check();
467 const family: u32 = if (address == .ip4) linux.AF.INET else linux.AF.INET6;
468 const rc = linux.socket(family, linux.SOCK.STREAM | linux.SOCK.NONBLOCK | linux.SOCK.CLOEXEC, 0);
469 switch (linux.errno(rc)) {
470 .SUCCESS => {
471 const result: Socket = @intCast(rc);
472 errdefer close(result);
473 try self.check();
474 return result;
475 },
476 .INTR => {},
477 else => return error.SocketFailed,
478 }
479 }
480 }
481
482 fn nonblocking(self: *Control, socket_fd: Socket) !void {
483 const flags = while (true) {
484 try self.check();
485 const rc = linux.fcntl(socket_fd, linux.F.GETFL, 0);
486 switch (linux.errno(rc)) {
487 .SUCCESS => break rc,
488 .INTR => {},
489 else => return error.FlagUpdateFailed,
490 }
491 };
492 while (true) {
493 try self.check();
494 const rc = linux.fcntl(socket_fd, linux.F.SETFL, flags | @as(u32, @bitCast(linux.O{ .NONBLOCK = true })));
495 switch (linux.errno(rc)) {
496 .SUCCESS => return,
497 .INTR => {},
498 else => return error.FlagUpdateFailed,
499 }
500 }
501 }
502
503 pub fn connectTo(self: *Control, socket_fd: Socket, address: IpAddress) !void {
504 if (comptime !supported()) return error.UnsupportedPlatform;
505 try self.check();
506 try self.nonblocking(socket_fd);
507 const rc = switch (address) {
508 .ip4 => |ip| linux.connect(socket_fd, @ptrCast(&ip), @sizeOf(Ip4Address)),
509 .ip6 => |ip| linux.connect(socket_fd, @ptrCast(&ip), @sizeOf(Ip6Address)),
510 };
511 switch (linux.errno(rc)) {
512 .SUCCESS, .ISCONN => {},
513 .INTR, .AGAIN, .INPROGRESS, .ALREADY => {
514 try self.wait(socket_fd, true);
515 try connectionStatus(socket_fd);
516 },
517 else => return error.ConnectFailed,
518 }
519 try self.check();
520 }
521
522 pub fn receive(self: *Control, socket_fd: Socket, output: []u8) !usize {
523 if (comptime !supported()) return error.UnsupportedPlatform;
524 std.debug.assert(output.len > 0);
525 while (true) {
526 const length = try self.window(output.len);
527 const rc = linux.recvfrom(socket_fd, output.ptr, length, linux.MSG.DONTWAIT, null, null);
528 switch (linux.errno(rc)) {
529 .SUCCESS => {
530 self.bytes_remaining -= rc;
531 try self.check();
532 return rc;
533 },
534 .INTR => {},
535 .AGAIN => try self.wait(socket_fd, false),
536 else => return error.ReceiveFailed,
537 }
538 }
539 }
540
541 pub fn transmit(self: *Control, socket_fd: Socket, input: []const u8) !usize {
542 if (comptime !supported()) return error.UnsupportedPlatform;
543 std.debug.assert(input.len > 0);
544 while (true) {
545 const length = try self.window(input.len);
546 const rc = linux.sendto(socket_fd, input.ptr, length, linux.MSG.DONTWAIT | linux.MSG.NOSIGNAL, null, 0);
547 switch (linux.errno(rc)) {
548 .SUCCESS => {
549 if (rc == 0) return error.BrokenPipe;
550 self.bytes_remaining -= rc;
551 try self.check();
552 return rc;
553 },
554 .INTR => {},
555 .AGAIN => try self.wait(socket_fd, true),
556 else => return error.SendFailed,
557 }
558 }
559 }
560
561 fn transmitAll(self: *Control, socket_fd: Socket, input: []const u8) !void {
562 var offset: usize = 0;
563 while (offset < input.len) offset += try self.transmit(socket_fd, input[offset..]);
564 }
565
566 fn receiveExact(self: *Control, socket_fd: Socket, output: []u8) !void {
567 var offset: usize = 0;
568 while (offset < output.len) {
569 const count = try self.receive(socket_fd, output[offset..]);
570 if (count == 0) return error.MalformedResponse;
571 offset += count;
572 }
573 }
574
575 /// Puts one DNS question to `nameserver` over TCP and returns the answer
576 /// address with `port` set, and `family` picks an A question or an AAAA
577 /// question. The question carries a random sixteen-bit identifier drawn
578 /// through `entropy`, and the response is matched against the question
579 /// before any address is taken. One question goes out and one answer comes
580 /// back: no search domain is appended, no second nameserver is tried, and
581 /// no retry follows a failure, all of which `resolveIpAddressForHost` does.
582 /// A response longer than 4096 bytes, and one the server marks truncated,
583 /// both fail with `QueryTooLarge`, while the request and response buffers
584 /// are wiped before the call returns.
585 pub fn resolve(
586 self: *Control,
587 host: []const u8,
588 port: u16,
589 nameserver: IpAddress,
590 family: DnsFamily,
591 ) !IpAddress {
592 try self.check();
593 var request: [514]u8 = undefined;
594 defer std.crypto.secureZero(u8, &request);
595 var entropy_bytes: [2]u8 = undefined;
596 try self.entropy(&entropy_bytes);
597 const id = std.mem.readInt(u16, &entropy_bytes, .big);
598 const encoded = try dnsQuery(request[2..], host, if (family == .ip4) .a else .aaaa, id);
599 std.mem.writeInt(u16, request[0..2], @intCast(encoded.len), .big);
600 const socket_fd = try self.open(nameserver);
601 defer close(socket_fd);
602 try self.connectTo(socket_fd, nameserver);
603 try self.transmitAll(socket_fd, request[0 .. encoded.len + 2]);
604 var size: [2]u8 = undefined;
605 try self.receiveExact(socket_fd, &size);
606 const length = std.mem.readInt(u16, &size, .big);
607 var response: [4096]u8 = undefined;
608 defer std.crypto.secureZero(u8, &response);
609 if (length == 0 or length > response.len) return error.QueryTooLarge;
610 try self.receiveExact(socket_fd, response[0..length]);
611 if (try dnsResponseTruncated(response[0..length], encoded.question)) return error.QueryTooLarge;
612 return dnsAddressFromResponse(response[0..length], encoded.question, port);
613 }
614 };
615
616 fn connectionStatus(socket_fd: Socket) !void {
617 if (comptime !Control.supported()) return error.UnsupportedPlatform;
618 var value: c_int = 0;
619 var length: linux.socklen_t = @sizeOf(c_int);
620 const rc = linux.getsockopt(socket_fd, linux.SOL.SOCKET, linux.SO.ERROR, @ptrCast(&value), &length);
621 if (linux.errno(rc) != .SUCCESS or length != @sizeOf(c_int) or value != 0)
622 return error.ConnectFailed;
623 }
624
625 pub const ResolveOptions = struct {
626 hosts_path: ?[]const u8 = "/etc/hosts",
627 nameservers: []const IpAddress = &.{},
628 timeout_ms: u32 = 1500,
629 };
630
631 const ResolveLocalError = error{
632 InvalidHostName,
633 MalformedResponse,
634 NoAddress,
635 NoNameserver,
636 QueryTooLarge,
637 };
638
639 pub const ResolveError =
640 AddressError ||
641 random.SecureError ||
642 SocketError ||
643 ConnectError ||
644 SendError ||
645 RecvError ||
646 SetOptionError ||
647 ResolveLocalError;
648
649 const dns_max_name_len = 253;
650 const default_ndots = 1;
651 const DnsRecordType = enum(u16) {
652 a = 1,
653 aaaa = 28,
654 };
655 const socket_flags_unsupported = switch (native_os) {
656 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .haiku => true,
657 else => false,
658 };
659 const accept4_unsupported = socket_flags_unsupported;
660
661 pub fn socketPolicy() SocketPolicy {
662 return switch (native_os) {
663 .windows, .wasi, .freestanding => .unsupported,
664 .linux => .linux_syscall,
665 else => .posix_host,
666 };
667 }
668
669 pub fn close(socket_fd: Socket) void {
670 fd.close(socket_fd);
671 }
672
673 pub fn shutdownReadWrite(socket_fd: Socket) void {
674 switch (socketPolicy()) {
675 .unsupported => {},
676 .linux_syscall => shutdownReadWriteLinux(socket_fd),
677 .posix_host => shutdownReadWritePosix(socket_fd),
678 }
679 }
680
681 pub fn tcpStreamSocket(options: SocketOptions) SocketError!Socket {
682 return streamSocket(posix.AF.INET, options);
683 }
684
685 pub fn tcpStreamSocketForAddress(address: IpAddress, options: SocketOptions) SocketError!Socket {
686 return streamSocket(addressFamily(address), options);
687 }
688
689 pub fn unixStreamSocket(options: SocketOptions) SocketError!Socket {
690 return streamSocket(posix.AF.UNIX, options);
691 }
692
693 pub fn udpDatagramSocket(options: SocketOptions) SocketError!Socket {
694 return udpDatagramSocketForFamily(posix.AF.INET, options);
695 }
696
697 pub fn udpDatagramSocketForAddress(address: IpAddress, options: SocketOptions) SocketError!Socket {
698 return udpDatagramSocketForFamily(addressFamily(address), options);
699 }
700
701 fn udpDatagramSocketForFamily(family: u32, options: SocketOptions) SocketError!Socket {
702 var socket_type: u32 = posix.SOCK.DGRAM;
703 if (options.nonblocking and !socket_flags_unsupported) socket_type |= posix.SOCK.NONBLOCK;
704 if (options.close_on_exec and !socket_flags_unsupported) socket_type |= posix.SOCK.CLOEXEC;
705
706 const socket_fd = try socket(family, socket_type, posix.IPPROTO.UDP);
707 errdefer close(socket_fd);
708
709 if (options.nonblocking and socket_flags_unsupported) {
710 fd.setNonBlocking(socket_fd) catch return error.SocketFailed;
711 }
712 if (options.close_on_exec and socket_flags_unsupported) {
713 fd.setCloseOnExec(socket_fd) catch return error.SocketFailed;
714 }
715
716 return socket_fd;
717 }
718
719 fn addressFamily(address: IpAddress) u32 {
720 return switch (address) {
721 .ip4 => posix.AF.INET,
722 .ip6 => posix.AF.INET6,
723 };
724 }
725
726 pub fn streamSocket(domain: u32, options: SocketOptions) SocketError!Socket {
727 var socket_type: u32 = posix.SOCK.STREAM;
728 if (options.nonblocking and !socket_flags_unsupported) socket_type |= posix.SOCK.NONBLOCK;
729 if (options.close_on_exec and !socket_flags_unsupported) socket_type |= posix.SOCK.CLOEXEC;
730
731 const socket_fd = try socket(domain, socket_type, 0);
732 errdefer close(socket_fd);
733
734 if (options.nonblocking and socket_flags_unsupported) {
735 fd.setNonBlocking(socket_fd) catch return error.SocketFailed;
736 }
737 if (options.close_on_exec and socket_flags_unsupported) {
738 fd.setCloseOnExec(socket_fd) catch return error.SocketFailed;
739 }
740
741 return socket_fd;
742 }
743
744 pub fn isInetFamily(family: u32) bool {
745 return family == posix.AF.INET or family == posix.AF.INET6;
746 }
747
748 pub fn isIp4Family(family: u32) bool {
749 return family == posix.AF.INET;
750 }
751
752 pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!Socket {
753 return switch (socketPolicy()) {
754 .unsupported => error.UnsupportedPlatform,
755 .linux_syscall => socketLinux(domain, socket_type, protocol),
756 .posix_host => socketPosix(domain, socket_type, protocol),
757 };
758 }
759
760 fn socketLinux(domain: u32, socket_type: u32, protocol: u32) SocketError!Socket {
761 if (comptime native_os != .linux) return error.UnsupportedPlatform;
762
763 while (true) {
764 const rc = linux.socket(domain, socket_type, protocol);
765 switch (linux.errno(rc)) {
766 .SUCCESS => return @intCast(rc),
767 .INTR => continue,
768 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
769 .INVAL => return error.ProtocolUnsupportedBySystem,
770 .MFILE => return error.ProcessFdQuotaExceeded,
771 .NFILE => return error.SystemFdQuotaExceeded,
772 .NOBUFS, .NOMEM => return error.SystemResources,
773 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
774 .PROTOTYPE => return error.SocketModeUnsupported,
775 else => return error.SocketFailed,
776 }
777 }
778 }
779
780 fn socketPosix(domain: u32, socket_type: u32, protocol: u32) SocketError!Socket {
781 if (!@hasDecl(system, "socket") or @TypeOf(system.socket) == void) {
782 return error.UnsupportedPlatform;
783 }
784
785 while (true) {
786 const rc = system.socket(domain, socket_type, protocol);
787 switch (posix.errno(rc)) {
788 .SUCCESS => return @intCast(rc),
789 .INTR => continue,
790 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
791 .INVAL => return error.ProtocolUnsupportedBySystem,
792 .MFILE => return error.ProcessFdQuotaExceeded,
793 .NFILE => return error.SystemFdQuotaExceeded,
794 .NOBUFS, .NOMEM => return error.SystemResources,
795 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
796 .PROTOTYPE => return error.SocketModeUnsupported,
797 else => return error.SocketFailed,
798 }
799 }
800 }
801
802 pub fn socketPairUnixStream() fd.SocketPairError![2]Socket {
803 return fd.socketPairUnixStream(.{ .close_on_exec = true });
804 }
805
806 pub fn send(socket_fd: Socket, bytes: []const u8, flags: u32) SendError!usize {
807 return sendTo(socket_fd, bytes, flags, null, 0);
808 }
809
810 pub fn sendTo(
811 socket_fd: Socket,
812 bytes: []const u8,
813 flags: u32,
814 address: ?*const SocketAddress,
815 len: SocketAddressLength,
816 ) SendError!usize {
817 if (bytes.len == 0) return 0;
818 return switch (socketPolicy()) {
819 .unsupported => error.UnsupportedPlatform,
820 .linux_syscall => sendToLinux(socket_fd, bytes, flags, address, len),
821 .posix_host => sendToPosix(socket_fd, bytes, flags, address, len),
822 };
823 }
824
825 fn sendToLinux(
826 socket_fd: Socket,
827 bytes: []const u8,
828 flags: u32,
829 address: ?*const SocketAddress,
830 len: SocketAddressLength,
831 ) SendError!usize {
832 if (comptime native_os != .linux) return error.UnsupportedPlatform;
833
834 while (true) {
835 const linux_address: ?*const linux.sockaddr = if (address) |ptr| @ptrCast(ptr) else null;
836 const rc = linux.sendto(socket_fd, bytes.ptr, bytes.len, flags, linux_address, len);
837 switch (linux.errno(rc)) {
838 .SUCCESS => return @intCast(rc),
839 .INTR => continue,
840 .AGAIN => return error.WouldBlock,
841 .PIPE => return error.BrokenPipe,
842 .BADF => return error.BadFileDescriptor,
843 .CONNRESET => return error.ConnectionResetByPeer,
844 .MSGSIZE => return error.MessageTooLarge,
845 else => return error.SendFailed,
846 }
847 }
848 }
849
850 fn sendToPosix(
851 socket_fd: Socket,
852 bytes: []const u8,
853 flags: u32,
854 address: ?*const SocketAddress,
855 len: SocketAddressLength,
856 ) SendError!usize {
857 if (!@hasDecl(system, "sendto") or @TypeOf(system.sendto) == void) {
858 return error.UnsupportedPlatform;
859 }
860
861 while (true) {
862 const rc = system.sendto(socket_fd, bytes.ptr, bytes.len, flags, address, len);
863 switch (posix.errno(rc)) {
864 .SUCCESS => return @intCast(rc),
865 .INTR => continue,
866 .AGAIN => return error.WouldBlock,
867 .PIPE => return error.BrokenPipe,
868 .BADF => return error.BadFileDescriptor,
869 .CONNRESET => return error.ConnectionResetByPeer,
870 .MSGSIZE => return error.MessageTooLarge,
871 else => return error.SendFailed,
872 }
873 }
874 }
875
876 pub fn sendNoSignal(socket_fd: Socket, bytes: []const u8) SendError!usize {
877 return send(socket_fd, bytes, noSignalFlag());
878 }
879
880 pub fn sendToIpAddress(socket_fd: Socket, bytes: []const u8, flags: u32, address: IpAddress) SendError!usize {
881 return switch (address) {
882 .ip4 => |ip4| sendTo(socket_fd, bytes, flags, @ptrCast(&ip4), @sizeOf(Ip4Address)),
883 .ip6 => |ip6| sendTo(socket_fd, bytes, flags, @ptrCast(&ip6), @sizeOf(Ip6Address)),
884 };
885 }
886
887 pub fn recv(socket_fd: Socket, buffer: []u8, flags: u32) RecvError!usize {
888 return recvFrom(socket_fd, buffer, flags, null, null);
889 }
890
891 pub fn recvFrom(
892 socket_fd: Socket,
893 buffer: []u8,
894 flags: u32,
895 address: ?*SocketAddress,
896 len: ?*SocketAddressLength,
897 ) RecvError!usize {
898 if (buffer.len == 0) return 0;
899 return switch (socketPolicy()) {
900 .unsupported => error.UnsupportedPlatform,
901 .linux_syscall => recvFromLinux(socket_fd, buffer, flags, address, len),
902 .posix_host => recvFromPosix(socket_fd, buffer, flags, address, len),
903 };
904 }
905
906 fn recvFromLinux(
907 socket_fd: Socket,
908 buffer: []u8,
909 flags: u32,
910 address: ?*SocketAddress,
911 len: ?*SocketAddressLength,
912 ) RecvError!usize {
913 if (comptime native_os != .linux) return error.UnsupportedPlatform;
914
915 while (true) {
916 const linux_address: ?*linux.sockaddr = if (address) |ptr| @ptrCast(ptr) else null;
917 const rc = linux.recvfrom(socket_fd, buffer.ptr, buffer.len, flags, linux_address, len);
918 switch (linux.errno(rc)) {
919 .SUCCESS => return @intCast(rc),
920 .INTR => continue,
921 .AGAIN => return error.WouldBlock,
922 .BADF => return error.BadFileDescriptor,
923 .CONNRESET => return error.ConnectionResetByPeer,
924 .TIMEDOUT => return error.ConnectionTimedOut,
925 else => return error.ReceiveFailed,
926 }
927 }
928 }
929
930 fn recvFromPosix(
931 socket_fd: Socket,
932 buffer: []u8,
933 flags: u32,
934 address: ?*SocketAddress,
935 len: ?*SocketAddressLength,
936 ) RecvError!usize {
937 if (!@hasDecl(system, "recvfrom") or @TypeOf(system.recvfrom) == void) {
938 return error.UnsupportedPlatform;
939 }
940
941 while (true) {
942 const rc = system.recvfrom(socket_fd, buffer.ptr, buffer.len, flags, address, len);
943 switch (posix.errno(rc)) {
944 .SUCCESS => return @intCast(rc),
945 .INTR => continue,
946 .AGAIN => return error.WouldBlock,
947 .BADF => return error.BadFileDescriptor,
948 .CONNRESET => return error.ConnectionResetByPeer,
949 .TIMEDOUT => return error.ConnectionTimedOut,
950 else => return error.ReceiveFailed,
951 }
952 }
953 }
954
955 pub fn recvFromIpAddress(socket_fd: Socket, buffer: []u8, flags: u32) RecvError!ReceivedDatagram {
956 var address: posix.sockaddr.storage = undefined;
957 var len: SocketAddressLength = @sizeOf(posix.sockaddr.storage);
958 const reported = try recvFrom(
959 socket_fd,
960 buffer,
961 flags | receiveTruncationFlag(),
962 @ptrCast(&address),
963 &len,
964 );
965 return .{
966 .address = socketAddressToIpAddress(&address, len) orelse return error.ReceiveFailed,
967 .bytes = @min(reported, buffer.len),
968 .truncated = if (comptime native_os == .linux)
969 reported > buffer.len
970 else
971 reported == buffer.len,
972 };
973 }
974
975 pub fn bindIp4(socket_fd: Socket, address: Ip4Address) BindError!void {
976 return bind(socket_fd, @ptrCast(&address), @sizeOf(Ip4Address));
977 }
978
979 pub fn bindIp6(socket_fd: Socket, address: Ip6Address) BindError!void {
980 return bind(socket_fd, @ptrCast(&address), @sizeOf(Ip6Address));
981 }
982
983 pub fn bindIpAddress(socket_fd: Socket, address: IpAddress) BindError!void {
984 return switch (address) {
985 .ip4 => |ip4| bindIp4(socket_fd, ip4),
986 .ip6 => |ip6| bindIp6(socket_fd, ip6),
987 };
988 }
989
990 pub fn bind(socket_fd: Socket, address: *const SocketAddress, len: SocketAddressLength) BindError!void {
991 return switch (socketPolicy()) {
992 .unsupported => error.UnsupportedPlatform,
993 .linux_syscall => bindLinux(socket_fd, address, len),
994 .posix_host => bindPosix(socket_fd, address, len),
995 };
996 }
997
998 fn bindLinux(socket_fd: Socket, address: *const SocketAddress, len: SocketAddressLength) BindError!void {
999 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1000
1001 const linux_address: *const linux.sockaddr = @ptrCast(address);
1002 while (true) {
1003 const rc = linux.bind(socket_fd, linux_address, len);
1004 switch (linux.errno(rc)) {
1005 .SUCCESS => return,
1006 .INTR => continue,
1007 .ADDRINUSE => return error.AddressInUse,
1008 else => return error.BindFailed,
1009 }
1010 }
1011 }
1012
1013 fn bindPosix(socket_fd: Socket, address: *const SocketAddress, len: SocketAddressLength) BindError!void {
1014 if (!@hasDecl(system, "bind") or @TypeOf(system.bind) == void) {
1015 return error.UnsupportedPlatform;
1016 }
1017
1018 while (true) {
1019 const rc = system.bind(socket_fd, address, len);
1020 switch (posix.errno(rc)) {
1021 .SUCCESS => return,
1022 .INTR => continue,
1023 .ADDRINUSE => return error.AddressInUse,
1024 else => return error.BindFailed,
1025 }
1026 }
1027 }
1028
1029 pub fn listen(socket_fd: Socket, backlog: u32) ListenError!void {
1030 return switch (socketPolicy()) {
1031 .unsupported => error.UnsupportedPlatform,
1032 .linux_syscall => listenLinux(socket_fd, backlog),
1033 .posix_host => listenPosix(socket_fd, backlog),
1034 };
1035 }
1036
1037 fn listenLinux(socket_fd: Socket, backlog: u32) ListenError!void {
1038 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1039
1040 while (true) {
1041 const rc = linux.listen(socket_fd, backlog);
1042 switch (linux.errno(rc)) {
1043 .SUCCESS => return,
1044 .INTR => continue,
1045 else => return error.ListenFailed,
1046 }
1047 }
1048 }
1049
1050 fn listenPosix(socket_fd: Socket, backlog: u32) ListenError!void {
1051 if (!@hasDecl(system, "listen") or @TypeOf(system.listen) == void) {
1052 return error.UnsupportedPlatform;
1053 }
1054
1055 while (true) {
1056 const rc = system.listen(socket_fd, backlog);
1057 switch (posix.errno(rc)) {
1058 .SUCCESS => return,
1059 .INTR => continue,
1060 else => return error.ListenFailed,
1061 }
1062 }
1063 }
1064
1065 fn startListening(socket_fd: Socket, backlog: u32) ListenError!void {
1066 return listen(socket_fd, backlog);
1067 }
1068
1069 pub fn accept(
1070 socket_fd: Socket,
1071 address: ?*SocketAddress,
1072 len: ?*SocketAddressLength,
1073 flags: u32,
1074 ) AcceptError!Socket {
1075 return switch (socketPolicy()) {
1076 .unsupported => error.UnsupportedPlatform,
1077 .linux_syscall => acceptLinux(socket_fd, address, len, flags),
1078 .posix_host => acceptPosix(socket_fd, address, len, flags),
1079 };
1080 }
1081
1082 fn acceptLinux(
1083 socket_fd: Socket,
1084 address: ?*SocketAddress,
1085 len: ?*SocketAddressLength,
1086 flags: u32,
1087 ) AcceptError!Socket {
1088 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1089
1090 while (true) {
1091 const linux_address: ?*linux.sockaddr = if (address) |ptr| @ptrCast(ptr) else null;
1092 const rc = linux.accept4(socket_fd, linux_address, len, flags);
1093 switch (linux.errno(rc)) {
1094 .SUCCESS => return @intCast(rc),
1095 .INTR => continue,
1096 .AGAIN => return error.WouldBlock,
1097 .CONNABORTED => return error.ConnectionAborted,
1098 .CONNRESET => return error.ConnectionResetByPeer,
1099 .BADF => return error.FileDescriptorNotASocket,
1100 .INVAL => return error.SocketNotListening,
1101 else => return error.AcceptFailed,
1102 }
1103 }
1104 }
1105
1106 fn acceptPosix(
1107 socket_fd: Socket,
1108 address: ?*SocketAddress,
1109 len: ?*SocketAddressLength,
1110 flags: u32,
1111 ) AcceptError!Socket {
1112 const can_accept4 = !accept4_unsupported and @hasDecl(system, "accept4") and @TypeOf(system.accept4) != void;
1113 if (comptime can_accept4) {
1114 while (true) {
1115 const rc = system.accept4(socket_fd, address, len, flags);
1116 switch (posix.errno(rc)) {
1117 .SUCCESS => return @intCast(rc),
1118 .INTR => continue,
1119 .AGAIN => return error.WouldBlock,
1120 .CONNABORTED => return error.ConnectionAborted,
1121 .CONNRESET => return error.ConnectionResetByPeer,
1122 .BADF => return error.FileDescriptorNotASocket,
1123 .INVAL => return error.SocketNotListening,
1124 else => return error.AcceptFailed,
1125 }
1126 }
1127 }
1128
1129 if (!@hasDecl(system, "accept") or @TypeOf(system.accept) == void) {
1130 return error.UnsupportedPlatform;
1131 }
1132
1133 while (true) {
1134 const rc = system.accept(socket_fd, address, len);
1135 switch (posix.errno(rc)) {
1136 .SUCCESS => {
1137 const accepted: Socket = @intCast(rc);
1138 errdefer close(accepted);
1139 if ((flags & posix.SOCK.NONBLOCK) != 0) {
1140 fd.setNonBlocking(accepted) catch return error.AcceptFailed;
1141 }
1142 if ((flags & posix.SOCK.CLOEXEC) != 0) {
1143 fd.setCloseOnExec(accepted) catch return error.AcceptFailed;
1144 }
1145 return accepted;
1146 },
1147 .INTR => continue,
1148 .AGAIN => return error.WouldBlock,
1149 .CONNABORTED => return error.ConnectionAborted,
1150 .CONNRESET => return error.ConnectionResetByPeer,
1151 .BADF => return error.FileDescriptorNotASocket,
1152 .INVAL => return error.SocketNotListening,
1153 else => return error.AcceptFailed,
1154 }
1155 }
1156 }
1157
1158 fn acceptSocket(
1159 socket_fd: Socket,
1160 address: ?*SocketAddress,
1161 len: ?*SocketAddressLength,
1162 flags: u32,
1163 ) AcceptError!Socket {
1164 return accept(socket_fd, address, len, flags);
1165 }
1166
1167 pub fn acceptNonBlocking(socket_fd: Socket) AcceptError!Socket {
1168 return accept(socket_fd, null, null, posix.SOCK.NONBLOCK | posix.SOCK.CLOEXEC);
1169 }
1170
1171 pub fn acceptStreamSocket(socket_fd: Socket) AcceptError!Socket {
1172 return accept(socket_fd, null, null, posix.SOCK.CLOEXEC);
1173 }
1174
1175 pub fn connectIp4(socket_fd: Socket, address: Ip4Address) ConnectError!void {
1176 return connect(socket_fd, @ptrCast(&address), @sizeOf(Ip4Address));
1177 }
1178
1179 pub fn connectIp6(socket_fd: Socket, address: Ip6Address) ConnectError!void {
1180 return connect(socket_fd, @ptrCast(&address), @sizeOf(Ip6Address));
1181 }
1182
1183 pub fn connectIpAddress(socket_fd: Socket, address: IpAddress) ConnectError!void {
1184 return switch (address) {
1185 .ip4 => |ip4| connectIp4(socket_fd, ip4),
1186 .ip6 => |ip6| connectIp6(socket_fd, ip6),
1187 };
1188 }
1189
1190 pub fn connectStream(address: Address) StreamConnectError!Stream {
1191 const socket_fd = try streamSocket(address.family(), .{ .close_on_exec = true });
1192 errdefer close(socket_fd);
1193 try connect(socket_fd, address.socketAddress(), address.socketAddressLen());
1194 return Stream.initFd(socket_fd);
1195 }
1196
1197 pub fn connect(socket_fd: Socket, address: *const SocketAddress, len: SocketAddressLength) ConnectError!void {
1198 return switch (socketPolicy()) {
1199 .unsupported => error.UnsupportedPlatform,
1200 .linux_syscall => connectLinux(socket_fd, address, len),
1201 .posix_host => connectPosix(socket_fd, address, len),
1202 };
1203 }
1204
1205 pub fn peerCredentialsPolicy() PeerCredentialsPolicy {
1206 return switch (native_os) {
1207 .linux => .linux_so_peercred,
1208 else => .unsupported,
1209 };
1210 }
1211
1212 pub fn peerCredentials(
1213 socket_fd: Socket,
1214 ) PeerCredentialsError!PeerCredentials {
1215 return switch (comptime peerCredentialsPolicy()) {
1216 .unsupported => error.UnsupportedPlatform,
1217 .linux_so_peercred => peerCredentialsLinux(socket_fd),
1218 };
1219 }
1220
1221 fn peerCredentialsLinux(
1222 socket_fd: Socket,
1223 ) PeerCredentialsError!PeerCredentials {
1224 if (comptime native_os != .linux) {
1225 return error.UnsupportedPlatform;
1226 }
1227 var credentials: PeerCredentials = undefined;
1228 var length: linux.socklen_t = @sizeOf(PeerCredentials);
1229 const rc = linux.getsockopt(
1230 socket_fd,
1231 linux.SOL.SOCKET,
1232 linux.SO.PEERCRED,
1233 std.mem.asBytes(&credentials).ptr,
1234 &length,
1235 );
1236 try peerCredentialsErrorFromLinuxErrno(linux.errno(rc));
1237 if (length != @sizeOf(PeerCredentials)) {
1238 return error.PeerCredentialsUnavailable;
1239 }
1240 return credentials;
1241 }
1242
1243 fn peerCredentialsErrorFromLinuxErrno(
1244 err: linux.E,
1245 ) PeerCredentialsError!void {
1246 return switch (err) {
1247 .SUCCESS => {},
1248 .BADF => error.BadFileDescriptor,
1249 .NOTSOCK => error.FileDescriptorNotASocket,
1250 .NOTCONN => error.SocketNotConnected,
1251 else => error.PeerCredentialsUnavailable,
1252 };
1253 }
1254
1255 fn connectLinux(socket_fd: Socket, address: *const SocketAddress, len: SocketAddressLength) ConnectError!void {
1256 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1257
1258 while (true) {
1259 const rc = linux.connect(socket_fd, address, len);
1260 switch (linux.errno(rc)) {
1261 .SUCCESS => return,
1262 .INTR => continue,
1263 .AGAIN => return error.WouldBlock,
1264 .CONNREFUSED => return error.ConnectionRefused,
1265 .TIMEDOUT => return error.ConnectionTimedOut,
1266 .HOSTUNREACH => return error.HostUnreachable,
1267 .NETUNREACH => return error.NetworkUnreachable,
1268 else => return error.ConnectFailed,
1269 }
1270 }
1271 }
1272
1273 fn connectPosix(socket_fd: Socket, address: *const SocketAddress, len: SocketAddressLength) ConnectError!void {
1274 if (!@hasDecl(system, "connect") or @TypeOf(system.connect) == void) {
1275 return error.UnsupportedPlatform;
1276 }
1277
1278 while (true) {
1279 const rc = system.connect(socket_fd, address, len);
1280 switch (posix.errno(rc)) {
1281 .SUCCESS => return,
1282 .INTR => continue,
1283 .AGAIN => return error.WouldBlock,
1284 .CONNREFUSED => return error.ConnectionRefused,
1285 .TIMEDOUT => return error.ConnectionTimedOut,
1286 .HOSTUNREACH => return error.HostUnreachable,
1287 .NETUNREACH => return error.NetworkUnreachable,
1288 else => return error.ConnectFailed,
1289 }
1290 }
1291 }
1292
1293 pub fn socketPort(socket_fd: Socket) SocketNameError!u16 {
1294 var address: posix.sockaddr.storage = undefined;
1295 var len: SocketAddressLength = @sizeOf(posix.sockaddr.storage);
1296 try socketAddress(socket_fd, @ptrCast(&address), &len);
1297 return ipAddressPort(socketAddressToIpAddress(&address, len) orelse return error.SocketNameFailed);
1298 }
1299
1300 pub fn socketAddress(socket_fd: Socket, address: *SocketAddress, len: *SocketAddressLength) SocketNameError!void {
1301 return switch (socketPolicy()) {
1302 .unsupported => error.UnsupportedPlatform,
1303 .linux_syscall => socketAddressLinux(socket_fd, address, len),
1304 .posix_host => socketAddressPosix(socket_fd, address, len),
1305 };
1306 }
1307
1308 fn socketAddressLinux(socket_fd: Socket, address: *SocketAddress, len: *SocketAddressLength) SocketNameError!void {
1309 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1310
1311 const linux_address: *linux.sockaddr = @ptrCast(address);
1312 while (true) {
1313 const rc = linux.getsockname(socket_fd, linux_address, len);
1314 switch (linux.errno(rc)) {
1315 .SUCCESS => return,
1316 .INTR => continue,
1317 else => return error.SocketNameFailed,
1318 }
1319 }
1320 }
1321
1322 fn socketAddressPosix(socket_fd: Socket, address: *SocketAddress, len: *SocketAddressLength) SocketNameError!void {
1323 if (!@hasDecl(system, "getsockname") or @TypeOf(system.getsockname) == void) {
1324 return error.UnsupportedPlatform;
1325 }
1326
1327 while (true) {
1328 const rc = system.getsockname(socket_fd, address, len);
1329 switch (posix.errno(rc)) {
1330 .SUCCESS => return,
1331 .INTR => continue,
1332 else => return error.SocketNameFailed,
1333 }
1334 }
1335 }
1336
1337 pub fn socketAddressValue(socket_fd: Socket) SocketNameError!Address {
1338 var address = Address.empty();
1339 try readSocketAddress(socket_fd, &address);
1340 return address;
1341 }
1342
1343 fn readSocketAddress(socket_fd: Socket, address: *Address) SocketNameError!void {
1344 try socketAddress(socket_fd, address.mutableSocketAddress(), &address.len);
1345 }
1346
1347 pub fn pollReadable(socket_fd: Socket, timeout_ms: i32) PollError!bool {
1348 return switch (socketPolicy()) {
1349 .unsupported => error.UnsupportedPlatform,
1350 .linux_syscall => pollReadableLinux(socket_fd, timeout_ms),
1351 .posix_host => pollReadablePosix(socket_fd, timeout_ms),
1352 };
1353 }
1354
1355 pub fn pollWritable(socket_fd: Socket, timeout_ms: i32) PollError!bool {
1356 return switch (socketPolicy()) {
1357 .unsupported => error.UnsupportedPlatform,
1358 .linux_syscall => pollWritableLinux(socket_fd, timeout_ms),
1359 .posix_host => pollWritablePosix(socket_fd, timeout_ms),
1360 };
1361 }
1362
1363 pub fn pollReadableSet(sockets: []const Socket, ready: []bool, timeout_ms: i32) PollError!u8 {
1364 var interests: [readiness_max]PollInterest = undefined;
1365 @memset(interests[0..sockets.len], .read);
1366 return pollSet(sockets, interests[0..sockets.len], ready, timeout_ms);
1367 }
1368
1369 pub fn pollSet(
1370 sockets: []const Socket,
1371 interests: []const PollInterest,
1372 ready: []bool,
1373 timeout_ms: i32,
1374 ) PollError!u8 {
1375 std.debug.assert(sockets.len == ready.len);
1376 std.debug.assert(sockets.len == interests.len);
1377 std.debug.assert(sockets.len <= readiness_max);
1378 @memset(ready, false);
1379 if (sockets.len == 0) return 0;
1380 return switch (socketPolicy()) {
1381 .unsupported => error.UnsupportedPlatform,
1382 .linux_syscall => pollSetLinux(sockets, interests, ready, timeout_ms),
1383 .posix_host => pollSetPosix(sockets, interests, ready, timeout_ms),
1384 };
1385 }
1386
1387 fn readinessRequest(
1388 sockets: []const Socket,
1389 interests: []const PollInterest,
1390 buffer: *[readiness_max]posix.pollfd,
1391 ) []posix.pollfd {
1392 std.debug.assert(sockets.len <= buffer.len);
1393 std.debug.assert(sockets.len == interests.len);
1394 for (sockets, interests, buffer[0..sockets.len]) |socket_fd, interest, *slot| {
1395 slot.* = .{
1396 .fd = socket_fd,
1397 .events = switch (interest) {
1398 .read => posix.POLL.IN,
1399 .write => posix.POLL.OUT,
1400 },
1401 .revents = 0,
1402 };
1403 }
1404 return buffer[0..sockets.len];
1405 }
1406
1407 fn readinessOutcome(
1408 fds: []const posix.pollfd,
1409 interests: []const PollInterest,
1410 ready: []bool,
1411 ) u8 {
1412 std.debug.assert(fds.len == ready.len);
1413 std.debug.assert(fds.len == interests.len);
1414 var count: u8 = 0;
1415 for (fds, interests, ready) |slot, interest, *flag| {
1416 const mask: @TypeOf(slot.revents) = switch (interest) {
1417 .read => readable_mask,
1418 .write => writable_mask,
1419 };
1420 flag.* = (slot.revents & mask) != 0;
1421 count += @intFromBool(flag.*);
1422 }
1423 return count;
1424 }
1425
1426 fn pollSetLinux(
1427 sockets: []const Socket,
1428 interests: []const PollInterest,
1429 ready: []bool,
1430 timeout_ms: i32,
1431 ) PollError!u8 {
1432 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1433
1434 var buffer: [readiness_max]posix.pollfd = undefined;
1435 const fds = readinessRequest(sockets, interests, &buffer);
1436 while (true) {
1437 const rc = linux.poll(fds.ptr, @intCast(fds.len), timeout_ms);
1438 switch (linux.errno(rc)) {
1439 .SUCCESS => return if (rc == 0) 0 else readinessOutcome(fds, interests, ready),
1440 .INTR => continue,
1441 else => return error.PollFailed,
1442 }
1443 }
1444 }
1445
1446 fn pollSetPosix(
1447 sockets: []const Socket,
1448 interests: []const PollInterest,
1449 ready: []bool,
1450 timeout_ms: i32,
1451 ) PollError!u8 {
1452 if (!@hasDecl(posix, "poll")) return error.UnsupportedPlatform;
1453
1454 var buffer: [readiness_max]posix.pollfd = undefined;
1455 const fds = readinessRequest(sockets, interests, &buffer);
1456 const signalled = posix.poll(fds, timeout_ms) catch return error.PollFailed;
1457 return if (signalled == 0) 0 else readinessOutcome(fds, interests, ready);
1458 }
1459
1460 fn pollReadableLinux(socket_fd: Socket, timeout_ms: i32) PollError!bool {
1461 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1462
1463 var fds = [_]posix.pollfd{.{
1464 .fd = socket_fd,
1465 .events = posix.POLL.IN,
1466 .revents = 0,
1467 }};
1468 while (true) {
1469 const rc = linux.poll(&fds, fds.len, timeout_ms);
1470 switch (linux.errno(rc)) {
1471 .SUCCESS => {
1472 return rc > 0 and (fds[0].revents & readable_mask) != 0;
1473 },
1474 .INTR => continue,
1475 else => return error.PollFailed,
1476 }
1477 }
1478 }
1479
1480 fn pollReadablePosix(socket_fd: Socket, timeout_ms: i32) PollError!bool {
1481 if (!@hasDecl(posix, "poll")) return error.UnsupportedPlatform;
1482
1483 var fds = [_]posix.pollfd{.{
1484 .fd = socket_fd,
1485 .events = posix.POLL.IN,
1486 .revents = 0,
1487 }};
1488 const n = posix.poll(&fds, timeout_ms) catch return error.PollFailed;
1489 return n > 0 and (fds[0].revents & readable_mask) != 0;
1490 }
1491
1492 fn pollWritableLinux(socket_fd: Socket, timeout_ms: i32) PollError!bool {
1493 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1494
1495 var fds = [_]posix.pollfd{.{
1496 .fd = socket_fd,
1497 .events = posix.POLL.OUT,
1498 .revents = 0,
1499 }};
1500 while (true) {
1501 const rc = linux.poll(&fds, fds.len, timeout_ms);
1502 switch (linux.errno(rc)) {
1503 .SUCCESS => {
1504 return rc > 0 and (fds[0].revents & writable_mask) != 0;
1505 },
1506 .INTR => continue,
1507 else => return error.PollFailed,
1508 }
1509 }
1510 }
1511
1512 fn pollWritablePosix(socket_fd: Socket, timeout_ms: i32) PollError!bool {
1513 if (!@hasDecl(posix, "poll")) return error.UnsupportedPlatform;
1514
1515 var fds = [_]posix.pollfd{.{
1516 .fd = socket_fd,
1517 .events = posix.POLL.OUT,
1518 .revents = 0,
1519 }};
1520 const n = posix.poll(&fds, timeout_ms) catch return error.PollFailed;
1521 return n > 0 and (fds[0].revents & writable_mask) != 0;
1522 }
1523
1524 pub fn setReuseAddress(socket_fd: Socket) SetOptionError!void {
1525 var enabled: c_int = 1;
1526 return setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, std.mem.asBytes(&enabled));
1527 }
1528
1529 pub fn setTcpNoDelay(socket_fd: Socket) SetOptionError!void {
1530 if (comptime @TypeOf(posix.TCP) == void) return error.UnsupportedPlatform;
1531 if (comptime !@hasDecl(posix.TCP, "NODELAY")) return error.UnsupportedPlatform;
1532 var enabled: c_int = 1;
1533 return setSocketOption(socket_fd, posix.IPPROTO.TCP, posix.TCP.NODELAY, std.mem.asBytes(&enabled));
1534 }
1535
1536 pub fn setReadTimeout(socket_fd: Socket, timeout_ms: u32) SetOptionError!void {
1537 const timeout = timeoutFromMillis(timeout_ms);
1538 return setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.RCVTIMEO, std.mem.asBytes(&timeout));
1539 }
1540
1541 pub fn setWriteTimeout(socket_fd: Socket, timeout_ms: u32) SetOptionError!void {
1542 const timeout = timeoutFromMillis(timeout_ms);
1543 return setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.SNDTIMEO, std.mem.asBytes(&timeout));
1544 }
1545
1546 pub fn setReceiveBuffer(
1547 socket_fd: Socket,
1548 bytes: u32,
1549 ) SetOptionError!void {
1550 if (bytes == 0 or bytes > std.math.maxInt(c_int)) {
1551 return error.InvalidArgument;
1552 }
1553 var capacity: c_int = @intCast(bytes);
1554 return setSocketOption(
1555 socket_fd,
1556 posix.SOL.SOCKET,
1557 posix.SO.RCVBUF,
1558 std.mem.asBytes(&capacity),
1559 );
1560 }
1561
1562 pub fn setNonBlocking(socket_fd: Socket) SetOptionError!void {
1563 fd.setNonBlocking(socket_fd) catch |err| switch (err) {
1564 error.UnsupportedPlatform => return error.UnsupportedPlatform,
1565 error.InvalidFlags => return error.InvalidArgument,
1566 error.FlagUpdateFailed => return error.SetOptionFailed,
1567 };
1568 }
1569
1570 pub fn ip4AddressForHost(host: []const u8, port: u16) AddressError!Ip4Address {
1571 if (host.len == 0 or std.mem.eql(u8, host, "0.0.0.0")) return ip4Address(.{ 0, 0, 0, 0 }, port);
1572 if (std.mem.eql(u8, host, "localhost")) return ip4Address(.{ 127, 0, 0, 1 }, port);
1573
1574 var parts: [4]u8 = undefined;
1575 var iter = std.mem.splitScalar(u8, host, '.');
1576 var index: usize = 0;
1577 while (iter.next()) |part| : (index += 1) {
1578 if (index >= parts.len) return error.InvalidAddress;
1579 parts[index] = std.fmt.parseInt(u8, part, 10) catch return error.InvalidAddress;
1580 }
1581 if (index != parts.len) return error.InvalidAddress;
1582 return ip4Address(parts, port);
1583 }
1584
1585 pub fn ip6AddressForHost(host: []const u8, port: u16) AddressError!Ip6Address {
1586 if (host.len == 0 or std.mem.eql(u8, host, "::")) return ip6Address(.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, port);
1587 if (std.mem.eql(u8, host, "localhost")) return ip6Address(.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, port);
1588 const parsed = std.Io.net.Ip6Address.parse(host, port) catch return error.InvalidAddress;
1589 return ip6Address(parsed.bytes, port);
1590 }
1591
1592 pub fn ipAddressForHost(host: []const u8, port: u16) AddressError!IpAddress {
1593 if (ip4AddressForHost(host, port)) |address| return .{ .ip4 = address } else |_| {}
1594 return .{ .ip6 = try ip6AddressForHost(host, port) };
1595 }
1596
1597 pub fn ipAddressPort(address: IpAddress) u16 {
1598 return switch (address) {
1599 .ip4 => |ip4| std.mem.bigToNative(u16, ip4.port),
1600 .ip6 => |ip6| std.mem.bigToNative(u16, ip6.port),
1601 };
1602 }
1603
1604 pub fn ipAddressEql(lhs: IpAddress, rhs: IpAddress) bool {
1605 return switch (lhs) {
1606 .ip4 => |left| switch (rhs) {
1607 .ip4 => |right| left.addr == right.addr and left.port == right.port,
1608 .ip6 => false,
1609 },
1610 .ip6 => |left| switch (rhs) {
1611 .ip4 => false,
1612 .ip6 => |right| left.port == right.port and
1613 left.flowinfo == right.flowinfo and
1614 left.scope_id == right.scope_id and
1615 std.mem.eql(u8, &left.addr, &right.addr),
1616 },
1617 };
1618 }
1619
1620 pub fn resolveIp4AddressForHost(host: []const u8, port: u16, options: ResolveOptions) ResolveError!Ip4Address {
1621 return ip4AddressForHost(host, port) catch {
1622 if (options.hosts_path) |hosts_path| {
1623 if (hostsAddressForHost(host, port, hosts_path, .a)) |address| return address.ip4;
1624 }
1625 const address = if (options.nameservers.len != 0)
1626 try resolveAddressWithNameservers(host, port, options.nameservers, options.timeout_ms, .a)
1627 else address: {
1628 const config = try resolvConfConfig();
1629 break :address try resolveAddressWithConfig(host, port, &config, options.timeout_ms, .a);
1630 };
1631 return switch (address) {
1632 .ip4 => |ip4| ip4,
1633 .ip6 => error.NoAddress,
1634 };
1635 };
1636 }
1637
1638 pub fn resolveIp6AddressForHost(host: []const u8, port: u16, options: ResolveOptions) ResolveError!Ip6Address {
1639 return ip6AddressForHost(host, port) catch {
1640 if (options.hosts_path) |hosts_path| {
1641 if (hostsAddressForHost(host, port, hosts_path, .aaaa)) |address| return address.ip6;
1642 }
1643 const address = if (options.nameservers.len != 0)
1644 try resolveAddressWithNameservers(host, port, options.nameservers, options.timeout_ms, .aaaa)
1645 else address: {
1646 const config = try resolvConfConfig();
1647 break :address try resolveAddressWithConfig(host, port, &config, options.timeout_ms, .aaaa);
1648 };
1649 return switch (address) {
1650 .ip4 => error.NoAddress,
1651 .ip6 => |ip6| ip6,
1652 };
1653 };
1654 }
1655
1656 pub fn resolveIpAddressForHost(host: []const u8, port: u16, options: ResolveOptions) ResolveError!IpAddress {
1657 if (ipAddressForHost(host, port)) |address| return address else |_| {}
1658 if (options.hosts_path) |hosts_path| {
1659 if (hostsAddressForHost(host, port, hosts_path, .a)) |address| return address;
1660 if (hostsAddressForHost(host, port, hosts_path, .aaaa)) |address| return address;
1661 }
1662 const dns_options = ResolveOptions{
1663 .hosts_path = null,
1664 .nameservers = options.nameservers,
1665 .timeout_ms = options.timeout_ms,
1666 };
1667 return .{ .ip4 = resolveIp4AddressForHost(host, port, dns_options) catch |err| switch (err) {
1668 error.NoAddress => return .{ .ip6 = try resolveIp6AddressForHost(host, port, dns_options) },
1669 else => return err,
1670 } };
1671 }
1672
1673 pub fn ip4Address(bytes: [4]u8, port: u16) Ip4Address {
1674 return .{
1675 .port = std.mem.nativeToBig(u16, port),
1676 .addr = @bitCast(bytes),
1677 };
1678 }
1679
1680 pub fn ip6Address(bytes: [16]u8, port: u16) Ip6Address {
1681 return .{
1682 .port = std.mem.nativeToBig(u16, port),
1683 .flowinfo = 0,
1684 .addr = bytes,
1685 .scope_id = 0,
1686 };
1687 }
1688
1689 fn resolveAddressWithNameserver(
1690 host: []const u8,
1691 port: u16,
1692 nameserver: IpAddress,
1693 timeout_ms: u32,
1694 record_type: DnsRecordType,
1695 ) ResolveError!IpAddress {
1696 var query: [512]u8 = undefined;
1697 const query_id = try secureDnsQueryId();
1698 const encoded = try dnsQuery(&query, host, record_type, query_id);
1699
1700 const socket_fd = try udpDatagramSocketForAddress(nameserver, .{ .close_on_exec = true });
1701 defer close(socket_fd);
1702 if (timeout_ms != 0) {
1703 try setReadTimeout(socket_fd, timeout_ms);
1704 try setWriteTimeout(socket_fd, timeout_ms);
1705 }
1706 try connectIpAddress(socket_fd, nameserver);
1707 try sendAllSocket(socket_fd, query[0..encoded.len]);
1708
1709 var response: [512]u8 = undefined;
1710 const response_len = try recv(socket_fd, &response, 0);
1711 if (try dnsResponseTruncated(response[0..response_len], encoded.question)) {
1712 return resolveAddressWithNameserverTcp(
1713 query[0..encoded.len],
1714 encoded.question,
1715 port,
1716 nameserver,
1717 timeout_ms,
1718 );
1719 }
1720 return dnsAddressFromResponse(response[0..response_len], encoded.question, port);
1721 }
1722
1723 fn resolveAddressWithNameserverTcp(
1724 query: []const u8,
1725 question: DnsExpectedQuestion,
1726 port: u16,
1727 nameserver: IpAddress,
1728 timeout_ms: u32,
1729 ) ResolveError!IpAddress {
1730 var request: [514]u8 = undefined;
1731 std.mem.writeInt(u16, request[0..2], @intCast(query.len), .big);
1732 @memcpy(request[2..][0..query.len], query);
1733
1734 const socket_fd = try tcpStreamSocketForAddress(nameserver, .{ .close_on_exec = true });
1735 defer close(socket_fd);
1736 if (timeout_ms != 0) {
1737 try setReadTimeout(socket_fd, timeout_ms);
1738 try setWriteTimeout(socket_fd, timeout_ms);
1739 }
1740 try connectIpAddress(socket_fd, nameserver);
1741 try sendAllSocket(socket_fd, request[0 .. query.len + 2]);
1742
1743 var response_len_bytes: [2]u8 = undefined;
1744 try recvExactSocket(socket_fd, &response_len_bytes);
1745 const response_len = std.mem.readInt(u16, &response_len_bytes, .big);
1746 if (response_len == 0) return error.MalformedResponse;
1747 var response: [4096]u8 = undefined;
1748 if (response_len > response.len) return error.QueryTooLarge;
1749 try recvExactSocket(socket_fd, response[0..response_len]);
1750 return dnsAddressFromResponse(response[0..response_len], question, port);
1751 }
1752
1753 fn resolveAddressWithNameservers(
1754 host: []const u8,
1755 port: u16,
1756 nameservers: []const IpAddress,
1757 timeout_ms: u32,
1758 record_type: DnsRecordType,
1759 ) ResolveError!IpAddress {
1760 if (nameservers.len == 0) return error.NoNameserver;
1761 var last_error: ResolveError = error.NoNameserver;
1762 for (nameservers) |nameserver| {
1763 return resolveAddressWithNameserver(host, port, nameserver, timeout_ms, record_type) catch |err| {
1764 last_error = err;
1765 continue;
1766 };
1767 }
1768 return last_error;
1769 }
1770
1771 fn resolveAddressWithConfig(
1772 host: []const u8,
1773 port: u16,
1774 config: *const ResolverConfig,
1775 timeout_ms: u32,
1776 record_type: DnsRecordType,
1777 ) ResolveError!IpAddress {
1778 if (config.search_domains.count() == 0 or hostIsAbsolute(host)) {
1779 return resolveAddressWithNameservers(canonicalHostName(host), port, config.nameservers.slice(), timeout_ms, record_type);
1780 }
1781
1782 var last_error: ResolveError = error.NoAddress;
1783 const name = canonicalHostName(host);
1784 const name_first = hostDotCount(name) >= config.ndots;
1785 if (name_first) {
1786 if (tryResolveAddressWithNameservers(name, port, config.nameservers.slice(), timeout_ms, record_type, &last_error)) |address| return address;
1787 }
1788 var index: usize = 0;
1789 while (index < config.search_domains.count()) : (index += 1) {
1790 if (tryResolveSearchDomainName(name, config.search_domains.get(index), port, config.nameservers.slice(), timeout_ms, record_type, &last_error)) |address| return address;
1791 }
1792 if (!name_first) {
1793 if (tryResolveAddressWithNameservers(name, port, config.nameservers.slice(), timeout_ms, record_type, &last_error)) |address| return address;
1794 }
1795 return last_error;
1796 }
1797
1798 fn tryResolveAddressWithNameservers(
1799 host: []const u8,
1800 port: u16,
1801 nameservers: []const IpAddress,
1802 timeout_ms: u32,
1803 record_type: DnsRecordType,
1804 last_error: *ResolveError,
1805 ) ?IpAddress {
1806 return resolveAddressWithNameservers(host, port, nameservers, timeout_ms, record_type) catch |err| {
1807 last_error.* = err;
1808 return null;
1809 };
1810 }
1811
1812 fn tryResolveSearchDomainName(
1813 host: []const u8,
1814 domain: []const u8,
1815 port: u16,
1816 nameservers: []const IpAddress,
1817 timeout_ms: u32,
1818 record_type: DnsRecordType,
1819 last_error: *ResolveError,
1820 ) ?IpAddress {
1821 var candidate: [dns_max_name_len]u8 = undefined;
1822 const candidate_len = searchDomainName(&candidate, host, domain) catch |err| {
1823 last_error.* = err;
1824 return null;
1825 };
1826 return tryResolveAddressWithNameservers(candidate[0..candidate_len], port, nameservers, timeout_ms, record_type, last_error);
1827 }
1828
1829 fn sendAllSocket(socket_fd: Socket, bytes: []const u8) ResolveError!void {
1830 var sent: usize = 0;
1831 while (sent < bytes.len) {
1832 const n = try send(socket_fd, bytes[sent..], 0);
1833 if (n == 0) return error.NoAddress;
1834 sent += n;
1835 }
1836 }
1837
1838 fn recvExactSocket(socket_fd: Socket, buffer: []u8) ResolveError!void {
1839 var filled: usize = 0;
1840 while (filled < buffer.len) {
1841 const n = try recv(socket_fd, buffer[filled..], 0);
1842 if (n == 0) return error.NoAddress;
1843 filled += n;
1844 }
1845 }
1846
1847 fn searchDomainName(buffer: []u8, host: []const u8, domain: []const u8) ResolveError!usize {
1848 const name = canonicalHostName(host);
1849 const suffix = canonicalHostName(domain);
1850 if (name.len == 0 or suffix.len == 0) return error.InvalidHostName;
1851 if (name.len + 1 + suffix.len > dns_max_name_len or name.len + 1 + suffix.len > buffer.len) return error.InvalidHostName;
1852 @memcpy(buffer[0..name.len], name);
1853 buffer[name.len] = '.';
1854 @memcpy(buffer[name.len + 1 ..][0..suffix.len], suffix);
1855 return name.len + 1 + suffix.len;
1856 }
1857
1858 fn hostIsAbsolute(host: []const u8) bool {
1859 return host.len > 1 and host[host.len - 1] == '.';
1860 }
1861
1862 fn hostDotCount(host: []const u8) usize {
1863 var count: usize = 0;
1864 for (host) |byte| {
1865 if (byte == '.') count += 1;
1866 }
1867 return count;
1868 }
1869
1870 fn hostsAddressForHost(host: []const u8, port: u16, path: []const u8, record_type: DnsRecordType) ?IpAddress {
1871 const file = posix.openat(posix.AT.FDCWD, path, .{ .CLOEXEC = true }, 0) catch
1872 return null;
1873 defer fd.close(file);
1874
1875 var read_buffer: [4096]u8 = undefined;
1876 var line_buffer: [4096]u8 = undefined;
1877 var line_len: usize = 0;
1878 var skipping_line = false;
1879 while (true) {
1880 const n = fd.read(file, read_buffer[0..]) catch return null;
1881 if (n == 0) break;
1882 for (read_buffer[0..n]) |byte| {
1883 if (byte == '\n') {
1884 if (!skipping_line) {
1885 if (hostsAddressFromLine(line_buffer[0..line_len], host, port, record_type)) |address| return address;
1886 }
1887 line_len = 0;
1888 skipping_line = false;
1889 } else if (!skipping_line) {
1890 if (line_len == line_buffer.len) {
1891 line_len = 0;
1892 skipping_line = true;
1893 } else {
1894 line_buffer[line_len] = byte;
1895 line_len += 1;
1896 }
1897 }
1898 }
1899 }
1900 if (!skipping_line) {
1901 if (hostsAddressFromLine(line_buffer[0..line_len], host, port, record_type)) |address| return address;
1902 }
1903 return null;
1904 }
1905
1906 fn hostsAddressFromContents(contents: []const u8, host: []const u8, port: u16, record_type: DnsRecordType) ?IpAddress {
1907 var lines = std.mem.splitScalar(u8, contents, '\n');
1908 while (lines.next()) |line| {
1909 if (hostsAddressFromLine(line, host, port, record_type)) |address| return address;
1910 }
1911 return null;
1912 }
1913
1914 fn hostsAddressFromLine(line: []const u8, host: []const u8, port: u16, record_type: DnsRecordType) ?IpAddress {
1915 const stripped = stripDnsLine(line);
1916 var fields = std.mem.tokenizeAny(u8, stripped, " \t\r");
1917 const address_text = fields.next() orelse return null;
1918 const address = addressFromText(address_text, port, record_type) catch return null;
1919 while (fields.next()) |name| {
1920 if (hostNamesEqual(name, host)) return address;
1921 }
1922 return null;
1923 }
1924
1925 fn addressFromText(text: []const u8, port: u16, record_type: DnsRecordType) AddressError!IpAddress {
1926 return switch (record_type) {
1927 .a => .{ .ip4 = try ip4AddressForHost(text, port) },
1928 .aaaa => .{ .ip6 = try ip6AddressForHost(text, port) },
1929 };
1930 }
1931
1932 fn hostNamesEqual(lhs: []const u8, rhs: []const u8) bool {
1933 const left = canonicalHostName(lhs);
1934 const right = canonicalHostName(rhs);
1935 if (left.len != right.len) return false;
1936 for (left, right) |left_byte, right_byte| {
1937 if (std.ascii.toLower(left_byte) != std.ascii.toLower(right_byte)) return false;
1938 }
1939 return true;
1940 }
1941
1942 fn canonicalHostName(host: []const u8) []const u8 {
1943 if (host.len > 1 and host[host.len - 1] == '.') return host[0 .. host.len - 1];
1944 return host;
1945 }
1946
1947 const resolv_conf_nameserver_limit = 3;
1948 const resolv_conf_search_limit = 6;
1949 const resolv_conf_search_storage_len = 256;
1950
1951 const ResolverConfig = struct {
1952 nameservers: Nameservers = .{},
1953 search_domains: SearchDomains = .{},
1954 ndots: u8 = default_ndots,
1955 };
1956
1957 const Nameservers = struct {
1958 buffer: [resolv_conf_nameserver_limit]IpAddress = undefined,
1959 len: usize = 0,
1960
1961 fn append(self: *Nameservers, address: IpAddress) void {
1962 if (self.len == self.buffer.len) return;
1963 self.buffer[self.len] = address;
1964 self.len += 1;
1965 }
1966
1967 fn slice(self: *const Nameservers) []const IpAddress {
1968 return self.buffer[0..self.len];
1969 }
1970 };
1971
1972 const SearchDomain = struct {
1973 offset: usize,
1974 len: usize,
1975 };
1976
1977 const SearchDomains = struct {
1978 entries: [resolv_conf_search_limit]SearchDomain = undefined,
1979 len: usize = 0,
1980 storage: [resolv_conf_search_storage_len]u8 = undefined,
1981 storage_len: usize = 0,
1982
1983 fn clear(self: *SearchDomains) void {
1984 self.len = 0;
1985 self.storage_len = 0;
1986 }
1987
1988 fn append(self: *SearchDomains, domain: []const u8) void {
1989 const name = canonicalHostName(domain);
1990 if (name.len == 0 or name.len > dns_max_name_len) return;
1991 if (self.len == self.entries.len) return;
1992 if (self.storage_len + name.len > self.storage.len) return;
1993 const offset = self.storage_len;
1994 @memcpy(self.storage[offset..][0..name.len], name);
1995 self.entries[self.len] = .{ .offset = offset, .len = name.len };
1996 self.len += 1;
1997 self.storage_len += name.len;
1998 }
1999
2000 fn count(self: *const SearchDomains) usize {
2001 return self.len;
2002 }
2003
2004 fn get(self: *const SearchDomains, index: usize) []const u8 {
2005 const entry = self.entries[index];
2006 return self.storage[entry.offset..][0..entry.len];
2007 }
2008 };
2009
2010 fn resolvConfConfig() ResolveError!ResolverConfig {
2011 const resolv_conf = "/etc/resolv.conf";
2012 const file = posix.openat(posix.AT.FDCWD, resolv_conf, .{ .CLOEXEC = true }, 0) catch
2013 return error.NoNameserver;
2014 defer fd.close(file);
2015
2016 var buffer: [4096]u8 = undefined;
2017 var filled: usize = 0;
2018 while (filled < buffer.len) {
2019 const n = fd.read(file, buffer[filled..]) catch return error.NoNameserver;
2020 if (n == 0) break;
2021 filled += n;
2022 }
2023 const config = configFromResolvConf(buffer[0..filled]);
2024 if (config.nameservers.len == 0) return error.NoNameserver;
2025 return config;
2026 }
2027
2028 fn configFromResolvConf(contents: []const u8) ResolverConfig {
2029 var config = ResolverConfig{};
2030 var lines = std.mem.splitScalar(u8, contents, '\n');
2031 while (lines.next()) |line| {
2032 const stripped = stripDnsLine(line);
2033 var fields = std.mem.tokenizeAny(u8, stripped, " \t\r");
2034 const key = fields.next() orelse continue;
2035 if (std.mem.eql(u8, key, "nameserver")) {
2036 const value = fields.next() orelse continue;
2037 config.nameservers.append(ipAddressForHost(value, 53) catch continue);
2038 } else if (std.mem.eql(u8, key, "search")) {
2039 config.search_domains.clear();
2040 while (fields.next()) |domain| config.search_domains.append(domain);
2041 } else if (std.mem.eql(u8, key, "domain")) {
2042 config.search_domains.clear();
2043 const domain = fields.next() orelse continue;
2044 config.search_domains.append(domain);
2045 } else if (std.mem.eql(u8, key, "options")) {
2046 while (fields.next()) |option| {
2047 if (ndotsFromOption(option)) |ndots| config.ndots = ndots;
2048 }
2049 }
2050 }
2051 return config;
2052 }
2053
2054 fn ndotsFromOption(option: []const u8) ?u8 {
2055 const prefix = "ndots:";
2056 if (!std.mem.startsWith(u8, option, prefix)) return null;
2057 const parsed = std.fmt.parseInt(u8, option[prefix.len..], 10) catch return null;
2058 return @min(parsed, 15);
2059 }
2060
2061 fn stripDnsLine(line: []const u8) []const u8 {
2062 var end = line.len;
2063 if (std.mem.indexOfScalar(u8, line, '#')) |index| end = @min(end, index);
2064 if (std.mem.indexOfScalar(u8, line, ';')) |index| end = @min(end, index);
2065 return std.mem.trim(u8, line[0..end], " \t\r");
2066 }
2067
2068 const dns_header_len = 12;
2069 const dns_name_wire_capacity = dns_max_name_len + 2;
2070 const dns_name_step_limit = dns_name_wire_capacity;
2071 const dns_cname_hop_limit = 16;
2072 const dns_record_min_len = 11;
2073 const dns_class_in = 1;
2074 const dns_cname_type = 5;
2075
2076 const DnsName = struct {
2077 bytes: [dns_name_wire_capacity]u8 = undefined,
2078 len: usize = 0,
2079
2080 fn slice(self: *const DnsName) []const u8 {
2081 return self.bytes[0..self.len];
2082 }
2083
2084 fn eql(self: *const DnsName, other: *const DnsName) bool {
2085 return self.eqlSlice(other.slice());
2086 }
2087
2088 fn eqlSlice(self: *const DnsName, other: []const u8) bool {
2089 if (self.len != other.len) return false;
2090 for (self.slice(), other) |left, right| {
2091 if (std.ascii.toLower(left) != std.ascii.toLower(right)) return false;
2092 }
2093 return true;
2094 }
2095 };
2096
2097 const DecodedDnsName = struct {
2098 name: DnsName,
2099 end: usize,
2100 };
2101
2102 const DnsQuestion = struct {
2103 id: u16,
2104 name: DnsName,
2105 record_type: u16,
2106 class: u16,
2107 end: usize,
2108 };
2109
2110 const DnsExpectedQuestion = struct {
2111 id: u16,
2112 name: []const u8,
2113 record_type: u16,
2114 class: u16,
2115 };
2116
2117 const DnsQuery = struct {
2118 len: usize,
2119 question: DnsExpectedQuestion,
2120 };
2121
2122 const DnsResponseView = struct {
2123 flags: u16,
2124 question: DnsQuestion,
2125 answer_count: u16,
2126 authority_count: u16,
2127 additional_count: u16,
2128 };
2129
2130 const DnsRecord = struct {
2131 owner: DnsName,
2132 record_type: u16,
2133 class: u16,
2134 data_start: usize,
2135 data_end: usize,
2136 };
2137
2138 const DnsAnswerScan = struct {
2139 address: ?IpAddress = null,
2140 cname: ?DnsName = null,
2141 };
2142
2143 fn dnsQuery(
2144 buffer: []u8,
2145 host: []const u8,
2146 record_type: DnsRecordType,
2147 query_id: u16,
2148 ) ResolveError!DnsQuery {
2149 if (buffer.len < 17) return error.QueryTooLarge;
2150 const name = canonicalHostName(host);
2151 if (name.len == 0 or name.len > dns_max_name_len) return error.InvalidHostName;
2152
2153 std.mem.writeInt(u16, buffer[0..2], query_id, .big);
2154 std.mem.writeInt(u16, buffer[2..4], 0x0100, .big);
2155 std.mem.writeInt(u16, buffer[4..6], 1, .big);
2156 std.mem.writeInt(u16, buffer[6..8], 0, .big);
2157 std.mem.writeInt(u16, buffer[8..10], 0, .big);
2158 std.mem.writeInt(u16, buffer[10..12], 0, .big);
2159
2160 var index: usize = dns_header_len;
2161 var labels = std.mem.splitScalar(u8, name, '.');
2162 while (labels.next()) |label| {
2163 if (label.len == 0 or label.len > 63) return error.InvalidHostName;
2164 if (index + 1 + label.len >= buffer.len) return error.QueryTooLarge;
2165 buffer[index] = @intCast(label.len);
2166 index += 1;
2167 for (label) |byte| {
2168 if (!dnsLabelByte(byte)) return error.InvalidHostName;
2169 buffer[index] = std.ascii.toLower(byte);
2170 index += 1;
2171 }
2172 }
2173 if (index + 5 > buffer.len) return error.QueryTooLarge;
2174 buffer[index] = 0;
2175 index += 1;
2176 const wire_name = buffer[dns_header_len..index];
2177 std.mem.writeInt(u16, buffer[index..][0..2], @backingInt(record_type), .big);
2178 index += 2;
2179 std.mem.writeInt(u16, buffer[index..][0..2], 1, .big);
2180 index += 2;
2181 return .{
2182 .len = index,
2183 .question = .{
2184 .id = query_id,
2185 .name = wire_name,
2186 .record_type = @backingInt(record_type),
2187 .class = dns_class_in,
2188 },
2189 };
2190 }
2191
2192 fn dnsLabelByte(byte: u8) bool {
2193 return std.ascii.isAlphanumeric(byte) or byte == '-';
2194 }
2195
2196 fn secureDnsQueryId() random.SecureError!u16 {
2197 return random.csprngU16();
2198 }
2199
2200 fn decodeDnsName(packet: []const u8, start: usize) ResolveError!DecodedDnsName {
2201 var decoded = DecodedDnsName{ .name = .{}, .end = undefined };
2202 var cursor = start;
2203 var compressed_end: ?usize = null;
2204 for (0..dns_name_step_limit) |_| {
2205 if (cursor >= packet.len) return error.MalformedResponse;
2206 const label_len = packet[cursor];
2207 if ((label_len & 0xc0) == 0xc0) {
2208 if (packet.len - cursor < 2) return error.MalformedResponse;
2209 const target = (@as(usize, label_len & 0x3f) << 8) | packet[cursor + 1];
2210 if (target < dns_header_len or target >= cursor or target >= packet.len) {
2211 return error.MalformedResponse;
2212 }
2213 if (compressed_end == null) compressed_end = cursor + 2;
2214 cursor = target;
2215 continue;
2216 }
2217 if ((label_len & 0xc0) != 0) return error.MalformedResponse;
2218 cursor += 1;
2219 if (label_len == 0) {
2220 decoded.name.bytes[decoded.name.len] = 0;
2221 decoded.name.len += 1;
2222 decoded.end = compressed_end orelse cursor;
2223 return decoded;
2224 }
2225 if (label_len > packet.len - cursor) return error.MalformedResponse;
2226 const next_len = decoded.name.len + 1 + label_len;
2227 if (next_len >= decoded.name.bytes.len) return error.MalformedResponse;
2228 decoded.name.bytes[decoded.name.len] = label_len;
2229 decoded.name.len += 1;
2230 @memcpy(
2231 decoded.name.bytes[decoded.name.len..][0..label_len],
2232 packet[cursor..][0..label_len],
2233 );
2234 decoded.name.len += label_len;
2235 cursor += label_len;
2236 }
2237 return error.MalformedResponse;
2238 }
2239
2240 fn parseDnsQuestion(packet: []const u8) ResolveError!DnsQuestion {
2241 if (packet.len < dns_header_len) return error.MalformedResponse;
2242 if (std.mem.readInt(u16, packet[4..6], .big) != 1) return error.MalformedResponse;
2243 const decoded = try decodeDnsName(packet, dns_header_len);
2244 if (decoded.end > packet.len or packet.len - decoded.end < 4) return error.MalformedResponse;
2245 return .{
2246 .id = std.mem.readInt(u16, packet[0..2], .big),
2247 .name = decoded.name,
2248 .record_type = std.mem.readInt(u16, packet[decoded.end..][0..2], .big),
2249 .class = std.mem.readInt(u16, packet[decoded.end + 2 ..][0..2], .big),
2250 .end = decoded.end + 4,
2251 };
2252 }
2253
2254 fn dnsResponseView(
2255 response: []const u8,
2256 expected: DnsExpectedQuestion,
2257 ) ResolveError!DnsResponseView {
2258 const actual = try parseDnsQuestion(response);
2259 const flags = std.mem.readInt(u16, response[2..4], .big);
2260 if ((flags & 0x8000) == 0 or (flags & 0x7800) != 0) return error.MalformedResponse;
2261 if ((flags & 0x0040) != 0) return error.MalformedResponse;
2262 if (actual.id != expected.id) return error.MalformedResponse;
2263 if (!actual.name.eqlSlice(expected.name)) return error.MalformedResponse;
2264 if (actual.record_type != expected.record_type) return error.MalformedResponse;
2265 if (actual.class != expected.class) return error.MalformedResponse;
2266 return .{
2267 .flags = flags,
2268 .question = actual,
2269 .answer_count = std.mem.readInt(u16, response[6..8], .big),
2270 .authority_count = std.mem.readInt(u16, response[8..10], .big),
2271 .additional_count = std.mem.readInt(u16, response[10..12], .big),
2272 };
2273 }
2274
2275 fn parseDnsRecord(response: []const u8, index: *usize) ResolveError!DnsRecord {
2276 const owner = try decodeDnsName(response, index.*);
2277 if (owner.end > response.len or response.len - owner.end < 10) return error.MalformedResponse;
2278 const data_len = std.mem.readInt(u16, response[owner.end + 8 ..][0..2], .big);
2279 const data_start = owner.end + 10;
2280 if (data_len > response.len - data_start) return error.MalformedResponse;
2281 const data_end = data_start + data_len;
2282 index.* = data_end;
2283 return .{
2284 .owner = owner.name,
2285 .record_type = std.mem.readInt(u16, response[owner.end..][0..2], .big),
2286 .class = std.mem.readInt(u16, response[owner.end + 2 ..][0..2], .big),
2287 .data_start = data_start,
2288 .data_end = data_end,
2289 };
2290 }
2291
2292 fn validateDnsRecordSections(response: []const u8, view: DnsResponseView) ResolveError!void {
2293 const record_count = @as(usize, view.answer_count) +
2294 @as(usize, view.authority_count) +
2295 @as(usize, view.additional_count);
2296 if (view.question.end > response.len) return error.MalformedResponse;
2297 if (record_count > (response.len - view.question.end) / dns_record_min_len) {
2298 return error.MalformedResponse;
2299 }
2300 var index = view.question.end;
2301 for (0..record_count) |_| _ = try parseDnsRecord(response, &index);
2302 if (index != response.len) return error.MalformedResponse;
2303 }
2304
2305 fn addressFromDnsRecord(
2306 response: []const u8,
2307 record: DnsRecord,
2308 record_type: DnsRecordType,
2309 port: u16,
2310 ) ResolveError!IpAddress {
2311 const data = response[record.data_start..record.data_end];
2312 return switch (record_type) {
2313 .a => if (data.len == 4)
2314 .{ .ip4 = ip4Address(data[0..4].*, port) }
2315 else
2316 error.MalformedResponse,
2317 .aaaa => if (data.len == 16)
2318 .{ .ip6 = ip6Address(data[0..16].*, port) }
2319 else
2320 error.MalformedResponse,
2321 };
2322 }
2323
2324 fn cnameFromDnsRecord(response: []const u8, record: DnsRecord) ResolveError!DnsName {
2325 const decoded = try decodeDnsName(response, record.data_start);
2326 if (decoded.end != record.data_end) return error.MalformedResponse;
2327 return decoded.name;
2328 }
2329
2330 fn scanDnsAnswers(
2331 response: []const u8,
2332 view: DnsResponseView,
2333 target: *const DnsName,
2334 record_type: DnsRecordType,
2335 port: u16,
2336 validate_sections: bool,
2337 ) ResolveError!DnsAnswerScan {
2338 const record_count = @as(usize, view.answer_count) +
2339 @as(usize, view.authority_count) +
2340 @as(usize, view.additional_count);
2341 if (validate_sections and
2342 record_count > (response.len - view.question.end) / dns_record_min_len)
2343 {
2344 return error.MalformedResponse;
2345 }
2346 var scan = DnsAnswerScan{};
2347 var index = view.question.end;
2348 for (0..view.answer_count) |_| {
2349 const record = try parseDnsRecord(response, &index);
2350 if (record.class != dns_class_in or !record.owner.eql(target)) continue;
2351 if (record.record_type == @backingInt(record_type)) {
2352 if (scan.cname != null) return error.MalformedResponse;
2353 const address = try addressFromDnsRecord(response, record, record_type, port);
2354 if (scan.address == null) scan.address = address;
2355 } else if (record.record_type == dns_cname_type) {
2356 if (scan.address != null) return error.MalformedResponse;
2357 const cname = try cnameFromDnsRecord(response, record);
2358 if (scan.cname) |existing| {
2359 if (!existing.eql(&cname)) return error.MalformedResponse;
2360 } else {
2361 scan.cname = cname;
2362 }
2363 }
2364 }
2365 if (validate_sections) {
2366 const remaining_count = @as(usize, view.authority_count) +
2367 @as(usize, view.additional_count);
2368 for (0..remaining_count) |_| _ = try parseDnsRecord(response, &index);
2369 if (index != response.len) return error.MalformedResponse;
2370 }
2371 return scan;
2372 }
2373
2374 fn dnsAddressFromResponse(
2375 response: []const u8,
2376 expected: DnsExpectedQuestion,
2377 port: u16,
2378 ) ResolveError!IpAddress {
2379 const view = try dnsResponseView(response, expected);
2380 if ((view.flags & 0x0200) != 0) return error.MalformedResponse;
2381 if ((view.flags & 0x000f) != 0) {
2382 try validateDnsRecordSections(response, view);
2383 return error.NoAddress;
2384 }
2385 if (view.question.class != dns_class_in) return error.MalformedResponse;
2386 const record_type = std.enums.fromInt(DnsRecordType, view.question.record_type) orelse
2387 return error.MalformedResponse;
2388 var target = view.question.name;
2389 for (0..dns_cname_hop_limit + 1) |hop| {
2390 const scan = try scanDnsAnswers(response, view, &target, record_type, port, hop == 0);
2391 if (scan.address) |address| return address;
2392 const cname = scan.cname orelse return error.NoAddress;
2393 if (cname.eql(&target) or hop == dns_cname_hop_limit) {
2394 return error.MalformedResponse;
2395 }
2396 target = cname;
2397 }
2398 unreachable;
2399 }
2400
2401 fn dnsResponseTruncated(response: []const u8, expected: DnsExpectedQuestion) ResolveError!bool {
2402 const view = try dnsResponseView(response, expected);
2403 return (view.flags & 0x0200) != 0;
2404 }
2405
2406 fn skipDnsName(packet: []const u8, index: *usize) ResolveError!void {
2407 index.* = (try decodeDnsName(packet, index.*)).end;
2408 }
2409
2410 fn setSocketOption(socket_fd: Socket, level: i32, option: u32, value: []const u8) SetOptionError!void {
2411 return switch (socketPolicy()) {
2412 .unsupported => error.UnsupportedPlatform,
2413 .linux_syscall => setSocketOptionLinux(socket_fd, level, option, value),
2414 .posix_host => setSocketOptionPosix(socket_fd, level, option, value),
2415 };
2416 }
2417
2418 fn setSocketOptionLinux(socket_fd: Socket, level: i32, option: u32, value: []const u8) SetOptionError!void {
2419 if (comptime native_os != .linux) return error.UnsupportedPlatform;
2420
2421 switch (linux.errno(linux.setsockopt(socket_fd, level, option, value.ptr, @intCast(value.len)))) {
2422 .SUCCESS => {},
2423 .INVAL => return error.InvalidArgument,
2424 else => return error.SetOptionFailed,
2425 }
2426 }
2427
2428 fn setSocketOptionPosix(socket_fd: Socket, level: i32, option: u32, value: []const u8) SetOptionError!void {
2429 posix.setsockopt(socket_fd, level, option, value) catch return error.SetOptionFailed;
2430 }
2431
2432 fn shutdownReadWriteLinux(socket_fd: Socket) void {
2433 if (comptime native_os != .linux) return;
2434 _ = linux.shutdown(socket_fd, posix.SHUT.RDWR);
2435 }
2436
2437 fn shutdownReadWritePosix(socket_fd: Socket) void {
2438 if (@hasDecl(system, "shutdown")) {
2439 _ = system.shutdown(socket_fd, posix.SHUT.RDWR);
2440 }
2441 }
2442
2443 fn timeoutFromMillis(timeout_ms: u32) posix.timeval {
2444 if (timeout_ms == 0) return .{ .sec = 0, .usec = 0 };
2445 return .{
2446 .sec = @intCast(timeout_ms / 1000),
2447 .usec = @intCast((timeout_ms % 1000) * 1000),
2448 };
2449 }
2450
2451 fn noSignalFlag() u32 {
2452 if (@hasField(posix.MSG, "NOSIGNAL")) return posix.MSG.NOSIGNAL;
2453 return 0;
2454 }
2455
2456 fn receiveTruncationFlag() u32 {
2457 if (comptime native_os == .linux) return linux.MSG.TRUNC;
2458 return 0;
2459 }
2460
2461 fn socketAddressToIpAddress(address: *const posix.sockaddr.storage, len: SocketAddressLength) ?IpAddress {
2462 const any: *const SocketAddress = @ptrCast(address);
2463 return switch (any.family) {
2464 posix.AF.INET => {
2465 if (len < @sizeOf(Ip4Address)) return null;
2466 const ip4: *const Ip4Address = @ptrCast(@alignCast(address));
2467 return .{ .ip4 = ip4.* };
2468 },
2469 posix.AF.INET6 => {
2470 if (len < @sizeOf(Ip6Address)) return null;
2471 const ip6: *const Ip6Address = @ptrCast(@alignCast(address));
2472 return .{ .ip6 = ip6.* };
2473 },
2474 else => null,
2475 };
2476 }
2477
2478 fn expectIp4Address(address: Ip4Address, bytes: [4]u8, port: u16) !void {
2479 try std.testing.expectEqual(std.mem.nativeToBig(u16, port), address.port);
2480 try std.testing.expectEqual(@as(u32, @bitCast(bytes)), address.addr);
2481 }
2482
2483 fn expectIp6Address(address: Ip6Address, bytes: [16]u8, port: u16) !void {
2484 try std.testing.expectEqual(std.mem.nativeToBig(u16, port), address.port);
2485 try std.testing.expectEqual(bytes, address.addr);
2486 }
2487
2488 fn expectIpAddress4(address: IpAddress, bytes: [4]u8, port: u16) !void {
2489 return switch (address) {
2490 .ip4 => |ip4| expectIp4Address(ip4, bytes, port),
2491 .ip6 => error.InvalidAddress,
2492 };
2493 }
2494
2495 fn expectIpAddress6(address: IpAddress, bytes: [16]u8, port: u16) !void {
2496 return switch (address) {
2497 .ip4 => error.InvalidAddress,
2498 .ip6 => |ip6| expectIp6Address(ip6, bytes, port),
2499 };
2500 }
2501
2502 test "abstract unix address leads with a NUL and excludes a trailing one" {
2503 const name = "/tmp/.X11-unix/X1";
2504 const address = try Address.initUnixAbstract(name);
2505 try std.testing.expectEqual(@as(u32, posix.AF.UNIX), address.family());
2506 try std.testing.expectEqual(@as(u8, 0), address.storage.unix.path[0]);
2507 try std.testing.expectEqualSlices(u8, name, address.storage.unix.path[1 .. 1 + name.len]);
2508 try std.testing.expectEqual(
2509 @as(usize, @offsetOf(posix.sockaddr.un, "path") + 1 + name.len),
2510 @as(usize, address.socketAddressLen()),
2511 );
2512 }
2513
2514 test "pathname unix address copies the path verbatim with no leading NUL" {
2515 const path = "/tmp/.X11-unix/X1";
2516 const address = try Address.initUnix(path);
2517 try std.testing.expectEqualSlices(u8, path, address.storage.unix.path[0..path.len]);
2518 try std.testing.expect(address.storage.unix.path[0] != 0);
2519 }
2520
2521 test "IPv4 host parsing recognizes localhost and any address" {
2522 const loopback = try ip4AddressForHost("localhost", 1234);
2523 try expectIp4Address(loopback, .{ 127, 0, 0, 1 }, 1234);
2524
2525 const any = try ip4AddressForHost("", 8080);
2526 try expectIp4Address(any, .{ 0, 0, 0, 0 }, 8080);
2527 }
2528
2529 test "IPv6 host parsing recognizes localhost and any address" {
2530 const loopback = try ip6AddressForHost("localhost", 1234);
2531 try expectIp6Address(loopback, .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, 1234);
2532
2533 const any = try ip6AddressForHost("", 8080);
2534 try expectIp6Address(any, .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 8080);
2535
2536 const documentation = try ip6AddressForHost("2001:db8::42", 443);
2537 try expectIp6Address(documentation, .{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x42 }, 443);
2538 }
2539
2540 test "resolv.conf parser returns resolver config" {
2541 const config = configFromResolvConf(
2542 \\# generated
2543 \\domain stale.test
2544 \\search example.test
2545 \\options attempts:2 ndots:2
2546 \\nameserver 127.0.0.53
2547 \\nameserver 10.0.0.1
2548 \\nameserver 2001:db8::53
2549 \\nameserver 10.0.0.3
2550 );
2551 try std.testing.expectEqual(@as(usize, 3), config.nameservers.len);
2552 try expectIpAddress4(config.nameservers.buffer[0], .{ 127, 0, 0, 53 }, 53);
2553 try expectIpAddress4(config.nameservers.buffer[1], .{ 10, 0, 0, 1 }, 53);
2554 try expectIpAddress6(config.nameservers.buffer[2], .{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x53 }, 53);
2555 try std.testing.expectEqual(@as(u8, 2), config.ndots);
2556 try std.testing.expectEqual(@as(usize, 1), config.search_domains.count());
2557 try std.testing.expectEqualStrings("example.test", config.search_domains.get(0));
2558 }
2559
2560 test "resolv.conf parser lets later domain replace search list" {
2561 const config = configFromResolvConf(
2562 \\search first.test second.test
2563 \\domain final.test.
2564 \\options ndots:99
2565 );
2566 try std.testing.expectEqual(@as(u8, 15), config.ndots);
2567 try std.testing.expectEqual(@as(usize, 1), config.search_domains.count());
2568 try std.testing.expectEqualStrings("final.test", config.search_domains.get(0));
2569 }
2570
2571 test "hosts parser returns matching IPv4 alias" {
2572 const contents =
2573 \\# generated
2574 \\::1 ip6-localhost
2575 \\192.0.2.44 alpha.example alpha
2576 \\198.51.100.8 beta.example BETA.
2577 ;
2578 const address = hostsAddressFromContents(contents, "beta", 9000, .a) orelse return error.NoAddress;
2579 try expectIpAddress4(address, .{ 198, 51, 100, 8 }, 9000);
2580 }
2581
2582 test "hosts parser returns matching IPv6 alias" {
2583 const contents =
2584 \\# generated
2585 \\127.0.0.1 localhost
2586 \\2001:db8::88 gamma.example GAMMA.
2587 ;
2588 const address = hostsAddressFromContents(contents, "gamma", 9443, .aaaa) orelse return error.NoAddress;
2589 try expectIpAddress6(address, .{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x88 }, 9443);
2590 }
2591
2592 test "resolver uses hosts file before DNS" {
2593 var tmp = std.testing.tmpDir(.{});
2594 defer tmp.cleanup();
2595 try tmp.dir.writeFile(std.Options.debug_io, .{
2596 .sub_path = "hosts",
2597 .data =
2598 \\203.0.113.9 local-only.test
2599 ,
2600 });
2601
2602 const hosts_path = try tmp.dir.realPathFileAlloc(std.Options.debug_io, "hosts", std.testing.allocator);
2603 defer std.testing.allocator.free(hosts_path);
2604 const address = try resolveIp4AddressForHost("local-only.test", 443, .{
2605 .hosts_path = hosts_path,
2606 .nameservers = &.{.{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, 9) }},
2607 .timeout_ms = 1,
2608 });
2609 try expectIp4Address(address, .{ 203, 0, 113, 9 }, 443);
2610 }
2611
2612 test "generic resolver uses IPv6 hosts file entry before DNS" {
2613 var tmp = std.testing.tmpDir(.{});
2614 defer tmp.cleanup();
2615 try tmp.dir.writeFile(std.Options.debug_io, .{
2616 .sub_path = "hosts",
2617 .data =
2618 \\2001:db8::99 ip6-local-only.test
2619 ,
2620 });
2621
2622 const hosts_path = try tmp.dir.realPathFileAlloc(std.Options.debug_io, "hosts", std.testing.allocator);
2623 defer std.testing.allocator.free(hosts_path);
2624 const address = try resolveIpAddressForHost("ip6-local-only.test", 443, .{
2625 .hosts_path = hosts_path,
2626 .nameservers = &.{.{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, 9) }},
2627 .timeout_ms = 1,
2628 });
2629 try expectIpAddress6(address, .{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x99 }, 443);
2630 }
2631
2632 test "DNS response parser returns IPv4 answer" {
2633 var query: [512]u8 = undefined;
2634 const encoded = try dnsQuery(&query, "example.test", .a, 0x1234);
2635
2636 var response: [512]u8 = undefined;
2637 const response_len = try dnsTestResponse(
2638 &response,
2639 query[0..encoded.len],
2640 .{ .ip4 = ip4Address(.{ 127, 0, 0, 42 }, 0) },
2641 false,
2642 );
2643 const address = try dnsAddressFromResponse(response[0..response_len], encoded.question, 8080);
2644
2645 try expectIpAddress4(address, .{ 127, 0, 0, 42 }, 8080);
2646 }
2647
2648 test "DNS response parser returns IPv6 answer" {
2649 const answer_bytes = [16]u8{
2650 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0,
2651 0, 0, 0, 0, 0, 0, 0, 0x42,
2652 };
2653 var query: [512]u8 = undefined;
2654 const encoded = try dnsQuery(&query, "example.test", .aaaa, 0x5678);
2655
2656 var response: [512]u8 = undefined;
2657 const response_len = try dnsTestResponse(
2658 &response,
2659 query[0..encoded.len],
2660 .{ .ip6 = ip6Address(answer_bytes, 0) },
2661 false,
2662 );
2663 const address = try dnsAddressFromResponse(response[0..response_len], encoded.question, 8080);
2664
2665 try expectIpAddress6(address, answer_bytes, 8080);
2666 }
2667
2668 test "DNS response parser rejects a mismatched question" {
2669 var query: [512]u8 = undefined;
2670 const encoded = try dnsQuery(&query, "example.test", .a, 0x9abc);
2671 var response: [512]u8 = undefined;
2672 const response_len = try dnsTestResponse(
2673 &response,
2674 query[0..encoded.len],
2675 .{ .ip4 = ip4Address(.{ 127, 0, 0, 42 }, 0) },
2676 false,
2677 );
2678 var question_end: usize = 12;
2679 try skipDnsName(response[0..response_len], &question_end);
2680
2681 response[13] = 'f';
2682 try std.testing.expectError(
2683 error.MalformedResponse,
2684 dnsAddressFromResponse(response[0..response_len], encoded.question, 8080),
2685 );
2686 }
2687
2688 test "DNS resolver uses configured UDP nameserver" {
2689 const server_socket = udpDatagramSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
2690 error.UnsupportedPlatform => return error.SkipZigTest,
2691 else => return err,
2692 };
2693 defer close(server_socket);
2694 try bindIp4(server_socket, ip4Address(.{ 127, 0, 0, 1 }, 0));
2695 try setReadTimeout(server_socket, 1000);
2696 const server_port = try socketPort(server_socket);
2697
2698 var server = TestDnsServer{ .socket = server_socket };
2699 const thread = try std.Thread.spawn(.{}, TestDnsServer.run, .{&server});
2700 const resolved = resolveIp4AddressForHost("example.test", 8080, .{
2701 .hosts_path = null,
2702 .nameservers = &.{.{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, server_port) }},
2703 .timeout_ms = 1000,
2704 });
2705 thread.join();
2706 if (server.err) |err| return err;
2707
2708 const address = try resolved;
2709 try expectIp4Address(address, .{ 127, 0, 0, 42 }, 8080);
2710 }
2711
2712 test "DNS resolver uses configured UDP nameserver for IPv6" {
2713 const server_socket = udpDatagramSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
2714 error.UnsupportedPlatform => return error.SkipZigTest,
2715 else => return err,
2716 };
2717 defer close(server_socket);
2718 try bindIp4(server_socket, ip4Address(.{ 127, 0, 0, 1 }, 0));
2719 try setReadTimeout(server_socket, 1000);
2720 const server_port = try socketPort(server_socket);
2721
2722 var server = TestDnsServer{ .socket = server_socket, .answer = .{ .ip6 = ip6Address(.{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x77 }, 0) } };
2723 const thread = try std.Thread.spawn(.{}, TestDnsServer.run, .{&server});
2724 const resolved = resolveIp6AddressForHost("example.test", 9443, .{
2725 .hosts_path = null,
2726 .nameservers = &.{.{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, server_port) }},
2727 .timeout_ms = 1000,
2728 });
2729 thread.join();
2730 if (server.err) |err| return err;
2731
2732 const address = try resolved;
2733 try expectIp6Address(address, .{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x77 }, 9443);
2734 }
2735
2736 test "generic DNS resolver falls back to IPv6 answer" {
2737 const server_socket = udpDatagramSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
2738 error.UnsupportedPlatform => return error.SkipZigTest,
2739 else => return err,
2740 };
2741 defer close(server_socket);
2742 try bindIp4(server_socket, ip4Address(.{ 127, 0, 0, 1 }, 0));
2743 try setReadTimeout(server_socket, 1000);
2744 const server_port = try socketPort(server_socket);
2745
2746 var server = TestDnsSequenceServer{
2747 .socket = server_socket,
2748 .answers = .{
2749 null,
2750 .{ .ip6 = ip6Address(.{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x66 }, 0) },
2751 },
2752 };
2753 const thread = try std.Thread.spawn(.{}, TestDnsSequenceServer.run, .{&server});
2754 const resolved = resolveIpAddressForHost("example.test", 9443, .{
2755 .hosts_path = null,
2756 .nameservers = &.{.{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, server_port) }},
2757 .timeout_ms = 1000,
2758 });
2759 thread.join();
2760 if (server.err) |err| return err;
2761
2762 const address = try resolved;
2763 try expectIpAddress6(address, .{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x66 }, 9443);
2764 }
2765
2766 test "DNS resolver tries configured nameservers in order" {
2767 const first_socket = udpDatagramSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
2768 error.UnsupportedPlatform => return error.SkipZigTest,
2769 else => return err,
2770 };
2771 defer close(first_socket);
2772 try bindIp4(first_socket, ip4Address(.{ 127, 0, 0, 1 }, 0));
2773 try setReadTimeout(first_socket, 1000);
2774 const first_port = try socketPort(first_socket);
2775
2776 const second_socket = udpDatagramSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
2777 error.UnsupportedPlatform => return error.SkipZigTest,
2778 else => return err,
2779 };
2780 defer close(second_socket);
2781 try bindIp4(second_socket, ip4Address(.{ 127, 0, 0, 1 }, 0));
2782 try setReadTimeout(second_socket, 1000);
2783 const second_port = try socketPort(second_socket);
2784
2785 var first = TestDnsServer{ .socket = first_socket, .answer = null };
2786 var second = TestDnsServer{ .socket = second_socket, .answer = .{ .ip4 = ip4Address(.{ 127, 0, 0, 99 }, 0) } };
2787 const first_thread = try std.Thread.spawn(.{}, TestDnsServer.run, .{&first});
2788 const second_thread = try std.Thread.spawn(.{}, TestDnsServer.run, .{&second});
2789 const nameservers = [_]IpAddress{
2790 .{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, first_port) },
2791 .{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, second_port) },
2792 };
2793 const resolved = resolveIp4AddressForHost("example.test", 8080, .{
2794 .hosts_path = null,
2795 .nameservers = &nameservers,
2796 .timeout_ms = 1000,
2797 });
2798 first_thread.join();
2799 second_thread.join();
2800 if (first.err) |err| return err;
2801 if (second.err) |err| return err;
2802
2803 const address = try resolved;
2804 try expectIp4Address(address, .{ 127, 0, 0, 99 }, 8080);
2805 }
2806
2807 test "DNS resolver applies search domains before bare single label" {
2808 const server_socket = udpDatagramSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
2809 error.UnsupportedPlatform => return error.SkipZigTest,
2810 else => return err,
2811 };
2812 defer close(server_socket);
2813 try bindIp4(server_socket, ip4Address(.{ 127, 0, 0, 1 }, 0));
2814 try setReadTimeout(server_socket, 1000);
2815 const server_port = try socketPort(server_socket);
2816
2817 var config = ResolverConfig{};
2818 config.nameservers.append(.{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, server_port) });
2819 config.search_domains.append("example.test");
2820 config.ndots = 1;
2821
2822 var server = TestDnsServer{
2823 .socket = server_socket,
2824 .expected = "service.example.test",
2825 .answer = .{ .ip4 = ip4Address(.{ 127, 0, 0, 77 }, 0) },
2826 };
2827 const thread = try std.Thread.spawn(.{}, TestDnsServer.run, .{&server});
2828 const resolved = resolveAddressWithConfig("service", 8080, &config, 1000, .a);
2829 thread.join();
2830 if (server.err) |err| return err;
2831
2832 const address = try resolved;
2833 try expectIpAddress4(address, .{ 127, 0, 0, 77 }, 8080);
2834 }
2835
2836 test "DNS resolver uses TCP fallback for truncated UDP response" {
2837 const tcp_socket = tcpStreamSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
2838 error.UnsupportedPlatform => return error.SkipZigTest,
2839 else => return err,
2840 };
2841 defer close(tcp_socket);
2842 try bindIp4(tcp_socket, ip4Address(.{ 127, 0, 0, 1 }, 0));
2843 try listen(tcp_socket, 1);
2844 const server_port = try socketPort(tcp_socket);
2845
2846 const udp_socket = udpDatagramSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
2847 error.UnsupportedPlatform => return error.SkipZigTest,
2848 else => return err,
2849 };
2850 defer close(udp_socket);
2851 try bindIp4(udp_socket, ip4Address(.{ 127, 0, 0, 1 }, server_port));
2852 try setReadTimeout(udp_socket, 1000);
2853
2854 var udp_server = TestDnsServer{ .socket = udp_socket, .answer = null, .truncated = true };
2855 var tcp_server = TestTcpDnsServer{ .socket = tcp_socket, .answer = .{ .ip4 = ip4Address(.{ 127, 0, 0, 88 }, 0) } };
2856 const udp_thread = try std.Thread.spawn(.{}, TestDnsServer.run, .{&udp_server});
2857 const tcp_thread = try std.Thread.spawn(.{}, TestTcpDnsServer.run, .{&tcp_server});
2858 const resolved = resolveIp4AddressForHost("example.test", 8080, .{
2859 .hosts_path = null,
2860 .nameservers = &.{.{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, server_port) }},
2861 .timeout_ms = 1000,
2862 });
2863 udp_thread.join();
2864 tcp_thread.join();
2865 if (udp_server.err) |err| return err;
2866 if (tcp_server.err) |err| return err;
2867
2868 const address = try resolved;
2869 try expectIp4Address(address, .{ 127, 0, 0, 88 }, 8080);
2870 }
2871
2872 const TestDnsServer = struct {
2873 socket: Socket,
2874 expected: ?[]const u8 = null,
2875 answer: ?IpAddress = .{ .ip4 = ip4Address(.{ 127, 0, 0, 42 }, 0) },
2876 truncated: bool = false,
2877 err: ?anyerror = null,
2878
2879 fn run(self: *TestDnsServer) void {
2880 self.serve() catch |err| {
2881 self.err = err;
2882 };
2883 }
2884
2885 fn serve(self: *TestDnsServer) !void {
2886 var query: [512]u8 = undefined;
2887 var peer: SocketAddress = undefined;
2888 var peer_len: SocketAddressLength = @sizeOf(SocketAddress);
2889 const query_len = try recvFrom(self.socket, &query, 0, &peer, &peer_len);
2890
2891 var response: [512]u8 = undefined;
2892 const answer = if (self.expected) |expected|
2893 if (dnsQuestionNameEquals(query[0..query_len], expected)) self.answer else null
2894 else
2895 self.answer;
2896 const response_len = try dnsTestResponse(&response, query[0..query_len], answer, self.truncated);
2897 _ = try sendTo(self.socket, response[0..response_len], 0, &peer, peer_len);
2898 }
2899 };
2900
2901 const TestTcpDnsServer = struct {
2902 socket: Socket,
2903 answer: ?IpAddress = .{ .ip4 = ip4Address(.{ 127, 0, 0, 42 }, 0) },
2904 err: ?anyerror = null,
2905
2906 fn run(self: *TestTcpDnsServer) void {
2907 self.serve() catch |err| {
2908 self.err = err;
2909 };
2910 }
2911
2912 fn serve(self: *TestTcpDnsServer) !void {
2913 if (!(try pollReadable(self.socket, 1000))) return error.ConnectionTimedOut;
2914 const client = try accept(self.socket, null, null, posix.SOCK.CLOEXEC);
2915 defer close(client);
2916 try setReadTimeout(client, 1000);
2917 try setWriteTimeout(client, 1000);
2918
2919 var query_len_bytes: [2]u8 = undefined;
2920 try recvExactSocket(client, &query_len_bytes);
2921 const query_len = std.mem.readInt(u16, &query_len_bytes, .big);
2922 if (query_len == 0) return error.MalformedResponse;
2923 var query: [512]u8 = undefined;
2924 if (query_len > query.len) return error.QueryTooLarge;
2925 try recvExactSocket(client, query[0..query_len]);
2926
2927 var response: [512]u8 = undefined;
2928 const response_len = try dnsTestResponse(&response, query[0..query_len], self.answer, false);
2929 var frame: [514]u8 = undefined;
2930 std.mem.writeInt(u16, frame[0..2], @intCast(response_len), .big);
2931 @memcpy(frame[2..][0..response_len], response[0..response_len]);
2932 try sendAllSocket(client, frame[0 .. response_len + 2]);
2933 }
2934 };
2935
2936 const TestDnsSequenceServer = struct {
2937 socket: Socket,
2938 answers: [2]?IpAddress,
2939 err: ?anyerror = null,
2940
2941 fn run(self: *TestDnsSequenceServer) void {
2942 self.serve() catch |err| {
2943 self.err = err;
2944 };
2945 }
2946
2947 fn serve(self: *TestDnsSequenceServer) !void {
2948 var request_index: usize = 0;
2949 while (request_index < self.answers.len) : (request_index += 1) {
2950 var query: [512]u8 = undefined;
2951 var peer: SocketAddress = undefined;
2952 var peer_len: SocketAddressLength = @sizeOf(SocketAddress);
2953 const query_len = try recvFrom(self.socket, &query, 0, &peer, &peer_len);
2954
2955 var response: [512]u8 = undefined;
2956 const response_len = try dnsTestResponse(&response, query[0..query_len], self.answers[request_index], false);
2957 _ = try sendTo(self.socket, response[0..response_len], 0, &peer, peer_len);
2958 }
2959 }
2960 };
2961
2962 fn dnsQuestionNameEquals(query: []const u8, expected: []const u8) bool {
2963 var expected_query: [512]u8 = undefined;
2964 const encoded = dnsQuery(&expected_query, expected, .a, 0) catch return false;
2965 var query_name_end: usize = 12;
2966 skipDnsName(query, &query_name_end) catch return false;
2967 if (query_name_end > query.len) return false;
2968 return std.mem.eql(u8, query[12..query_name_end], encoded.question.name);
2969 }
2970
2971 fn dnsTestResponse(buffer: []u8, query: []const u8, address: ?IpAddress, truncated: bool) !usize {
2972 if (query.len < 17 or buffer.len < query.len + 28) return error.QueryTooLarge;
2973 var question_end: usize = 12;
2974 try skipDnsName(query, &question_end);
2975 question_end += 4;
2976 if (question_end > query.len) return error.MalformedResponse;
2977
2978 std.mem.writeInt(u16, buffer[0..2], std.mem.readInt(u16, query[0..2], .big), .big);
2979 std.mem.writeInt(u16, buffer[2..4], if (truncated) 0x8380 else 0x8180, .big);
2980 std.mem.writeInt(u16, buffer[4..6], 1, .big);
2981 std.mem.writeInt(u16, buffer[6..8], if (address == null) 0 else 1, .big);
2982 std.mem.writeInt(u16, buffer[8..10], 0, .big);
2983 std.mem.writeInt(u16, buffer[10..12], 0, .big);
2984
2985 var index: usize = 12;
2986 @memcpy(buffer[index..][0 .. question_end - 12], query[12..question_end]);
2987 index += question_end - 12;
2988 const answer = address orelse return index;
2989 buffer[index] = 0xc0;
2990 buffer[index + 1] = 0x0c;
2991 index += 2;
2992 switch (answer) {
2993 .ip4 => |ip4| {
2994 std.mem.writeInt(u16, buffer[index..][0..2], @backingInt(DnsRecordType.a), .big);
2995 index += 2;
2996 std.mem.writeInt(u16, buffer[index..][0..2], 1, .big);
2997 index += 2;
2998 std.mem.writeInt(u32, buffer[index..][0..4], 0, .big);
2999 index += 4;
3000 std.mem.writeInt(u16, buffer[index..][0..2], 4, .big);
3001 index += 2;
3002 const bytes: [4]u8 = @bitCast(ip4.addr);
3003 @memcpy(buffer[index..][0..4], &bytes);
3004 index += 4;
3005 },
3006 .ip6 => |ip6| {
3007 std.mem.writeInt(u16, buffer[index..][0..2], @backingInt(DnsRecordType.aaaa), .big);
3008 index += 2;
3009 std.mem.writeInt(u16, buffer[index..][0..2], 1, .big);
3010 index += 2;
3011 std.mem.writeInt(u32, buffer[index..][0..4], 0, .big);
3012 index += 4;
3013 std.mem.writeInt(u16, buffer[index..][0..2], 16, .big);
3014 index += 2;
3015 @memcpy(buffer[index..][0..16], &ip6.addr);
3016 index += 16;
3017 },
3018 }
3019 return index;
3020 }
3021
3022 test "stream socket can be created for IPv4" {
3023 const socket_fd = tcpStreamSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
3024 error.UnsupportedPlatform => return error.SkipZigTest,
3025 else => return err,
3026 };
3027 defer close(socket_fd);
3028 }
3029
3030 test "socket policy is explicit" {
3031 const expected: SocketPolicy = switch (native_os) {
3032 .windows, .wasi, .freestanding => .unsupported,
3033 .linux => .linux_syscall,
3034 else => .posix_host,
3035 };
3036 try std.testing.expectEqual(expected, socketPolicy());
3037 }
3038
3039 test "peer credentials policy is explicit" {
3040 const expected: PeerCredentialsPolicy = switch (native_os) {
3041 .linux => .linux_so_peercred,
3042 else => .unsupported,
3043 };
3044 try std.testing.expectEqual(
3045 expected,
3046 peerCredentialsPolicy(),
3047 );
3048 }
3049
3050 test "Unix stream socket pair exposes kernel peer identity" {
3051 if (comptime native_os != .linux) {
3052 return error.SkipZigTest;
3053 }
3054 const sockets = try socketPairUnixStream();
3055 defer close(sockets[0]);
3056 defer close(sockets[1]);
3057
3058 const left = try peerCredentials(sockets[0]);
3059 const right = try peerCredentials(sockets[1]);
3060 const process_id = linux.getpid();
3061 const user_id = linux.geteuid();
3062 const group_id = linux.getegid();
3063 try std.testing.expectEqual(process_id, left.process_id);
3064 try std.testing.expectEqual(user_id, left.user_id);
3065 try std.testing.expectEqual(group_id, left.group_id);
3066 try std.testing.expectEqual(process_id, right.process_id);
3067 try std.testing.expectEqual(user_id, right.user_id);
3068 try std.testing.expectEqual(group_id, right.group_id);
3069 }
3070
3071 test "TCP stream socket can disable Nagle" {
3072 const socket_fd = tcpStreamSocket(.{ .close_on_exec = true }) catch |err| switch (err) {
3073 error.UnsupportedPlatform => return error.SkipZigTest,
3074 else => return err,
3075 };
3076 defer close(socket_fd);
3077 setTcpNoDelay(socket_fd) catch |err| switch (err) {
3078 error.UnsupportedPlatform => return error.SkipZigTest,
3079 else => return err,
3080 };
3081 }
3082
3083 test "Unix stream socket pair send recv round trip" {
3084 const sockets = socketPairUnixStream() catch |err| switch (err) {
3085 error.UnsupportedPlatform => return error.SkipZigTest,
3086 else => return err,
3087 };
3088 defer close(sockets[0]);
3089 defer close(sockets[1]);
3090
3091 try std.testing.expectEqual(@as(usize, 4), try sendNoSignal(sockets[0], "pong"));
3092
3093 var buffer: [8]u8 = undefined;
3094 const count = try recv(sockets[1], &buffer, 0);
3095 try std.testing.expectEqualStrings("pong", buffer[0..count]);
3096 }
3097
3098 test "Unix stream socket pair poll detects readable data" {
3099 const sockets = socketPairUnixStream() catch |err| switch (err) {
3100 error.UnsupportedPlatform => return error.SkipZigTest,
3101 else => return err,
3102 };
3103 defer close(sockets[0]);
3104 defer close(sockets[1]);
3105
3106 try std.testing.expect(!try pollReadable(sockets[1], 0));
3107 try std.testing.expectEqual(@as(usize, 4), try sendNoSignal(sockets[0], "pong"));
3108 try std.testing.expect(try pollReadable(sockets[1], 0));
3109 }
3110
3111 test "readiness set reports each ready socket without blocking on silent peers" {
3112 const testing = std.testing;
3113 const quiet = socketPairUnixStream() catch |err| switch (err) {
3114 error.UnsupportedPlatform => return error.SkipZigTest,
3115 else => return err,
3116 };
3117 defer close(quiet[0]);
3118 defer close(quiet[1]);
3119 const busy = try socketPairUnixStream();
3120 defer close(busy[0]);
3121 defer close(busy[1]);
3122
3123 const watched = [_]Socket{ quiet[1], busy[1] };
3124 var ready: [watched.len]bool = undefined;
3125 try testing.expectEqual(
3126 @as(u8, 0),
3127 try pollReadableSet(&watched, &ready, 0),
3128 );
3129 try testing.expectEqual([_]bool{ false, false }, ready);
3130
3131 try testing.expectEqual(@as(usize, 4), try sendNoSignal(busy[0], "ping"));
3132 try testing.expectEqual(
3133 @as(u8, 1),
3134 try pollReadableSet(&watched, &ready, 0),
3135 );
3136 try testing.expectEqual([_]bool{ false, true }, ready);
3137
3138 try testing.expectEqual(@as(usize, 4), try sendNoSignal(quiet[0], "ping"));
3139 try testing.expectEqual(
3140 @as(u8, 2),
3141 try pollReadableSet(&watched, &ready, 0),
3142 );
3143 try testing.expectEqual([_]bool{ true, true }, ready);
3144 }
3145
3146 test "readiness set reports a hung up peer and admits an empty watch list" {
3147 const testing = std.testing;
3148 const sockets = socketPairUnixStream() catch |err| switch (err) {
3149 error.UnsupportedPlatform => return error.SkipZigTest,
3150 else => return err,
3151 };
3152 defer close(sockets[1]);
3153 close(sockets[0]);
3154
3155 const watched = [_]Socket{sockets[1]};
3156 var ready: [watched.len]bool = undefined;
3157 try testing.expectEqual(
3158 @as(u8, 1),
3159 try pollReadableSet(&watched, &ready, 0),
3160 );
3161 try testing.expect(ready[0]);
3162
3163 var none: [0]bool = undefined;
3164 try testing.expectEqual(
3165 @as(u8, 0),
3166 try pollReadableSet(&.{}, &none, 0),
3167 );
3168 }
3169
3170 test "readiness set joins readable and writable interests" {
3171 const readable = socketPairUnixStream() catch |err| switch (err) {
3172 error.UnsupportedPlatform => return error.SkipZigTest,
3173 else => return err,
3174 };
3175 defer close(readable[0]);
3176 defer close(readable[1]);
3177 const writable = try socketPairUnixStream();
3178 defer close(writable[0]);
3179 defer close(writable[1]);
3180 try setNonBlocking(readable[1]);
3181 try setNonBlocking(writable[0]);
3182 try std.testing.expectEqual(@as(usize, 4), try sendNoSignal(readable[0], "ping"));
3183
3184 const watched = [_]Socket{ readable[1], writable[0] };
3185 const interests = [_]PollInterest{ .read, .write };
3186 var ready: [watched.len]bool = undefined;
3187 try std.testing.expectEqual(
3188 @as(u8, 2),
3189 try pollSet(&watched, &interests, &ready, 0),
3190 );
3191 try std.testing.expectEqual([_]bool{ true, true }, ready);
3192 }
3193
3194 test "Unix stream socket pair poll detects writable backpressure" {
3195 const sockets = socketPairUnixStream() catch |err| switch (err) {
3196 error.UnsupportedPlatform => return error.SkipZigTest,
3197 else => return err,
3198 };
3199 defer close(sockets[0]);
3200 defer close(sockets[1]);
3201
3202 try setNonBlocking(sockets[0]);
3203 try std.testing.expect(try pollWritable(sockets[0], 0));
3204 try fillSocketForBackpressure(sockets[0]);
3205 try std.testing.expect(!try pollWritable(sockets[0], 0));
3206 }
3207
3208 fn fillSocketForBackpressure(socket_fd: Socket) !void {
3209 var chunk: [4096]u8 = undefined;
3210 @memset(&chunk, 0xAA);
3211
3212 while (true) {
3213 _ = sendNoSignal(socket_fd, &chunk) catch |err| switch (err) {
3214 error.WouldBlock => return,
3215 else => return err,
3216 };
3217 }
3218 }
3219
3220 test "controlled sockets: stalled read and full write honor an absolute deadline" {
3221 if (!Control.supported()) return error.SkipZigTest;
3222 const sockets = try socketPairUnixStream();
3223 defer close(sockets[0]);
3224 defer close(sockets[1]);
3225 var control = Control{ .deadline_ns = try Control.clock() + 30 * std.time.ns_per_ms, .bytes_remaining = 16 * 1024 * 1024 };
3226 var byte: [1]u8 = undefined;
3227 try std.testing.expectError(error.DeadlineExceeded, control.receive(sockets[0], &byte));
3228 try std.testing.expect(try Control.clock() < control.deadline_ns + 100 * std.time.ns_per_ms);
3229 try setNonBlocking(sockets[0]);
3230 const fill: [8192]u8 = @splat(1);
3231 var filled: usize = 0;
3232 while (filled < 16 * 1024 * 1024) {
3233 filled += sendNoSignal(sockets[0], &fill) catch |err| {
3234 if (err == error.WouldBlock) break;
3235 return err;
3236 };
3237 }
3238 try std.testing.expect(filled < 16 * 1024 * 1024);
3239 control = .{ .deadline_ns = try Control.clock() + 30 * std.time.ns_per_ms, .bytes_remaining = 65536 };
3240 try std.testing.expectError(error.DeadlineExceeded, control.transmit(sockets[0], "x"));
3241 try std.testing.expect(try Control.clock() < control.deadline_ns + 100 * std.time.ns_per_ms);
3242 }
3243
3244 const ControlledSocketWorker = struct {
3245 socket: Socket,
3246 address: ?IpAddress = null,
3247 control: *Control,
3248 failure: ?anyerror = null,
3249 done: std.atomic.Value(bool) = .init(false),
3250
3251 fn run(self: *ControlledSocketWorker) void {
3252 self.perform() catch |err| {
3253 self.failure = err;
3254 };
3255 self.done.store(true, .release);
3256 }
3257
3258 fn perform(self: *ControlledSocketWorker) !void {
3259 if (self.address) |address| try self.control.connectTo(self.socket, address) else _ = try self.control.transmit(self.socket, "x");
3260 }
3261
3262 fn cancelAndJoin(self: *ControlledSocketWorker) !void {
3263 const thread = try @import("root.zig").thread.spawn(ControlledSocketWorker.run, .{self});
3264 defer thread.join();
3265 defer self.control.cancel();
3266 @import("root.zig").time.sleepMilliseconds(20);
3267 try std.testing.expect(!self.done.load(.acquire));
3268 const canceled_at = try Control.clock();
3269 self.control.cancel();
3270 while (!self.done.load(.acquire) and try Control.clock() < canceled_at + 200 * std.time.ns_per_ms)
3271 @import("root.zig").time.sleepMilliseconds(1);
3272 try std.testing.expect(self.done.load(.acquire));
3273 try std.testing.expectEqual(error.Canceled, self.failure.?);
3274 }
3275 };
3276
3277 test "controlled sockets: cancellation interrupts backpressure without closing a descriptor" {
3278 if (!Control.supported()) return error.SkipZigTest;
3279 const sockets = try socketPairUnixStream();
3280 defer close(sockets[0]);
3281 defer close(sockets[1]);
3282 try setNonBlocking(sockets[0]);
3283 const fill: [8192]u8 = @splat(1);
3284 var filled: usize = 0;
3285 while (filled < 16 * 1024 * 1024) {
3286 filled += sendNoSignal(sockets[0], &fill) catch |err| {
3287 if (err == error.WouldBlock) break;
3288 return err;
3289 };
3290 }
3291 try std.testing.expect(filled < 16 * 1024 * 1024);
3292 var control = Control{ .deadline_ns = try Control.clock() + std.time.ns_per_s, .bytes_remaining = 65536 };
3293 var worker = ControlledSocketWorker{ .socket = sockets[0], .control = &control };
3294 try worker.cancelAndJoin();
3295 var read_back: [1]u8 = undefined;
3296 try std.testing.expectEqual(@as(usize, 1), try recv(sockets[1], &read_back, 0));
3297 }
3298
3299 test "controlled sockets: full local accept queue bounds connect deadline and cancellation" {
3300 if (!Control.supported()) return error.SkipZigTest;
3301 const listener = try tcpStreamSocket(.{ .nonblocking = true, .close_on_exec = true });
3302 defer close(listener);
3303 try bindIp4(listener, ip4Address(.{ 127, 0, 0, 1 }, 0));
3304 try listen(listener, 1);
3305 const address: IpAddress = .{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, try socketPort(listener)) };
3306 const first = try tcpStreamSocket(.{});
3307 defer close(first);
3308 const second = try tcpStreamSocket(.{});
3309 defer close(second);
3310 var control = Control{ .deadline_ns = try Control.clock() + std.time.ns_per_s, .bytes_remaining = 1024 };
3311 try control.connectTo(first, address);
3312 try control.connectTo(second, address);
3313 const pending = try tcpStreamSocket(.{});
3314 defer close(pending);
3315 control = .{ .deadline_ns = try Control.clock() + 30 * std.time.ns_per_ms, .bytes_remaining = 1024 };
3316 try std.testing.expectError(error.DeadlineExceeded, control.connectTo(pending, address));
3317 try std.testing.expect(try Control.clock() < control.deadline_ns + 100 * std.time.ns_per_ms);
3318 const canceled = try tcpStreamSocket(.{});
3319 defer close(canceled);
3320 control = .{ .deadline_ns = try Control.clock() + std.time.ns_per_s, .bytes_remaining = 1024 };
3321 var worker = ControlledSocketWorker{ .socket = canceled, .address = address, .control = &control };
3322 try worker.cancelAndJoin();
3323 }
3324
3325 test "controlled sockets: explicit DNS over TCP and exact wire budget" {
3326 if (!Control.supported()) return error.SkipZigTest;
3327 const listener = try tcpStreamSocket(.{});
3328 defer close(listener);
3329 try bindIp4(listener, ip4Address(.{ 127, 0, 0, 1 }, 0));
3330 try listen(listener, 1);
3331 const nameserver: IpAddress = .{ .ip4 = ip4Address(.{ 127, 0, 0, 1 }, try socketPort(listener)) };
3332 var server = TestTcpDnsServer{ .socket = listener };
3333 const thread = try @import("root.zig").thread.spawn(TestTcpDnsServer.run, .{&server});
3334 var control = Control{ .deadline_ns = try Control.clock() + std.time.ns_per_s, .bytes_remaining = 4096 };
3335 const resolved = control.resolve("example.test", 443, nameserver, .ip4);
3336 thread.join();
3337 if (server.err) |err| return err;
3338 try expectIp4Address((try resolved).ip4, .{ 127, 0, 0, 42 }, 443);
3339 const sockets = try socketPairUnixStream();
3340 defer close(sockets[0]);
3341 defer close(sockets[1]);
3342 control = .{ .deadline_ns = try Control.clock() + std.time.ns_per_s, .bytes_remaining = 1 };
3343 try std.testing.expectEqual(@as(usize, 1), try control.transmit(sockets[0], "xy"));
3344 try std.testing.expectError(error.TransferCapacityExceeded, control.transmit(sockets[0], "y"));
3345 }