diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index cd4393f3625c4eda22ce7a83fe6392bdcc67d6bc..6554e145d962344b00d30fb4087ae93dcd420d13 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -13482,21 +13482,80 @@ fn netLookupFallible( const name = host_name.bytes; assert(name.len <= HostName.max_len); - if (is_windows) { - if (options.family == null) { - if (IpAddress.parseIp4(name, options.port)) |addr| { - if (copyCanon(options.canonical_name_buffer, name)) |canon| { - try resolved.putAll(t_io, &.{ - .{ .address = addr }, - .{ .canonical_name = canon }, - }); - } else { - try resolved.putOne(t_io, .{ .address = addr }); - } - return; - } else |_| {} + // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics. + // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it. + + if (builtin.target.isGnuLibC()) { + // TODO use getaddrinfo_a / gai_cancel + } + + if (native_os == .linux or is_windows) { + if (IpAddress.parseIp6(name, options.port)) |addr| { + if (options.family == .ip4) return error.UnknownHostName; + if (copyCanon(options.canonical_name_buffer, name)) |canon| { + try resolved.putAll(t_io, &.{ + .{ .address = addr }, + .{ .canonical_name = canon }, + }); + } else { + try resolved.putOne(t_io, .{ .address = addr }); + } + return; + } else |_| {} + + if (IpAddress.parseIp4(name, options.port)) |addr| { + if (options.family == .ip6) return error.UnknownHostName; + if (copyCanon(options.canonical_name_buffer, name)) |canon| { + try resolved.putAll(t_io, &.{ + .{ .address = addr }, + .{ .canonical_name = canon }, + }); + } else { + try resolved.putOne(t_io, .{ .address = addr }); + } + return; + } else |_| {} + + if (t.lookupHosts(host_name, resolved, options)) return else |err| switch (err) { + error.UnknownHostName => {}, + else => |e| return e, } + // RFC 6761 Section 6.3.3 + // Name resolution APIs and libraries SHOULD recognize + // localhost names as special and SHOULD always return the IP + // loopback address for address queries and negative responses + // for all other query types. + + // Check for equal to "localhost(.)" or ends in ".localhost(.)" + const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; + if (std.mem.endsWith(u8, name, localhost) and + (name.len == localhost.len or name[name.len - localhost.len] == '.')) + { + var results_buffer: [3]HostName.LookupResult = undefined; + var results_index: usize = 0; + if (options.family != .ip4) { + results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } }; + results_index += 1; + } + if (options.family != .ip6) { + results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } }; + results_index += 1; + } + if (options.canonical_name_buffer) |buf| { + const canon_name = "localhost"; + const canon_name_dest = buf[0..canon_name.len]; + canon_name_dest.* = canon_name.*; + results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } }; + results_index += 1; + } + try resolved.putAll(t_io, results_buffer[0..results_index]); + return; + } + + if (native_os == .linux) return t.lookupDnsSearch(host_name, resolved, options); + + comptime assert(is_windows); var DnsQueryEx = t.dl.DnsQueryEx.load(.acquire); //var DnsCancelQuery = t.dl.DnsCancelQuery.load(.acquire); var DnsFree = t.dl.DnsFree.load(.acquire); @@ -13540,6 +13599,7 @@ fn netLookupFallible( else => |status| return windows.unexpectedStatus(status), } } + try Thread.checkCancel(); const current_thread = Thread.current; var lookup_dns: LookupDnsWindows = .{ .threaded = t, @@ -13562,126 +13622,71 @@ fn netLookupFallible( } ] = 0; //var cancel_token: windows.DNS.QUERY.CANCEL = undefined; + // Workaround various bugs by attempting a synchronous non-wire query first switch (DnsQueryEx.?(&.{ .Version = 1, .QueryName = &host_name_w, .QueryType = if (options.family == .ip4) .A else .AAAA, .QueryOptions = .{ + .NO_WIRE_QUERY = true, + .NO_HOSTS_FILE = true, // handled above .ADDRCONFIG = true, .DUAL_ADDR = options.family == null, - .MULTICAST_WAIT = true, }, - .pQueryCompletionCallback = if (current_thread) |_| &LookupDnsWindows.completed else null, - }, &lookup_dns.results, - //&cancel_token, - null)) { + }, &lookup_dns.results, null)) { + .SUCCESS => try lookup_dns.completedFallible(), // We must wait for the APC routine. - .SUCCESS, .DNS_REQUEST_PENDING => |status| if (current_thread) |_| { - while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) { - // Once we get here we must not return from the function until the - // operation completes, thereby releasing references to `host_name_w`, - // `lookup_dns.results`, and `cancel_token`. - const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { - error.Canceled => |e| { - //_ = DnsCancelQuery.?(&cancel_token); - while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) waitForApcOrAlert(); - return e; - }, - }; - waitForApcOrAlert(); - alertable_syscall.finish(); - } - } else switch (status) { + .DNS_REQUEST_PENDING => unreachable, // `pQueryCompletionCallback` was `null` + .DNS_ERROR_RECORD_DOES_NOT_EXIST => switch (DnsQueryEx.?(&.{ + .Version = 1, + .QueryName = &host_name_w, + .QueryType = if (options.family == .ip4) .A else .AAAA, + .QueryOptions = .{ + .NO_HOSTS_FILE = true, // handled above + .ADDRCONFIG = true, + .DUAL_ADDR = options.family == null, + .MULTICAST_WAIT = true, + }, + .pQueryCompletionCallback = if (current_thread) |_| &LookupDnsWindows.completed else null, + }, &lookup_dns.results, + //&cancel_token, + null)) { .SUCCESS => try lookup_dns.completedFallible(), - .DNS_REQUEST_PENDING => unreachable, // `pQueryCompletionCallback` was `null` - else => unreachable, + // We must wait for the APC routine. + .DNS_REQUEST_PENDING => { + assert(current_thread != null); // `pQueryCompletionCallback` was `null` + while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) { + // Once we get here we must not return from the function until the + // operation completes, thereby releasing references to `host_name_w`, + // `lookup_dns.results`, and `cancel_token`. + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { + error.Canceled => |e| { + //_ = DnsCancelQuery.?(&cancel_token); + while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) waitForApcOrAlert(); + return e; + }, + }; + waitForApcOrAlert(); + alertable_syscall.finish(); + } + }, + else => |status| lookup_dns.results.QueryStatus = status, }, else => |status| lookup_dns.results.QueryStatus = status, } switch (lookup_dns.results.QueryStatus) { .SUCCESS => return, .DNS_REQUEST_PENDING => unreachable, // already handled - .INVALID_NAME, .DNS_INFO_NO_RECORDS => return error.UnknownHostName, + .INVALID_NAME, + .DNS_ERROR_RCODE_NAME_ERROR, + .DNS_INFO_NO_RECORDS, + .DNS_ERROR_INVALID_NAME_CHAR, + .DNS_ERROR_RECORD_DOES_NOT_EXIST, + => return error.UnknownHostName, else => |err| return windows.unexpectedError(err), } } - // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics. - // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it. - - if (builtin.target.isGnuLibC()) { - // TODO use getaddrinfo_a / gai_cancel - } - - if (native_os == .linux) { - if (options.family != .ip4) { - if (IpAddress.parseIp6(name, options.port)) |addr| { - if (copyCanon(options.canonical_name_buffer, name)) |canon| { - try resolved.putAll(t_io, &.{ - .{ .address = addr }, - .{ .canonical_name = canon }, - }); - } else { - try resolved.putOne(t_io, .{ .address = addr }); - } - return; - } else |_| {} - } - - if (options.family != .ip6) { - if (IpAddress.parseIp4(name, options.port)) |addr| { - if (copyCanon(options.canonical_name_buffer, name)) |canon| { - try resolved.putAll(t_io, &.{ - .{ .address = addr }, - .{ .canonical_name = canon }, - }); - } else { - try resolved.putOne(t_io, .{ .address = addr }); - } - return; - } else |_| {} - } - - t.lookupHosts(host_name, resolved, options) catch |err| switch (err) { - error.UnknownHostName => {}, - else => |e| return e, - }; - - // RFC 6761 Section 6.3.3 - // Name resolution APIs and libraries SHOULD recognize - // localhost names as special and SHOULD always return the IP - // loopback address for address queries and negative responses - // for all other query types. - - // Check for equal to "localhost(.)" or ends in ".localhost(.)" - const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; - if (std.mem.endsWith(u8, name, localhost) and - (name.len == localhost.len or name[name.len - localhost.len] == '.')) - { - var results_buffer: [3]HostName.LookupResult = undefined; - var results_index: usize = 0; - if (options.family != .ip4) { - results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } }; - results_index += 1; - } - if (options.family != .ip6) { - results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } }; - results_index += 1; - } - if (options.canonical_name_buffer) |buf| { - const canon_name = "localhost"; - const canon_name_dest = buf[0..canon_name.len]; - canon_name_dest.* = canon_name.*; - results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } }; - results_index += 1; - } - try resolved.putAll(t_io, results_buffer[0..results_index]); - return; - } - - return t.lookupDnsSearch(host_name, resolved, options); - } - if (native_os == .openbsd) { // TODO use getaddrinfo_async / asr_abort } @@ -14510,8 +14515,32 @@ fn lookupHosts( resolved: *Io.Queue(HostName.LookupResult), options: HostName.LookupOptions, ) !void { - const t_io = io(t); - const file = Dir.openFileAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) { + const path_w = if (is_windows) path_w: { + var path_w_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; + const system_dir = windows.getSystemDirectoryWtf16Le(); + const suffix = [_]u16{ + '\\', 'd', 'r', 'i', 'v', 'e', 'r', 's', '\\', 'e', 't', 'c', '\\', 'h', 'o', 's', 't', 's', + }; + @memcpy(path_w_buf[0..system_dir.len], system_dir); + @memcpy(path_w_buf[system_dir.len..][0..suffix.len], &suffix); + path_w_buf[system_dir.len + suffix.len] = 0; + break :path_w wToPrefixedFileW(null, &path_w_buf, .{}) catch |err| switch (err) { + error.FileNotFound, + error.AccessDenied, + => return error.UnknownHostName, + + error.Canceled => |e| return e, + + else => { + // Here we could add more detailed diagnostics to the results queue. + return error.DetectingNetworkConfigurationFailed; + }, + }; + }; + const file = (if (is_windows) + dirOpenFileWtf16(null, path_w.span(), .{}) + else + dirOpenFile(t, .cwd(), "/etc/hosts", .{})) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, @@ -14524,10 +14553,10 @@ fn lookupHosts( return error.DetectingNetworkConfigurationFailed; }, }; - defer file.close(t_io); + defer fileClose(t, &.{file}); var line_buf: [512]u8 = undefined; - var file_reader = file.reader(t_io, &line_buf); + var file_reader = file.reader(t.io(), &line_buf); return t.lookupHostsReader(host_name, resolved, options, &file_reader.interface) catch |err| switch (err) { error.ReadFailed => switch (file_reader.err.?) { error.Canceled => |e| return e, @@ -14567,14 +14596,17 @@ fn lookupHostsReader( error.EndOfStream => break, }; reader.toss(@min(1, reader.bufferedLen())); - var split_it = std.mem.splitScalar(u8, line, '#'); + var split_it = std.mem.splitScalar(u8, if (is_windows and std.mem.endsWith(u8, line, "\r")) + line[0 .. line.len - 1] + else + line, '#'); const no_comment_line = split_it.first(); var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t"); const ip_text = line_it.next() orelse continue; var first_name_text: ?[]const u8 = null; while (line_it.next()) |name_text| { - if (std.mem.eql(u8, name_text, host_name.bytes)) { + if (std.ascii.eqlIgnoreCase(name_text, host_name.bytes)) { if (first_name_text == null) first_name_text = name_text; break; } diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 450adefc0081e30b3cc91c5895ce8f7be5c30482..8a108479020578dfa0c836b2a4211b46113bfbc6 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -121,7 +121,7 @@ test "resolve DNS" { // Resolve localhost, this should not fail. { const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80); - const localhost_v6 = try net.IpAddress.parse("::2", 80); + const localhost_v6 = try net.IpAddress.parse("::1", 80); var canonical_name_buffer: [net.HostName.max_len]u8 = undefined; var results_buffer: [32]net.HostName.LookupResult = undefined; @@ -142,8 +142,10 @@ test "resolve DNS" { if (address.eql(&localhost_v4) or address.eql(&localhost_v6)) addresses_found += 1; }, - .canonical_name => |canonical_name| if (builtin.os.tag == .linux) - try testing.expectEqualStrings("localhost", canonical_name.bytes), + .canonical_name => |canonical_name| try testing.expectEqualStrings( + if (canonical_name.bytes[canonical_name.bytes.len - 1] == '.') "localhost." else "localhost", + canonical_name.bytes, + ), } else |err| switch (err) { error.Closed => {}, error.Canceled => |e| return e,