mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
1399 lines
46 KiB
Zig
1399 lines
46 KiB
Zig
const Bun = @This();
|
|
const default_allocator = @import("bun").default_allocator;
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const NetworkThread = @import("bun").HTTP.NetworkThread;
|
|
const Global = bun.Global;
|
|
const strings = bun.strings;
|
|
const string = bun.string;
|
|
const Output = @import("bun").Output;
|
|
const MutableString = @import("bun").MutableString;
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const JSC = @import("bun").JSC;
|
|
const JSValue = JSC.JSValue;
|
|
const JSGlobalObject = JSC.JSGlobalObject;
|
|
const c_ares = bun.c_ares;
|
|
|
|
const GetAddrInfoAsyncCallback = fn (i32, ?*std.c.addrinfo, ?*anyopaque) callconv(.C) void;
|
|
|
|
const LibInfo = struct {
|
|
// static int32_t (*getaddrinfo_async_start)(mach_port_t*,
|
|
// const char*,
|
|
// const char*,
|
|
// const struct addrinfo*,
|
|
// getaddrinfo_async_callback,
|
|
// void*);
|
|
// static int32_t (*getaddrinfo_async_handle_reply)(void*);
|
|
// static void (*getaddrinfo_async_cancel)(mach_port_t);
|
|
// typedef void getaddrinfo_async_callback(int32_t, struct addrinfo*, void*)
|
|
const GetaddrinfoAsyncStart = fn (*?*anyopaque, noalias node: ?[*:0]const u8, noalias service: ?[*:0]const u8, noalias hints: ?*const std.c.addrinfo, callback: *const GetAddrInfoAsyncCallback, noalias context: ?*anyopaque) callconv(.C) i32;
|
|
const GetaddrinfoAsyncHandleReply = fn (?**anyopaque) callconv(.C) i32;
|
|
const GetaddrinfoAsyncCancel = fn (?**anyopaque) callconv(.C) void;
|
|
|
|
var handle: ?*anyopaque = null;
|
|
var loaded = false;
|
|
pub fn getHandle() ?*anyopaque {
|
|
if (loaded)
|
|
return handle;
|
|
loaded = true;
|
|
const RTLD_LAZY = 1;
|
|
const RTLD_LOCAL = 4;
|
|
|
|
handle = std.c.dlopen("libinfo.dylib", RTLD_LAZY | RTLD_LOCAL);
|
|
if (handle == null)
|
|
Output.debug("libinfo.dylib not found", .{});
|
|
return handle;
|
|
}
|
|
|
|
pub const getaddrinfo_async_start = struct {
|
|
pub fn get() ?*const GetaddrinfoAsyncStart {
|
|
bun.Environment.onlyMac();
|
|
|
|
return bun.C.dlsymWithHandle(*const GetaddrinfoAsyncStart, "getaddrinfo_async_start", getHandle);
|
|
}
|
|
}.get;
|
|
|
|
pub const getaddrinfo_async_handle_reply = struct {
|
|
pub fn get() ?*const GetaddrinfoAsyncHandleReply {
|
|
bun.Environment.onlyMac();
|
|
|
|
return bun.C.dlsymWithHandle(*const GetaddrinfoAsyncHandleReply, "getaddrinfo_async_handle_reply", getHandle);
|
|
}
|
|
}.get;
|
|
|
|
pub fn get() ?*const GetaddrinfoAsyncCancel {
|
|
bun.Environment.onlyMac();
|
|
|
|
return bun.C.dlsymWithHandle(*const GetaddrinfoAsyncCancel, "getaddrinfo_async_cancel", getHandle);
|
|
}
|
|
|
|
pub fn lookup(this: *DNSResolver, query: GetAddrInfo, globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
|
bun.Environment.onlyMac();
|
|
|
|
const getaddrinfo_async_start_ = LibInfo.getaddrinfo_async_start() orelse return LibC.lookup(this, query, globalThis);
|
|
|
|
var key = GetAddrInfoRequest.PendingCacheKey.init(query);
|
|
var cache = this.getOrPutIntoPendingCache(key, .pending_host_cache_native);
|
|
|
|
if (cache == .inflight) {
|
|
var dns_lookup = DNSLookup.init(globalThis, globalThis.allocator()) catch unreachable;
|
|
|
|
cache.inflight.append(dns_lookup);
|
|
|
|
return dns_lookup.promise.value();
|
|
}
|
|
|
|
var name_buf: [1024]u8 = undefined;
|
|
_ = strings.copy(name_buf[0..], query.name);
|
|
|
|
name_buf[query.name.len] = 0;
|
|
var name_z = name_buf[0..query.name.len :0];
|
|
|
|
var request = GetAddrInfoRequest.init(
|
|
cache,
|
|
.{ .libinfo = undefined },
|
|
this,
|
|
query,
|
|
globalThis,
|
|
"pending_host_cache_native",
|
|
) catch unreachable;
|
|
const promise_value = request.head.promise.value();
|
|
|
|
const errno = getaddrinfo_async_start_(
|
|
&request.backend.libinfo.machport,
|
|
name_z.ptr,
|
|
null,
|
|
null,
|
|
GetAddrInfoRequest.getAddrInfoAsyncCallback,
|
|
request,
|
|
);
|
|
|
|
if (errno != 0) {
|
|
request.head.promise.reject(globalThis, globalThis.createErrorInstance("getaddrinfo_async_start error: {s}", .{@tagName(std.c.getErrno(errno))}));
|
|
if (request.cache.pending_cache) this.pending_host_cache_native.available.set(request.cache.pos_in_pending);
|
|
this.vm.allocator.destroy(request);
|
|
|
|
return promise_value;
|
|
}
|
|
std.debug.assert(request.backend.libinfo.machport != null);
|
|
request.backend.libinfo.file_poll = bun.JSC.FilePoll.init(this.vm, std.math.maxInt(i32) - 1, .{}, GetAddrInfoRequest, request);
|
|
std.debug.assert(
|
|
request.backend.libinfo.file_poll.?.registerWithFd(
|
|
this.vm.uws_event_loop.?,
|
|
.machport,
|
|
true,
|
|
@ptrToInt(request.backend.libinfo.machport),
|
|
) == .result,
|
|
);
|
|
|
|
return promise_value;
|
|
}
|
|
};
|
|
|
|
const LibC = struct {
|
|
pub fn lookup(this: *DNSResolver, query_init: GetAddrInfo, globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
|
const key = GetAddrInfoRequest.PendingCacheKey.init(query_init);
|
|
|
|
var cache = this.getOrPutIntoPendingCache(key, .pending_host_cache_native);
|
|
if (cache == .inflight) {
|
|
var dns_lookup = DNSLookup.init(globalThis, globalThis.allocator()) catch unreachable;
|
|
|
|
cache.inflight.append(dns_lookup);
|
|
|
|
return dns_lookup.promise.value();
|
|
}
|
|
|
|
var query = query_init.clone();
|
|
|
|
var request = GetAddrInfoRequest.init(
|
|
cache,
|
|
.{
|
|
.libc = .{
|
|
.query = query,
|
|
},
|
|
},
|
|
this,
|
|
query,
|
|
globalThis,
|
|
"pending_host_cache_native",
|
|
) catch unreachable;
|
|
const promise_value = request.head.promise.value();
|
|
|
|
var io = GetAddrInfoRequest.Task.createOnJSThread(this.vm.allocator, globalThis, request) catch unreachable;
|
|
|
|
io.schedule();
|
|
|
|
return promise_value;
|
|
}
|
|
};
|
|
|
|
pub fn addressToString(
|
|
allocator: std.mem.Allocator,
|
|
address: std.net.Address,
|
|
) JSC.ZigString {
|
|
const str: []const u8 = brk: {
|
|
switch (address.any.family) {
|
|
std.os.AF.INET => {
|
|
var self = address.in;
|
|
const bytes = @ptrCast(*const [4]u8, &self.sa.addr);
|
|
break :brk std.fmt.allocPrint(allocator, "{}.{}.{}.{}", .{
|
|
bytes[0],
|
|
bytes[1],
|
|
bytes[2],
|
|
bytes[3],
|
|
}) catch unreachable;
|
|
},
|
|
std.os.AF.INET6 => {
|
|
var out = std.fmt.allocPrint(allocator, "{any}", .{address}) catch unreachable;
|
|
// TODO: this is a hack, fix it
|
|
// This removes [.*]:port
|
|
// ^ ^^^^^^
|
|
break :brk out[1 .. out.len - 1 - std.fmt.count("{d}", .{address.in6.getPort()}) - 1];
|
|
},
|
|
std.os.AF.UNIX => {
|
|
break :brk std.mem.sliceTo(&address.un.path, 0);
|
|
},
|
|
else => break :brk "",
|
|
}
|
|
};
|
|
|
|
return JSC.ZigString.init(str);
|
|
}
|
|
|
|
pub fn normalizeDNSName(name: []const u8, backend: *GetAddrInfo.Backend) []const u8 {
|
|
if (backend.* == .c_ares) {
|
|
// https://github.com/c-ares/c-ares/issues/477
|
|
if (strings.endsWithComptime(name, ".localhost")) {
|
|
backend.* = .system;
|
|
return "localhost";
|
|
} else if (strings.endsWithComptime(name, ".local")) {
|
|
backend.* = .system;
|
|
// https://github.com/c-ares/c-ares/pull/463
|
|
} else if (strings.isIPV6Address(name)) {
|
|
backend.* = .system;
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
pub fn addressToJS(
|
|
allocator: std.mem.Allocator,
|
|
address: std.net.Address,
|
|
globalThis: *JSC.JSGlobalObject,
|
|
) JSC.JSValue {
|
|
return addressToString(allocator, address).toValueGC(globalThis);
|
|
}
|
|
|
|
fn addrInfoCount(addrinfo: *std.c.addrinfo) u32 {
|
|
var count: u32 = 1;
|
|
var current: ?*std.c.addrinfo = addrinfo.next;
|
|
while (current != null) : (current = current.?.next) {
|
|
count += @boolToInt(current.?.addr != null);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
pub fn addrInfoToJSArray(
|
|
parent_allocator: std.mem.Allocator,
|
|
addr_info: *std.c.addrinfo,
|
|
globalThis: *JSC.JSGlobalObject,
|
|
) JSC.JSValue {
|
|
var stack = std.heap.stackFallback(2048, parent_allocator);
|
|
var arena = std.heap.ArenaAllocator.init(stack.get());
|
|
const array = JSC.JSValue.createEmptyArray(
|
|
globalThis,
|
|
addrInfoCount(addr_info),
|
|
);
|
|
|
|
{
|
|
defer arena.deinit();
|
|
|
|
var allocator = arena.allocator();
|
|
var j: u32 = 0;
|
|
var current: ?*std.c.addrinfo = addr_info;
|
|
while (current) |this_node| : (current = current.?.next) {
|
|
array.putIndex(
|
|
globalThis,
|
|
j,
|
|
bun.JSC.DNS.GetAddrInfo.Result.toJS(
|
|
&(bun.JSC.DNS.GetAddrInfo.Result.fromAddrInfo(this_node) orelse continue),
|
|
globalThis,
|
|
allocator,
|
|
),
|
|
);
|
|
j += 1;
|
|
}
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
pub const GetAddrInfo = struct {
|
|
name: []const u8 = "",
|
|
port: u16 = 0,
|
|
options: Options = Options{},
|
|
|
|
pub fn clone(this: GetAddrInfo) GetAddrInfo {
|
|
return GetAddrInfo{
|
|
.name = bun.default_allocator.dupe(u8, this.name) catch unreachable,
|
|
.port = this.port,
|
|
.options = this.options,
|
|
};
|
|
}
|
|
|
|
pub fn toCAres(this: GetAddrInfo) bun.c_ares.AddrInfo_hints {
|
|
var hints: bun.c_ares.AddrInfo_hints = undefined;
|
|
@memset(std.mem.asBytes(&hints), 0, @sizeOf(bun.c_ares.AddrInfo_hints));
|
|
|
|
hints.ai_family = this.options.family.toLibC();
|
|
hints.ai_socktype = this.options.socktype.toLibC();
|
|
hints.ai_protocol = this.options.protocol.toLibC();
|
|
hints.ai_flags = this.options.flags;
|
|
|
|
return hints;
|
|
}
|
|
|
|
pub fn hash(self: GetAddrInfo) u64 {
|
|
var hasher = std.hash.Wyhash.init(0);
|
|
const bytes =
|
|
std.mem.asBytes(&self.port) ++
|
|
std.mem.asBytes(&self.options);
|
|
|
|
hasher.update(bytes);
|
|
hasher.update(self.name);
|
|
|
|
return hasher.final();
|
|
}
|
|
|
|
pub const Options = packed struct {
|
|
family: Family = .unspecified,
|
|
socktype: SocketType = .unspecified,
|
|
protocol: Protocol = .unspecified,
|
|
backend: Backend = Backend.default,
|
|
flags: i32 = 0,
|
|
|
|
pub fn toLibC(this: Options) ?std.c.addrinfo {
|
|
if (this.family == .unspecified and this.socktype == .unspecified and this.protocol == .unspecified and this.flags == 0) {
|
|
return null;
|
|
}
|
|
|
|
var hints: std.c.addrinfo = undefined;
|
|
@memset(std.mem.asBytes(&hints), 0, @sizeOf(std.c.addrinfo));
|
|
|
|
hints.family = this.family.toLibC();
|
|
hints.socktype = this.socktype.toLibC();
|
|
hints.protocol = this.protocol.toLibC();
|
|
hints.flags = this.flags;
|
|
return hints;
|
|
}
|
|
|
|
pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Options {
|
|
if (value.isEmptyOrUndefinedOrNull())
|
|
return Options{};
|
|
|
|
if (value.isObject()) {
|
|
var options = Options{};
|
|
|
|
if (value.get(globalObject, "family")) |family| {
|
|
options.family = try Family.fromJS(family, globalObject);
|
|
}
|
|
|
|
if (value.get(globalObject, "socketType") orelse value.get(globalObject, "socktype")) |socktype| {
|
|
options.socktype = try SocketType.fromJS(socktype, globalObject);
|
|
}
|
|
|
|
if (value.get(globalObject, "protocol")) |protocol| {
|
|
options.protocol = try Protocol.fromJS(protocol, globalObject);
|
|
}
|
|
|
|
if (value.get(globalObject, "backend")) |backend| {
|
|
options.backend = try Backend.fromJS(backend, globalObject);
|
|
}
|
|
|
|
if (value.get(globalObject, "flags")) |flags| {
|
|
if (!flags.isNumber())
|
|
return error.InvalidFlags;
|
|
|
|
options.flags = flags.coerce(i32, globalObject);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
return error.InvalidOptions;
|
|
}
|
|
};
|
|
|
|
pub const Family = enum(u2) {
|
|
unspecified,
|
|
inet,
|
|
inet6,
|
|
unix,
|
|
|
|
pub const map = bun.ComptimeStringMap(Family, .{
|
|
.{ "IPv4", Family.inet },
|
|
.{ "IPv6", Family.inet6 },
|
|
.{ "ipv4", Family.inet },
|
|
.{ "ipv6", Family.inet6 },
|
|
.{ "any", Family.unspecified },
|
|
});
|
|
|
|
pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Family {
|
|
if (value.isEmptyOrUndefinedOrNull())
|
|
return .unspecified;
|
|
|
|
if (value.isNumber()) {
|
|
return switch (value.to(i32)) {
|
|
0 => .unspecified,
|
|
4 => .inet,
|
|
6 => .inet6,
|
|
else => return error.InvalidFamily,
|
|
};
|
|
}
|
|
|
|
if (value.isString()) {
|
|
const str = value.getZigString(globalObject);
|
|
if (str.len == 0)
|
|
return .unspecified;
|
|
|
|
return map.getWithEql(str, JSC.ZigString.eqlComptime) orelse return error.InvalidFamily;
|
|
}
|
|
|
|
return error.InvalidFamily;
|
|
}
|
|
|
|
pub fn toLibC(this: Family) i32 {
|
|
return switch (this) {
|
|
.unspecified => 0,
|
|
.inet => std.os.AF.INET,
|
|
.inet6 => std.os.AF.INET6,
|
|
.unix => std.os.AF.UNIX,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const SocketType = enum(u2) {
|
|
unspecified,
|
|
stream,
|
|
dgram,
|
|
|
|
const map = bun.ComptimeStringMap(SocketType, .{
|
|
.{ "stream", SocketType.stream },
|
|
.{ "dgram", SocketType.dgram },
|
|
.{ "tcp", SocketType.stream },
|
|
.{ "udp", SocketType.dgram },
|
|
});
|
|
|
|
pub fn toLibC(this: SocketType) i32 {
|
|
switch (this) {
|
|
.unspecified => return 0,
|
|
.stream => return std.os.SOCK.STREAM,
|
|
.dgram => return std.os.SOCK.DGRAM,
|
|
}
|
|
}
|
|
|
|
pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !SocketType {
|
|
if (value.isEmptyOrUndefinedOrNull())
|
|
return .unspecified;
|
|
|
|
if (value.isNumber()) {
|
|
return switch (value.to(i32)) {
|
|
0 => .unspecified,
|
|
1 => .stream,
|
|
2 => .dgram,
|
|
else => return error.InvalidSocketType,
|
|
};
|
|
}
|
|
|
|
if (value.isString()) {
|
|
const str = value.getZigString(globalObject);
|
|
if (str.len == 0)
|
|
return .unspecified;
|
|
|
|
return map.getWithEql(str, JSC.ZigString.eqlComptime) orelse return error.InvalidSocketType;
|
|
}
|
|
|
|
return error.InvalidSocketType;
|
|
}
|
|
};
|
|
|
|
pub const Protocol = enum(u2) {
|
|
unspecified,
|
|
tcp,
|
|
udp,
|
|
|
|
const map = bun.ComptimeStringMap(Protocol, .{
|
|
.{ "tcp", Protocol.tcp },
|
|
.{ "udp", Protocol.udp },
|
|
});
|
|
|
|
pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Protocol {
|
|
if (value.isEmptyOrUndefinedOrNull())
|
|
return .unspecified;
|
|
|
|
if (value.isNumber()) {
|
|
return switch (value.to(i32)) {
|
|
0 => .unspecified,
|
|
6 => .tcp,
|
|
17 => .udp,
|
|
else => return error.InvalidProtocol,
|
|
};
|
|
}
|
|
|
|
if (value.isString()) {
|
|
const str = value.getZigString(globalObject);
|
|
if (str.len == 0)
|
|
return .unspecified;
|
|
|
|
return map.getWithEql(str, JSC.ZigString.eqlComptime) orelse return error.InvalidProtocol;
|
|
}
|
|
|
|
return error.InvalidProtocol;
|
|
}
|
|
|
|
pub fn toLibC(this: Protocol) i32 {
|
|
switch (this) {
|
|
.unspecified => return 0,
|
|
.tcp => return std.os.IPPROTO.TCP,
|
|
.udp => return std.os.IPPROTO.UDP,
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const Backend = enum(u2) {
|
|
c_ares,
|
|
system,
|
|
libc,
|
|
|
|
pub const label = bun.ComptimeStringMap(GetAddrInfo.Backend, .{
|
|
.{ "c-ares", .c_ares },
|
|
.{ "c_ares", .c_ares },
|
|
.{ "cares", .c_ares },
|
|
.{ "async", .c_ares },
|
|
.{ "libc", .libc },
|
|
.{ "system", .system },
|
|
.{ "getaddrinfo", .libc },
|
|
});
|
|
|
|
pub const default: GetAddrInfo.Backend = if (Environment.isMac)
|
|
GetAddrInfo.Backend.system
|
|
else
|
|
GetAddrInfo.Backend.c_ares;
|
|
|
|
pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Backend {
|
|
if (value.isEmptyOrUndefinedOrNull())
|
|
return default;
|
|
|
|
if (value.isString()) {
|
|
const str = value.getZigString(globalObject);
|
|
if (str.len == 0)
|
|
return default;
|
|
|
|
return label.getWithEql(str, JSC.ZigString.eqlComptime) orelse return error.InvalidBackend;
|
|
}
|
|
|
|
return error.InvalidBackend;
|
|
}
|
|
};
|
|
|
|
pub const Result = struct {
|
|
address: std.net.Address,
|
|
ttl: i32 = 0,
|
|
|
|
pub const List = std.ArrayList(Result);
|
|
|
|
pub const Any = union(enum) {
|
|
addrinfo: ?*std.c.addrinfo,
|
|
list: List,
|
|
|
|
pub fn toJS(this: Any, globalThis: *JSC.JSGlobalObject) ?JSC.JSValue {
|
|
return switch (this) {
|
|
.addrinfo => |addrinfo| addrInfoToJSArray(globalThis.allocator(), addrinfo orelse return null, globalThis),
|
|
.list => |list| brk: {
|
|
var stack = std.heap.stackFallback(2048, globalThis.allocator());
|
|
var arena = std.heap.ArenaAllocator.init(stack.get());
|
|
const array = JSC.JSValue.createEmptyArray(globalThis, @truncate(u32, list.items.len));
|
|
var i: u32 = 0;
|
|
const items: []const Result = list.items;
|
|
for (items) |item| {
|
|
array.putIndex(globalThis, i, item.toJS(globalThis, arena.allocator()));
|
|
i += 1;
|
|
}
|
|
break :brk array;
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn deinit(this: Any) void {
|
|
switch (this) {
|
|
.addrinfo => |addrinfo| {
|
|
if (addrinfo) |a| {
|
|
std.c.freeaddrinfo(a);
|
|
}
|
|
},
|
|
.list => |list| {
|
|
var list_ = list;
|
|
list_.deinit();
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn toList(allocator: std.mem.Allocator, addrinfo: *std.c.addrinfo) !List {
|
|
var list = try List.initCapacity(allocator, addrInfoCount(addrinfo));
|
|
|
|
var addr: ?*std.c.addrinfo = addrinfo;
|
|
while (addr) |a| : (addr = a.next) {
|
|
list.appendAssumeCapacity(fromAddrInfo(a) orelse continue);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
pub fn fromAddrInfo(addrinfo: *std.c.addrinfo) ?Result {
|
|
return Result{
|
|
.address = std.net.Address.initPosix(@alignCast(4, addrinfo.addr orelse return null)),
|
|
// no TTL in POSIX getaddrinfo()
|
|
.ttl = 0,
|
|
};
|
|
}
|
|
|
|
pub fn toJS(this: *const Result, globalThis: *JSC.JSGlobalObject, allocator: std.mem.Allocator) JSValue {
|
|
const obj = JSC.JSValue.createEmptyObject(globalThis, 3);
|
|
obj.put(globalThis, JSC.ZigString.static("address"), addressToJS(allocator, this.address, globalThis));
|
|
obj.put(globalThis, JSC.ZigString.static("family"), switch (this.address.any.family) {
|
|
std.os.AF.INET => JSValue.jsNumber(4),
|
|
std.os.AF.INET6 => JSValue.jsNumber(6),
|
|
else => JSValue.jsNumber(0),
|
|
});
|
|
obj.put(globalThis, JSC.ZigString.static("ttl"), JSValue.jsNumber(this.ttl));
|
|
return obj;
|
|
}
|
|
};
|
|
};
|
|
|
|
pub const GetAddrInfoRequest = struct {
|
|
const log = Output.scoped(.GetAddrInfoRequest, false);
|
|
|
|
backend: Backend = undefined,
|
|
resolver_for_caching: ?*DNSResolver = null,
|
|
hash: u64 = 0,
|
|
cache: CacheConfig = CacheConfig{},
|
|
head: DNSLookup,
|
|
tail: *DNSLookup = undefined,
|
|
task: bun.ThreadPool.Task = undefined,
|
|
|
|
pub fn init(
|
|
cache: DNSResolver.CacheHit,
|
|
backend: Backend,
|
|
resolver: ?*DNSResolver,
|
|
query: GetAddrInfo,
|
|
globalThis: *JSC.JSGlobalObject,
|
|
comptime cache_field: []const u8,
|
|
) !*GetAddrInfoRequest {
|
|
var request = try globalThis.allocator().create(GetAddrInfoRequest);
|
|
request.* = .{
|
|
.backend = backend,
|
|
.resolver_for_caching = resolver,
|
|
.hash = query.hash(),
|
|
.head = .{
|
|
.globalThis = globalThis,
|
|
.promise = JSC.JSPromise.Strong.init(globalThis),
|
|
.allocated = false,
|
|
},
|
|
};
|
|
request.tail = &request.head;
|
|
if (cache == .new) {
|
|
request.resolver_for_caching = resolver;
|
|
request.cache = CacheConfig{
|
|
.pending_cache = true,
|
|
.entry_cache = false,
|
|
.pos_in_pending = @truncate(u5, @field(resolver.?, cache_field).indexOf(cache.new).?),
|
|
.name_len = @truncate(u9, query.name.len),
|
|
};
|
|
cache.new.lookup = request;
|
|
}
|
|
return request;
|
|
}
|
|
|
|
pub const Task = bun.JSC.WorkTask(GetAddrInfoRequest, false);
|
|
|
|
pub const CacheConfig = packed struct(u16) {
|
|
pending_cache: bool = false,
|
|
entry_cache: bool = false,
|
|
pos_in_pending: u5 = 0,
|
|
name_len: u9 = 0,
|
|
};
|
|
|
|
pub const PendingCacheKey = struct {
|
|
hash: u64,
|
|
len: u16,
|
|
lookup: *GetAddrInfoRequest = undefined,
|
|
|
|
pub fn append(this: *PendingCacheKey, dns_lookup: *DNSLookup) void {
|
|
var tail = this.lookup.tail;
|
|
tail.next = dns_lookup;
|
|
this.lookup.tail = dns_lookup;
|
|
}
|
|
|
|
pub fn init(query: GetAddrInfo) PendingCacheKey {
|
|
return PendingCacheKey{
|
|
.hash = query.hash(),
|
|
.len = @truncate(u16, query.name.len),
|
|
.lookup = undefined,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn getAddrInfoAsyncCallback(
|
|
status: i32,
|
|
addr_info: ?*std.c.addrinfo,
|
|
arg: ?*anyopaque,
|
|
) callconv(.C) void {
|
|
const this = @intToPtr(*GetAddrInfoRequest, @ptrToInt(arg));
|
|
log("getAddrInfoAsyncCallback: status={d}", .{status});
|
|
|
|
if (this.backend == .libinfo) {
|
|
if (this.backend.libinfo.file_poll) |poll| poll.deinit();
|
|
}
|
|
|
|
if (this.resolver_for_caching) |resolver| {
|
|
if (this.cache.pending_cache) {
|
|
resolver.drainPendingHostNative(this.cache.pos_in_pending, this.head.globalThis, status, .{ .addrinfo = addr_info });
|
|
return;
|
|
}
|
|
}
|
|
|
|
var head = this.head;
|
|
bun.default_allocator.destroy(this);
|
|
head.processGetAddrInfoNative(status, addr_info);
|
|
}
|
|
|
|
pub const Backend = union(enum) {
|
|
c_ares: void,
|
|
libinfo: GetAddrInfoRequest.Backend.LibInfo,
|
|
libc: union(enum) {
|
|
success: GetAddrInfo.Result.List,
|
|
err: i32,
|
|
query: GetAddrInfo,
|
|
|
|
pub fn run(this: *@This()) void {
|
|
const query = this.query;
|
|
defer bun.default_allocator.free(bun.constStrToU8(query.name));
|
|
var hints = query.options.toLibC();
|
|
var port_buf: [128]u8 = undefined;
|
|
var port = std.fmt.bufPrintIntToSlice(&port_buf, query.port, 10, .lower, .{});
|
|
port_buf[port.len] = 0;
|
|
var portZ = port_buf[0..port.len :0];
|
|
var hostname: [bun.MAX_PATH_BYTES]u8 = undefined;
|
|
_ = strings.copy(hostname[0..], query.name);
|
|
hostname[query.name.len] = 0;
|
|
var addrinfo: *std.c.addrinfo = undefined;
|
|
var host = hostname[0..query.name.len :0];
|
|
const debug_timer = bun.Output.DebugTimer.start();
|
|
const err = std.c.getaddrinfo(
|
|
host.ptr,
|
|
if (port.len > 0) portZ.ptr else null,
|
|
if (hints) |*hint| hint else null,
|
|
&addrinfo,
|
|
);
|
|
JSC.Node.Syscall.syslog("getaddrinfo({s}, {d}) = {d} ({any})", .{
|
|
query.name,
|
|
port,
|
|
err,
|
|
debug_timer,
|
|
});
|
|
if (@enumToInt(err) != 0) {
|
|
this.* = .{ .err = @enumToInt(err) };
|
|
return;
|
|
}
|
|
|
|
// do not free addrinfo when err != 0
|
|
// https://github.com/ziglang/zig/pull/14242
|
|
defer std.c.freeaddrinfo(addrinfo);
|
|
|
|
this.* = .{ .success = GetAddrInfo.Result.toList(default_allocator, addrinfo) catch unreachable };
|
|
}
|
|
},
|
|
|
|
pub const LibInfo = struct {
|
|
file_poll: ?*bun.JSC.FilePoll = null,
|
|
machport: ?*anyopaque = null,
|
|
|
|
extern fn getaddrinfo_send_reply(*anyopaque, *const JSC.DNS.LibInfo.GetaddrinfoAsyncHandleReply) bool;
|
|
pub fn onMachportChange(this: *GetAddrInfoRequest) void {
|
|
if (comptime !Environment.isMac)
|
|
unreachable;
|
|
|
|
if (!getaddrinfo_send_reply(this.backend.libinfo.machport.?, JSC.DNS.LibInfo.getaddrinfo_async_handle_reply().?)) {
|
|
log("onMachportChange: getaddrinfo_send_reply failed", .{});
|
|
getAddrInfoAsyncCallback(-1, null, this);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
pub const onMachportChange = Backend.LibInfo.onMachportChange;
|
|
|
|
pub fn run(this: *GetAddrInfoRequest, task: *Task) void {
|
|
this.backend.libc.run();
|
|
task.onFinish();
|
|
}
|
|
|
|
pub fn then(this: *GetAddrInfoRequest, _: *JSC.JSGlobalObject) void {
|
|
switch (this.backend.libc) {
|
|
.success => |result| {
|
|
const any = GetAddrInfo.Result.Any{ .list = result };
|
|
defer any.deinit();
|
|
if (this.resolver_for_caching) |resolver| {
|
|
// if (this.cache.entry_cache and result != null and result.?.node != null) {
|
|
// resolver.putEntryInCache(this.hash, this.cache.name_len, result.?);
|
|
// }
|
|
|
|
if (this.cache.pending_cache) {
|
|
resolver.drainPendingHostNative(this.cache.pos_in_pending, this.head.globalThis, 0, any);
|
|
return;
|
|
}
|
|
}
|
|
var head = this.head;
|
|
bun.default_allocator.destroy(this);
|
|
head.onCompleteNative(any);
|
|
},
|
|
.err => |err| {
|
|
getAddrInfoAsyncCallback(err, null, this);
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
pub fn onCaresComplete(this: *GetAddrInfoRequest, err_: ?c_ares.Error, timeout: i32, result: ?*c_ares.AddrInfo) void {
|
|
if (this.resolver_for_caching) |resolver| {
|
|
// if (this.cache.entry_cache and result != null and result.?.node != null) {
|
|
// resolver.putEntryInCache(this.hash, this.cache.name_len, result.?);
|
|
// }
|
|
|
|
if (this.cache.pending_cache) {
|
|
resolver.drainPendingHostCares(
|
|
this.cache.pos_in_pending,
|
|
err_,
|
|
timeout,
|
|
result,
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var head = this.head;
|
|
bun.default_allocator.destroy(this);
|
|
|
|
head.processGetAddrInfo(err_, timeout, result);
|
|
}
|
|
};
|
|
|
|
pub const DNSLookup = struct {
|
|
const log = Output.scoped(.DNSLookup, true);
|
|
|
|
globalThis: *JSC.JSGlobalObject = undefined,
|
|
promise: JSC.JSPromise.Strong,
|
|
allocated: bool = false,
|
|
next: ?*DNSLookup = null,
|
|
|
|
pub fn init(globalThis: *JSC.JSGlobalObject, allocator: std.mem.Allocator) !*DNSLookup {
|
|
var this = try allocator.create(DNSLookup);
|
|
this.* = .{
|
|
.globalThis = globalThis,
|
|
.promise = JSC.JSPromise.Strong.init(globalThis),
|
|
.allocated = true,
|
|
};
|
|
return this;
|
|
}
|
|
|
|
pub fn onCompleteNative(this: *DNSLookup, result: GetAddrInfo.Result.Any) void {
|
|
const array = result.toJS(this.globalThis).?;
|
|
this.onCompleteWithArray(array);
|
|
}
|
|
|
|
pub fn processGetAddrInfoNative(this: *DNSLookup, status: i32, result: ?*std.c.addrinfo) void {
|
|
if (c_ares.Error.initEAI(status)) |err| {
|
|
var promise = this.promise;
|
|
var globalThis = this.globalThis;
|
|
|
|
const error_value = brk: {
|
|
if (err == .ESERVFAIL) {
|
|
break :brk JSC.Node.Syscall.Error.fromCode(std.c.getErrno(-1), .getaddrinfo).toJSC(globalThis);
|
|
}
|
|
const error_value = globalThis.createErrorInstance("DNS lookup failed: {s}", .{err.label()});
|
|
error_value.put(
|
|
globalThis,
|
|
JSC.ZigString.static("code"),
|
|
JSC.ZigString.init(err.code()).toValueGC(globalThis),
|
|
);
|
|
break :brk error_value;
|
|
};
|
|
|
|
this.deinit();
|
|
|
|
promise.reject(globalThis, error_value);
|
|
return;
|
|
}
|
|
|
|
onCompleteNative(this, .{ .addrinfo = result });
|
|
}
|
|
|
|
pub fn processGetAddrInfo(this: *DNSLookup, err_: ?c_ares.Error, _: i32, result: ?*c_ares.AddrInfo) void {
|
|
if (err_) |err| {
|
|
var promise = this.promise;
|
|
var globalThis = this.globalThis;
|
|
const error_value = globalThis.createErrorInstance("DNS lookup failed: {s}", .{err.label()});
|
|
error_value.put(
|
|
globalThis,
|
|
JSC.ZigString.static("code"),
|
|
JSC.ZigString.init(err.code()).toValueGC(globalThis),
|
|
);
|
|
|
|
this.deinit();
|
|
|
|
promise.reject(globalThis, error_value);
|
|
return;
|
|
}
|
|
|
|
if (result == null or result.?.node == null) {
|
|
var promise = this.promise;
|
|
var globalThis = this.globalThis;
|
|
const error_value = globalThis.createErrorInstance("DNS lookup failed: {s}", .{"No results"});
|
|
error_value.put(
|
|
globalThis,
|
|
JSC.ZigString.static("code"),
|
|
JSC.ZigString.init("EUNREACHABLE").toValueGC(globalThis),
|
|
);
|
|
|
|
this.deinit();
|
|
promise.reject(globalThis, error_value);
|
|
return;
|
|
}
|
|
|
|
this.onComplete(result.?);
|
|
}
|
|
|
|
pub fn onComplete(this: *DNSLookup, result: *c_ares.AddrInfo) void {
|
|
const array = result.toJSArray(this.globalThis.allocator(), this.globalThis);
|
|
this.onCompleteWithArray(array);
|
|
}
|
|
|
|
pub fn onCompleteWithArray(this: *DNSLookup, result: JSC.JSValue) void {
|
|
var promise = this.promise;
|
|
var globalThis = this.globalThis;
|
|
this.promise = .{};
|
|
|
|
this.deinit();
|
|
promise.resolveOnNextTick(globalThis, result);
|
|
}
|
|
|
|
pub fn deinit(this: *DNSLookup) void {
|
|
if (this.allocated)
|
|
this.globalThis.allocator().destroy(this);
|
|
}
|
|
};
|
|
|
|
pub const GlobalData = struct {
|
|
resolver: DNSResolver,
|
|
|
|
pub fn init(allocator: std.mem.Allocator, vm: *JSC.VirtualMachine) *GlobalData {
|
|
var global = allocator.create(GlobalData) catch unreachable;
|
|
global.* = .{
|
|
.resolver = .{
|
|
.vm = vm,
|
|
.polls = std.AutoArrayHashMap(i32, ?*JSC.FilePoll).init(allocator),
|
|
},
|
|
};
|
|
|
|
return global;
|
|
}
|
|
};
|
|
|
|
pub const DNSResolver = struct {
|
|
const log = Output.scoped(.DNSResolver, true);
|
|
|
|
channel: ?*c_ares.Channel = null,
|
|
vm: *JSC.VirtualMachine,
|
|
polls: std.AutoArrayHashMap(i32, ?*JSC.FilePoll) = undefined,
|
|
|
|
pending_host_cache_cares: PendingCache = PendingCache.init(),
|
|
pending_host_cache_native: PendingCache = PendingCache.init(),
|
|
// entry_host_cache: std.BoundedArray(128)
|
|
|
|
const PendingCache = bun.HiveArray(GetAddrInfoRequest.PendingCacheKey, 32);
|
|
|
|
fn getKey(this: *DNSResolver, index: u8, comptime cache_name: []const u8) GetAddrInfoRequest.PendingCacheKey {
|
|
var cache: *PendingCache = &@field(this, cache_name);
|
|
std.debug.assert(!cache.available.isSet(index));
|
|
const entry = cache.buffer[index];
|
|
cache.buffer[index] = undefined;
|
|
|
|
var available = cache.available;
|
|
available.set(index);
|
|
cache.available = available;
|
|
|
|
return entry;
|
|
}
|
|
|
|
pub fn drainPendingHostCares(this: *DNSResolver, index: u8, err: ?c_ares.Error, timeout: i32, result: ?*c_ares.AddrInfo) void {
|
|
const key = this.getKey(index, "pending_host_cache_cares");
|
|
|
|
var addr = result orelse {
|
|
var pending: ?*DNSLookup = key.lookup.head.next;
|
|
key.lookup.head.processGetAddrInfo(err, timeout, null);
|
|
bun.default_allocator.destroy(key.lookup);
|
|
|
|
while (pending) |value| {
|
|
pending = value.next;
|
|
value.processGetAddrInfo(err, timeout, null);
|
|
}
|
|
return;
|
|
};
|
|
|
|
var pending: ?*DNSLookup = key.lookup.head.next;
|
|
var prev_global = key.lookup.head.globalThis;
|
|
var array = addr.toJSArray(this.vm.allocator, prev_global);
|
|
defer addr.deinit();
|
|
array.ensureStillAlive();
|
|
key.lookup.head.onCompleteWithArray(array);
|
|
bun.default_allocator.destroy(key.lookup);
|
|
|
|
array.ensureStillAlive();
|
|
// std.c.addrinfo
|
|
|
|
while (pending) |value| {
|
|
var new_global = value.globalThis;
|
|
if (prev_global != new_global) {
|
|
array = addr.toJSArray(this.vm.allocator, new_global);
|
|
prev_global = new_global;
|
|
}
|
|
pending = value.next;
|
|
|
|
{
|
|
array.ensureStillAlive();
|
|
value.onCompleteWithArray(array);
|
|
array.ensureStillAlive();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn drainPendingHostNative(this: *DNSResolver, index: u8, globalObject: *JSC.JSGlobalObject, err: i32, result: GetAddrInfo.Result.Any) void {
|
|
const key = this.getKey(index, "pending_host_cache_native");
|
|
|
|
var array = result.toJS(globalObject) orelse {
|
|
var pending: ?*DNSLookup = key.lookup.head.next;
|
|
var head = key.lookup.head;
|
|
head.processGetAddrInfoNative(err, null);
|
|
bun.default_allocator.destroy(key.lookup);
|
|
|
|
while (pending) |value| {
|
|
pending = value.next;
|
|
value.processGetAddrInfoNative(err, null);
|
|
}
|
|
|
|
return;
|
|
};
|
|
var pending: ?*DNSLookup = key.lookup.head.next;
|
|
var prev_global = key.lookup.head.globalThis;
|
|
|
|
{
|
|
array.ensureStillAlive();
|
|
key.lookup.head.onCompleteWithArray(array);
|
|
bun.default_allocator.destroy(key.lookup);
|
|
array.ensureStillAlive();
|
|
}
|
|
|
|
// std.c.addrinfo
|
|
|
|
while (pending) |value| {
|
|
var new_global = value.globalThis;
|
|
pending = value.next;
|
|
if (prev_global != new_global) {
|
|
array = result.toJS(new_global).?;
|
|
prev_global = new_global;
|
|
}
|
|
|
|
{
|
|
array.ensureStillAlive();
|
|
value.onCompleteWithArray(array);
|
|
array.ensureStillAlive();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub const CacheHit = union(enum) {
|
|
inflight: *GetAddrInfoRequest.PendingCacheKey,
|
|
new: *GetAddrInfoRequest.PendingCacheKey,
|
|
disabled: void,
|
|
};
|
|
|
|
pub fn getOrPutIntoPendingCache(
|
|
this: *DNSResolver,
|
|
key: GetAddrInfoRequest.PendingCacheKey,
|
|
comptime field: std.meta.FieldEnum(DNSResolver),
|
|
) CacheHit {
|
|
var cache: *PendingCache = &@field(this, @tagName(field));
|
|
var inflight_iter = cache.available.iterator(
|
|
.{
|
|
.kind = .unset,
|
|
},
|
|
);
|
|
|
|
while (inflight_iter.next()) |index| {
|
|
var entry: *GetAddrInfoRequest.PendingCacheKey = &cache.buffer[index];
|
|
if (entry.hash == key.hash and entry.len == key.len) {
|
|
return .{ .inflight = entry };
|
|
}
|
|
}
|
|
|
|
if (cache.get()) |new| {
|
|
new.hash = key.hash;
|
|
new.len = key.len;
|
|
return .{ .new = new };
|
|
}
|
|
|
|
return .{ .disabled = {} };
|
|
}
|
|
|
|
pub const ChannelResult = union(enum) {
|
|
err: c_ares.Error,
|
|
result: *c_ares.Channel,
|
|
};
|
|
pub fn getChannel(this: *DNSResolver) ChannelResult {
|
|
if (this.channel == null) {
|
|
if (c_ares.Channel.init(DNSResolver, this)) |err| {
|
|
return .{ .err = err };
|
|
}
|
|
}
|
|
|
|
return .{ .result = this.channel.? };
|
|
}
|
|
|
|
pub fn onDNSPoll(
|
|
this: *DNSResolver,
|
|
poll: *JSC.FilePoll,
|
|
) void {
|
|
var channel = this.channel orelse {
|
|
_ = this.polls.orderedRemove(@intCast(i32, poll.fd));
|
|
poll.deinit();
|
|
return;
|
|
};
|
|
|
|
channel.process(
|
|
@intCast(i32, poll.fd),
|
|
poll.isReadable(),
|
|
poll.isWritable(),
|
|
);
|
|
}
|
|
|
|
pub fn onDNSSocketState(
|
|
this: *DNSResolver,
|
|
fd: i32,
|
|
readable: bool,
|
|
writable: bool,
|
|
) void {
|
|
var vm = this.vm;
|
|
|
|
if (!readable and !writable) {
|
|
// read == 0 and write == 0 this is c-ares's way of notifying us that
|
|
// the socket is now closed. We must free the data associated with
|
|
// socket.
|
|
if (this.polls.fetchOrderedRemove(fd)) |entry| {
|
|
if (entry.value) |val| {
|
|
val.deinitWithVM(vm);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var poll_entry = this.polls.getOrPut(fd) catch unreachable;
|
|
|
|
if (!poll_entry.found_existing) {
|
|
poll_entry.value_ptr.* = JSC.FilePoll.init(vm, fd, .{}, DNSResolver, this);
|
|
}
|
|
|
|
var poll = poll_entry.value_ptr.*.?;
|
|
|
|
if (readable and !poll.flags.contains(.poll_readable))
|
|
_ = poll.register(vm.uws_event_loop.?, .readable, false);
|
|
|
|
if (writable and !poll.flags.contains(.poll_writable))
|
|
_ = poll.register(vm.uws_event_loop.?, .writable, false);
|
|
}
|
|
|
|
const DNSQuery = struct {
|
|
name: JSC.ZigString.Slice,
|
|
record_type: RecordType,
|
|
|
|
ttl: i32 = 0,
|
|
};
|
|
|
|
pub const RecordType = enum(u8) {
|
|
A = 1,
|
|
AAAA = 28,
|
|
CNAME = 5,
|
|
MX = 15,
|
|
NS = 2,
|
|
PTR = 12,
|
|
SOA = 6,
|
|
SRV = 33,
|
|
TXT = 16,
|
|
|
|
pub const default = RecordType.A;
|
|
|
|
pub const map = bun.ComptimeStringMap(RecordType, .{
|
|
.{ "A", .A },
|
|
.{ "AAAA", .AAAA },
|
|
.{ "CNAME", .CNAME },
|
|
.{ "MX", .MX },
|
|
.{ "NS", .NS },
|
|
.{ "PTR", .PTR },
|
|
.{ "SOA", .SOA },
|
|
.{ "SRV", .SRV },
|
|
.{ "TXT", .TXT },
|
|
.{ "a", .A },
|
|
.{ "aaaa", .AAAA },
|
|
.{ "cname", .CNAME },
|
|
.{ "mx", .MX },
|
|
.{ "ns", .NS },
|
|
.{ "ptr", .PTR },
|
|
.{ "soa", .SOA },
|
|
.{ "srv", .SRV },
|
|
.{ "txt", .TXT },
|
|
});
|
|
};
|
|
|
|
pub fn resolve(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
|
const arguments = callframe.arguments(3);
|
|
if (arguments.len < 1) {
|
|
globalThis.throwNotEnoughArguments("resolve", 2, arguments.len);
|
|
return .zero;
|
|
}
|
|
|
|
const record_type: RecordType = if (arguments.len == 1)
|
|
RecordType.default
|
|
else brk: {
|
|
const record_type_value = arguments.ptr[1];
|
|
if (record_type_value.isEmptyOrUndefinedOrNull() or !record_type_value.isString()) {
|
|
break :brk RecordType.default;
|
|
}
|
|
|
|
const record_type_str = record_type_value.toStringOrNull(globalThis) orelse {
|
|
return .zero;
|
|
};
|
|
|
|
if (record_type_str.length() == 0) {
|
|
break :brk RecordType.default;
|
|
}
|
|
|
|
break :brk RecordType.map.getWithEql(record_type_str.getZigString(globalThis), JSC.ZigString.eqlComptime) orelse {
|
|
globalThis.throwInvalidArgumentType("resolve", "record", "one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SRV, TXT");
|
|
return .zero;
|
|
};
|
|
};
|
|
_ = record_type;
|
|
|
|
const name_value = arguments.ptr[0];
|
|
|
|
if (name_value.isEmptyOrUndefinedOrNull() or !name_value.isString()) {
|
|
globalThis.throwInvalidArgumentType("resolve", "name", "string");
|
|
return .zero;
|
|
}
|
|
|
|
const name_str = name_value.toStringOrNull(globalThis) orelse {
|
|
return .zero;
|
|
};
|
|
|
|
if (name_str.length() == 0) {
|
|
globalThis.throwInvalidArgumentType("resolve", "name", "non-empty string");
|
|
return .zero;
|
|
}
|
|
|
|
// const name = name_str.toSliceZ(globalThis).cloneZ(bun.default_allocator) catch unreachable;
|
|
// TODO:
|
|
return JSC.JSValue.jsUndefined();
|
|
}
|
|
// pub fn reverse(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
|
// const arguments = callframe.arguments(3);
|
|
|
|
// }
|
|
|
|
pub fn lookup(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
|
const arguments = callframe.arguments(2);
|
|
if (arguments.len < 1) {
|
|
globalThis.throwNotEnoughArguments("lookup", 2, arguments.len);
|
|
return .zero;
|
|
}
|
|
|
|
const name_value = arguments.ptr[0];
|
|
|
|
if (name_value.isEmptyOrUndefinedOrNull() or !name_value.isString()) {
|
|
globalThis.throwInvalidArgumentType("lookup", "hostname", "string");
|
|
return .zero;
|
|
}
|
|
|
|
const name_str = name_value.toStringOrNull(globalThis) orelse {
|
|
return .zero;
|
|
};
|
|
|
|
if (name_str.length() == 0) {
|
|
globalThis.throwInvalidArgumentType("lookup", "hostname", "non-empty string");
|
|
return .zero;
|
|
}
|
|
|
|
var options = GetAddrInfo.Options{};
|
|
var port: u16 = 0;
|
|
|
|
if (arguments.len > 1 and arguments.ptr[1].isCell()) {
|
|
if (arguments.ptr[1].get(globalThis, "port")) |port_value| {
|
|
if (port_value.isNumber()) {
|
|
port = port_value.to(u16);
|
|
}
|
|
}
|
|
|
|
options = GetAddrInfo.Options.fromJS(arguments.ptr[1], globalThis) catch |err| {
|
|
globalThis.throw("Invalid options passed to lookup(): {s}", .{@errorName(err)});
|
|
return .zero;
|
|
};
|
|
}
|
|
|
|
const name = name_str.toSlice(globalThis, bun.default_allocator);
|
|
defer name.deinit();
|
|
var vm = globalThis.bunVM();
|
|
var resolver = vm.rareData().globalDNSResolver(vm);
|
|
|
|
return resolver.doLookup(name.slice(), port, options, globalThis);
|
|
}
|
|
|
|
pub fn doLookup(this: *DNSResolver, name: []const u8, port: u16, options: GetAddrInfo.Options, globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
|
var opts = options;
|
|
var backend = opts.backend;
|
|
const normalized = normalizeDNSName(name, &backend);
|
|
opts.backend = backend;
|
|
const query = GetAddrInfo{
|
|
.options = opts,
|
|
.port = port,
|
|
.name = normalized,
|
|
};
|
|
return switch (opts.backend) {
|
|
.c_ares => this.c_aresLookupWithNormalizedName(query, globalThis),
|
|
.libc => LibC.lookup(this, query, globalThis),
|
|
.system => if (comptime Environment.isMac)
|
|
LibInfo.lookup(this, query, globalThis)
|
|
else
|
|
LibC.lookup(this, query, globalThis),
|
|
};
|
|
}
|
|
|
|
pub fn c_aresLookupWithNormalizedName(this: *DNSResolver, query: GetAddrInfo, globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
|
var channel: *c_ares.Channel = switch (this.getChannel()) {
|
|
.result => |res| res,
|
|
.err => |err| {
|
|
const system_error = JSC.SystemError{
|
|
.errno = -1,
|
|
.code = JSC.ZigString.init(err.code()),
|
|
.message = JSC.ZigString.init(err.label()),
|
|
};
|
|
|
|
globalThis.throwValue(system_error.toErrorInstance(globalThis));
|
|
return .zero;
|
|
},
|
|
};
|
|
|
|
const key = GetAddrInfoRequest.PendingCacheKey.init(query);
|
|
|
|
var cache = this.getOrPutIntoPendingCache(key, .pending_host_cache_cares);
|
|
if (cache == .inflight) {
|
|
var dns_lookup = DNSLookup.init(globalThis, globalThis.allocator()) catch unreachable;
|
|
cache.inflight.append(dns_lookup);
|
|
return dns_lookup.promise.value();
|
|
}
|
|
|
|
// var hints_buf = &[_]c_ares.AddrInfo_hints{query.toCAres()};
|
|
var request = GetAddrInfoRequest.init(
|
|
cache,
|
|
.{
|
|
.c_ares = {},
|
|
},
|
|
this,
|
|
query,
|
|
globalThis,
|
|
"pending_host_cache_cares",
|
|
) catch unreachable;
|
|
const promise = request.tail.promise.value();
|
|
|
|
channel.getAddrInfo(
|
|
query.name,
|
|
query.port,
|
|
&.{},
|
|
GetAddrInfoRequest,
|
|
request,
|
|
GetAddrInfoRequest.onCaresComplete,
|
|
);
|
|
|
|
return promise;
|
|
}
|
|
|
|
comptime {
|
|
@export(
|
|
lookup,
|
|
.{
|
|
.name = "Bun__DNSResolver__lookup",
|
|
},
|
|
);
|
|
}
|
|
// pub fn lookupService(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
|
// const arguments = callframe.arguments(3);
|
|
|
|
// }
|
|
// pub fn cancel(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
|
// const arguments = callframe.arguments(3);
|
|
|
|
// }
|
|
};
|