diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c index 0a7afc2bad03c6e2970349f5e6de7e1873c0e844..5aa304059e039c66522015b7537559dd8b045bd0 100644 --- a/src/knot/updates/ddns.c +++ b/src/knot/updates/ddns.c @@ -31,99 +31,9 @@ #include "common/descriptor.h" #include "common/lists.h" -static bool list_contains_rr(const list_t *l, const knot_rrset_t *rr) -{ - knot_rr_ln_t *n; - WALK_LIST(n, *l) { - const knot_rrset_t *list_rr = n->rr; - if (knot_rrset_equal(rr, list_rr, KNOT_RRSET_COMPARE_WHOLE)) { - return true; - } - }; - - return false; -} - -static bool removed_rr(const knot_changeset_t *changeset, const knot_rrset_t *rr) -{ - return list_contains_rr(&changeset->remove, rr); -} - -static void remove_rr_from_list(list_t *l, const knot_rrset_t *rr) -{ - knot_rr_ln_t *rr_node = NULL; - node_t *nxt = NULL; - WALK_LIST_DELSAFE(rr_node, nxt, *l) { - knot_rrset_t *rrset = rr_node->rr; - if (knot_rrset_equal(rrset, rr, KNOT_RRSET_COMPARE_WHOLE)) { - knot_rrset_free(&rrset, NULL); - rem_node((node_t *)rr_node); - return; - } - } -} - -static void remove_header_from_list(list_t *l, const knot_rrset_t *rr) -{ - knot_rr_ln_t *rr_node = NULL; - node_t *nxt = NULL; - WALK_LIST_DELSAFE(rr_node, nxt, *l) { - knot_rrset_t *rrset = rr_node->rr; - if (knot_rrset_equal(rrset, rr, KNOT_RRSET_COMPARE_HEADER)) { - knot_rrset_free(&rrset, NULL); - rem_node((node_t *)rr_node); - } - } -} - -static void remove_owner_from_list(list_t *l, const knot_dname_t *owner) -{ - knot_rr_ln_t *rr_node = NULL; - node_t *nxt = NULL; - WALK_LIST_DELSAFE(rr_node, nxt, *l) { - knot_rrset_t *rrset = rr_node->rr; - if (knot_dname_is_equal(rrset->owner, owner)) { - knot_rrset_free(&rrset, NULL); - rem_node((node_t *)rr_node); - } - } -} - -static bool node_empty(const knot_node_t *node, const knot_changeset_t *changeset) -{ - if (node == NULL) { - return true; - } - - for (uint16_t i = 0; i < node->rrset_count; ++i) { - knot_rrset_t node_rrset = NODE_RR_INIT_N(node, i); - knot_rrset_t node_rr; - knot_rrset_init(&node_rr, node->owner, node_rrset.type, KNOT_CLASS_IN); - for (uint16_t j = 0; j < node_rrset.rrs.rr_count; ++j) { - knot_rrset_add_rr_from_rrset(&node_rr, &node_rrset, j, NULL); - if (!removed_rr(changeset, &node_rr)) { - knot_rrs_clear(&node_rr.rrs, NULL); - return false; - } - knot_rrs_clear(&node_rr.rrs, NULL); - } - } - - return true; -} - -static bool rrset_empty(const knot_rrset_t *rrset) -{ - uint16_t rr_count = knot_rrset_rr_count(rrset); - if (rr_count == 0) { - return true; - } - if (rr_count == 1) { - return knot_rrset_rr_size(rrset, 0) == 0; - } - return false; -} +/* ----------------------------- prereq check ------------------------------- */ +/*!< \brief Clears prereq RRSet list. */ static void rrset_list_clear(list_t *l) { node_t *n, *nxt; @@ -135,6 +45,7 @@ static void rrset_list_clear(list_t *l) }; } +/*!< \brief Adds RR to prereq RRSet list, merges RRs into RRSets. */ static int add_rr_to_list(list_t *l, const knot_rrset_t *rr) { node_t *n; @@ -157,6 +68,7 @@ static int add_rr_to_list(list_t *l, const knot_rrset_t *rr) return ptrlist_add(l, rr_copy, NULL) != NULL ? KNOT_EOK : KNOT_ENOMEM; } +/*!< \brief Checks whether RR type exists in the zone. */ static int knot_ddns_check_exist(const knot_zone_contents_t *zone, const knot_rrset_t *rrset, uint16_t *rcode) { @@ -185,6 +97,7 @@ static int knot_ddns_check_exist(const knot_zone_contents_t *zone, return KNOT_EOK; } +/*!< \brief Checks whether RRSet exists in the zone. */ static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone, const knot_rrset_t *rrset, uint16_t *rcode) @@ -210,10 +123,7 @@ static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone, return KNOT_EPREREQ; } else { knot_rrset_t found = NODE_RR_INIT(node, rrset->type); - // do not have to compare the header, it is already done - assert((&found)->type == rrset->type); - assert(knot_dname_cmp((&found)->owner, - rrset->owner) == 0); + assert(!knot_rrset_empty(&found)); if (!knot_rrset_equal(&found, rrset, KNOT_RRSET_COMPARE_WHOLE)) { *rcode = KNOT_RCODE_NXRRSET; return KNOT_EPREREQ; @@ -223,6 +133,7 @@ static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone, return KNOT_EOK; } +/*!< \brief Checks whether RRSets in the list exist in the zone. */ static int check_exists_in_list(list_t *l, const knot_zone_contents_t *zone, uint16_t *rcode) { @@ -239,6 +150,7 @@ static int check_exists_in_list(list_t *l, const knot_zone_contents_t *zone, return KNOT_EOK; } +/*!< \brief Checks whether RR type is not in the zone. */ static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone, const knot_rrset_t *rrset, uint16_t *rcode) @@ -264,12 +176,11 @@ static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone, return KNOT_EOK; } - /* RDATA is always empty for simple RRset checks. */ - *rcode = KNOT_RCODE_YXRRSET; return KNOT_EPREREQ; } +/*!< \brief Checks whether DNAME is in the zone. */ static int knot_ddns_check_in_use(const knot_zone_contents_t *zone, const knot_dname_t *dname, uint16_t *rcode) @@ -298,6 +209,7 @@ static int knot_ddns_check_in_use(const knot_zone_contents_t *zone, return KNOT_EOK; } +/*!< \brief Checks whether DNAME is not in the zone. */ static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone, const knot_dname_t *dname, uint16_t *rcode) @@ -325,6 +237,20 @@ static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone, return KNOT_EPREREQ; } +/*!< \brief Returns true if rrset has 0 data or RDATA of size 0 (we need TTL). */ +static bool rrset_empty(const knot_rrset_t *rrset) +{ + uint16_t rr_count = knot_rrset_rr_count(rrset); + if (rr_count == 0) { + return true; + } + if (rr_count == 1) { + return knot_rrset_rr_size(rrset, 0) == 0; + } + return false; +} + +/*!< \brief Checks prereq for given packet RR. */ static int knot_ddns_check_prereq(const knot_rrset_t *rrset, uint16_t qclass, const knot_zone_contents_t *zone, @@ -357,6 +283,7 @@ static int knot_ddns_check_prereq(const knot_rrset_t *rrset, return knot_ddns_check_not_exist(zone, rrset, rcode); } } else if (rrset->rclass == qclass) { + // Store RRs for full check into list return add_rr_to_list(rrset_list, rrset); } else { dbg_ddns("ddns: add_prereq: Bad class.\n"); @@ -364,111 +291,68 @@ static int knot_ddns_check_prereq(const knot_rrset_t *rrset, } } -/* API functions */ +/* --------------------------- DDNS processing ------------------------------ */ +/* ----------------------- changeset lists helpers -------------------------- */ -int knot_ddns_check_zone(const knot_zone_contents_t *zone, - const knot_pkt_t *query, uint16_t *rcode) +/*!< \brief Checks whether RR was already removed. */ +static bool removed_rr(const knot_changeset_t *changeset, const knot_rrset_t *rr) { - if (zone == NULL || query == NULL || rcode == NULL) { - if (rcode != NULL) { - *rcode = KNOT_RCODE_SERVFAIL; + knot_rr_ln_t *n; + WALK_LIST(n, changeset->remove) { + const knot_rrset_t *list_rr = n->rr; + if (knot_rrset_equal(rr, list_rr, KNOT_RRSET_COMPARE_WHOLE)) { + return true; } - return KNOT_EINVAL; - } - - if (knot_pkt_qtype(query) != KNOT_RRTYPE_SOA) { - *rcode = KNOT_RCODE_FORMERR; - return KNOT_EMALF; - } - - // check zone CLASS - if (knot_pkt_qclass(query) != KNOT_CLASS_IN) { - *rcode = KNOT_RCODE_NOTAUTH; - return KNOT_ENOZONE; - } + }; - return KNOT_EOK; + return false; } -int knot_ddns_process_prereqs(const knot_pkt_t *query, const knot_zone_contents_t *zone, - uint16_t *rcode) +/*!< \brief Removes RR from list, full equality check. */ +static void remove_rr_from_list(list_t *l, const knot_rrset_t *rr) { - if (query == NULL || rcode == NULL || zone == NULL) { - return KNOT_EINVAL; - } - - dbg_ddns("Processing prerequisities.\n"); - - int ret = KNOT_EOK; - list_t rrset_list; // List used to store merged RRSets - init_list(&rrset_list); - - const knot_pktsection_t *answer = knot_pkt_section(query, KNOT_ANSWER); - for (int i = 0; i < answer->count; ++i) { - // Check what can be checked, store full RRs into list - ret = knot_ddns_check_prereq(&answer->rr[i], - knot_pkt_qclass(query), - zone, rcode, &rrset_list); - if (ret != KNOT_EOK) { - rrset_list_clear(&rrset_list); - return ret; + knot_rr_ln_t *rr_node = NULL; + node_t *nxt = NULL; + WALK_LIST_DELSAFE(rr_node, nxt, *l) { + knot_rrset_t *rrset = rr_node->rr; + if (knot_rrset_equal(rrset, rr, KNOT_RRSET_COMPARE_WHOLE)) { + knot_rrset_free(&rrset, NULL); + rem_node((node_t *)rr_node); + return; } } - - // Check stored RRSets - ret = check_exists_in_list(&rrset_list, zone, rcode); - rrset_list_clear(&rrset_list); - return ret; } -static int knot_ddns_check_update(const knot_rrset_t *rrset, - const knot_pkt_t *query, - uint16_t *rcode) +/*!< \brief Removes RR from list, owner and type check. */ +static void remove_header_from_list(list_t *l, const knot_rrset_t *rr) { - /* Accept both subdomain and dname match. */ - dbg_ddns("Checking UPDATE packet.\n"); - const knot_dname_t *owner = rrset->owner; - const knot_dname_t *qname = knot_pkt_qname(query); - int is_sub = knot_dname_is_sub(owner, qname); - if (!is_sub && knot_dname_cmp(owner, qname) != 0) { - *rcode = KNOT_RCODE_NOTZONE; - return KNOT_EOUTOFZONE; - } - - if (knot_rrtype_is_ddns_forbidden(rrset->type)) { - *rcode = KNOT_RCODE_REFUSED; - log_zone_error("Refusing to update DNSSEC-related record!\n"); - return KNOT_EDENIED; + knot_rr_ln_t *rr_node = NULL; + node_t *nxt = NULL; + WALK_LIST_DELSAFE(rr_node, nxt, *l) { + knot_rrset_t *rrset = rr_node->rr; + if (knot_rrset_equal(rrset, rr, KNOT_RRSET_COMPARE_HEADER)) { + knot_rrset_free(&rrset, NULL); + rem_node((node_t *)rr_node); + } } +} - if (rrset->rclass == knot_pkt_qclass(query)) { - if (knot_rrtype_is_metatype(rrset->type)) { - *rcode = KNOT_RCODE_FORMERR; - return KNOT_EMALF; - } - } else if (rrset->rclass == KNOT_CLASS_ANY) { - if (!rrset_empty(rrset) - || (knot_rrtype_is_metatype(rrset->type) - && rrset->type != KNOT_RRTYPE_ANY)) { - *rcode = KNOT_RCODE_FORMERR; - return KNOT_EMALF; - } - } else if (rrset->rclass == KNOT_CLASS_NONE) { - if (knot_rrset_rr_ttl(rrset, 0) != 0 - || knot_rrtype_is_metatype(rrset->type)) { - *rcode = KNOT_RCODE_FORMERR; - return KNOT_EMALF; +/*!< \brief Removes RR from list, owner check. */ +static void remove_owner_from_list(list_t *l, const knot_dname_t *owner) +{ + knot_rr_ln_t *rr_node = NULL; + node_t *nxt = NULL; + WALK_LIST_DELSAFE(rr_node, nxt, *l) { + knot_rrset_t *rrset = rr_node->rr; + if (knot_dname_is_equal(rrset->owner, owner)) { + knot_rrset_free(&rrset, NULL); + rem_node((node_t *)rr_node); } - } else { - *rcode = KNOT_RCODE_FORMERR; - return KNOT_EMALF; } - - return KNOT_EOK; } -/* DDNS processing */ +/* --------------------- true/false helper functions ------------------------ */ static inline bool is_addition(const knot_rrset_t *rr) { @@ -495,6 +379,7 @@ static inline bool is_node_removal(const knot_rrset_t *rr) return rr->rclass == KNOT_CLASS_ANY && rr->type == KNOT_RRTYPE_ANY; } +/*!< \brief Returns true if last addition of certain types is to be replaced. */ static bool should_replace(const knot_rrset_t *chg_rrset, const knot_rrset_t *rrset) { @@ -507,6 +392,86 @@ static bool should_replace(const knot_rrset_t *chg_rrset, } } +/*!< \brief Returns true if node will be empty after update application. */ +static bool node_empty(const knot_node_t *node, const knot_changeset_t *changeset) +{ + if (node == NULL) { + return true; + } + + for (uint16_t i = 0; i < node->rrset_count; ++i) { + knot_rrset_t node_rrset = NODE_RR_INIT_N(node, i); + knot_rrset_t node_rr; + knot_rrset_init(&node_rr, node->owner, node_rrset.type, KNOT_CLASS_IN); + for (uint16_t j = 0; j < node_rrset.rrs.rr_count; ++j) { + knot_rrset_add_rr_from_rrset(&node_rr, &node_rrset, j, NULL); + if (!removed_rr(changeset, &node_rr)) { + knot_rrs_clear(&node_rr.rrs, NULL); + return false; + } + knot_rrs_clear(&node_rr.rrs, NULL); + } + } + + return true; +} + +/*!< \brief Returns true if node contains given RR in its RRSets. */ +static bool node_contains_rr(const knot_node_t *node, + const knot_rrset_t *rr) +{ + knot_rrset_t zone_rrset = NODE_RR_INIT(node, rr->type); + if (!knot_rrset_empty(&zone_rrset)) { + knot_rrset_t intersection; + int ret = knot_rrset_intersection(&zone_rrset, rr, + &intersection, NULL); + if (ret != KNOT_EOK) { + return false; + } + const bool contains = !knot_rrset_empty(&intersection); + knot_rrs_clear(&intersection.rrs, NULL); + return contains; + } else { + return false; + } +} + +/*!< \brief Returns true if CNAME is in this node. */ +static bool adding_to_cname(const knot_node_t *node, + knot_changeset_t *changeset) +{ + if (node == NULL) { + // Node did not exist before update. + return false; + } + + knot_rrset_t cname = NODE_RR_INIT(node, KNOT_RRTYPE_CNAME); + if (knot_rrset_empty(&cname)) { + // Node did not contain CNAME before update. + return false; + } + + // Return true if we have not removed CNAME in this update. + return !removed_rr(changeset, &cname); +} + +/*!< \brief Used to ignore SOA deletions and SOAs with lower serial than zone. */ +static bool skip_soa(const knot_rrset_t *rr, int64_t sn) +{ + if (rr->type == KNOT_RRTYPE_SOA + && (rr->rclass == KNOT_CLASS_NONE + || rr->rclass == KNOT_CLASS_ANY + || knot_serial_compare(knot_rrs_soa_serial(&rr->rrs), + sn) <= 0)) { + return true; + } + + return false; +} + +/* ---------------------- changeset manipulation ---------------------------- */ + +/*!< \brief Checks whether record should be added or replaced. */ static bool skip_record_addition(knot_changeset_t *changeset, knot_rrset_t *rr) { @@ -514,10 +479,12 @@ static bool skip_record_addition(knot_changeset_t *changeset, WALK_LIST(rr_node, changeset->add) { knot_rrset_t *rrset = rr_node->rr; if (should_replace(rr, rrset)) { + // Replacing singleton RR. knot_rrset_free(&rrset, NULL); rrset = rr; return true; } else if (knot_rrset_equal(rr, rrset, KNOT_RRSET_COMPARE_WHOLE)) { + // Freeing duplication. knot_rrset_free(&rr, NULL); return true; } @@ -526,6 +493,7 @@ static bool skip_record_addition(knot_changeset_t *changeset, return false; } +/*!< \brief Adds RR into add section of changeset if it is deemed worthy. */ static int add_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset, int *apex_ns_rem) { @@ -541,12 +509,14 @@ static int add_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset, } if (apex_ns_rem) { + // Increase post update apex NS count. (*apex_ns_rem)--; } return knot_changeset_add_rrset(changeset, rr_copy, KNOT_CHANGESET_ADD); } +/*!< \brief Checks whether record should be removed (duplicate check). */ static bool skip_record_removal(knot_changeset_t *changeset, knot_rrset_t *rr) { knot_rr_ln_t *rr_node = NULL; @@ -559,21 +529,10 @@ static bool skip_record_removal(knot_changeset_t *changeset, knot_rrset_t *rr) } } - node_t *nxt = NULL; - WALK_LIST_DELSAFE(rr_node, nxt, changeset->add) { - knot_rrset_t *rrset = rr_node->rr; - if (knot_rrset_equal(rrset, rr, KNOT_RRSET_COMPARE_WHOLE)) { - // Adding and removing identical RRs, drop both. - knot_rrset_free(&rrset, NULL); - knot_rrset_free(&rr, NULL); - rem_node((node_t *)rr_node); - return true; - } - } - return false; } +/*!< \brief Adds RR into remove section of changeset if it is deemed worthy. */ static int rem_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset, int *apex_ns_rem) { @@ -589,12 +548,14 @@ static int rem_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset, } if (apex_ns_rem) { + // Decrease post update apex NS count. (*apex_ns_rem)++; } return knot_changeset_add_rrset(changeset, rr_copy, KNOT_CHANGESET_REMOVE); } +/*!< \brief Adds all RRs from RRSet into remove section of changeset. */ static int rem_rrset_to_chgset(const knot_rrset_t *rrset, knot_changeset_t *changeset, int *apex_ns_rem) @@ -616,6 +577,11 @@ static int rem_rrset_to_chgset(const knot_rrset_t *rrset, return KNOT_EOK; } +/* ------------------------ RR processing logic ----------------------------- */ + +/* --------------------------- RR additions --------------------------------- */ + +/*!< \brief Processes CNAME addition (replace or ignore) */ static int process_add_cname(const knot_node_t *node, const knot_rrset_t *rr, knot_changeset_t *changeset) @@ -637,12 +603,14 @@ static int process_add_cname(const knot_node_t *node, // Other occupied node => ignore. return KNOT_EOK; } else { + // Can add. return add_rr_to_chgset(rr, changeset, NULL); } return KNOT_EOK; } +/*!< \brief Processes CNAME addition (ignore when not removed, or non-apex) */ static int process_add_nsec3param(const knot_node_t *node, const knot_rrset_t *rr, knot_changeset_t *changeset) @@ -669,14 +637,81 @@ static int process_add_nsec3param(const knot_node_t *node, return KNOT_EOK; } +/*! + * \brief Processes SOA addition (ignore when non-apex), lower serials + * dropped before. + */ +static int process_add_soa(const knot_node_t *node, + const knot_rrset_t *rr, + knot_changeset_t *changeset) +{ + if (node == NULL || !knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA)) { + // Adding SOA to non-apex node, ignore + return KNOT_EOK; + } + + /* Get the current SOA RR from the node. */ + knot_rrset_t removed = NODE_RR_INIT(node, KNOT_RRTYPE_SOA); + /* If they are identical, ignore. */ + if (knot_rrset_equal(&removed, rr, KNOT_RRSET_COMPARE_WHOLE)) { + return KNOT_EOK; + } + return add_rr_to_chgset(rr, changeset, NULL); +} + +/*!< \brief Adds normal RR, ignores when CNAME exists in node. */ +static int process_add_normal(const knot_node_t *node, + const knot_rrset_t *rr, + knot_changeset_t *changeset, + int *apex_ns_rem) +{ + if (adding_to_cname(node, changeset)) { + // Adding RR to CNAME node, ignore. + return KNOT_EOK; + } + + if (node && node_contains_rr(node, rr)) { + // Adding existing RR, remove removal from changeset if it's there. + remove_rr_from_list(&changeset->remove, rr); + // And ignore. + return KNOT_EOK; + } + + const bool apex_ns = knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA) && + rr->type == KNOT_RRTYPE_NS; + return add_rr_to_chgset(rr, changeset, apex_ns ? apex_ns_rem : NULL); +} + +/*!< \brief Decides what to do with RR addition. */ +static int process_add(const knot_rrset_t *rr, + const knot_node_t *node, + knot_changeset_t *changeset, + int *apex_ns_rem) +{ + switch(rr->type) { + case KNOT_RRTYPE_CNAME: + return process_add_cname(node, rr, changeset); + case KNOT_RRTYPE_SOA: + return process_add_soa(node, rr, changeset); + case KNOT_RRTYPE_NSEC3PARAM: + return process_add_nsec3param(node, rr, changeset); + default: + return process_add_normal(node, rr, changeset, apex_ns_rem); + } +} + +/* --------------------------- RR deletions --------------------------------- */ + +/*!< \brief Removes single RR from zone. */ static int process_rem_rr(const knot_rrset_t *rr, const knot_node_t *node, knot_changeset_t *changeset, int *apex_ns_rem) { + // Remove possible previously added RR + remove_rr_from_list(&changeset->add, rr); if (node == NULL) { // Removing from node that did not exists before update - remove_rr_from_list(&changeset->add, rr); return KNOT_EOK; } @@ -693,8 +728,7 @@ static int process_rem_rr(const knot_rrset_t *rr, knot_rrset_t to_modify = NODE_RR_INIT(node, rr->type); if (knot_rrset_empty(&to_modify)) { - // No such RRSet, but check duplicates - remove_rr_from_list(&changeset->add, rr); + // No such RRSet return KNOT_EOK; } @@ -705,8 +739,7 @@ static int process_rem_rr(const knot_rrset_t *rr, } if (knot_rrset_empty(&intersection)) { - // No such RR, but check duplicates - remove_rr_from_list(&changeset->add, rr); + // No such RR return KNOT_EOK; } assert(intersection.rrs.rr_count == 1); @@ -716,47 +749,50 @@ static int process_rem_rr(const knot_rrset_t *rr, return ret; } +/*!< \brief Removes RRSet from zone. */ static int process_rem_rrset(const knot_rrset_t *rrset, const knot_node_t *node, knot_changeset_t *changeset) { - // Removing all added RRs with this owner and type + // Removing all previously added RRs with this owner and type from changeset remove_header_from_list(&changeset->add, rrset); if (node == NULL) { return KNOT_EOK; } - uint16_t type = rrset->type; - if (type == KNOT_RRTYPE_SOA || knot_rrtype_is_ddns_forbidden(type)) { + if (rrset->type == KNOT_RRTYPE_SOA || + knot_rrtype_is_ddns_forbidden(rrset->type)) { // Ignore SOA and DNSSEC removals. return KNOT_EOK; } - if (knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA) && type == KNOT_RRTYPE_NS) { - // Ignore whole NS apex removals. + if (knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA) && + rrset->type == KNOT_RRTYPE_NS) { + // Ignore NS apex RRSet removals. return KNOT_EOK; } - // no such RR - if (!knot_node_rrtype_exists(node, type)) { - // ignore + if (!knot_node_rrtype_exists(node, rrset->type)) { + // no such RR, ignore return KNOT_EOK; } - knot_rrset_t to_remove = NODE_RR_INIT(node, type); + knot_rrset_t to_remove = NODE_RR_INIT(node, rrset->type); return rem_rrset_to_chgset(&to_remove, changeset, NULL); } +/*!< \brief Removes node from zone. */ static int process_rem_node(const knot_rrset_t *rr, const knot_node_t *node, knot_changeset_t *changeset) { - // Remove all added records + // Remove all previously added records with given owner from changeset remove_owner_from_list(&changeset->add, rr->owner); if (node == NULL) { return KNOT_EOK; } + // Remove all RRSets from node for (int i = 0; i < node->rrset_count; ++i) { knot_rrset_t rrset = NODE_RR_INIT_N(node, i); int ret = process_rem_rrset(&rrset, node, changeset); @@ -768,99 +804,7 @@ static int process_rem_node(const knot_rrset_t *rr, return KNOT_EOK; } -static int process_add_soa(const knot_node_t *node, - const knot_rrset_t *rr, - knot_changeset_t *changeset) -{ - if (node == NULL || !knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA)) { - // Adding SOA to non-apex node, ignore - return KNOT_EOK; - } - - /* Get the current SOA RR from the node. */ - knot_rrset_t removed = NODE_RR_INIT(node, KNOT_RRTYPE_SOA); - /* If they are identical, ignore. */ - if (knot_rrset_equal(&removed, rr, KNOT_RRSET_COMPARE_WHOLE)) { - return KNOT_EOK; - } - return add_rr_to_chgset(rr, changeset, NULL); -} - -static bool node_contains_rr(const knot_node_t *node, - const knot_rrset_t *rr) -{ - knot_rrset_t zone_rrset = NODE_RR_INIT(node, rr->type); - if (!knot_rrset_empty(&zone_rrset)) { - knot_rrset_t intersection; - int ret = knot_rrset_intersection(&zone_rrset, rr, - &intersection, NULL); - if (ret != KNOT_EOK) { - return false; - } - const bool contains = !knot_rrset_empty(&intersection); - knot_rrs_clear(&intersection.rrs, NULL); - return contains; - } else { - return false; - } -} - -static bool adding_to_cname(const knot_node_t *node, - knot_changeset_t *changeset) -{ - if (node == NULL) { - // Node did not exist before update. - return false; - } - - knot_rrset_t cname = NODE_RR_INIT(node, KNOT_RRTYPE_CNAME); - if (knot_rrset_empty(&cname)) { - // Node did not contain CNAME before update. - return false; - } - - return !removed_rr(changeset, &cname); -} - -static int process_add_normal(const knot_node_t *node, - const knot_rrset_t *rr, - knot_changeset_t *changeset, - int *apex_ns_rem) -{ - if (adding_to_cname(node, changeset)) { - // Adding RR to CNAME node, ignore. - return KNOT_EOK; - } - - if (node && node_contains_rr(node, rr)) { - // Adding existing RR, remove from changeset if it's there. - remove_rr_from_list(&changeset->remove, rr); - // And ignore. - return KNOT_EOK; - } - - const bool apex_ns = knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA) && - rr->type == KNOT_RRTYPE_NS; - return add_rr_to_chgset(rr, changeset, apex_ns ? apex_ns_rem : NULL); -} - -static int process_add(const knot_rrset_t *rr, - const knot_node_t *node, - knot_changeset_t *changeset, - int *apex_ns_rem) -{ - switch(rr->type) { - case KNOT_RRTYPE_CNAME: - return process_add_cname(node, rr, changeset); - case KNOT_RRTYPE_SOA: - return process_add_soa(node, rr, changeset); - case KNOT_RRTYPE_NSEC3PARAM: - return process_add_nsec3param(node, rr, changeset); - default: - return process_add_normal(node, rr, changeset, apex_ns_rem); - } -} - +/*!< \brief Decides what to with removal. */ static int process_remove(const knot_rrset_t *rr, const knot_node_t *node, knot_changeset_t *changeset, @@ -877,14 +821,9 @@ static int process_remove(const knot_rrset_t *rr, } } -static int knot_ddns_final_soa_to_chgset(knot_rrset_t *soa, - knot_changeset_t *changeset) -{ - knot_changeset_add_soa(changeset, soa, KNOT_CHANGESET_ADD); - - return KNOT_EOK; -} +/* --------------------------- validity checks ------------------------------ */ +/*!< \brief Checks whether addition has not violated DNAME rules. */ static bool sem_check(const knot_rrset_t *rr, const knot_node_t *zone_node, const knot_zone_contents_t *zone) @@ -915,8 +854,56 @@ static bool sem_check(const knot_rrset_t *rr, return true; } +/*!< \brief Checks whether we can accept this RR. */ +static int knot_ddns_check_update(const knot_rrset_t *rrset, + const knot_pkt_t *query, + uint16_t *rcode) +{ + /* Accept both subdomain and dname match. */ + dbg_ddns("Checking UPDATE packet.\n"); + const knot_dname_t *owner = rrset->owner; + const knot_dname_t *qname = knot_pkt_qname(query); + int is_sub = knot_dname_is_sub(owner, qname); + if (!is_sub && knot_dname_cmp(owner, qname) != 0) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EOUTOFZONE; + } + + if (knot_rrtype_is_ddns_forbidden(rrset->type)) { + *rcode = KNOT_RCODE_REFUSED; + log_zone_error("Refusing to update DNSSEC-related record!\n"); + return KNOT_EDENIED; + } + + if (rrset->rclass == knot_pkt_qclass(query)) { + if (knot_rrtype_is_metatype(rrset->type)) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else if (rrset->rclass == KNOT_CLASS_ANY) { + if (!rrset_empty(rrset) + || (knot_rrtype_is_metatype(rrset->type) + && rrset->type != KNOT_RRTYPE_ANY)) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else if (rrset->rclass == KNOT_CLASS_NONE) { + if (knot_rrset_rr_ttl(rrset, 0) != 0 + || knot_rrtype_is_metatype(rrset->type)) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*!< \brief Checks RR and decides what to do with it. */ static int knot_ddns_process_rr(const knot_rrset_t *rr, - knot_zone_contents_t *zone, + const knot_zone_contents_t *zone, knot_changeset_t *changeset, int *apex_ns_rem) { @@ -936,24 +923,7 @@ static int knot_ddns_process_rr(const knot_rrset_t *rr, } } -/* - * Check if the record is SOA. If yes, check the SERIAL. - * If this record should cause the SOA to be replaced in the - * zone, use it as the ending SOA. - */ -static bool skip_soa(const knot_rrset_t *rr, int64_t sn) -{ - if (rr->type == KNOT_RRTYPE_SOA - && (rr->rclass == KNOT_CLASS_NONE - || rr->rclass == KNOT_CLASS_ANY - || knot_serial_compare(knot_rrs_soa_serial(&rr->rrs), - sn) <= 0)) { - return true; - } - - return false; -} - +/*!< \brief Maps Knot return code to RCODE. */ static uint16_t ret_to_rcode(int ret) { if (ret == KNOT_EMALF) { @@ -965,7 +935,40 @@ static uint16_t ret_to_rcode(int ret) } } -int knot_ddns_process_update(knot_zone_contents_t *zone, +/* ---------------------------------- API ----------------------------------- */ + +int knot_ddns_process_prereqs(const knot_pkt_t *query, const knot_zone_contents_t *zone, + uint16_t *rcode) +{ + if (query == NULL || rcode == NULL || zone == NULL) { + return KNOT_EINVAL; + } + + dbg_ddns("Processing prerequisities.\n"); + + int ret = KNOT_EOK; + list_t rrset_list; // List used to store merged RRSets + init_list(&rrset_list); + + const knot_pktsection_t *answer = knot_pkt_section(query, KNOT_ANSWER); + for (int i = 0; i < answer->count; ++i) { + // Check what can be checked, store full RRs into list + ret = knot_ddns_check_prereq(&answer->rr[i], + knot_pkt_qclass(query), + zone, rcode, &rrset_list); + if (ret != KNOT_EOK) { + rrset_list_clear(&rrset_list); + return ret; + } + } + + // Check stored RRSets + ret = check_exists_in_list(&rrset_list, zone, rcode); + rrset_list_clear(&rrset_list); + return ret; +} + +int knot_ddns_process_update(const knot_zone_contents_t *zone, const knot_pkt_t *query, knot_changeset_t *changeset, uint16_t *rcode, uint32_t new_serial) @@ -977,25 +980,14 @@ int knot_ddns_process_update(knot_zone_contents_t *zone, /* Copy base SOA RR. */ knot_rrset_t *soa_begin = knot_node_create_rrset(zone->apex, KNOT_RRTYPE_SOA); - knot_rrset_t *soa_end = NULL; knot_changeset_add_soa(changeset, soa_begin, KNOT_CHANGESET_REMOVE); - /* Current SERIAL */ - int64_t sn = knot_rrs_soa_serial(&soa_begin->rrs); - int64_t sn_new; - - /* Set the new serial according to policy. */ - if (sn > -1) { - sn_new = new_serial; - assert(sn_new != KNOT_EINVAL); - } else { - *rcode = KNOT_RCODE_SERVFAIL; - return KNOT_EINVAL; - } + int64_t sn_old = knot_zone_serial(zone); /* Process all RRs the Authority (Update) section. */ dbg_ddns("Processing UPDATE section.\n"); + knot_rrset_t *soa_end = NULL; int apex_ns_rem = 0; const knot_pktsection_t *authority = knot_pkt_section(query, KNOT_AUTHORITY); for (uint16_t i = 0; i < authority->count; ++i) { @@ -1007,7 +999,7 @@ int knot_ddns_process_update(knot_zone_contents_t *zone, return ret; } - if (skip_soa(rr, sn)) { + if (skip_soa(rr, sn_old)) { continue; } @@ -1023,8 +1015,7 @@ int knot_ddns_process_update(knot_zone_contents_t *zone, knot_rrset_free(&soa_end, NULL); } int64_t sn_rr = knot_rrs_soa_serial(&rr->rrs); - assert(knot_serial_compare(sn_rr, sn) > 0); - sn_new = sn_rr; + assert(knot_serial_compare(sn_rr, sn_old) > 0); soa_end = knot_rrset_cpy(rr, NULL); if (soa_end == NULL) { return KNOT_ENOMEM; @@ -1044,8 +1035,9 @@ int knot_ddns_process_update(knot_zone_contents_t *zone, *rcode = KNOT_RCODE_SERVFAIL; return KNOT_ENOMEM; } - knot_rrs_soa_serial_set(&soa_end->rrs, sn_new); + knot_rrs_soa_serial_set(&soa_end->rrs, new_serial); } - return knot_ddns_final_soa_to_chgset(soa_end, changeset); + knot_changeset_add_soa(changeset, soa_end, KNOT_CHANGESET_ADD); + return KNOT_EOK; } diff --git a/src/knot/updates/ddns.h b/src/knot/updates/ddns.h index 7cccd47c6f3495ce029179f373c25d85ca54345c..67652615e95ed3f33213d78cbc73878898457ffb 100644 --- a/src/knot/updates/ddns.h +++ b/src/knot/updates/ddns.h @@ -2,6 +2,7 @@ * \file ddns.h * * \author Lubos Slovak <lubos.slovak@nic.cz> + * \author Jan Kadlec <jan.kadlec@nic.cz> * * \brief Dynamic updates processing. * @@ -30,17 +31,34 @@ #include "knot/updates/changesets.h" #include "knot/zone/zone.h" #include "libknot/packet/pkt.h" -#include "libknot/rrset.h" #include "libknot/dname.h" -#include "libknot/consts.h" -#include "common/lists.h" -int knot_ddns_check_zone(const knot_zone_contents_t *zone, - const knot_pkt_t *query, uint16_t *rcode); - -int knot_ddns_process_prereqs(const knot_pkt_t *query, const knot_zone_contents_t *zone, uint16_t *rcode); +/*! + * \brief Checks update prerequisite section. + * + * \param query DNS message containing the update. + * \param zone Zone to be checked. + * \param rcode Returned DNS RCODE. + * + * \return KNOT_E* + */ +int knot_ddns_process_prereqs(const knot_pkt_t *query, + const knot_zone_contents_t *zone, + uint16_t *rcode); -int knot_ddns_process_update(knot_zone_contents_t *zone, +/*! + * \brief Processes DNS update and creates a changeset out of it. Zone is left + * intact. + * + * \param zone Zone to be updated. + * \param query DNS message containing the update. + * \param changeset Output changeset. + * \param rcode Output DNS RCODE. + * \param new_serial New serial to use for updated zone. + * + * \return KNOT_E* + */ +int knot_ddns_process_update(const knot_zone_contents_t *zone, const knot_pkt_t *query, knot_changeset_t *changeset, uint16_t *rcode, uint32_t new_serial);