Merge pull request #30809 from yuwata/resolve-fix-EDE-handling

resolve: fix EDE handling
This commit is contained in:
Luca Boccassi 2024-01-10 19:21:55 +00:00 committed by GitHub
commit dadd7d46d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 818 additions and 208 deletions

View File

@ -196,6 +196,20 @@ executables += [
],
'include_directories' : resolve_includes,
},
test_template + {
'sources' : [
files('test-resolved-dummy-server.c'),
basic_dns_sources,
systemd_resolved_sources,
],
'dependencies' : [
lib_openssl_or_gcrypt,
libm,
systemd_resolved_dependencies,
],
'include_directories' : resolve_includes,
'type' : 'manual',
},
resolve_fuzz_template + {
'sources' : files('fuzz-dns-packet.c'),
},

View File

@ -2715,24 +2715,26 @@ static int print_answer(JsonVariant *answer) {
static void monitor_query_dump(JsonVariant *v) {
_cleanup_(json_variant_unrefp) JsonVariant *question = NULL, *answer = NULL, *collected_questions = NULL;
int rcode = -1, error = 0, r;
const char *state = NULL;
int rcode = -1, error = 0, ede_code = -1;
const char *state = NULL, *result = NULL, *ede_msg = NULL;
assert(v);
JsonDispatch dispatch_table[] = {
{ "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY },
{ "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 },
{ "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 },
{ "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY },
{ "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 },
{ "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 },
{ "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY },
{ "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 },
{ "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 },
{ "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY },
{ "result", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&result), 0 },
{ "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 },
{ "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 },
{ "extendedDNSErrorCode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&ede_code), 0 },
{ "extendedDNSErrorMessage", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&ede_msg), 0 },
{}
};
r = json_dispatch(v, dispatch_table, 0, NULL);
if (r < 0)
return (void) log_warning("Received malformed monitor message, ignoring.");
if (json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, NULL) < 0)
return;
/* First show the current question */
print_question('Q', ansi_highlight_cyan(), question);
@ -2740,7 +2742,7 @@ static void monitor_query_dump(JsonVariant *v) {
/* And then show the questions that led to this one in case this was a CNAME chain */
print_question('C', ansi_highlight_grey(), collected_questions);
printf("%s%s S%s: %s\n",
printf("%s%s S%s: %s",
streq_ptr(state, "success") ? ansi_highlight_green() : ansi_highlight_red(),
special_glyph(SPECIAL_GLYPH_ARROW_LEFT),
ansi_normal(),
@ -2748,6 +2750,17 @@ static void monitor_query_dump(JsonVariant *v) {
streq_ptr(state, "rcode-failure") ? dns_rcode_to_string(rcode) :
state));
if (!isempty(result))
printf(": %s", result);
if (ede_code >= 0)
printf(" (%s%s%s)",
FORMAT_DNS_EDE_RCODE(ede_code),
!isempty(ede_msg) ? ": " : "",
strempty(ede_msg));
puts("");
print_answer(answer);
}
@ -2856,7 +2869,7 @@ static int dump_cache_item(JsonVariant *item) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
int r, c = 0;
r = json_dispatch(item, dispatch_table, JSON_LOG, &item_info);
r = json_dispatch(item, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &item_info);
if (r < 0)
return r;
@ -2918,7 +2931,7 @@ static int dump_cache_scope(JsonVariant *scope) {
{},
};
r = json_dispatch(scope, dispatch_table, JSON_LOG, &scope_info);
r = json_dispatch(scope, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &scope_info);
if (r < 0)
return r;
@ -3034,7 +3047,7 @@ static int dump_server_state(JsonVariant *server) {
{},
};
r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_PERMISSIVE, &server_state);
r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &server_state);
if (r < 0)
return r;

View File

@ -145,8 +145,13 @@ static int reply_query_state(DnsQuery *q) {
return reply_method_errorf(q, BUS_ERROR_ABORTED, "Query aborted");
case DNS_TRANSACTION_DNSSEC_FAILED:
return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s",
dnssec_result_to_string(q->answer_dnssec_result));
return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s%s%s%s%s%s",
dnssec_result_to_string(q->answer_dnssec_result),
q->answer_ede_rcode >= 0 ? " (" : "",
q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "",
(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "",
q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "",
q->answer_ede_rcode >= 0 ? ")" : "");
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return reply_method_errorf(q, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known");
@ -183,17 +188,18 @@ static int reply_query_state(DnsQuery *q) {
rc = FORMAT_DNS_RCODE(q->answer_rcode);
n = strjoina(_BUS_ERROR_DNS, rc);
sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc);
sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error: %s%s%s%s%s%s",
dns_query_string(q), rc,
q->answer_ede_rcode >= 0 ? " (" : "",
q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "",
(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "",
q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "",
q->answer_ede_rcode >= 0 ? ")" : "");
}
return sd_bus_reply_method_error(req, &error);
}
case DNS_TRANSACTION_UPSTREAM_DNSSEC_FAILURE:
return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed upstream: %s%s%s",
dns_ede_rcode_to_string(q->answer_ede_rcode),
isempty(q->answer_ede_msg) ? "" : ": ", q->answer_ede_msg);
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_VALIDATING:

View File

@ -2564,6 +2564,7 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
[DNSSEC_UPSTREAM_FAILURE] = "upstream-failure",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);

View File

@ -20,11 +20,12 @@ enum DnssecResult {
DNSSEC_NO_SIGNATURE,
DNSSEC_MISSING_KEY,
/* These two are added by the DnsTransaction logic */
/* These five are added by the DnsTransaction logic */
DNSSEC_UNSIGNED,
DNSSEC_FAILED_AUXILIARY,
DNSSEC_NSEC_MISMATCH,
DNSSEC_INCOMPATIBLE_SERVER,
DNSSEC_UPSTREAM_FAILURE,
_DNSSEC_RESULT_MAX,
_DNSSEC_RESULT_INVALID = -EINVAL,

View File

@ -2588,17 +2588,15 @@ bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b) {
return dns_packet_compare_func(a, b) == 0;
}
int dns_packet_ede_rcode(DnsPacket *p, char **ret_ede_msg) {
assert(p);
_cleanup_free_ char *msg = NULL, *msg_escaped = NULL;
int ede_rcode = _DNS_EDNS_OPT_MAX_DEFINED;
int r;
int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg) {
const uint8_t *d;
size_t l;
int r;
assert(p);
if (!p->opt)
return _DNS_EDE_RCODE_INVALID;
return -ENOENT;
d = p->opt->opt.data;
l = p->opt->opt.data_size;
@ -2618,31 +2616,40 @@ int dns_packet_ede_rcode(DnsPacket *p, char **ret_ede_msg) {
"Truncated option in EDNS0 variable part.");
if (code == DNS_EDNS_OPT_EXT_ERROR) {
_cleanup_free_ char *msg = NULL;
if (length < 2U)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"EDNS0 truncated EDE info code.");
ede_rcode = unaligned_read_be16(d + 4);
r = make_cstring((char *)d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg);
"EDNS0 truncated EDE info code.");
r = make_cstring((char *) d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg);
if (r < 0)
return log_debug_errno(r, "Invalid EDE text in opt");
else if (!utf8_is_valid(msg))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid EDE text in opt");
else if (ede_rcode < _DNS_EDNS_OPT_MAX_DEFINED) {
msg_escaped = cescape(msg);
if (!msg_escaped)
return -ENOMEM;
return log_debug_errno(r, "Invalid EDE text in opt.");
if (ret_ede_msg) {
if (!utf8_is_valid(msg)) {
_cleanup_free_ char *msg_escaped = NULL;
msg_escaped = cescape(msg);
if (!msg_escaped)
return log_oom_debug();
*ret_ede_msg = TAKE_PTR(msg_escaped);
} else
*ret_ede_msg = TAKE_PTR(msg);
}
break;
if (ret_ede_rcode)
*ret_ede_rcode = unaligned_read_be16(d + 4);
return 0;
}
d += 4U + length;
l -= 4U + length;
}
if (ret_ede_msg)
*ret_ede_msg = TAKE_PTR(msg_escaped);
return ede_rcode;
return -ENOENT;
}
bool dns_ede_rcode_is_dnssec(int ede_rcode) {
@ -2732,6 +2739,7 @@ static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
[DNS_RCODE_NXRRSET] = "NXRRSET",
[DNS_RCODE_NOTAUTH] = "NOTAUTH",
[DNS_RCODE_NOTZONE] = "NOTZONE",
[DNS_RCODE_DSOTYPENI] = "DSOTYPENI",
[DNS_RCODE_BADVERS] = "BADVERS",
[DNS_RCODE_BADKEY] = "BADKEY",
[DNS_RCODE_BADTIME] = "BADTIME",

View File

@ -253,94 +253,100 @@ int dns_packet_extract(DnsPacket *p);
bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b);
int dns_packet_ede_rcode(DnsPacket *p, char **ret_ede_msg);
int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg);
bool dns_ede_rcode_is_dnssec(int ede_rcode);
int dns_packet_has_nsid_request(DnsPacket *p);
/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */
enum {
DNS_RCODE_SUCCESS = 0,
DNS_RCODE_FORMERR = 1,
DNS_RCODE_SERVFAIL = 2,
DNS_RCODE_NXDOMAIN = 3,
DNS_RCODE_NOTIMP = 4,
DNS_RCODE_REFUSED = 5,
DNS_RCODE_YXDOMAIN = 6,
DNS_RCODE_YXRRSET = 7,
DNS_RCODE_NXRRSET = 8,
DNS_RCODE_NOTAUTH = 9,
DNS_RCODE_NOTZONE = 10,
DNS_RCODE_BADVERS = 16,
DNS_RCODE_BADSIG = 16, /* duplicate value! */
DNS_RCODE_BADKEY = 17,
DNS_RCODE_BADTIME = 18,
DNS_RCODE_BADMODE = 19,
DNS_RCODE_BADNAME = 20,
DNS_RCODE_BADALG = 21,
DNS_RCODE_BADTRUNC = 22,
DNS_RCODE_BADCOOKIE = 23,
DNS_RCODE_SUCCESS = 0,
DNS_RCODE_FORMERR = 1,
DNS_RCODE_SERVFAIL = 2,
DNS_RCODE_NXDOMAIN = 3,
DNS_RCODE_NOTIMP = 4,
DNS_RCODE_REFUSED = 5,
DNS_RCODE_YXDOMAIN = 6,
DNS_RCODE_YXRRSET = 7,
DNS_RCODE_NXRRSET = 8,
DNS_RCODE_NOTAUTH = 9,
DNS_RCODE_NOTZONE = 10,
DNS_RCODE_DSOTYPENI = 11,
/* 12-15 are unassigned. */
DNS_RCODE_BADVERS = 16,
DNS_RCODE_BADSIG = 16, /* duplicate value! */
DNS_RCODE_BADKEY = 17,
DNS_RCODE_BADTIME = 18,
DNS_RCODE_BADMODE = 19,
DNS_RCODE_BADNAME = 20,
DNS_RCODE_BADALG = 21,
DNS_RCODE_BADTRUNC = 22,
DNS_RCODE_BADCOOKIE = 23,
/* 24-3840 are unassigned. */
/* 3841-4095 are for private use. */
/* 4096-65534 are unassigned. */
_DNS_RCODE_MAX_DEFINED,
_DNS_RCODE_MAX = 4095 /* 4 bit rcode in the header plus 8 bit rcode in OPT, makes 12 bit */
_DNS_RCODE_MAX = 65535, /* reserved */
_DNS_RCODE_INVALID = -EINVAL,
};
/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11 */
enum {
DNS_EDNS_OPT_RESERVED = 0, /* RFC 6891 */
DNS_EDNS_OPT_LLQ = 1, /* RFC 8764 */
DNS_EDNS_OPT_UL = 2,
DNS_EDNS_OPT_NSID = 3, /* RFC 5001 */
/* DNS_EDNS_OPT_RESERVED = 4 */
DNS_EDNS_OPT_DAU = 5, /* RFC 6975 */
DNS_EDNS_OPT_DHU = 6, /* RFC 6975 */
DNS_EDNS_OPT_N3U = 7, /* RFC 6975 */
DNS_EDNS_OPT_RESERVED = 0, /* RFC 6891 */
DNS_EDNS_OPT_LLQ = 1, /* RFC 8764 */
DNS_EDNS_OPT_UL = 2,
DNS_EDNS_OPT_NSID = 3, /* RFC 5001 */
/* DNS_EDNS_OPT_RESERVED = 4 */
DNS_EDNS_OPT_DAU = 5, /* RFC 6975 */
DNS_EDNS_OPT_DHU = 6, /* RFC 6975 */
DNS_EDNS_OPT_N3U = 7, /* RFC 6975 */
DNS_EDNS_OPT_CLIENT_SUBNET = 8, /* RFC 7871 */
DNS_EDNS_OPT_EXPIRE = 9, /* RFC 7314 */
DNS_EDNS_OPT_COOKIE = 10, /* RFC 7873 */
DNS_EDNS_OPT_EXPIRE = 9, /* RFC 7314 */
DNS_EDNS_OPT_COOKIE = 10, /* RFC 7873 */
DNS_EDNS_OPT_TCP_KEEPALIVE = 11, /* RFC 7828 */
DNS_EDNS_OPT_PADDING = 12, /* RFC 7830 */
DNS_EDNS_OPT_CHAIN = 13, /* RFC 7901 */
DNS_EDNS_OPT_KEY_TAG = 14, /* RFC 8145 */
DNS_EDNS_OPT_EXT_ERROR = 15, /* RFC 8914 */
DNS_EDNS_OPT_CLIENT_TAG = 16,
DNS_EDNS_OPT_SERVER_TAG = 17,
DNS_EDNS_OPT_PADDING = 12, /* RFC 7830 */
DNS_EDNS_OPT_CHAIN = 13, /* RFC 7901 */
DNS_EDNS_OPT_KEY_TAG = 14, /* RFC 8145 */
DNS_EDNS_OPT_EXT_ERROR = 15, /* RFC 8914 */
DNS_EDNS_OPT_CLIENT_TAG = 16,
DNS_EDNS_OPT_SERVER_TAG = 17,
_DNS_EDNS_OPT_MAX_DEFINED,
_DNS_EDNS_OPT_INVALID = -EINVAL
_DNS_EDNS_OPT_INVALID = -EINVAL,
};
/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#extended-dns-error-codes */
enum {
DNS_EDE_RCODE_OTHER = 0, /* RFC 8914, Section 4.1 */
DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG = 1, /* RFC 8914, Section 4.2 */
DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST = 2, /* RFC 8914, Section 4.3 */
DNS_EDE_RCODE_STALE_ANSWER = 3, /* RFC 8914, Section 4.4 */
DNS_EDE_RCODE_FORGED_ANSWER = 4, /* RFC 8914, Section 4.5 */
DNS_EDE_RCODE_DNSSEC_INDETERMINATE = 5, /* RFC 8914, Section 4.6 */
DNS_EDE_RCODE_DNSSEC_BOGUS = 6, /* RFC 8914, Section 4.7 */
DNS_EDE_RCODE_SIG_EXPIRED = 7, /* RFC 8914, Section 4.8 */
DNS_EDE_RCODE_SIG_NOT_YET_VALID = 8, /* RFC 8914, Section 4.9 */
DNS_EDE_RCODE_DNSKEY_MISSING = 9, /* RFC 8914, Section 4.10 */
DNS_EDE_RCODE_RRSIG_MISSING = 10, /* RFC 8914, Section 4.11 */
DNS_EDE_RCODE_NO_ZONE_KEY_BIT = 11, /* RFC 8914, Section 4.12 */
DNS_EDE_RCODE_NSEC_MISSING = 12, /* RFC 8914, Section 4.13 */
DNS_EDE_RCODE_CACHED_ERROR = 13, /* RFC 8914, Section 4.14 */
DNS_EDE_RCODE_NOT_READY = 14, /* RFC 8914, Section 4.15 */
DNS_EDE_RCODE_BLOCKED = 15, /* RFC 8914, Section 4.16 */
DNS_EDE_RCODE_CENSORED = 16, /* RFC 8914, Section 4.17 */
DNS_EDE_RCODE_FILTERED = 17, /* RFC 8914, Section 4.18 */
DNS_EDE_RCODE_PROHIBITIED = 18, /* RFC 8914, Section 4.19 */
DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER = 19, /* RFC 8914, Section 4.20 */
DNS_EDE_RCODE_NOT_AUTHORITATIVE = 20, /* RFC 8914, Section 4.21 */
DNS_EDE_RCODE_NOT_SUPPORTED = 21, /* RFC 8914, Section 4.22 */
DNS_EDE_RCODE_UNREACH_AUTHORITY = 22, /* RFC 8914, Section 4.23 */
DNS_EDE_RCODE_NET_ERROR = 23, /* RFC 8914, Section 4.24 */
DNS_EDE_RCODE_INVALID_DATA = 24, /* RFC 8914, Section 4.25 */
DNS_EDE_RCODE_SIG_NEVER = 25,
DNS_EDE_RCODE_TOO_EARLY = 26, /* RFC 9250 */
DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER = 27, /* RFC 9276 */
DNS_EDE_RCODE_TRANSPORT_POLICY = 28,
DNS_EDE_RCODE_SYNTHESIZED = 29,
DNS_EDE_RCODE_OTHER = 0, /* RFC 8914, Section 4.1 */
DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG = 1, /* RFC 8914, Section 4.2 */
DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST = 2, /* RFC 8914, Section 4.3 */
DNS_EDE_RCODE_STALE_ANSWER = 3, /* RFC 8914, Section 4.4 */
DNS_EDE_RCODE_FORGED_ANSWER = 4, /* RFC 8914, Section 4.5 */
DNS_EDE_RCODE_DNSSEC_INDETERMINATE = 5, /* RFC 8914, Section 4.6 */
DNS_EDE_RCODE_DNSSEC_BOGUS = 6, /* RFC 8914, Section 4.7 */
DNS_EDE_RCODE_SIG_EXPIRED = 7, /* RFC 8914, Section 4.8 */
DNS_EDE_RCODE_SIG_NOT_YET_VALID = 8, /* RFC 8914, Section 4.9 */
DNS_EDE_RCODE_DNSKEY_MISSING = 9, /* RFC 8914, Section 4.10 */
DNS_EDE_RCODE_RRSIG_MISSING = 10, /* RFC 8914, Section 4.11 */
DNS_EDE_RCODE_NO_ZONE_KEY_BIT = 11, /* RFC 8914, Section 4.12 */
DNS_EDE_RCODE_NSEC_MISSING = 12, /* RFC 8914, Section 4.13 */
DNS_EDE_RCODE_CACHED_ERROR = 13, /* RFC 8914, Section 4.14 */
DNS_EDE_RCODE_NOT_READY = 14, /* RFC 8914, Section 4.15 */
DNS_EDE_RCODE_BLOCKED = 15, /* RFC 8914, Section 4.16 */
DNS_EDE_RCODE_CENSORED = 16, /* RFC 8914, Section 4.17 */
DNS_EDE_RCODE_FILTERED = 17, /* RFC 8914, Section 4.18 */
DNS_EDE_RCODE_PROHIBITIED = 18, /* RFC 8914, Section 4.19 */
DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER = 19, /* RFC 8914, Section 4.20 */
DNS_EDE_RCODE_NOT_AUTHORITATIVE = 20, /* RFC 8914, Section 4.21 */
DNS_EDE_RCODE_NOT_SUPPORTED = 21, /* RFC 8914, Section 4.22 */
DNS_EDE_RCODE_UNREACH_AUTHORITY = 22, /* RFC 8914, Section 4.23 */
DNS_EDE_RCODE_NET_ERROR = 23, /* RFC 8914, Section 4.24 */
DNS_EDE_RCODE_INVALID_DATA = 24, /* RFC 8914, Section 4.25 */
DNS_EDE_RCODE_SIG_NEVER = 25,
DNS_EDE_RCODE_TOO_EARLY = 26, /* RFC 9250 */
DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER = 27, /* RFC 9276 */
DNS_EDE_RCODE_TRANSPORT_POLICY = 28,
DNS_EDE_RCODE_SYNTHESIZED = 29,
_DNS_EDE_RCODE_MAX_DEFINED,
_DNS_EDE_RCODE_INVALID = -EINVAL
_DNS_EDE_RCODE_INVALID = -EINVAL,
};
const char* dns_rcode_to_string(int i) _const_;

View File

@ -368,6 +368,8 @@ static void dns_query_reset_answer(DnsQuery *q) {
q->answer = dns_answer_unref(q->answer);
q->answer_rcode = 0;
q->answer_ede_rcode = _DNS_EDE_RCODE_INVALID;
q->answer_ede_msg = mfree(q->answer_ede_msg);
q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
q->answer_errno = 0;
q->answer_query_flags = 0;
@ -421,8 +423,6 @@ DnsQuery *dns_query_free(DnsQuery *q) {
dns_answer_unref(q->reply_authoritative);
dns_answer_unref(q->reply_additional);
free(q->answer_ede_msg);
if (q->request_stream) {
/* Detach the stream from our query, in case something else keeps a reference to it. */
(void) set_remove(q->request_stream->queries, q);
@ -516,6 +516,7 @@ int dns_query_new(
.question_bypass = dns_packet_ref(question_bypass),
.ifindex = ifindex,
.flags = flags,
.answer_ede_rcode = _DNS_EDE_RCODE_INVALID,
.answer_dnssec_result = _DNSSEC_RESULT_INVALID,
.answer_protocol = _DNS_PROTOCOL_INVALID,
.answer_family = AF_UNSPEC,
@ -588,7 +589,7 @@ void dns_query_complete(DnsQuery *q, DnsTransactionState state) {
q->state = state;
(void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->question_bypass, q->collected_questions, q->answer);
(void) manager_monitor_send(q->manager, q);
dns_query_stop(q);
if (q->complete)
@ -898,20 +899,13 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
!FLAGS_SET(t->answer_query_flags, SD_RESOLVED_AUTHENTICATED))
continue;
char *answer_ede_msg = NULL;
if (t->answer_ede_msg) {
answer_ede_msg = strdup(t->answer_ede_msg);
if (!answer_ede_msg) {
r = log_oom();
goto fail;
}
}
DNS_ANSWER_REPLACE(q->answer, dns_answer_ref(t->answer));
q->answer_rcode = t->answer_rcode;
q->answer_dnssec_result = t->answer_dnssec_result;
q->answer_ede_rcode = t->answer_ede_rcode;
q->answer_ede_msg = answer_ede_msg;
r = free_and_strdup_warn(&q->answer_ede_msg, t->answer_ede_msg);
if (r < 0)
goto fail;
q->answer_dnssec_result = t->answer_dnssec_result;
q->answer_query_flags = t->answer_query_flags | dns_transaction_source_to_query_flags(t->answer_source);
q->answer_errno = t->answer_errno;
DNS_PACKET_REPLACE(q->answer_full_packet, dns_packet_ref(t->received));

View File

@ -73,9 +73,9 @@ struct DnsQuery {
/* Discovered data */
DnsAnswer *answer;
int answer_rcode;
DnssecResult answer_dnssec_result;
int answer_ede_rcode;
char *answer_ede_msg;
DnssecResult answer_dnssec_result;
uint64_t answer_query_flags;
DnsProtocol answer_protocol;
int answer_family;

View File

@ -28,6 +28,8 @@ static void dns_transaction_reset_answer(DnsTransaction *t) {
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
t->answer_rcode = 0;
t->answer_ede_rcode = _DNS_EDE_RCODE_INVALID;
t->answer_ede_msg = mfree(t->answer_ede_msg);
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
t->answer_query_flags = 0;
@ -166,8 +168,6 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
dns_resource_key_unref(t->key);
dns_packet_unref(t->bypass);
free(t->answer_ede_msg);
return mfree(t);
}
@ -411,21 +411,6 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
"DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level));
}
if (state == DNS_TRANSACTION_UPSTREAM_DNSSEC_FAILURE) {
dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str);
log_struct(LOG_NOTICE,
"MESSAGE_ID=" SD_MESSAGE_DNSSEC_FAILURE_STR,
LOG_MESSAGE("Upstream resolver reported failure for question %s: %s%s%s",
key_str, dns_ede_rcode_to_string(t->answer_ede_rcode),
isempty(t->answer_ede_msg) ? "" : ": ", t->answer_ede_msg),
"DNS_TRANSACTION=%" PRIu16, t->id,
"DNS_QUESTION=%s", key_str,
"DNS_EDE_RCODE=%s", dns_ede_rcode_to_string(t->answer_ede_rcode),
"DNS_SERVER=%s", strna(dns_server_string_full(t->server)),
"DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level));
}
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
@ -903,8 +888,21 @@ static int dns_transaction_dnssec_ready(DnsTransaction *t) {
/* We handle DNSSEC failures different from other errors, as we care about the DNSSEC
* validation result */
log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result));
t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */
log_debug("Auxiliary DNSSEC RR query failed validation: %s%s%s%s%s%s",
dnssec_result_to_string(dt->answer_dnssec_result),
dt->answer_ede_rcode >= 0 ? " (" : "",
dt->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(dt->answer_ede_rcode) : "",
(dt->answer_ede_rcode >= 0 && !isempty(dt->answer_ede_msg)) ? ": " : "",
dt->answer_ede_rcode >= 0 ? strempty(dt->answer_ede_msg) : "",
dt->answer_ede_rcode >= 0 ? ")" : "");
/* Copy error code over */
t->answer_dnssec_result = dt->answer_dnssec_result;
t->answer_ede_rcode = dt->answer_ede_rcode;
r = free_and_strdup(&t->answer_ede_msg, dt->answer_ede_msg);
if (r < 0)
log_oom_debug();
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return 0;
@ -1223,44 +1221,37 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
switch (t->scope->protocol) {
case DNS_PROTOCOL_DNS: {
int ede_rcode;
_cleanup_free_ char *ede_msg = NULL;
assert(t->server);
ede_rcode = dns_packet_ede_rcode(p, &ede_msg);
if (ede_rcode < 0 && ede_rcode != -EINVAL)
log_debug_errno(ede_rcode, "Unable to extract EDE error code from packet, ignoring: %m");
else {
t->answer_ede_rcode = ede_rcode;
t->answer_ede_msg = TAKE_PTR(ede_msg);
}
(void) dns_packet_ede_rcode(p, &t->answer_ede_rcode, &t->answer_ede_msg);
if (!t->bypass &&
IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
/* If the server has replied with detailed error data, using a degraded feature set
* will likely not help anyone. Examine the detailed error to determine the best
* course of action. */
if (ede_rcode >= 0 && DNS_PACKET_RCODE(p) == DNS_RCODE_SERVFAIL) {
if (t->answer_ede_rcode >= 0 && DNS_PACKET_RCODE(p) == DNS_RCODE_SERVFAIL) {
/* These codes are related to DNSSEC configuration errors. If accurate,
* this is the domain operator's problem, and retrying won't help. */
if (dns_ede_rcode_is_dnssec(ede_rcode)) {
if (dns_ede_rcode_is_dnssec(t->answer_ede_rcode)) {
log_debug("Server returned error: %s (%s%s%s). Lookup failed.",
FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
FORMAT_DNS_EDE_RCODE(ede_rcode),
isempty(t->answer_ede_msg) ? "" : ": ",
t->answer_ede_msg);
dns_transaction_complete(t, DNS_TRANSACTION_UPSTREAM_DNSSEC_FAILURE);
FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
isempty(t->answer_ede_msg) ? "" : ": ",
strempty(t->answer_ede_msg));
t->answer_dnssec_result = DNSSEC_UPSTREAM_FAILURE;
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return;
}
/* These codes probably indicate a transient error. Let's try again. */
if (IN_SET(ede_rcode, DNS_EDE_RCODE_NOT_READY, DNS_EDE_RCODE_NET_ERROR)) {
if (IN_SET(t->answer_ede_rcode, DNS_EDE_RCODE_NOT_READY, DNS_EDE_RCODE_NET_ERROR)) {
log_debug("Server returned error: %s (%s%s%s), retrying transaction.",
FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
FORMAT_DNS_EDE_RCODE(ede_rcode),
isempty(t->answer_ede_msg) ? "" : ": ",
t->answer_ede_msg);
FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
isempty(t->answer_ede_msg) ? "" : ": ",
strempty(t->answer_ede_msg));
dns_transaction_retry(t, false);
return;
}
@ -1268,11 +1259,12 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
/* OK, the query failed, but we still shouldn't degrade the feature set for
* this server. */
log_debug("Server returned error: %s (%s%s%s)",
FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
FORMAT_DNS_EDE_RCODE(ede_rcode),
isempty(t->answer_ede_msg) ? "" : ": ", t->answer_ede_msg);
FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
isempty(t->answer_ede_msg) ? "" : ": ",
strempty(t->answer_ede_msg));
break;
} /* No EDE rcode, or EDE rcode we don't understand */
}
/* Request failed, immediately try again with reduced features */
@ -1329,9 +1321,9 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
if (DNS_PACKET_RCODE(p) == DNS_RCODE_REFUSED) {
/* This server refused our request? If so, try again, use a different server */
if (ede_rcode > 0)
if (t->answer_ede_rcode >= 0)
log_debug("Server returned REFUSED (%s), switching servers, and retrying.",
FORMAT_DNS_EDE_RCODE(ede_rcode));
FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode));
else
log_debug("Server returned REFUSED, switching servers, and retrying.");
@ -1829,8 +1821,12 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
t->answer_source = DNS_TRANSACTION_CACHE;
if (t->answer_rcode == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
else
else {
if (t->received)
(void) dns_packet_ede_rcode(t->received, &t->answer_ede_rcode, &t->answer_ede_msg);
dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE);
}
return 0;
}
}

View File

@ -20,7 +20,6 @@ enum DnsTransactionState {
DNS_TRANSACTION_PENDING,
DNS_TRANSACTION_VALIDATING,
DNS_TRANSACTION_RCODE_FAILURE,
DNS_TRANSACTION_UPSTREAM_DNSSEC_FAILURE,
DNS_TRANSACTION_SUCCESS,
DNS_TRANSACTION_NO_SERVERS,
DNS_TRANSACTION_TIMEOUT,

View File

@ -894,7 +894,7 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
return 1;
}
static int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
usec_t end;
int r;
@ -1098,17 +1098,7 @@ static int dns_question_to_json(DnsQuestion *q, JsonVariant **ret) {
return 0;
}
int manager_monitor_send(
Manager *m,
int state,
int rcode,
int error,
DnsQuestion *question_idna,
DnsQuestion *question_utf8,
DnsPacket *question_bypass,
DnsQuestion *collected_questions,
DnsAnswer *answer) {
int manager_monitor_send(Manager *m, DnsQuery *q) {
_cleanup_(json_variant_unrefp) JsonVariant *jquestion = NULL, *jcollected_questions = NULL, *janswer = NULL;
_cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL;
Varlink *connection;
@ -1121,14 +1111,14 @@ int manager_monitor_send(
return 0;
/* Merge all questions into one */
r = dns_question_merge(question_idna, question_utf8, &merged);
r = dns_question_merge(q->question_idna, q->question_utf8, &merged);
if (r < 0)
return log_error_errno(r, "Failed to merge UTF8/IDNA questions: %m");
if (question_bypass) {
if (q->question_bypass) {
_cleanup_(dns_question_unrefp) DnsQuestion *merged2 = NULL;
r = dns_question_merge(merged, question_bypass->question, &merged2);
r = dns_question_merge(merged, q->question_bypass->question, &merged2);
if (r < 0)
return log_error_errno(r, "Failed to merge UTF8/IDNA questions and DNS packet question: %m");
@ -1142,11 +1132,11 @@ int manager_monitor_send(
return log_error_errno(r, "Failed to convert question to JSON: %m");
/* Generate a JSON array of the questions preceding the current one in the CNAME chain */
r = dns_question_to_json(collected_questions, &jcollected_questions);
r = dns_question_to_json(q->collected_questions, &jcollected_questions);
if (r < 0)
return log_error_errno(r, "Failed to convert question to JSON: %m");
DNS_ANSWER_FOREACH_ITEM(rri, answer) {
DNS_ANSWER_FOREACH_ITEM(rri, q->answer) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
r = dns_resource_record_to_json(rri->rr, &v);
@ -1169,12 +1159,28 @@ int manager_monitor_send(
SET_FOREACH(connection, m->varlink_subscription) {
r = varlink_notifyb(connection,
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(state))),
JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_RCODE_FAILURE, "rcode", JSON_BUILD_INTEGER(rcode)),
JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_ERRNO, "errno", JSON_BUILD_INTEGER(error)),
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(q->state))),
JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_DNSSEC_FAILED,
"result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_RCODE_FAILURE,
"rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_ERRNO,
"errno", JSON_BUILD_INTEGER(q->answer_errno)),
JSON_BUILD_PAIR_CONDITION(IN_SET(q->state,
DNS_TRANSACTION_DNSSEC_FAILED,
DNS_TRANSACTION_RCODE_FAILURE) &&
q->answer_ede_rcode >= 0,
"extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
JSON_BUILD_PAIR_CONDITION(IN_SET(q->state,
DNS_TRANSACTION_DNSSEC_FAILED,
DNS_TRANSACTION_RCODE_FAILURE) &&
q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
"extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)),
JSON_BUILD_PAIR("question", JSON_BUILD_VARIANT(jquestion)),
JSON_BUILD_PAIR_CONDITION(jcollected_questions, "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)),
JSON_BUILD_PAIR_CONDITION(janswer, "answer", JSON_BUILD_VARIANT(janswer))));
JSON_BUILD_PAIR_CONDITION(jcollected_questions,
"collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)),
JSON_BUILD_PAIR_CONDITION(janswer,
"answer", JSON_BUILD_VARIANT(janswer))));
if (r < 0)
log_debug_errno(r, "Failed to send monitor event, ignoring: %m");
}

View File

@ -176,8 +176,9 @@ int manager_start(Manager *m);
uint32_t manager_find_mtu(Manager *m);
int manager_monitor_send(Manager *m, int state, int rcode, int error, DnsQuestion *question_idna, DnsQuestion *question_utf8, DnsPacket *question_bypass, DnsQuestion *collected_questions, DnsAnswer *answer);
int manager_monitor_send(Manager *m, DnsQuery *q);
int sendmsg_loop(int fd, struct msghdr *mh, int flags);
int manager_write(Manager *m, int fd, DnsPacket *p);
int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p);
int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret);

View File

@ -49,7 +49,11 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_DNSSEC_FAILED:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed",
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result)))));
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
"extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
"extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL);
@ -74,7 +78,11 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_RCODE_FAILURE:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError",
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode))));
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
"extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
"extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:

