aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js')
-rw-r--r--src/bun.js/api/bun/dns_resolver.zig306
-rw-r--r--src/bun.js/bindings/BunObject.cpp3
2 files changed, 305 insertions, 4 deletions
diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig
index b2d209c95..eea516242 100644
--- a/src/bun.js/api/bun/dns_resolver.zig
+++ b/src/bun.js/api/bun/dns_resolver.zig
@@ -812,6 +812,170 @@ pub const GetHostByAddrInfoRequest = struct {
}
};
+pub const CAresNameInfo = struct {
+ const log = Output.scoped(@This(), true);
+
+ globalThis: *JSC.JSGlobalObject = undefined,
+ promise: JSC.JSPromise.Strong,
+ poll_ref: JSC.PollRef,
+ allocated: bool = false,
+ next: ?*@This() = null,
+ name: []const u8,
+
+ pub fn init(globalThis: *JSC.JSGlobalObject, allocator: std.mem.Allocator, name: []const u8) !*@This() {
+ var this = try allocator.create(@This());
+ var poll_ref = JSC.PollRef.init();
+ poll_ref.ref(globalThis.bunVM());
+ this.* = .{ .globalThis = globalThis, .promise = JSC.JSPromise.Strong.init(globalThis), .poll_ref = poll_ref, .allocated = true, .name = name };
+ return this;
+ }
+
+ pub fn processResolve(this: *@This(), err_: ?c_ares.Error, _: i32, result: ?c_ares.struct_nameinfo) void {
+ if (err_) |err| {
+ var promise = this.promise;
+ var globalThis = this.globalThis;
+ const error_value = globalThis.createErrorInstance("lookupService failed: {s}", .{err.label()});
+ error_value.put(
+ globalThis,
+ JSC.ZigString.static("code"),
+ JSC.ZigString.init(err.code()).toValueGC(globalThis),
+ );
+
+ promise.reject(globalThis, error_value);
+ this.deinit();
+ return;
+ }
+ if (result == null) {
+ var promise = this.promise;
+ var globalThis = this.globalThis;
+ const error_value = globalThis.createErrorInstance("lookupService failed: No results", .{});
+ error_value.put(
+ globalThis,
+ JSC.ZigString.static("code"),
+ JSC.ZigString.init("EUNREACHABLE").toValueGC(globalThis),
+ );
+
+ promise.reject(globalThis, error_value);
+ this.deinit();
+ return;
+ }
+ var name_info = result.?;
+ const array = name_info.toJSReponse(this.globalThis.allocator(), this.globalThis);
+ this.onComplete(array);
+ return;
+ }
+
+ pub fn onComplete(this: *@This(), result: JSC.JSValue) void {
+ var promise = this.promise;
+ var globalThis = this.globalThis;
+ this.promise = .{};
+ promise.resolve(globalThis, result);
+ this.deinit();
+ }
+
+ pub fn deinit(this: *@This()) void {
+ this.poll_ref.unrefOnNextTick(this.globalThis.bunVM());
+ // freed
+ bun.default_allocator.free(this.name);
+
+ if (this.allocated)
+ this.globalThis.allocator().destroy(this);
+ }
+};
+
+pub const GetNameInfoRequest = struct {
+ const request_type = @This();
+
+ const log = Output.scoped(@This(), false);
+
+ resolver_for_caching: ?*DNSResolver = null,
+ hash: u64 = 0,
+ cache: @This().CacheConfig = @This().CacheConfig{},
+ head: CAresNameInfo,
+ tail: *CAresNameInfo = undefined,
+
+ pub fn init(
+ cache: DNSResolver.LookupCacheHit(@This()),
+ resolver: ?*DNSResolver,
+ name: []const u8,
+ globalThis: *JSC.JSGlobalObject,
+ comptime cache_field: []const u8,
+ ) !*@This() {
+ var request = try globalThis.allocator().create(@This());
+ var hasher = std.hash.Wyhash.init(0);
+ hasher.update(name);
+ const hash = hasher.final();
+ var poll_ref = JSC.PollRef.init();
+ poll_ref.ref(globalThis.bunVM());
+ request.* = .{
+ .resolver_for_caching = resolver,
+ .hash = hash,
+ .head = .{ .poll_ref = poll_ref, .globalThis = globalThis, .promise = JSC.JSPromise.Strong.init(globalThis), .allocated = false, .name = name },
+ };
+ request.tail = &request.head;
+ if (cache == .new) {
+ request.resolver_for_caching = resolver;
+ request.cache = @This().CacheConfig{
+ .pending_cache = true,
+ .entry_cache = false,
+ .pos_in_pending = @as(u5, @truncate(@field(resolver.?, cache_field).indexOf(cache.new).?)),
+ .name_len = @as(u9, @truncate(name.len)),
+ };
+ cache.new.lookup = request;
+ }
+ return request;
+ }
+
+ 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: *request_type = undefined,
+
+ pub fn append(this: *PendingCacheKey, cares_lookup: *CAresNameInfo) void {
+ var tail = this.lookup.tail;
+ tail.next = cares_lookup;
+ this.lookup.tail = cares_lookup;
+ }
+
+ pub fn init(name: []const u8) PendingCacheKey {
+ var hasher = std.hash.Wyhash.init(0);
+ hasher.update(name);
+ const hash = hasher.final();
+ return PendingCacheKey{
+ .hash = hash,
+ .len = @as(u16, @truncate(name.len)),
+ .lookup = undefined,
+ };
+ }
+ };
+
+ pub fn onCaresComplete(this: *@This(), err_: ?c_ares.Error, timeout: i32, result: ?c_ares.struct_nameinfo) void {
+ if (this.resolver_for_caching) |resolver| {
+ if (this.cache.pending_cache) {
+ resolver.drainPendingNameInfoCares(
+ this.cache.pos_in_pending,
+ err_,
+ timeout,
+ result,
+ );
+ return;
+ }
+ }
+
+ var head = this.head;
+ bun.default_allocator.destroy(this);
+
+ head.processResolve(err_, timeout, result);
+ }
+};
+
pub const GetAddrInfoRequest = struct {
const log = Output.scoped(.GetAddrInfoRequest, false);
@@ -1325,6 +1489,7 @@ pub const DNSResolver = struct {
pending_ptr_cache_cares: PtrPendingCache = PtrPendingCache.init(),
pending_cname_cache_cares: CnamePendingCache = CnamePendingCache.init(),
pending_addr_cache_crares: AddrPendingCache = AddrPendingCache.init(),
+ pending_nameinfo_cache_cares: NameInfoPendingCache = NameInfoPendingCache.init(),
const PendingCache = bun.HiveArray(GetAddrInfoRequest.PendingCacheKey, 32);
const SrvPendingCache = bun.HiveArray(ResolveInfoRequest(c_ares.struct_ares_srv_reply, "srv").PendingCacheKey, 32);
@@ -1337,6 +1502,7 @@ pub const DNSResolver = struct {
const PtrPendingCache = bun.HiveArray(ResolveInfoRequest(c_ares.struct_hostent, "ptr").PendingCacheKey, 32);
const CnamePendingCache = bun.HiveArray(ResolveInfoRequest(c_ares.struct_hostent, "cname").PendingCacheKey, 32);
const AddrPendingCache = bun.HiveArray(GetHostByAddrInfoRequest.PendingCacheKey, 32);
+ const NameInfoPendingCache = bun.HiveArray(GetNameInfoRequest.PendingCacheKey, 32);
fn getKey(this: *DNSResolver, index: u8, comptime cache_name: []const u8, comptime request_type: type) request_type.PendingCacheKey {
var cache = &@field(this, cache_name);
@@ -1523,6 +1689,47 @@ pub const DNSResolver = struct {
}
}
+ pub fn drainPendingNameInfoCares(this: *DNSResolver, index: u8, err: ?c_ares.Error, timeout: i32, result: ?c_ares.struct_nameinfo) void {
+ const key = this.getKey(index, "pending_nameinfo_cache_cares", GetNameInfoRequest);
+
+ var name_info = result orelse {
+ var pending: ?*CAresNameInfo = key.lookup.head.next;
+ key.lookup.head.processResolve(err, timeout, null);
+ bun.default_allocator.destroy(key.lookup);
+
+ while (pending) |value| {
+ pending = value.next;
+ value.processResolve(err, timeout, null);
+ }
+ return;
+ };
+
+ var pending: ?*CAresNameInfo = key.lookup.head.next;
+ var prev_global = key.lookup.head.globalThis;
+
+ var array = name_info.toJSReponse(this.vm.allocator, prev_global);
+ array.ensureStillAlive();
+ key.lookup.head.onComplete(array);
+ bun.default_allocator.destroy(key.lookup);
+
+ array.ensureStillAlive();
+
+ while (pending) |value| {
+ var new_global = value.globalThis;
+ if (prev_global != new_global) {
+ array = name_info.toJSReponse(this.vm.allocator, new_global);
+ prev_global = new_global;
+ }
+ pending = value.next;
+
+ {
+ array.ensureStillAlive();
+ value.onComplete(array);
+ array.ensureStillAlive();
+ }
+ }
+ }
+
pub const CacheHit = union(enum) {
inflight: *GetAddrInfoRequest.PendingCacheKey,
new: *GetAddrInfoRequest.PendingCacheKey,
@@ -2376,6 +2583,95 @@ pub const DNSResolver = struct {
return values;
}
+ // Resolves the given address and port into a host name and service using the operating system's underlying getnameinfo implementation.
+ // If address is not a valid IP address, a TypeError will be thrown. The port will be coerced to a number.
+ // If it is not a legal port, a TypeError will be thrown.
+ pub fn lookupService(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ const arguments = callframe.arguments(3);
+ if (arguments.len < 2) {
+ globalThis.throwNotEnoughArguments("lookupService", 3, arguments.len);
+ return .zero;
+ }
+
+ const addr_value = arguments.ptr[0];
+ const port_value = arguments.ptr[1];
+ if (addr_value.isEmptyOrUndefinedOrNull() or !addr_value.isString()) {
+ globalThis.throwInvalidArgumentType("lookupService", "address", "string");
+ return .zero;
+ }
+ const addr_str = addr_value.toStringOrNull(globalThis) orelse {
+ return .zero;
+ };
+ if (addr_str.length() == 0) {
+ globalThis.throwInvalidArgumentType("lookupService", "address", "non-empty string");
+ return .zero;
+ }
+
+ const addr_s = addr_str.getZigString(globalThis).slice();
+ const port: u16 = if (port_value.isNumber()) blk: {
+ break :blk port_value.to(u16);
+ } else {
+ globalThis.throwInvalidArgumentType("lookupService", "port", "invalid port");
+ return .zero;
+ };
+
+ var sa: std.os.sockaddr.storage = std.mem.zeroes(std.os.sockaddr.storage);
+ if (c_ares.getSockaddr(addr_s, port, @as(*std.os.sockaddr, @ptrCast(&sa))) != 0) {
+ globalThis.throwInvalidArgumentType("lookupService", "address", "invalid address");
+ return .zero;
+ }
+
+ 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;
+ },
+ };
+
+ // This string will be freed in `CAresNameInfo.deinit`
+ const cache_name = std.fmt.allocPrint(bun.default_allocator, "{s}|{d}", .{ addr_s, port }) catch unreachable;
+
+ const key = GetNameInfoRequest.PendingCacheKey.init(cache_name);
+ var cache = resolver.getOrPutIntoResolvePendingCache(
+ GetNameInfoRequest,
+ key,
+ "pending_nameinfo_cache_cares",
+ );
+
+ if (cache == .inflight) {
+ var info = CAresNameInfo.init(globalThis, globalThis.allocator(), cache_name) catch unreachable;
+ cache.inflight.append(info);
+ return info.promise.value();
+ }
+
+ var request = GetNameInfoRequest.init(
+ cache,
+ resolver,
+ cache_name, // transfer ownership here
+ globalThis,
+ "pending_nameinfo_cache_cares",
+ ) catch unreachable;
+
+ const promise = request.tail.promise.value();
+ channel.getNameInfo(
+ @as(*std.os.sockaddr, @ptrCast(&sa)),
+ GetNameInfoRequest,
+ request,
+ GetNameInfoRequest.onCaresComplete,
+ );
+
+ return promise;
+ }
+
comptime {
@export(
resolve,
@@ -2455,11 +2751,13 @@ pub const DNSResolver = struct {
.name = "Bun__DNSResolver__reverse",
},
);
+ @export(
+ lookupService,
+ .{
+ .name = "Bun__DNSResolver__lookupService",
+ },
+ );
}
- // 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);
diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp
index 91fc810da..9d8ce6674 100644
--- a/src/bun.js/bindings/BunObject.cpp
+++ b/src/bun.js/bindings/BunObject.cpp
@@ -215,6 +215,7 @@ extern "C" EncodedJSValue Bun__DNSResolver__resolvePtr(JSGlobalObject*, JSC::Cal
extern "C" EncodedJSValue Bun__DNSResolver__resolveCname(JSGlobalObject*, JSC::CallFrame*);
extern "C" EncodedJSValue Bun__DNSResolver__getServers(JSGlobalObject*, JSC::CallFrame*);
extern "C" EncodedJSValue Bun__DNSResolver__reverse(JSGlobalObject*, JSC::CallFrame*);
+extern "C" EncodedJSValue Bun__DNSResolver__lookupService(JSGlobalObject*, JSC::CallFrame*);
static JSValue constructDNSObject(VM& vm, JSObject* bunObject)
{
@@ -246,6 +247,8 @@ static JSValue constructDNSObject(VM& vm, JSObject* bunObject)
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
dnsObject->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "reverse"_s), 2, Bun__DNSResolver__reverse, ImplementationVisibility::Public, NoIntrinsic,
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
+ dnsObject->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "lookupService"_s), 2, Bun__DNSResolver__lookupService, ImplementationVisibility::Public, NoIntrinsic,
+ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
return dnsObject;
}