mirror of
https://github.com/oven-sh/bun
synced 2026-02-15 05:12:29 +00:00
fix(dns): fallback to public DNS servers when c-ares detects only 127.0.0.1 on Windows
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 results in ECONNREFUSED errors for dns.resolveSrv() and other DNS record queries that must use c-ares. This fix detects when c-ares has only 127.0.0.1 configured as a DNS server after initialization and replaces it with public DNS servers (Cloudflare 1.1.1.1, Google 8.8.8.8/8.8.4.4) as a fallback. Fixes #26467 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
49
test/regression/issue/26467.test.ts
Normal file
49
test/regression/issue/26467.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user