diff options
author | Cy Schubert <cy@FreeBSD.org> | 2022-06-08 14:43:13 +0000 |
---|---|---|
committer | Cy Schubert <cy@FreeBSD.org> | 2022-06-08 14:43:13 +0000 |
commit | 5f9f82264b91e041df7cba2406625146e7268ce4 (patch) | |
tree | ba7309ee547bf22115420277f45a3478aafb6397 /services | |
parent | 3574dc0bd83e731bba79edc130c0569bf05f7af5 (diff) | |
download | src-5f9f82264b91e041df7cba2406625146e7268ce4.tar.gz src-5f9f82264b91e041df7cba2406625146e7268ce4.zip |
unbound: Vendor import 1.16.0vendor/unbound/1.16.0
Diffstat (limited to 'services')
-rw-r--r-- | services/authzone.c | 103 | ||||
-rw-r--r-- | services/authzone.h | 3 | ||||
-rw-r--r-- | services/cache/dns.c | 5 | ||||
-rw-r--r-- | services/listen_dnsport.c | 79 | ||||
-rw-r--r-- | services/localzone.c | 26 | ||||
-rw-r--r-- | services/mesh.c | 181 | ||||
-rw-r--r-- | services/mesh.h | 17 | ||||
-rw-r--r-- | services/outside_network.c | 5 | ||||
-rw-r--r-- | services/rpz.c | 41 | ||||
-rw-r--r-- | services/rpz.h | 4 |
10 files changed, 405 insertions, 59 deletions
diff --git a/services/authzone.c b/services/authzone.c index e83af533dbc0..02fb621a22ff 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -132,6 +132,7 @@ msg_create(struct regional* region, struct query_info* qinfo) return NULL; msg->rep->flags = (uint16_t)(BIT_QR | BIT_AA); msg->rep->authoritative = 1; + msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->qdcount = 1; /* rrsets is NULL, no rrsets yet */ return msg; @@ -1882,6 +1883,8 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, struct regional* region = NULL; struct sldns_buffer* buf = NULL; uint32_t soa_serial = 0; + char* unsupported_reason = NULL; + int only_unsupported = 1; region = env->scratch; regional_free_all(region); buf = env->scratch_buffer; @@ -1911,6 +1914,7 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, &hashalgo, &hash, &hashlen)) { /* malformed RR */ *reason = "ZONEMD rdata malformed"; + only_unsupported = 0; continue; } /* check for duplicates */ @@ -1920,25 +1924,51 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, * is not allowed. */ *reason = "ZONEMD RRSet contains more than one RR " "with the same scheme and hash algorithm"; + only_unsupported = 0; continue; } regional_free_all(region); if(serial != soa_serial) { *reason = "ZONEMD serial is wrong"; + only_unsupported = 0; continue; } + *reason = NULL; if(auth_zone_generate_zonemd_check(z, scheme, hashalgo, hash, hashlen, region, buf, reason)) { /* success */ + if(*reason) { + if(!unsupported_reason) + unsupported_reason = *reason; + /* continue to check for valid ZONEMD */ + if(verbosity >= VERB_ALGO) { + char zstr[255+1]; + dname_str(z->name, zstr); + verbose(VERB_ALGO, "auth-zone %s ZONEMD %d %d is unsupported: %s", zstr, (int)scheme, (int)hashalgo, *reason); + } + *reason = NULL; + continue; + } if(verbosity >= VERB_ALGO) { char zstr[255+1]; dname_str(z->name, zstr); - verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); + if(!*reason) + verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); } return 1; } + only_unsupported = 0; /* try next one */ } + /* have we seen no failures but only unsupported algo, + * and one unsupported algorithm, or more. */ + if(only_unsupported && unsupported_reason) { + /* only unsupported algorithms, with valid serial, not + * malformed. Did not see supported algorithms, failed or + * successful ones. */ + *reason = unsupported_reason; + return 1; + } /* fail, we may have reason */ if(!*reason) *reason = "no ZONEMD records found"; @@ -4456,7 +4486,7 @@ chunkline_get_line_collated(struct auth_chunk** chunk, size_t* chunk_pos, return 1; } -/** process $ORIGIN for http */ +/** process $ORIGIN for http, 0 nothing, 1 done, 2 error */ static int http_parse_origin(sldns_buffer* buf, struct sldns_file_parse_state* pstate) { @@ -4467,13 +4497,16 @@ http_parse_origin(sldns_buffer* buf, struct sldns_file_parse_state* pstate) pstate->origin_len = sizeof(pstate->origin); s = sldns_str2wire_dname_buf(sldns_strip_ws(line+8), pstate->origin, &pstate->origin_len); - if(s) pstate->origin_len = 0; + if(s) { + pstate->origin_len = 0; + return 2; + } return 1; } return 0; } -/** process $TTL for http */ +/** process $TTL for http, 0 nothing, 1 done, 2 error */ static int http_parse_ttl(sldns_buffer* buf, struct sldns_file_parse_state* pstate) { @@ -4481,8 +4514,12 @@ http_parse_ttl(sldns_buffer* buf, struct sldns_file_parse_state* pstate) if(strncmp(line, "$TTL", 4) == 0 && isspace((unsigned char)line[4])) { const char* end = NULL; + int overflow = 0; pstate->default_ttl = sldns_str2period( - sldns_strip_ws(line+5), &end); + sldns_strip_ws(line+5), &end, &overflow); + if(overflow) { + return 2; + } return 1; } return 0; @@ -4493,15 +4530,20 @@ static int chunkline_non_comment_RR(struct auth_chunk** chunk, size_t* chunk_pos, sldns_buffer* buf, struct sldns_file_parse_state* pstate) { + int ret; while(chunkline_get_line_collated(chunk, chunk_pos, buf)) { if(chunkline_is_comment_line_or_empty(buf)) { /* a comment, go to next line */ continue; } - if(http_parse_origin(buf, pstate)) { + if((ret=http_parse_origin(buf, pstate))!=0) { + if(ret == 2) + return 0; continue; /* $ORIGIN has been handled */ } - if(http_parse_ttl(buf, pstate)) { + if((ret=http_parse_ttl(buf, pstate))!=0) { + if(ret == 2) + return 0; continue; /* $TTL has been handled */ } return 1; @@ -5007,6 +5049,7 @@ apply_http(struct auth_xfer* xfr, struct auth_zone* z, struct sldns_file_parse_state pstate; struct auth_chunk* chunk; size_t chunk_pos; + int ret; memset(&pstate, 0, sizeof(pstate)); pstate.default_ttl = 3600; if(xfr->namelen < sizeof(pstate.origin)) { @@ -5063,10 +5106,24 @@ apply_http(struct auth_xfer* xfr, struct auth_zone* z, continue; } /* parse line and add RR */ - if(http_parse_origin(scratch_buffer, &pstate)) { + if((ret=http_parse_origin(scratch_buffer, &pstate))!=0) { + if(ret == 2) { + verbose(VERB_ALGO, "error parsing ORIGIN on line [%s:%d] %s", + xfr->task_transfer->master->file, + pstate.lineno, + sldns_buffer_begin(scratch_buffer)); + return 0; + } continue; /* $ORIGIN has been handled */ } - if(http_parse_ttl(scratch_buffer, &pstate)) { + if((ret=http_parse_ttl(scratch_buffer, &pstate))!=0) { + if(ret == 2) { + verbose(VERB_ALGO, "error parsing TTL on line [%s:%d] %s", + xfr->task_transfer->master->file, + pstate.lineno, + sldns_buffer_begin(scratch_buffer)); + return 0; + } continue; /* $TTL has been handled */ } if(!http_parse_add_rr(xfr, z, scratch_buffer, &pstate)) { @@ -5370,7 +5427,7 @@ xfr_transfer_lookup_host(struct auth_xfer* xfr, struct module_env* env) * called straight away */ lock_basic_unlock(&xfr->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, - &auth_xfer_transfer_lookup_callback, xfr)) { + &auth_xfer_transfer_lookup_callback, xfr, 0)) { lock_basic_lock(&xfr->lock); log_err("out of memory lookup up master %s", master->host); return 0; @@ -6561,7 +6618,7 @@ xfr_probe_lookup_host(struct auth_xfer* xfr, struct module_env* env) * called straight away */ lock_basic_unlock(&xfr->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, - &auth_xfer_probe_lookup_callback, xfr)) { + &auth_xfer_probe_lookup_callback, xfr, 0)) { lock_basic_lock(&xfr->lock); log_err("out of memory lookup up master %s", master->host); return 0; @@ -7632,13 +7689,16 @@ int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, { uint8_t gen[512]; size_t genlen = 0; + *reason = NULL; if(!zonemd_hashalgo_supported(hashalgo)) { + /* allow it */ *reason = "unsupported algorithm"; - return 0; + return 1; } if(!zonemd_scheme_supported(scheme)) { + /* allow it */ *reason = "unsupported scheme"; - return 0; + return 1; } if(hashlen < 12) { /* the ZONEMD draft requires digests to fail if too small */ @@ -7726,7 +7786,7 @@ static int zonemd_dnssec_verify_rrset(struct auth_zone* z, auth_zone_log(z->name, VERB_ALGO, "zonemd: verify %s RRset with DNSKEY", typestr); } - sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, sigalg, why_bogus, + sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, sigalg, why_bogus, NULL, LDNS_SECTION_ANSWER, NULL); if(sec == sec_status_secure) { return 1; @@ -8003,9 +8063,13 @@ auth_zone_verify_zonemd_with_key(struct auth_zone* z, struct module_env* env, } /* success! log the success */ - auth_zone_log(z->name, VERB_ALGO, "ZONEMD verification successful"); + if(reason) + auth_zone_log(z->name, VERB_ALGO, "ZONEMD %s", reason); + else auth_zone_log(z->name, VERB_ALGO, "ZONEMD verification successful"); if(result) { - *result = strdup("ZONEMD verification successful"); + if(reason) + *result = strdup(reason); + else *result = strdup("ZONEMD verification successful"); if(!*result) log_err("out of memory"); } } @@ -8065,7 +8129,7 @@ zonemd_get_dnskey_from_anchor(struct auth_zone* z, struct module_env* env, auth_zone_log(z->name, VERB_QUERY, "zonemd: verify DNSKEY RRset with trust anchor"); sec = val_verify_DNSKEY_with_TA(env, ve, keystorage, anchor->ds_rrset, - anchor->dnskey_rrset, NULL, why_bogus, NULL); + anchor->dnskey_rrset, NULL, why_bogus, NULL, NULL); regional_free_all(env->scratch); if(sec == sec_status_secure) { /* success */ @@ -8123,8 +8187,9 @@ auth_zone_verify_zonemd_key_with_ds(struct auth_zone* z, keystorage->rk.type = htons(LDNS_RR_TYPE_DNSKEY); keystorage->rk.rrset_class = htons(z->dclass); auth_zone_log(z->name, VERB_QUERY, "zonemd: verify zone DNSKEY with DS"); + // @TODO add EDE here? we currently just pass NULL sec = val_verify_DNSKEY_with_DS(env, ve, keystorage, ds, sigalg, - why_bogus, NULL); + why_bogus, NULL, NULL); regional_free_all(env->scratch); if(sec == sec_status_secure) { /* success */ @@ -8340,7 +8405,7 @@ zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env) /* the callback can be called straight away */ lock_rw_unlock(&z->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, - &auth_zonemd_dnskey_lookup_callback, z)) { + &auth_zonemd_dnskey_lookup_callback, z, 0)) { lock_rw_wrlock(&z->lock); log_err("out of memory lookup of %s for zonemd", (fetch_ds?"DS":"DNSKEY")); diff --git a/services/authzone.h b/services/authzone.h index d24e569d3b85..07614ed82963 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -747,6 +747,9 @@ int zonemd_scheme_supported(int scheme); * @param region: temp region for allocs during canonicalisation. * @param buf: temp buffer during canonicalisation. * @param reason: string returned with failure reason. + * If the hash cannot be checked, but it is allowed, for unknown + * algorithms, the routine returns success, and the reason is nonNULL, + * with the allowance reason. * @return false on failure. */ int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, diff --git a/services/cache/dns.c b/services/cache/dns.c index 5b64fe47520c..f6c11451c93a 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -428,6 +428,7 @@ dns_msg_create(uint8_t* qname, size_t qnamelen, uint16_t qtype, return NULL; /* integer overflow protection */ msg->rep->flags = BIT_QR; /* with QR, no AA */ msg->rep->qdcount = 1; + msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->rrsets = (struct ub_packed_rrset_key**) regional_alloc(region, capacity*sizeof(struct ub_packed_rrset_key*)); @@ -524,6 +525,7 @@ gen_dns_msg(struct regional* region, struct query_info* q, size_t num) sizeof(struct reply_info) - sizeof(struct rrset_ref)); if(!msg->rep) return NULL; + msg->rep->reason_bogus = LDNS_EDE_NONE; if(num > RR_COUNT_MAX) return NULL; /* integer overflow protection */ msg->rep->rrsets = (struct ub_packed_rrset_key**) @@ -577,6 +579,7 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, msg->rep->ar_numrrsets = r->ar_numrrsets; msg->rep->rrset_count = r->rrset_count; msg->rep->authoritative = r->authoritative; + msg->rep->reason_bogus = r->reason_bogus; if(!rrset_array_lock(r->ref, r->rrset_count, now_control)) { return NULL; } @@ -632,6 +635,7 @@ rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region, msg->rep->ns_numrrsets = 0; msg->rep->ar_numrrsets = 0; msg->rep->rrset_count = 1; + msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->rrsets[0] = packed_rrset_copy_region(rrset, region, now); if(!msg->rep->rrsets[0]) /* copy CNAME */ return NULL; @@ -670,6 +674,7 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region, msg->rep->ns_numrrsets = 0; msg->rep->ar_numrrsets = 0; msg->rep->rrset_count = 1; + msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->rrsets[0] = packed_rrset_copy_region(rrset, region, now); if(!msg->rep->rrsets[0]) /* copy DNAME */ return NULL; diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 983b96f09eec..03153bd64778 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -47,6 +47,7 @@ #ifdef USE_TCP_FASTOPEN #include <netinet/tcp.h> #endif +#include <ctype.h> #include "services/listen_dnsport.h" #include "services/outside_network.h" #include "util/netevent.h" @@ -1157,7 +1158,7 @@ if_is_ssl(const char* ifname, const char* port, int ssl_port, * @param do_auto: use automatic interface detection. * If enabled, then ifname must be the wildcard name. * @param do_udp: if udp should be used. - * @param do_tcp: if udp should be used. + * @param do_tcp: if tcp should be used. * @param hints: for getaddrinfo. family and flags have to be set by caller. * @param port: Port number to use (as string). * @param list: list of open ports, appended to, changed to point to list head. @@ -1369,17 +1370,17 @@ listen_create(struct comm_base* base, struct listen_port* ports, while(ports) { struct comm_point* cp = NULL; if(ports->ftype == listen_type_udp || - ports->ftype == listen_type_udp_dnscrypt) + ports->ftype == listen_type_udp_dnscrypt) { cp = comm_point_create_udp(base, ports->fd, front->udp_buff, cb, cb_arg, ports->socket); - else if(ports->ftype == listen_type_tcp || - ports->ftype == listen_type_tcp_dnscrypt) + } else if(ports->ftype == listen_type_tcp || + ports->ftype == listen_type_tcp_dnscrypt) { cp = comm_point_create_tcp(base, ports->fd, tcp_accept_count, tcp_idle_timeout, harden_large_queries, 0, NULL, tcp_conn_limit, bufsize, front->udp_buff, ports->ftype, cb, cb_arg, ports->socket); - else if(ports->ftype == listen_type_ssl || + } else if(ports->ftype == listen_type_ssl || ports->ftype == listen_type_http) { cp = comm_point_create_tcp(base, ports->fd, tcp_accept_count, tcp_idle_timeout, @@ -1410,15 +1411,22 @@ listen_create(struct comm_base* base, struct listen_port* ports, #endif } } else if(ports->ftype == listen_type_udpancil || - ports->ftype == listen_type_udpancil_dnscrypt) + ports->ftype == listen_type_udpancil_dnscrypt) { cp = comm_point_create_udp_ancil(base, ports->fd, front->udp_buff, cb, cb_arg, ports->socket); + } if(!cp) { log_err("can't create commpoint"); listen_delete(front); return NULL; } - if(http_notls && ports->ftype == listen_type_http) + if((http_notls && ports->ftype == listen_type_http) || + (ports->ftype == listen_type_tcp) || + (ports->ftype == listen_type_udp) || + (ports->ftype == listen_type_udpancil) || + (ports->ftype == listen_type_tcp_dnscrypt) || + (ports->ftype == listen_type_udp_dnscrypt) || + (ports->ftype == listen_type_udpancil_dnscrypt)) cp->ssl = NULL; else cp->ssl = sslctx; @@ -1709,6 +1717,63 @@ listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, } /* create ip4 and ip6 ports so that return addresses are nice. */ if(do_auto || num_ifs == 0) { + if(do_auto && cfg->if_automatic_ports && + cfg->if_automatic_ports[0]!=0) { + char* now = cfg->if_automatic_ports; + while(now && *now) { + char* after; + int extraport; + while(isspace((unsigned char)*now)) + now++; + if(!*now) + break; + after = now; + extraport = (int)strtol(now, &after, 10); + if(extraport < 0 || extraport > 65535) { + log_err("interface-automatic-ports port number out of range, at position %d of '%s'", (int)(now-cfg->if_automatic_ports)+1, cfg->if_automatic_ports); + listening_ports_free(list); + return NULL; + } + if(extraport == 0 && now == after) { + log_err("interface-automatic-ports could not be parsed, at position %d of '%s'", (int)(now-cfg->if_automatic_ports)+1, cfg->if_automatic_ports); + listening_ports_free(list); + return NULL; + } + now = after; + snprintf(portbuf, sizeof(portbuf), "%d", extraport); + if(do_ip6) { + hints.ai_family = AF_INET6; + if(!ports_create_if("::0", + do_auto, cfg->do_udp, do_tcp, + &hints, portbuf, &list, + cfg->so_rcvbuf, cfg->so_sndbuf, + cfg->ssl_port, cfg->tls_additional_port, + cfg->https_port, reuseport, cfg->ip_transparent, + cfg->tcp_mss, cfg->ip_freebind, + cfg->http_nodelay, cfg->use_systemd, + cfg->dnscrypt_port, cfg->ip_dscp)) { + listening_ports_free(list); + return NULL; + } + } + if(do_ip4) { + hints.ai_family = AF_INET; + if(!ports_create_if("0.0.0.0", + do_auto, cfg->do_udp, do_tcp, + &hints, portbuf, &list, + cfg->so_rcvbuf, cfg->so_sndbuf, + cfg->ssl_port, cfg->tls_additional_port, + cfg->https_port, reuseport, cfg->ip_transparent, + cfg->tcp_mss, cfg->ip_freebind, + cfg->http_nodelay, cfg->use_systemd, + cfg->dnscrypt_port, cfg->ip_dscp)) { + listening_ports_free(list); + return NULL; + } + } + } + return list; + } if(do_ip6) { hints.ai_family = AF_INET6; if(!ports_create_if(do_auto?"::0":"::1", diff --git a/services/localzone.c b/services/localzone.c index 3e3a71aea3c5..3ed7d835d33e 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -1328,7 +1328,8 @@ local_encode(struct query_info* qinfo, struct module_env* env, static void local_error_encode(struct query_info* qinfo, struct module_env* env, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, - struct regional* temp, int rcode, int r) + struct regional* temp, int rcode, int r, int ede_code, + const char* ede_txt) { edns->edns_version = EDNS_ADVERTISED_VERSION; edns->udp_size = EDNS_ADVERTISED_SIZE; @@ -1338,6 +1339,12 @@ local_error_encode(struct query_info* qinfo, struct module_env* env, if(!inplace_cb_reply_local_call(env, qinfo, NULL, NULL, rcode, edns, repinfo, temp, env->now_tv)) edns->opt_list_inplace_cb_out = NULL; + + if(ede_code != LDNS_EDE_NONE && env->cfg->ede) { + edns_opt_list_append_ede(&edns->opt_list_out, temp, + ede_code, ede_txt); + } + error_encode(buf, r, qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); } @@ -1535,7 +1542,9 @@ local_data_answer(struct local_zone* z, struct module_env* env, qinfo->local_alias = NULL; local_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_YXDOMAIN, - (LDNS_RCODE_YXDOMAIN|BIT_AA)); + (LDNS_RCODE_YXDOMAIN|BIT_AA), + LDNS_EDE_OTHER, + "DNAME expansion became too large"); return 1; } memset(&qinfo->local_alias->rrset->entry, 0, @@ -1638,7 +1647,8 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, } else if(lz_type == local_zone_refuse || lz_type == local_zone_always_refuse) { local_error_encode(qinfo, env, edns, repinfo, buf, temp, - LDNS_RCODE_REFUSED, (LDNS_RCODE_REFUSED|BIT_AA)); + LDNS_RCODE_REFUSED, (LDNS_RCODE_REFUSED|BIT_AA), + LDNS_EDE_NONE, NULL); return 1; } else if(lz_type == local_zone_static || lz_type == local_zone_redirect || @@ -1663,8 +1673,8 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, if(z != NULL && z->soa && z->soa_negative) return local_encode(qinfo, env, edns, repinfo, buf, temp, z->soa_negative, 0, rcode); - local_error_encode(qinfo, env, edns, repinfo, buf, temp, rcode, - (rcode|BIT_AA)); + local_error_encode(qinfo, env, edns, repinfo, buf, temp, + rcode, (rcode|BIT_AA), LDNS_EDE_NONE, NULL); return 1; } else if(lz_type == local_zone_typetransparent || lz_type == local_zone_always_transparent) { @@ -1705,9 +1715,10 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, return local_encode(qinfo, env, edns, repinfo, buf, temp, &lrr, 1, LDNS_RCODE_NOERROR); } else { + /* NODATA: No EDE needed */ local_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_NOERROR, - (LDNS_RCODE_NOERROR|BIT_AA)); + (LDNS_RCODE_NOERROR|BIT_AA), -1, NULL); } return 1; } @@ -1720,8 +1731,9 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, if(z != NULL && z->soa && z->soa_negative) return local_encode(qinfo, env, edns, repinfo, buf, temp, z->soa_negative, 0, rcode); + /* NODATA: No EDE needed */ local_error_encode(qinfo, env, edns, repinfo, buf, temp, rcode, - (rcode|BIT_AA)); + (rcode|BIT_AA), LDNS_EDE_NONE, NULL); return 1; } diff --git a/services/mesh.c b/services/mesh.c index cdcfedda270c..fbaa966bdd05 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -64,6 +64,11 @@ #include "respip/respip.h" #include "services/listen_dnsport.h" +#ifdef CLIENT_SUBNET +#include "edns-subnet/subnetmod.h" +#include "edns-subnet/edns-subnet.h" +#endif + /** subtract timers and the values do not overflow or become negative */ static void timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start) @@ -458,7 +463,8 @@ mesh_serve_expired_init(struct mesh_state* mstate, int timeout) void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, - struct edns_data* edns, struct comm_reply* rep, uint16_t qid) + struct edns_data* edns, struct comm_reply* rep, uint16_t qid, + int rpz_passthru) { struct mesh_state* s = NULL; int unique = unique_mesh_state(edns->opt_list_in, mesh->env); @@ -513,6 +519,7 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, } if(unique) mesh_state_make_unique(s); + s->s.rpz_passthru = rpz_passthru; /* copy the edns options we got from the front */ if(edns->opt_list_in) { s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list_in, @@ -606,7 +613,7 @@ servfail_mem: int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, struct edns_data* edns, sldns_buffer* buf, - uint16_t qid, mesh_cb_func_type cb, void* cb_arg) + uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru) { struct mesh_state* s = NULL; int unique = unique_mesh_state(edns->opt_list_in, mesh->env); @@ -632,6 +639,7 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, } if(unique) mesh_state_make_unique(s); + s->s.rpz_passthru = rpz_passthru; if(edns->opt_list_in) { s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list_in, s->s.region); @@ -686,7 +694,8 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, * 0 (false), in which case the new state is only made runnable so it * will not be run recursively on top of the current state. */ static void mesh_schedule_prefetch(struct mesh_area* mesh, - struct query_info* qinfo, uint16_t qflags, time_t leeway, int run) + struct query_info* qinfo, uint16_t qflags, time_t leeway, int run, + int rpz_passthru) { struct mesh_state* s = mesh_area_find(mesh, NULL, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); @@ -732,15 +741,109 @@ static void mesh_schedule_prefetch(struct mesh_area* mesh, /* move to either the forever or the jostle_list */ if(mesh->num_forever_states < mesh->max_forever_states) { mesh->num_forever_states ++; - mesh_list_insert(s, &mesh->forever_first, + mesh_list_insert(s, &mesh->forever_first, &mesh->forever_last); s->list_select = mesh_forever_list; } else { - mesh_list_insert(s, &mesh->jostle_first, + mesh_list_insert(s, &mesh->jostle_first, + &mesh->jostle_last); + s->list_select = mesh_jostle_list; + } + } + s->s.rpz_passthru = rpz_passthru; + + if(!run) { +#ifdef UNBOUND_DEBUG + n = +#else + (void) +#endif + rbtree_insert(&mesh->run, &s->run_node); + log_assert(n != NULL); + return; + } + + mesh_run(mesh, s, module_event_new, NULL); +} + +#ifdef CLIENT_SUBNET +/* Same logic as mesh_schedule_prefetch but tailored to the subnet module logic + * like passing along the comm_reply info. This will be faked into an EDNS + * option for processing by the subnet module if the client has not already + * attached its own ECS data. */ +static void mesh_schedule_prefetch_subnet(struct mesh_area* mesh, + struct query_info* qinfo, uint16_t qflags, time_t leeway, int run, + int rpz_passthru, struct comm_reply* rep, struct edns_option* edns_list) +{ + struct mesh_state* s = NULL; + struct edns_option* opt = NULL; +#ifdef UNBOUND_DEBUG + struct rbnode_type* n; +#endif + if(!mesh_make_new_space(mesh, NULL)) { + verbose(VERB_ALGO, "Too many queries. dropped prefetch."); + mesh->stats_dropped ++; + return; + } + + s = mesh_state_create(mesh->env, qinfo, NULL, + qflags&(BIT_RD|BIT_CD), 0, 0); + if(!s) { + log_err("prefetch_subnet mesh_state_create: out of memory"); + return; + } + mesh_state_make_unique(s); + + opt = edns_opt_list_find(edns_list, mesh->env->cfg->client_subnet_opcode); + if(opt) { + /* Use the client's ECS data */ + if(!edns_opt_list_append(&s->s.edns_opts_front_in, opt->opt_code, + opt->opt_len, opt->opt_data, s->s.region)) { + log_err("prefetch_subnet edns_opt_list_append: out of memory"); + return; + } + } else { + /* Fake the ECS data from the client's IP */ + struct ecs_data ecs; + memset(&ecs, 0, sizeof(ecs)); + subnet_option_from_ss(&rep->addr, &ecs, mesh->env->cfg); + if(ecs.subnet_validdata == 0) { + log_err("prefetch_subnet subnet_option_from_ss: invalid data"); + return; + } + subnet_ecs_opt_list_append(&ecs, &s->s.edns_opts_front_in, &s->s); + if(!s->s.edns_opts_front_in) { + log_err("prefetch_subnet subnet_ecs_opt_list_append: out of memory"); + return; + } + } +#ifdef UNBOUND_DEBUG + n = +#else + (void) +#endif + rbtree_insert(&mesh->all, &s->node); + log_assert(n != NULL); + /* set detached (it is now) */ + mesh->num_detached_states++; + /* make it ignore the cache */ + sock_list_insert(&s->s.blacklist, NULL, 0, s->s.region); + s->s.prefetch_leeway = leeway; + + if(s->list_select == mesh_no_list) { + /* move to either the forever or the jostle_list */ + if(mesh->num_forever_states < mesh->max_forever_states) { + mesh->num_forever_states ++; + mesh_list_insert(s, &mesh->forever_first, + &mesh->forever_last); + s->list_select = mesh_forever_list; + } else { + mesh_list_insert(s, &mesh->jostle_first, &mesh->jostle_last); s->list_select = mesh_jostle_list; } } + s->s.rpz_passthru = rpz_passthru; if(!run) { #ifdef UNBOUND_DEBUG @@ -755,11 +858,22 @@ static void mesh_schedule_prefetch(struct mesh_area* mesh, mesh_run(mesh, s, module_event_new, NULL); } +#endif /* CLIENT_SUBNET */ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, - uint16_t qflags, time_t leeway) + uint16_t qflags, time_t leeway, int rpz_passthru, + struct comm_reply* rep, struct edns_option* opt_list) { - mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1); + (void)opt_list; + (void)rep; +#ifdef CLIENT_SUBNET + if(rep) + mesh_schedule_prefetch_subnet(mesh, qinfo, qflags, leeway, 1, + rpz_passthru, rep, opt_list); + else +#endif + mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1, + rpz_passthru); } void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e, @@ -1234,7 +1348,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, (rep->security <= sec_status_bogus || rep->security == sec_status_secure_sentinel_fail)) { rcode = LDNS_RCODE_SERVFAIL; - if(m->s.env->cfg->stat_extended) + if(m->s.env->cfg->stat_extended) m->s.env->mesh->ans_bogus++; } if(rep && rep->security == sec_status_secure) @@ -1290,6 +1404,36 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, &r->edns, &r->query_reply, m->s.region, &r->start_time)) r->edns.opt_list_inplace_cb_out = NULL; } + /* Send along EDE BOGUS EDNS0 option when answer is bogus */ + if(m->s.env->cfg->ede && rcode == LDNS_RCODE_SERVFAIL && + m->s.env->need_to_validate && (!(r->qflags&BIT_CD) || + m->s.env->cfg->ignore_cd) && rep && + (rep->security <= sec_status_bogus || + rep->security == sec_status_secure_sentinel_fail)) { + char *reason = m->s.env->cfg->val_log_level >= 2 + ? errinf_to_str_bogus(&m->s) : NULL; + + /* During validation the EDE code can be received via two + * code paths. One code path fills the reply_info EDE, and + * the other fills it in the errinf_strlist. These paths + * intersect at some points, but where is opaque due to + * the complexity of the validator. At the time of writing + * we make the choice to prefer the EDE from errinf_strlist + * but a compelling reason to do otherwise is just as valid + */ + sldns_ede_code reason_bogus = errinf_to_reason_bogus(&m->s); + if ((reason_bogus == LDNS_EDE_DNSSEC_BOGUS && + rep->reason_bogus != LDNS_EDE_NONE) || + reason_bogus == LDNS_EDE_NONE) { + reason_bogus = rep->reason_bogus; + } + + if(reason_bogus != LDNS_EDE_NONE) { + edns_opt_list_append_ede(&r->edns.opt_list_out, + m->s.region, reason_bogus, reason); + } + free(reason); + } error_encode(r_buffer, rcode, &m->s.qinfo, r->qid, r->qflags, &r->edns); m->reply_list = NULL; @@ -1313,6 +1457,8 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, rep, LDNS_RCODE_SERVFAIL, &r->edns, &r->query_reply, m->s.region, &r->start_time)) r->edns.opt_list_inplace_cb_out = NULL; + /* internal server error (probably malloc failure) so no + * EDE (RFC8914) needed */ error_encode(r_buffer, LDNS_RCODE_SERVFAIL, &m->s.qinfo, r->qid, r->qflags, &r->edns); } @@ -1524,7 +1670,7 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, struct comm_reply* rep, uint16_t qid, uint16_t qflags, const struct query_info* qinfo) { - struct mesh_reply* r = regional_alloc(s->s.region, + struct mesh_reply* r = regional_alloc(s->s.region, sizeof(struct mesh_reply)); if(!r) return 0; @@ -1693,6 +1839,7 @@ mesh_continue(struct mesh_area* mesh, struct mesh_state* mstate, if(mstate->s.curmod == 0) { struct query_info* qinfo = NULL; uint16_t qflags; + int rpz_p = 0; mesh_query_done(mstate); mesh_walk_supers(mesh, mstate); @@ -1701,13 +1848,15 @@ mesh_continue(struct mesh_area* mesh, struct mesh_state* mstate, * from an external DNS server, we'll need to schedule * a prefetch after removing the current state, so * we need to make a copy of the query info here. */ - if(mstate->s.need_refetch) + if(mstate->s.need_refetch) { mesh_copy_qinfo(mstate, &qinfo, &qflags); + rpz_p = mstate->s.rpz_passthru; + } mesh_state_delete(&mstate->s); if(qinfo) { mesh_schedule_prefetch(mesh, qinfo, qflags, - 0, 1); + 0, 1, rpz_p); } return 0; } @@ -1917,7 +2066,7 @@ apply_respip_action(struct module_qstate* qstate, return 1; if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, actinfo, - alias_rrset, 0, qstate->region, az)) + alias_rrset, 0, qstate->region, az, NULL)) return 0; /* xxx_deny actions mean dropping the reply, unless the original reply @@ -2042,6 +2191,14 @@ mesh_serve_expired_callback(void* arg) } } + /* Add EDE Stale Answer (RCF8914). Ignore global ede as this is + * warning instead of an error */ + if (r->edns.edns_present && qstate->env->cfg->ede_serve_expired && + qstate->env->cfg->ede) { + edns_opt_list_append_ede(&r->edns.opt_list_out, + mstate->s.region, LDNS_EDE_STALE_ANSWER, NULL); + } + r_buffer = r->query_reply.c->buffer; if(r->query_reply.c->tcp_req_info) r_buffer = r->query_reply.c->tcp_req_info->spool_buffer; diff --git a/services/mesh.h b/services/mesh.h index d0a4b5fb3d0d..3be9b63faedd 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -296,10 +296,13 @@ void mesh_delete(struct mesh_area* mesh); * @param edns: edns data from client query. * @param rep: where to reply to. * @param qid: query id to reply with. + * @param rpz_passthru: if true, the rpz passthru was previously found and + * further rpz processing is stopped. */ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, - struct edns_data* edns, struct comm_reply* rep, uint16_t qid); + struct edns_data* edns, struct comm_reply* rep, uint16_t qid, + int rpz_passthru); /** * New query with callback. Create new query state if needed, and @@ -314,11 +317,13 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, * @param qid: query id to reply with. * @param cb: callback function. * @param cb_arg: callback user arg. + * @param rpz_passthru: if true, the rpz passthru was previously found and + * further rpz processing is stopped. * @return 0 on error. */ int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, struct edns_data* edns, struct sldns_buffer* buf, - uint16_t qid, mesh_cb_func_type cb, void* cb_arg); + uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru); /** * New prefetch message. Create new query state if needed. @@ -328,9 +333,15 @@ int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, * @param qinfo: query from client. * @param qflags: flags from client query. * @param leeway: TTL leeway what to expire earlier for this update. + * @param rpz_passthru: if true, the rpz passthru was previously found and + * further rpz processing is stopped. + * @param rep: comm_reply for the client; to be used when subnet is enabled. + * @param opt_list: edns opt_list from the client; to be used when subnet is + * enabled. */ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, - uint16_t qflags, time_t leeway); + uint16_t qflags, time_t leeway, int rpz_passthru, + struct comm_reply* rep, struct edns_option* opt_list); /** * Handle new event from the wire. A serviced query has returned. diff --git a/services/outside_network.c b/services/outside_network.c index a7e5fa3ad583..ec37a4a80d71 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -1995,6 +1995,9 @@ static int udp_connect_needs_log(int err) # ifdef ENETDOWN case ENETDOWN: # endif +# ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: +# endif case EPERM: case EACCES: if(verbosity >= VERB_ALGO) @@ -2294,7 +2297,7 @@ reuse_tcp_select_id(struct reuse_tcp* reuse, struct outside_network* outnet) node = rbtree_first(&reuse->tree_by_id); log_assert(node && node != RBTREE_NULL); /* tree not empty */ /* see if select is before first node */ - if(select < tree_by_id_get_id(node)) + if(select < (unsigned)tree_by_id_get_id(node)) return select; count += tree_by_id_get_id(node); /* perhaps select is between nodes */ diff --git a/services/rpz.c b/services/rpz.c index 322e9d1393c4..77b6266fecb9 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -526,13 +526,13 @@ rpz_create(struct config_auth* p) size_t nmlen = sizeof(nm); if(!p->rpz_cname) { - log_err("RPZ override with cname action found, but no " + log_err("rpz: override with cname action found, but no " "rpz-cname-override configured"); goto err; } if(sldns_str2wire_dname_buf(p->rpz_cname, nm, &nmlen) != 0) { - log_err("cannot parse RPZ cname override: %s", + log_err("rpz: cannot parse cname override: %s", p->rpz_cname); goto err; } @@ -614,7 +614,7 @@ rpz_insert_local_zones_trigger(struct local_zones* lz, uint8_t* dname, return; /* no need to log these types as unsupported */ } dname_str(dname, str); - verbose(VERB_ALGO, "RPZ: qname trigger, %s skipping unsupported action: %s", + verbose(VERB_ALGO, "rpz: qname trigger, %s skipping unsupported action: %s", str, rpz_action_to_string(a)); free(dname); return; @@ -999,7 +999,7 @@ rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, rpz_action_to_respip_action(a) == respip_invalid) { char str[255+1]; dname_str(dname, str); - verbose(VERB_ALGO, "RPZ: respip trigger, %s skipping unsupported action: %s", + verbose(VERB_ALGO, "rpz: respip trigger, %s skipping unsupported action: %s", str, rpz_action_to_string(a)); return 0; } @@ -1560,7 +1560,9 @@ rpz_local_encode(struct module_env* env, struct query_info* qinfo, } static struct local_rrset* -rpz_find_synthesized_rrset(int qtype, struct clientip_synthesized_rr* data) { +rpz_find_synthesized_rrset(uint16_t qtype, + struct clientip_synthesized_rr* data) +{ struct local_rrset* cursor = data->data; while( cursor != NULL) { struct packed_rrset_key* packed_rrset = &cursor->rrset->rk; @@ -1997,6 +1999,7 @@ rpz_apply_nsip_trigger(struct module_qstate* ms, struct rpz* r, break; case RPZ_PASSTHRU_ACTION: ret = NULL; + ms->rpz_passthru = 1; break; default: verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'", @@ -2051,6 +2054,7 @@ rpz_apply_nsdname_trigger(struct module_qstate* ms, struct rpz* r, break; case RPZ_PASSTHRU_ACTION: ret = NULL; + ms->rpz_passthru = 1; break; default: verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'", @@ -2114,6 +2118,11 @@ rpz_callback_from_iterator_module(struct module_qstate* ms, struct iter_qstate* struct local_zone* z = NULL; struct matched_delegation_point match = {0}; + if(ms->rpz_passthru) { + verbose(VERB_ALGO, "query is rpz_passthru, no further processing"); + return NULL; + } + if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; } az = ms->env->auth_zones; @@ -2179,6 +2188,11 @@ struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms, enum localzone_type lzt; struct dns_msg* ret = NULL; + if(ms->rpz_passthru) { + verbose(VERB_ALGO, "query is rpz_passthru, no further processing"); + return NULL; + } + if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; } az = ms->env->auth_zones; @@ -2253,6 +2267,7 @@ struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms, break; case RPZ_PASSTHRU_ACTION: ret = NULL; + ms->rpz_passthru = 1; break; default: verbose(VERB_ALGO, "rpz: qname trigger after cname: bug: unhandled or invalid action: '%s'", @@ -2270,7 +2285,8 @@ rpz_apply_maybe_clientip_trigger(struct auth_zones* az, struct module_env* env, uint8_t* taglist, size_t taglen, struct ub_server_stats* stats, sldns_buffer* buf, struct regional* temp, /* output parameters */ - struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out) + struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out, + int* passthru) { int ret = 0; enum rpz_action client_action; @@ -2278,7 +2294,9 @@ rpz_apply_maybe_clientip_trigger(struct auth_zones* az, struct module_env* env, az, qinfo, repinfo, taglist, taglen, stats, z_out, a_out, r_out); client_action = ((node == NULL) ? RPZ_INVALID_ACTION : node->action); - + if(client_action == RPZ_PASSTHRU_ACTION) { + *passthru = 1; + } if(*z_out == NULL || (client_action != RPZ_INVALID_ACTION && client_action != RPZ_PASSTHRU_ACTION)) { if(client_action == RPZ_PASSTHRU_ACTION @@ -2323,7 +2341,7 @@ int rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, - size_t taglen, struct ub_server_stats* stats) + size_t taglen, struct ub_server_stats* stats, int* passthru) { struct rpz* r = NULL; struct auth_zone* a = NULL; @@ -2332,7 +2350,8 @@ rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, enum localzone_type lzt; int clientip_trigger = rpz_apply_maybe_clientip_trigger(az, env, qinfo, - edns, repinfo, taglist, taglen, stats, buf, temp, &z, &a, &r); + edns, repinfo, taglist, taglen, stats, buf, temp, &z, &a, &r, + passthru); if(clientip_trigger >= 0) { if(a) { lock_rw_unlock(&a->lock); @@ -2357,6 +2376,10 @@ rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, } else { lzt = rpz_action_to_localzone_type(r->action_override); } + if(r->action_override == RPZ_PASSTHRU_ACTION || + lzt == local_zone_always_transparent /* RPZ_PASSTHRU_ACTION */) { + *passthru = 1; + } if(verbosity >= VERB_ALGO) { char nm[255+1], zn[255+1]; diff --git a/services/rpz.h b/services/rpz.h index c29d30dff506..53781197aeec 100644 --- a/services/rpz.h +++ b/services/rpz.h @@ -176,12 +176,14 @@ void rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, * @param taglist: taglist to lookup. * @param taglen: length of taglist. * @param stats: worker stats struct + * @param passthru: returns if the query can passthru further rpz processing. * @return: 1 if client answer is ready, 0 to continue resolving */ int rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, - uint8_t* taglist, size_t taglen, struct ub_server_stats* stats); + uint8_t* taglist, size_t taglen, struct ub_server_stats* stats, + int* passthru); /** * Callback to process when the iterator module is about to send queries. |