fix outbound DNS lookup callback shape (#1101)

This commit is contained in:
ZacharyZcR
2026-07-28 01:47:58 +08:00
committed by GitHub
parent 1139f17319
commit cf827f9a9c
2 changed files with 57 additions and 25 deletions
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import type { LookupAddress, LookupAllOptions } from "dns";
import type { LookupAddress, LookupAllOptions, LookupOptions } from "dns";
import {
createDnsLookupHook,
isBlockedAddress,
@@ -56,6 +56,7 @@ describe("isBlockedAddress", () => {
function runHook(
addresses: LookupAddress[],
error: NodeJS.ErrnoException | null = null,
options: LookupOptions = { all: true },
) {
const fakeLookup = (
_host: string,
@@ -65,14 +66,43 @@ function runHook(
const hook = createDnsLookupHook(fakeLookup);
const callback = vi.fn();
hook("example.invalid", { all: true }, callback);
hook("example.invalid", options, callback);
return callback;
}
describe("createDnsLookupHook", () => {
it("allows a public IPv4 address through", () => {
const callback = runHook([{ address: "104.21.52.150", family: 4 }]);
expect(callback).toHaveBeenCalledWith(null, "104.21.52.150", 4);
it("returns every public address when all addresses are requested", () => {
const addresses = [
{ address: "104.21.52.150", family: 4 },
{ address: "2606:4700:3034::ac43:c88d", family: 6 },
];
const callback = runHook(addresses);
expect(callback).toHaveBeenCalledWith(null, addresses);
});
it.each<LookupOptions>([{ all: false }, {}])(
"returns one public address for single-address lookup options %j",
(options) => {
const callback = runHook(
[{ address: "104.21.52.150", family: 4 }],
null,
options,
);
expect(callback).toHaveBeenCalledWith(null, "104.21.52.150", 4);
},
);
it("rejects a mixed result containing a mapped private address", () => {
const callback = runHook([
{ address: "104.21.52.150", family: 4 },
{ address: "::ffff:127.0.0.1", family: 6 },
]);
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
message: "Private destinations are not allowed",
}),
[],
);
});
it("rejects a private address with the private-destination error", () => {
@@ -81,8 +111,7 @@ describe("createDnsLookupHook", () => {
expect.objectContaining({
message: "Private destinations are not allowed",
}),
"",
0,
[],
);
});
@@ -92,8 +121,7 @@ describe("createDnsLookupHook", () => {
expect.objectContaining({
message: "DNS resolution returned no addresses",
}),
"",
0,
[],
);
});
@@ -102,6 +130,6 @@ describe("createDnsLookupHook", () => {
code: "ENOTFOUND",
});
const callback = runHook([], dnsError);
expect(callback).toHaveBeenCalledWith(dnsError, "", 0);
expect(callback).toHaveBeenCalledWith(dnsError, []);
});
});
+19 -15
View File
@@ -1,4 +1,9 @@
import { lookup, type LookupAddress, type LookupAllOptions } from "dns";
import {
lookup,
type LookupAddress,
type LookupAllOptions,
type LookupOptions,
} from "dns";
import { BlockList, isIP } from "net";
import { Agent } from "undici";
@@ -13,8 +18,8 @@ type DnsLookupFn = (
type LookupHookCallback = (
error: NodeJS.ErrnoException | Error | null,
address: string,
family: number,
address: string | LookupAddress[],
family?: number,
) => void;
const blockedAddresses = new BlockList();
@@ -71,28 +76,27 @@ export function isBlockedAddress(address: string): boolean {
export function createDnsLookupHook(dnsLookup: DnsLookupFn = lookup) {
return function lookupHook(
host: string,
lookupOptions: LookupAllOptions,
lookupOptions: LookupOptions,
callback: LookupHookCallback,
): void {
const fail = (error: NodeJS.ErrnoException | Error) => {
if (lookupOptions.all) return callback(error, []);
callback(error, "", 0);
};
dnsLookup(
host,
{ ...lookupOptions, all: true, verbatim: true },
(error, addresses) => {
if (error) return callback(error, "", 0);
if (error) return fail(error);
if (!addresses.length) {
return callback(
new Error("DNS resolution returned no addresses"),
"",
0,
);
return fail(new Error("DNS resolution returned no addresses"));
}
if (addresses.some(({ address }) => isBlockedAddress(address))) {
return callback(
new Error("Private destinations are not allowed"),
"",
0,
);
return fail(new Error("Private destinations are not allowed"));
}
if (lookupOptions.all) return callback(null, addresses);
const selected = addresses[0];
callback(null, selected.address, selected.family);
},