diff options
Diffstat (limited to '')
-rw-r--r-- | src/knot/catalog/generate.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/src/knot/catalog/generate.c b/src/knot/catalog/generate.c new file mode 100644 index 0000000..e05442b --- /dev/null +++ b/src/knot/catalog/generate.c @@ -0,0 +1,346 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <string.h> + +#include "knot/catalog/generate.h" +#include "knot/common/log.h" +#include "knot/updates/zone-update.h" +#include "knot/zone/zonedb.h" +#include "contrib/openbsd/siphash.h" +#include "contrib/wire_ctx.h" + +static knot_dname_t *catalog_member_owner(const knot_dname_t *member, + const knot_dname_t *catzone, + time_t member_time) +{ + SIPHASH_CTX hash; + SIPHASH_KEY shkey = { 0 }; // only used for hashing -> zero key + SipHash24_Init(&hash, &shkey); + SipHash24_Update(&hash, member, knot_dname_size(member)); + uint64_t u64time = htobe64(member_time); + SipHash24_Update(&hash, &u64time, sizeof(u64time)); + uint64_t hashres = SipHash24_End(&hash); + + char *hexhash = bin_to_hex((uint8_t *)&hashres, sizeof(hashres), false); + if (hexhash == NULL) { + return NULL; + } + size_t hexlen = strlen(hexhash); + assert(hexlen == 16); + size_t zoneslen = knot_dname_size((uint8_t *)CATALOG_ZONES_LABEL); + assert(hexlen <= KNOT_DNAME_MAXLABELLEN && zoneslen <= KNOT_DNAME_MAXLABELLEN); + size_t catzlen = knot_dname_size(catzone); + + size_t outlen = hexlen + zoneslen + catzlen; + knot_dname_t *out; + if (outlen > KNOT_DNAME_MAXLEN || (out = malloc(outlen)) == NULL) { + free(hexhash); + return NULL; + } + + wire_ctx_t wire = wire_ctx_init(out, outlen); + wire_ctx_write_u8(&wire, hexlen); + wire_ctx_write(&wire, hexhash, hexlen); + wire_ctx_write(&wire, CATALOG_ZONES_LABEL, zoneslen); + wire_ctx_skip(&wire, -1); + wire_ctx_write(&wire, catzone, catzlen); + assert(wire.error == KNOT_EOK); + + free(hexhash); + return out; +} + +static bool same_group(zone_t *old_z, zone_t *new_z) +{ + if (old_z->catalog_group == NULL || new_z->catalog_group == NULL) { + return (old_z->catalog_group == new_z->catalog_group); + } else { + return (strcmp(old_z->catalog_group, new_z->catalog_group) == 0); + } +} + +void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old) +{ + // general comment: catz->contents!=NULL means incremental update of catalog + + if (db_old != NULL) { + knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old); + while (!knot_zonedb_iter_finished(it)) { + zone_t *zone = knot_zonedb_iter_val(it); + knot_dname_t *cg = zone->catalog_gen; + if (cg != NULL && knot_zonedb_find(db_new, zone->name) == NULL) { + zone_t *catz = knot_zonedb_find(db_new, cg); + if (catz != NULL && catz->contents != NULL) { + assert(catz->cat_members != NULL); // if this failed to allocate, catz wasn't added to zonedb + knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); + if (owner == NULL) { + catz->cat_members->error = KNOT_ENOENT; + knot_zonedb_iter_next(it); + continue; + } + int ret = catalog_update_add(catz->cat_members, zone->name, owner, + cg, CAT_UPD_REM, NULL, 0, NULL); + free(owner); + if (ret != KNOT_EOK) { + catz->cat_members->error = ret; + } else { + zone_events_schedule_now(catz, ZONE_EVENT_LOAD); + } + } + } + knot_zonedb_iter_next(it); + } + knot_zonedb_iter_free(it); + } + + knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_new); + while (!knot_zonedb_iter_finished(it)) { + zone_t *zone = knot_zonedb_iter_val(it); + knot_dname_t *cg = zone->catalog_gen; + if (cg == NULL) { + knot_zonedb_iter_next(it); + continue; + } + zone_t *catz = knot_zonedb_find(db_new, cg); + zone_t *old = knot_zonedb_find(db_old, zone->name); + knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); + size_t cgroup_size = zone->catalog_group == NULL ? 0 : strlen(zone->catalog_group); + if (catz == NULL) { + log_zone_warning(zone->name, "member zone belongs to non-existing catalog zone"); + } else if (catz->contents == NULL || old == NULL) { + assert(catz->cat_members != NULL); + if (owner == NULL) { + catz->cat_members->error = KNOT_ENOENT; + knot_zonedb_iter_next(it); + continue; + } + int ret = catalog_update_add(catz->cat_members, zone->name, owner, + cg, CAT_UPD_ADD, zone->catalog_group, + cgroup_size, NULL); + if (ret != KNOT_EOK) { + catz->cat_members->error = ret; + } else { + zone_events_schedule_now(catz, ZONE_EVENT_LOAD); + } + } else if (!same_group(zone, old)) { + int ret = catalog_update_add(catz->cat_members, zone->name, owner, + cg, CAT_UPD_PROP, zone->catalog_group, + cgroup_size, NULL); + if (ret != KNOT_EOK) { + catz->cat_members->error = ret; + } else { + zone_events_schedule_now(catz, ZONE_EVENT_LOAD); + } + } + free(owner); + knot_zonedb_iter_next(it); + } + knot_zonedb_iter_free(it); +} + +static void set_rdata(knot_rrset_t *rrset, uint8_t *data, uint16_t len) +{ + knot_rdata_init(rrset->rrs.rdata, len, data); + rrset->rrs.size = knot_rdata_size(len); +} + +#define def_txt_owner(ptr_owner) \ + knot_dname_storage_t txt_owner = "\x05""group"; \ + size_t _ptr_ow_len = knot_dname_size(ptr_owner); \ + size_t _ptr_ow_ind = strlen((const char *)txt_owner); \ + if (_ptr_ow_ind + _ptr_ow_len > sizeof(txt_owner)) { \ + return KNOT_ERANGE; \ + } \ + memcpy(txt_owner + _ptr_ow_ind, (ptr_owner), _ptr_ow_len); + +static int add_group_txt(const knot_dname_t *ptr_owner, const char *group, + zone_contents_t *conts, zone_update_t *up) +{ + assert((conts == NULL) != (up == NULL)); + size_t group_len; + if (group == NULL || (group_len = strlen(group)) < 1) { + return KNOT_EOK; + } + assert(group_len <= 255); + + def_txt_owner(ptr_owner); + + uint8_t data[256] = { group_len }; + memcpy(data + 1, group, group_len); + + knot_rrset_t txt; + knot_rrset_init(&txt, txt_owner, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 0); + uint8_t txt_rd[256] = { 0 }; + txt.rrs.rdata = (knot_rdata_t *)txt_rd; + txt.rrs.count = 1; + set_rdata(&txt, data, 1 + group_len ); + + int ret; + if (conts != NULL) { + zone_node_t *unused = NULL; + ret = zone_contents_add_rr(conts, &txt, &unused); + } else { + ret = zone_update_add(up, &txt); + } + + return ret; +} + +static int rem_group_txt(const knot_dname_t *ptr_owner, zone_update_t *up) +{ + def_txt_owner(ptr_owner); + + int ret = zone_update_remove_rrset(up, txt_owner, KNOT_RRTYPE_TXT); + if (ret == KNOT_ENOENT || ret == KNOT_ENONODE) { + ret = KNOT_EOK; + } + + return ret; +} + +struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone, + uint32_t soa_serial) +{ + if (u->error != KNOT_EOK) { + return NULL; + } + zone_contents_t *c = zone_contents_new(catzone, true); + if (c == NULL) { + return c; + } + + zone_node_t *unused = NULL; + uint8_t invalid[9] = "\x07""invalid"; + uint8_t version[9] = "\x07""version"; + uint8_t cat_version[2] = "\x01" CATALOG_ZONE_VERSION; + + // prepare common rrset with one rdata item + uint8_t rdata[256] = { 0 }; + knot_rrset_t rrset; + knot_rrset_init(&rrset, (knot_dname_t *)catzone, KNOT_RRTYPE_SOA, KNOT_CLASS_IN, 0); + rrset.rrs.rdata = (knot_rdata_t *)rdata; + rrset.rrs.count = 1; + + // set catalog zone's SOA + uint8_t data[250]; + assert(sizeof(knot_rdata_t) + sizeof(data) <= sizeof(rdata)); + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); + wire_ctx_write(&wire, invalid, sizeof(invalid)); + wire_ctx_write(&wire, invalid, sizeof(invalid)); + wire_ctx_write_u32(&wire, soa_serial); + wire_ctx_write_u32(&wire, CATALOG_SOA_REFRESH); + wire_ctx_write_u32(&wire, CATALOG_SOA_RETRY); + wire_ctx_write_u32(&wire, CATALOG_SOA_EXPIRE); + wire_ctx_write_u32(&wire, 0); + set_rdata(&rrset, data, wire_ctx_offset(&wire)); + if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { + goto fail; + } + + // set catalog zone's NS + unused = NULL; + rrset.type = KNOT_RRTYPE_NS; + set_rdata(&rrset, invalid, sizeof(invalid)); + if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { + goto fail; + } + + // set catalog zone's version TXT + unused = NULL; + knot_dname_storage_t owner; + if (knot_dname_store(owner, version) == 0 || catalog_dname_append(owner, catzone) == 0) { + goto fail; + } + rrset.owner = owner; + rrset.type = KNOT_RRTYPE_TXT; + set_rdata(&rrset, cat_version, sizeof(cat_version)); + if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { + goto fail; + } + + // insert member zone PTR records + rrset.type = KNOT_RRTYPE_PTR; + catalog_it_t *it = catalog_it_begin(u); + while (!catalog_it_finished(it)) { + catalog_upd_val_t *val = catalog_it_val(it); + if (val->add_owner == NULL) { + continue; + } + rrset.owner = val->add_owner; + set_rdata(&rrset, val->member, knot_dname_size(val->member)); + unused = NULL; + if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK || + add_group_txt(val->add_owner, val->new_group, c, NULL) != KNOT_EOK) { + catalog_it_free(it); + goto fail; + } + catalog_it_next(it); + } + catalog_it_free(it); + + return c; +fail: + zone_contents_deep_free(c); + return NULL; +} + +int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu) +{ + knot_rrset_t ptr; + knot_rrset_init(&ptr, NULL, KNOT_RRTYPE_PTR, KNOT_CLASS_IN, 0); + uint8_t tmp[KNOT_DNAME_MAXLEN + sizeof(knot_rdata_t)]; + ptr.rrs.rdata = (knot_rdata_t *)tmp; + ptr.rrs.count = 1; + + int ret = u->error; + catalog_it_t *it = catalog_it_begin(u); + while (!catalog_it_finished(it) && ret == KNOT_EOK) { + catalog_upd_val_t *val = catalog_it_val(it); + if (val->type == CAT_UPD_INVALID) { + catalog_it_next(it); + continue; + } + + if (val->type == CAT_UPD_PROP && knot_dname_is_equal(zu->zone->name, val->add_catz)) { + ret = rem_group_txt(val->add_owner, zu); + if (ret == KNOT_EOK) { + ret = add_group_txt(val->add_owner, val->new_group, NULL, zu); + } + catalog_it_next(it); + continue; + } + + set_rdata(&ptr, val->member, knot_dname_size(val->member)); + if (val->type == CAT_UPD_REM && knot_dname_is_equal(zu->zone->name, val->rem_catz)) { + ptr.owner = val->rem_owner; + ret = zone_update_remove(zu, &ptr); + if (ret == KNOT_EOK) { + ret = rem_group_txt(val->rem_owner, zu); + } + } + if (val->type == CAT_UPD_ADD && knot_dname_is_equal(zu->zone->name, val->add_catz)) { + ptr.owner = val->add_owner; + ret = zone_update_add(zu, &ptr); + if (ret == KNOT_EOK) { + ret = add_group_txt(val->add_owner, val->new_group, NULL, zu); + } + } + catalog_it_next(it); + } + catalog_it_free(it); + return ret; +} |