]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
RPZ: refactor clientip to handle multiple rrsets
authormb <mb@64k.by>
Mon, 9 Nov 2020 14:14:25 +0000 (15:14 +0100)
committermb <mb@64k.by>
Mon, 9 Nov 2020 14:59:00 +0000 (15:59 +0100)
services/rpz.c
services/rpz.h
testdata/rpz_clientip.rpl

index 6e9265c9f6653c91fedae413750e6e621f9e519a..37438da3a89b64c5bc7cfe3bc4360cbfe5c8edde 100644 (file)
@@ -302,13 +302,53 @@ 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);
        respip_set_delete(r->respip_set);
-       respip_set_delete(r->client_set);
+       rpz_clientip_synthesized_set_delete(r->client_set);
        regional_destroy(r->region);
        free(r->taglist);
        free(r->log_name);
@@ -321,14 +361,14 @@ rpz_clear(struct rpz* r)
        /* must hold write lock on auth_zone */
        local_zones_delete(r->local_zones);
        respip_set_delete(r->respip_set);
-       respip_set_delete(r->client_set);
+       rpz_clientip_synthesized_set_delete(r->client_set);
        if(!(r->local_zones = local_zones_create())){
                return 0;
        }
        if(!(r->respip_set = respip_set_create())) {
                return 0;
        }
-       if(!(r->client_set = respip_set_create())) {
+       if(!(r->client_set = rpz_clientip_synthesized_set_create())) {
                return 0;
        }
        return 1;
@@ -342,7 +382,7 @@ rpz_finish_config(struct rpz* r)
        lock_rw_unlock(&r->respip_set->lock);
 
        lock_rw_wrlock(&r->client_set->lock);
-       addr_tree_init_parents(&r->client_set->ip_tree);
+       addr_tree_init_parents(&r->client_set->entries);
        lock_rw_unlock(&r->client_set->lock);
 }
 
@@ -411,8 +451,8 @@ rpz_create(struct config_auth* p)
                goto err;
        }
 
-       /* (ab)use respip for client acl */
-       if(!(r->client_set = respip_set_create())) {
+       r->client_set = rpz_clientip_synthesized_set_create();
+       if(r->client_set == NULL) {
                goto err;
        }
 
@@ -458,8 +498,8 @@ err:
                        local_zones_delete(r->local_zones);
                if(r->respip_set)
                        respip_set_delete(r->respip_set);
-               if(r->client_set)
-                       respip_set_delete(r->client_set);
+               if(r->client_set != NULL)
+                       rpz_clientip_synthesized_set_delete(r->client_set);
                if(r->taglist)
                        free(r->taglist);
                if(r->region)
@@ -598,8 +638,149 @@ rpz_insert_ipaddr_based_trigger(struct respip_set* set, struct sockaddr_storage*
        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;
+       struct sockaddr* sa;
+       sa = (struct sockaddr*)&raddr->node.addr;
+       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, "fixme");
+}
+
+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_client_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+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)
 {
@@ -608,7 +789,8 @@ rpz_insert_client_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
        int net, af;
        enum respip_action respa = rpz_action_to_respip_action(a);
 
-       verbose(VERB_ALGO, "RPZ: insert client ip trigger: %s", rpz_action_to_string(a));
+       verbose(VERB_ALGO, "RPZ: insert clientip trigger: %s", rpz_action_to_string(a));
+
        if(a == RPZ_INVALID_ACTION || respa == respip_invalid) {
                verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s",
                        rpz_action_to_string(a));
@@ -620,10 +802,12 @@ rpz_insert_client_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
                return 0;
        }
 
-       return rpz_insert_ipaddr_based_trigger(r->client_set, &addr, addrlen, net,
+       return rpz_clientip_insert_trigger_rr(r->client_set, &addr, addrlen, net,
                        a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len);
 }
 
+
+
 /** Insert RR into RPZ's respip_set */
 static int
 rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
