From: Ralph Dolmans Date: Tue, 16 Jul 2019 16:43:16 +0000 (+0200) Subject: - Added RPZ response IP support X-Git-Tag: release-1.10.0rc1~28^2~28^2~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a8d6147ae426c392c47e9a989715b9a3c92f4bca;p=thirdparty%2Funbound.git - Added RPZ response IP support --- diff --git a/daemon/daemon.c b/daemon/daemon.c index 7461a26e2..de67b073d 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -616,7 +616,8 @@ daemon_fork(struct daemon* daemon) have_view_respip_cfg; /* read auth zonefiles */ - if(!auth_zones_apply_cfg(daemon->env->auth_zones, daemon->cfg, 1)) + if(!auth_zones_apply_cfg(daemon->env->auth_zones, daemon->cfg, 1, + &daemon->use_rpz)) fatal_exit("auth_zones could not be setup"); /* setup modules */ @@ -628,6 +629,12 @@ daemon_fork(struct daemon* daemon) if(daemon->use_response_ip && modstack_find(&daemon->mods, "respip") < 0) fatal_exit("response-ip options require respip module"); + /* RPZ response ip triggers don't work as expected without the respip + * module. To avoid run-time operational surprise we reject such + * configuration. */ + if(daemon->use_rpz && + modstack_find(&daemon->mods, "respip") < 0) + fatal_exit("RPZ requires the respip module"); /* first create all the worker structures, so we can pass * them to the newly created threads. diff --git a/daemon/daemon.h b/daemon/daemon.h index 5749dbef8..3effbafb7 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -132,6 +132,8 @@ struct daemon { struct respip_set* respip_set; /** some response-ip tags or actions are configured if true */ int use_response_ip; + /** some RPZ policies are configured */ + int use_rpz; #ifdef USE_DNSCRYPT /** the dnscrypt environment */ struct dnsc_env* dnscenv; diff --git a/daemon/worker.c b/daemon/worker.c index 8440dd22c..547a7610b 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -572,7 +572,7 @@ static int apply_respip_action(struct worker* worker, const struct query_info* qinfo, struct respip_client_info* cinfo, struct reply_info* rep, struct comm_reply* repinfo, struct ub_packed_rrset_key** alias_rrset, - struct reply_info** encode_repp) + struct reply_info** encode_repp, struct auth_zones* az) { struct respip_action_info actinfo = {respip_none, NULL}; @@ -582,7 +582,7 @@ apply_respip_action(struct worker* worker, const struct query_info* qinfo, return 1; if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, &actinfo, - alias_rrset, 0, worker->scratchpad)) + alias_rrset, 0, worker->scratchpad, az)) return 0; /* xxx_deny actions mean dropping the reply, unless the original reply @@ -709,20 +709,20 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, (int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad)) goto bail_out; *alias_rrset = NULL; /* avoid confusion if caller set it to non-NULL */ - if(worker->daemon->use_response_ip && !partial_rep && - !apply_respip_action(worker, qinfo, cinfo, rep, repinfo, alias_rrset, - &encode_rep)) { + if((worker->daemon->use_response_ip || worker->daemon->use_rpz) && + !partial_rep && !apply_respip_action(worker, qinfo, cinfo, rep, + repinfo, alias_rrset, + &encode_rep, worker->env.auth_zones)) { goto bail_out; } else if(partial_rep && !respip_merge_cname(partial_rep, qinfo, rep, cinfo, - must_validate, &encode_rep, worker->scratchpad)) { + must_validate, &encode_rep, worker->scratchpad, + worker->env.auth_zones)) { goto bail_out; } if(encode_rep != rep) secure = 0; /* if rewritten, it can't be considered "secure" */ if(!encode_rep || *alias_rrset) { - sldns_buffer_clear(repinfo->c->buffer); - sldns_buffer_flip(repinfo->c->buffer); if(!encode_rep) *need_drop = 1; else { @@ -768,12 +768,18 @@ bail_out: * being deferred). */ static void reply_and_prefetch(struct worker* worker, struct query_info* qinfo, - uint16_t flags, struct comm_reply* repinfo, time_t leeway) + uint16_t flags, struct comm_reply* repinfo, time_t leeway, int noreply) { /* first send answer to client to keep its latency * as small as a cachereply */ - if(sldns_buffer_limit(repinfo->c->buffer) != 0) + if(!noreply) { + if(repinfo->c->tcp_req_info) { + sldns_buffer_copy( + repinfo->c->tcp_req_info->spool_buffer, + repinfo->c->buffer); + } comm_point_send_reply(repinfo); + } server_stats_prefetch(&worker->stats, worker); /* create the prefetch in the mesh as a normal lookup without @@ -1445,7 +1451,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, /* If we may apply IP-based actions to the answer, build the client * information. As this can be expensive, skip it if there is * absolutely no possibility of it. */ - if(worker->daemon->use_response_ip && + if((worker->daemon->use_response_ip || worker->daemon->use_rpz) && (qinfo.qtype == LDNS_RR_TYPE_A || qinfo.qtype == LDNS_RR_TYPE_AAAA || qinfo.qtype == LDNS_RR_TYPE_ANY)) { @@ -1490,7 +1496,8 @@ lookup_cache: lock_rw_unlock(&e->lock); reply_and_prefetch(worker, lookup_qinfo, sldns_buffer_read_u16_at(c->buffer, 2), - repinfo, leeway); + repinfo, leeway, + (partial_rep || need_drop)); if(!partial_rep) { rc = 0; regional_free_all(worker->scratchpad); diff --git a/libunbound/context.c b/libunbound/context.c index 20e3680ec..524544a91 100644 --- a/libunbound/context.c +++ b/libunbound/context.c @@ -55,6 +55,7 @@ int context_finalize(struct ub_ctx* ctx) { + int is_rpz; struct config_file* cfg = ctx->env->cfg; verbosity = cfg->verbosity; if(ctx->logfile_override) @@ -69,7 +70,7 @@ context_finalize(struct ub_ctx* ctx) return UB_NOMEM; if(!local_zones_apply_cfg(ctx->local_zones, cfg)) return UB_INITFAIL; - if(!auth_zones_apply_cfg(ctx->env->auth_zones, cfg, 1)) + if(!auth_zones_apply_cfg(ctx->env->auth_zones, cfg, 1i, &is_rpz)) return UB_INITFAIL; if(!slabhash_is_size(ctx->env->msg_cache, cfg->msg_cache_size, cfg->msg_cache_slabs)) { diff --git a/respip/respip.c b/respip/respip.c index d61877b55..176abea82 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -12,6 +12,7 @@ #include "config.h" #include "services/localzone.h" +#include "services/authzone.h" #include "services/cache/dns.h" #include "sldns/str2wire.h" #include "util/config_file.h" @@ -25,30 +26,6 @@ #include "services/view.h" #include "sldns/rrdef.h" -/** - * Conceptual set of IP addresses for response AAAA or A records that should - * trigger special actions. - */ -struct respip_set { - struct regional* region; - struct rbtree_type ip_tree; - char* const* tagname; /* shallow copy of tag names, for logging */ - int num_tags; /* number of tagname entries */ -}; - -/** An address span with response control information */ -struct resp_addr { - /** node in address tree */ - struct addr_tree_node node; - /** tag bitlist */ - uint8_t* taglist; - /** length of the taglist (in bytes) */ - size_t taglen; - /** action for this address span */ - enum respip_action action; - /** "local data" for this node */ - struct ub_packed_rrset_key* data; -}; /** Subset of resp_addr.node, used for inform-variant logging */ struct respip_addr_info { @@ -88,6 +65,7 @@ respip_set_create(void) return NULL; } addr_tree_init(&set->ip_tree); + lock_rw_init(&set->lock); return set; } @@ -96,6 +74,7 @@ respip_set_delete(struct respip_set* set) { if(!set) return; + lock_rw_destroy(&set->lock); regional_destroy(set->region); free(set); } @@ -108,29 +87,21 @@ respip_set_get_tree(struct respip_set* set) return &set->ip_tree; } -/** returns the node in the address tree for the specified netblock string; - * non-existent node will be created if 'create' is true */ -static struct resp_addr* -respip_find_or_create(struct respip_set* set, const char* ipstr, int create) +struct resp_addr* +respip_sockaddr_find_or_create(struct respip_set* set, struct sockaddr_storage* addr, + socklen_t addrlen, int net, int create, const char* ipstr) { struct resp_addr* node; - struct sockaddr_storage addr; - int net; - socklen_t addrlen; - - if(!netblockstrtoaddr(ipstr, 0, &addr, &addrlen, &net)) { - log_err("cannot parse netblock: '%s'", ipstr); - return NULL; - } - node = (struct resp_addr*)addr_tree_find(&set->ip_tree, &addr, addrlen, net); + node = (struct resp_addr*)addr_tree_find(&set->ip_tree, addr, addrlen, net); if(!node && create) { node = regional_alloc_zero(set->region, sizeof(*node)); if(!node) { log_err("out of memory"); return NULL; } + lock_rw_init(&node->lock); node->action = respip_none; - if(!addr_tree_insert(&set->ip_tree, &node->node, &addr, + if(!addr_tree_insert(&set->ip_tree, &node->node, addr, addrlen, net)) { /* We know we didn't find it, so this should be * impossible. */ @@ -140,6 +111,37 @@ respip_find_or_create(struct respip_set* set, const char* ipstr, int create) return node; } +void +respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node) +{ + struct resp_addr* prev; + prev = (struct resp_addr*)rbtree_previous((struct rbnode_type*)node); + lock_rw_destroy(&node->lock); + rbtree_delete(&set->ip_tree, node); + /* no free'ing, all allocated in region */ + if(!prev) + addr_tree_init_parents((rbtree_type*)set); + else + addr_tree_init_parents_node(&prev->node); +} + +/** returns the node in the address tree for the specified netblock string; + * non-existent node will be created if 'create' is true */ +static struct resp_addr* +respip_find_or_create(struct respip_set* set, const char* ipstr, int create) +{ + struct sockaddr_storage addr; + int net; + socklen_t addrlen; + + if(!netblockstrtoaddr(ipstr, 0, &addr, &addrlen, &net)) { + log_err("cannot parse netblock: '%s'", ipstr); + return NULL; + } + return respip_sockaddr_find_or_create(set, &addr, addrlen, net, create, + ipstr); +} + static int respip_tag_cfg(struct respip_set* set, const char* ipstr, const uint8_t* taglist, size_t taglen) @@ -191,6 +193,8 @@ respip_action_cfg(struct respip_set* set, const char* ipstr, action = respip_always_refuse; else if(strcmp(actnstr, "always_nxdomain") == 0) action = respip_always_nxdomain; + else if(strcmp(actnstr, "always_nodata") == 0) + action = respip_always_nodata; else { log_err("unknown response-ip action %s", actnstr); return 0; @@ -232,8 +236,43 @@ new_rrset(struct regional* region, uint16_t rrtype, uint16_t rrclass) } /** enter local data as resource records into a response-ip node */ -static int + +int respip_enter_rr(struct regional* region, struct resp_addr* raddr, + uint16_t rrtype, uint16_t rrclass, time_t ttl, uint8_t* rdata, + size_t rdata_len, const char* rrstr, const char* netblockstr) +{ + struct packed_rrset_data* pd; + struct sockaddr* sa; + sa = (struct sockaddr*)&raddr->node.addr; + if (rrtype == LDNS_RR_TYPE_CNAME && raddr->data) { + log_err("CNAME response-ip data (%s) can not co-exist with other " + "response-ip data for netblock %s", rrstr, netblockstr); + return 0; + } else if (raddr->data && + raddr->data->rk.type == htons(LDNS_RR_TYPE_CNAME)) { + log_err("response-ip data (%s) can not be added; CNAME response-ip " + "data already in place for netblock %s", rrstr, netblockstr); + return 0; + } else if((rrtype != LDNS_RR_TYPE_CNAME) && + ((sa->sa_family == AF_INET && rrtype != LDNS_RR_TYPE_A) || + (sa->sa_family == AF_INET6 && rrtype != LDNS_RR_TYPE_AAAA))) { + log_err("response-ip data %s record type does not correspond " + "to netblock %s address family", rrstr, netblockstr); + return 0; + } + + if(!raddr->data) { + raddr->data = new_rrset(region, rrtype, rrclass); + if(!raddr->data) + return 0; + } + pd = raddr->data->entry.data; + return rrset_insert_rr(region, pd, rdata, rdata_len, ttl, rrstr); +} + +static int +respip_enter_rrstr(struct regional* region, struct resp_addr* raddr, const char* rrstr, const char* netblock) { uint8_t* nm; @@ -244,8 +283,6 @@ respip_enter_rr(struct regional* region, struct resp_addr* raddr, size_t rdata_len = 0; char buf[65536]; char bufshort[64]; - struct packed_rrset_data* pd; - struct sockaddr* sa; int ret; if(raddr->action != respip_redirect && raddr->action != respip_inform_redirect) { @@ -265,31 +302,8 @@ respip_enter_rr(struct regional* region, struct resp_addr* raddr, return 0; } free(nm); - sa = (struct sockaddr*)&raddr->node.addr; - if (rrtype == LDNS_RR_TYPE_CNAME && raddr->data) { - log_err("CNAME response-ip data (%s) can not co-exist with other " - "response-ip data for netblock %s", rrstr, netblock); - return 0; - } else if (raddr->data && - raddr->data->rk.type == htons(LDNS_RR_TYPE_CNAME)) { - log_err("response-ip data (%s) can not be added; CNAME response-ip " - "data already in place for netblock %s", rrstr, netblock); - return 0; - } else if((rrtype != LDNS_RR_TYPE_CNAME) && - ((sa->sa_family == AF_INET && rrtype != LDNS_RR_TYPE_A) || - (sa->sa_family == AF_INET6 && rrtype != LDNS_RR_TYPE_AAAA))) { - log_err("response-ip data %s record type does not correspond " - "to netblock %s address family", rrstr, netblock); - return 0; - } - - if(!raddr->data) { - raddr->data = new_rrset(region, rrtype, rrclass); - if(!raddr->data) - return 0; - } - pd = raddr->data->entry.data; - return rrset_insert_rr(region, pd, rdata, rdata_len, ttl, rrstr); + return respip_enter_rr(region, raddr, rrtype, rrclass, ttl, rdata, + rdata_len, rrstr, netblock); } static int @@ -303,7 +317,7 @@ respip_data_cfg(struct respip_set* set, const char* ipstr, const char* rrstr) "response-ip node for %s not found", rrstr, ipstr); return 0; } - return respip_enter_rr(set->region, node, rrstr, ipstr); + return respip_enter_rrstr(set->region, node, rrstr, ipstr); } static int @@ -361,6 +375,7 @@ respip_set_apply_cfg(struct respip_set* set, char* const* tagname, int num_tags, free(pd); pd = np; } + addr_tree_init_parents(&set->ip_tree); return 1; } @@ -557,9 +572,10 @@ rdata2sockaddr(const struct packed_rrset_data* rd, uint16_t rtype, size_t i, * rep->rrsets for the RRset that contains the matching IP address record * (the index is normally 0, but can be larger than that if this is a CNAME * chain or type-ANY response). + * Returns resp_addr holding read lock. */ -static const struct resp_addr* -respip_addr_lookup(const struct reply_info *rep, struct rbtree_type* iptree, +static struct resp_addr* +respip_addr_lookup(const struct reply_info *rep, struct respip_set* rs, size_t* rrset_id) { size_t i; @@ -567,6 +583,7 @@ respip_addr_lookup(const struct reply_info *rep, struct rbtree_type* iptree, struct sockaddr_storage ss; socklen_t addrlen; + lock_rw_rdlock(&rs->lock); for(i=0; ian_numrrsets; i++) { size_t j; const struct packed_rrset_data* rd; @@ -578,15 +595,17 @@ respip_addr_lookup(const struct reply_info *rep, struct rbtree_type* iptree, for(j = 0; j < rd->count; j++) { if(!rdata2sockaddr(rd, rtype, j, &ss, &addrlen)) continue; - ra = (struct resp_addr*)addr_tree_lookup(iptree, &ss, - addrlen); + ra = (struct resp_addr*)addr_tree_lookup(&rs->ip_tree, + &ss, addrlen); if(ra) { *rrset_id = i; + lock_rw_rdlock(&ra->lock); + lock_rw_unlock(&rs->lock); return ra; } } } - + lock_rw_unlock(&rs->lock); return NULL; } @@ -754,6 +773,7 @@ respip_nodata_answer(uint16_t qtype, enum respip_action action, return 1; } else if(action == respip_static || action == respip_redirect || action == respip_always_nxdomain || + action == respip_always_nodata || action == respip_inform_redirect) { /* Since we don't know about other types of the owner name, * we generally return NOERROR/NODATA unless an NXDOMAIN action @@ -817,7 +837,7 @@ respip_rewrite_reply(const struct query_info* qinfo, const struct respip_client_info* cinfo, const struct reply_info* rep, struct reply_info** new_repp, struct respip_action_info* actinfo, struct ub_packed_rrset_key** alias_rrset, int search_only, - struct regional* region) + struct regional* region, struct auth_zones* az) { const uint8_t* ctaglist; size_t ctaglen; @@ -830,9 +850,10 @@ respip_rewrite_reply(const struct query_info* qinfo, size_t rrset_id = 0; enum respip_action action = respip_none; int tag = -1; - const struct resp_addr* raddr = NULL; + struct resp_addr* raddr = NULL; int ret = 1; struct ub_packed_rrset_key* redirect_rrset = NULL; + struct rpz* r; if(!cinfo) goto done; @@ -859,7 +880,7 @@ respip_rewrite_reply(const struct query_info* qinfo, lock_rw_rdlock(&view->lock); if(view->respip_set) { if((raddr = respip_addr_lookup(rep, - &view->respip_set->ip_tree, &rrset_id))) { + view->respip_set, &rrset_id))) { /** for per-view respip directives the action * can only be direct (i.e. not tag-based) */ action = raddr->action; @@ -868,7 +889,7 @@ respip_rewrite_reply(const struct query_info* qinfo, if(!raddr && !view->isfirst) goto done; } - if(!raddr && ipset && (raddr = respip_addr_lookup(rep, &ipset->ip_tree, + if(!raddr && ipset && (raddr = respip_addr_lookup(rep, ipset, &rrset_id))) { action = (enum respip_action)local_data_find_tag_action( raddr->taglist, raddr->taglen, ctaglist, ctaglen, @@ -876,6 +897,17 @@ respip_rewrite_reply(const struct query_info* qinfo, (enum localzone_type)raddr->action, &tag, ipset->tagname, ipset->num_tags); } + lock_rw_rdlock(&az->rpz_lock); + for(r = az->rpz_first; r && !raddr; r = r->next) { + if(!r->taglist || taglist_intersect(r->taglist, + r->taglistlen, ctaglist, ctaglen)) { + if((raddr = respip_addr_lookup(rep, + r->respip_set, &rrset_id))) { + action = raddr->action; + } + } + } + lock_rw_unlock(&az->rpz_lock); if(raddr && !search_only) { int result = 0; @@ -884,6 +916,7 @@ respip_rewrite_reply(const struct query_info* qinfo, if(action != respip_always_refuse && action != respip_always_transparent && action != respip_always_nxdomain + && action != respip_always_nodata && (result = respip_data_answer(raddr, action, qinfo->qtype, rep, rrset_id, new_repp, tag, tag_datas, tag_datas_size, ipset->tagname, ipset->num_tags, @@ -920,6 +953,8 @@ respip_rewrite_reply(const struct query_info* qinfo, ret = populate_action_info(actinfo, action, raddr, redirect_rrset, tag, ipset, search_only, region); } + if(raddr) + lock_rw_unlock(&raddr->lock); return ret; } @@ -981,7 +1016,7 @@ respip_operate(struct module_qstate* qstate, enum module_ev event, int id, if(!respip_rewrite_reply(&qstate->qinfo, qstate->client_info, qstate->return_msg->rep, &new_rep, &actinfo, &alias_rrset, 0, - qstate->region)) { + qstate->region, qstate->env->auth_zones)) { goto servfail; } if(actinfo.action != respip_none) { @@ -1027,7 +1062,8 @@ int respip_merge_cname(struct reply_info* base_rep, const struct query_info* qinfo, const struct reply_info* tgt_rep, const struct respip_client_info* cinfo, int must_validate, - struct reply_info** new_repp, struct regional* region) + struct reply_info** new_repp, struct regional* region, + struct auth_zones* az) { struct reply_info* new_rep; struct reply_info* tmp_rep = NULL; /* just a placeholder */ @@ -1053,7 +1089,7 @@ respip_merge_cname(struct reply_info* base_rep, /* see if the target reply would be subject to a response-ip action. */ if(!respip_rewrite_reply(qinfo, cinfo, tgt_rep, &tmp_rep, &actinfo, - &alias_rrset, 1, region)) + &alias_rrset, 1, region, az)) return 0; if(actinfo.action != respip_none) { log_info("CNAME target of redirect response-ip action would " @@ -1105,7 +1141,8 @@ respip_inform_super(struct module_qstate* qstate, int id, if(!respip_merge_cname(super->return_msg->rep, &qstate->qinfo, qstate->return_msg->rep, super->client_info, - super->env->need_to_validate, &new_rep, super->region)) + super->env->need_to_validate, &new_rep, super->region, + qstate->env->auth_zones)) goto fail; super->return_msg->rep = new_rep; return; diff --git a/respip/respip.h b/respip/respip.h index 01309caec..d2d39872c 100644 --- a/respip/respip.h +++ b/respip/respip.h @@ -14,19 +14,37 @@ #include "util/module.h" #include "services/localzone.h" +#include "util/locks.h" /** - * Set of response IP addresses with associated actions and tags. - * Forward declaration only here. Actual definition is hidden within the - * module. + * Conceptual set of IP addresses for response AAAA or A records that should + * trigger special actions. */ -struct respip_set; +struct respip_set { + struct regional* region; + struct rbtree_type ip_tree; + lock_rw_type lock; /* lock on the respip tree */ + char* const* tagname; /* shallow copy of tag names, for logging */ + int num_tags; /* number of tagname entries */ +}; + + +/** An address span with response control information */ +struct resp_addr { + /** node in address tree */ + struct addr_tree_node node; + /** lock on the node item */ + lock_rw_type lock; + /** tag bitlist */ + uint8_t* taglist; + /** length of the taglist (in bytes) */ + size_t taglen; + /** action for this address span */ + enum respip_action action; + /** "local data" for this node */ + struct ub_packed_rrset_key* data; +}; -/** - * Forward declaration for the structure that represents a node in the - * respip_set address tree - */ -struct resp_addr; /** * Forward declaration for the structure that represents a tree of view data. @@ -124,12 +142,14 @@ int respip_views_apply_cfg(struct views* vs, struct config_file* cfg, * @param new_repp: pointer placeholder for the merged reply. will be intact * on error. * @param region: allocator to build *new_repp. + * @param az: auth zones containing RPZ information. * @return 1 on success, 0 on error. */ int respip_merge_cname(struct reply_info* base_rep, const struct query_info* qinfo, const struct reply_info* tgt_rep, const struct respip_client_info* cinfo, int must_validate, - struct reply_info** new_repp, struct regional* region); + struct reply_info** new_repp, struct regional* region, + struct auth_zones* az); /** * See if any IP-based action should apply to any IP address of AAAA/A answer @@ -148,6 +168,7 @@ int respip_merge_cname(struct reply_info* base_rep, * @param alias_rrset: must not be NULL. * @param search_only: if true, only check if an action would apply. actionp * will be set (or intact) accordingly but the modified reply won't be built. + * @param az: auth zones containing RPZ information. * @param region: allocator to build *new_repp. * @return 1 on success, 0 on error. */ @@ -156,7 +177,7 @@ int respip_rewrite_reply(const struct query_info* qinfo, const struct reply_info *rep, struct reply_info** new_repp, struct respip_action_info* actinfo, struct ub_packed_rrset_key** alias_rrset, - int search_only, struct regional* region); + int search_only, struct regional* region, struct auth_zones* az); /** * Get the response-ip function block. @@ -227,4 +248,44 @@ void respip_inform_print(struct respip_addr_info* respip_addr, uint8_t* qname, uint16_t qtype, uint16_t qclass, struct local_rrset* local_alias, struct comm_reply* repinfo); +/** + * Find resp_addr in tree, create and add to tree if it does not exist. + * @param set: struct containing the tree and region to alloc new node on. + * should hold write lock. + * @param addr: address to look up. + * @param addrlen: length of addr. + * @param net: netblock to lookup. + * @param create: create node if it does not exist when 1. + * @param ipstr: human redable ip string, for logging. + * @return newly created of found node, not holding lock. + */ +struct resp_addr* +respip_sockaddr_find_or_create(struct respip_set* set, struct sockaddr_storage* addr, + socklen_t addrlen, int net, int create, const char* ipstr); + +/** + * Add RR to resp_addr's RRset. Create RRset is not existing. + * @param region: region to alloc RR(set). + * @param raddr: resp_addr containing RRset. Must hold write lock. + * @param rrtype: RR type. + * @param rrclass: RR class. + * @param ttl: TTL. + * @param rdata: RDATA. + * @param rdata_len: length of rdata. + * @param rrstr: RR as string, for logging + * @param netblockstr: netblock as string, for logging + * @return 0 on error + */ +int +respip_enter_rr(struct regional* region, struct resp_addr* raddr, + uint16_t rrtype, uint16_t rrclass, time_t ttl, uint8_t* rdata, + size_t rdata_len, const char* rrstr, const char* netblockstr); + +/** + * Delete resp_addr node from tree. + * @param set: struct containing tree. Must hold write lock. + * @param node: node to delete. Must hold write lock. + */ +void +respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node); #endif /* RESPIP_RESPIP_H */ diff --git a/services/authzone.c b/services/authzone.c index a408166e5..3594fe41d 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -1542,9 +1542,9 @@ auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg) /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); - /* clear the RPZ local_zone tree */ + /* clear the RPZ policies */ if(z->rpz) - rpz_clear_lz(z->rpz); + rpz_clear(z->rpz); memset(&state, 0, sizeof(state)); /* default TTL to 3600 */ @@ -1564,6 +1564,9 @@ auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg) return 0; } fclose(in); + + if(z->rpz) + rpz_finish_config(z->rpz); return 1; } @@ -1926,7 +1929,7 @@ az_delete_deleted_zones(struct auth_zones* az) } int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, - int setup) + int setup, int* is_rpz) { struct config_auth* p; az_setall_deleted(az); @@ -1935,6 +1938,7 @@ int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, log_warn("auth-zone without a name, skipped"); continue; } + *is_rpz = (*is_rpz || p->isrpz); if(!auth_zones_cfg(az, p)) { log_err("cannot config auth zone %s", p->name); return 0; @@ -4654,9 +4658,9 @@ apply_axfr(struct auth_xfer* xfr, struct auth_zone* z, /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); - /* clear the RPZ local_zone tree */ + /* clear the RPZ policies */ if(z->rpz) - rpz_clear_lz(z->rpz); + rpz_clear(z->rpz); xfr->have_zone = 0; xfr->serial = 0; @@ -4754,9 +4758,9 @@ apply_http(struct auth_xfer* xfr, struct auth_zone* z, /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); - /* clear the RPZ local_zone tree */ + /* clear the RPZ policies */ if(z->rpz) - rpz_clear_lz(z->rpz); + rpz_clear(z->rpz); xfr->have_zone = 0; xfr->serial = 0; @@ -4937,6 +4941,9 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, if(xfr->have_zone) xfr->lease_time = *env->now; + if(z->rpz) + rpz_finish_config(z->rpz); + /* unlock */ lock_rw_unlock(&z->lock); diff --git a/services/authzone.h b/services/authzone.h index 00f5d6655..968ff7218 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -468,10 +468,11 @@ struct auth_zones* auth_zones_create(void); * @param az: auth zones structure * @param cfg: config to apply. * @param setup: if true, also sets up values in the auth zones structure + * @param is_rpz: set to 1 if at least one RPZ zone is configured. * @return false on failure. */ int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, - int setup); + int setup, int* iz_rpz); /** initial pick up of worker timeouts, ties events to worker event loop * @param az: auth zones structure diff --git a/services/localzone.h b/services/localzone.h index e8c493f63..a476f36c9 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -555,6 +555,8 @@ enum respip_action { respip_transparent = local_zone_transparent, /** gives response data (if any), else nodata answer. */ respip_typetransparent = local_zone_typetransparent, + /** type invalid */ + respip_invalid = local_zone_invalid, }; int diff --git a/services/rpz.c b/services/rpz.c index ef2d504ca..0b39c95c9 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -209,6 +209,24 @@ rpz_action_to_localzone_type(enum rpz_action a) } } +static enum respip_action +rpz_action_to_respip_action(enum rpz_action a) +{ + switch(a) { + case RPZ_NXDOMAIN_ACTION: return respip_always_nxdomain; + case RPZ_NODATA_ACTION: return respip_always_nodata; + case RPZ_DROP_ACTION: return respip_deny; + case RPZ_PASSTHRU_ACTION: return respip_always_transparent; + case RPZ_LOCAL_DATA_ACTION: + case RPZ_CNAME_OVERRIDE_ACTION: + return respip_redirect; + case RPZ_INVALID_ACTION: + case RPZ_TCP_ONLY_ACTION: + default: + return respip_invalid; + } +} + static enum rpz_action localzone_type_to_rpz_action(enum localzone_type lzt) { @@ -256,6 +274,7 @@ void rpz_delete(struct rpz* r) if(!r) return; local_zones_delete(r->local_zones); + respip_set_delete(r->respip_set); regional_destroy(r->region); free(r->taglist); free(r->log_name); @@ -263,16 +282,28 @@ void rpz_delete(struct rpz* r) } int -rpz_clear_lz(struct rpz* r) +rpz_clear(struct rpz* r) { /* must hold write lock on auth_zone */ local_zones_delete(r->local_zones); + respip_set_delete(r->respip_set); if(!(r->local_zones = local_zones_create())){ return 0; } + if(!(r->respip_set = respip_set_create())) { + return 0; + } return 1; } +void +rpz_finish_config(struct rpz* r) +{ + lock_rw_wrlock(&r->respip_set->lock); + addr_tree_init_parents(&r->respip_set->ip_tree); + lock_rw_unlock(&r->respip_set->lock); +} + /** new rrset containing CNAME override, does not yet contain a dname */ static struct ub_packed_rrset_key* new_cname_override(struct regional* region, uint8_t* ct, size_t ctlen) @@ -323,17 +354,18 @@ rpz_create(struct config_auth* p) { struct rpz* r = calloc(1, sizeof(*r)); if(!r) - return 0; + goto err; r->region = regional_create_custom(sizeof(struct regional)); if(!r->region) { - free(r); - return 0; + goto err; } if(!(r->local_zones = local_zones_create())){ - free(r); - return 0; + goto err; + } + if(!(r->respip_set = respip_set_create())) { + goto err; } r->taglistlen = p->rpz_taglistlen; r->taglist = memdup(p->rpz_taglist, r->taglistlen); @@ -350,26 +382,34 @@ rpz_create(struct config_auth* p) if(!p->rpz_cname) { log_err("RPZ override with cname action found, but not " "rpz-cname-override configured"); - free(r); - return 0; + goto err; } if(sldns_str2wire_dname_buf(p->rpz_cname, nm, &nmlen) != 0) { log_err("cannot parse RPZ cname override: %s", p->rpz_cname); - free(r); - return 0; + goto err; } r->cname_override = new_cname_override(r->region, nm, nmlen); if(!r->cname_override) { - free(r); - return 0; + goto err; } } r->log = p->rpz_log; if(p->rpz_log_name) r->log_name = strdup(p->rpz_log_name); return r; +err: + if(r) { + if(r->local_zones) + local_zones_delete(r->local_zones); + if(r->respip_set) + respip_set_delete(r->respip_set); + if(r->taglist) + free(r->taglist); + free(r); + } + return NULL; } /** Remove RPZ zone name from dname */ @@ -436,46 +476,46 @@ rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, return 1; } -/** Insert RR into RPZ's ACL tree */ +/** Insert RR into RPZ's respip_set */ static int -rpz_insert_client_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, +rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { - /* TODO: remove void casts */ - (void)r; - (void)dnamelen; - (void)rrtype; - (void)rrclass; - (void)ttl; - (void)rdata; - (void)rdata_len; - (void)rr; - (void)rr_len; - - if(a == RPZ_DROP_ACTION) { - /* insert into r->acl tree */ - struct sockaddr_storage addr; - char str[INET6_ADDRSTRLEN]; - socklen_t addrlen; - int net, af; - if(!netblockdnametoaddr(dname, &addr, &addrlen, &net, &af)) - return 0; - /* TODO insert into acl tree */ -#if 0 - if((inet_ntop(af, - (af == AF_INET) ? - (void*)&((struct sockaddr_in*)&addr)->sin_addr : - (void*)&((struct sockaddr_in6*)&addr)->sin6_addr, - str, INET6_ADDRSTRLEN))) - log_info("rpz %s/%d\n", str, net); -#endif - } else { + struct resp_addr* node; + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + char* rrstr = sldns_wire2str_rr(rr, rr_len); + enum respip_action respa = rpz_action_to_respip_action(a); + + if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION || + respa == respip_invalid) { verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s", rpz_action_to_string(a)); return 0; } - return 0; + + if(!netblockdnametoaddr(dname, &addr, &addrlen, &net, &af)) + return 0; + + lock_rw_wrlock(&r->respip_set->lock); + if(!(node=respip_sockaddr_find_or_create(r->respip_set, &addr, addrlen, + net, 1, rrstr))) { + lock_rw_unlock(&r->respip_set->lock); + return 0; + } + + lock_rw_wrlock(&node->lock); + lock_rw_unlock(&r->respip_set->lock); + node->action = respa; + + if(a == RPZ_LOCAL_DATA_ACTION) { + respip_enter_rr(r->respip_set->region, node, rrtype, + rrclass, ttl, rdata, rdata_len, rrstr, ""); + } + lock_rw_unlock(&node->lock); + return 1; } void @@ -501,8 +541,8 @@ rpz_insert_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); } - else if(t == RPZ_CLIENT_IP_TRIGGER) { - rpz_insert_client_ip_trigger(r, policydname, policydnamelen, + else if(t == RPZ_RESPONSE_IP_TRIGGER) { + rpz_insert_response_ip_trigger(r, policydname, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); } @@ -637,46 +677,135 @@ rpz_data_delete_rr(struct local_zone* z, uint8_t* policydname, return 1; } -void -rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, - size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, - size_t rdatalen) +/** + * Remove RR from RPZ's respip set + * @param raddr: respip node + * @param rdata: rdata of RR to remove + * @param rdatalen: length of rdata + * @param region: RPZ's repsip_set region + * @return: 1 if zone must be removed after RR deletion + */ +static int +rpz_rrset_delete_rr(struct resp_addr* raddr, uint16_t rr_type, uint8_t* rdata, + size_t rdatalen, struct regional* region) +{ + size_t index; + struct packed_rrset_data* d; + if(!raddr->data) + return 1; + d = raddr->data->entry.data; + if(ntohs(raddr->data->rk.type) != rr_type) { + return 0; + } + if(packed_rrset_find_rr(d, rdata, rdatalen, &index)) { + if(d->count == 1) { + /* regional alloc'd */ + raddr->data->entry.data = NULL; + raddr->data = NULL; + return 1; + } + if(d->count > 1) { + struct packed_rrset_data* new; + new = packed_rrset_remove_rr(d, index, region); + if(!new) + return 0; + raddr->data->entry.data = new; + } + } + return 0; + +} + +/** Remove RR from RPZ's local-zone */ +static void +rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rr_type, uint16_t rr_class, + uint8_t* rdatawl, size_t rdatalen) { struct local_zone* z; + int delete_zone = 1; + z = rpz_find_zone(r, dname, dnamelen, rr_class, + 1 /* only exact */, 1 /* wr lock */); + if(!z) { + verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, " + "RPZ domain not found"); + return; + } + if(a == RPZ_LOCAL_DATA_ACTION) + delete_zone = rpz_data_delete_rr(z, dname, + dnamelen, rr_type, rdatawl, rdatalen); + else if(a != localzone_type_to_rpz_action(z->type)) { + return; + } + lock_rw_unlock(&z->lock); + if(delete_zone) { + local_zones_del_zone(r->local_zones, z); + } + return; +} + +static void +rpz_remove_response_ip_trigger(struct rpz* r, uint8_t* dname, enum rpz_action a, + uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) +{ + struct resp_addr* node; + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + int delete_respip = 1; + + if(!netblockdnametoaddr(dname, &addr, &addrlen, &net, &af)) + return; + + lock_rw_wrlock(&r->respip_set->lock); + if(!(node = (struct resp_addr*)addr_tree_find( + &r->respip_set->ip_tree, &addr, addrlen, net))) { + verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, " + "RPZ domain not found"); + lock_rw_unlock(&r->respip_set->lock); + return; + } + + lock_rw_wrlock(&node->lock); + if(a == RPZ_LOCAL_DATA_ACTION) { + /* remove RR, signal whether RR can be removed */ + delete_respip = rpz_rrset_delete_rr(node, rr_type, rdatawl, + rdatalen, r->respip_set->region); + } + if(delete_respip) { + /* delete + reset parent pointers */ + respip_sockaddr_delete(r->respip_set, node); + } else { + lock_rw_unlock(&node->lock); + } + lock_rw_unlock(&r->respip_set->lock); +} + +void +rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, size_t dnamelen, + uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) +{ size_t policydnamelen; /* name is free'd in local_zone delete */ uint8_t* policydname = calloc(1, LDNS_MAX_DOMAINLEN + 1); enum rpz_trigger t; enum rpz_action a; - int delete_zone = 1; a = rpz_rr_to_action(rr_type, rdatawl, rdatalen); + if(a == RPZ_INVALID_ACTION) + return; if(!(policydnamelen = strip_dname_origin(dname, dnamelen, aznamelen, policydname))) { free(policydname); return; } t = rpz_dname_to_trigger(policydname); - if(a != RPZ_INVALID_ACTION && t == RPZ_QNAME_TRIGGER) { - z = rpz_find_zone(r, policydname, policydnamelen, rr_class, - 1 /* only exact */, 1 /* wr lock */); - if(!z) { - verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, " - "RPZ domain not found"); - free(policydname); - return; - } - if(a == RPZ_LOCAL_DATA_ACTION) - delete_zone = rpz_data_delete_rr(z, policydname, - policydnamelen, rr_type, rdatawl, rdatalen); - else if(a != localzone_type_to_rpz_action(z->type)) { - free(policydname); - return; - } - lock_rw_unlock(&z->lock); - if(delete_zone) { - local_zones_del_zone(r->local_zones, z); - } + if(t == RPZ_QNAME_TRIGGER) { + rpz_remove_qname_trigger(r, policydname, policydnamelen, a, + rr_type, rr_class, rdatawl, rdatalen); + } else if(t == RPZ_RESPONSE_IP_TRIGGER) { + rpz_remove_response_ip_trigger(r, policydname, a, rr_type, + rdatawl, rdatalen); } free(policydname); } diff --git a/services/rpz.h b/services/rpz.h index 64fac1ea4..a5b9e67ab 100644 --- a/services/rpz.h +++ b/services/rpz.h @@ -48,6 +48,7 @@ #include "services/authzone.h" #include "sldns/sbuffer.h" #include "daemon/stats.h" +#include "respip/respip.h" /** * RPZ triggers, only the QNAME trigger is currently supported in Unbound. @@ -87,6 +88,7 @@ enum rpz_action { */ struct rpz { struct local_zones* local_zones; + struct respip_set* respip_set; uint8_t* taglist; size_t taglistlen; enum rpz_action action_override; @@ -157,10 +159,11 @@ int rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env, void rpz_delete(struct rpz* r); /** - * Clear local-zones in RPZ, used after reloading file or AXFR/HTTP transfer. + * Clear local-zones and respip data in RPZ, used after reloading file or + * AXFR/HTTP transfer. * @param r: RPZ to use */ -int rpz_clear_lz(struct rpz* r); +int rpz_clear(struct rpz* r); /** * Create RPZ. RPZ must be added to linked list after creation. @@ -175,4 +178,10 @@ struct rpz* rpz_create(struct config_auth* p); */ const char* rpz_action_to_string(enum rpz_action a); +/** + * Prepare RPZ after procesing feed content. + * @param r: RPZ to use + */ +void rpz_finish_config(struct rpz* r); + #endif /* SERVICES_RPZ_H */ diff --git a/smallapp/unbound-checkconf.c b/smallapp/unbound-checkconf.c index 0cf3d35aa..713380600 100644 --- a/smallapp/unbound-checkconf.c +++ b/smallapp/unbound-checkconf.c @@ -642,8 +642,9 @@ check_hints(struct config_file* cfg) static void check_auth(struct config_file* cfg) { + int is_rpz; struct auth_zones* az = auth_zones_create(); - if(!az || !auth_zones_apply_cfg(az, cfg, 0)) { + if(!az || !auth_zones_apply_cfg(az, cfg, 0i, &is_rpz)) { fatal_exit("Could not setup authority zones"); } auth_zones_delete(az); diff --git a/util/config_file.c b/util/config_file.c index dbd5a7bad..1042d1a2c 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -1900,7 +1900,7 @@ char* config_taglist2str(struct config_file* cfg, uint8_t* taglist, return strdup(buf); } -int taglist_intersect(uint8_t* list1, size_t list1len, uint8_t* list2, +int taglist_intersect(uint8_t* list1, size_t list1len, const uint8_t* list2, size_t list2len) { size_t i; diff --git a/util/config_file.h b/util/config_file.h index 3bcd3db0a..a8fb88a4f 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -1036,7 +1036,7 @@ char* config_taglist2str(struct config_file* cfg, uint8_t* taglist, * @param list2len: length in bytes of second list. * @return true if there are tags in common, 0 if not. */ -int taglist_intersect(uint8_t* list1, size_t list1len, uint8_t* list2, +int taglist_intersect(uint8_t* list1, size_t list1len, const uint8_t* list2, size_t list2len); /** diff --git a/util/net_help.c b/util/net_help.c index 9bc240c5d..9ba5ca15b 100644 --- a/util/net_help.c +++ b/util/net_help.c @@ -356,13 +356,12 @@ static int ipdnametoaddr(uint8_t* dname, struct sockaddr_storage* addr, int netblockdnametoaddr(uint8_t* dname, struct sockaddr_storage* addr, socklen_t* addrlen, int* net, int* af) { - int e; char buff[3 /* 3 digit netblock */ + 1]; if(*dname > 3) /* netblock invalid */ return 0; memcpy(buff, dname+1, *dname); - buff[(*dname)+1] = '\0'; + buff[*dname] = '\0'; *net = atoi(buff); dname += *dname; dname++; diff --git a/util/storage/dnstree.c b/util/storage/dnstree.c index 190369d85..f883044af 100644 --- a/util/storage/dnstree.c +++ b/util/storage/dnstree.c @@ -104,11 +104,12 @@ int addr_tree_insert(rbtree_type* tree, struct addr_tree_node* node, return rbtree_insert(tree, &node->node) != NULL; } -void addr_tree_init_parents(rbtree_type* tree) +void addr_tree_init_parents_node(struct addr_tree_node* node) { - struct addr_tree_node* node, *prev = NULL, *p; + struct addr_tree_node* prev = NULL, *p; int m; - RBTREE_FOR(node, struct addr_tree_node*, tree) { + for(; (rbnode_type*)node != RBTREE_NULL; + node = (struct addr_tree_node*)rbtree_next((rbnode_type*)node)) { node->parent = NULL; if(!prev || prev->addrlen != node->addrlen) { prev = node; @@ -130,6 +131,12 @@ void addr_tree_init_parents(rbtree_type* tree) } } +void addr_tree_init_parents(rbtree_type* tree) +{ + addr_tree_init_parents_node( + (struct addr_tree_node*)rbtree_first(tree)); +} + void name_tree_init_parents(rbtree_type* tree) { struct name_tree_node* node, *prev = NULL, *p; diff --git a/util/storage/dnstree.h b/util/storage/dnstree.h index 782644b63..d54602fd7 100644 --- a/util/storage/dnstree.h +++ b/util/storage/dnstree.h @@ -173,6 +173,13 @@ int addr_tree_insert(rbtree_type* tree, struct addr_tree_node* node, */ void addr_tree_init_parents(rbtree_type* tree); +/** + * Initialize parent pointers in partial addr tree. + * Reinitialize pointer for part of tree, used after node deletion + * @param node: node to start parent pointer initialization for. + */ +void addr_tree_init_parents_node(struct addr_tree_node* node); + /** * Lookup closest encloser in addr tree. * @param tree: addr tree