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
36 changes: 36 additions & 0 deletions patches/cares/accept-rdata-compression.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Accept DNS name compression in RDATA on parse.

c-ares 1.34.8 started rejecting compression pointers inside RDATA for RR
types that RFC 3597 says must not use them (SRV, NAPTR, etc.). That is
correct for writers, but a large share of deployed resolvers and caches
(older BIND, dnsmasq, mDNSResponder on macOS, assorted corporate
forwarders) still compress SRV targets on the wire, so dns.resolveSrv()
fails with EBADRESP against those servers.

c-ares already refuses to emit compression for these types
(ares_dns_rec_allow_name_comp gates the writer). This patch keeps the
parser lenient the way it was before 1.34.8 so existing servers keep
working, while the writer remains strict.

--- a/src/lib/record/ares_dns_parse.c
+++ b/src/lib/record/ares_dns_parse.c
@@ -46,14 +46,12 @@
{
ares_status_t status;
char *name = NULL;
- /* Only RR types defined in RFC1035 may use name compression within their
- * RDATA (RFC3597). Reject compression pointers for any other type (e.g.
- * SRV per RFC2782) to match the write-side policy and avoid following
- * pointers that a non-understanding nameserver could not have rewritten. */
- ares_bool_t allow_compression =
- ares_dns_rec_allow_name_comp(ares_dns_rr_get_type(rr));

- status = ares_dns_name_parse(buf, &name, is_hostname, allow_compression);
+ /* Bun: accept compression in RDATA on parse regardless of RR type.
+ * RFC 3597 says writers must not compress here and the c-ares writer
+ * already enforces that, but plenty of real resolvers still emit
+ * compressed SRV/NAPTR targets, so stay lenient when reading. */
+ status = ares_dns_name_parse(buf, &name, is_hostname, ARES_TRUE);
if (status != ARES_SUCCESS) {
return status;
}
4 changes: 3 additions & 1 deletion scripts/build/deps/cares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { Config } from "../config.ts";
import type { Dependency } from "../source.ts";
import { depBuildDir } from "../source.ts";

const CARES_COMMIT = "3ac47ee46edd8ea40370222f91613fc16c434853";
const CARES_COMMIT = "c7a3138dcfe3bb0eaaf10c0c24c36dc66dc790ab";
Comment thread
robobun marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.

// prettier-ignore
const SOURCES = [
Expand Down Expand Up @@ -58,6 +58,8 @@ export const cares: Dependency = {
commit: CARES_COMMIT,
}),

patches: ["patches/cares/accept-rdata-compression.patch"],

build: cfg => ({
kind: "direct",
pic: true,
Expand Down
61 changes: 61 additions & 0 deletions test/js/node/dns/node-dns.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,67 @@ test("dns.resolveSrv (_test._tcp.invalid.localhost)", () => {
return promise;
});

// RFC 2782 says SRV targets must not be compressed, but a lot of deployed
// resolvers (dnsmasq, mDNSResponder, older BIND, various corporate forwarders)
// still compress them. c-ares 1.34.8 started rejecting these responses with
// EBADRESP; make sure we keep accepting them.
test.skipIf(isWindows)("dns.resolveSrv accepts compressed target in RDATA", async () => {
const socket = dgram.createSocket("udp4");
try {
socket.on("message", (query, rinfo) => {
// Find end of question section: QNAME + QTYPE(2) + QCLASS(2).
let off = 12;
while (off < query.length && query[off] !== 0) off += query[off] + 1;
off += 1 + 2 + 2;
const question = query.subarray(12, off);

const header = Buffer.alloc(12);
header[0] = query[0];
header[1] = query[1];
header[2] = 0x81; // QR=1, RD=1
header[3] = 0x80; // RA=1
header[5] = 1; // QDCOUNT
header[7] = 1; // ANCOUNT

// RDATA: priority=10, weight=50, port=80, target = "srv" + pointer to
// QNAME at offset 12. After decompression the target is
// srv.<query-name>.
const rdata = Buffer.from([
0x00,
0x0a, // priority
0x00,
0x32, // weight
0x00,
0x50, // port
0x03,
0x73,
0x72,
0x76, // "srv"
0xc0,
0x0c, // compression pointer -> offset 12
]);
const answer = Buffer.concat([
Buffer.from([0xc0, 0x0c, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c]),
Buffer.from([rdata.length >> 8, rdata.length & 0xff]),
rdata,
]);
socket.send(Buffer.concat([header, question, answer]), rinfo.port, rinfo.address);
});
socket.bind(0, "127.0.0.1");
await once(socket, "listening");
const { port } = socket.address();

const resolver = new dns.Resolver({ timeout: 1000, tries: 1 });
resolver.setServers(["127.0.0.1:" + port]);
const { promise, resolve, reject } = Promise.withResolvers();
resolver.resolveSrv("_test._tcp.example.test", (err, records) => (err ? reject(err) : resolve(records)));
const records = await promise;
expect(records).toEqual([{ name: "srv._test._tcp.example.test", priority: 10, weight: 50, port: 80 }]);
} finally {
socket.close();
}
});

test("dns.resolveTxt (txt.socketify.dev)", () => {
const { promise, resolve, reject } = Promise.withResolvers();
dns.resolveTxt("txt.socketify.dev", (err, results) => {
Expand Down
2 changes: 1 addition & 1 deletion test/js/node/process/process.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ it("process.versions", () => {
zlib: "12731092979c6d07f42da27da673a9f6c7b13586",
tinycc: "05f0fafaa3be31e31d7b4b5c17dc60f62c991171",
lolhtml: "725ce499aa9b71e38b7a2d0a9fbb6d7294a4079e",
ares: "3ac47ee46edd8ea40370222f91613fc16c434853",
ares: "c7a3138dcfe3bb0eaaf10c0c24c36dc66dc790ab",
libdeflate: "c8c56a20f8f621e6a966b716b31f1dedab6a41e3",
zstd: "f8745da6ff1ad1e7bab384bd1f9d742439278e99",
lshpack: "8905c024b6d052f083a3d11d0a169b3c2735c8a1",
Expand Down