View File

@ -0,0 +1,450 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-daemon.h"
#include "fd-util.h"
#include "iovec-util.h"
#include "log.h"
#include "main-func.h"
#include "resolved-dns-packet.h"
#include "resolved-manager.h"
#include "socket-netlink.h"
#include "socket-util.h"
/* Taken from resolved-dns-stub.c */
#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U)
/* This is more or less verbatim manager_recv() from resolved-manager.c, sans the manager stuff */
static int server_recv(int fd, DnsPacket **ret) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
CMSG_BUFFER_TYPE(CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo))
+ CMSG_SPACE(int) /* ttl/hoplimit */
+ EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */) control;
union sockaddr_union sa;
struct iovec iov;
struct msghdr mh = {
.msg_name = &sa.sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg;
ssize_t ms, l;
int r;
assert(fd >= 0);
assert(ret);
ms = next_datagram_size_fd(fd);
if (ms < 0)
return ms;
r = dns_packet_new(&p, DNS_PROTOCOL_DNS, ms, DNS_PACKET_SIZE_MAX);
if (r < 0)
return r;
iov = IOVEC_MAKE(DNS_PACKET_DATA(p), p->allocated);
l = recvmsg_safe(fd, &mh, 0);
if (ERRNO_IS_NEG_TRANSIENT(l))
return 0;
if (l <= 0)
return l;
assert(!(mh.msg_flags & MSG_TRUNC));
p->size = (size_t) l;
p->family = sa.sa.sa_family;
p->ipproto = IPPROTO_UDP;
if (p->family == AF_INET) {
p->sender.in = sa.in.sin_addr;
p->sender_port = be16toh(sa.in.sin_port);
} else if (p->family == AF_INET6) {
p->sender.in6 = sa.in6.sin6_addr;
p->sender_port = be16toh(sa.in6.sin6_port);
p->ifindex = sa.in6.sin6_scope_id;
} else
return -EAFNOSUPPORT;
p->timestamp = now(CLOCK_BOOTTIME);
CMSG_FOREACH(cmsg, &mh) {
if (cmsg->cmsg_level == IPPROTO_IPV6) {
assert(p->family == AF_INET6);
switch (cmsg->cmsg_type) {
case IPV6_PKTINFO: {
struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
if (p->ifindex <= 0)
p->ifindex = i->ipi6_ifindex;
p->destination.in6 = i->ipi6_addr;
break;
}
case IPV6_HOPLIMIT:
p->ttl = *CMSG_TYPED_DATA(cmsg, int);
break;
case IPV6_RECVFRAGSIZE:
p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
break;
}
} else if (cmsg->cmsg_level == IPPROTO_IP) {
assert(p->family == AF_INET);
switch (cmsg->cmsg_type) {
case IP_PKTINFO: {
struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
if (p->ifindex <= 0)
p->ifindex = i->ipi_ifindex;
p->destination.in = i->ipi_addr;
break;
}
case IP_TTL:
p->ttl = *CMSG_TYPED_DATA(cmsg, int);
break;
case IP_RECVFRAGSIZE:
p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
break;
}
}
}
/* The Linux kernel sets the interface index to the loopback
* device if the packet came from the local host since it
* avoids the routing table in such a case. Let's unset the
* interface index in such a case. */
if (p->ifindex == LOOPBACK_IFINDEX)
p->ifindex = 0;
log_debug("Received DNS UDP packet of size %zu, ifindex=%i, ttl=%u, fragsize=%zu, sender=%s, destination=%s",
p->size, p->ifindex, p->ttl, p->fragsize,
IN_ADDR_TO_STRING(p->family, &p->sender),
IN_ADDR_TO_STRING(p->family, &p->destination));
*ret = TAKE_PTR(p);
return 1;
}
/* Same as above, see manager_ipv4_send() in resolved-manager.c */
static int server_ipv4_send(
int fd,
const struct in_addr *destination,
uint16_t port,
const struct in_addr *source,
DnsPacket *packet) {
union sockaddr_union sa;
struct iovec iov;
struct msghdr mh = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_name = &sa.sa,
.msg_namelen = sizeof(sa.in),
};
assert(fd >= 0);
assert(destination);
assert(port > 0);
assert(packet);
iov = IOVEC_MAKE(DNS_PACKET_DATA(packet), packet->size);
sa = (union sockaddr_union) {
.in.sin_family = AF_INET,
.in.sin_addr = *destination,
.in.sin_port = htobe16(port),
};
return sendmsg_loop(fd, &mh, 0);
}
static int make_reply_packet(DnsPacket *packet, DnsPacket **ret) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
int r;
assert(packet);
assert(ret);
r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_PAYLOAD_SIZE_MAX(packet));
if (r < 0)
return r;
r = dns_packet_append_question(p, packet->question);
if (r < 0)
return r;
DNS_PACKET_HEADER(p)->id = DNS_PACKET_ID(packet);
DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(packet->question));
*ret = TAKE_PTR(p);
return 0;
}
static int reply_append_edns(DnsPacket *packet, DnsPacket *reply, const char *extra_text, size_t rcode, uint16_t ede_code) {
size_t saved_size;
int r;
assert(packet);
assert(reply);
/* Append EDNS0 stuff (inspired by dns_packet_append_opt() from resolved-dns-packet.c).
*
* Relevant headers from RFC 6891:
*
* +------------+--------------+------------------------------+
* | Field Name | Field Type | Description |
* +------------+--------------+------------------------------+
* | NAME | domain name | MUST be 0 (root domain) |
* | TYPE | u_int16_t | OPT (41) |
* | CLASS | u_int16_t | requestor's UDP payload size |
* | TTL | u_int32_t | extended RCODE and flags |
* | RDLEN | u_int16_t | length of all RDATA |
* | RDATA | octet stream | {attribute,value} pairs |
* +------------+--------------+------------------------------+
*
* +0 (MSB) +1 (LSB)
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* 0: | OPTION-CODE |
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* 2: | OPTION-LENGTH |
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* 4: | |
* / OPTION-DATA /
* / /
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*
* And from RFC 8914:
*
* 1 1 1 1 1 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* 0: | OPTION-CODE |
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* 2: | OPTION-LENGTH |
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* 4: | INFO-CODE |
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* 6: / EXTRA-TEXT ... /
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
saved_size = reply->size;
/* empty name */
r = dns_packet_append_uint8(reply, 0, NULL);
if (r < 0)
return r;
/* type */
r = dns_packet_append_uint16(reply, DNS_TYPE_OPT, NULL);
if (r < 0)
return r;
/* class: maximum udp packet that can be received */
r = dns_packet_append_uint16(reply, ADVERTISE_DATAGRAM_SIZE_MAX, NULL);
if (r < 0)
return r;
/* extended RCODE and VERSION */
r = dns_packet_append_uint16(reply, ((uint16_t) rcode & 0x0FF0) << 4, NULL);
if (r < 0)
return r;
/* flags: DNSSEC OK (DO), see RFC3225 */
r = dns_packet_append_uint16(reply, 0, NULL);
if (r < 0)
return r;
/* RDATA */
size_t extra_text_len = isempty(extra_text) ? 0 : strlen(extra_text);
/* RDLENGTH (OPTION CODE + OPTION LENGTH + INFO-CODE + EXTRA-TEXT) */
r = dns_packet_append_uint16(reply, 2 + 2 + 2 + extra_text_len, NULL);
if (r < 0)
return 0;
/* OPTION-CODE: 15 for EDE */
r = dns_packet_append_uint16(reply, 15, NULL);
if (r < 0)
return r;
/* OPTION-LENGTH: INFO-CODE + EXTRA-TEXT */
r = dns_packet_append_uint16(reply, 2 + extra_text_len, NULL);
if (r < 0)
return r;
/* INFO-CODE: EDE code */
r = dns_packet_append_uint16(reply, ede_code, NULL);
if (r < 0)
return r;
/* EXTRA-TEXT */
if (extra_text_len > 0) {
/* From RFC 8914:
* EDE text may be null terminated but MUST NOT be assumed to be; the length MUST be derived
* from the OPTION-LENGTH field
*
* Let's exercise our code on the receiving side and not NUL-terminate the EXTRA-TEXT field
*/
r = dns_packet_append_blob(reply, extra_text, extra_text_len, NULL);
if (r < 0)
return r;
}
DNS_PACKET_HEADER(reply)->arcount = htobe16(DNS_PACKET_ARCOUNT(reply) + 1);
reply->opt_start = saved_size;
reply->opt_size = reply->size - saved_size;
/* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */
DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode));
return 0;
}
static void server_fail(DnsPacket *packet, DnsPacket *reply, int rcode) {
assert(reply);
/* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */
DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode));
}
static int server_handle_edns_bogus_dnssec(DnsPacket *packet, DnsPacket *reply) {
assert(packet);
assert(reply);
return reply_append_edns(packet, reply, NULL, DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_DNSSEC_BOGUS);
}
static int server_handle_edns_extra_text(DnsPacket *packet, DnsPacket *reply) {
assert(packet);
assert(reply);
return reply_append_edns(packet, reply, "Nothing to see here!", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_CENSORED);
}
static int server_handle_edns_invalid_code(DnsPacket *packet, DnsPacket *reply, const char *extra_text) {
assert(packet);
assert(reply);
assert_cc(_DNS_EDE_RCODE_MAX_DEFINED < UINT16_MAX);
return reply_append_edns(packet, reply, extra_text, DNS_RCODE_SERVFAIL, _DNS_EDE_RCODE_MAX_DEFINED + 1);
}
static int server_handle_edns_code_zero(DnsPacket *packet, DnsPacket *reply) {
assert(packet);
assert(reply);
assert_cc(DNS_EDE_RCODE_OTHER == 0);
return reply_append_edns(packet, reply, "\xF0\x9F\x90\xB1", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_OTHER);
}
static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
_cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
const char *name;
int r;
assert(fd >= 0);
r = server_recv(fd, &packet);
if (r < 0) {
log_debug_errno(r, "Failed to receive packet, ignoring: %m");
return 0;
}
r = dns_packet_validate_query(packet);
if (r < 0) {
log_debug_errno(r, "Invalid DNS UDP packet, ignoring.");
return 0;
}
r = dns_packet_extract(packet);
if (r < 0) {
log_debug_errno(r, "Failed to extract DNS packet, ignoring: %m");
return 0;
}
name = dns_question_first_name(packet->question);
log_info("Processing question for name '%s'", name);
(void) dns_question_dump(packet->question, stdout);
r = make_reply_packet(packet, &reply);
if (r < 0) {
log_debug_errno(r, "Failed to make reply packet, ignoring: %m");
return 0;
}
if (streq_ptr(name, "edns-bogus-dnssec.forwarded.test"))
r = server_handle_edns_bogus_dnssec(packet, reply);
else if (streq_ptr(name, "edns-extra-text.forwarded.test"))
r = server_handle_edns_extra_text(packet, reply);
else if (streq_ptr(name, "edns-invalid-code.forwarded.test"))
r = server_handle_edns_invalid_code(packet, reply, NULL);
else if (streq_ptr(name, "edns-invalid-code-with-extra-text.forwarded.test"))
r = server_handle_edns_invalid_code(packet, reply, "Hello [#]$%~ World");
else if (streq_ptr(name, "edns-code-zero.forwarded.test"))
r = server_handle_edns_code_zero(packet, reply);
else
r = log_debug_errno(SYNTHETIC_ERRNO(EFAULT), "Unhandled name '%s', ignoring.", name);
if (r < 0)
server_fail(packet, reply, DNS_RCODE_NXDOMAIN);
r = server_ipv4_send(fd, &packet->sender.in, packet->sender_port, &packet->destination.in, reply);
if (r < 0)
log_debug_errno(r, "Failed to send reply, ignoring: %m");
return 0;
}
static int run(int argc, char *argv[]) {
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_close_ int fd = -EBADF;
int r;
log_setup();
if (argc != 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"This program takes one argument in format ip_address:port");
fd = make_socket_fd(LOG_DEBUG, argv[1], SOCK_DGRAM, SOCK_CLOEXEC);
if (fd < 0)
return log_error_errno(fd, "Failed to listen on address '%s': %m", argv[1]);
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event: %m");
r = sd_event_add_io(event, NULL, fd, EPOLLIN, on_dns_packet, NULL);
if (r < 0)
return log_error_errno(r, "Failed to add IO event source: %m");
r = sd_event_set_signal_exit(event, true);
if (r < 0)
return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
(void) sd_notify(/* unset_environment=false */ false, "READY=1");
r = sd_event_loop(event);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
return 0;
}
DEFINE_MAIN_FUNCTION(run);

