2025-05-17 09:23:17 +00:00
|
|
|
From ef91595e2fc1f96b7c8eb51bfcc1408a5adaa4a9 Mon Sep 17 00:00:00 2001
|
|
|
|
From: Rithvik Vibhu <rithvikvibhu@gmail.com>
|
|
|
|
Date: Tue, 14 May 2024 23:10:31 +0530
|
|
|
|
Subject: [PATCH] dns: add TLSA record query and parsing
|
|
|
|
|
|
|
|
PR-URL: https://github.com/nodejs/node/pull/52983
|
|
|
|
Refs: https://github.com/nodejs/node/issues/39569
|
|
|
|
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
|
|
|
|
Reviewed-By: James M Snell <jasnell@gmail.com>
|
|
|
|
Reviewed-By: Rich Trott <rtrott@gmail.com>
|
|
|
|
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
|
|
|
|
---
|
|
|
|
doc/api/dns.md | 71 ++++++++++++++++++
|
|
|
|
lib/internal/dns/utils.js | 2 +
|
|
|
|
src/cares_wrap.cc | 96 +++++++++++++++++++++++++
|
|
|
|
src/cares_wrap.h | 8 +++
|
|
|
|
src/env_properties.h | 4 ++
|
|
|
|
test/common/internet.js | 2 +
|
|
|
|
test/internet/test-dns-cares-domains.js | 1 +
|
|
|
|
test/internet/test-dns.js | 41 +++++++++++
|
|
|
|
test/internet/test-trace-events-dns.js | 1 +
|
|
|
|
9 files changed, 226 insertions(+)
|
|
|
|
|
|
|
|
diff --git a/lib/internal/dns/utils.js b/lib/internal/dns/utils.js
|
|
|
|
index 7d9e22d1c2458f..bcca83fd4fe54d 100644
|
|
|
|
--- a/third_party/electron_node/lib/internal/dns/utils.js
|
|
|
|
+++ b/third_party/electron_node/lib/internal/dns/utils.js
|
|
|
|
@@ -235,6 +235,7 @@ const resolverKeys = [
|
|
|
|
'resolvePtr',
|
|
|
|
'resolveSoa',
|
|
|
|
'resolveSrv',
|
|
|
|
+ 'resolveTlsa',
|
|
|
|
'resolveTxt',
|
|
|
|
'reverse',
|
|
|
|
];
|
|
|
|
@@ -300,6 +301,7 @@ function createResolverClass(resolver) {
|
|
|
|
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
|
|
|
|
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
|
|
|
|
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
|
|
|
|
+ Resolver.prototype.resolveTlsa = resolveMap.TLSA = resolver('queryTlsa');
|
|
|
|
Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt');
|
|
|
|
Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv');
|
|
|
|
Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr');
|
2025-07-03 13:00:54 +00:00
|
|
|
--- src/third_party/electron_node/src/cares_wrap.cc 2025-07-03 13:55:28.684461768 +0200
|
|
|
|
+++ src/third_party/electron_node/src/cares_wrap.cc.orig 2025-07-03 07:25:07.137463925 +0200
|
2025-05-17 09:23:17 +00:00
|
|
|
@@ -40,6 +40,10 @@
|
|
|
|
#include <vector>
|
|
|
|
#include <unordered_set>
|
|
|
|
|
|
|
|
+#ifndef T_TLSA
|
|
|
|
+#define T_TLSA 52 /* TLSA certificate association */
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
#ifndef T_CAA
|
|
|
|
# define T_CAA 257 /* Certification Authority Authorization */
|
|
|
|
#endif
|
|
|
|
@@ -57,6 +61,7 @@ namespace node {
|
|
|
|
namespace cares_wrap {
|
|
|
|
|
|
|
|
using v8::Array;
|
|
|
|
+using v8::ArrayBuffer;
|
|
|
|
using v8::Context;
|
|
|
|
using v8::EscapableHandleScope;
|
|
|
|
using v8::Exception;
|
2025-07-03 13:00:54 +00:00
|
|
|
@@ -383,6 +388,69 @@ Maybe<int> ParseCaaReply(Environment* en
|
|
|
|
return Just<int>(ARES_SUCCESS);
|
2025-05-17 09:23:17 +00:00
|
|
|
}
|
|
|
|
|
2025-07-03 13:00:54 +00:00
|
|
|
+Maybe<int> ParseTlsaReply(Environment* env,
|
|
|
|
+ unsigned char* buf,
|
|
|
|
+ int len,
|
|
|
|
+ Local<Array> ret) {
|
2025-05-17 09:23:17 +00:00
|
|
|
+ EscapableHandleScope handle_scope(env->isolate());
|
|
|
|
+
|
|
|
|
+ ares_dns_record_t* dnsrec = nullptr;
|
|
|
|
+
|
|
|
|
+ int status = ares_dns_parse(buf, len, 0, &dnsrec);
|
|
|
|
+ if (status != ARES_SUCCESS) {
|
|
|
|
+ ares_dns_record_destroy(dnsrec);
|
2025-07-03 13:00:54 +00:00
|
|
|
+ return Just<int>(status);
|
2025-05-17 09:23:17 +00:00
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ uint32_t offset = ret->Length();
|
|
|
|
+ size_t rr_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER);
|
|
|
|
+
|
|
|
|
+ for (size_t i = 0; i < rr_count; i++) {
|
|
|
|
+ const ares_dns_rr_t* rr =
|
|
|
|
+ ares_dns_record_rr_get(dnsrec, ARES_SECTION_ANSWER, i);
|
|
|
|
+
|
|
|
|
+ if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_TLSA) continue;
|
|
|
|
+
|
|
|
|
+ unsigned char certusage = ares_dns_rr_get_u8(rr, ARES_RR_TLSA_CERT_USAGE);
|
|
|
|
+ unsigned char selector = ares_dns_rr_get_u8(rr, ARES_RR_TLSA_SELECTOR);
|
|
|
|
+ unsigned char match = ares_dns_rr_get_u8(rr, ARES_RR_TLSA_MATCH);
|
|
|
|
+ size_t data_len;
|
|
|
|
+ const unsigned char* data =
|
|
|
|
+ ares_dns_rr_get_bin(rr, ARES_RR_TLSA_DATA, &data_len);
|
|
|
|
+ if (!data || data_len == 0) continue;
|
|
|
|
+
|
|
|
|
+ Local<ArrayBuffer> data_ab = ArrayBuffer::New(env->isolate(), data_len);
|
|
|
|
+ memcpy(data_ab->Data(), data, data_len);
|
|
|
|
+
|
|
|
|
+ Local<Object> tlsa_rec = Object::New(env->isolate());
|
2025-07-03 13:00:54 +00:00
|
|
|
+
|
|
|
|
+ if (tlsa_rec
|
|
|
|
+ ->Set(env->context(),
|
|
|
|
+ env->cert_usage_string(),
|
|
|
|
+ Integer::NewFromUnsigned(env->isolate(), certusage))
|
|
|
|
+ .IsNothing() ||
|
|
|
|
+ tlsa_rec
|
|
|
|
+ ->Set(env->context(),
|
|
|
|
+ env->selector_string(),
|
|
|
|
+ Integer::NewFromUnsigned(env->isolate(), selector))
|
|
|
|
+ .IsNothing() ||
|
|
|
|
+ tlsa_rec
|
|
|
|
+ ->Set(env->context(),
|
|
|
|
+ env->match_string(),
|
|
|
|
+ Integer::NewFromUnsigned(env->isolate(), match))
|
|
|
|
+ .IsNothing() ||
|
|
|
|
+ tlsa_rec->Set(env->context(), env->data_string(), data_ab)
|
|
|
|
+ .IsNothing() ||
|
|
|
|
+ ret->Set(env->context(), offset + i, tlsa_rec).IsNothing()) {
|
|
|
|
+ ares_dns_record_destroy(dnsrec);
|
|
|
|
+ return Nothing<int>();
|
|
|
|
+ }
|
2025-05-17 09:23:17 +00:00
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ares_dns_record_destroy(dnsrec);
|
2025-07-03 13:00:54 +00:00
|
|
|
+ return Just<int>(ARES_SUCCESS);
|
2025-05-17 09:23:17 +00:00
|
|
|
+}
|
|
|
|
+
|
2025-07-03 13:00:54 +00:00
|
|
|
Maybe<int> ParseTxtReply(Environment* env,
|
|
|
|
const unsigned char* buf,
|
|
|
|
int len,
|
|
|
|
@@ -945,6 +1013,11 @@ int NsTraits::Send(QueryWrap<NsTraits>*
|
2025-05-17 09:23:17 +00:00
|
|
|
return ARES_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
+int TlsaTraits::Send(QueryWrap<TlsaTraits>* wrap, const char* name) {
|
|
|
|
+ wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_TLSA);
|
|
|
|
+ return ARES_SUCCESS;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
int TxtTraits::Send(QueryWrap<TxtTraits>* wrap, const char* name) {
|
|
|
|
wrap->AresQuery(name, ARES_CLASS_IN, ARES_REC_TYPE_TXT);
|
|
|
|
return ARES_SUCCESS;
|
2025-07-03 13:00:54 +00:00
|
|
|
@@ -1156,6 +1229,14 @@ Maybe<int> AnyTraits::Parse(QueryAnyWrap
|
|
|
|
}
|
|
|
|
}
|
2025-05-17 09:23:17 +00:00
|
|
|
|
|
|
|
+ /* Parse TLSA records */
|
2025-07-03 13:00:54 +00:00
|
|
|
+ if (!ParseTlsaReply(env, buf, len, ret).To(&status)) {
|
|
|
|
+ return Nothing<int>();
|
|
|
|
+ }
|
|
|
|
+ if (status != ARES_SUCCESS && status != ARES_ENODATA) {
|
|
|
|
+ return Just<int>(status);
|
|
|
|
+ }
|
2025-05-17 09:23:17 +00:00
|
|
|
+
|
|
|
|
/* Parse CAA records */
|
2025-07-03 13:00:54 +00:00
|
|
|
if (!ParseCaaReply(env, buf, len, ret, true).To(&status)) {
|
|
|
|
return Nothing<int>();
|
|
|
|
@@ -1339,6 +1420,32 @@ Maybe<int> NsTraits::Parse(QueryNsWrap*
|
|
|
|
return Just<int>(ARES_SUCCESS);
|
2025-05-17 09:23:17 +00:00
|
|
|
}
|
|
|
|
|
2025-07-03 13:00:54 +00:00
|
|
|
+Maybe<int> TlsaTraits::Parse(QueryTlsaWrap* wrap,
|
|
|
|
+ const std::unique_ptr<ResponseData>& response) {
|
2025-05-17 09:23:17 +00:00
|
|
|
+ if (response->is_host) [[unlikely]] {
|
2025-07-03 13:00:54 +00:00
|
|
|
+ return Just<int>(ARES_EBADRESP);
|
2025-05-17 09:23:17 +00:00
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ unsigned char* buf = response->buf.data;
|
|
|
|
+ int len = response->buf.size;
|
|
|
|
+
|
|
|
|
+ Environment* env = wrap->env();
|
|
|
|
+ HandleScope handle_scope(env->isolate());
|
|
|
|
+ Context::Scope context_scope(env->context());
|
|
|
|
+
|
|
|
|
+ Local<Array> tlsa_records = Array::New(env->isolate());
|
2025-07-03 13:00:54 +00:00
|
|
|
+ int status;
|
|
|
|
+ if (!ParseTlsaReply(env, buf, len, tlsa_records).To(&status)) {
|
|
|
|
+ return Nothing<int>();
|
|
|
|
+ }
|
|
|
|
+ if (status != ARES_SUCCESS) {
|
|
|
|
+ return Just<int>(status);
|
|
|
|
+ }
|
2025-05-17 09:23:17 +00:00
|
|
|
+
|
|
|
|
+ wrap->CallOnComplete(tlsa_records);
|
2025-07-03 13:00:54 +00:00
|
|
|
+ return Just<int>(ARES_SUCCESS);
|
2025-05-17 09:23:17 +00:00
|
|
|
+}
|
|
|
|
+
|
2025-07-03 13:00:54 +00:00
|
|
|
Maybe<int> TxtTraits::Parse(QueryTxtWrap* wrap,
|
|
|
|
const std::unique_ptr<ResponseData>& response) {
|
|
|
|
if (response->is_host) [[unlikely]] {
|
|
|
|
--- src/third_party/electron_node/src/cares_wrap.h 2025-07-03 13:50:26.156515766 +0200
|
|
|
|
+++ src/third_party/electron_node/src/cares_wrap.h.orig 2025-07-03 07:25:07.009463947 +0200
|
|
|
|
@@ -419,6 +419,7 @@ class QueryWrap final : public AsyncWrap
|
|
|
|
V(Ptr, resolvePtr, queryPtr) \
|
|
|
|
V(Srv, resolveSrv, querySrv) \
|
|
|
|
V(Soa, resolveSoa, querySoa) \
|
|
|
|
+ V(Tlsa, resolveTlsa, queryTlsa) \
|
|
|
|
V(Txt, resolveTxt, queryTxt)
|
2025-05-17 09:23:17 +00:00
|
|
|
|
2025-07-03 13:00:54 +00:00
|
|
|
// All query type handlers share the same basic structure, so we can simplify
|
2025-05-17 09:23:17 +00:00
|
|
|
diff --git a/src/env_properties.h b/src/env_properties.h
|
|
|
|
index 9d22dc69754178..bc97dfc66c96f9 100644
|
|
|
|
--- a/third_party/electron_node/src/env_properties.h
|
|
|
|
+++ b/third_party/electron_node/src/env_properties.h
|
|
|
|
@@ -91,6 +91,7 @@
|
|
|
|
V(cached_data_rejected_string, "cachedDataRejected") \
|
|
|
|
V(cached_data_string, "cachedData") \
|
|
|
|
V(cache_key_string, "cacheKey") \
|
|
|
|
+ V(cert_usage_string, "certUsage") \
|
|
|
|
V(change_string, "change") \
|
|
|
|
V(changes_string, "changes") \
|
|
|
|
V(channel_string, "channel") \
|
|
|
|
@@ -135,6 +136,7 @@
|
|
|
|
V(dns_ptr_string, "PTR") \
|
|
|
|
V(dns_soa_string, "SOA") \
|
|
|
|
V(dns_srv_string, "SRV") \
|
|
|
|
+ V(dns_tlsa_string, "TLSA") \
|
|
|
|
V(dns_txt_string, "TXT") \
|
|
|
|
V(done_string, "done") \
|
|
|
|
V(duration_string, "duration") \
|
|
|
|
@@ -237,6 +239,7 @@
|
|
|
|
V(line_number_string, "lineNumber") \
|
|
|
|
V(loop_count, "loopCount") \
|
|
|
|
V(mac_string, "mac") \
|
|
|
|
+ V(match_string, "match") \
|
|
|
|
V(max_buffer_string, "maxBuffer") \
|
|
|
|
V(max_concurrent_streams_string, "maxConcurrentStreams") \
|
|
|
|
V(message_port_constructor_string, "MessagePort") \
|
|
|
|
@@ -336,6 +339,7 @@
|
|
|
|
V(scopeid_string, "scopeid") \
|
|
|
|
V(script_id_string, "scriptId") \
|
|
|
|
V(script_name_string, "scriptName") \
|
|
|
|
+ V(selector_string, "selector") \
|
|
|
|
V(serial_number_string, "serialNumber") \
|
|
|
|
V(serial_string, "serial") \
|
|
|
|
V(servername_string, "servername") \
|