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 }