@@ -707,7 +891,7 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
                        rr_len);
                free(policydname);
        } else if(t == RPZ_CLIENT_IP_TRIGGER) {
-               rpz_insert_client_ip_trigger(r, policydname, policydnamelen,
+               rpz_insert_clientip_trigger(r, policydname, policydnamelen,
                        a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
                        rr_len);
                free(policydname);
@@ -1018,17 +1202,17 @@ log_rpz_apply(uint8_t* dname, enum rpz_action a, struct query_info* qinfo,
 static enum rpz_action
 rpz_apply_client_ip_trigger(struct rpz* r, struct comm_reply* repinfo)
 {
-       struct resp_addr* raddr = NULL;
+       struct clientip_synthesized_rr* raddr = NULL;
        enum rpz_action action = RPZ_INVALID_ACTION;
        struct sockaddr_storage* addr = &repinfo->addr;
        socklen_t addrlen = repinfo->addrlen;
 
        lock_rw_rdlock(&r->client_set->lock);
 
-       raddr = (struct resp_addr*)addr_tree_lookup(&r->client_set->ip_tree,
+       raddr = (struct clientip_synthesized_rr*)addr_tree_lookup(&r->client_set->entries,
                        addr, addrlen);
        if(raddr != NULL) {
-               action = respip_action_to_rpz_action(raddr->action);
+               action = raddr->action;
                lock_rw_unlock(&raddr->lock);
        }
 
@@ -1114,12 +1298,12 @@ rpz_local_encode(struct query_info* qinfo, struct module_env* env,
        memset(&rep, 0, sizeof(rep));
        rep.flags = (uint16_t)((BIT_QR | BIT_AA | BIT_RA) | rcode);
        rep.qdcount = 1;
-       if(ansec)
+       rep.rrset_count = ansec;
+       if(ansec > 0) {
                rep.an_numrrsets = 1;
-       else    rep.ns_numrrsets = 1;
-       rep.rrset_count = 1;
-       rep.rrsets = &rrset;
-       rep.ttl = ((struct packed_rrset_data*)rrset->entry.data)->rr_ttl[0];
+               rep.rrsets = &rrset;
+               rep.ttl = ((struct packed_rrset_data*)rrset->entry.data)->rr_ttl[0];
+       }
 
        udpsize = edns->udp_size;
        edns->edns_version = EDNS_ADVERTISED_VERSION;
@@ -1137,39 +1321,63 @@ rpz_local_encode(struct query_info* qinfo, struct module_env* env,
        return 1;
 }
 
+static struct local_rrset*
+rpz_clientip_find_rrset(struct query_info* qinfo, 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(qinfo->qtype) == packed_rrset->type) {
+                       return cursor;
+               }
+               cursor = cursor->next;
+       }
+       return NULL;
+}
+
 static void
 rpz_apply_clientip_localdata_action(struct rpz* r, struct module_env* env,
        struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo,
        sldns_buffer* buf, struct regional* temp)
 {
-       struct resp_addr* raddr = NULL;
+       struct clientip_synthesized_rr* raddr = NULL;
+       struct local_rrset* rrset;
        enum rpz_action action = RPZ_INVALID_ACTION;
        struct sockaddr_storage* addr = &repinfo->addr;
        socklen_t addrlen = repinfo->addrlen;
        struct ub_packed_rrset_key* rp = NULL;
        int rcode = LDNS_RCODE_NOERROR|BIT_AA;
+       int rrset_count = 1;
 
        verbose(VERB_ALGO, "RPZ: apply client ip trigger: found=%d action=%s",
                raddr != NULL, rpz_action_to_string(action));
 
        lock_rw_rdlock(&r->client_set->lock);
 
-       raddr = (struct resp_addr*)addr_tree_lookup(&r->client_set->ip_tree,
-                       addr, addrlen);
+       raddr = (struct clientip_synthesized_rr*)addr_tree_lookup(
+                       &r->client_set->entries, addr, addrlen);
        if(raddr == NULL) {
                lock_rw_unlock(&r->client_set->lock);
                return;
        }
 
-       /* prepare synthesized answer for the matching client */
+       /* prepare synthesized answer for client */
 
-       action = respip_action_to_rpz_action(raddr->action);
+       action = raddr->action;
        if(action != RPZ_LOCAL_DATA_ACTION && raddr->data == NULL ) {
                verbose(VERB_ALGO, "RPZ: bug: local-data action and no local data");
                goto done;
        }
 
-       rp = respip_copy_rrset(raddr->data, temp);
+       /* check query type / rr type */
+
+       rrset = rpz_clientip_find_rrset(qinfo, 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");
                goto done;
@@ -1178,16 +1386,15 @@ rpz_apply_clientip_localdata_action(struct rpz* r, struct module_env* env,
        //struct packed_rrset_data* pd = raddr->data->entry.data;
        //struct packed_rrset_data* pd2 = rp->entry.data;
        //verbose(VERB_ALGO, "ttl=%ld ttl=%ld", pd->rr_ttl[0],  pd2->rr_ttl[0]);
-
        rp->rk.flags |= PACKED_RRSET_FIXEDTTL;
        rp->rk.dname = qinfo->qname;
        rp->rk.dname_len = qinfo->qname_len;
-       rpz_local_encode(qinfo, env, edns, repinfo, buf, temp, rp, 1, rcode);
+nodata:
+       rpz_local_encode(qinfo, env, edns, repinfo, buf, temp, rp, rrset_count, rcode);
 
 done:
        lock_rw_unlock(&raddr->lock);
        lock_rw_unlock(&r->client_set->lock);
-
 }
 
 static int
index 1210f2039a4c06d72fb19273e065e00f0b2c8947..19dd867b68f1637bb102bb432b93297c060d3e4b 100644 (file)
@@ -83,6 +83,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,7 +113,7 @@ enum rpz_action {
 struct rpz {
        struct local_zones* local_zones;
        struct respip_set* respip_set;
-       struct respip_set* client_set;
+       struct clientip_synthesized_rrset* client_set;
        uint8_t* taglist;
        size_t taglistlen;
        enum rpz_action action_override;
index f679d55755dc45d6e28b30522aec99ab3708bf39..64082210cd3a2d2a77fbec04b23b5c7c384f386e 100644 (file)
@@ -22,6 +22,7 @@ $ORIGIN rpz.example.com.
 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:
@@ -55,6 +56,26 @@ 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
@@ -175,7 +196,7 @@ STEP 60 QUERY ADDRESS 192.0.5.1
 ENTRY_BEGIN
 REPLY RD
 SECTION QUESTION
-a.a.  IN TXT
+a.a.  IN A
 ENTRY_END
 
 STEP 61 CHECK_ANSWER
@@ -188,7 +209,7 @@ SECTION ANSWER
 a.a.  IN A 127.0.0.1
 ENTRY_END
 
-; should be synthesized NODATA
+; should be synthesized
 
 STEP 62 QUERY ADDRESS 192.0.5.1
 ENTRY_BEGIN
@@ -204,6 +225,24 @@ REPLY QR AA RD RA NOERROR
 SECTION QUESTION
 a.a.  IN TXT
 SECTION ANSWER
+a.a.  IN TXT "42"
+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
 ENTRY_END
 
 ; should be DROPPED