Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
6411ae5ef0 fix(server): show proper DNS error instead of EADDRINUSE for unresolvable hostnames
When `Bun.serve()` is called with a hostname that cannot be resolved
via DNS (e.g., `something.localhost`), the error message now correctly
shows `getaddrinfo EAI_NONAME` with code `ENOTFOUND` instead of the
misleading `EADDRINUSE` error suggesting the port is in use.

The fix captures the `getaddrinfo()` return code in `bsd.c` and maps it
to libuv-style error codes (UV__EAI_*), which are then detected and
displayed properly in `server.zig`.

Fixes #25765

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 12:09:20 +00:00
3 changed files with 127 additions and 1 deletions

View File

@@ -1001,7 +1001,49 @@ LIBUS_SOCKET_DESCRIPTOR bsd_create_listen_socket(const char *host, int port, int
char port_string[16];
snprintf(port_string, 16, "%d", port);
if (getaddrinfo(host, port_string, &hints, &result)) {
int gai_error = getaddrinfo(host, port_string, &hints, &result);
if (gai_error) {
// Map EAI_* errors to libuv-style negative error codes
// These match UV__EAI_* values from libuv/uv/errno.h
// We set both the error pointer and errno so the error can be detected
// by callers that check either one
int uv_error;
switch (gai_error) {
#ifdef EAI_ADDRFAMILY
case EAI_ADDRFAMILY: uv_error = -3000; break;
#endif
case EAI_AGAIN: uv_error = -3001; break;
case EAI_BADFLAGS: uv_error = -3002; break;
#ifdef EAI_CANCELED
case EAI_CANCELED: uv_error = -3003; break;
#endif
case EAI_FAIL: uv_error = -3004; break;
case EAI_FAMILY: uv_error = -3005; break;
case EAI_MEMORY: uv_error = -3006; break;
#ifdef EAI_NODATA
case EAI_NODATA: uv_error = -3007; break;
#endif
case EAI_NONAME: uv_error = -3008; break;
#ifdef EAI_OVERFLOW
case EAI_OVERFLOW: uv_error = -3009; break;
#endif
case EAI_SERVICE: uv_error = -3010; break;
case EAI_SOCKTYPE: uv_error = -3011; break;
#ifdef EAI_SYSTEM
case EAI_SYSTEM: uv_error = LIBUS_ERR; break; // Use actual errno for system errors
#endif
default: uv_error = -3008; break; // Default to EAI_NONAME
}
if (error) {
*error = uv_error;
}
// Store the libuv-style error code in errno so it can be retrieved
// by callers that don't have access to the error pointer
#ifdef _WIN32
WSASetLastError(-uv_error);
#else
errno = -uv_error;
#endif
return LIBUS_SOCKET_ERROR;
}

View File

@@ -1745,6 +1745,37 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
switch (this.config.address) {
.tcp => |tcp| {
error_set: {
// Check for getaddrinfo errors (stored as positive libuv-style error codes)
// UV__EAI_* values from libuv/uv/errno.h: 3000-3014
const raw_errno: c_int = std.c._errno().*;
if (raw_errno >= 3000 and raw_errno <= 3014) {
// This is a getaddrinfo error - DNS resolution failed
const hostname_slice: []const u8 = if (tcp.hostname) |h| bun.span(h) else "0.0.0.0";
const eai_code: []const u8 = switch (raw_errno) {
3000 => "EAI_ADDRFAMILY",
3001 => "EAI_AGAIN",
3002 => "EAI_BADFLAGS",
3003 => "EAI_CANCELED",
3004 => "EAI_FAIL",
3005 => "EAI_FAMILY",
3006 => "EAI_MEMORY",
3007 => "EAI_NODATA",
3008 => "EAI_NONAME",
3009 => "EAI_OVERFLOW",
3010 => "EAI_SERVICE",
3011 => "EAI_SOCKTYPE",
3013 => "EAI_BADHINTS",
3014 => "EAI_PROTOCOL",
else => "EAI_NONAME",
};
error_instance = (jsc.SystemError{
.message = bun.String.init(std.fmt.bufPrint(&output_buf, "getaddrinfo {s}", .{eai_code}) catch "getaddrinfo error"),
.code = bun.String.static("ENOTFOUND"),
.syscall = bun.String.static("getaddrinfo"),
.hostname = bun.String.init(hostname_slice),
}).toErrorInstance(globalThis);
break :error_set;
}
if (comptime Environment.isLinux) {
const rc: i32 = -1;
const code = Sys.getErrno(rc);

View File

@@ -0,0 +1,53 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
// https://github.com/oven-sh/bun/issues/25765
// Bun.serve() with unresolvable hostname should show proper DNS error, not EADDRINUSE
test("Bun.serve() with unresolvable hostname shows DNS error", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `Bun.serve({ hostname: "something.localhost", fetch: () => new Response("Hello") });`],
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should not claim the port is in use (EADDRINUSE)
expect(stderr).not.toContain("EADDRINUSE");
expect(stderr).not.toContain("Is port");
// Should show a DNS resolution error
expect(stderr).toContain("ENOTFOUND");
expect(stderr).toContain("getaddrinfo");
// Should fail
expect(exitCode).not.toBe(0);
});
test("Bun.serve() with valid hostname still works", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const server = Bun.serve({
hostname: "localhost",
port: 0,
fetch: () => new Response("Hello")
});
console.log("listening on port", server.port);
server.stop();
`,
],
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("listening on port");
expect(exitCode).toBe(0);
});