- Added RPZ response IP support

This commit is contained in:
Ralph Dolmans 2019-07-16 18:43:16 +02:00
parent 395d83cfc8
commit a8d6147ae4
17 changed files with 473 additions and 196 deletions

View file

@ -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.

View file

@ -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;

View file

@ -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);

View file

@ -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)) {

View file

@ -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,12 +87,49 @@ respip_set_get_tree(struct respip_set* set)
return &set->ip_tree;
}
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;
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,
addrlen, net)) {
/* We know we didn't find it, so this should be
* impossible. */
log_warn("unexpected: duplicate address: %s", ipstr);
}
}
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 resp_addr* node;
struct sockaddr_storage addr;
int net;
socklen_t addrlen;
@ -122,22 +138,8 @@ respip_find_or_create(struct respip_set* set, const char* ipstr, int create)
log_err("cannot parse netblock: '%s'", ipstr);
return NULL;
}
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;
}
node->action = respip_none;
if(!addr_tree_insert(&set->ip_tree, &node->node, &addr,
addrlen, net)) {
/* We know we didn't find it, so this should be
* impossible. */
log_warn("unexpected: duplicate address: %s", ipstr);
}
}
return node;
return respip_sockaddr_find_or_create(set, &addr, addrlen, net, create,
ipstr);
}
static int
@ -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; i<rep->an_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;

View file

@ -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 */

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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;
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_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 {
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);
}

View file

@ -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 */

View file

@ -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);

View file

@ -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;

View file

@ -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);
/**

View file

@ -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++;

View file

@ -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;

View file

@ -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