diff --git a/daemon/worker.c b/daemon/worker.c index b994645b4..b3b74c3af 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -1355,7 +1355,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, goto send_reply; } if(worker->env.auth_zones && - rpz_apply_qname_trigger(worker->env.auth_zones, + rpz_callback_from_worker_request(worker->env.auth_zones, &worker->env, &qinfo, &edns, c->buffer, worker->scratchpad, repinfo, acladdr->taglist, acladdr->taglen, &worker->stats)) { regional_free_all(worker->scratchpad); diff --git a/doc/example.conf.in b/doc/example.conf.in index a7287ec5e..ff5c7ca8e 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -1152,10 +1152,11 @@ remote-control: # dnstap-log-forwarder-response-messages: no # Response Policy Zones -# RPZ policies. Applied in order of configuration. QNAME and Response IP -# Address trigger are the only supported triggers. Supported actions are: -# NXDOMAIN, NODATA, PASSTHRU, DROP and Local Data. Policies can be loaded from -# file, using zone transfer, or using HTTP. The respip module needs to be added +# RPZ policies. Applied in order of configuration. QNAME, Response IP +# Address, nsdname, nsip and clientip triggers are supported. Supported +# actions are: NXDOMAIN, NODATA, PASSTHRU, DROP, Local Data, tcp-only +# and drop. Policies can be loaded from a file, or using zone +# transfer, or using HTTP. The respip module needs to be added # to the module-config, e.g.: module-config: "respip validator iterator". # rpz: # name: "rpz.example.com" diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in index 1b5ef393c..c107c3bbc 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -684,8 +684,8 @@ specific cache, after getting processed by the edns client subnet module. .TP .I num.rpz.action. Number of queries answered using configured RPZ policy, per RPZ action type. -Possible actions are: nxdomain, nodata, passthru, drop, local_data, disabled, -and cname_override. +Possible actions are: nxdomain, nodata, passthru, drop, tcp\-only, local\-data, +disabled, and cname\-override. .SH "FILES" .TP .I @ub_conf_file@ diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 1f7e191ed..ecf7245a0 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -2516,10 +2516,49 @@ with a different name. RPZ clauses are applied in order of configuration. The \fBrespip\fR module needs to be added to the \fBmodule-config\fR, e.g.: \fBmodule-config: "respip validator iterator"\fR. .P -Only the QNAME and Response IP Address triggers are supported. The supported RPZ -actions are: NXDOMAIN, NODATA, PASSTHRU, DROP and Local Data. RPZ QNAME triggers -are applied after -\fBlocal-zones\fR and before \fBauth-zones\fR. +QNAME, Response IP Address, nsdname, nsip and clientip triggers are supported. +Supported actions are: NXDOMAIN, NODATA, PASSTHRU, DROP, Local Data, tcp\-only +and drop. RPZ QNAME triggers are applied after \fBlocal\-zones\fR and +before \fBauth\-zones\fR. +.P +The rpz zone is formatted with a SOA start record as usual. The items in +the zone are entries, that specify what to act on (the trigger) and what to +do (the action). The trigger to act on is recorded in the name, the action +to do is recorded as the resource record. The names all end in the zone +name, so you could type the trigger names without a trailing dot in the +zonefile. +.P +An example RPZ record, that answers example.com with NXDOMAIN +.nf + example.com CNAME . +.fi +.P +The triggers are encoded in the name on the left +.nf + name query name + netblock.rpz-client-ip client IP address + netblock.rpz-ip response IP address in the answer + name.rpz-nsdname nameserver name + netblock.rpz-nsip nameserver IP address +.fi +The netblock is written as .. +For IPv6 use 'zz' for '::'. Specify individual addresses with scope length +of 32 or 128. For example, 24.10.100.51.198.rpz-ip is 198.51.100.10/24 and +32.10.zz.db8.2001.rpz-ip is 2001:db8:0:0:0:0:0:10/32. +.P +The actions are specified with the record on the right +.nf + CNAME . nxdomain reply + CNAME *. nodata reply + CNAME rpz-passthru. do nothing, allow to continue + CNAME rpz-drop. the query is dropped + CNAME rpz-tcp-only. answer over TCP + A 192.0.2.1 answer with this IP address +.fi +Other records like AAAA, TXT and other CNAMEs (not rpz-..) can also be used to +answer queries with that content. +.P +The RPZ zones can be configured in the config file with these settings in the \fBrpz:\fR block. .TP .B name: \fI Name of the authority zone. diff --git a/iterator/iterator.c b/iterator/iterator.c index efc94c37b..e5a855282 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -2529,6 +2529,23 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, /* Add the current set of unused targets to our queue. */ delegpt_add_unused_targets(iq->dp); + if(qstate->env->auth_zones) { + /* apply rpz triggers at query time */ + struct dns_msg* forged_response = rpz_callback_from_iterator_module(qstate, iq); + if(forged_response != NULL) { + qstate->ext_state[id] = module_finished; + qstate->return_rcode = FLAGS_GET_RCODE(forged_response->rep->flags); + qstate->return_msg = forged_response; + iq->response = forged_response; + next_state(iq, FINISHED_STATE); + if(!iter_prepend(iq, qstate->return_msg, qstate->region)) { + log_err("rpz, prepend rrsets: out of memory"); + return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + } + return 0; + } + } + /* Select the next usable target, filtering out unsuitable targets. */ target = iter_server_selection(ie, qstate->env, iq->dp, iq->dp->name, iq->dp->namelen, iq->qchase.qtype, @@ -2719,6 +2736,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, { int dnsseclame = 0; enum response_type type; + iq->num_current_queries--; if(!inplace_cb_query_response_call(qstate->env, qstate, iq->response)) @@ -3062,6 +3080,39 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, /* set the current request's qname to the new value. */ iq->qchase.qname = sname; iq->qchase.qname_len = snamelen; + if(qstate->env->auth_zones) { + /* apply rpz qname triggers after cname */ + struct dns_msg* forged_response = + rpz_callback_from_iterator_cname(qstate, iq); + while(forged_response && reply_find_rrset_section_an( + forged_response->rep, iq->qchase.qname, + iq->qchase.qname_len, LDNS_RR_TYPE_CNAME, + iq->qchase.qclass)) { + /* another cname to follow */ + if(!handle_cname_response(qstate, iq, forged_response, + &sname, &snamelen)) { + errinf(qstate, "malloc failure, CNAME info"); + return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + } + iq->qchase.qname = sname; + iq->qchase.qname_len = snamelen; + forged_response = + rpz_callback_from_iterator_cname(qstate, iq); + } + if(forged_response != NULL) { + qstate->ext_state[id] = module_finished; + qstate->return_rcode = FLAGS_GET_RCODE(forged_response->rep->flags); + qstate->return_msg = forged_response; + iq->response = forged_response; + next_state(iq, FINISHED_STATE); + if(!iter_prepend(iq, qstate->return_msg, qstate->region)) { + log_err("rpz after cname, prepend rrsets: out of memory"); + return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + } + qstate->return_msg->qinfo = qstate->qinfo; + return 0; + } + } /* Clear the query state, since this is a query restart. */ iq->deleg_msg = NULL; iq->dp = NULL; diff --git a/respip/respip.c b/respip/respip.c index aae41f5d6..3d1b3feaf 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -25,6 +25,7 @@ #include "respip/respip.h" #include "services/view.h" #include "sldns/rrdef.h" +#include "util/data/dname.h" /** Subset of resp_addr.node, used for inform-variant logging */ @@ -483,8 +484,8 @@ respip_views_apply_cfg(struct views* vs, struct config_file* cfg, * This function returns the copied rrset key on success, and NULL on memory * allocation failure. */ -static struct ub_packed_rrset_key* -copy_rrset(const struct ub_packed_rrset_key* key, struct regional* region) +struct ub_packed_rrset_key* +respip_copy_rrset(const struct ub_packed_rrset_key* key, struct regional* region) { struct ub_packed_rrset_key* ck = regional_alloc(region, sizeof(struct ub_packed_rrset_key)); @@ -602,7 +603,7 @@ rdata2sockaddr(const struct packed_rrset_data* rd, uint16_t rtype, size_t i, */ static struct resp_addr* respip_addr_lookup(const struct reply_info *rep, struct respip_set* rs, - size_t* rrset_id) + size_t* rrset_id, size_t* rr_id) { size_t i; struct resp_addr* ra; @@ -625,6 +626,7 @@ respip_addr_lookup(const struct reply_info *rep, struct respip_set* rs, &ss, addrlen); if(ra) { *rrset_id = i; + *rr_id = j; lock_rw_rdlock(&ra->lock); lock_rw_unlock(&rs->lock); return ra; @@ -635,43 +637,6 @@ respip_addr_lookup(const struct reply_info *rep, struct respip_set* rs, return NULL; } -/* - * Create a new reply_info based on 'rep'. The new info is based on - * the passed 'rep', but ignores any rrsets except for the first 'an_numrrsets' - * RRsets in the answer section. These answer rrsets are copied to the - * new info, up to 'copy_rrsets' rrsets (which must not be larger than - * 'an_numrrsets'). If an_numrrsets > copy_rrsets, the remaining rrsets array - * entries will be kept empty so the caller can fill them later. When rrsets - * are copied, they are shallow copied. The caller must ensure that the - * copied rrsets are valid throughout its lifetime and must provide appropriate - * mutex if it can be shared by multiple threads. - */ -static struct reply_info * -make_new_reply_info(const struct reply_info* rep, struct regional* region, - size_t an_numrrsets, size_t copy_rrsets) -{ - struct reply_info* new_rep; - size_t i; - - /* create a base struct. we specify 'insecure' security status as - * the modified response won't be DNSSEC-valid. In our faked response - * the authority and additional sections will be empty (except possible - * EDNS0 OPT RR in the additional section appended on sending it out), - * so the total number of RRsets is an_numrrsets. */ - new_rep = construct_reply_info_base(region, rep->flags, - rep->qdcount, rep->ttl, rep->prefetch_ttl, - rep->serve_expired_ttl, an_numrrsets, 0, 0, an_numrrsets, - sec_status_insecure); - if(!new_rep) - return NULL; - if(!reply_info_alloc_rrset_keys(new_rep, NULL, region)) - return NULL; - for(i=0; irrsets[i] = rep->rrsets[i]; - - return new_rep; -} - /** * See if response-ip or tag data should override the original answer rrset * (which is rep->rrsets[rrset_id]) and if so override it. @@ -730,7 +695,7 @@ respip_data_answer(enum respip_action action, "response-ip redirect with tag data [%d] %s", tag, (tagrk.dname = rep->rrsets[rrset_id]->rk.dname; @@ -807,7 +772,6 @@ respip_nodata_answer(uint16_t qtype, enum respip_action action, * is explicitly specified. */ int rcode = (action == respip_always_nxdomain)? LDNS_RCODE_NXDOMAIN:LDNS_RCODE_NOERROR; - /* We should empty the answer section except for any preceding * CNAMEs (in that case rrset_id > 0). Type-ANY case is * special as noted in respip_data_answer(). */ @@ -907,7 +871,7 @@ respip_rewrite_reply(const struct query_info* qinfo, size_t tag_datas_size; struct view* view = NULL; struct respip_set* ipset = NULL; - size_t rrset_id = 0; + size_t rrset_id = 0, rr_id = 0; enum respip_action action = respip_none; int tag = -1; struct resp_addr* raddr = NULL; @@ -948,7 +912,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, &rrset_id))) { + view->respip_set, &rrset_id, &rr_id))) { /** for per-view respip directives the action * can only be direct (i.e. not tag-based) */ action = raddr->action; @@ -962,7 +926,7 @@ respip_rewrite_reply(const struct query_info* qinfo, } } if(!raddr && (raddr = respip_addr_lookup(rep, ipset, - &rrset_id))) { + &rrset_id, &rr_id))) { action = (enum respip_action)local_data_find_tag_action( raddr->taglist, raddr->taglen, ctaglist, ctaglen, tag_actions, tag_actions_size, @@ -976,7 +940,7 @@ respip_rewrite_reply(const struct query_info* qinfo, if(!r->taglist || taglist_intersect(r->taglist, r->taglistlen, ctaglist, ctaglen)) { if((raddr = respip_addr_lookup(rep, - r->respip_set, &rrset_id))) { + r->respip_set, &rrset_id, &rr_id))) { if(!respip_use_rpz(raddr, r, &action, &data, &rpz_log, &log_name, &rpz_cname_override, region, &rpz_used)) { @@ -987,6 +951,21 @@ respip_rewrite_reply(const struct query_info* qinfo, return 0; } if(rpz_used) { + if(verbosity >= VERB_ALGO) { + struct sockaddr_storage ss; + socklen_t ss_len = 0; + char nm[256], ip[256]; + char qn[255+1]; + if(!rdata2sockaddr(rep->rrsets[rrset_id]->entry.data, ntohs(rep->rrsets[rrset_id]->rk.type), rr_id, &ss, &ss_len)) + snprintf(ip, sizeof(ip), "invalidRRdata"); + else + addr_to_str(&ss, ss_len, ip, sizeof(ip)); + dname_str(qinfo->qname, qn); + addr_to_str(&raddr->node.addr, + raddr->node.addrlen, + nm, sizeof(nm)); + verbose(VERB_ALGO, "respip: rpz response-ip trigger %s/%d on %s %s with action %s", nm, raddr->node.net, qn, ip, rpz_action_to_string(respip_action_to_rpz_action(action))); + } /* break to make sure 'a' stays pointed * to used auth_zone, and keeps lock */ break; @@ -1209,7 +1188,7 @@ respip_merge_cname(struct reply_info* base_rep, if(!new_rep) return 0; for(i=0,j=base_rep->an_numrrsets; ian_numrrsets; i++,j++) { - new_rep->rrsets[j] = copy_rrset(tgt_rep->rrsets[i], region); + new_rep->rrsets[j] = respip_copy_rrset(tgt_rep->rrsets[i], region); if(!new_rep->rrsets[j]) return 0; } diff --git a/respip/respip.h b/respip/respip.h index bbd471421..3dfb4e9f0 100644 --- a/respip/respip.h +++ b/respip/respip.h @@ -294,4 +294,7 @@ respip_enter_rr(struct regional* region, struct resp_addr* raddr, */ void respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node); + +struct ub_packed_rrset_key* +respip_copy_rrset(const struct ub_packed_rrset_key* key, struct regional* region); #endif /* RESPIP_RESPIP_H */ diff --git a/services/authzone.c b/services/authzone.c index f9f5bba9e..44dda2a71 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -1950,6 +1950,17 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, return 0; } +/** find the apex SOA RRset, if it exists */ +struct auth_rrset* auth_zone_get_soa_rrset(struct auth_zone* z) +{ + struct auth_data* apex; + struct auth_rrset* soa; + apex = az_find_name(z, z->name, z->namelen); + if(!apex) return NULL; + soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); + return soa; +} + /** find serial number of zone or false if none */ int auth_zone_get_serial(struct auth_zone* z, uint32_t* serial) diff --git a/services/authzone.h b/services/authzone.h index 1c6d5cf42..d24e569d3 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -636,6 +636,9 @@ int auth_zones_startprobesequence(struct auth_zones* az, /** read auth zone from zonefile. caller must lock zone. false on failure */ int auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg); +/** find the apex SOA RRset, if it exists. NULL if no SOA RRset. */ +struct auth_rrset* auth_zone_get_soa_rrset(struct auth_zone* z); + /** find serial number of zone or false if none (no SOA record) */ int auth_zone_get_serial(struct auth_zone* z, uint32_t* serial); diff --git a/services/localzone.c b/services/localzone.c index 54f55ab81..075f1087e 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -1570,6 +1570,15 @@ local_zone_does_not_cover(struct local_zone* z, struct query_info* qinfo, return (lr == NULL); } +static inline int +local_zone_is_udp_query(struct comm_reply* repinfo) { + return repinfo != NULL + ? (repinfo->c != NULL + ? repinfo->c->type == comm_udp + : 0) + : 0; +} + int local_zones_zone_answer(struct local_zone* z, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, @@ -1592,7 +1601,9 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, lz_type == local_zone_redirect || lz_type == local_zone_inform_redirect || lz_type == local_zone_always_nxdomain || - lz_type == local_zone_always_nodata) { + lz_type == local_zone_always_nodata || + (lz_type == local_zone_truncate + && local_zone_is_udp_query(repinfo))) { /* for static, reply nodata or nxdomain * for redirect, reply nodata */ /* no additional section processing, @@ -1602,9 +1613,11 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, */ int rcode = (ld || lz_type == local_zone_redirect || lz_type == local_zone_inform_redirect || - lz_type == local_zone_always_nodata)? + lz_type == local_zone_always_nodata || + lz_type == local_zone_truncate)? LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; - if(z->soa && z->soa_negative) + rcode = (lz_type == local_zone_truncate ? (rcode|BIT_TC) : rcode); + 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, @@ -1661,7 +1674,7 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, * does not, then we should make this noerror/nodata */ if(ld && ld->rrsets) { int rcode = LDNS_RCODE_NOERROR; - if(z->soa && z->soa_negative) + 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, @@ -1860,6 +1873,7 @@ const char* local_zone_type2str(enum localzone_type t) case local_zone_always_deny: return "always_deny"; case local_zone_always_null: return "always_null"; case local_zone_noview: return "noview"; + case local_zone_truncate: return "truncate"; case local_zone_invalid: return "invalid"; } return "badtyped"; @@ -1899,6 +1913,8 @@ int local_zone_str2type(const char* type, enum localzone_type* t) *t = local_zone_always_null; else if(strcmp(type, "noview") == 0) *t = local_zone_noview; + else if(strcmp(type, "truncate") == 0) + *t = local_zone_truncate; else if(strcmp(type, "nodefault") == 0) *t = local_zone_nodefault; else return 0; diff --git a/services/localzone.h b/services/localzone.h index b52d81dc7..9a06a7f84 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -101,6 +101,8 @@ enum localzone_type { local_zone_always_null, /** answer not from the view, but global or no-answer */ local_zone_noview, + /** truncate the response; client should retry via tcp */ + local_zone_truncate, /** Invalid type, cannot be used to generate answer */ local_zone_invalid }; @@ -563,6 +565,8 @@ enum respip_action { respip_always_nodata = local_zone_always_nodata, /** answer with nodata response */ respip_always_deny = local_zone_always_deny, + /** RPZ: truncate answer in order to force switch to tcp */ + respip_truncate = local_zone_truncate, /* The rest of the values are only possible as * access-control-tag-action */ diff --git a/services/mesh.c b/services/mesh.c index 5679a8b64..200531903 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -1183,6 +1183,22 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, m->s.env->mesh->num_reply_addrs--; } +static inline int +mesh_is_rpz_respip_tcponly_action(struct mesh_state const* m) +{ + struct respip_action_info const* respip_info = m->s.respip_action_info; + return respip_info == NULL + ? 0 + : (respip_info->rpz_used + && !respip_info->rpz_disabled + && respip_info->action == respip_truncate); +} + +static inline int +mesh_is_udp(struct mesh_reply const* r) { + return r->query_reply.c->type == comm_udp; +} + /** * Send reply to mesh reply entry * @param m: mesh state to send it for. @@ -1210,6 +1226,11 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, * null stops the mesh state remove and thus * reply_list modification and accounting */ struct mesh_reply* rlist = m->reply_list; + + /* rpz: apply actions */ + rcode = mesh_is_udp(r) && mesh_is_rpz_respip_tcponly_action(m) + ? (rcode|BIT_TC) : rcode; + /* examine security status */ if(m->s.env->need_to_validate && (!(r->qflags&BIT_CD) || m->s.env->cfg->ignore_cd) && rep && diff --git a/services/rpz.c b/services/rpz.c index 3a1ec00d7..085353b3f 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -50,45 +50,50 @@ #include "util/data/dname.h" #include "util/locks.h" #include "util/regional.h" +#include "util/data/msgencode.h" +#include "services/cache/dns.h" +#include "iterator/iterator.h" +#include "iterator/iter_delegpt.h" +#include "daemon/worker.h" + +typedef struct resp_addr rpz_aclnode_type; + +struct matched_delegation_point { + uint8_t* dname; + size_t dname_len; +}; /** string for RPZ action enum */ const char* rpz_action_to_string(enum rpz_action a) { switch(a) { - case RPZ_NXDOMAIN_ACTION: return "nxdomain"; - case RPZ_NODATA_ACTION: return "nodata"; - case RPZ_PASSTHRU_ACTION: return "passthru"; - case RPZ_DROP_ACTION: return "drop"; - case RPZ_TCP_ONLY_ACTION: return "tcp_only"; - case RPZ_INVALID_ACTION: return "invalid"; - case RPZ_LOCAL_DATA_ACTION: return "local_data"; - case RPZ_DISABLED_ACTION: return "disabled"; - case RPZ_CNAME_OVERRIDE_ACTION: return "cname_override"; - case RPZ_NO_OVERRIDE_ACTION: return "no_override"; + case RPZ_NXDOMAIN_ACTION: return "rpz-nxdomain"; + case RPZ_NODATA_ACTION: return "rpz-nodata"; + case RPZ_PASSTHRU_ACTION: return "rpz-passthru"; + case RPZ_DROP_ACTION: return "rpz-drop"; + case RPZ_TCP_ONLY_ACTION: return "rpz-tcp-only"; + case RPZ_INVALID_ACTION: return "rpz-invalid"; + case RPZ_LOCAL_DATA_ACTION: return "rpz-local-data"; + case RPZ_DISABLED_ACTION: return "rpz-disabled"; + case RPZ_CNAME_OVERRIDE_ACTION: return "rpz-cname-override"; + case RPZ_NO_OVERRIDE_ACTION: return "rpz-no-override"; + default: return "rpz-unknown-action"; } - return "unknown"; } /** RPZ action enum for config string */ static enum rpz_action rpz_config_to_action(char* a) { - if(strcmp(a, "nxdomain") == 0) - return RPZ_NXDOMAIN_ACTION; - else if(strcmp(a, "nodata") == 0) - return RPZ_NODATA_ACTION; - else if(strcmp(a, "passthru") == 0) - return RPZ_PASSTHRU_ACTION; - else if(strcmp(a, "drop") == 0) - return RPZ_DROP_ACTION; - else if(strcmp(a, "tcp_only") == 0) - return RPZ_TCP_ONLY_ACTION; - else if(strcmp(a, "cname") == 0) - return RPZ_CNAME_OVERRIDE_ACTION; - else if(strcmp(a, "disabled") == 0) - return RPZ_DISABLED_ACTION; - return RPZ_INVALID_ACTION; + if(strcmp(a, "nxdomain") == 0) return RPZ_NXDOMAIN_ACTION; + else if(strcmp(a, "nodata") == 0) return RPZ_NODATA_ACTION; + else if(strcmp(a, "passthru") == 0) return RPZ_PASSTHRU_ACTION; + else if(strcmp(a, "drop") == 0) return RPZ_DROP_ACTION; + else if(strcmp(a, "tcp_only") == 0) return RPZ_TCP_ONLY_ACTION; + else if(strcmp(a, "cname") == 0) return RPZ_CNAME_OVERRIDE_ACTION; + else if(strcmp(a, "disabled") == 0) return RPZ_DISABLED_ACTION; + else return RPZ_INVALID_ACTION; } /** string for RPZ trigger enum */ @@ -96,14 +101,14 @@ static const char* rpz_trigger_to_string(enum rpz_trigger r) { switch(r) { - case RPZ_QNAME_TRIGGER: return "qname"; - case RPZ_CLIENT_IP_TRIGGER: return "client_ip"; - case RPZ_RESPONSE_IP_TRIGGER: return "response_ip"; - case RPZ_NSDNAME_TRIGGER: return "nsdname"; - case RPZ_NSIP_TRIGGER: return "nsip"; - case RPZ_INVALID_TRIGGER: return "invalid"; + case RPZ_QNAME_TRIGGER: return "rpz-qname"; + case RPZ_CLIENT_IP_TRIGGER: return "rpz-client-ip"; + case RPZ_RESPONSE_IP_TRIGGER: return "rpz-response-ip"; + case RPZ_NSDNAME_TRIGGER: return "rpz-nsdname"; + case RPZ_NSIP_TRIGGER: return "rpz-nsip"; + case RPZ_INVALID_TRIGGER: return "rpz-invalid"; + default: return "rpz-unknown-trigger"; } - return "unknown"; } /** @@ -137,6 +142,31 @@ get_tld_label(uint8_t* dname, size_t maxdnamelen) return prevlab; } +/** + * The RR types that are to be ignored. + * DNSSEC RRs at the apex, and SOA and NS are ignored. + */ +static int +rpz_type_ignored(uint16_t rr_type) +{ + switch(rr_type) { + case LDNS_RR_TYPE_SOA: + case LDNS_RR_TYPE_NS: + case LDNS_RR_TYPE_DNAME: + /* all DNSSEC-related RRs must be ignored */ + case LDNS_RR_TYPE_DNSKEY: + case LDNS_RR_TYPE_DS: + case LDNS_RR_TYPE_RRSIG: + case LDNS_RR_TYPE_NSEC: + case LDNS_RR_TYPE_NSEC3: + case LDNS_RR_TYPE_NSEC3PARAM: + return 1; + default: + break; + } + return 0; +} + /** * Classify RPZ action for RR type/rdata * @param rr_type: the RR type @@ -208,15 +238,15 @@ static enum localzone_type rpz_action_to_localzone_type(enum rpz_action a) { switch(a) { - case RPZ_NXDOMAIN_ACTION: return local_zone_always_nxdomain; - case RPZ_NODATA_ACTION: return local_zone_always_nodata; - case RPZ_DROP_ACTION: return local_zone_always_deny; - case RPZ_PASSTHRU_ACTION: return local_zone_always_transparent; + case RPZ_NXDOMAIN_ACTION: return local_zone_always_nxdomain; + case RPZ_NODATA_ACTION: return local_zone_always_nodata; + case RPZ_DROP_ACTION: return local_zone_always_deny; + case RPZ_PASSTHRU_ACTION: return local_zone_always_transparent; case RPZ_LOCAL_DATA_ACTION: /* fallthrough */ case RPZ_CNAME_OVERRIDE_ACTION: return local_zone_redirect; - case RPZ_INVALID_ACTION: /* fallthrough */ - case RPZ_TCP_ONLY_ACTION: /* fallthrough */ - default: return local_zone_invalid; + case RPZ_TCP_ONLY_ACTION: return local_zone_truncate; + case RPZ_INVALID_ACTION: /* fallthrough */ + default: return local_zone_invalid; } } @@ -224,15 +254,15 @@ 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_always_deny; - case RPZ_PASSTHRU_ACTION: return respip_always_transparent; - case RPZ_LOCAL_DATA_ACTION: /* fallthrough */ + case RPZ_NXDOMAIN_ACTION: return respip_always_nxdomain; + case RPZ_NODATA_ACTION: return respip_always_nodata; + case RPZ_DROP_ACTION: return respip_always_deny; + case RPZ_PASSTHRU_ACTION: return respip_always_transparent; + case RPZ_LOCAL_DATA_ACTION: /* fallthrough */ case RPZ_CNAME_OVERRIDE_ACTION: return respip_redirect; - case RPZ_INVALID_ACTION: /* fallthrough */ - case RPZ_TCP_ONLY_ACTION: /* fallthrough */ - default: return respip_invalid; + case RPZ_TCP_ONLY_ACTION: return respip_truncate; + case RPZ_INVALID_ACTION: /* fallthrough */ + default: return respip_invalid; } } @@ -240,14 +270,14 @@ static enum rpz_action localzone_type_to_rpz_action(enum localzone_type lzt) { switch(lzt) { - case local_zone_always_nxdomain: return RPZ_NXDOMAIN_ACTION; - case local_zone_always_nodata: return RPZ_NODATA_ACTION; - case local_zone_always_deny: return RPZ_DROP_ACTION; - case local_zone_always_transparent: return RPZ_PASSTHRU_ACTION; - case local_zone_redirect: return RPZ_LOCAL_DATA_ACTION; - case local_zone_invalid: - default: - return RPZ_INVALID_ACTION; + case local_zone_always_nxdomain: return RPZ_NXDOMAIN_ACTION; + case local_zone_always_nodata: return RPZ_NODATA_ACTION; + case local_zone_always_deny: return RPZ_DROP_ACTION; + case local_zone_always_transparent: return RPZ_PASSTHRU_ACTION; + case local_zone_redirect: return RPZ_LOCAL_DATA_ACTION; + case local_zone_truncate: return RPZ_TCP_ONLY_ACTION; + case local_zone_invalid: /* fallthrough */ + default: return RPZ_INVALID_ACTION; } } @@ -255,14 +285,14 @@ enum rpz_action respip_action_to_rpz_action(enum respip_action a) { switch(a) { - case respip_always_nxdomain: return RPZ_NXDOMAIN_ACTION; - case respip_always_nodata: return RPZ_NODATA_ACTION; - case respip_always_deny: return RPZ_DROP_ACTION; - case respip_always_transparent: return RPZ_PASSTHRU_ACTION; - case respip_redirect: return RPZ_LOCAL_DATA_ACTION; - case respip_invalid: - default: - return RPZ_INVALID_ACTION; + case respip_always_nxdomain: return RPZ_NXDOMAIN_ACTION; + case respip_always_nodata: return RPZ_NODATA_ACTION; + case respip_always_deny: return RPZ_DROP_ACTION; + case respip_always_transparent: return RPZ_PASSTHRU_ACTION; + case respip_redirect: return RPZ_LOCAL_DATA_ACTION; + case respip_truncate: return RPZ_TCP_ONLY_ACTION; + case respip_invalid: /* fallthrough */ + default: return RPZ_INVALID_ACTION; } } @@ -298,12 +328,55 @@ rpz_dname_to_trigger(uint8_t* dname, size_t dname_len) return RPZ_QNAME_TRIGGER; } -void rpz_delete(struct rpz* r) +static inline struct clientip_synthesized_rrset* +rpz_clientip_synthesized_set_create(void) +{ + struct clientip_synthesized_rrset* set = calloc(1, sizeof(*set)); + if(set == NULL) { + return NULL; + } + set->region = regional_create(); + if(set->region == NULL) { + free(set); + return NULL; + } + addr_tree_init(&set->entries); + lock_rw_init(&set->lock); + return set; +} + +static void +rpz_clientip_synthesized_rr_delete(rbnode_type* n, void* ATTR_UNUSED(arg)) +{ + struct clientip_synthesized_rr* r = (struct clientip_synthesized_rr*)n->key; + lock_rw_destroy(&r->lock); +#ifdef THREADS_DISABLED + (void)r; +#endif +} + +static inline void +rpz_clientip_synthesized_set_delete(struct clientip_synthesized_rrset* set) +{ + if(set == NULL) { + return; + } + lock_rw_destroy(&set->lock); + traverse_postorder(&set->entries, rpz_clientip_synthesized_rr_delete, NULL); + regional_destroy(set->region); + free(set); +} + +void +rpz_delete(struct rpz* r) { if(!r) return; local_zones_delete(r->local_zones); + local_zones_delete(r->nsdname_zones); respip_set_delete(r->respip_set); + rpz_clientip_synthesized_set_delete(r->client_set); + rpz_clientip_synthesized_set_delete(r->ns_set); regional_destroy(r->region); free(r->taglist); free(r->log_name); @@ -315,13 +388,31 @@ rpz_clear(struct rpz* r) { /* must hold write lock on auth_zone */ local_zones_delete(r->local_zones); + r->local_zones = NULL; + local_zones_delete(r->nsdname_zones); + r->nsdname_zones = NULL; respip_set_delete(r->respip_set); + r->respip_set = NULL; + rpz_clientip_synthesized_set_delete(r->client_set); + r->client_set = NULL; + rpz_clientip_synthesized_set_delete(r->ns_set); + r->ns_set = NULL; if(!(r->local_zones = local_zones_create())){ return 0; } + r->nsdname_zones = local_zones_create(); + if(r->nsdname_zones == NULL) { + return 0; + } if(!(r->respip_set = respip_set_create())) { return 0; } + if(!(r->client_set = rpz_clientip_synthesized_set_create())) { + return 0; + } + if(!(r->ns_set = rpz_clientip_synthesized_set_create())) { + return 0; + } return 1; } @@ -331,6 +422,14 @@ 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); + + lock_rw_wrlock(&r->client_set->lock); + addr_tree_init_parents(&r->client_set->entries); + lock_rw_unlock(&r->client_set->lock); + + lock_rw_wrlock(&r->ns_set->lock); + addr_tree_init_parents(&r->ns_set->entries); + lock_rw_unlock(&r->ns_set->lock); } /** new rrset containing CNAME override, does not yet contain a dname */ @@ -394,9 +493,26 @@ rpz_create(struct config_auth* p) if(!(r->local_zones = local_zones_create())){ goto err; } + + r->nsdname_zones = local_zones_create(); + if(r->local_zones == NULL){ + goto err; + } + if(!(r->respip_set = respip_set_create())) { goto err; } + + r->client_set = rpz_clientip_synthesized_set_create(); + if(r->client_set == NULL) { + goto err; + } + + r->ns_set = rpz_clientip_synthesized_set_create(); + if(r->ns_set == NULL) { + goto err; + } + r->taglistlen = p->rpz_taglistlen; r->taglist = memdup(p->rpz_taglist, r->taglistlen); if(p->rpz_action_override) { @@ -437,8 +553,14 @@ err: if(r) { if(r->local_zones) local_zones_delete(r->local_zones); + if(r->nsdname_zones) + local_zones_delete(r->nsdname_zones); if(r->respip_set) respip_set_delete(r->respip_set); + if(r->client_set != NULL) + rpz_clientip_synthesized_set_delete(r->client_set); + if(r->ns_set != NULL) + rpz_clientip_synthesized_set_delete(r->ns_set); if(r->taglist) free(r->taglist); if(r->region) @@ -467,19 +589,17 @@ strip_dname_origin(uint8_t* dname, size_t dnamelen, size_t originlen, return newdnamelen + 1; /* + 1 for root label */ } -/** Insert RR into RPZ's local-zone */ static void -rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, - 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) +rpz_insert_local_zones_trigger(struct local_zones* lz, uint8_t* dname, + size_t dnamelen, 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) { struct local_zone* z; enum localzone_type tp = local_zone_always_transparent; int dnamelabs = dname_count_labels(dname); - char* rrstr; int newzone = 0; - if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION) { + if(a == RPZ_INVALID_ACTION) { char str[255+1]; if(rrtype == LDNS_RR_TYPE_SOA || rrtype == LDNS_RR_TYPE_NS || rrtype == LDNS_RR_TYPE_DNAME || @@ -499,56 +619,360 @@ rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, return; } - lock_rw_wrlock(&r->local_zones->lock); + lock_rw_wrlock(&lz->lock); /* exact match */ - z = local_zones_find(r->local_zones, dname, dnamelen, dnamelabs, - LDNS_RR_CLASS_IN); - if(z && a != RPZ_LOCAL_DATA_ACTION) { - rrstr = sldns_wire2str_rr(rr, rr_len); - if(!rrstr) { - log_err("malloc error while inserting RPZ qname " - "trigger"); + z = local_zones_find(lz, dname, dnamelen, dnamelabs, LDNS_RR_CLASS_IN); + if(z != NULL && a != RPZ_LOCAL_DATA_ACTION) { + char* rrstr = sldns_wire2str_rr(rr, rr_len); + if(rrstr == NULL) { + log_err("malloc error while inserting rpz nsdname trigger"); free(dname); - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&lz->lock); return; } - verbose(VERB_ALGO, "RPZ: skipping duplicate record: '%s'", - rrstr); + if(rrstr[0]) + rrstr[strlen(rrstr)-1]=0; /* remove newline */ + verbose(VERB_ALGO, "rpz: skipping duplicate record: '%s'", rrstr); free(rrstr); free(dname); - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&lz->lock); return; } - if(!z) { + if(z == NULL) { tp = rpz_action_to_localzone_type(a); - if(!(z = local_zones_add_zone(r->local_zones, dname, dnamelen, - dnamelabs, rrclass, tp))) { - log_warn("RPZ create failed"); - lock_rw_unlock(&r->local_zones->lock); + z = local_zones_add_zone(lz, dname, dnamelen, + dnamelabs, rrclass, tp); + if(z == NULL) { + log_warn("rpz: create failed"); + lock_rw_unlock(&lz->lock); /* dname will be free'd in failed local_zone_create() */ return; } newzone = 1; } if(a == RPZ_LOCAL_DATA_ACTION) { - rrstr = sldns_wire2str_rr(rr, rr_len); - if(!rrstr) { - log_err("malloc error while inserting RPZ qname " - "trigger"); + char* rrstr = sldns_wire2str_rr(rr, rr_len); + if(rrstr == NULL) { + log_err("malloc error while inserting rpz nsdname trigger"); free(dname); - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&lz->lock); return; } lock_rw_wrlock(&z->lock); - local_zone_enter_rr(z, dname, dnamelen, dnamelabs, - rrtype, rrclass, ttl, rdata, rdata_len, rrstr); + local_zone_enter_rr(z, dname, dnamelen, dnamelabs, rrtype, + rrclass, ttl, rdata, rdata_len, rrstr); lock_rw_unlock(&z->lock); free(rrstr); } - if(!newzone) + if(!newzone) { free(dname); - lock_rw_unlock(&r->local_zones->lock); - return; + } + lock_rw_unlock(&lz->lock); +} + +static void +rpz_log_dname(char const* msg, uint8_t* dname, size_t dname_len) +{ + char buf[LDNS_MAX_DOMAINLEN+1]; + (void)dname_len; + dname_str(dname, buf); + verbose(VERB_ALGO, "rpz: %s: <%s>", msg, buf); +} + +static void +rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + 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) +{ + if(a == RPZ_INVALID_ACTION) { + verbose(VERB_ALGO, "rpz: skipping invalid action"); + free(dname); + return; + } + + rpz_insert_local_zones_trigger(r->local_zones, dname, dnamelen, a, rrtype, + rrclass, ttl, rdata, rdata_len, rr, rr_len); +} + +static int +rpz_strip_nsdname_suffix(uint8_t* dname, size_t maxdnamelen, + uint8_t** stripdname, size_t* stripdnamelen) +{ + uint8_t* tldstart = get_tld_label(dname, maxdnamelen); + uint8_t swap; + if(tldstart == NULL) { + if(dname == NULL) { + *stripdname = NULL; + *stripdnamelen = 0; + return 0; + } + *stripdname = memdup(dname, maxdnamelen); + if(!*stripdname) { + *stripdnamelen = 0; + log_err("malloc failure for rpz strip suffix"); + return 0; + } + *stripdnamelen = maxdnamelen; + return 1; + } + /* shorten the domain name briefly, + * then we allocate a new name with the correct length */ + swap = *tldstart; + *tldstart = 0; + (void)dname_count_size_labels(dname, stripdnamelen); + *stripdname = memdup(dname, *stripdnamelen); + *tldstart = swap; + if(!*stripdname) { + *stripdnamelen = 0; + log_err("malloc failure for rpz strip suffix"); + return 0; + } + return 1; +} + +static void +rpz_insert_nsdname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + 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) +{ + uint8_t* dname_stripped = NULL; + size_t dnamelen_stripped = 0; + + rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped, + &dnamelen_stripped); + if(a == RPZ_INVALID_ACTION) { + verbose(VERB_ALGO, "rpz: skipping invalid action"); + free(dname_stripped); + return; + } + + /* dname_stripped is consumed or freed by the insert routine */ + rpz_insert_local_zones_trigger(r->nsdname_zones, dname_stripped, + dnamelen_stripped, a, rrtype, rrclass, ttl, rdata, rdata_len, + rr, rr_len); +} + +static int +rpz_insert_ipaddr_based_trigger(struct respip_set* set, struct sockaddr_storage* addr, + socklen_t addrlen, int net, 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) +{ + struct resp_addr* node; + char* rrstr; + enum respip_action respa = rpz_action_to_respip_action(a); + + lock_rw_wrlock(&set->lock); + rrstr = sldns_wire2str_rr(rr, rr_len); + if(rrstr == NULL) { + log_err("malloc error while inserting rpz ipaddr based trigger"); + lock_rw_unlock(&set->lock); + return 0; + } + + node = respip_sockaddr_find_or_create(set, addr, addrlen, net, 1, rrstr); + if(node == NULL) { + lock_rw_unlock(&set->lock); + free(rrstr); + return 0; + } + + lock_rw_wrlock(&node->lock); + lock_rw_unlock(&set->lock); + + node->action = respa; + + if(a == RPZ_LOCAL_DATA_ACTION) { + respip_enter_rr(set->region, node, rrtype, + rrclass, ttl, rdata, rdata_len, rrstr, ""); + } + + lock_rw_unlock(&node->lock); + free(rrstr); + return 1; +} + +static inline struct clientip_synthesized_rr* +rpz_clientip_ensure_entry(struct clientip_synthesized_rrset* set, + struct sockaddr_storage* addr, socklen_t addrlen, int net) +{ + int insert_ok; + struct clientip_synthesized_rr* node = + (struct clientip_synthesized_rr*)addr_tree_find(&set->entries, + addr, addrlen, net); + + if(node != NULL) { return node; } + + /* node does not yet exist => allocate one */ + node = regional_alloc_zero(set->region, sizeof(*node)); + if(node == NULL) { + log_err("out of memory"); + return NULL; + } + + lock_rw_init(&node->lock); + node->action = RPZ_INVALID_ACTION; + insert_ok = addr_tree_insert(&set->entries, &node->node, + addr, addrlen, net); + if (!insert_ok) { + log_warn("rpz: unexpected: unable to insert clientip address node"); + /* we can not free the just allocated node. + * theoretically a memleak */ + return NULL; + } + + return node; +} + +static void +rpz_report_rrset_error(const char* msg, uint8_t* rr, size_t rr_len) { + char* rrstr = sldns_wire2str_rr(rr, rr_len); + if(rrstr == NULL) { + log_err("malloc error while inserting rpz clientip based record"); + return; + } + log_err("rpz: unexpected: unable to insert %s: %s", msg, rrstr); + free(rrstr); +} + +/* from localzone.c; difference is we don't have a dname */ +struct local_rrset* +rpz_clientip_new_rrset(struct regional* region, + struct clientip_synthesized_rr* raddr, uint16_t rrtype, uint16_t rrclass) +{ + struct packed_rrset_data* pd; + struct local_rrset* rrset = (struct local_rrset*) + regional_alloc_zero(region, sizeof(*rrset)); + if(rrset == NULL) { + log_err("out of memory"); + return NULL; + } + rrset->next = raddr->data; + raddr->data = rrset; + rrset->rrset = (struct ub_packed_rrset_key*) + regional_alloc_zero(region, sizeof(*rrset->rrset)); + if(rrset->rrset == NULL) { + log_err("out of memory"); + return NULL; + } + rrset->rrset->entry.key = rrset->rrset; + pd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*pd)); + if(pd == NULL) { + log_err("out of memory"); + return NULL; + } + pd->trust = rrset_trust_prim_noglue; + pd->security = sec_status_insecure; + rrset->rrset->entry.data = pd; + rrset->rrset->rk.type = htons(rrtype); + rrset->rrset->rk.rrset_class = htons(rrclass); + rrset->rrset->rk.dname = regional_alloc_zero(region, 1); + if(rrset->rrset->rk.dname == NULL) { + log_err("out of memory"); + return NULL; + } + rrset->rrset->rk.dname_len = 1; + return rrset; +} + +static int +rpz_clientip_enter_rr(struct regional* region, struct clientip_synthesized_rr* raddr, + uint16_t rrtype, uint16_t rrclass, time_t ttl, uint8_t* rdata, + size_t rdata_len) +{ + struct local_rrset* rrset; + if (rrtype == LDNS_RR_TYPE_CNAME && raddr->data != NULL) { + log_err("CNAME response-ip data can not co-exist with other " + "client-ip data"); + return 0; + } + + rrset = rpz_clientip_new_rrset(region, raddr, rrtype, rrclass); + if(raddr->data == NULL) { + return 0; + } + + return rrset_insert_rr(region, rrset->rrset->entry.data, rdata, rdata_len, ttl, ""); +} + +static int +rpz_clientip_insert_trigger_rr(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr, + socklen_t addrlen, int net, 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) +{ + struct clientip_synthesized_rr* node; + + lock_rw_wrlock(&set->lock); + + node = rpz_clientip_ensure_entry(set, addr, addrlen, net); + if(node == NULL) { + lock_rw_unlock(&set->lock); + rpz_report_rrset_error("client ip address", rr, rr_len); + return 0; + } + + lock_rw_wrlock(&node->lock); + lock_rw_unlock(&set->lock); + + node->action = a; + if(a == RPZ_LOCAL_DATA_ACTION) { + if(!rpz_clientip_enter_rr(set->region, node, rrtype, + rrclass, ttl, rdata, rdata_len)) { + verbose(VERB_ALGO, "rpz: unable to insert clientip rr"); + lock_rw_unlock(&node->lock); + return 0; + } + + } + + lock_rw_unlock(&node->lock); + + return 1; +} + +static int +rpz_insert_clientip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + 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) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + + if(a == RPZ_INVALID_ACTION) { + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { + verbose(VERB_ALGO, "rpz: unable to parse client ip"); + return 0; + } + + return rpz_clientip_insert_trigger_rr(r->client_set, &addr, addrlen, net, + a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); +} + +static int +rpz_insert_nsip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + 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) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + + if(a == RPZ_INVALID_ACTION) { + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { + verbose(VERB_ALGO, "rpz: unable to parse ns ip"); + return 0; + } + + return rpz_clientip_insert_trigger_rr(r->ns_set, &addr, addrlen, net, + a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); } /** Insert RR into RPZ's respip_set */ @@ -557,15 +981,21 @@ rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, 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) { - struct resp_addr* node; struct sockaddr_storage addr; socklen_t addrlen; int net, af; - char* rrstr; - enum respip_action respa = rpz_action_to_respip_action(a); - if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION || - respa == respip_invalid) { + if(a == RPZ_INVALID_ACTION) { + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { + verbose(VERB_ALGO, "rpz: unable to parse response ip"); + return 0; + } + + if(a == RPZ_INVALID_ACTION || + 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", @@ -573,34 +1003,8 @@ rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, return 0; } - if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) - return 0; - - lock_rw_wrlock(&r->respip_set->lock); - rrstr = sldns_wire2str_rr(rr, rr_len); - if(!rrstr) { - log_err("malloc error while inserting RPZ respip trigger"); - lock_rw_unlock(&r->respip_set->lock); - return 0; - } - if(!(node=respip_sockaddr_find_or_create(r->respip_set, &addr, addrlen, - net, 1, rrstr))) { - lock_rw_unlock(&r->respip_set->lock); - free(rrstr); - 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); - free(rrstr); - return 1; + return rpz_insert_ipaddr_based_trigger(r->respip_set, &addr, addrlen, net, + a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); } int @@ -614,15 +1018,19 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, enum rpz_action a; uint8_t* policydname; + if(rpz_type_ignored(rr_type)) { + /* this rpz action is not valid, eg. this is the SOA or NS RR */ + return 1; + } if(!dname_subdomain_c(dname, azname)) { char* dname_str = sldns_wire2str_dname(dname, dnamelen); char* azname_str = sldns_wire2str_dname(azname, aznamelen); if(dname_str && azname_str) { - log_err("RPZ: name of record (%s) to insert into RPZ is not a " + log_err("rpz: name of record (%s) to insert into RPZ is not a " "subdomain of the configured name of the RPZ zone (%s)", dname_str, azname_str); } else { - log_err("RPZ: name of record to insert into RPZ is not a " + log_err("rpz: name of record to insert into RPZ is not a " "subdomain of the configured name of the RPZ zone"); } free(dname_str); @@ -645,23 +1053,37 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, t = rpz_dname_to_trigger(policydname, policydnamelen); if(t == RPZ_INVALID_TRIGGER) { free(policydname); - verbose(VERB_ALGO, "RPZ: skipping invalid trigger"); + verbose(VERB_ALGO, "rpz: skipping invalid trigger"); return 1; } if(t == RPZ_QNAME_TRIGGER) { + /* policydname will be consumed, no free */ rpz_insert_qname_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); - } - else if(t == RPZ_RESPONSE_IP_TRIGGER) { + } else if(t == RPZ_RESPONSE_IP_TRIGGER) { rpz_insert_response_ip_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); free(policydname); - } - else { + } else if(t == RPZ_CLIENT_IP_TRIGGER) { + rpz_insert_clientip_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); free(policydname); - verbose(VERB_ALGO, "RPZ: skipping unsupported trigger: %s", + } else if(t == RPZ_NSIP_TRIGGER) { + rpz_insert_nsip_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + free(policydname); + } else if(t == RPZ_NSDNAME_TRIGGER) { + rpz_insert_nsdname_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + free(policydname); + } else { + free(policydname); + verbose(VERB_ALGO, "rpz: skipping unsupported trigger: %s", rpz_trigger_to_string(t)); } return 1; @@ -669,7 +1091,7 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, /** * Find RPZ local-zone by qname. - * @param r: rpz containing local-zone tree + * @param zones: local-zone tree * @param qname: qname * @param qname_len: length of qname * @param qclass: qclass @@ -680,7 +1102,7 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, * @return: NULL or local-zone holding rd or wr lock */ static struct local_zone* -rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, +rpz_find_zone(struct local_zones* zones, uint8_t* qname, size_t qname_len, uint16_t qclass, int only_exact, int wr, int zones_keep_lock) { uint8_t* ce; @@ -689,16 +1111,17 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, uint8_t wc[LDNS_MAX_DOMAINLEN+1]; int exact; struct local_zone* z = NULL; + if(wr) { - lock_rw_wrlock(&r->local_zones->lock); + lock_rw_wrlock(&zones->lock); } else { - lock_rw_rdlock(&r->local_zones->lock); + lock_rw_rdlock(&zones->lock); } - z = local_zones_find_le(r->local_zones, qname, qname_len, + z = local_zones_find_le(zones, qname, qname_len, dname_count_labels(qname), LDNS_RR_CLASS_IN, &exact); if(!z || (only_exact && !exact)) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); return NULL; } if(wr) { @@ -707,7 +1130,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, lock_rw_rdlock(&z->lock); } if(!zones_keep_lock) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); } if(exact) @@ -721,7 +1144,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, if(!ce /* should not happen */) { lock_rw_unlock(&z->lock); if(zones_keep_lock) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); } return NULL; } @@ -729,7 +1152,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, if(ce_len+2 > sizeof(wc)) { lock_rw_unlock(&z->lock); if(zones_keep_lock) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); } return NULL; } @@ -740,15 +1163,15 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, if(!zones_keep_lock) { if(wr) { - lock_rw_wrlock(&r->local_zones->lock); + lock_rw_wrlock(&zones->lock); } else { - lock_rw_rdlock(&r->local_zones->lock); + lock_rw_rdlock(&zones->lock); } } - z = local_zones_find_le(r->local_zones, wc, + z = local_zones_find_le(zones, wc, ce_len+2, ce_labs+1, qclass, &exact); if(!z || !exact) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); return NULL; } if(wr) { @@ -757,7 +1180,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, lock_rw_rdlock(&z->lock); } if(!zones_keep_lock) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); } return z; } @@ -852,10 +1275,10 @@ rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, { struct local_zone* z; int delete_zone = 1; - z = rpz_find_zone(r, dname, dnamelen, rr_class, + z = rpz_find_zone(r->local_zones, dname, dnamelen, rr_class, 1 /* only exact */, 1 /* wr lock */, 1 /* keep lock*/); if(!z) { - verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, " + verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, " "RPZ domain not found"); return; } @@ -891,7 +1314,7 @@ rpz_remove_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, 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, " + verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, " "RPZ domain not found"); lock_rw_unlock(&r->respip_set->lock); return; @@ -944,118 +1367,981 @@ rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, size_t dnamelen, /** print log information for an applied RPZ policy. Based on local-zone's * lz_inform_print(). + * The repinfo contains the reply address. If it is NULL, the module + * state is used to report the first IP address (if any). + * The dname is used, for the applied rpz, if NULL, addrnode is used. */ static void -log_rpz_apply(uint8_t* dname, enum rpz_action a, struct query_info* qinfo, - struct comm_reply* repinfo, char* log_name) +log_rpz_apply(char* trigger, uint8_t* dname, struct addr_tree_node* addrnode, + enum rpz_action a, struct query_info* qinfo, + struct comm_reply* repinfo, struct module_qstate* ms, char* log_name) { - char ip[128], txt[512]; + char ip[128], txt[512], portstr[32]; char dnamestr[LDNS_MAX_DOMAINLEN+1]; - uint16_t port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port); - dname_str(dname, dnamestr); - addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); - if(log_name) - snprintf(txt, sizeof(txt), "RPZ applied [%s] %s %s %s@%u", - log_name, dnamestr, rpz_action_to_string(a), ip, - (unsigned)port); - else - snprintf(txt, sizeof(txt), "RPZ applied %s %s %s@%u", - dnamestr, rpz_action_to_string(a), ip, (unsigned)port); + uint16_t port = 0; + if(dname) { + dname_str(dname, dnamestr); + } else if(addrnode) { + char a[128]; + addr_to_str(&addrnode->addr, addrnode->addrlen, a, sizeof(a)); + snprintf(dnamestr, sizeof(dnamestr), "%s/%d", a, addrnode->net); + } else { + dnamestr[0]=0; + } + if(repinfo) { + addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); + port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port); + } else if(ms && ms->mesh_info && ms->mesh_info->reply_list) { + addr_to_str(&ms->mesh_info->reply_list->query_reply.addr, ms->mesh_info->reply_list->query_reply.addrlen, ip, sizeof(ip)); + port = ntohs(((struct sockaddr_in*)&ms->mesh_info->reply_list->query_reply.addr)->sin_port); + } else { + ip[0]=0; + port = 0; + } + snprintf(portstr, sizeof(portstr), "@%u", (unsigned)port); + snprintf(txt, sizeof(txt), "rpz: applied %s%s%s%s%s%s %s %s%s", + (log_name?"[":""), (log_name?log_name:""), (log_name?"] ":""), + (strcmp(trigger,"qname")==0?"":trigger), + (strcmp(trigger,"qname")==0?"":" "), + dnamestr, rpz_action_to_string(a), + (ip[0]?ip:""), (ip[0]?portstr:"")); log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); } -int -rpz_apply_qname_trigger(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) +static struct clientip_synthesized_rr* +rpz_ipbased_trigger_lookup(struct clientip_synthesized_rrset* set, + struct sockaddr_storage* addr, socklen_t addrlen, char* triggername) { + struct clientip_synthesized_rr* raddr = NULL; + enum rpz_action action = RPZ_INVALID_ACTION; + + lock_rw_rdlock(&set->lock); + + raddr = (struct clientip_synthesized_rr*)addr_tree_lookup(&set->entries, + addr, addrlen); + if(raddr != NULL) { + action = raddr->action; + if(verbosity >= VERB_ALGO) { + char ip[256], net[256]; + addr_to_str(addr, addrlen, ip, sizeof(ip)); + addr_to_str(&raddr->node.addr, raddr->node.addrlen, + net, sizeof(net)); + verbose(VERB_ALGO, "rpz: trigger %s %s/%d on %s action=%s", + triggername, net, raddr->node.net, ip, rpz_action_to_string(action)); + } + lock_rw_unlock(&raddr->lock); + } + lock_rw_unlock(&set->lock); + + return raddr; +} + +static inline +struct clientip_synthesized_rr* +rpz_resolve_client_action_and_zone(struct auth_zones* az, struct query_info* qinfo, + struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, + struct ub_server_stats* stats, + /* output parameters */ + struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out) +{ + struct clientip_synthesized_rr* node = NULL; + struct auth_zone* a = NULL; struct rpz* r = NULL; - struct auth_zone* a; - int ret; - enum localzone_type lzt; struct local_zone* z = NULL; - struct local_data* ld = NULL; + lock_rw_rdlock(&az->rpz_lock); + for(a = az->rpz_first; a; a = a->rpz_az_next) { lock_rw_rdlock(&a->lock); r = a->rpz; - if(!r->disabled && (!r->taglist || taglist_intersect(r->taglist, - r->taglistlen, taglist, taglen))) { - z = rpz_find_zone(r, qinfo->qname, qinfo->qname_len, - qinfo->qclass, 0, 0, 0); - if(z && r->action_override == RPZ_DISABLED_ACTION) { - if(r->log) - log_rpz_apply(z->name, - r->action_override, - qinfo, repinfo, r->log_name); - /* TODO only register stats when stats_extended? - * */ - stats->rpz_action[r->action_override]++; - lock_rw_unlock(&z->lock); - z = NULL; - } - if(z) - break; - } - lock_rw_unlock(&a->lock); /* not found in this auth_zone */ - } - lock_rw_unlock(&az->rpz_lock); - if(!z) - return 0; /* not holding auth_zone.lock anymore */ - - log_assert(r); - if(r->action_override == RPZ_NO_OVERRIDE_ACTION) - lzt = z->type; - else - lzt = rpz_action_to_localzone_type(r->action_override); - - if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { - qinfo->local_alias = - regional_alloc_zero(temp, sizeof(struct local_rrset)); - if(!qinfo->local_alias) { - lock_rw_unlock(&z->lock); + if(r->disabled) { lock_rw_unlock(&a->lock); + continue; + } + if(r->taglist && !taglist_intersect(r->taglist, + r->taglistlen, taglist, taglen)) { + lock_rw_unlock(&a->lock); + continue; + } + z = rpz_find_zone(r->local_zones, qinfo->qname, qinfo->qname_len, + qinfo->qclass, 0, 0, 0); + node = rpz_ipbased_trigger_lookup(r->client_set, &repinfo->addr, repinfo->addrlen, "clientip"); + if((z || node) && r->action_override == RPZ_DISABLED_ACTION) { + if(r->log) + log_rpz_apply((node?"clientip":"qname"), + (z?z->name:NULL), + (node?&node->node:NULL), + r->action_override, + qinfo, repinfo, NULL, r->log_name); + stats->rpz_action[r->action_override]++; + lock_rw_unlock(&z->lock); + z = NULL; + if(node != NULL) { + lock_rw_unlock(&node->lock); + node = NULL; + } + } + if(z || node) { + break; + } else { + if(node != NULL) { + lock_rw_unlock(&node->lock); + node = NULL; + } + } + /* not found in this auth_zone */ + lock_rw_unlock(&a->lock); + } + + lock_rw_unlock(&az->rpz_lock); + + *r_out = r; + *a_out = a; + *z_out = z; + + return node; +} + +static inline int +rpz_is_udp_query(struct comm_reply* repinfo) { + return repinfo != NULL + ? (repinfo->c != NULL + ? repinfo->c->type == comm_udp + : 0) + : 0; +} + +/** encode answer consisting of 1 rrset */ +static int +rpz_local_encode(struct module_env* env, struct query_info* qinfo, + struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, + struct regional* temp, struct ub_packed_rrset_key* rrset, int ansec, + int rcode, struct ub_packed_rrset_key* soa_rrset) +{ + struct reply_info rep; + uint16_t udpsize; + struct ub_packed_rrset_key* rrsetlist[3]; + + memset(&rep, 0, sizeof(rep)); + rep.flags = (uint16_t)((BIT_QR | BIT_AA | BIT_RA) | rcode); + rep.qdcount = 1; + rep.rrset_count = ansec; + rep.rrsets = rrsetlist; + if(ansec > 0) { + rep.an_numrrsets = 1; + rep.rrsets[0] = rrset; + rep.ttl = ((struct packed_rrset_data*)rrset->entry.data)->rr_ttl[0]; + } + if(soa_rrset != NULL) { + rep.ar_numrrsets = 1; + rep.rrsets[rep.rrset_count] = soa_rrset; + rep.rrset_count ++; + if(rep.ttl < ((struct packed_rrset_data*)soa_rrset->entry.data)->rr_ttl[0]) { + rep.ttl = ((struct packed_rrset_data*)soa_rrset->entry.data)->rr_ttl[0]; + } + } + + udpsize = edns->udp_size; + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; + if(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, + repinfo, temp, env->now_tv) || + !reply_info_answer_encode(qinfo, &rep, + *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), + buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { + error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, + *(uint16_t*)sldns_buffer_begin(buf), + sldns_buffer_read_u16_at(buf, 2), edns); + } + + return 1; +} + +static struct local_rrset* +rpz_find_synthesized_rrset(int qtype, struct clientip_synthesized_rr* data) { + struct local_rrset* cursor = data->data; + while( cursor != NULL) { + struct packed_rrset_key* packed_rrset = &cursor->rrset->rk; + if(htons(qtype) == packed_rrset->type) { + return cursor; + } + cursor = cursor->next; + } + return NULL; +} + +/** allocate SOA record ubrrsetkey in region */ +static struct ub_packed_rrset_key* +make_soa_ubrrset(struct auth_zone* auth_zone, struct auth_rrset* soa, + struct regional* temp) +{ + struct ub_packed_rrset_key csoa; + if(!soa) + return NULL; + memset(&csoa, 0, sizeof(csoa)); + csoa.entry.key = &csoa; + csoa.rk.rrset_class = htons(LDNS_RR_CLASS_IN); + csoa.rk.type = htons(LDNS_RR_TYPE_SOA); + csoa.rk.flags |= PACKED_RRSET_FIXEDTTL + | PACKED_RRSET_RPZ; + csoa.rk.dname = auth_zone->name; + csoa.rk.dname_len = auth_zone->namelen; + csoa.entry.hash = rrset_key_hash(&csoa.rk); + csoa.entry.data = soa->data; + return respip_copy_rrset(&csoa, temp); +} + +static void +rpz_apply_clientip_localdata_action(struct clientip_synthesized_rr* raddr, + struct module_env* env, struct query_info* qinfo, + struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, + struct regional* temp, struct auth_zone* auth_zone) +{ + struct local_rrset* rrset; + enum rpz_action action = RPZ_INVALID_ACTION; + struct ub_packed_rrset_key* rp = NULL; + struct ub_packed_rrset_key* rsoa = NULL; + int rcode = LDNS_RCODE_NOERROR|BIT_AA; + int rrset_count = 1; + + /* prepare synthesized answer for client */ + action = raddr->action; + if(action == RPZ_LOCAL_DATA_ACTION && raddr->data == NULL ) { + verbose(VERB_ALGO, "rpz: bug: local-data action but no local data"); + return; + } + + /* check query type / rr type */ + rrset = rpz_find_synthesized_rrset(qinfo->qtype, raddr); + if(rrset == NULL) { + verbose(VERB_ALGO, "rpz: unable to find local-data for query"); + rrset_count = 0; + goto nodata; + } + + rp = respip_copy_rrset(rrset->rrset, temp); + if(!rp) { + verbose(VERB_ALGO, "rpz: local data action: out of memory"); + return; + } + + rp->rk.flags |= PACKED_RRSET_FIXEDTTL | PACKED_RRSET_RPZ; + rp->rk.dname = qinfo->qname; + rp->rk.dname_len = qinfo->qname_len; + rp->entry.hash = rrset_key_hash(&rp->rk); +nodata: + if(auth_zone) { + struct auth_rrset* soa = NULL; + soa = auth_zone_get_soa_rrset(auth_zone); + if(soa) { + rsoa = make_soa_ubrrset(auth_zone, soa, temp); + if(!rsoa) { + verbose(VERB_ALGO, "rpz: local data action soa: out of memory"); + return; + } + } + } + + rpz_local_encode(env, qinfo, edns, repinfo, buf, temp, rp, + rrset_count, rcode, rsoa); +} + +/** add additional section SOA record to the reply. + * Since this gets fed into the normal iterator answer creation, it + * gets minimal-responses applied to it, that can remove the additional SOA + * again. */ +static int +rpz_add_soa(struct reply_info* rep, struct module_qstate* ms, + struct auth_zone* az) +{ + struct auth_rrset* soa = NULL; + struct ub_packed_rrset_key* rsoa = NULL; + struct ub_packed_rrset_key** prevrrsets; + if(!az) return 1; + soa = auth_zone_get_soa_rrset(az); + if(!soa) return 1; + if(!rep) return 0; + rsoa = make_soa_ubrrset(az, soa, ms->region); + if(!rsoa) return 0; + prevrrsets = rep->rrsets; + rep->rrsets = regional_alloc_zero(ms->region, + sizeof(*rep->rrsets)*(rep->rrset_count+1)); + if(!rep->rrsets) + return 0; + if(prevrrsets && rep->rrset_count > 0) + memcpy(rep->rrsets, prevrrsets, rep->rrset_count*sizeof(*rep->rrsets)); + rep->rrset_count++; + rep->ar_numrrsets++; + rep->rrsets[rep->rrset_count-1] = rsoa; + return 1; +} + +static inline struct dns_msg* +rpz_dns_msg_new(struct regional* region) +{ + struct dns_msg* msg = + (struct dns_msg*)regional_alloc(region, + sizeof(struct dns_msg)); + if(msg == NULL) { return NULL; } + memset(msg, 0, sizeof(struct dns_msg)); + + return msg; +} + +static inline struct dns_msg* +rpz_synthesize_nodata(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms, + struct query_info* qinfo, struct auth_zone* az) +{ + struct dns_msg* msg = rpz_dns_msg_new(ms->region); + if(msg == NULL) { return msg; } + msg->qinfo = *qinfo; + msg->rep = construct_reply_info_base(ms->region, + LDNS_RCODE_NOERROR | BIT_RD | BIT_QR | BIT_AA | BIT_RA, + 1, /* qd */ + 0, /* ttl */ + 0, /* prettl */ + 0, /* expttl */ + 0, /* an */ + 0, /* ns */ + 0, /* ar */ + 0, /* total */ + sec_status_insecure); + if(msg->rep) + msg->rep->authoritative = 1; + if(!rpz_add_soa(msg->rep, ms, az)) + return NULL; + return msg; +} + +static inline struct dns_msg* +rpz_synthesize_nxdomain(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms, + struct query_info* qinfo, struct auth_zone* az) +{ + struct dns_msg* msg = rpz_dns_msg_new(ms->region); + if(msg == NULL) { return msg; } + msg->qinfo = *qinfo; + msg->rep = construct_reply_info_base(ms->region, + LDNS_RCODE_NXDOMAIN | BIT_RD | BIT_QR | BIT_AA | BIT_RA, + 1, /* qd */ + 0, /* ttl */ + 0, /* prettl */ + 0, /* expttl */ + 0, /* an */ + 0, /* ns */ + 0, /* ar */ + 0, /* total */ + sec_status_insecure); + if(msg->rep) + msg->rep->authoritative = 1; + if(!rpz_add_soa(msg->rep, ms, az)) + return NULL; + return msg; +} + +static inline struct dns_msg* +rpz_synthesize_localdata_from_rrset(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms, + struct query_info* qi, struct local_rrset* rrset, struct auth_zone* az) +{ + struct dns_msg* msg = NULL; + struct reply_info* new_reply_info; + struct ub_packed_rrset_key* rp; + + + msg = rpz_dns_msg_new(ms->region); + if(msg == NULL) { return NULL; } + + new_reply_info = construct_reply_info_base(ms->region, + LDNS_RCODE_NOERROR | BIT_RD | BIT_QR | BIT_AA | BIT_RA, + 1, /* qd */ + 0, /* ttl */ + 0, /* prettl */ + 0, /* expttl */ + 1, /* an */ + 0, /* ns */ + 0, /* ar */ + 1, /* total */ + sec_status_insecure); + if(new_reply_info == NULL) { + log_err("out of memory"); + return NULL; + } + new_reply_info->authoritative = 1; + rp = respip_copy_rrset(rrset->rrset, ms->region); + if(rp == NULL) { + log_err("out of memory"); + return NULL; + } + rp->rk.dname = qi->qname; + rp->rk.dname_len = qi->qname_len; + /* this rrset is from the rpz data, or synthesized. + * It is not actually from the network, so we flag it with this + * flags as a fake RRset. If later the cache is used to look up + * rrsets, then the fake ones are not returned (if you look without + * the flag). For like CNAME lookups from the iterator or A, AAAA + * lookups for nameserver targets, it would use the without flag + * actual data. So that the actual network data and fake data + * are kept track of separately. */ + rp->rk.flags |= PACKED_RRSET_RPZ; + new_reply_info->rrsets[0] = rp; + msg->rep = new_reply_info; + if(!rpz_add_soa(msg->rep, ms, az)) + return NULL; + return msg; +} + +static inline struct dns_msg* +rpz_synthesize_nsip_localdata(struct rpz* r, struct module_qstate* ms, + struct clientip_synthesized_rr* data, struct auth_zone* az) +{ + struct query_info* qi = &ms->qinfo; + struct local_rrset* rrset; + + rrset = rpz_find_synthesized_rrset(qi->qtype, data); + if(rrset == NULL) { + verbose(VERB_ALGO, "rpz: nsip: no matching local data found"); + return NULL; + } + + return rpz_synthesize_localdata_from_rrset(r, ms, &ms->qinfo, rrset, az); +} + +/* copy'n'paste from localzone.c */ +static struct local_rrset* +local_data_find_type(struct local_data* data, uint16_t type, int alias_ok) +{ + struct local_rrset* p; + type = htons(type); + for(p = data->rrsets; p; p = p->next) { + if(p->rrset->rk.type == type) + return p; + if(alias_ok && p->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) + return p; + } + return NULL; +} + +/* based on localzone.c:local_data_answer() */ +static inline struct dns_msg* +rpz_synthesize_nsdname_localdata(struct rpz* r, struct module_qstate* ms, + struct local_zone* z, struct matched_delegation_point const* match, + struct auth_zone* az) +{ + struct local_data key; + struct local_data* ld; + struct local_rrset* rrset; + + if(match->dname == NULL) { return NULL; } + + key.node.key = &key; + key.name = match->dname; + key.namelen = match->dname_len; + key.namelabs = dname_count_labels(match->dname); + + rpz_log_dname("nsdname local data", key.name, key.namelen); + + ld = (struct local_data*)rbtree_search(&z->data, &key.node); + if(ld == NULL) { + verbose(VERB_ALGO, "rpz: nsdname: impossible: qname not found"); + return NULL; + } + + rrset = local_data_find_type(ld, ms->qinfo.qtype, 1); + if(rrset == NULL) { + verbose(VERB_ALGO, "rpz: nsdname: no matching local data found"); + return NULL; + } + + return rpz_synthesize_localdata_from_rrset(r, ms, &ms->qinfo, rrset, az); +} + +/* like local_data_answer for qname triggers after a cname */ +static struct dns_msg* +rpz_synthesize_qname_localdata_msg(struct rpz* r, struct module_qstate* ms, + struct query_info* qinfo, struct local_zone* z, struct auth_zone* az) +{ + struct local_data key; + struct local_data* ld; + struct local_rrset* rrset; + key.node.key = &key; + key.name = qinfo->qname; + key.namelen = qinfo->qname_len; + key.namelabs = dname_count_labels(qinfo->qname); + ld = (struct local_data*)rbtree_search(&z->data, &key.node); + if(ld == NULL) { + verbose(VERB_ALGO, "rpz: qname after cname: name not found"); + return NULL; + } + rrset = local_data_find_type(ld, qinfo->qtype, 1); + if(rrset == NULL) { + verbose(VERB_ALGO, "rpz: qname after cname: type not found"); + return NULL; + } + return rpz_synthesize_localdata_from_rrset(r, ms, qinfo, rrset, az); +} + +static int +rpz_synthesize_qname_localdata(struct module_env* env, struct rpz* r, + struct local_zone* z, enum localzone_type lzt, struct query_info* qinfo, + struct edns_data* edns, sldns_buffer* buf, struct regional* temp, + struct comm_reply* repinfo, struct ub_server_stats* stats) +{ + struct local_data* ld = NULL; + int ret = 0; + if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { + qinfo->local_alias = regional_alloc_zero(temp, sizeof(struct local_rrset)); + if(qinfo->local_alias == NULL) { return 0; /* out of memory */ } - qinfo->local_alias->rrset = - regional_alloc_init(temp, r->cname_override, - sizeof(*r->cname_override)); - if(!qinfo->local_alias->rrset) { - lock_rw_unlock(&z->lock); - lock_rw_unlock(&a->lock); + qinfo->local_alias->rrset = regional_alloc_init(temp, r->cname_override, + sizeof(*r->cname_override)); + if(qinfo->local_alias->rrset == NULL) { return 0; /* out of memory */ } qinfo->local_alias->rrset->rk.dname = qinfo->qname; qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len; - if(r->log) - log_rpz_apply(z->name, RPZ_CNAME_OVERRIDE_ACTION, - qinfo, repinfo, r->log_name); + if(r->log) { + log_rpz_apply("qname", z->name, NULL, RPZ_CNAME_OVERRIDE_ACTION, + qinfo, repinfo, NULL, r->log_name); + } stats->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++; - lock_rw_unlock(&z->lock); - lock_rw_unlock(&a->lock); return 0; } if(lzt == local_zone_redirect && local_data_answer(z, env, qinfo, edns, repinfo, buf, temp, dname_count_labels(qinfo->qname), &ld, lzt, -1, NULL, 0, NULL, 0)) { - if(r->log) - log_rpz_apply(z->name, + if(r->log) { + log_rpz_apply("qname", z->name, NULL, localzone_type_to_rpz_action(lzt), qinfo, - repinfo, r->log_name); + repinfo, NULL, r->log_name); + } stats->rpz_action[localzone_type_to_rpz_action(lzt)]++; - lock_rw_unlock(&z->lock); - lock_rw_unlock(&a->lock); return !qinfo->local_alias; } ret = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, 0 /* no local data used */, lzt); - if(r->log) - log_rpz_apply(z->name, localzone_type_to_rpz_action(lzt), - qinfo, repinfo, r->log_name); + if(r->log) { + log_rpz_apply("qname", z->name, NULL, localzone_type_to_rpz_action(lzt), + qinfo, repinfo, NULL, r->log_name); + } stats->rpz_action[localzone_type_to_rpz_action(lzt)]++; + return ret; +} + +struct clientip_synthesized_rr* +rpz_delegation_point_ipbased_trigger_lookup(struct rpz* rpz, struct iter_qstate* is) +{ + struct delegpt_addr* cursor; + struct clientip_synthesized_rr* action = NULL; + if(is->dp == NULL) { return NULL; } + for(cursor = is->dp->target_list; + cursor != NULL; + cursor = cursor->next_target) { + if(cursor->bogus) { continue; } + action = rpz_ipbased_trigger_lookup(rpz->ns_set, &cursor->addr, + cursor->addrlen, "nsip"); + if(action != NULL) { return action; } + } + return NULL; +} + +struct dns_msg* +rpz_apply_nsip_trigger(struct module_qstate* ms, struct rpz* r, + struct clientip_synthesized_rr* raddr, struct auth_zone* az) +{ + enum rpz_action action = raddr->action; + struct dns_msg* ret = NULL; + + if(r->action_override != RPZ_NO_OVERRIDE_ACTION) { + verbose(VERB_ALGO, "rpz: using override action=%s (replaces=%s)", + rpz_action_to_string(r->action_override), rpz_action_to_string(action)); + action = r->action_override; + } + + if(action == RPZ_LOCAL_DATA_ACTION && raddr->data == NULL) { + verbose(VERB_ALGO, "rpz: bug: nsip local data action but no local data"); + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + goto done; + } + + switch(action) { + case RPZ_NXDOMAIN_ACTION: + ret = rpz_synthesize_nxdomain(r, ms, &ms->qinfo, az); + break; + case RPZ_NODATA_ACTION: + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + break; + case RPZ_TCP_ONLY_ACTION: + /* basically a passthru here but the tcp-only will be + * honored before the query gets sent. */ + ms->respip_action_info->action = respip_truncate; + ret = NULL; + break; + case RPZ_DROP_ACTION: + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + ms->is_drop = 1; + break; + case RPZ_LOCAL_DATA_ACTION: + ret = rpz_synthesize_nsip_localdata(r, ms, raddr, az); + if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); } + break; + case RPZ_PASSTHRU_ACTION: + ret = NULL; + break; + default: + verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'", + rpz_action_to_string(action)); + ret = NULL; + } + +done: + if(r->log) + log_rpz_apply("nsip", NULL, &raddr->node, + action, &ms->qinfo, NULL, ms, r->log_name); + if(ms->env->worker) + ms->env->worker->stats.rpz_action[action]++; + lock_rw_unlock(&raddr->lock); + return ret; +} + +struct dns_msg* +rpz_apply_nsdname_trigger(struct module_qstate* ms, struct rpz* r, + struct local_zone* z, struct matched_delegation_point const* match, + struct auth_zone* az) +{ + struct dns_msg* ret = NULL; + enum rpz_action action = localzone_type_to_rpz_action(z->type); + + if(r->action_override != RPZ_NO_OVERRIDE_ACTION) { + verbose(VERB_ALGO, "rpz: using override action=%s (replaces=%s)", + rpz_action_to_string(r->action_override), rpz_action_to_string(action)); + action = r->action_override; + } + + switch(action) { + case RPZ_NXDOMAIN_ACTION: + ret = rpz_synthesize_nxdomain(r, ms, &ms->qinfo, az); + break; + case RPZ_NODATA_ACTION: + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + break; + case RPZ_TCP_ONLY_ACTION: + /* basically a passthru here but the tcp-only will be + * honored before the query gets sent. */ + ms->respip_action_info->action = respip_truncate; + ret = NULL; + break; + case RPZ_DROP_ACTION: + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + ms->is_drop = 1; + break; + case RPZ_LOCAL_DATA_ACTION: + ret = rpz_synthesize_nsdname_localdata(r, ms, z, match, az); + if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); } + break; + case RPZ_PASSTHRU_ACTION: + ret = NULL; + break; + default: + verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'", + rpz_action_to_string(action)); + ret = NULL; + } + + if(r->log) + log_rpz_apply("nsdname", match->dname, NULL, + action, &ms->qinfo, NULL, ms, r->log_name); + if(ms->env->worker) + ms->env->worker->stats.rpz_action[action]++; + return ret; +} + +static struct local_zone* +rpz_delegation_point_zone_lookup(struct delegpt* dp, struct local_zones* zones, + uint16_t qclass, + /* output parameter */ + struct matched_delegation_point* match) +{ + struct delegpt_ns* nameserver; + struct local_zone* z = NULL; + + /* the rpz specs match the nameserver names (NS records), not the + * name of the delegation point itself, to the nsdname triggers */ + for(nameserver = dp->nslist; + nameserver != NULL; + nameserver = nameserver->next) { + z = rpz_find_zone(zones, nameserver->name, nameserver->namelen, + qclass, 0, 0, 0); + if(z != NULL) { + match->dname = nameserver->name; + match->dname_len = nameserver->namelen; + if(verbosity >= VERB_ALGO) { + char nm[255+1], zn[255+1]; + dname_str(match->dname, nm); + dname_str(z->name, zn); + if(strcmp(nm, zn) != 0) + verbose(VERB_ALGO, "rpz: trigger nsdname %s on %s action=%s", + zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(z->type))); + else + verbose(VERB_ALGO, "rpz: trigger nsdname %s action=%s", + nm, rpz_action_to_string(localzone_type_to_rpz_action(z->type))); + } + break; + } + } + + return z; +} + +struct dns_msg* +rpz_callback_from_iterator_module(struct module_qstate* ms, struct iter_qstate* is) +{ + struct auth_zones* az; + struct auth_zone* a; + struct clientip_synthesized_rr* raddr = NULL; + struct rpz* r; + struct local_zone* z = NULL; + struct matched_delegation_point match = {0}; + + if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; } + + az = ms->env->auth_zones; + + verbose(VERB_ALGO, "rpz: iterator module callback: have_rpz=%d", az->rpz_first != NULL); + + lock_rw_rdlock(&az->rpz_lock); + + /* precedence of RPZ works, loosely, like this: + * CNAMEs in order of the CNAME chain. rpzs in the order they are + * configured. In an RPZ: first client-IP addr, then QNAME, then + * response IP, then NSDNAME, then NSIP. Longest match first. Smallest + * one from a set. */ + /* we use the precedence rules for the topics and triggers that + * are pertinent at this stage of the resolve processing */ + for(a = az->rpz_first; a != NULL; a = a->rpz_az_next) { + lock_rw_rdlock(&a->lock); + r = a->rpz; + if(r->disabled) { + lock_rw_unlock(&a->lock); + continue; + } + + /* the nsdname has precedence over the nsip triggers */ + z = rpz_delegation_point_zone_lookup(is->dp, r->nsdname_zones, + ms->qinfo.qclass, &match); + if(z != NULL) { + lock_rw_unlock(&a->lock); + break; + } + + raddr = rpz_delegation_point_ipbased_trigger_lookup(r, is); + if(raddr != NULL) { + lock_rw_unlock(&a->lock); + break; + } + lock_rw_unlock(&a->lock); + } + + lock_rw_unlock(&az->rpz_lock); + + if(raddr == NULL && z == NULL) { return NULL; } + else if(raddr != NULL) { return rpz_apply_nsip_trigger(ms, r, raddr, a); } + else if(z != NULL) { return rpz_apply_nsdname_trigger(ms, r, z, &match, a); } + else { return NULL; } +} + +struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms, + struct iter_qstate* is) +{ + struct auth_zones* az; + struct auth_zone* a = NULL; + struct rpz* r = NULL; + struct local_zone* z = NULL; + enum localzone_type lzt; + struct dns_msg* ret = NULL; + + if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; } + az = ms->env->auth_zones; + + lock_rw_rdlock(&az->rpz_lock); + + for(a = az->rpz_first; a; a = a->rpz_az_next) { + lock_rw_rdlock(&a->lock); + r = a->rpz; + if(r->disabled) { + lock_rw_unlock(&a->lock); + continue; + } + z = rpz_find_zone(r->local_zones, is->qchase.qname, + is->qchase.qname_len, is->qchase.qclass, 0, 0, 0); + if(z && r->action_override == RPZ_DISABLED_ACTION) { + if(r->log) + log_rpz_apply("qname", z->name, NULL, + r->action_override, + &ms->qinfo, NULL, ms, r->log_name); + if(ms->env->worker) + ms->env->worker->stats.rpz_action[r->action_override]++; + lock_rw_unlock(&z->lock); + z = NULL; + } + if(z) { + break; + } + /* not found in this auth_zone */ + lock_rw_unlock(&a->lock); + } + lock_rw_unlock(&az->rpz_lock); + + if(z == NULL) + return NULL; + if(r->action_override == RPZ_NO_OVERRIDE_ACTION) { + lzt = z->type; + } else { + lzt = rpz_action_to_localzone_type(r->action_override); + } + + if(verbosity >= VERB_ALGO) { + char nm[255+1], zn[255+1]; + dname_str(is->qchase.qname, nm); + dname_str(z->name, zn); + if(strcmp(zn, nm) != 0) + verbose(VERB_ALGO, "rpz: qname trigger after cname %s on %s, with action=%s", + zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + else + verbose(VERB_ALGO, "rpz: qname trigger after cname %s, with action=%s", + nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + } + switch(localzone_type_to_rpz_action(lzt)) { + case RPZ_NXDOMAIN_ACTION: + ret = rpz_synthesize_nxdomain(r, ms, &is->qchase, a); + break; + case RPZ_NODATA_ACTION: + ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); + break; + case RPZ_TCP_ONLY_ACTION: + /* basically a passthru here but the tcp-only will be + * honored before the query gets sent. */ + ms->respip_action_info->action = respip_truncate; + ret = NULL; + break; + case RPZ_DROP_ACTION: + ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); + ms->is_drop = 1; + break; + case RPZ_LOCAL_DATA_ACTION: + ret = rpz_synthesize_qname_localdata_msg(r, ms, &is->qchase, z, a); + if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); } + break; + case RPZ_PASSTHRU_ACTION: + ret = NULL; + break; + default: + verbose(VERB_ALGO, "rpz: qname trigger after cname: bug: unhandled or invalid action: '%s'", + rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + ret = NULL; + } + lock_rw_unlock(&z->lock); + lock_rw_unlock(&a->lock); + return ret; +} + +static int +rpz_apply_maybe_clientip_trigger(struct auth_zones* az, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, + 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) +{ + int ret = 0; + enum rpz_action client_action; + struct clientip_synthesized_rr* node = rpz_resolve_client_action_and_zone( + az, qinfo, repinfo, taglist, taglen, stats, z_out, a_out, r_out); + + client_action = ((node == NULL) ? RPZ_INVALID_ACTION : node->action); + + if(*z_out == NULL || (client_action != RPZ_INVALID_ACTION && + client_action != RPZ_PASSTHRU_ACTION)) { + if(client_action == RPZ_PASSTHRU_ACTION + || client_action == RPZ_INVALID_ACTION + || (client_action == RPZ_TCP_ONLY_ACTION + && !rpz_is_udp_query(repinfo))) { + ret = 0; + goto done; + } + stats->rpz_action[client_action]++; + if(client_action == RPZ_LOCAL_DATA_ACTION) { + rpz_apply_clientip_localdata_action(node, env, qinfo, + edns, repinfo, buf, temp, *a_out); + } else { + if(*r_out && (*r_out)->log) + log_rpz_apply( + (node?"clientip":"qname"), + ((*z_out)?(*z_out)->name:NULL), + (node?&node->node:NULL), + client_action, qinfo, repinfo, NULL, + (*r_out)->log_name); + local_zones_zone_answer(*z_out /*likely NULL, no zone*/, env, qinfo, edns, + repinfo, buf, temp, 0 /* no local data used */, + rpz_action_to_localzone_type(client_action)); + } + ret = 1; + goto done; + } + ret = -1; +done: + if(node != NULL) { + lock_rw_unlock(&node->lock); + } + return ret; +} + +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) +{ + struct rpz* r = NULL; + struct auth_zone* a = NULL; + struct local_zone* z = NULL; + int ret; + 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); + if(clientip_trigger >= 0) { return clientip_trigger; } + + if(z == NULL) { + return 0; + } + + log_assert(r); + + if(r->action_override == RPZ_NO_OVERRIDE_ACTION) { + lzt = z->type; + } else { + lzt = rpz_action_to_localzone_type(r->action_override); + } + + if(verbosity >= VERB_ALGO) { + char nm[255+1], zn[255+1]; + dname_str(qinfo->qname, nm); + dname_str(z->name, zn); + if(strcmp(zn, nm) != 0) + verbose(VERB_ALGO, "rpz: qname trigger %s on %s with action=%s", + zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + else + verbose(VERB_ALGO, "rpz: qname trigger %s with action=%s", + nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + } + + ret = rpz_synthesize_qname_localdata(env, r, z, lzt, qinfo, edns, buf, temp, + repinfo, stats); + lock_rw_unlock(&z->lock); lock_rw_unlock(&a->lock); diff --git a/services/rpz.h b/services/rpz.h index d5996a6cf..642548bdd 100644 --- a/services/rpz.h +++ b/services/rpz.h @@ -50,6 +50,7 @@ #include "sldns/sbuffer.h" #include "daemon/stats.h" #include "respip/respip.h" +struct iter_qstate; /** * RPZ triggers, only the QNAME trigger is currently supported in Unbound. @@ -83,6 +84,27 @@ enum rpz_action { RPZ_CNAME_OVERRIDE_ACTION, /* RPZ CNAME action override*/ }; +struct clientip_synthesized_rrset{ + struct regional* region; + struct rbtree_type entries; + lock_rw_type lock; /* lock on the respip tree */ +}; + +struct clientip_synthesized_rr { + /** 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 rpz_action action; + /** "local data" for this node */ + struct local_rrset* data; +}; + /** * RPZ containing policies. Pointed to from corresponding auth-zone. Part of a * linked list to keep configuration order. Iterating or changing the linked @@ -92,6 +114,9 @@ enum rpz_action { struct rpz { struct local_zones* local_zones; struct respip_set* respip_set; + struct clientip_synthesized_rrset* client_set; + struct clientip_synthesized_rrset* ns_set; + struct local_zones* nsdname_zones; uint8_t* taglist; size_t taglistlen; enum rpz_action action_override; @@ -151,11 +176,34 @@ void rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, * @param stats: worker stats struct * @return: 1 if client answer is ready, 0 to continue resolving */ -int rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env, +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); +/** + * Callback to process when the iterator module is about to send queries. + * Checks for nsip and nsdname triggers. + * @param qstate: the query state. + * @param iq: iterator module query state. + * @return NULL if nothing is done. Or a new message with the contents from + * the rpz, based on the delegation point. It is allocated in the + * qstate region. + */ +struct dns_msg* rpz_callback_from_iterator_module(struct module_qstate* qstate, + struct iter_qstate* iq); + +/** + * Callback to process when the iterator module has followed a cname. + * There can be a qname trigger for the new query name. + * @param qstate: the query state. + * @param iq: iterator module query state. + * @return NULL if nothing is done. Or a new message with the contents from + * the rpz, based on the iq.qchase. It is allocated in the qstate region. + */ +struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* qstate, + struct iter_qstate* iq); + /** * Delete RPZ * @param r: RPZ struct to delete diff --git a/testdata/rpz_clientip.rpl b/testdata/rpz_clientip.rpl new file mode 100644 index 000000000..78e05ad91 --- /dev/null +++ b/testdata/rpz_clientip.rpl @@ -0,0 +1,264 @@ +; config options +server: + module-config: "respip validator iterator" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + minimal-responses: no + access-control: 192.0.0.0/8 allow + +rpz: + name: "rpz.example.com." + zonefile: +TEMPFILE_NAME rpz.example.com +TEMPFILE_CONTENTS rpz.example.com +$ORIGIN example.com. +rpz 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.rpz.example.com. + 3600 IN NS ns2.rpz.example.com. +$ORIGIN rpz.example.com. +24.0.0.0.192.rpz-client-ip CNAME . +24.0.1.0.192.rpz-client-ip CNAME *. +24.0.2.0.192.rpz-client-ip CNAME rpz-drop. +24.0.3.0.192.rpz-client-ip CNAME rpz-passthru. +24.0.4.0.192.rpz-client-ip CNAME rpz-tcp-only. +24.0.5.0.192.rpz-client-ip A 127.0.0.1 +24.0.5.0.192.rpz-client-ip TXT "42" +TEMPFILE_END + +stub-zone: + name: "a." + stub-addr: 10.20.30.40 +CONFIG_END + +SCENARIO_BEGIN Test RPZ client ip triggers + +RANGE_BEGIN 0 100 + ADDRESS 10.20.30.40 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a. IN NS +SECTION ANSWER +a. IN NS ns.a. +SECTION ADDITIONAL +ns.a IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a.a. IN A +SECTION ANSWER +a.a. IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a.a. IN AAAA +SECTION ANSWER +a.a. IN AAAA 2001:db8::123 +ENTRY_END + +RANGE_END + +; unrelated client ip address -- passthru + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +; should be NXDOMAIN + +STEP 20 QUERY ADDRESS 192.0.0.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NXDOMAIN +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +ENTRY_END + +; should be NODATA + +STEP 30 QUERY ADDRESS 192.0.1.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +ENTRY_END + +; should be PASSTHRU + +STEP 40 QUERY ADDRESS 192.0.3.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 41 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +; should be TRUNCATED + +STEP 50 QUERY ADDRESS 192.0.4.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 51 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA TC RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +ENTRY_END + +; should not be TRUNCATED via TCP + +STEP 52 QUERY ADDRESS 192.0.4.1 +ENTRY_BEGIN +MATCH TCP +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 53 CHECK_ANSWER +ENTRY_BEGIN +MATCH all TCP +REPLY QR RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +; should be synthesized + +STEP 60 QUERY ADDRESS 192.0.5.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN A +ENTRY_END + +STEP 61 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NOERROR +SECTION QUESTION +a.a. IN A +SECTION ANSWER +a.a. IN A 127.0.0.1 +SECTION ADDITIONAL +rpz.example.com. 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( 1379078166 28800 7200 604800 7200 ) +ENTRY_END + +; should be synthesized + +STEP 62 QUERY ADDRESS 192.0.5.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 63 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "42" +SECTION ADDITIONAL +rpz.example.com. 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( 1379078166 28800 7200 604800 7200 ) +ENTRY_END + +; should be synthesized NODATA + +STEP 64 QUERY ADDRESS 192.0.5.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN AAAA +ENTRY_END + +STEP 65 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NOERROR +SECTION QUESTION +a.a. IN AAAA +SECTION ADDITIONAL +rpz.example.com. 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( 1379078166 28800 7200 604800 7200 ) +ENTRY_END + +; should be DROPPED + +STEP 90 QUERY ADDRESS 192.0.2.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +SCENARIO_END diff --git a/testdata/rpz_nsdname.rpl b/testdata/rpz_nsdname.rpl new file mode 100644 index 000000000..08ff3c2e2 --- /dev/null +++ b/testdata/rpz_nsdname.rpl @@ -0,0 +1,390 @@ +; config options +server: + module-config: "respip validator iterator" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + access-control: 192.0.0.0/8 allow + +rpz: + name: "rpz.example.com." + rpz-log: yes + rpz-log-name: "rpz.example.com" + zonefile: +TEMPFILE_NAME rpz.example.com +TEMPFILE_CONTENTS rpz.example.com +$ORIGIN example.com. +rpz 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.rpz.example.com. + 3600 IN NS ns2.rpz.example.com. +$ORIGIN rpz.example.com. +ns1.gotham.aa.rpz-nsdname CNAME . +ns1.gotham.bb.rpz-nsdname CNAME *. +ns1.gotham.cc.rpz-nsdname CNAME rpz-drop. +ns1.gotham.com.rpz-nsdname CNAME rpz-passthru. +ns1.gotham.dd.rpz-nsdname CNAME rpz-tcp-only. +ns1.gotham.ff.rpz-nsdname A 127.0.0.1 +ns1.gotham.ff.rpz-nsdname TXT "42" +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 1.1.1.1 +CONFIG_END + +SCENARIO_BEGIN Test RPZ nsip triggers + +; . -------------------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 1.1.1.1 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS ns.root. +SECTION ADDITIONAL +ns.root IN A 1.1.1.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN A +SECTION AUTHORITY +com. IN NS ns1.com. +SECTION ADDITIONAL +ns1.com. IN A 8.8.8.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +aa. IN A +SECTION AUTHORITY +aa. IN NS ns1.aa. +SECTION ADDITIONAL +ns1.aa. IN A 8.8.0.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +bb. IN A +SECTION AUTHORITY +bb. IN NS ns1.bb. +SECTION ADDITIONAL +ns1.bb. IN A 8.8.1.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +cc. IN A +SECTION AUTHORITY +cc. IN NS ns1.cc. +SECTION ADDITIONAL +ns1.cc. IN A 8.8.2.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +dd. IN A +SECTION AUTHORITY +dd. IN NS ns1.dd. +SECTION ADDITIONAL +ns1.dd. IN A 8.8.3.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +ee. IN A +SECTION AUTHORITY +ee. IN NS ns1.ee. +SECTION ADDITIONAL +ns1.ee. IN A 8.8.5.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +ff. IN A +SECTION AUTHORITY +ff. IN NS ns1.ff. +SECTION ADDITIONAL +ns1.ff. IN A 8.8.6.8 +ENTRY_END + +RANGE_END + +; com. ----------------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 8.8.8.8 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS ns1.com. +SECTION ADDITIONAL +ns1.com. IN A 8.8.8.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +gotham.com. IN A +SECTION AUTHORITY +gotham.com. IN NS ns1.gotham.com. +SECTION ADDITIONAL +ns1.gotham.com. IN A 192.0.6.1 +ENTRY_END + +RANGE_END + +; aa. ------------------------------------------------------------------------ +RANGE_BEGIN 0 100 + ADDRESS 8.8.0.8 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +aa. IN NS +SECTION ANSWER +aa. IN NS ns1.aa. +SECTION ADDITIONAL +ns1.aa. IN A 8.8.0.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +gotham.aa. IN A +SECTION AUTHORITY +gotham.aa. IN NS ns1.gotham.aa. +SECTION ADDITIONAL +ns1.gotham.aa. IN A 192.0.0.1 +ENTRY_END + +RANGE_END + +; bb. ------------------------------------------------------------------------ +RANGE_BEGIN 0 100 + ADDRESS 8.8.1.8 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +bb. IN NS +SECTION ANSWER +bb. IN NS ns1.bb. +SECTION ADDITIONAL +ns1.bb. IN A 8.8.1.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +gotham.bb. IN A +SECTION AUTHORITY +gotham.bb. IN NS ns1.gotham.bb. +SECTION ADDITIONAL +ns1.gotham.bb. IN A 192.0.1.1 +ENTRY_END + +RANGE_END + +; ff. ------------------------------------------------------------------------ +RANGE_BEGIN 0 100 + ADDRESS 8.8.6.8 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ff. IN NS +SECTION ANSWER +ff. IN NS ns1.ff. +SECTION ADDITIONAL +ns1.ff. IN A 8.8.6.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +gotham.ff. IN A +SECTION AUTHORITY +gotham.ff. IN NS ns1.gotham.ff. +SECTION ADDITIONAL +ns1.gotham.ff. IN A 192.0.5.1 +ENTRY_END + +RANGE_END + +; ns1.gotham.com. ------------------------------------------------------------ +RANGE_BEGIN 0 100 + ADDRESS 192.0.6.1 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +gotham.com. IN A +SECTION ANSWER +gotham.com. IN A 192.0.6.2 +ENTRY_END + +RANGE_END + +; ns1.gotham.aa. ------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 192.0.0.1 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +gotham.aa. IN A +SECTION ANSWER +gotham.aa. IN A 192.0.0.2 +ENTRY_END + +RANGE_END + +; ns1.gotham.bb. ------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 192.0.1.1 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +gotham.bb. IN A +SECTION ANSWER +gotham.bb. IN A 192.0.1.2 +ENTRY_END + +RANGE_END + +; ns1.gotham.ff. ------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 192.0.5.1 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +gotham.ff. IN A +SECTION ANSWER +gotham.ff. IN A 192.0.5.2 +ENTRY_END + +RANGE_END + +; ---------------------------------------------------------------------------- + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.com. IN A +ENTRY_END + +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +gotham.com. IN A +SECTION ANSWER +gotham.com. IN A 192.0.6.2 +ENTRY_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.aa. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NXDOMAIN +SECTION QUESTION +gotham.aa. IN A +SECTION ANSWER +ENTRY_END + +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.bb. IN A +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AA NOERROR +SECTION QUESTION +gotham.bb. IN A +SECTION ANSWER +ENTRY_END + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.ff. IN A +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AA NOERROR +SECTION QUESTION +gotham.ff. IN A +SECTION ANSWER +gotham.ff. IN A 127.0.0.1 +ENTRY_END + +SCENARIO_END diff --git a/testdata/rpz_nsip.rpl b/testdata/rpz_nsip.rpl new file mode 100644 index 000000000..ac132cae0 --- /dev/null +++ b/testdata/rpz_nsip.rpl @@ -0,0 +1,408 @@ +; config options +server: + module-config: "respip validator iterator" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + access-control: 192.0.0.0/8 allow + +rpz: + name: "rpz.example.com." + rpz-log: yes + rpz-log-name: "rpz.example.com" + zonefile: +TEMPFILE_NAME rpz.example.com +TEMPFILE_CONTENTS rpz.example.com +$ORIGIN example.com. +rpz 3600 IN SOA ns1.rpz.gotham.com. hostmaster.rpz.example.com. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.rpz.example.com. + 3600 IN NS ns2.rpz.example.com. +$ORIGIN rpz.example.com. +24.0.0.0.192.rpz-nsip CNAME . +24.0.1.0.192.rpz-nsip CNAME *. +24.0.2.0.192.rpz-nsip CNAME rpz-drop. +24.0.3.0.192.rpz-nsip CNAME rpz-passthru. +24.0.4.0.192.rpz-nsip CNAME rpz-tcp-only. +24.0.5.0.192.rpz-nsip A 127.0.0.1 +24.0.5.0.192.rpz-nsip TXT "42" +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 1.1.1.1 +CONFIG_END + +SCENARIO_BEGIN Test RPZ nsip triggers + +; . -------------------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 1.1.1.1 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS ns.root. +SECTION ADDITIONAL +ns.root IN A 1.1.1.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN A +SECTION AUTHORITY +com. IN NS ns1.com. +SECTION ADDITIONAL +ns1.com. IN A 8.8.8.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +aa. IN A +SECTION AUTHORITY +aa. IN NS ns1.aa. +SECTION ADDITIONAL +ns1.aa. IN A 8.8.0.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +bb. IN A +SECTION AUTHORITY +bb. IN NS ns1.bb. +SECTION ADDITIONAL +ns1.bb. IN A 8.8.1.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +cc. IN A +SECTION AUTHORITY +cc. IN NS ns1.cc. +SECTION ADDITIONAL +ns1.cc. IN A 8.8.2.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +dd. IN A +SECTION AUTHORITY +dd. IN NS ns1.dd. +SECTION ADDITIONAL +ns1.dd. IN A 8.8.3.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +ee. IN A +SECTION AUTHORITY +ee. IN NS ns1.ee. +SECTION ADDITIONAL +ns1.ee. IN A 8.8.5.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +ff. IN A +SECTION AUTHORITY +ff. IN NS ns1.ff. +SECTION ADDITIONAL +ns1.ff. IN A 8.8.6.8 +ENTRY_END + +RANGE_END + +; com. ----------------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 8.8.8.8 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS ns1.com. +SECTION ADDITIONAL +ns1.com. IN A 8.8.8.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +gotham.com. IN A +SECTION AUTHORITY +gotham.com. IN NS ns1.gotham.com. +SECTION ADDITIONAL +ns1.gotham.com. IN A 192.0.6.1 +ENTRY_END + +RANGE_END + +; aa. ------------------------------------------------------------------------ +RANGE_BEGIN 0 100 + ADDRESS 8.8.0.8 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +aa. IN NS +SECTION ANSWER +aa. IN NS ns1.aa. +SECTION ADDITIONAL +ns1.aa. IN A 8.8.0.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +gotham.aa. IN A +SECTION AUTHORITY +gotham.aa. IN NS ns1.gotham.aa. +SECTION ADDITIONAL +ns1.gotham.aa. IN A 192.0.0.1 +ENTRY_END + +RANGE_END + +; bb. ------------------------------------------------------------------------ +RANGE_BEGIN 0 100 + ADDRESS 8.8.1.8 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +bb. IN NS +SECTION ANSWER +bb. IN NS ns1.bb. +SECTION ADDITIONAL +ns1.bb. IN A 8.8.1.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +gotham.bb. IN A +SECTION AUTHORITY +gotham.bb. IN NS ns1.gotham.bb. +SECTION ADDITIONAL +ns1.gotham.bb. IN A 192.0.1.1 +ENTRY_END + +RANGE_END + +; ff. ------------------------------------------------------------------------ +RANGE_BEGIN 0 100 + ADDRESS 8.8.6.8 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ff. IN NS +SECTION ANSWER +ff. IN NS ns1.ff. +SECTION ADDITIONAL +ns1.ff. IN A 8.8.6.8 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +gotham.ff. IN A +SECTION AUTHORITY +gotham.ff. IN NS ns1.gotham.ff. +SECTION ADDITIONAL +ns1.gotham.ff. IN A 192.0.5.1 +ENTRY_END + +RANGE_END + +; ns1.gotham.com. ------------------------------------------------------------ +RANGE_BEGIN 0 100 + ADDRESS 192.0.6.1 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +gotham.com. IN A +SECTION ANSWER +gotham.com. IN A 192.0.6.2 +ENTRY_END + +RANGE_END + +; ns1.gotham.aa. ------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 192.0.0.1 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +gotham.aa. IN A +SECTION ANSWER +gotham.aa. IN A 192.0.0.2 +ENTRY_END + +RANGE_END + +; ns1.gotham.bb. ------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 192.0.1.1 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +gotham.bb. IN A +SECTION ANSWER +gotham.bb. IN A 192.0.1.2 +ENTRY_END + +RANGE_END + +; ns1.gotham.ff. ------------------------------------------------------------- +RANGE_BEGIN 0 100 + ADDRESS 192.0.5.1 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +gotham.ff. IN A +SECTION ANSWER +gotham.ff. IN A 192.0.5.2 +ENTRY_END + +RANGE_END + +; ---------------------------------------------------------------------------- + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.com. IN A +ENTRY_END + +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +gotham.com. IN A +SECTION ANSWER +gotham.com. IN A 192.0.6.2 +ENTRY_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.aa. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NXDOMAIN +SECTION QUESTION +gotham.aa. IN A +SECTION ANSWER +ENTRY_END + +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.bb. IN A +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AA NOERROR +SECTION QUESTION +gotham.bb. IN A +SECTION ANSWER +ENTRY_END + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.ff. IN A +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AA NOERROR +SECTION QUESTION +gotham.ff. IN A +SECTION ANSWER +gotham.ff. IN A 127.0.0.1 +ENTRY_END + +; again with more cache items +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +gotham.ff. IN A +ENTRY_END + +STEP 41 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AA NOERROR +SECTION QUESTION +gotham.ff. IN A +SECTION ANSWER +gotham.ff. IN A 127.0.0.1 +ENTRY_END + +SCENARIO_END diff --git a/testdata/rpz_qname.rpl b/testdata/rpz_qname.rpl index 7940e9392..ede697233 100644 --- a/testdata/rpz_qname.rpl +++ b/testdata/rpz_qname.rpl @@ -38,6 +38,7 @@ d TXT "local data 2nd zone" e CNAME *.a.example. *.e CNAME *.b.example. drop CNAME rpz-drop. +tcp CNAME rpz-tcp-only. TEMPFILE_END stub-zone: @@ -46,12 +47,15 @@ stub-zone: stub-zone: name: "example." stub-addr: 10.20.30.50 +stub-zone: + name: "tcp." + stub-addr: 10.20.30.60 CONFIG_END SCENARIO_BEGIN Test all support RPZ action for QNAME trigger ; a. -RANGE_BEGIN 0 100 +RANGE_BEGIN 0 1000 ADDRESS 10.20.30.40 ENTRY_BEGIN MATCH opcode qtype qname @@ -88,7 +92,7 @@ ENTRY_END RANGE_END ; example. -RANGE_BEGIN 0 100 +RANGE_BEGIN 0 1000 ADDRESS 10.20.30.50 ENTRY_BEGIN MATCH opcode qtype qname @@ -122,6 +126,42 @@ SECTION ANSWER something.e.b.example. IN TXT "*.b.example. answer from upstream ns" ENTRY_END +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +f.example. IN TXT +SECTION ANSWER +f.example. IN CNAME d. +ENTRY_END + +RANGE_END + +; tcp. +RANGE_BEGIN 0 1000 + ADDRESS 10.20.30.60 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +tcp. IN NS +SECTION ANSWER +tcp. IN NS ns.example. +SECTION ADDITIONAL +ns.tcp IN A 10.20.30.60 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +tcp. IN TXT +SECTION ANSWER +tcp. IN TXT "tcp. answer from upstream ns" +ENTRY_END RANGE_END STEP 10 QUERY @@ -295,10 +335,66 @@ something.e.b.example. IN TXT "*.b.example. answer from upstream ns" ENTRY_END ; deny zone -STEP 90 QUERY +;STEP 90 QUERY +;ENTRY_BEGIN +;SECTION QUESTION +;drop. IN TXT +;ENTRY_END + +; tcp-only action + +STEP 95 QUERY ENTRY_BEGIN +REPLY RD SECTION QUESTION -drop. IN TXT +tcp. IN TXT ENTRY_END + +STEP 96 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AA TC NOERROR +SECTION QUESTION +tcp. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 97 QUERY +ENTRY_BEGIN +MATCH TCP +REPLY RD +SECTION QUESTION +tcp. IN TXT +ENTRY_END + +STEP 98 CHECK_ANSWER +ENTRY_BEGIN +MATCH all TCP +REPLY QR RD RA NOERROR +SECTION QUESTION +tcp. IN TXT +SECTION ANSWER +tcp. IN TXT "tcp. answer from upstream ns" +ENTRY_END + +; check if the name after the CNAME has the qname trigger applied to it. +STEP 100 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +f.example. IN TXT +ENTRY_END + +STEP 101 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AA NOERROR +SECTION QUESTION +f.example. IN TXT +SECTION ANSWER +f.example. IN CNAME d. +d. IN TXT "local data 2nd zone" +ENTRY_END + ; no answer is checked at exit of testbound. SCENARIO_END diff --git a/testdata/rpz_qname_tcponly.rpl b/testdata/rpz_qname_tcponly.rpl new file mode 100644 index 000000000..d30b88616 --- /dev/null +++ b/testdata/rpz_qname_tcponly.rpl @@ -0,0 +1,117 @@ +; config options +server: + module-config: "respip validator iterator" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + +rpz: + name: "rpz.example.com." + zonefile: +TEMPFILE_NAME rpz.example.com +TEMPFILE_CONTENTS rpz.example.com +$ORIGIN example.com. +rpz 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.rpz.example.com. + 3600 IN NS ns2.rpz.example.com. +$ORIGIN rpz.example.com. +a.a CNAME rpz-passthru. +b.a CNAME rpz-tcp-only. +TEMPFILE_END + +stub-zone: + name: "a." + stub-addr: 10.20.30.40 +CONFIG_END + +SCENARIO_BEGIN Test RPZ qname trigger and tcp-only action + +RANGE_BEGIN 0 100 + ADDRESS 10.20.30.40 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a. IN NS +SECTION ANSWER +a. IN NS ns.a. +SECTION ADDITIONAL +ns.a IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +b.a. IN TXT +SECTION ANSWER +b.a. IN TXT "upstream txt rr b.a." +ENTRY_END + +RANGE_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +STEP 20 QUERY +ENTRY_BEGIN +MATCH UDP +REPLY RD +SECTION QUESTION +b.a. IN TXT +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all UDP +REPLY QR AA TC RD RA NOERROR +SECTION QUESTION +b.a. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 30 QUERY +ENTRY_BEGIN +MATCH TCP +REPLY RD +SECTION QUESTION +b.a. IN TXT +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all TCP +REPLY QR RD RA NOERROR +SECTION QUESTION +b.a. IN TXT +SECTION ANSWER +b.a. IN TXT "upstream txt rr b.a." +ENTRY_END + +SCENARIO_END diff --git a/testdata/rpz_respip.rpl b/testdata/rpz_respip.rpl index 94f998be6..894a7cc5f 100644 --- a/testdata/rpz_respip.rpl +++ b/testdata/rpz_respip.rpl @@ -20,6 +20,7 @@ $ORIGIN rpz.example.com. 16.0.0.10.10.rpz-ip CNAME . 24.0.10.10.10.rpz-ip CNAME rpz-drop. 32.10.10.10.10.rpz-ip CNAME rpz-passthru. +32.1.1.1.10.rpz-ip CNAME rpz-tcp-only. 32.zz.db8.2001.rpz-ip CNAME *. 48.zz.aa.db8.2001.rpz-ip CNAME . 64.zz.bb.aa.db8.2001.rpz-ip CNAME rpz-drop. @@ -217,6 +218,16 @@ SECTION ANSWER h. IN AAAA 2001:db8:aa:bb:cc::124 ENTRY_END +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +y. IN A +SECTION ANSWER +y. IN A 10.1.1.1 +ENTRY_END + RANGE_END STEP 1 QUERY @@ -446,4 +457,21 @@ SECTION QUESTION e. IN AAAA ENTRY_END STEP 29 TIME_PASSES ELAPSE 12 + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +y. IN A +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR TC RD RA NOERROR +SECTION QUESTION +y. IN A +SECTION ANSWER +ENTRY_END + SCENARIO_END diff --git a/testdata/rpz_respip_tcponly.rpl b/testdata/rpz_respip_tcponly.rpl new file mode 100644 index 000000000..c495de203 --- /dev/null +++ b/testdata/rpz_respip_tcponly.rpl @@ -0,0 +1,207 @@ +; config options +server: + module-config: "respip validator iterator" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + +rpz: + name: "rpz.example.com." + zonefile: +TEMPFILE_NAME rpz.example.com +TEMPFILE_CONTENTS rpz.example.com +$ORIGIN example.com. +rpz 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.rpz.example.com. + 3600 IN NS ns2.rpz.example.com. +$ORIGIN rpz.example.com. +8.0.0.0.10.rpz-ip CNAME *. +16.0.0.10.10.rpz-ip CNAME . +24.0.10.10.10.rpz-ip CNAME rpz-drop. +32.10.10.10.10.rpz-ip CNAME rpz-passthru. +32.1.1.1.10.rpz-ip CNAME rpz-tcp-only. +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 10.20.30.40 +CONFIG_END + +SCENARIO_BEGIN Test RPZ response IP address trigger and tcp-only action + +RANGE_BEGIN 0 100 + ADDRESS 10.20.30.40 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS ns. +SECTION ADDITIONAL +ns. IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a. IN A +SECTION ANSWER +a. IN A 10.0.0.123 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +b. IN A +SECTION ANSWER +b. IN A 10.1.0.123 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +c. IN A +SECTION ANSWER +c. IN A 10.11.0.123 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +d. IN A +SECTION ANSWER +d. IN A 10.10.0.123 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +f. IN A +SECTION ANSWER +f. IN A 10.10.10.10 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +y. IN A +SECTION ANSWER +y. IN A 10.1.1.1 +ENTRY_END + +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a. IN A +ENTRY_END + +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +a. IN A +SECTION ANSWER +ENTRY_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +b. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +b. IN A +SECTION ANSWER +ENTRY_END + +STEP 13 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +d. IN A +ENTRY_END + +STEP 14 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NXDOMAIN +SECTION QUESTION +d. IN A +SECTION ANSWER +ENTRY_END + +STEP 17 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +f. IN A +ENTRY_END + +STEP 18 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +f. IN A +SECTION ANSWER +f. IN A 10.10.10.10 +ENTRY_END + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +y. IN A +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR TC RD RA NOERROR +SECTION QUESTION +y. IN A +SECTION ANSWER +ENTRY_END + +STEP 40 QUERY +ENTRY_BEGIN +MATCH TCP +REPLY RD +SECTION QUESTION +y. IN A +ENTRY_END + +STEP 41 CHECK_ANSWER +ENTRY_BEGIN +MATCH all TCP +REPLY QR RD RA NOERROR +SECTION QUESTION +y. IN A +SECTION ANSWER +y. IN A 10.1.1.1 +ENTRY_END + +SCENARIO_END diff --git a/util/data/msgreply.c b/util/data/msgreply.c index 6c875674d..4f6d3398b 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -166,6 +166,32 @@ reply_info_alloc_rrset_keys(struct reply_info* rep, struct alloc_cache* alloc, return 1; } +struct reply_info * +make_new_reply_info(const struct reply_info* rep, struct regional* region, + size_t an_numrrsets, size_t copy_rrsets) +{ + struct reply_info* new_rep; + size_t i; + + /* create a base struct. we specify 'insecure' security status as + * the modified response won't be DNSSEC-valid. In our faked response + * the authority and additional sections will be empty (except possible + * EDNS0 OPT RR in the additional section appended on sending it out), + * so the total number of RRsets is an_numrrsets. */ + new_rep = construct_reply_info_base(region, rep->flags, + rep->qdcount, rep->ttl, rep->prefetch_ttl, + rep->serve_expired_ttl, an_numrrsets, 0, 0, an_numrrsets, + sec_status_insecure); + if(!new_rep) + return NULL; + if(!reply_info_alloc_rrset_keys(new_rep, NULL, region)) + return NULL; + for(i=0; irrsets[i] = rep->rrsets[i]; + + return new_rep; +} + /** find the minimumttl in the rdata of SOA record */ static time_t soa_find_minttl(struct rr_parse* rr) diff --git a/util/data/msgreply.h b/util/data/msgreply.h index c6b220ed8..5a30a1d77 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -382,6 +382,21 @@ struct reply_info* reply_info_copy(struct reply_info* rep, int reply_info_alloc_rrset_keys(struct reply_info* rep, struct alloc_cache* alloc, struct regional* region); +/* + * Create a new reply_info based on 'rep'. The new info is based on + * the passed 'rep', but ignores any rrsets except for the first 'an_numrrsets' + * RRsets in the answer section. These answer rrsets are copied to the + * new info, up to 'copy_rrsets' rrsets (which must not be larger than + * 'an_numrrsets'). If an_numrrsets > copy_rrsets, the remaining rrsets array + * entries will be kept empty so the caller can fill them later. When rrsets + * are copied, they are shallow copied. The caller must ensure that the + * copied rrsets are valid throughout its lifetime and must provide appropriate + * mutex if it can be shared by multiple threads. + */ +struct reply_info * +make_new_reply_info(const struct reply_info* rep, struct regional* region, + size_t an_numrrsets, size_t copy_rrsets); + /** * Copy a parsed rrset into given key, decompressing and allocating rdata. * @param pkt: packet for decompression diff --git a/util/data/packed_rrset.h b/util/data/packed_rrset.h index ff95c0af0..e1feb22bb 100644 --- a/util/data/packed_rrset.h +++ b/util/data/packed_rrset.h @@ -61,6 +61,13 @@ typedef uint64_t rrset_id_type; * updated on encoding in a reply. This flag is not expected to be set in * cached data. */ #define PACKED_RRSET_FIXEDTTL 0x80000000 +/** This rrset is from RPZ. It is not real, it is synthesized data to block + * access. The flag makes lookups, from cache in iterator, ignore the fake + * items and only use actual data. Eg. when the iterator looksup NS, CNAME, + * A and AAAA types, it then gets items without this flag that are the + * actual network. But messages with these records in it can be stored in + * the cache and retrieved for a reply. */ +#define PACKED_RRSET_RPZ 0x8 /** number of rrs and rrsets for integer overflow protection. More than * this is not really possible (64K packet has much less RRs and RRsets) in @@ -88,6 +95,7 @@ struct packed_rrset_key { * o PACKED_RRSET_PARENT_SIDE * o PACKED_RRSET_SOA_NEG * o PACKED_RRSET_FIXEDTTL (not supposed to be cached) + * o PACKED_RRSET_RPZ */ uint32_t flags; /** the rrset type in network format */