From 11c52d032be7a111094419194fc8cb03802d0edf Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 21 Jun 2021 17:37:46 +0100 Subject: [PATCH 1/2] Initial changes for extended DNS error codes. --- src/dns-protocol.h | 31 ++++++++++++++++++++++++++ src/dnsmasq.h | 21 +++++++++--------- src/dnssec.c | 31 +++++++++++++------------- src/forward.c | 54 ++++++++++++++++++++++------------------------ 4 files changed, 84 insertions(+), 53 deletions(-) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 8ad1964..6e98637 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -80,10 +80,41 @@ #define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */ #define EDNS0_OPTION_CLIENT_SUBNET 8 /* IANA */ +#define EDNS0_OPTION_EDE 15 /* IANA - RFC 8914 */ #define EDNS0_OPTION_NOMDEVICEID 65073 /* Nominum temporary assignment */ #define EDNS0_OPTION_NOMCPEID 65074 /* Nominum temporary assignment */ #define EDNS0_OPTION_UMBRELLA 20292 /* Cisco Umbrella temporary assignment */ +/* RFC-8914 extended errors */ +#define EDE_OTHER 0 /* Other */ +#define EDE_USUPDNSKEY 1 /* Unsupported DNSKEY algo */ +#define EDE_USUPDS 2 /* Unsupported DS Digest */ +#define EDE_STALE 3 /* Stale answer */ +#define EDE_FORGED 4 /* Forged answer */ +#define EDE_DNSSEC_IND 5 /* DNSSEC Indeterminate */ +#define EDE_DNSSEC_BOGUS 6 /* DNSSEC Bogus */ +#define EDE_SIG_EXP 7 /* Signature Expired */ +#define EDE_SIG_NYV 8 /* Signature Not Yet Valid */ +#define EDE_NO_DNSKEY 9 /* DNSKEY missing */ +#define EDE_NO_RRISG 10 /* RRSIGs missing */ +#define EDE_NO_ZONEKEY 11 /* No Zone Key Bit Set */ +#define EDE_NO_NSEC 12 /* NSEC Missing */ +#define EDE_CACHED_ERR 13 /* Cached Error */ +#define EDE_NOT_READY 14 /* Not Ready */ +#define EDE_BLOCKED 15 /* Blocked */ +#define EDE_CENSORED 16 /* Censored */ +#define EDE_FILTERED 17 /* Filtered */ +#define EDE_PROHIBITED 18 /* Prohibited */ +#define EDE_STALE_NXD 19 /* Stale NXDOMAIN */ +#define EDE_NOT_AUTH 20 /* Not Authoritative */ +#define EDE_NOT_SUP 21 /* Not Supported */ +#define EDE_NO_AUTH 22 /* No Reachable Authority */ +#define EDE_NETERR 23 /* Network error */ +#define EDE_INVALID_DATA 24 /* Invalid Data */ + + + + struct dns_header { u16 id; u8 hb3,hb4; diff --git a/src/dnsmasq.h b/src/dnsmasq.h index b21fc17..8dde4e5 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -686,17 +686,18 @@ struct hostsfile { #define DUMP_BOGUS 0x0040 #define DUMP_SEC_BOGUS 0x0080 - /* DNSSEC status values. */ -#define STAT_SECURE 1 -#define STAT_INSECURE 2 -#define STAT_BOGUS 3 -#define STAT_NEED_DS 4 -#define STAT_NEED_KEY 5 -#define STAT_TRUNCATED 6 -#define STAT_SECURE_WILDCARD 7 -#define STAT_OK 8 -#define STAT_ABANDONED 9 +#define STAT_SECURE 0x10000 +#define STAT_INSECURE 0x20000 +#define STAT_BOGUS 0x30000 +#define STAT_NEED_DS 0x40000 +#define STAT_NEED_KEY 0x50000 +#define STAT_TRUNCATED 0x60000 +#define STAT_SECURE_WILDCARD 0x70000 +#define STAT_OK 0x80000 +#define STAT_ABANDONED 0x90000 + +#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b)) #define FREC_NOREBIND 1 #define FREC_CHECKING_DISABLED 2 diff --git a/src/dnssec.c b/src/dnssec.c index 3ee1e9e..f4b5ce3 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -847,8 +847,9 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && sigcnt != 0 && rrcnt != 0 && - validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, - NULL, key, rdlen - 4, algo, keytag, &sig_ttl) == STAT_SECURE) + STAT_ISEQUAL(validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, + NULL, key, rdlen - 4, algo, keytag, &sig_ttl), + STAT_SECURE)) { valid = 1; break; @@ -971,7 +972,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char else rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl); - if (rc == STAT_INSECURE) + if (STAT_ISEQUAL(rc, STAT_INSECURE)) { my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name); rc = STAT_BOGUS; @@ -984,13 +985,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char /* If the key needed to validate the DS is on the same domain as the DS, we'll loop getting nowhere. Stop that now. This can happen of the DS answer comes from the DS's zone, and not the parent zone. */ - if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname))) + if (STAT_ISEQUAL(rc, STAT_BOGUS) || (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))) { log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); return STAT_BOGUS; } - if (rc != STAT_SECURE) + if (!STAT_ISEQUAL(rc, STAT_SECURE)) return rc; if (!neganswer) @@ -1959,15 +1960,15 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch if (check_unsigned && i < ntohs(header->ancount)) { rc = zone_status(name, class1, keyname, now); - if (rc == STAT_SECURE) + if (STAT_ISEQUAL(rc, STAT_SECURE)) rc = STAT_BOGUS; if (class) *class = class1; /* Class for NEED_DS or NEED_KEY */ } - else + else rc = STAT_INSECURE; - if (rc != STAT_INSECURE) + if (!STAT_ISEQUAL(rc, STAT_INSECURE)) return rc; } } @@ -1978,7 +1979,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch strcpy(daemon->workspacename, keyname); rc = zone_status(daemon->workspacename, class1, keyname, now); - if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS)) { if (class) *class = class1; /* Class for NEED_DS or NEED_KEY */ @@ -1986,13 +1987,13 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch } /* Zone is insecure, don't need to validate RRset */ - if (rc == STAT_SECURE) + if (STAT_ISEQUAL(rc, STAT_SECURE)) { unsigned long sig_ttl; rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl); - if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS)) { if (class) *class = class1; /* Class for DS or DNSKEY */ @@ -2025,7 +2026,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch Note that we may not yet have validated the NSEC/NSEC3 RRsets. That's not a problem since if the RRsets later fail we'll return BOGUS then. */ - if (rc == STAT_SECURE_WILDCARD && + if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL)) return STAT_BOGUS; @@ -2034,12 +2035,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch } } - if (rc == STAT_INSECURE) + if (STAT_ISEQUAL(rc, STAT_INSECURE)) secure = STAT_INSECURE; } /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */ - if (secure == STAT_SECURE) + if (STAT_ISEQUAL(secure, STAT_SECURE)) for (j = 0; j daemon->edns_pktsz) { @@ -722,7 +721,6 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server /* do this after extract_addresses. Ensure NODATA reply and remove nameserver info. */ - if (munged) { header->ancount = htons(0); @@ -762,7 +760,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise would invite infinite loops, since the answers to DNSKEY and DS queries will not be cached, so they'll be repeated. */ - if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED) + if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED)) { if (forward->flags & FREC_DNSKEY_QUERY) status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); @@ -773,7 +771,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC), NULL, NULL, NULL); #ifdef HAVE_DUMPFILE - if (status == STAT_BOGUS) + if (STAT_ISEQUAL(status, STAT_BOGUS)) dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS, header, (size_t)plen, &forward->sentto->addr, NULL); #endif @@ -781,7 +779,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, /* Can't validate, as we're missing key data. Put this answer aside, whilst we get that. */ - if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY)) { struct frec *new = NULL; int serverind; @@ -800,9 +798,9 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, /* validate routines leave name of required record in daemon->keyname */ nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz, daemon->keyname, forward->class, - status == STAT_NEED_KEY ? T_DNSKEY : T_DS, server->edns_pktsz); + STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz); - flags = (status == STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY; + flags = STAT_ISEQUAL(status, STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY; hash = hash_questions(header, nn, daemon->namebuff); if ((new = lookup_frec_by_query(hash, flags, FREC_DNSKEY_QUERY | FREC_DS_QUERY))) @@ -872,7 +870,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, #endif server_send_log(server, fd, header, nn, DUMP_SEC_QUERY, F_NOEXTRA | F_DNSSEC, daemon->keyname, - querystr("dnssec-query", status == STAT_NEED_KEY ? T_DNSKEY : T_DS)); + querystr("dnssec-query", STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS)); server->queries++; } @@ -1085,33 +1083,33 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he no_cache_dnssec = 1; #ifdef HAVE_DNSSEC - if (status != STAT_OK) + if (!STAT_ISEQUAL(status, STAT_OK)) { no_cache_dnssec = 0; - if (status == STAT_TRUNCATED) + if (STAT_ISEQUAL(status, STAT_TRUNCATED)) header->hb3 |= HB3_TC; else { char *result, *domain = "result"; - if (status == STAT_ABANDONED) + if (STAT_ISEQUAL(status, STAT_ABANDONED)) { result = "ABANDONED"; status = STAT_BOGUS; } else - result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS")); - if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) + if (STAT_ISEQUAL(status, STAT_BOGUS) && extract_request(header, n, daemon->namebuff, NULL)) domain = daemon->namebuff; log_query(F_SECSTAT, domain, NULL, result); } - if (status == STAT_SECURE) + if (STAT_ISEQUAL(status, STAT_SECURE)) cache_secure = 1; - else if (status == STAT_BOGUS) + else if (STAT_ISEQUAL(status, STAT_BOGUS)) { no_cache_dnssec = 1; bogusanswer = 1; @@ -1731,16 +1729,16 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ if (--(*keycount) == 0) new_status = STAT_ABANDONED; - else if (status == STAT_NEED_KEY) + else if (STAT_ISEQUAL(status, STAT_NEED_KEY)) new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); - else if (status == STAT_NEED_DS) + else if (STAT_ISEQUAL(status, STAT_NEED_DS)) new_status = dnssec_validate_ds(now, header, n, name, keyname, class); else new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC), NULL, NULL, NULL); - if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY) + if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY)) break; /* Can't validate because we need a key/DS whose name now in keyname. @@ -1758,7 +1756,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si } m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, - new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, server->edns_pktsz); + STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz); if ((start = dnssec_server(server, daemon->keyname, &first, &last)) == -1 || (m = tcp_talk(first, last, start, packet, m, have_mark, mark, &server)) == 0) @@ -1771,13 +1769,13 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si daemon->log_display_id = ++daemon->log_id; log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, keyname, &server->addr, - querystr("dnssec-query", new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS)); + querystr("dnssec-query", STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS)); new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); daemon->log_display_id = log_save; - if (new_status != STAT_OK) + if (!STAT_ISEQUAL(new_status, STAT_OK)) break; } @@ -2043,26 +2041,26 @@ unsigned char *tcp_request(int confd, time_t now, serv, have_mark, mark, &keycount); char *result, *domain = "result"; - if (status == STAT_ABANDONED) + if (STAT_ISEQUAL(status, STAT_ABANDONED)) { result = "ABANDONED"; status = STAT_BOGUS; } else - result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS")); - if (status == STAT_BOGUS && extract_request(header, m, daemon->namebuff, NULL)) + if (STAT_ISEQUAL(status, STAT_BOGUS) && extract_request(header, m, daemon->namebuff, NULL)) domain = daemon->namebuff; log_query(F_SECSTAT, domain, NULL, result); - if (status == STAT_BOGUS) + if (STAT_ISEQUAL(status, STAT_BOGUS)) { no_cache_dnssec = 1; bogusanswer = 1; } - if (status == STAT_SECURE) + if (STAT_ISEQUAL(status, STAT_SECURE)) cache_secure = 1; } #endif From 6261aba026005dace47d1e93af0240c382bf227a Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Sat, 26 Jun 2021 00:38:01 +0100 Subject: [PATCH 2/2] Initial implementation of RFC-8914 extended DNS errors. --- src/cache.c | 54 ++++++++++++++++- src/dns-protocol.h | 2 +- src/dnsmasq.h | 18 +++++- src/dnssec.c | 142 ++++++++++++++++++++++++++++++++------------- src/domain-match.c | 4 +- src/forward.c | 119 ++++++++++++++++++++++++++----------- src/rfc1035.c | 3 +- 7 files changed, 259 insertions(+), 83 deletions(-) diff --git a/src/cache.c b/src/cache.c index e671fa2..a4acad6 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1861,10 +1861,44 @@ char *querystr(char *desc, unsigned short type) return buff ? buff : ""; } +static char *edestr(int ede) +{ + switch (ede) + { + case EDE_OTHER: return "other"; + case EDE_USUPDNSKEY: return "unsupported DNSKEY algorithm"; + case EDE_USUPDS: return "unsupported DS digest"; + case EDE_STALE: return "stale answer"; + case EDE_FORGED: return "forged"; + case EDE_DNSSEC_IND: return "DNSSEC indeterminate"; + case EDE_DNSSEC_BOGUS: return "DNSSEC bogus"; + case EDE_SIG_EXP: return "DNSSEC signature expired"; + case EDE_SIG_NYV: return "DNSSEC sig not yet valid"; + case EDE_NO_DNSKEY: return "DNSKEY missing"; + case EDE_NO_RRSIG: return "RRSIG missing"; + case EDE_NO_ZONEKEY: return "no zone key bit set"; + case EDE_NO_NSEC: return "NSEC(3) missing"; + case EDE_CACHED_ERR: return "cached error"; + case EDE_NOT_READY: return "not ready"; + case EDE_BLOCKED: return "blocked"; + case EDE_CENSORED: return "censored"; + case EDE_FILTERED: return "filtered"; + case EDE_PROHIBITED: return "prohibited"; + case EDE_STALE_NXD: return "stale NXDOMAIN"; + case EDE_NOT_AUTH: return "not authoritative"; + case EDE_NOT_SUP: return "not supported"; + case EDE_NO_AUTH: return "no reachable authority"; + case EDE_NETERR: return "network error"; + case EDE_INVALID_DATA: return "invalid data"; + default: return "unknown"; + } +} + void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) { char *source, *dest = daemon->addrbuff; char *verb = "is"; + char *extra = ""; if (!option_bool(OPT_LOG)) return; @@ -1887,6 +1921,12 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) dest = "not implemented"; else sprintf(daemon->addrbuff, "%u", rcode); + + if (addr->log.ede != -1) + { + extra = daemon->addrbuff; + sprintf(extra, " (EDE:%s)", edestr(addr->log.ede)); + } } else inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, @@ -1932,7 +1972,15 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) else if (flags & F_UPSTREAM) source = "reply"; else if (flags & F_SECSTAT) - source = "validation"; + { + if (addr && addr->log.ede != -1) + { + extra = daemon->addrbuff; + sprintf(extra, " (EDE:%s)", edestr(addr->log.ede)); + } + source = "validation"; + dest = arg; + } else if (flags & F_AUTH) source = "auth"; else if (flags & F_SERVER) @@ -1966,11 +2014,11 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) if (option_bool(OPT_EXTRALOG)) { if (flags & F_NOEXTRA) - my_syslog(LOG_INFO, "%u %s %s %s %s", daemon->log_display_id, source, name, verb, dest); + my_syslog(LOG_INFO, "%u %s %s %s %s%s", daemon->log_display_id, source, name, verb, dest, extra); else { int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2); - my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest); + my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s%s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest, extra); } } else diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 6e98637..01d5f8f 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -96,7 +96,7 @@ #define EDE_SIG_EXP 7 /* Signature Expired */ #define EDE_SIG_NYV 8 /* Signature Not Yet Valid */ #define EDE_NO_DNSKEY 9 /* DNSKEY missing */ -#define EDE_NO_RRISG 10 /* RRSIGs missing */ +#define EDE_NO_RRSIG 10 /* RRSIGs missing */ #define EDE_NO_ZONEKEY 11 /* No Zone Key Bit Set */ #define EDE_NO_NSEC 12 /* NSEC Missing */ #define EDE_CACHED_ERR 13 /* Cached Error */ diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 8dde4e5..0aa5392 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -323,6 +323,7 @@ union all_addr { /* for log_query */ struct { unsigned short keytag, algo, digest, rcode; + int ede; } log; }; @@ -697,6 +698,16 @@ struct hostsfile { #define STAT_OK 0x80000 #define STAT_ABANDONED 0x90000 +#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */ +#define DNSSEC_FAIL_EXP 0x0002 /* key expired */ +#define DNSSEC_FAIL_INDET 0x0004 /* indetermined */ +#define DNSSEC_FAIL_NOKEYSUP 0x0008 /* no supported key algo. */ +#define DNSSEC_FAIL_NOSIG 0x0010 /* No RRsigs */ +#define DNSSEC_FAIL_NOZONE 0x0020 /* No Zone bit set */ +#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */ +#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */ +#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */ + #define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b)) #define FREC_NOREBIND 1 @@ -1281,7 +1292,7 @@ unsigned char *skip_questions(struct dns_header *header, size_t plen); unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen); unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep); -void setup_reply(struct dns_header *header, unsigned int flags); +void setup_reply(struct dns_header *header, unsigned int flags, int ede); int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure, int *doctored); @@ -1312,6 +1323,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ +#ifdef HAVE_DNSSEC size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); @@ -1320,6 +1332,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); size_t filter_rrsigs(struct dns_header *header, size_t plen); int setup_timestamp(void); +int errflags_to_ede(int status); +#endif /* hash_questions.c */ void hash_questions_init(void); @@ -1750,7 +1764,7 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout); int filter_servers(int seed, int flags, int *lowout, int *highout); int is_local_answer(time_t now, int first, char *name); size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, - char *name, char *limit, int first, int last); + char *name, char *limit, int first, int last, int ede); int server_samegroup(struct server *a, struct server *b); #ifdef HAVE_DNSSEC int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp); diff --git a/src/dnssec.c b/src/dnssec.c index f4b5ce3..3152d83 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -215,14 +215,6 @@ static int is_check_date(unsigned long curtime) return !daemon->dnssec_no_time_check; } -/* Check whether today/now is between date_start and date_end */ -static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end) -{ - /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ - return serial_compare_32(curtime, date_start) == SERIAL_GT - && serial_compare_32(curtime, date_end) == SERIAL_LT; -} - /* Return bytes of canonicalised rrdata one by one. Init state->ip with the RR, and state->end with the end of same. Init state->op to NULL. @@ -534,7 +526,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in struct crec *crecp = NULL; u16 *rr_desc = rrfilter_desc(type); u32 sig_expiration, sig_inception; - + int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP; + unsigned long curtime = time(0); int time_check = is_check_date(curtime); @@ -557,6 +550,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in void *ctx; char *name_start; u32 nsigttl, ttl, orig_ttl; + + failflags &= ~DNSSEC_FAIL_NOSIG; p = sigs[j]; GETLONG(ttl, p); @@ -574,12 +569,31 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (!extract_name(header, plen, &p, keyname, 1, 0)) return STAT_BOGUS; - if ((time_check && !check_date_range(curtime, sig_inception, sig_expiration)) || - labels > name_labels || - !(hash = hash_find(algo_digest_name(algo))) || + if (!time_check) + failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP); + else + { + /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ + if (serial_compare_32(curtime, sig_inception) == SERIAL_LT) + continue; + else + failflags &= ~DNSSEC_FAIL_NYV; + + if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT) + continue; + else + failflags &= ~DNSSEC_FAIL_EXP; + } + + if (!(hash = hash_find(algo_digest_name(algo)))) + continue; + else + failflags &= ~DNSSEC_FAIL_NOKEYSUP; + + if (labels > name_labels || !hash_init(hash, &ctx, &digest)) continue; - + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) return STAT_NEED_KEY; @@ -730,7 +744,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in } } - return STAT_BOGUS; + return STAT_BOGUS | failflags; } @@ -751,17 +765,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch unsigned long ttl, sig_ttl; struct blockdata *key; union all_addr a; + int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY; if (ntohs(header->qdcount) != 1 || RCODE(header) == SERVFAIL || RCODE(header) == REFUSED || !extract_name(header, plen, &p, name, 1, 4)) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NOKEY; GETSHORT(qtype, p); GETSHORT(qclass, p); if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NOKEY; /* See if we have cached a DS record which validates this key */ if (!(crecp = cache_find_by_name(NULL, name, now, F_DS))) @@ -795,14 +810,17 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch GETSHORT(flags, p); if (*p++ != 3) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NOKEY; algo = *p++; keytag = dnskey_keytag(algo, flags, p, rdlen - 4); key = NULL; /* key must have zone key flag set */ if (flags & 0x100) - key = blockdata_alloc((char*)p, rdlen - 4); + { + key = blockdata_alloc((char*)p, rdlen - 4); + failflags &= ~DNSSEC_FAIL_NOZONE; + } p = psave; @@ -823,15 +841,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch unsigned char *digest, *ds_digest; const struct nettle_hash *hash; int sigcnt, rrcnt; - + int wire_len; + if (recp1->addr.ds.algo == algo && recp1->addr.ds.keytag == keytag && - recp1->uid == (unsigned int)class && - (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) && - hash_init(hash, &ctx, &digest)) - + recp1->uid == (unsigned int)class) { - int wire_len = to_wire(name); + failflags &= ~DNSSEC_FAIL_NOKEY; + + if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest)))) + continue; + else + failflags &= ~DNSSEC_FAIL_NODSSUP; + + if (!hash_init(hash, &ctx, &digest)) + continue; + + wire_len = to_wire(name); /* Note that digest may be different between DSs, so we can't move this outside the loop. */ @@ -846,13 +872,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) && memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && - sigcnt != 0 && rrcnt != 0 && - STAT_ISEQUAL(validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, - NULL, key, rdlen - 4, algo, keytag, &sig_ttl), - STAT_SECURE)) + rrcnt != 0) { - valid = 1; - break; + if (sigcnt == 0) + continue; + else + failflags &= ~DNSSEC_FAIL_NOSIG; + + rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, + NULL, key, rdlen - 4, algo, keytag, &sig_ttl); + + failflags &= rc; + + if (STAT_ISEQUAL(rc, STAT_SECURE)) + { + valid = 1; + break; + } } } } @@ -937,7 +973,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch } log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); - return STAT_BOGUS; + return STAT_BOGUS | failflags; } /* The DNS packet is expected to contain the answer to a DS query @@ -975,7 +1011,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char if (STAT_ISEQUAL(rc, STAT_INSECURE)) { my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name); - rc = STAT_BOGUS; + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure"); + return STAT_BOGUS | DNSSEC_FAIL_INDET; } p = (unsigned char *)(header+1); @@ -985,7 +1022,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char /* If the key needed to validate the DS is on the same domain as the DS, we'll loop getting nowhere. Stop that now. This can happen of the DS answer comes from the DS's zone, and not the parent zone. */ - if (STAT_ISEQUAL(rc, STAT_BOGUS) || (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))) + if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname)) { log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); return STAT_BOGUS; @@ -1457,7 +1494,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns if (!(p = skip_name(nsecs[i], header, plen, 15))) return 0; /* bad packet */ - p += 10; /* type, class, TTL, rdlen */ + p += 10; /* type, class, TTL, rdlen */ algo = *p++; if ((hash = hash_find(nsec3_digest_name(algo)))) @@ -1961,11 +1998,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch { rc = zone_status(name, class1, keyname, now); if (STAT_ISEQUAL(rc, STAT_SECURE)) - rc = STAT_BOGUS; + rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG; if (class) *class = class1; /* Class for NEED_DS or NEED_KEY */ } - else + else rc = STAT_INSECURE; if (!STAT_ISEQUAL(rc, STAT_INSECURE)) @@ -2028,7 +2065,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch we'll return BOGUS then. */ if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL)) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NONSEC; rc = STAT_SECURE; } @@ -2058,7 +2095,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch { /* Empty DS without NSECS */ if (qtype == T_DS) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NONSEC; if (STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE)) { @@ -2067,7 +2104,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch return rc; } - return STAT_BOGUS; /* signed zone, no NSECs */ + return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */ } } @@ -2131,4 +2168,31 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char return ret; } +int errflags_to_ede(int status) +{ + /* We can end up with more than one flag set for some errors, + so this encodes a rough priority so the (eg) No sig is reported + before no-unexpired-sig. */ + + if (status & DNSSEC_FAIL_NYV) + return EDE_SIG_NYV; + else if (status & DNSSEC_FAIL_EXP) + return EDE_SIG_EXP; + else if (status & DNSSEC_FAIL_NOKEYSUP) + return EDE_USUPDNSKEY; + else if (status & DNSSEC_FAIL_NOZONE) + return EDE_NO_ZONEKEY; + else if (status & DNSSEC_FAIL_NOKEY) + return EDE_NO_DNSKEY; + else if (status & DNSSEC_FAIL_NODSSUP) + return EDE_USUPDS; + else if (status & DNSSEC_FAIL_NONSEC) + return EDE_NO_NSEC; + else if (status & DNSSEC_FAIL_INDET) + return EDE_DNSSEC_IND; + else if (status & DNSSEC_FAIL_NOSIG) + return EDE_NO_RRSIG; + else + return -1; +} #endif /* HAVE_DNSSEC */ diff --git a/src/domain-match.c b/src/domain-match.c index ee457c0..232620d 100644 --- a/src/domain-match.c +++ b/src/domain-match.c @@ -335,7 +335,7 @@ int is_local_answer(time_t now, int first, char *name) return rc; } -size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last) +size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede) { int trunc = 0; unsigned char *p; @@ -345,7 +345,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header if (flags & (F_NXDOMAIN | F_NOERR)) log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL); - setup_reply(header, flags); + setup_reply(header, flags, ede); if (!(p = skip_questions(header, size))) return 0; diff --git a/src/forward.c b/src/forward.c index 6416568..7647b13 100644 --- a/src/forward.c +++ b/src/forward.c @@ -177,6 +177,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, int subnet, cacheable, forwarded = 0; size_t edns0_len; unsigned char *pheader; + int ede = -1; (void)do_bit; if (header->hb4 & HB4_CD) @@ -270,7 +271,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, /* no available server. */ if (!lookup_domain(daemon->namebuff, gotname, &first, &last)) - goto reply; + { + ede = EDE_NOT_READY; + goto reply; + } /* Configured answer. */ if ((flags = is_local_answer(now, first, daemon->namebuff))) @@ -521,15 +525,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, /* could not send on, prepare to return */ header->id = htons(forward->frec_src.orig_id); free_frec(forward); /* cancel */ + ede = EDE_NETERR; reply: if (udpfd != -1) { - if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, limit, first, last))) + if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, limit, first, last, ede))) return 0; if (oph) - plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + { + u16 swap = htons((u16)ede); + + if (ede != -1) + plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + else + plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + } #if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) if (option_bool(OPT_CMARK_ALST_EN)) @@ -549,7 +561,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, int no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader, - int check_subnet, union mysockaddr *query_source) + int check_subnet, union mysockaddr *query_source, unsigned char *limit, int ede) { unsigned char *pheader, *sizep; char **sets = 0; @@ -644,6 +656,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server { union all_addr a; a.log.rcode = rcode; + a.log.ede = ede; log_query(F_UPSTREAM | F_RCODE, "error", &a, NULL); return resize_packet(header, n, pheader, plen); @@ -666,6 +679,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server SET_RCODE(header, NXDOMAIN); header->hb3 &= ~HB3_AA; cache_secure = 0; + ede = EDE_BLOCKED; } else { @@ -692,6 +706,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); munged = 1; cache_secure = 0; + ede = EDE_BLOCKED; } if (doctored) @@ -719,6 +734,12 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } #endif + if (pheader && ede != -1) + { + u16 swap = htons((u16)ede); + n = add_pseudoheader(header, n, limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1); + } + /* do this after extract_addresses. Ensure NODATA reply and remove nameserver info. */ if (munged) @@ -1071,6 +1092,7 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he { int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; size_t nn; + int ede = -1; (void)status; @@ -1085,6 +1107,7 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he #ifdef HAVE_DNSSEC if (!STAT_ISEQUAL(status, STAT_OK)) { + /* status is STAT_OK when validation not turned on. */ no_cache_dnssec = 0; if (STAT_ISEQUAL(status, STAT_TRUNCATED)) @@ -1092,7 +1115,10 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he else { char *result, *domain = "result"; - + union all_addr a; + + a.log.ede = ede = errflags_to_ede(status); + if (STAT_ISEQUAL(status, STAT_ABANDONED)) { result = "ABANDONED"; @@ -1101,18 +1127,18 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he else result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS")); - if (STAT_ISEQUAL(status, STAT_BOGUS) && extract_request(header, n, daemon->namebuff, NULL)) - domain = daemon->namebuff; + if (STAT_ISEQUAL(status, STAT_SECURE)) + cache_secure = 1; + else if (STAT_ISEQUAL(status, STAT_BOGUS)) + { + no_cache_dnssec = 1; + bogusanswer = 1; + + if (extract_request(header, n, daemon->namebuff, NULL)) + domain = daemon->namebuff; + } - log_query(F_SECSTAT, domain, NULL, result); - } - - if (STAT_ISEQUAL(status, STAT_SECURE)) - cache_secure = 1; - else if (STAT_ISEQUAL(status, STAT_BOGUS)) - { - no_cache_dnssec = 1; - bogusanswer = 1; + log_query(F_SECSTAT, domain, &a, result); } } #endif @@ -1133,7 +1159,8 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, - forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source))) + forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source, + ((unsigned char *)header) + daemon->edns_pktsz, ede))) { struct frec_src *src; @@ -1219,7 +1246,7 @@ static size_t answer_disallowed(struct dns_header *header, size_t qlen, u32 mark ubus_event_bcast_connmark_allowlist_refused(mark, name); #endif - setup_reply(header, /* flags: */ 0); + setup_reply(header, /* flags: */ 0, EDE_BLOCKED); if (!(p = skip_questions(header, qlen))) return 0; @@ -1543,10 +1570,13 @@ void receive_query(struct listener *listen, time_t now) #ifdef HAVE_CONNTRACK else if (!allowed) { + u16 swap = htons(EDE_BLOCKED); + m = answer_disallowed(header, (size_t)n, (u32)mark, is_single_query ? daemon->namebuff : NULL); if (have_pseudoheader && m != 0) - m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, + EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); if (m >= 1) { @@ -1824,7 +1854,7 @@ unsigned char *tcp_request(int confd, time_t now, int have_mark = 0; int first, last; unsigned int flags = 0; - + if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1) return packet; @@ -1875,6 +1905,8 @@ unsigned char *tcp_request(int confd, time_t now, while (1) { + int ede = -1; + if (query_count == TCP_MAX_QUERIES || !packet || !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || @@ -1960,10 +1992,13 @@ unsigned char *tcp_request(int confd, time_t now, #ifdef HAVE_CONNTRACK else if (!allowed) { + u16 swap = htons(EDE_BLOCKED); + m = answer_disallowed(header, size, (u32)mark, is_single_query ? daemon->namebuff : NULL); if (have_pseudoheader && m != 0) - m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, + EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); } #endif #ifdef HAVE_AUTH @@ -1996,7 +2031,9 @@ unsigned char *tcp_request(int confd, time_t now, !strchr(daemon->namebuff, '.') && strlen(daemon->namebuff) != 0) flags = F_NOERR; - else if (lookup_domain(daemon->namebuff, gotname, &first, &last) && !(flags = is_local_answer(now, first, daemon->namebuff))) + else if (!lookup_domain(daemon->namebuff, gotname, &first, &last)) + ede = EDE_NOT_READY; /* No configured servers */ + else if (!(flags = is_local_answer(now, first, daemon->namebuff))) { master = daemon->serverarray[first]; @@ -2026,7 +2063,10 @@ unsigned char *tcp_request(int confd, time_t now, /* Loop round available servers until we succeed in connecting to one. */ if ((m = tcp_talk(first, last, start, packet, size, have_mark, mark, &serv)) == 0) - break; + { + ede = EDE_NETERR; + break; + } /* get query name again for logging - may have been overwritten */ if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) @@ -2040,7 +2080,10 @@ unsigned char *tcp_request(int confd, time_t now, int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, serv, have_mark, mark, &keycount); char *result, *domain = "result"; - + + union all_addr a; + a.log.ede = ede = errflags_to_ede(status); + if (STAT_ISEQUAL(status, STAT_ABANDONED)) { result = "ABANDONED"; @@ -2049,19 +2092,18 @@ unsigned char *tcp_request(int confd, time_t now, else result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS")); - if (STAT_ISEQUAL(status, STAT_BOGUS) && extract_request(header, m, daemon->namebuff, NULL)) - domain = daemon->namebuff; - - log_query(F_SECSTAT, domain, NULL, result); - - if (STAT_ISEQUAL(status, STAT_BOGUS)) + if (STAT_ISEQUAL(status, STAT_SECURE)) + cache_secure = 1; + else if (STAT_ISEQUAL(status, STAT_BOGUS)) { no_cache_dnssec = 1; bogusanswer = 1; + + if (extract_request(header, m, daemon->namebuff, NULL)) + domain = daemon->namebuff; } - if (STAT_ISEQUAL(status, STAT_SECURE)) - cache_secure = 1; + log_query(F_SECSTAT, domain, &a, result); } #endif @@ -2078,7 +2120,7 @@ unsigned char *tcp_request(int confd, time_t now, m = process_reply(header, now, serv, (unsigned int)m, option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, - ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); + ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr, ((unsigned char *)header) + 65536, ede); } } } @@ -2087,11 +2129,18 @@ unsigned char *tcp_request(int confd, time_t now, if (m == 0) { if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff, - ((char *) header) + 65536, first, last))) + ((char *) header) + 65536, first, last, ede))) break; if (have_pseudoheader) - m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + { + u16 swap = htons((u16)ede); + + if (ede != -1) + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + else + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + } } check_log_writer(1); diff --git a/src/rfc1035.c b/src/rfc1035.c index 3bd728e..17ff432 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1015,7 +1015,7 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, return F_QUERY; } -void setup_reply(struct dns_header *header, unsigned int flags) +void setup_reply(struct dns_header *header, unsigned int flags, int ede) { /* clear authoritative and truncated flags, set QR flag */ header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC )) | HB3_QR; @@ -1038,6 +1038,7 @@ void setup_reply(struct dns_header *header, unsigned int flags) { union all_addr a; a.log.rcode = REFUSED; + a.log.ede = ede; log_query(F_CONFIG | F_RCODE, "error", &a, NULL); SET_RCODE(header, REFUSED); }