diff --git a/src/deps/c_ares.zig b/src/deps/c_ares.zig index 41ecbca7ab..1ee999dd71 100644 --- a/src/deps/c_ares.zig +++ b/src/deps/c_ares.zig @@ -607,10 +607,57 @@ pub const Channel = opaque { return err; } + // On Windows, c-ares may fail to detect DNS servers from the system + // and fall back to 127.0.0.1. This causes DNS queries to fail with + // ECONNREFUSED. Detect this case and use public DNS servers as fallback. + // See: https://github.com/oven-sh/bun/issues/26467 + if (bun.Environment.isWindows) { + channel.applyWindowsDnsFallback(); + } + this.channel = channel; return null; } + /// On Windows, c-ares uses IPHLPAPI (GetNetworkParams/GetAdaptersInfo) to detect + /// system DNS servers. This can fail on some Windows configurations, causing + /// c-ares to fall back to 127.0.0.1, which doesn't respond to DNS queries. + /// This function detects that case and sets public DNS servers as a fallback. + fn applyWindowsDnsFallback(channel: *Channel) void { + var servers: ?*struct_ares_addr_port_node = null; + if (Error.get(ares_get_servers_ports(channel, &servers))) |_| { + // Failed to get servers, can't apply fallback + return; + } + defer ares_free_data(servers); + + // Check if the only configured server is localhost (127.0.0.1) + const first_server = servers orelse return; + if (first_server.next != null) { + // Multiple servers configured, don't override + return; + } + + // Check if it's an IPv4 address + if (first_server.family != AF.INET) { + return; + } + + // Check if it's 127.0.0.1 (localhost) + // struct_in_addr is std.posix.sockaddr.in which contains .sa (sockaddr struct) + // The s_addr is the first 4 bytes in network byte order + const addr_bytes: *const [4]u8 = @ptrCast(&first_server.addr.addr4); + const is_localhost = addr_bytes[0] == 127 and addr_bytes[1] == 0 and addr_bytes[2] == 0 and addr_bytes[3] == 1; + + if (!is_localhost) { + return; + } + + // The only DNS server is 127.0.0.1, which likely won't work. + // Set fallback public DNS servers: Cloudflare (1.1.1.1) and Google (8.8.8.8, 8.8.4.4) + _ = ares_set_servers_csv(channel, "1.1.1.1,8.8.8.8,8.8.4.4"); + } + pub fn deinit(this: *Channel) void { ares_destroy(this); } diff --git a/test/regression/issue/26467.test.ts b/test/regression/issue/26467.test.ts new file mode 100644 index 0000000000..98b6dfa56e --- /dev/null +++ b/test/regression/issue/26467.test.ts @@ -0,0 +1,49 @@ +import { beforeAll, expect, setDefaultTimeout, test } from "bun:test"; +import dns from "dns"; + +beforeAll(() => { + setDefaultTimeout(30_000); +}); + +// https://github.com/oven-sh/bun/issues/26467 +// On Windows, dns.resolveSrv() was failing with ECONNREFUSED when c-ares +// fell back to using 127.0.0.1 as the DNS server due to IPHLPAPI failures. +// The fix adds a fallback to public DNS servers when only 127.0.0.1 is configured. +test("dns.resolveSrv should resolve SRV records", async () => { + // Use a known SRV record for testing + const hostname = "_test._tcp.test.socketify.dev"; + + const results = await dns.promises.resolveSrv(hostname); + + expect(Array.isArray(results)).toBe(true); + expect(results.length).toBeGreaterThan(0); + + // Verify the SRV record structure + const record = results[0]; + expect(record).toHaveProperty("name"); + expect(record).toHaveProperty("port"); + expect(record).toHaveProperty("priority"); + expect(record).toHaveProperty("weight"); + expect(typeof record.name).toBe("string"); + expect(typeof record.port).toBe("number"); + expect(typeof record.priority).toBe("number"); + expect(typeof record.weight).toBe("number"); +}); + +test("dns.getServers should not return only 127.0.0.1 after initialization", () => { + // After the fix, if c-ares detects only 127.0.0.1 on Windows, + // it should have been replaced with public DNS servers. + const servers = dns.getServers(); + + expect(Array.isArray(servers)).toBe(true); + expect(servers.length).toBeGreaterThan(0); + + // The servers should contain at least one non-localhost server + // If the fix worked, we shouldn't see only "127.0.0.1" as the sole server + const hasNonLocalhost = servers.some(server => !server.startsWith("127.") && server !== "::1"); + + // Note: This test may pass even without the fix on systems with proper DNS configuration. + // The fix specifically targets Windows systems where IPHLPAPI fails to detect DNS servers. + // On Linux/macOS, this should always pass since /etc/resolv.conf is reliable. + expect(hasNonLocalhost).toBe(true); +});