View File

@ -78,8 +78,11 @@ VARLINK_DEFINE_METHOD(
VARLINK_DEFINE_OUTPUT(ready, VARLINK_BOOL, VARLINK_NULLABLE),
/* Subsequent replies */
VARLINK_DEFINE_OUTPUT(state, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(result, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(rcode, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(errno, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT_BY_TYPE(question, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(collectedQuestions, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(answer, Answer, VARLINK_NULLABLE|VARLINK_ARRAY));

View File

@ -40,7 +40,9 @@ static VARLINK_DEFINE_ERROR(InvalidReply);
static VARLINK_DEFINE_ERROR(QueryAborted);
static VARLINK_DEFINE_ERROR(
DNSSECValidationFailed,
VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0));
VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(NoTrustAnchor);
static VARLINK_DEFINE_ERROR(ResourceRecordTypeUnsupported);
static VARLINK_DEFINE_ERROR(NetworkDown);
@ -48,7 +50,9 @@ static VARLINK_DEFINE_ERROR(NoSource);
static VARLINK_DEFINE_ERROR(StubLoop);
static VARLINK_DEFINE_ERROR(
DNSError,
VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0));
VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(CNAMELoop);
static VARLINK_DEFINE_ERROR(BadAddressSize);

View File

@ -29,6 +29,9 @@ remote:
address: 10.0.0.1@53
address: fd00:dead:beef:cafe::1@53
- id: forwarded
address: 10.99.0.1@53
submission:
- id: parent_zone_sbm
check-interval: 2s
@ -69,6 +72,11 @@ policy:
- id: manual
manual: on
mod-dnsproxy:
- id: forwarded
remote: forwarded
fallback: off
template:
# Sign everything by default and propagate the respective DS records to the parent
- id: default
@ -86,6 +94,11 @@ template:
semantic-checks: on
storage: "/var/lib/knot/zones"
- id: forwarded
dnssec-signing: off
module: mod-dnsproxy/forwarded
zonefile-load: none
zone:
# Create our own DNSSEC-aware root zone, so we can test the whole chain of
# trust. This needs a ZSK/KSK keypair to be generated before running knot +
@ -119,3 +132,7 @@ zone:
# An unsigned zone
- domain: unsigned.test
template: unsigned
# Forward all queries for this zone to our dummy test server
- domain: forwarded.test
template: forwarded

View File

@ -197,6 +197,25 @@ DNSSEC=allow-downgrade
DNS=10.0.0.1
DNS=fd00:dead:beef:cafe::1
EOF
cat >/etc/systemd/network/10-dns1.netdev <<EOF
[NetDev]
Name=dns1
Kind=dummy
EOF
cat >/etc/systemd/network/10-dns1.network <<EOF
[Match]
Name=dns1
[Network]
Address=10.99.0.1/24
DNSSEC=no
EOF
systemctl edit --stdin --full --runtime --force "resolved-dummy-server.service" <<EOF
[Service]
Type=notify
Environment=SYSTEMD_LOG_LEVEL=debug
ExecStart=/usr/lib/systemd/tests/unit-tests/manual/test-resolved-dummy-server 10.99.0.1:53
EOF
DNS_ADDRESSES=(
"10.0.0.1"
@ -236,6 +255,7 @@ ln -svf /etc/bind.keys /etc/bind/bind.keys
systemctl unmask systemd-networkd
systemctl start systemd-networkd
restart_resolved
systemctl start resolved-dummy-server
# Create knot's runtime dir, since from certain version it's provided only by
# the package and not created by tmpfiles/systemd
if [[ ! -d /run/knot ]]; then
@ -246,6 +266,7 @@ systemctl start knot
# Wait a bit for the keys to propagate
sleep 4
systemctl status resolved-dummy-server
networkctl status
resolvectl status
resolvectl log-level debug
@ -254,7 +275,14 @@ resolvectl log-level debug
systemd-run -u resolvectl-monitor.service -p Type=notify resolvectl monitor
systemd-run -u resolvectl-monitor-json.service -p Type=notify resolvectl monitor --json=short
knotc --force zone-check
# FIXME: knot, unfortunately, incorrectly complains about missing zone files for zones
# that are forwarded using the `dnsproxy` module. Until the issue is resolved,
# let's fall back to pre-processing the `zone-check` output a bit before checking it
#
# See: https://gitlab.nic.cz/knot/knot-dns/-/issues/913
run knotc zone-check || :
sed -i '/forwarded.test./d' "$RUN_OUT"
[[ ! -s "$RUN_OUT" ]]
# We need to manually propagate the DS records of onlinesign.test. to the parent
# zone, since they're generated online
knotc zone-begin test.
@ -552,6 +580,61 @@ grep -qF "fd00:dead:beef:cafe::123" "$RUN_OUT"
#run dig +dnssec this.does.not.exist.untrusted.test
#grep -qF "status: NXDOMAIN" "$RUN_OUT"
: "--- ZONE: forwarded.test (queries forwarded to our dummy test server) ---"
JOURNAL_CURSOR="$(mktemp)"
journalctl -n0 -q --cursor-file="$JOURNAL_CURSOR"
# See "test-resolved-dummy-server.c" for the server part
(! run resolvectl query nope.forwarded.test)
grep -qF "nope.forwarded.test" "$RUN_OUT"
grep -qF "not found" "$RUN_OUT"
# SERVFAIL + EDE code 6: DNSSEC Bogus
(! run resolvectl query edns-bogus-dnssec.forwarded.test)
grep -qE "^edns-bogus-dnssec.forwarded.test:.+: upstream-failure \(DNSSEC Bogus\)" "$RUN_OUT"
# Same thing, but over Varlink
(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-bogus-dnssec.forwarded.test"}')
grep -qF "io.systemd.Resolve.DNSSECValidationFailed" "$RUN_OUT"
grep -qF '{"result":"upstream-failure","extendedDNSErrorCode":6}' "$RUN_OUT"
journalctl --sync
journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(DNSSEC Bogus\). Lookup failed."
# SERVFAIL + EDE code 16: Censored + extra text
(! run resolvectl query edns-extra-text.forwarded.test)
grep -qE "^edns-extra-text.forwarded.test.+: SERVFAIL \(Censored: Nothing to see here!\)" "$RUN_OUT"
(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-extra-text.forwarded.test"}')
grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!"}' "$RUN_OUT"
journalctl --sync
journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Censored: Nothing to see here!\)"
# SERVFAIL + EDE code 0: Other + extra text
(! run resolvectl query edns-code-zero.forwarded.test)
grep -qE "^edns-code-zero.forwarded.test:.+: SERVFAIL \(Other: 🐱\)" "$RUN_OUT"
(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-code-zero.forwarded.test"}')
grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱"}' "$RUN_OUT"
journalctl --sync
journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Other: 🐱\)"
# SERVFAIL + invalid EDE code
(! run resolvectl query edns-invalid-code.forwarded.test)
grep -qE "^edns-invalid-code.forwarded.test:.+: SERVFAIL \([0-9]+\)" "$RUN_OUT"
(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code.forwarded.test"}')
grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+}' "$RUN_OUT"
journalctl --sync
journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+\)"
# SERVFAIL + invalid EDE code + extra text
(! run resolvectl query edns-invalid-code-with-extra-text.forwarded.test)
grep -qE '^edns-invalid-code-with-extra-text.forwarded.test:.+: SERVFAIL \([0-9]+: Hello \[#\]\$%~ World\)' "$RUN_OUT"
(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code-with-extra-text.forwarded.test"}')
grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World"}' "$RUN_OUT"
journalctl --sync
journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+: Hello \[\#\]\\$%~ World\)"
### Test resolvectl show-cache
run resolvectl show-cache
run resolvectl show-cache --json=short