authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-03-27 19:57:32-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-30 09:33:04+02:00
log0db75c0116661357a57647e9ed91f82f44f944b1
treee640a24a90a93e38a2de7f97fd6561d433ebb802
parent42e33db9d00a839ddb3e5c717afd03f004eda884

std.Io.Threaded: fix dns queries on windows

by working around various bugs in `DnsQueryEx`. Closes #31659

2 files changed, 156 insertions(+), 122 deletions(-)

lib/std/Io/Threaded.zig+151-119
......@@ -13482,21 +13482,80 @@ fn netLookupFallible(
1348213482 const name = host_name.bytes;
1348313483 assert(name.len <= HostName.max_len);
1348413484
13485 if (is_windows) {
13486 if (options.family == null) {
13487 if (IpAddress.parseIp4(name, options.port)) |addr| {
13488 if (copyCanon(options.canonical_name_buffer, name)) |canon| {
13489 try resolved.putAll(t_io, &.{
13490 .{ .address = addr },
13491 .{ .canonical_name = canon },
13492 });
13493 } else {
13494 try resolved.putOne(t_io, .{ .address = addr });
13495 }
13496 return;
13497 } else |_| {}
13485 // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics.
13486 // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it.
13487
13488 if (builtin.target.isGnuLibC()) {
13489 // TODO use getaddrinfo_a / gai_cancel
13490 }
13491
13492 if (native_os == .linux or is_windows) {
13493 if (IpAddress.parseIp6(name, options.port)) |addr| {
13494 if (options.family == .ip4) return error.UnknownHostName;
13495 if (copyCanon(options.canonical_name_buffer, name)) |canon| {
13496 try resolved.putAll(t_io, &.{
13497 .{ .address = addr },
13498 .{ .canonical_name = canon },
13499 });
13500 } else {
13501 try resolved.putOne(t_io, .{ .address = addr });
13502 }
13503 return;
13504 } else |_| {}
13505
13506 if (IpAddress.parseIp4(name, options.port)) |addr| {
13507 if (options.family == .ip6) return error.UnknownHostName;
13508 if (copyCanon(options.canonical_name_buffer, name)) |canon| {
13509 try resolved.putAll(t_io, &.{
13510 .{ .address = addr },
13511 .{ .canonical_name = canon },
13512 });
13513 } else {
13514 try resolved.putOne(t_io, .{ .address = addr });
13515 }
13516 return;
13517 } else |_| {}
13518
13519 if (t.lookupHosts(host_name, resolved, options)) return else |err| switch (err) {
13520 error.UnknownHostName => {},
13521 else => |e| return e,
13522 }
13523
13524 // RFC 6761 Section 6.3.3
13525 // Name resolution APIs and libraries SHOULD recognize
13526 // localhost names as special and SHOULD always return the IP
13527 // loopback address for address queries and negative responses
13528 // for all other query types.
13529
13530 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
13531 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
13532 if (std.mem.endsWith(u8, name, localhost) and
13533 (name.len == localhost.len or name[name.len - localhost.len] == '.'))
13534 {
13535 var results_buffer: [3]HostName.LookupResult = undefined;
13536 var results_index: usize = 0;
13537 if (options.family != .ip4) {
13538 results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } };
13539 results_index += 1;
13540 }
13541 if (options.family != .ip6) {
13542 results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } };
13543 results_index += 1;
13544 }
13545 if (options.canonical_name_buffer) |buf| {
13546 const canon_name = "localhost";
13547 const canon_name_dest = buf[0..canon_name.len];
13548 canon_name_dest.* = canon_name.*;
13549 results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } };
13550 results_index += 1;
13551 }
13552 try resolved.putAll(t_io, results_buffer[0..results_index]);
13553 return;
1349813554 }
1349913555
13556 if (native_os == .linux) return t.lookupDnsSearch(host_name, resolved, options);
13557
13558 comptime assert(is_windows);
1350013559 var DnsQueryEx = t.dl.DnsQueryEx.load(.acquire);
1350113560 //var DnsCancelQuery = t.dl.DnsCancelQuery.load(.acquire);
1350213561 var DnsFree = t.dl.DnsFree.load(.acquire);
......@@ -13540,6 +13599,7 @@ fn netLookupFallible(
1354013599 else => |status| return windows.unexpectedStatus(status),
1354113600 }
1354213601 }
13602 try Thread.checkCancel();
1354313603 const current_thread = Thread.current;
1354413604 var lookup_dns: LookupDnsWindows = .{
1354513605 .threaded = t,
......@@ -13562,126 +13622,71 @@ fn netLookupFallible(
1356213622 }
1356313623 ] = 0;
1356413624 //var cancel_token: windows.DNS.QUERY.CANCEL = undefined;
13625 // Workaround various bugs by attempting a synchronous non-wire query first
1356513626 switch (DnsQueryEx.?(&.{
1356613627 .Version = 1,
1356713628 .QueryName = &host_name_w,
1356813629 .QueryType = if (options.family == .ip4) .A else .AAAA,
1356913630 .QueryOptions = .{
13631 .NO_WIRE_QUERY = true,
13632 .NO_HOSTS_FILE = true, // handled above
1357013633 .ADDRCONFIG = true,
1357113634 .DUAL_ADDR = options.family == null,
13572 .MULTICAST_WAIT = true,
1357313635 },
13574 .pQueryCompletionCallback = if (current_thread) |_| &LookupDnsWindows.completed else null,
13575 }, &lookup_dns.results,
13576 //&cancel_token,
13577 null)) {
13636 }, &lookup_dns.results, null)) {
13637 .SUCCESS => try lookup_dns.completedFallible(),
1357813638 // We must wait for the APC routine.
13579 .SUCCESS, .DNS_REQUEST_PENDING => |status| if (current_thread) |_| {
13580 while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) {
13581 // Once we get here we must not return from the function until the
13582 // operation completes, thereby releasing references to `host_name_w`,
13583 // `lookup_dns.results`, and `cancel_token`.
13584 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
13585 error.Canceled => |e| {
13586 //_ = DnsCancelQuery.?(&cancel_token);
13587 while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) waitForApcOrAlert();
13588 return e;
13589 },
13590 };
13591 waitForApcOrAlert();
13592 alertable_syscall.finish();
13593 }
13594 } else switch (status) {
13639 .DNS_REQUEST_PENDING => unreachable, // `pQueryCompletionCallback` was `null`
13640 .DNS_ERROR_RECORD_DOES_NOT_EXIST => switch (DnsQueryEx.?(&.{
13641 .Version = 1,
13642 .QueryName = &host_name_w,
13643 .QueryType = if (options.family == .ip4) .A else .AAAA,
13644 .QueryOptions = .{
13645 .NO_HOSTS_FILE = true, // handled above
13646 .ADDRCONFIG = true,
13647 .DUAL_ADDR = options.family == null,
13648 .MULTICAST_WAIT = true,
13649 },
13650 .pQueryCompletionCallback = if (current_thread) |_| &LookupDnsWindows.completed else null,
13651 }, &lookup_dns.results,
13652 //&cancel_token,
13653 null)) {
1359513654 .SUCCESS => try lookup_dns.completedFallible(),
13596 .DNS_REQUEST_PENDING => unreachable, // `pQueryCompletionCallback` was `null`
13597 else => unreachable,
13655 // We must wait for the APC routine.
13656 .DNS_REQUEST_PENDING => {
13657 assert(current_thread != null); // `pQueryCompletionCallback` was `null`
13658 while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) {
13659 // Once we get here we must not return from the function until the
13660 // operation completes, thereby releasing references to `host_name_w`,
13661 // `lookup_dns.results`, and `cancel_token`.
13662 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
13663 error.Canceled => |e| {
13664 //_ = DnsCancelQuery.?(&cancel_token);
13665 while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) waitForApcOrAlert();
13666 return e;
13667 },
13668 };
13669 waitForApcOrAlert();
13670 alertable_syscall.finish();
13671 }
13672 },
13673 else => |status| lookup_dns.results.QueryStatus = status,
1359813674 },
1359913675 else => |status| lookup_dns.results.QueryStatus = status,
1360013676 }
1360113677 switch (lookup_dns.results.QueryStatus) {
1360213678 .SUCCESS => return,
1360313679 .DNS_REQUEST_PENDING => unreachable, // already handled
13604 .INVALID_NAME, .DNS_INFO_NO_RECORDS => return error.UnknownHostName,
13680 .INVALID_NAME,
13681 .DNS_ERROR_RCODE_NAME_ERROR,
13682 .DNS_INFO_NO_RECORDS,
13683 .DNS_ERROR_INVALID_NAME_CHAR,
13684 .DNS_ERROR_RECORD_DOES_NOT_EXIST,
13685 => return error.UnknownHostName,
1360513686 else => |err| return windows.unexpectedError(err),
1360613687 }
1360713688 }
1360813689
13609 // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics.
13610 // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it.
13611
13612 if (builtin.target.isGnuLibC()) {
13613 // TODO use getaddrinfo_a / gai_cancel
13614 }
13615
13616 if (native_os == .linux) {
13617 if (options.family != .ip4) {
13618 if (IpAddress.parseIp6(name, options.port)) |addr| {
13619 if (copyCanon(options.canonical_name_buffer, name)) |canon| {
13620 try resolved.putAll(t_io, &.{
13621 .{ .address = addr },
13622 .{ .canonical_name = canon },
13623 });
13624 } else {
13625 try resolved.putOne(t_io, .{ .address = addr });
13626 }
13627 return;
13628 } else |_| {}
13629 }
13630
13631 if (options.family != .ip6) {
13632 if (IpAddress.parseIp4(name, options.port)) |addr| {
13633 if (copyCanon(options.canonical_name_buffer, name)) |canon| {
13634 try resolved.putAll(t_io, &.{
13635 .{ .address = addr },
13636 .{ .canonical_name = canon },
13637 });
13638 } else {
13639 try resolved.putOne(t_io, .{ .address = addr });
13640 }
13641 return;
13642 } else |_| {}
13643 }
13644
13645 t.lookupHosts(host_name, resolved, options) catch |err| switch (err) {
13646 error.UnknownHostName => {},
13647 else => |e| return e,
13648 };
13649
13650 // RFC 6761 Section 6.3.3
13651 // Name resolution APIs and libraries SHOULD recognize
13652 // localhost names as special and SHOULD always return the IP
13653 // loopback address for address queries and negative responses
13654 // for all other query types.
13655
13656 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
13657 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
13658 if (std.mem.endsWith(u8, name, localhost) and
13659 (name.len == localhost.len or name[name.len - localhost.len] == '.'))
13660 {
13661 var results_buffer: [3]HostName.LookupResult = undefined;
13662 var results_index: usize = 0;
13663 if (options.family != .ip4) {
13664 results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } };
13665 results_index += 1;
13666 }
13667 if (options.family != .ip6) {
13668 results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } };
13669 results_index += 1;
13670 }
13671 if (options.canonical_name_buffer) |buf| {
13672 const canon_name = "localhost";
13673 const canon_name_dest = buf[0..canon_name.len];
13674 canon_name_dest.* = canon_name.*;
13675 results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } };
13676 results_index += 1;
13677 }
13678 try resolved.putAll(t_io, results_buffer[0..results_index]);
13679 return;
13680 }
13681
13682 return t.lookupDnsSearch(host_name, resolved, options);
13683 }
13684
1368513690 if (native_os == .openbsd) {
1368613691 // TODO use getaddrinfo_async / asr_abort
1368713692 }
......@@ -14510,8 +14515,32 @@ fn lookupHosts(
1451014515 resolved: *Io.Queue(HostName.LookupResult),
1451114516 options: HostName.LookupOptions,
1451214517) !void {
14513 const t_io = io(t);
14514 const file = Dir.openFileAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) {
14518 const path_w = if (is_windows) path_w: {
14519 var path_w_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined;
14520 const system_dir = windows.getSystemDirectoryWtf16Le();
14521 const suffix = [_]u16{
14522 '\\', 'd', 'r', 'i', 'v', 'e', 'r', 's', '\\', 'e', 't', 'c', '\\', 'h', 'o', 's', 't', 's',
14523 };
14524 @memcpy(path_w_buf[0..system_dir.len], system_dir);
14525 @memcpy(path_w_buf[system_dir.len..][0..suffix.len], &suffix);
14526 path_w_buf[system_dir.len + suffix.len] = 0;
14527 break :path_w wToPrefixedFileW(null, &path_w_buf, .{}) catch |err| switch (err) {
14528 error.FileNotFound,
14529 error.AccessDenied,
14530 => return error.UnknownHostName,
14531
14532 error.Canceled => |e| return e,
14533
14534 else => {
14535 // Here we could add more detailed diagnostics to the results queue.
14536 return error.DetectingNetworkConfigurationFailed;
14537 },
14538 };
14539 };
14540 const file = (if (is_windows)
14541 dirOpenFileWtf16(null, path_w.span(), .{})
14542 else
14543 dirOpenFile(t, .cwd(), "/etc/hosts", .{})) catch |err| switch (err) {
1451514544 error.FileNotFound,
1451614545 error.NotDir,
1451714546 error.AccessDenied,
......@@ -14524,10 +14553,10 @@ fn lookupHosts(
1452414553 return error.DetectingNetworkConfigurationFailed;
1452514554 },
1452614555 };
14527 defer file.close(t_io);
14556 defer fileClose(t, &.{file});
1452814557
1452914558 var line_buf: [512]u8 = undefined;
14530 var file_reader = file.reader(t_io, &line_buf);
14559 var file_reader = file.reader(t.io(), &line_buf);
1453114560 return t.lookupHostsReader(host_name, resolved, options, &file_reader.interface) catch |err| switch (err) {
1453214561 error.ReadFailed => switch (file_reader.err.?) {
1453314562 error.Canceled => |e| return e,
......@@ -14567,14 +14596,17 @@ fn lookupHostsReader(
1456714596 error.EndOfStream => break,
1456814597 };
1456914598 reader.toss(@min(1, reader.bufferedLen()));
14570 var split_it = std.mem.splitScalar(u8, line, '#');
14599 var split_it = std.mem.splitScalar(u8, if (is_windows and std.mem.endsWith(u8, line, "\r"))
14600 line[0 .. line.len - 1]
14601 else
14602 line, '#');
1457114603 const no_comment_line = split_it.first();
1457214604
1457314605 var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t");
1457414606 const ip_text = line_it.next() orelse continue;
1457514607 var first_name_text: ?[]const u8 = null;
1457614608 while (line_it.next()) |name_text| {
14577 if (std.mem.eql(u8, name_text, host_name.bytes)) {
14609 if (std.ascii.eqlIgnoreCase(name_text, host_name.bytes)) {
1457814610 if (first_name_text == null) first_name_text = name_text;
1457914611 break;
1458014612 }
lib/std/Io/net/test.zig+5-3
......@@ -121,7 +121,7 @@ test "resolve DNS" {
121121 // Resolve localhost, this should not fail.
122122 {
123123 const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80);
124 const localhost_v6 = try net.IpAddress.parse("::2", 80);
124 const localhost_v6 = try net.IpAddress.parse("::1", 80);
125125
126126 var canonical_name_buffer: [net.HostName.max_len]u8 = undefined;
127127 var results_buffer: [32]net.HostName.LookupResult = undefined;
......@@ -142,8 +142,10 @@ test "resolve DNS" {
142142 if (address.eql(&localhost_v4) or address.eql(&localhost_v6))
143143 addresses_found += 1;
144144 },
145 .canonical_name => |canonical_name| if (builtin.os.tag == .linux)
146 try testing.expectEqualStrings("localhost", canonical_name.bytes),
145 .canonical_name => |canonical_name| try testing.expectEqualStrings(
146 if (canonical_name.bytes[canonical_name.bytes.len - 1] == '.') "localhost." else "localhost",
147 canonical_name.bytes,
148 ),
147149 } else |err| switch (err) {
148150 error.Closed => {},
149151 error.Canceled => |e| return e,