mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 20:09:04 +00:00
feat: impl dns.getServers (#3982)
* feat: impl `dns.getServers` Close: #3981 * check return value of `ares_inet_ntop`
This commit is contained in:
@@ -16,6 +16,8 @@ const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const c_ares = bun.c_ares;
|
||||
|
||||
const GetAddrInfoAsyncCallback = fn (i32, ?*std.c.addrinfo, ?*anyopaque) callconv(.C) void;
|
||||
const INET6_ADDRSTRLEN = if (bun.Environment.isWindows) 65 else 46;
|
||||
const IANA_DNS_PORT = 53;
|
||||
|
||||
const LibInfo = struct {
|
||||
// static int32_t (*getaddrinfo_async_start)(mach_port_t*,
|
||||
@@ -2015,6 +2017,89 @@ pub const DNSResolver = struct {
|
||||
return promise;
|
||||
}
|
||||
|
||||
pub fn getServers(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
||||
_ = callframe;
|
||||
|
||||
var vm = globalThis.bunVM();
|
||||
var resolver = vm.rareData().globalDNSResolver(vm);
|
||||
var channel: *c_ares.Channel = switch (resolver.getChannel()) {
|
||||
.result => |res| res,
|
||||
.err => |err| {
|
||||
const system_error = JSC.SystemError{
|
||||
.errno = -1,
|
||||
.code = bun.String.static(err.code()),
|
||||
.message = bun.String.static(err.label()),
|
||||
};
|
||||
|
||||
globalThis.throwValue(system_error.toErrorInstance(globalThis));
|
||||
return .zero;
|
||||
},
|
||||
};
|
||||
|
||||
var servers: [*c]c_ares.struct_ares_addr_port_node = undefined;
|
||||
const r = c_ares.ares_get_servers_ports(channel, &servers);
|
||||
if (r != c_ares.ARES_SUCCESS) {
|
||||
const err = c_ares.Error.get(r).?;
|
||||
globalThis.throwValue(globalThis.createErrorInstance("ares_get_servers_ports error: {s}", .{err.label()}));
|
||||
return .zero;
|
||||
}
|
||||
defer c_ares.ares_free_data(servers);
|
||||
|
||||
const values = JSC.JSValue.createEmptyArray(globalThis, 0);
|
||||
|
||||
var i: u32 = 0;
|
||||
var cur = servers;
|
||||
while (cur != null) : ({
|
||||
i += 1;
|
||||
cur = cur.*.next;
|
||||
}) {
|
||||
// Formatting reference: https://nodejs.org/api/dns.html#dnsgetservers
|
||||
// Brackets '[' and ']' consume 2 bytes, used for IPv6 format (e.g., '[2001:4860:4860::8888]:1053').
|
||||
// Port range is 6 bytes (e.g., ':65535').
|
||||
// Null terminator '\0' uses 1 byte.
|
||||
var buf: [INET6_ADDRSTRLEN + 2 + 6 + 1]u8 = undefined;
|
||||
const family = cur.*.family;
|
||||
|
||||
const ip = if (family == std.os.AF.INET6) blk: {
|
||||
break :blk c_ares.ares_inet_ntop(family, &cur.*.addr.addr6, buf[1..], @sizeOf(@TypeOf(buf)) - 1);
|
||||
} else blk: {
|
||||
break :blk c_ares.ares_inet_ntop(family, &cur.*.addr.addr4, buf[1..], @sizeOf(@TypeOf(buf)) - 1);
|
||||
};
|
||||
if (ip == null) {
|
||||
globalThis.throwValue(globalThis.createErrorInstance(
|
||||
"ares_inet_ntop error: no more space to convert a network format address",
|
||||
.{},
|
||||
));
|
||||
return .zero;
|
||||
}
|
||||
|
||||
var port = cur.*.tcp_port;
|
||||
if (port == 0) {
|
||||
port = cur.*.udp_port;
|
||||
}
|
||||
if (port == 0) {
|
||||
port = IANA_DNS_PORT;
|
||||
}
|
||||
|
||||
const size = bun.len(bun.cast([*:0]u8, &buf));
|
||||
if (port == IANA_DNS_PORT) {
|
||||
values.putIndex(globalThis, i, JSC.ZigString.init(buf[1..size]).withEncoding().toValueGC(globalThis));
|
||||
} else {
|
||||
if (family == std.os.AF.INET6) {
|
||||
buf[0] = '[';
|
||||
buf[size] = ']';
|
||||
const port_slice = std.fmt.bufPrint(buf[size + 1 ..], ":{d}", .{port}) catch unreachable;
|
||||
values.putIndex(globalThis, i, JSC.ZigString.init(buf[0 .. size + 1 + port_slice.len]).withEncoding().toValueGC(globalThis));
|
||||
} else {
|
||||
const port_slice = std.fmt.bufPrint(buf[size..], ":{d}", .{port}) catch unreachable;
|
||||
values.putIndex(globalThis, i, JSC.ZigString.init(buf[1 .. size + port_slice.len]).withEncoding().toValueGC(globalThis));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
comptime {
|
||||
@export(
|
||||
resolve,
|
||||
@@ -2082,6 +2167,12 @@ pub const DNSResolver = struct {
|
||||
.name = "Bun__DNSResolver__resolveCname",
|
||||
},
|
||||
);
|
||||
@export(
|
||||
getServers,
|
||||
.{
|
||||
.name = "Bun__DNSResolver__getServers",
|
||||
},
|
||||
);
|
||||
}
|
||||
// pub fn lookupService(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
||||
// const arguments = callframe.arguments(3);
|
||||
|
||||
@@ -2860,6 +2860,7 @@ extern "C" EncodedJSValue Bun__DNSResolver__resolveCaa(JSGlobalObject*, JSC::Cal
|
||||
extern "C" EncodedJSValue Bun__DNSResolver__resolveNs(JSGlobalObject*, JSC::CallFrame*);
|
||||
extern "C" EncodedJSValue Bun__DNSResolver__resolvePtr(JSGlobalObject*, JSC::CallFrame*);
|
||||
extern "C" EncodedJSValue Bun__DNSResolver__resolveCname(JSGlobalObject*, JSC::CallFrame*);
|
||||
extern "C" EncodedJSValue Bun__DNSResolver__getServers(JSGlobalObject*, JSC::CallFrame*);
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionPerformMicrotaskVariadic, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
{
|
||||
@@ -3251,6 +3252,8 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
|
||||
dnsObject->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "resolveCname"_s), 2, Bun__DNSResolver__resolveCname, ImplementationVisibility::Public, NoIntrinsic,
|
||||
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
|
||||
dnsObject->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "getServers"_s), 2, Bun__DNSResolver__getServers, ImplementationVisibility::Public, NoIntrinsic,
|
||||
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
|
||||
init.set(dnsObject);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// only resolve4, resolve, lookup, resolve6 and resolveSrv are implemented.
|
||||
const dns = Bun.dns;
|
||||
|
||||
function getServers() {
|
||||
return dns.getServers();
|
||||
}
|
||||
|
||||
function lookup(domain, options, callback) {
|
||||
if (typeof options == "function") {
|
||||
callback = options;
|
||||
@@ -685,4 +689,5 @@ export default {
|
||||
resolveTxt,
|
||||
resolveNaptr,
|
||||
promises,
|
||||
getServers,
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,8 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import * as dns from "node:dns";
|
||||
import * as dns_promises from "node:dns/promises";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
|
||||
// TODO:
|
||||
test("it exists", () => {
|
||||
@@ -190,3 +192,34 @@ test("dns.lookup (localhost)", done => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
test("dns.getServers", done => {
|
||||
function parseResolvConf() {
|
||||
let servers = [];
|
||||
try {
|
||||
const content = fs.readFileSync("/etc/resolv.conf", "utf-8");
|
||||
const lines = content.split(os.EOL);
|
||||
|
||||
for (const line of lines) {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
if (parts.length >= 2 && parts[0] === "nameserver") {
|
||||
servers.push(parts[1]);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
const expectServers = parseResolvConf();
|
||||
const actualServers = dns.getServers();
|
||||
try {
|
||||
for (const server of expectServers) {
|
||||
expect(actualServers).toContain(server);
|
||||
}
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user