Files
bun.sh/src/dns.zig

448 lines
15 KiB
Zig

const bun = @import("root").bun;
const std = @import("std");
const JSC = bun.JSC;
const JSValue = JSC.JSValue;
pub const AI_V4MAPPED: c_int = if (bun.Environment.isWindows) 2048 else bun.c.AI_V4MAPPED;
pub const AI_ADDRCONFIG: c_int = if (bun.Environment.isWindows) 1024 else bun.c.AI_ADDRCONFIG;
pub const AI_ALL: c_int = if (bun.Environment.isWindows) 256 else bun.c.AI_ALL;
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)], 0);
hints.ai_family = this.options.family.toLibC();
hints.ai_socktype = this.options.socktype.toLibC();
hints.ai_protocol = this.options.protocol.toLibC();
hints.ai_flags = @bitCast(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,
/// Leaving this unset leads to many duplicate addresses returned.
/// Node hardcodes to `SOCK_STREAM`.
/// There don't seem to be any issues in Node's repo about this
/// So I think it's likely that nobody actually needs `SOCK_DGRAM` as a flag
/// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/src/cares_wrap.cc#L1609
socktype: SocketType = .stream,
protocol: Protocol = .unspecified,
backend: Backend = Backend.default,
flags: std.c.AI = .{},
pub fn toLibC(this: Options) ?std.c.addrinfo {
if (this.family == .unspecified and this.socktype == .unspecified and this.protocol == .unspecified and this.flags == std.c.AI{}) {
return null;
}
var hints: std.c.addrinfo = undefined;
@memset(std.mem.asBytes(&hints)[0..@sizeOf(std.c.addrinfo)], 0);
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 (try value.get(globalObject, "family")) |family| {
options.family = try Family.fromJS(family, globalObject);
}
if (try value.get(globalObject, "socketType") orelse try value.get(globalObject, "socktype")) |socktype| {
options.socktype = try SocketType.fromJS(socktype, globalObject);
}
if (try value.get(globalObject, "protocol")) |protocol| {
options.protocol = try Protocol.fromJS(protocol, globalObject);
}
if (try value.get(globalObject, "backend")) |backend| {
options.backend = try Backend.fromJS(backend, globalObject);
}
if (try value.get(globalObject, "flags")) |flags| {
if (!flags.isNumber())
return error.InvalidFlags;
options.flags = flags.coerce(std.c.AI, globalObject);
if (!options.flags.ALL and !options.flags.ADDRCONFIG and !options.flags.V4MAPPED)
return error.InvalidFlags;
}
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.coerce(i32, globalObject)) {
0 => .unspecified,
4 => .inet,
6 => .inet6,
else => return error.InvalidFamily,
};
}
if (value.isString()) {
return map.fromJS(globalObject, value) orelse {
if (value.toString(globalObject).length() == 0) {
return .unspecified;
}
return error.InvalidFamily;
};
}
return error.InvalidFamily;
}
pub fn toLibC(this: Family) i32 {
return switch (this) {
.unspecified => 0,
.inet => std.posix.AF.INET,
.inet6 => std.posix.AF.INET6,
.unix => std.posix.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.posix.SOCK.STREAM,
.dgram => return std.posix.SOCK.DGRAM,
}
}
pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !SocketType {
if (value.isEmptyOrUndefinedOrNull())
// Default to .stream
return .stream;
if (value.isNumber()) {
return switch (value.to(i32)) {
0 => .unspecified,
1 => .stream,
2 => .dgram,
else => return error.InvalidSocketType,
};
}
if (value.isString()) {
return map.fromJS(globalObject, value) orelse {
if (value.toString(globalObject).length() == 0)
return .unspecified;
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()) {
return map.fromJS(globalObject, value) orelse {
const str = value.toString(globalObject);
if (str.length() == 0)
return .unspecified;
return error.InvalidProtocol;
};
}
return error.InvalidProtocol;
}
pub fn toLibC(this: Protocol) i32 {
switch (this) {
.unspecified => return 0,
.tcp => return std.posix.IPPROTO.TCP,
.udp => return std.posix.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 = switch (bun.Environment.os) {
.mac, .windows => .system,
else => .c_ares,
};
pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Backend {
if (value.isEmptyOrUndefinedOrNull())
return default;
if (value.isString()) {
return label.fromJS(globalObject, value) orelse {
if (value.toString(globalObject).length() == 0) {
return default;
}
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: *const Any, globalThis: *JSC.JSGlobalObject) ?JSC.JSValue {
return switch (this.*) {
.addrinfo => |addrinfo| addrInfoToJSArray(addrinfo orelse return null, globalThis),
.list => |list| brk: {
const array = JSC.JSValue.createEmptyArray(globalThis, @as(u32, @truncate(list.items.len)));
var i: u32 = 0;
const items: []const Result = list.items;
for (items) |item| {
array.putIndex(globalThis, i, item.toJS(globalThis));
i += 1;
}
break :brk array;
},
};
}
pub fn deinit(this: *const Any) void {
switch (this.*) {
.addrinfo => |addrinfo| {
if (addrinfo) |a| {
std.c.freeaddrinfo(a);
}
},
.list => |list_| {
var list = list_;
list.clearAndFree();
},
}
}
};
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(addrinfo.addr orelse return null)),
// no TTL in POSIX getaddrinfo()
.ttl = 0,
};
}
pub fn toJS(this: *const Result, globalThis: *JSC.JSGlobalObject) JSValue {
const obj = JSC.JSValue.createEmptyObject(globalThis, 3);
obj.put(globalThis, JSC.ZigString.static("address"), addressToJS(&this.address, globalThis) catch |err| return switch (err) {
error.JSError => .zero,
error.OutOfMemory => globalThis.throwOutOfMemoryValue(),
});
obj.put(globalThis, JSC.ZigString.static("family"), switch (this.address.any.family) {
std.posix.AF.INET => JSValue.jsNumber(4),
std.posix.AF.INET6 => JSValue.jsNumber(6),
else => JSValue.jsNumber(0),
});
obj.put(globalThis, JSC.ZigString.static("ttl"), JSValue.jsNumber(this.ttl));
return obj;
}
};
};
const String = bun.String;
const default_allocator = bun.default_allocator;
pub fn addressToString(address: *const std.net.Address) bun.OOM!bun.String {
switch (address.any.family) {
std.posix.AF.INET => {
var self = address.in;
const bytes = @as(*const [4]u8, @ptrCast(&self.sa.addr));
return String.createFormat("{}.{}.{}.{}", .{
bytes[0],
bytes[1],
bytes[2],
bytes[3],
});
},
std.posix.AF.INET6 => {
var stack = std.heap.stackFallback(512, default_allocator);
const allocator = stack.get();
var out = try std.fmt.allocPrint(allocator, "{any}", .{address.*});
defer allocator.free(out);
// TODO: this is a hack, fix it
// This removes [.*]:port
// ^ ^^^^^^
return String.createLatin1(out[1 .. out.len - 1 - std.fmt.count("{d}", .{address.in6.getPort()}) - 1]);
},
std.posix.AF.UNIX => {
if (comptime std.net.has_unix_sockets) {
return String.createLatin1(&address.un.path);
}
return String.empty;
},
else => return String.empty,
}
}
pub fn addressToJS(address: *const std.net.Address, globalThis: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue {
var str = addressToString(address) catch return globalThis.throwOutOfMemory();
return str.transferToJS(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 += @intFromBool(current.?.addr != null);
}
return count;
}
pub fn addrInfoToJSArray(
addr_info: *std.c.addrinfo,
globalThis: *JSC.JSGlobalObject,
) JSC.JSValue {
const array = JSC.JSValue.createEmptyArray(
globalThis,
addrInfoCount(addr_info),
);
{
var j: u32 = 0;
var current: ?*std.c.addrinfo = addr_info;
while (current) |this_node| : (current = current.?.next) {
array.putIndex(
globalThis,
j,
GetAddrInfo.Result.toJS(
&(GetAddrInfo.Result.fromAddrInfo(this_node) orelse continue),
globalThis,
),
);
j += 1;
}
}
return array;
}
pub const internal = bun.JSC.DNS.InternalDNS;