Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions src/node/internal/internal_dns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ export function lookup(
address: answer.data,
family: 4,
})) ?? [];
const ipv6Addresses: { address: string; family: 4 }[] =
const ipv6Addresses: { address: string; family: 6 }[] =
ipv6Response.Answer?.map((answer) => ({
address: answer.data,
family: 4,
family: 6,
})) ?? [];

// No addresses found
Expand All @@ -178,10 +178,43 @@ export function lookup(
.catch((error: unknown): void => {
process.nextTick(callback, error);
});
} else if (family === 0) {
// family=0, all=false: query both A and AAAA, return first result based on dnsOrder
Promise.all([
sendDnsRequest(hostname, 'A').catch(() => ({ Answer: [] })),
sendDnsRequest(hostname, 'AAAA').catch(() => ({ Answer: [] })),
])
.then(([ipv4Response, ipv6Response]): void => {
const ipv4 = ipv4Response.Answer?.at(0)?.data;
const ipv6 = ipv6Response.Answer?.at(0)?.data;

if (ipv4 == null && ipv6 == null) {
callback(new DnsError(hostname, errorCodes.NOTFOUND, 'queryA'));
return;
}

// Return the preferred address based on dnsOrder
if (dnsOrder === 'ipv6first') {
if (ipv6 != null) {
callback(null, ipv6, 6);
} else {
callback(null, ipv4 as string, 4);
}
} else {
if (ipv4 != null) {
callback(null, ipv4, 4);
} else {
callback(null, ipv6 as string, 6);
}
}
})
.catch((error: unknown): void => {
process.nextTick(callback, error);
});
} else {
const requestType = family === 4 ? 'A' : 'AAAA';

// Single request for all other cases (including when all=true but family is specified)
// Single request when family is specified (with or without all=true)
sendDnsRequest(hostname, requestType)
.then((json): void => {
validateAnswer(json.Answer, hostname, `query${requestType}`);
Expand Down
2 changes: 1 addition & 1 deletion src/node/internal/internal_dns_promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class Resolver implements dns.Resolver {
return resolveNs(name);
}

esolvePtr(name: string): Promise<string[]> {
resolvePtr(name: string): Promise<string[]> {
return resolvePtr(name);
}

Expand Down
90 changes: 90 additions & 0 deletions src/workerd/api/node/tests/dns-nodejs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,96 @@ export const getServers = {
},
};

// Regression: dns.lookup() with all:true and family:0 must return family:6 for
// IPv6 addresses (was returning family:4 due to copy-paste bug).
export const lookupAllFamilyZeroIPv6Family = {
async test() {
const results = await dnsPromises.lookup(addresses.INET_HOST, {
all: true,
family: 0,
});
ok(Array.isArray(results), 'expected array of addresses');
ok(results.length > 0, 'expected at least one address');

for (const entry of results) {
strictEqual(typeof entry.address, 'string');
ok(
entry.family === 4 || entry.family === 6,
`family must be 4 or 6, got ${entry.family}`
);
}

// If any IPv6 addresses are returned, they must have family:6
const ipv6Entries = results.filter((e) => e.address.includes(':'));
for (const entry of ipv6Entries) {
strictEqual(
entry.family,
6,
`IPv6 address ${entry.address} should have family:6 but got family:${entry.family}`
);
}

// If any IPv4 addresses are returned, they must have family:4
const ipv4Entries = results.filter((e) => !e.address.includes(':'));
for (const entry of ipv4Entries) {
strictEqual(
entry.family,
4,
`IPv4 address ${entry.address} should have family:4 but got family:${entry.family}`
);
}
},
};

// Regression: dns.lookup() with default family (0) and all:false must resolve
// hosts that only have A records (was only querying AAAA, failing for IPv4-only).
export const lookupDefaultFamilyResolvesIPv4 = {
async test() {
// Default options (family:0, all:false) — should return an address
const result = await dnsPromises.lookup(addresses.INET4_HOST);
ok(result != null, 'expected a result');
strictEqual(typeof result.address, 'string');
ok(result.address.length > 0, 'expected non-empty address');
ok(
result.family === 4 || result.family === 6,
`family must be 4 or 6, got ${result.family}`
);

// Also verify via callback API
const { promise, resolve, reject } = Promise.withResolvers();
dns.lookup(addresses.INET4_HOST, (error, address, family) => {
if (error) {
reject(error);
return;
}
strictEqual(typeof address, 'string');
ok(address.length > 0, 'expected non-empty address from callback');
ok(family === 4 || family === 6, `family must be 4 or 6, got ${family}`);
resolve();
});
await promise;
},
};

// Regression: Resolver.resolvePtr must exist (was misspelled as 'esolvePtr').
export const resolverResolvePtrExists = {
async test() {
const resolver = new dnsPromises.Resolver();
strictEqual(
typeof resolver.resolvePtr,
'function',
'Resolver.resolvePtr should be a function'
);
// Actually call it to verify it works end-to-end
const result = await resolver.resolvePtr(addresses.PTR_HOST);
ok(Array.isArray(result), 'expected array result');
ok(result.length > 0, 'expected at least one PTR record');
for (const item of result) {
strictEqual(typeof item, 'string');
}
},
};

// Tests are taken from
// https://github.com/nodejs/node/blob/3153c8333e3a8f2015b795642def4d81ec7cd7b3/test/parallel/test-dns-lookup.js
export const testDnsLookup = {
Expand Down
Loading