diff --git a/.gitignore b/.gitignore index e8d7aa4873..65540e2f90 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ unit/atf-src/test-programs/sh_helpers # ccc-analyzer store its results in .plist directories *.plist/ *~ +.project +.cproject +.settings diff --git a/CHANGES b/CHANGES index 8641170c45..e69d2f8d5d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +4376. [experimental] Added support for Catalog Zones, a new method for + provisioning secondary servers in which a list of + zones to be served is stored in a DNS zone and can + be propagated to slaves via AXFR/IXFR. [RT #41581] + +4375. [func] Add support for automatic reallocation of isc_buffer + to isc_buffer_put* functions. [RT #42394] + 4374. [bug] Use SAVE/RESTORE macros in query.c to reduce the probability of reference counting errors as seen in 4365. [RT #42405] diff --git a/bin/named/config.c b/bin/named/config.c index e74ebbfef5..fb061e7d3d 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -576,9 +576,7 @@ get_masters_def(const cfg_obj_t *cctx, const char *name, isc_result_t ns_config_getipandkeylist(const cfg_obj_t *config, const cfg_obj_t *list, - isc_mem_t *mctx, isc_sockaddr_t **addrsp, - isc_dscp_t **dscpsp, dns_name_t ***keysp, - isc_uint32_t *countp) + isc_mem_t *mctx, dns_ipkeylist_t *ipkl) { isc_uint32_t addrcount = 0, dscpcount = 0, keycount = 0, i = 0; isc_uint32_t listcount = 0, l = 0, j; @@ -601,10 +599,10 @@ ns_config_getipandkeylist(const cfg_obj_t *config, const cfg_obj_t *list, isc_dscp_t dscp; } *stack = NULL; - REQUIRE(addrsp != NULL && *addrsp == NULL); - REQUIRE(dscpsp != NULL && *dscpsp == NULL); - REQUIRE(keysp != NULL && *keysp == NULL); - REQUIRE(countp != NULL); + REQUIRE(ipkl != NULL); + REQUIRE(ipkl->addrs == NULL); + REQUIRE(ipkl->keys == NULL); + REQUIRE(ipkl->dscps == NULL); /* * Get system defaults. @@ -857,10 +855,10 @@ ns_config_getipandkeylist(const cfg_obj_t *config, const cfg_obj_t *list, INSIST(keycount == addrcount); - *addrsp = addrs; - *dscpsp = dscps; - *keysp = keys; - *countp = addrcount; + ipkl->addrs = addrs; + ipkl->dscps = dscps; + ipkl->keys = keys; + ipkl->count = addrcount; return (ISC_R_SUCCESS); @@ -886,37 +884,6 @@ ns_config_getipandkeylist(const cfg_obj_t *config, const cfg_obj_t *list, return (result); } -void -ns_config_putipandkeylist(isc_mem_t *mctx, isc_sockaddr_t **addrsp, - isc_dscp_t **dscpsp, dns_name_t ***keysp, - isc_uint32_t count) -{ - unsigned int i; - dns_name_t **keys; - - REQUIRE(addrsp != NULL && *addrsp != NULL); - REQUIRE(dscpsp == NULL || *dscpsp != NULL); - REQUIRE(keysp != NULL && *keysp != NULL); - - keys = *keysp; - - isc_mem_put(mctx, *addrsp, count * sizeof(isc_sockaddr_t)); - if (dscpsp != NULL) - isc_mem_put(mctx, *dscpsp, count * sizeof(isc_dscp_t)); - for (i = 0; i < count; i++) { - if (keys[i] == NULL) - continue; - if (dns_name_dynamic(keys[i])) - dns_name_free(keys[i], mctx); - isc_mem_put(mctx, keys[i], sizeof(dns_name_t)); - } - isc_mem_put(mctx, *keysp, count * sizeof(dns_name_t *)); - *addrsp = NULL; - if (dscpsp != NULL) - *dscpsp = NULL; - *keysp = NULL; -} - isc_result_t ns_config_getport(const cfg_obj_t *config, in_port_t *portp) { const cfg_obj_t *maps[3]; diff --git a/bin/named/include/named/config.h b/bin/named/include/named/config.h index 5302c9b29f..f051bfeca3 100644 --- a/bin/named/include/named/config.h +++ b/bin/named/include/named/config.h @@ -31,11 +31,11 @@ isc_result_t ns_config_parsedefaults(cfg_parser_t *parser, cfg_obj_t **conf); isc_result_t -ns_config_get(cfg_obj_t const * const *maps, const char* name, +ns_config_get(cfg_obj_t const * const *maps, const char *name, const cfg_obj_t **obj); isc_result_t -ns_checknames_get(const cfg_obj_t **maps, const char* name, +ns_checknames_get(const cfg_obj_t **maps, const char *name, const cfg_obj_t **obj); int @@ -64,14 +64,7 @@ ns_config_putiplist(isc_mem_t *mctx, isc_sockaddr_t **addrsp, isc_result_t ns_config_getipandkeylist(const cfg_obj_t *config, const cfg_obj_t *list, - isc_mem_t *mctx, isc_sockaddr_t **addrsp, - isc_dscp_t **dscpp, dns_name_t ***keys, - isc_uint32_t *countp); - -void -ns_config_putipandkeylist(isc_mem_t *mctx, isc_sockaddr_t **addrsp, - isc_dscp_t **dscpsp, dns_name_t ***keys, - isc_uint32_t count); + isc_mem_t *mctx, dns_ipkeylist_t *ipkl); isc_result_t ns_config_getport(const cfg_obj_t *config, in_port_t *portp); diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index b919a628c7..932afea19d 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -283,6 +283,16 @@ options { check-mx-cname ( fail | warn | ignore ); check-srv-cname ( fail | warn | ignore ); cache-file quoted_string; // test option + catalog-zones { + zone quoted_string + default-masters + port ip_port + dscp ip_dscp + { ( masters_list | ip_addr port ip_port key key ) ; ... } + in-memory yes_or_no + min-update-interval interval + ; ... }; + ; suppress-initial-notify boolean; // not yet implemented preferred-glue string; dual-stack-servers port integer { diff --git a/bin/named/server.c b/bin/named/server.c index f06678d078..58ab7c06be 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -65,11 +65,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -275,6 +277,19 @@ typedef struct { isc_refcount_t refs; } ns_zoneload_t; +typedef struct { + ns_server_t *server; +} catz_cb_data_t; + +typedef struct catz_chgzone_event { + ISC_EVENT_COMMON(struct catz_chgzone_event); + dns_catz_entry_t *entry; + dns_catz_zone_t *origin; + dns_view_t *view; + catz_cb_data_t *cbd; + isc_boolean_t mod; +} catz_chgzone_event_t; + /* * These zones should not leak onto the Internet. */ @@ -1990,12 +2005,439 @@ configure_rpz(dns_view_t *view, const cfg_obj_t *rpz_obj, "updated RPZ policy: version %d", view->rpzs->rpz_ver); } + if (pview != NULL) dns_view_detach(&pview); return (ISC_R_SUCCESS); } +static void +catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { + catz_chgzone_event_t *ev = (catz_chgzone_event_t *) event0; + isc_result_t result; + isc_buffer_t namebuf; + isc_buffer_t *confbuf; + char nameb[DNS_NAME_FORMATSIZE]; + const cfg_obj_t *zlist = NULL; + cfg_obj_t *zoneconf = NULL; + cfg_obj_t *zoneobj = NULL; + ns_cfgctx_t *cfg; + dns_zone_t *zone = NULL; + + cfg = (ns_cfgctx_t *) ev->view->new_zone_config; + if (cfg == NULL) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: allow-new-zones statement missing from " + "config; cannot add zone from the catalog"); + goto cleanup; + } + + isc_buffer_init(&namebuf, nameb, DNS_NAME_FORMATSIZE); + dns_name_totext(dns_catz_entry_getname(ev->entry), ISC_TRUE, &namebuf); + isc_buffer_putuint8(&namebuf, 0); + + /* Zone shouldn't already exist */ + result = dns_zt_find(ev->view->zonetable, + dns_catz_entry_getname(ev->entry), 0, NULL, &zone); + if (result == ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: zone \"%s\" already exists", nameb); + goto cleanup; + } else if (result == DNS_R_PARTIALMATCH) { + /* Create our sub-zone anyway */ + dns_zone_detach(&zone); + zone = NULL; + } else if (result != ISC_R_NOTFOUND) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: error \"%s\" while trying to " + "add zone \"%s\"", + isc_result_totext(result), nameb); + goto cleanup; + } + + /* Create a config for new zone */ + confbuf = NULL; + dns_catz_generate_zonecfg(ev->origin, ev->entry, &confbuf); + cfg_parser_reset(cfg->add_parser); + result = cfg_parse_buffer2(cfg->add_parser, confbuf, "catz", + &cfg_type_addzoneconf, &zoneconf); + isc_buffer_free(&confbuf); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: error \"%s\" while trying to generate " + "config for zone \"%s\"", + isc_result_totext(result), nameb); + goto cleanup; + } + CHECK(cfg_map_get(zoneconf, "zone", &zlist)); + if (!cfg_obj_islist(zlist)) + CHECK(ISC_R_FAILURE); + + /* For now we only support adding one zone at a time */ + zoneobj = cfg_listelt_value(cfg_list_first(zlist)); + + /* Mark view unfrozen so that zone can be added */ + + result = isc_task_beginexclusive(task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_view_thaw(ev->view); + result = configure_zone(cfg->config, zoneobj, cfg->vconfig, + ev->cbd->server->mctx, ev->view, NULL, + cfg->actx, ISC_TRUE, ISC_FALSE, ev->mod); + dns_view_freeze(ev->view); + isc_task_endexclusive(task); + + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: failed to configure zone \"%s\" - %d", + nameb, result); + goto cleanup; + } + + /* Is it there yet? */ + CHECK(dns_zt_find(ev->view->zonetable, + dns_catz_entry_getname(ev->entry), 0, NULL, &zone)); + + /* + * Load the zone from the master file. If this fails, we'll + * need to undo the configuration we've done already. + */ + result = dns_zone_loadnew(zone); + if (result != ISC_R_SUCCESS) { + dns_db_t *dbp = NULL; + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: dns_zone_loadnew() failed " + "with %s; reverting.", + isc_result_totext(result)); + + /* If the zone loaded partially, unload it */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + /* Remove the zone from the zone table */ + dns_zt_unmount(ev->view->zonetable, zone); + goto cleanup; + } + + /* Flag the zone as having been added at runtime */ + dns_zone_setadded(zone, ISC_TRUE); + + cleanup: + if (zone != NULL) + dns_zone_detach(&zone); + if (zoneconf != NULL) + cfg_obj_destroy(cfg->add_parser, &zoneconf); + dns_catz_entry_detach(ev->origin, &ev->entry); + dns_catz_zone_detach(&ev->origin); + dns_view_detach(&ev->view); + isc_event_free(ISC_EVENT_PTR(&ev)); +} + +static void +catz_delzone_taskaction(isc_task_t *task, isc_event_t *event0) { + catz_chgzone_event_t *ev = (catz_chgzone_event_t *) event0; + isc_result_t result; + dns_zone_t *zone = NULL; + dns_db_t *dbp = NULL; + char cname[DNS_NAME_FORMATSIZE]; + const char * file; + + result = isc_task_beginexclusive(task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_name_format(dns_catz_entry_getname(ev->entry), cname, + DNS_NAME_FORMATSIZE); + result = dns_zt_find(ev->view->zonetable, + dns_catz_entry_getname(ev->entry), 0, NULL, &zone); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' not found", cname); + goto cleanup; + } + + /* TODO make other flag for CZ zones */ + /* TODO2 make sure that we delete only 'own' zones */ + if (!dns_zone_getadded(zone)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' is not a dynamically added zone", + cname); + goto cleanup; + } + + /* Stop answering for this zone */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + CHECK(dns_zt_unmount(ev->view->zonetable, zone)); + file = dns_zone_getfile(zone); + isc_file_remove(file); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' deleted", cname); + cleanup: + isc_task_endexclusive(task); + if (zone != NULL) + dns_zone_detach(&zone); + dns_catz_entry_detach(ev->origin, &ev->entry); + dns_catz_zone_detach(&ev->origin); + dns_view_detach(&ev->view); + isc_event_free(ISC_EVENT_PTR(&ev)); +} + +static isc_result_t +catz_create_chg_task(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata, + isc_eventtype_t type) +{ + catz_chgzone_event_t *event; + isc_task_t *task; + isc_result_t result; + isc_taskaction_t action; + + switch (type) { + case DNS_EVENT_CATZADDZONE: + case DNS_EVENT_CATZMODZONE: + action = catz_addmodzone_taskaction; + break; + case DNS_EVENT_CATZDELZONE: + action = catz_delzone_taskaction; + break; + default: + REQUIRE(0); + } + + event = (catz_chgzone_event_t *) isc_event_allocate(view->mctx, origin, + type, action, NULL, + sizeof(*event)); + if (event == NULL) + return (ISC_R_NOMEMORY); + + event->cbd = (catz_cb_data_t *) udata; + event->entry = NULL; + event->origin = NULL; + event->view = NULL; + event->mod = ISC_TF(type == DNS_EVENT_CATZMODZONE); + dns_catz_entry_attach(entry, &event->entry); + dns_catz_zone_attach(origin, &event->origin); + dns_view_attach(view, &event->view); + + task = NULL; + result = isc_taskmgr_excltask(taskmgr, &task); + REQUIRE(result == ISC_R_SUCCESS); + isc_task_send(task, ISC_EVENT_PTR(&event)); + isc_task_detach(&task); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_addzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata) +{ + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZADDZONE)); +} + +static isc_result_t +catz_delzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata) +{ + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZDELZONE)); +} + +static isc_result_t +catz_modzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata) +{ + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZMODZONE)); +} + +static isc_result_t +configure_catz_zone(dns_view_t *view, const cfg_obj_t *config, + const cfg_listelt_t *element) +{ + const cfg_obj_t *catz_obj, *obj; + dns_catz_zone_t *zone = NULL; + const char *str; + isc_result_t result; + dns_name_t origin; + dns_catz_options_t *opts; + dns_view_t *pview = NULL; + + dns_name_init(&origin, NULL); + catz_obj = cfg_listelt_value(element); + + str = cfg_obj_asstring(cfg_tuple_get(catz_obj, "zone name")); + + result = dns_name_fromstring(&origin, str, DNS_NAME_DOWNCASE, + view->mctx); + if (result == ISC_R_SUCCESS && dns_name_equal(&origin, dns_rootname)) + result = DNS_R_EMPTYLABEL; + + if (result != ISC_R_SUCCESS) { + cfg_obj_log(catz_obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: invalid zone name '%s'", str); + goto cleanup; + } + + result = dns_catz_add_zone(view->catzs, &origin, &zone); + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + cfg_obj_log(catz_obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: unable to create catalog zone '%s', " + "error %s", + str, isc_result_totext(result)); + goto cleanup; + } + + if (result == ISC_R_EXISTS) { + isc_ht_iter_t *it = NULL; + + result = dns_viewlist_find(&ns_g_server->viewlist, + view->name, + view->rdclass, &pview); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * xxxwpk todo: reconfigure the zone!!!! + */ + cfg_obj_log(catz_obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: catalog zone '%s' will not be reconfigured", + str); + /* + * We have to walk through all the member zones and attach + * them to current view + */ + result = dns_catz_get_iterator(zone, &it); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(catz_obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: unable to create iterator"); + goto cleanup; + } + + for (result = isc_ht_iter_first(it); + result == ISC_R_SUCCESS; + result = isc_ht_iter_next(it)) + { + dns_name_t *name = NULL; + dns_zone_t *dnszone = NULL; + dns_catz_entry_t *entry = NULL; + isc_result_t tresult; + + isc_ht_iter_current(it, (void **) &entry); + name = dns_catz_entry_getname(entry); + + tresult = dns_view_findzone(pview, name, &dnszone); + RUNTIME_CHECK(tresult == ISC_R_SUCCESS); + + dns_zone_setview(dnszone, view); + if (view->acache != NULL) + dns_zone_setacache(dnszone, view->acache); + dns_view_addzone(view, dnszone); + } + + isc_ht_iter_destroy(&it); + + result = ISC_R_SUCCESS; + } + + dns_catz_zone_resetdefoptions(zone); + opts = dns_catz_zone_getdefoptions(zone); + + obj = cfg_tuple_get(catz_obj, "default-masters"); + if (obj != NULL) + result = ns_config_getipandkeylist(config, obj, + view->mctx, &opts->masters); + + obj = cfg_tuple_get(catz_obj, "in-memory"); + if (obj != NULL && cfg_obj_isboolean(obj)) + opts->in_memory = cfg_obj_asboolean(obj); + + obj = cfg_tuple_get(catz_obj, "min-update-interval"); + if (obj != NULL && cfg_obj_isuint32(obj)) + opts->min_update_interval = cfg_obj_asuint32(obj); + + cleanup: + if (pview != NULL) + dns_view_detach(&pview); + dns_name_free(&origin, view->mctx); + + return (result); +} + +static catz_cb_data_t ns_catz_cbdata; +static dns_catz_zonemodmethods_t ns_catz_zonemodmethods = { + catz_addzone, + catz_modzone, + catz_delzone, + &ns_catz_cbdata +}; + +static isc_result_t +configure_catz(dns_view_t *view, const cfg_obj_t *config, + const cfg_obj_t *catz_obj) +{ + const cfg_listelt_t *zone_element; + const dns_catz_zones_t *old = NULL; + dns_view_t *pview = NULL; + isc_result_t result; + + /* xxxwpk TODO do it cleaner, once, somewhere */ + ns_catz_cbdata.server = ns_g_server; + + zone_element = cfg_list_first(cfg_tuple_get(catz_obj, "zone list")); + if (zone_element == NULL) + return (ISC_R_SUCCESS); + + CHECK(dns_catz_new_zones(&view->catzs, &ns_catz_zonemodmethods, + view->mctx, ns_g_taskmgr, ns_g_timermgr)); + + result = dns_viewlist_find(&ns_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result == ISC_R_SUCCESS) + old = pview->catzs; + + if (old != NULL) { + dns_catz_catzs_detach(&view->catzs); + dns_catz_catzs_attach(pview->catzs, &view->catzs); + dns_catz_prereconfig(view->catzs); + } + + while (zone_element != NULL) { + CHECK(configure_catz_zone(view, config, zone_element)); + zone_element = cfg_list_next(zone_element); + } + + if (old != NULL) + dns_catz_postreconfig(view->catzs); + + result = ISC_R_SUCCESS; + + cleanup: + if (pview != NULL) + dns_view_detach(&pview); + + return (result); +} + #define CHECK_RRL(cond, pat, val1, val2) \ do { \ if (!(cond)) { \ @@ -2693,6 +3135,12 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, CHECK(configure_rpz(view, obj, &old_rpz_ok)); } + obj = NULL; + if (view->rdclass == dns_rdataclass_in && need_hints && + ns_config_get(maps, "catalog-zones", &obj) == ISC_R_SUCCESS) { + CHECK(configure_catz(view, config, obj)); + } + /* * Configure the zones. */ @@ -4574,6 +5022,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, dns_rdataclass_t zclass; const char *ztypestr; dns_rpz_num_t rpz_num; + isc_boolean_t zone_is_catz = ISC_FALSE; options = NULL; (void)cfg_map_get(config, "options", &options); @@ -4801,6 +5250,10 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, break; } + if (view->catzs != NULL && + dns_catz_get_zone(view->catzs, origin) != NULL) + zone_is_catz = ISC_TRUE; + /* * See if we can reuse an existing zone. This is * only possible if all of these are true: @@ -4850,7 +5303,6 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, CHECK(dns_zonemgr_managezone(ns_g_server->zonemgr, zone)); dns_zone_setstats(zone, ns_g_server->zonestats); } - if (rpz_num != DNS_RPZ_INVALID_NUM) { result = dns_zone_rpz_enable(zone, view->rpzs, rpz_num); if (result != ISC_R_SUCCESS) { @@ -4864,6 +5316,9 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, } } + if (zone_is_catz) + dns_zone_catz_enable(zone, view->catzs); + /* * If the zone contains a 'forwarders' statement, configure * selective forwarding. @@ -4919,7 +5374,19 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, * Add the zone to its view in the new view list. */ if (!modify) - CHECK(dns_view_addzone(view, zone)); + CHECK(dns_view_addzone(view, zone)); + + if (zone_is_catz) { + /* + * force catz reload if the zone is loaded; + * if it's not it'll get reloaded on zone load + */ + dns_db_t *db = NULL; + + tresult = dns_zone_getdb(zone, &db); + if (tresult == ISC_R_SUCCESS) + dns_catz_dbupdate_callback(db, view->catzs); + } /* * Ensure that zone keys are reloaded on reconfig @@ -5684,6 +6151,20 @@ setup_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, if (result == ISC_R_SUCCESS) allow = cfg_obj_asboolean(nz); + /* + * A non-empty catalog-zones statement implies allow-new-zones + */ + if (!allow) { + const cfg_obj_t *cz = NULL; + result = ns_config_get(maps, "catalog-zones", &cz); + if (result == ISC_R_SUCCESS) { + const cfg_listelt_t *e = + cfg_list_first(cfg_tuple_get(cz, "zone list")); + if (e != NULL) + allow = ISC_TRUE; + } + } + if (!allow) { dns_view_setnewzones(view, ISC_FALSE, NULL, NULL); return (ISC_R_SUCCESS); diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 335519a139..2d297a7c80 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -805,9 +806,6 @@ ns_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const char *filename = NULL; const char *dupcheck; dns_notifytype_t notifytype = dns_notifytype_yes; - isc_sockaddr_t *addrs; - isc_dscp_t *dscps; - dns_name_t **keynames; isc_uint32_t count; unsigned int dbargc; char **dbargv; @@ -1149,23 +1147,19 @@ ns_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, (notifytype == dns_notifytype_masteronly && ztype == dns_zone_master))) { - isc_uint32_t addrcount; - addrs = NULL; - keynames = NULL; - dscps = NULL; + dns_ipkeylist_t ipkl; + ipkl.count = 0; + ipkl.addrs = NULL; + ipkl.dscps = NULL; + ipkl.keys = NULL; RETERR(ns_config_getipandkeylist(config, obj, mctx, - &addrs, &dscps, - &keynames, - &addrcount)); - result = dns_zone_setalsonotifydscpkeys(zone, addrs, - dscps, keynames, - addrcount); - if (addrcount != 0) - ns_config_putipandkeylist(mctx, &addrs, &dscps, - &keynames, addrcount); - else - INSIST(addrs == NULL && dscps == NULL && - keynames == NULL); + &ipkl)); + result = dns_zone_setalsonotifydscpkeys(zone, + ipkl.addrs, + ipkl.dscps, + ipkl.keys, + ipkl.count); + dns_ipkeylist_clear(mctx, &ipkl); RETERR(result); } else RETERR(dns_zone_setalsonotify(zone, NULL, 0)); @@ -1628,19 +1622,19 @@ ns_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, obj = NULL; (void)cfg_map_get(zoptions, "masters", &obj); if (obj != NULL) { - addrs = NULL; - dscps = NULL; - keynames = NULL; + dns_ipkeylist_t ipkl; + ipkl.count = 0; + ipkl.addrs = NULL; + ipkl.dscps = NULL; + ipkl.keys = NULL; RETERR(ns_config_getipandkeylist(config, obj, mctx, - &addrs, &dscps, - &keynames, &count)); - result = dns_zone_setmasterswithkeys(mayberaw, addrs, - keynames, count); - if (count != 0) - ns_config_putipandkeylist(mctx, &addrs, &dscps, - &keynames, count); - else - INSIST(addrs == NULL && keynames == NULL); + &ipkl)); + result = dns_zone_setmasterswithkeys(mayberaw, + ipkl.addrs, + ipkl.keys, + ipkl.count); + dns_ipkeylist_clear(mctx, &ipkl); + RETERR(result); } else result = dns_zone_setmasters(mayberaw, NULL, 0); RETERR(result); diff --git a/bin/tests/system/catz/clean.sh b/bin/tests/system/catz/clean.sh new file mode 100644 index 0000000000..4e4bdc4316 --- /dev/null +++ b/bin/tests/system/catz/clean.sh @@ -0,0 +1,23 @@ +# Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +rm -f dig.out.* +rm -f nsupdate.out.* +rm -f ns*/named.memstats +rm -f ns*/named.run +rm -f ns*/named.lock +rm -f ns{1,2}/*dom*example.db +rm -f ns{1,2}/catalog.example.db +rm -f ns*/*.jnl +rm -f ns*/*.nzf diff --git a/bin/tests/system/catz/ns1/catalog.example.db.in b/bin/tests/system/catz/ns1/catalog.example.db.in new file mode 100644 index 0000000000..53013fe837 --- /dev/null +++ b/bin/tests/system/catz/ns1/catalog.example.db.in @@ -0,0 +1,3 @@ +@ 3600 SOA . . 1 86400 3600 86400 3600 +@ 3600 IN NS invalid. +version IN TXT "1" diff --git a/bin/tests/system/catz/ns1/named.conf b/bin/tests/system/catz/ns1/named.conf new file mode 100644 index 0000000000..db67cbfffa --- /dev/null +++ b/bin/tests/system/catz/ns1/named.conf @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011, 2013, 2016 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +include "../../common/rndc.key"; + +controls { + inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port 5300; + allow-new-zones yes; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + notify no; + recursion no; +}; + +zone "catalog.example" { + type master; + file "catalog.example.db"; + allow-transfer { any; }; + allow-update { any; }; + also-notify { 10.53.0.2; }; + notify explicit; +}; diff --git a/bin/tests/system/catz/ns2/named.conf b/bin/tests/system/catz/ns2/named.conf new file mode 100644 index 0000000000..9df04aeee0 --- /dev/null +++ b/bin/tests/system/catz/ns2/named.conf @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011, 2013, 2016 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +include "../../common/rndc.key"; + +controls { + inet 10.53.0.2 port 9953 allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + notify no; + recursion no; + serial-query-rate 100; + catalog-zones { + zone "catalog.example" default-masters { 10.53.0.1; }; + }; +}; + +zone "catalog.example" { + type slave; + file "catalog.example.db"; + masters { 10.53.0.1; }; +}; diff --git a/bin/tests/system/catz/ns3/dom4.example.db b/bin/tests/system/catz/ns3/dom4.example.db new file mode 100644 index 0000000000..4cbd501e07 --- /dev/null +++ b/bin/tests/system/catz/ns3/dom4.example.db @@ -0,0 +1,2 @@ +@ 3600 IN SOA . . 1 3600 3600 3600 3600 +@ IN NS invalid. diff --git a/bin/tests/system/catz/ns3/named.conf b/bin/tests/system/catz/ns3/named.conf new file mode 100644 index 0000000000..d4cb7954a4 --- /dev/null +++ b/bin/tests/system/catz/ns3/named.conf @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011, 2013, 2016 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +include "../../common/rndc.key"; + +controls { + inet 10.53.0.3 port 9953 allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + notify no; + recursion no; +}; + +zone "dom4.example" { + type master; + file "dom4.example.db"; + allow-transfer { any; }; + allow-update { any; }; + notify explicit; +}; diff --git a/bin/tests/system/catz/setup.sh b/bin/tests/system/catz/setup.sh new file mode 100644 index 0000000000..ece0f4f9e6 --- /dev/null +++ b/bin/tests/system/catz/setup.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# +# Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +$SHELL clean.sh + +cat ns1/catalog.example.db.in > ns1/catalog.example.db diff --git a/bin/tests/system/catz/tests.sh b/bin/tests/system/catz/tests.sh new file mode 100644 index 0000000000..77d54414b7 --- /dev/null +++ b/bin/tests/system/catz/tests.sh @@ -0,0 +1,305 @@ +#!/bin/sh -x +# +# Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +status=0 +n=0 + +n=`expr $n + 1` +echo "I:checking that dom1.example is not served by master ($n)" +ret=0 +$DIG soa dom1.example @10.53.0.1 -p 5300 > dig.out.test$n +grep "status: REFUSED" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:Adding a domain dom1.example to master via RNDC ($n)" +ret=0 +echo "@ 3600 IN SOA . . 1 3600 3600 3600 3600" > ns1/dom1.example.db +echo "@ IN NS invalid." >> ns1/dom1.example.db +$RNDC -c ../common/rndc.conf -s 10.53.0.1 -p 9953 addzone dom1.example '{type master; file "dom1.example.db";};' || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:checking that dom1.example is now served by master ($n)" +ret=0 +$DIG soa dom1.example @10.53.0.1 -p 5300 > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +cur=`awk 'BEGIN {l=0} /^/ {l++} END { print l }' ns2/named.run` + +n=`expr $n + 1` +echo "I:Adding domain dom1.example to catalog zone ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 5300 + update add e721433b6160b450260d4f54b3ec8bab30cb3b83.zones.catalog.example 3600 IN PTR dom1.example. + send +END +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:waiting for slave to sync up ($n)" +ret=1 +try=0 +while test $try -lt 45 +do + sleep 1 + sed -n "$cur,"'$p' < ns2/named.run | grep "catz: adding zone 'dom1.example' from catalog 'catalog.example'" > /dev/null && { + ret=0 + break + } + try=`expr $try + 1` +done +try=0 +while test $try -lt 45 +do + sleep 1 + sed -n "$cur,"'$p' < ns2/named.run | grep "transfer of 'dom1.example/IN' from 10.53.0.1#5300: Transfer status: success" > /dev/null && { + ret=0 + break + } + try=`expr $try + 1` +done +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:checking that dom1.example is served by slave ($n)" +ret=0 +$DIG soa dom1.example @10.53.0.2 -p 5300 > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:Removing domain dom1.example from catalog zone ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 5300 + update delete e721433b6160b450260d4f54b3ec8bab30cb3b83.zones.catalog.example + send +END +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:waiting for slave to sync up ($n)" +ret=1 +try=0 +while test $try -lt 45 +do + sleep 1 + sed -n "$cur,"'$p' < ns2/named.run | grep "catz: deleting zone 'dom1.example' from catalog 'catalog.example'" > /dev/null && { + ret=0 + break + } + try=`expr $try + 1` +done +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:checking that dom1.example is not served by slave ($n)" +ret=0 +$DIG soa dom1.example @10.53.0.2 -p 5300 > dig.out.test$n +grep "status: REFUSED" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:Adding a domain dom2.example to master via RNDC ($n)" +ret=0 +echo "@ 3600 IN SOA . . 1 3600 3600 3600 3600" > ns1/dom2.example.db +echo "@ IN NS invalid." >> ns1/dom2.example.db +$RNDC -c ../common/rndc.conf -s 10.53.0.1 -p 9953 addzone dom2.example '{type master; file "dom2.example.db";};' || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:Adding domains dom2.example, dom3.example and some trash to catalog zone ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 5300 + update add 636722929740e507aaf27c502812fc395d30fb17.zones.catalog.example 3600 IN PTR dom2.example. + update add b901f492f3ebf6c1e5b597e51766f02f0479eb03.zones.catalog.example 3600 IN PTR dom3.example. + update add e721433b6160b450260d4f54b3ec8bab30cb3b83.zones.catalog.example 3600 IN NS foo.bar. + update add trash.catalog.example 3600 IN A 1.2.3.4 + update add trash2.foo.catalog.example 3600 IN A 1.2.3.4 + update add trash3.zones.catalog.example 3600 IN NS a.dom2.example. + send +END +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:checking that dom3.example is not served by master ($n)" +ret=0 +$DIG soa dom3.example @10.53.0.1 -p 5300 > dig.out.test$n +grep "status: REFUSED" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:Adding a domain dom3.example to master via RNDC ($n)" +ret=0 +echo "@ 3600 IN SOA . . 1 3600 3600 3600 3600" > ns1/dom3.example.db +echo "@ IN NS invalid." >> ns1/dom3.example.db +$RNDC -c ../common/rndc.conf -s 10.53.0.1 -p 9953 addzone dom3.example '{type master; file "dom3.example.db"; also-notify { 10.53.0.2; }; };' || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:checking that dom3.example is served by master ($n)" +ret=0 +$DIG soa dom2.example @10.53.0.1 -p 5300 > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:waiting for slave to sync up ($n)" +ret=1 +try=0 +while test $try -lt 45 +do + sleep 1 + sed -n "$cur,"'$p' < ns2/named.run | grep "catz: adding zone 'dom3.example' from catalog 'catalog.example'" > /dev/null && { + ret=0 + break + } + try=`expr $try + 1` +done +try=0 +while test $try -lt 45 +do + sleep 1 + sed -n "$cur,"'$p' < ns2/named.run | grep "transfer of 'dom3.example/IN' from 10.53.0.1#5300: Transfer status: success" > /dev/null && { + ret=0 + break + } + try=`expr $try + 1` +done +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:checking that dom3.example is served by slave ($n)" +ret=0 +$DIG soa dom3.example @10.53.0.2 -p 5300 > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:Adding dom4.example with 'masters' defined and a random label ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 5300 + update add somerandomlabel.zones.catalog.example 3600 IN PTR dom4.example. + update add masters.somerandomlabel.zones.catalog.example 3600 IN A 10.53.0.3 + send +END +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:waiting for slave to sync up ($n)" +ret=1 +try=0 +while test $try -lt 45 +do + sleep 1 + sed -n "$cur,"'$p' < ns2/named.run | grep "catz: adding zone 'dom4.example' from catalog 'catalog.example'" > /dev/null && { + ret=0 + break + } + try=`expr $try + 1` +done +try=0 +while test $try -lt 45 +do + sleep 1 + sed -n "$cur,"'$p' < ns2/named.run | grep "transfer of 'dom4.example/IN' from 10.53.0.3#5300: Transfer status: success" > /dev/null && { + ret=0 + break + } + try=`expr $try + 1` +done +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:checking that dom4.example is served by slave ($n)" +ret=0 +$DIG soa dom4.example @10.53.0.2 -p 5300 > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:Removing domain dom2.example from catalog zone ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 5300 + update delete 636722929740e507aaf27c502812fc395d30fb17.zones.catalog.example + send +END +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:waiting for slave to sync up ($n)" +ret=1 +try=0 +while test $try -lt 45 +do + sleep 1 + sed -n "$cur,"'$p' < ns2/named.run | grep "catz: deleting zone 'dom2.example' from catalog 'catalog.example'" > /dev/null && { + ret=0 + break + } + try=`expr $try + 1` +done +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + + +n=`expr $n + 1` +echo "I:checking that dom2.example is not served by slave ($n)" +ret=0 +$DIG soa dom2.example @10.53.0.2 -p 5300 > dig.out.test$n +grep "status: REFUSED" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:checking that dom3.example is still served by slave ($n)" +ret=0 +$DIG soa dom3.example @10.53.0.2 -p 5300 > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:exit status: $status" +exit $status diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index 3e924723d1..3963447ed4 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -68,7 +68,7 @@ RANDFILE=$TOP/bin/tests/system/random.data # load on the machine to make it unusable to other users. # v6synth SUBDIRS="acl additional allow_query addzone autosign builtin - cacheclean case checkconf @CHECKDS@ checknames checkzone + cacheclean case catz checkconf @CHECKDS@ checknames checkzone cookie @COVERAGE@ database digdelv dlv dlvauto dlz dlzexternal dname dns64 dnssec dsdigest dscp @DNSTAP@ dyndb ecdsa ednscompliance emptyzones fetchlimit filter-aaaa formerr forward geoip glue gost diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index d14538e79b..25c81fd6de 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -2373,6 +2373,8 @@ options { + +
IPv6 Support in <acronym>BIND</acronym> 9 BIND 9 fully supports all currently @@ -4674,6 +4676,16 @@ badresp:1,adberr:0,findfail:0,valfail:0] qname-wait-recurse yes_or_no automatic-interface-scan yes_or_no ; + catalog-zones { + zone quoted_string + default-masters + port ip_port + dscp ip_dscp + { ( masters_list | ip_addr port ip_port key key ) ; ... } + in-memory yes_or_no + min-update-interval interval + ; ... }; + ; v6-bias number ; }; diff --git a/doc/arm/Bv9ARM.pdf b/doc/arm/Bv9ARM.pdf index 1569804414..a537c7664b 100644 Binary files a/doc/arm/Bv9ARM.pdf and b/doc/arm/Bv9ARM.pdf differ diff --git a/doc/arm/catz.xml b/doc/arm/catz.xml new file mode 100644 index 0000000000..9316ee8c6d --- /dev/null +++ b/doc/arm/catz.xml @@ -0,0 +1,229 @@ + +
Catalog Zones + + + A "catalog zone" is a special DNS zone that contains a list of + other zones to be served, along with their configuration parameters. + Zones listed in a catalog zone are called "member zones". + When a catalog zone is loaded or transferred to a slave server + which supports this functionality, the slave server will create + the member zones automatically. When the catalog zone is updated + is updated (for example, to add or delete member zones, or change + their configuration aprameters) those changes are immediately put + into effect. Because the catalog zone is a normal DNS zone, these + configuration changes can be propagated using the standard AXFR/IXFR + zone transfer mechanism. + + + Catalog zones' format and behavior are specified as an internet draft + for interoperability among DNS implementations. As of this release, the + latest revision of the DNS catalog zones draft can be found here: + https://datatracker.ietf.org/doc/draft-muks-dnsop-dns-catalog-zones/ + + +
Principle of Operation + + Normally, if a zone is to be served by a slave server, the + named.conf file on the server must list the + zone, or the zone must be added using rndc addzone. + In environments with a large number of slave servers and/or where + the zones being served are changing frequently, the overhead involved + in maintaining consistent zone configuration on all the slave + servers can be significant. + + + A catalog zone is a way to ease this administrative burden. It is a + DNS zone that lists member zones that should be served by slave servers. + When a slave server receives an update to the catalog zone, it adds, + removes, or reconfigures member zones based on the data received. + + + To use a catalog zone, it must first be set up as a normal zone on + the master and the on slave servers that will be configured to use + it. It must also be added to a list + in the or statement + in named.conf. (This is comparable to the way + a policy zone is configured as a normal zone and also listed in + a statement.) + + + To use the catalog zone feature to serve a new member zone: + + + + Set up the the member zone to be served on the master as normal. + This could be done by editing named.conf, + or by running rndc addzone. + + + + + Add an entry to the catalog zone for the new member zone. + This could be done by editing the catalog zone's master file + and running rndc reload, or by updating + the zone using nsupdate. + + + + The change to the catalog zone will be propagated from the master to all + slaves using the normal AXFR/IXFR mechanism. When the slave receives the + update to the catalog zone, it will detect the entry for the new member + zone, create an instance of of that zone on the slave server, and point + that instance to the specified in the catalog + zone data. The newly created member zone is a normal slave zone, so + BIND will immediately initiate a transfer of zone contents from the + master. Once complete, the slave will start serving the member zone. + + + Removing a member zone from a slave server requires nothing more than + deleting the member zone's entry in the catalog zone. The change to the + catalog cone is propagated to the slave server using the normal AXFR/IXFR + transfer mechanism. The slave server, on processing the update, will + notice that the member zone has been removed. It will stop serving the + zone and remove it froms its list of configured zones. (Removing the + member zone from the master server has to be done in the normal way, + by editing the configuration file or running + rndc delzone.) + +
+ +
Configuring Catalog Zones + + Catalog zones are configured with a catalog-zones + statement in the options or view + section of named.conf. For example, + + +catalog-zones { + zone "catalog.example" default-masters { 10.53.0.1; } in-memory true min-update-interval 10; +}; + + + This statement specifies that the zone + catalog.example is a catalog zone. This zone must be + properly configured in the same view. In most configurations, it would + be a slave zone. + + + The option defines the default masters + for member zones listed in a catalog zone. This can be overriden by + options within a catalog zone. If no such options are included, then + member zones will transfer their contents from the servers listed in + this option. + + + The option, if set to yes, + causes member zones to be stored only in memory. This is functionally + equivalent to configuring a slave zone without a . + option. The default is no; member zones' content + will be stored locally in a file whose name is automatically generated + from the view name, catalog zone name, and member zone name. + + + The option sets the minimum + interval between processing of updates to catalog zones, in seconds. + If an update to a catalog zone (for example, via IXFR) happens less + than seconds after the most + recent update, then the changes will not be carried out until this + interval has elapsed. The default is 5 seconds. + + + Catalog zones are defined on a per-view basis. Configuring a non-empty + statement in a view will automatically + turn on for that view. (Note: this + means rndc addzone and rndc delzone + will also work in any view that supports catalog zones.) + +
+ +
Catalog Zone format + + A catalog zone is a regular DNS zone; therefore, it has to have a + single SOA and at least one NS + record. + + + A record stating the version of the catalog zone format is + also required. If the version number listed is not supported by + the server, then a catalog zone may not be used by that server. + + +catalog.example. IN SOA . . 2016022901 900 600 86400 1 +catalog.example. IN NS nsexample. +version.catalog.example. IN TXT "1" + + + Note that this record must have the domain name + version.catalog-zone-name. This illustrates + how the meaning of data stored in a catalog zone is indicated by the + the domain name label immediately before the catalog zone domain. + + + Catalog zones can contain a set of global options that are applied to + all member zones, overriding the settings for the catalog zone + in the configuration file. Currently only the "masters" option + is supported: + + + +masters.catalog.example IN A 192.0.2.1 +masters.catalog.example IN AAAA 2001:db8::1 + + + (Note that if more than one server is defined, the order in which + they are used is undefined. The above example could correspond to + a zone configured with + + or with + . + There is currently no way to force a particular ordering.) + + + A member zone is added by including a PTR + resource record in the zones sub-domain of the + catalog zone. The record label is a SHA-1 hash + of the member zone name in wire format. The target of the PTR + record is the member zone name. For example, to add the member + zone domain.example: + + +5960775ba382e7a4e09263fc06e7c00569b6a05c.zones.catalog.example IN PTR domain.example. + + + The hash is necessary to identify options for a specific member + zone. The member zone-specific options are defined the same way as + global options, but in the member zone subdomain: + + +masters.5960775ba382e7a4e09263fc06e7c00569b6a05c.zones.catalog.example IN A 192.0.2.2 +masters.5960775ba382e7a4e09263fc06e7c00569b6a05c.zones.catalog.example IN AAAA 2001:db8::2 + + + As would be expected, options defined for a specific zone override + the global options defined in the catalog zone. These in turn override + the global options defined in the catalog-zones + statement in the configuration file. + + + (Note that none of the global records an option will be inherited if + any records are defined for that option for the specific zone. For + example, if the zone had a masters record of type + A but not AAAA, then it would not inherit the + type AAAA record from the global option.) + +
+
diff --git a/doc/arm/notes.xml b/doc/arm/notes.xml index 593cbf3879..b0993c4697 100644 --- a/doc/arm/notes.xml +++ b/doc/arm/notes.xml @@ -54,6 +54,35 @@
New Features + + + A new method of provisioning secondary servers called + "Catalog Zones" has been added. This is an implementation of + + draft-muks-dnsop-dns-catalog-zones/ + . + + + A catalog zone is a regular DNS zone which contains a list + of "member zones", along with the configuration options for + each of those zones. When a server is configured to use a + catalog zone, all the zones listed in the catalog zone are + added to the local server as slave zones. When the catalog + zone is updated (e.g., by adding or removing zones, or + changing configuration options for existing zones) those + changes will be put into effect. Since the catalog zone is + itself a DNS zone, this means configuration changes can be + propagated to slaves using the standard AXFR/IXFR update + mechanism. + + + This feature should be considered experimental. It currently + supports only basic features; more advanced features such as + ACLs and TSIG keys are not yet supported. Example catalog + zone configurations can be found in the Chapter 9 of the + BIND Administrator Reference Manual. + + Added rndc python module. diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index ee40594ed8..335d2f74da 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -65,11 +65,11 @@ DNSTAPOBJS = dnstap.@O@ dnstap.pb-c.@O@ # Alphabetically DNSOBJS = acache.@O@ acl.@O@ adb.@O@ badcache.@O@ byaddr.@O@ \ - cache.@O@ callbacks.@O@ clientinfo.@O@ compress.@O@ \ + cache.@O@ callbacks.@O@ catz.@O@ clientinfo.@O@ compress.@O@ \ db.@O@ dbiterator.@O@ dbtable.@O@ diff.@O@ dispatch.@O@ \ dlz.@O@ dns64.@O@ dnssec.@O@ ds.@O@ dyndb.@O@ forward.@O@ \ - iptable.@O@ journal.@O@ keydata.@O@ keytable.@O@ \ - lib.@O@ log.@O@ lookup.@O@ \ + ipkeylist.@O@ iptable.@O@ journal.@O@ keydata.@O@ \ + keytable.@O@ lib.@O@ log.@O@ lookup.@O@ \ master.@O@ masterdump.@O@ message.@O@ \ name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ order.@O@ peer.@O@ portlist.@O@ private.@O@ \ @@ -108,8 +108,8 @@ DNSSRCS = acache.c acl.c adb.c badcache. byaddr.c \ cache.c callbacks.c clientinfo.c compress.c \ db.c dbiterator.c dbtable.c diff.c dispatch.c \ dlz.c dns64.c dnssec.c ds.c dyndb.c forward.c \ - iptable.c journal.c keydata.c keytable.c lib.c log.c \ - lookup.c master.c masterdump.c message.c \ + ipkeylist.c iptable.c journal.c keydata.c keytable.c lib.c \ + log.c lookup.c master.c masterdump.c message.c \ name.c ncache.c nsec.c nsec3.c nta.c \ order.c peer.c portlist.c \ rbt.c rbtdb.c rbtdb64.c rcode.c rdata.c rdatalist.c \ diff --git a/lib/dns/acl.c b/lib/dns/acl.c index 41c865585a..d2d5139530 100644 --- a/lib/dns/acl.c +++ b/lib/dns/acl.c @@ -583,7 +583,7 @@ initialize_action(void) { } /* - * Called via isc_radix_walk() to find IP table nodes that are + * Called via isc_radix_process() to find IP table nodes that are * insecure. */ static void diff --git a/lib/dns/catz.c b/lib/dns/catz.c new file mode 100644 index 0000000000..4866a5a1b5 --- /dev/null +++ b/lib/dns/catz.c @@ -0,0 +1,1474 @@ +/* + * Copyright (C) 2015, 2016 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/*% + * Single member zone in a catalog + */ +struct dns_catz_entry { + dns_name_t name; + dns_catz_options_t opts; + isc_refcount_t refs; +}; + +/*% + * Catalog zone + */ +struct dns_catz_zone { + dns_name_t name; + dns_catz_zones_t *catzs; + dns_rdata_t soa; + /* key in entries is 'mhash', not domain name! */ + isc_ht_t *entries; + /* + * defoptions are taken from named.conf + * zoneoptions are global options from zone + */ + dns_catz_options_t defoptions; + dns_catz_options_t zoneoptions; + isc_time_t lastupdated; + isc_boolean_t updatepending; + isc_uint32_t version; + + dns_db_t *db; + dns_dbversion_t *dbversion; + + isc_timer_t *updatetimer; + isc_event_t updateevent; + + isc_boolean_t active; + + isc_refcount_t refs; +}; + +/*% + * Collection of catalog zones for a view + */ +struct dns_catz_zones { + isc_ht_t *zones; + isc_mem_t *mctx; + isc_refcount_t refs; + isc_mutex_t lock; + dns_catz_zonemodmethods_t *zmm; + isc_taskmgr_t *taskmgr; + isc_timermgr_t *timermgr; + dns_view_t *view; + isc_task_t *updater; +}; + +void +dns_catz_options_init(dns_catz_options_t *options) { + options->masters.addrs = NULL; + options->masters.dscps = NULL; + options->masters.keys = NULL; + options->masters.count = 0; + + options->in_memory = ISC_FALSE; + options->min_update_interval = 5; +} + +void +dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx) { + if (options->masters.count > 0) + dns_ipkeylist_clear(mctx, &options->masters); +} + +isc_result_t +dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *src, + dns_catz_options_t *dst) +{ + /* TODO error handling */ + REQUIRE(dst != NULL); + REQUIRE(dst->masters.count == 0); + + if (src->masters.count) { + dns_ipkeylist_copy(mctx, &src->masters, &dst->masters); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults, + dns_catz_options_t *opts) +{ + if (opts->masters.count == 0) + dns_catz_options_copy(mctx, defaults, opts); + + /* This option is always taken from config, so it's always 'default' */ + opts->in_memory = defaults->in_memory; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain, + dns_catz_entry_t **nentryp) +{ + dns_catz_entry_t *nentry; + isc_result_t res; + + REQUIRE(nentryp != NULL && *nentryp == NULL); + REQUIRE(domain != NULL); + + nentry = isc_mem_get(mctx, sizeof(dns_catz_entry_t)); + if (nentry == NULL) + return (ISC_R_NOMEMORY); + + dns_name_init(&nentry->name, NULL); + res = dns_name_dup(domain, mctx, &nentry->name); + if (res != ISC_R_SUCCESS) + goto cleanup; + + dns_catz_options_init(&nentry->opts); + isc_refcount_init(&nentry->refs, 1); + *nentryp = nentry; + return (ISC_R_SUCCESS); + +cleanup: + isc_mem_put(mctx, nentry, sizeof(dns_catz_entry_t)); + return (res); +} + +dns_name_t * +dns_catz_entry_getname(dns_catz_entry_t *entry) { + return (&entry->name); +} + +isc_result_t +dns_catz_entry_copy(dns_catz_zone_t *zone, const dns_catz_entry_t *entry, + dns_catz_entry_t **nentryp) +{ + isc_result_t res; + dns_catz_entry_t *nentry = NULL; + + res = dns_catz_entry_new(zone->catzs->mctx, &entry->name, &nentry); + if (res != ISC_R_SUCCESS) + return (res); + + res = dns_catz_options_copy(zone->catzs->mctx, &entry->opts, + &nentry->opts); + if (res != ISC_R_SUCCESS) + dns_catz_entry_detach(zone, &nentry); + + *nentryp = nentry; + return (res); +} + +void +dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp) { + REQUIRE(entryp != NULL && *entryp == NULL); + isc_refcount_increment(&entry->refs, NULL); + *entryp = entry; +} + +void +dns_catz_entry_detach(dns_catz_zone_t *zone, dns_catz_entry_t **entryp) { + dns_catz_entry_t *entry; + isc_mem_t *mctx; + unsigned int refs; + + REQUIRE(entryp != NULL && *entryp != NULL); + + entry = *entryp; + *entryp = NULL; + + mctx = zone->catzs->mctx; + + isc_refcount_decrement(&entry->refs, &refs); + if (refs == 0) { + dns_catz_options_free(&entry->opts, mctx); + dns_name_free(&entry->name, mctx); + isc_refcount_destroy(&entry->refs); + isc_mem_put(mctx, entry, sizeof(dns_catz_entry_t)); + } +} + +isc_boolean_t +dns_catz_entry_validate(const dns_catz_entry_t *entry) { + UNUSED(entry); + + return (ISC_TRUE); +} + +isc_boolean_t +dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb) { + if (ea->opts.masters.count != eb->opts.masters.count) + return (ISC_FALSE); + + if (memcmp(ea->opts.masters.addrs, eb->opts.masters.addrs, + ea->opts.masters.count * sizeof(isc_sockaddr_t))) + return (ISC_FALSE); + + /* xxxwpk TODO compare dscps/keys! */ + return (ISC_TRUE); +} + + +dns_name_t * +dns_catz_zone_getname(dns_catz_zone_t *zone) { + REQUIRE(zone != NULL); + + return (&zone->name); +} + +dns_catz_options_t * +dns_catz_zone_getdefoptions(dns_catz_zone_t *zone) { + REQUIRE(zone != NULL); + + return (&zone->defoptions); +} + +void +dns_catz_zone_resetdefoptions(dns_catz_zone_t *zone) { + REQUIRE(zone != NULL); + + dns_catz_options_free(&zone->defoptions, zone->catzs->mctx); + dns_catz_options_init(&zone->defoptions); +} + +static isc_result_t +newzonewalk(void *udata, const unsigned char *key, isc_uint32_t keysize, + void *data) +{ + isc_result_t result; + dns_catz_zone_t *zone = udata; + dns_catz_entry_t *nentry = (dns_catz_entry_t *) data; + dns_catz_entry_t *oentry; + char cznamebuf[DNS_NAME_FORMATSIZE]; + char znamebuf[DNS_NAME_FORMATSIZE]; + isc_buffer_t czname; + isc_buffer_t zname; + + REQUIRE(zone != NULL); + REQUIRE(zone->catzs->view != NULL); + + dns_catz_options_setdefault(zone->catzs->mctx, &zone->zoneoptions, + &nentry->opts); + isc_buffer_init(&czname, cznamebuf, DNS_NAME_FORMATSIZE); + isc_buffer_init(&zname, znamebuf, DNS_NAME_FORMATSIZE); + dns_name_totext(&zone->name, ISC_TRUE, &czname); + isc_buffer_putuint8(&czname, 0); + dns_name_totext(&nentry->name, ISC_TRUE, &zname); + isc_buffer_putuint8(&zname, 0); + + result = isc_ht_find(zone->entries, key, keysize, (void **) &oentry); + if (result != ISC_R_SUCCESS) { + result = zone->catzs->zmm->addzone(nentry, zone, + zone->catzs->view, + zone->catzs->taskmgr, + zone->catzs->zmm->udata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: adding zone '%s' from catalog '%s' - %s", + znamebuf, cznamebuf, isc_result_totext(result)); + return (ISC_R_SUCCESS); + } + + if (dns_catz_entry_cmp(oentry, nentry) != ISC_TRUE) { + result = zone->catzs->zmm->modzone(nentry, zone, + zone->catzs->view, + zone->catzs->taskmgr, + zone->catzs->zmm->udata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: modifying zone '%s' from catalog " + "'%s' - %s", + znamebuf, cznamebuf, + isc_result_totext(result)); + } + + dns_catz_entry_detach(zone, &oentry); + result = isc_ht_delete(zone->entries, key, keysize); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +oldzonewalk(void *udata, const unsigned char *key, isc_uint32_t keysize, + void *data) +{ + isc_result_t result; + dns_catz_zone_t *zone = udata; + dns_catz_entry_t *entry = (dns_catz_entry_t *) data; + char cznamebuf[DNS_NAME_FORMATSIZE]; + char znamebuf[DNS_NAME_FORMATSIZE]; + isc_buffer_t czname; + isc_buffer_t zname; + + UNUSED(key); + UNUSED(keysize); + + REQUIRE(zone != NULL); + REQUIRE(zone->catzs->view != NULL); + + isc_buffer_init(&czname, cznamebuf, DNS_NAME_MAXTEXT); + isc_buffer_init(&zname, znamebuf, DNS_NAME_MAXTEXT); + dns_name_totext(&zone->name, ISC_TRUE, &czname); + isc_buffer_putuint8(&czname, 0); + dns_name_totext(&entry->name, ISC_TRUE, &zname); + isc_buffer_putuint8(&czname, 0); + result = zone->catzs->zmm->delzone(entry, zone, zone->catzs->view, + zone->catzs->taskmgr, + zone->catzs->zmm->udata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: deleting zone '%s' from catalog '%s' - %s", + znamebuf, cznamebuf, isc_result_totext(result)); + dns_catz_entry_detach(zone, &entry); + + return (ISC_R_EXISTS); +} + +isc_result_t +dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) { + isc_result_t res; + + REQUIRE(target != NULL); + REQUIRE(newzone != NULL); + + /* TODO verify the new zone first! */ + + /* Copy zoneoptions from newzone into target */ + dns_catz_options_free(&target->zoneoptions, target->catzs->mctx); + dns_catz_options_copy(target->catzs->mctx, &newzone->zoneoptions, + &target->zoneoptions); + dns_catz_options_setdefault(target->catzs->mctx, &target->defoptions, + &target->zoneoptions); + + /* + * first - walk the new zone and find all nodes that are not in the + * old zone, or are in both zones and are modified + */ + res = isc_ht_walk(newzone->entries, newzonewalk, target); + /* newzonewalk always returns ISC_R_SUCCESS */ + RUNTIME_CHECK(res == ISC_R_SUCCESS); + + /* + * then - walk the old zone; only deleted entries should remain + * return (ISC_R_SUCCESS); + */ + res = isc_ht_walk(target->entries, oldzonewalk, target); + /* + * oldzonewalk always returns ISC_R_EXISTS, so walk should return + * ISC_R_SUCCESS + */ + RUNTIME_CHECK(res == ISC_R_SUCCESS); + + /* at this moment target->entries has to be be empty */ + INSIST(isc_ht_count(target->entries) == 0); + isc_ht_destroy(&target->entries); + + target->entries = newzone->entries; + newzone->entries = NULL; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_catz_new_zones(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm, + isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr) +{ + dns_catz_zones_t *new_zones; + isc_result_t result; + + REQUIRE(catzsp != NULL && *catzsp == NULL); + REQUIRE(zmm != NULL); + + new_zones = isc_mem_get(mctx, sizeof(*new_zones)); + if (new_zones == NULL) + return (ISC_R_NOMEMORY); + memset(new_zones, 0, sizeof(*new_zones)); + + result = isc_refcount_init(&new_zones->refs, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, new_zones, sizeof(*new_zones)); + return (result); + } + + result = isc_ht_init(&new_zones->zones, mctx, 4); + if (result != ISC_R_SUCCESS) { + isc_refcount_destroy(&new_zones->refs); + isc_mem_put(mctx, new_zones, sizeof(*new_zones)); + return (result); + } + + isc_mem_attach(mctx, &new_zones->mctx); + isc_mutex_init(&new_zones->lock); + new_zones->zmm = zmm; + new_zones->taskmgr = taskmgr; + new_zones->timermgr = timermgr; + isc_task_create(new_zones->taskmgr, 0, &new_zones->updater); + *catzsp = new_zones; + + return (ISC_R_SUCCESS); +} + +void +dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view) { + REQUIRE(catzs != NULL); + REQUIRE(view != NULL); + /* either it's a new one or it's being reconfigured */ + REQUIRE(catzs->view == NULL || !strcmp(catzs->view->name, view->name)); + catzs->view = view; +} + +isc_result_t +dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep, + const dns_name_t *name) { + isc_result_t res; + dns_catz_zone_t *new_zone; + + REQUIRE(zonep != NULL && *zonep == NULL); + + new_zone = isc_mem_get(catzs->mctx, sizeof(*new_zone)); + if (new_zone == NULL) { + return (ISC_R_NOMEMORY); + } + + memset(new_zone, 0, sizeof(*new_zone)); + + dns_name_init(&new_zone->name, NULL); + + res = dns_name_dup(name, catzs->mctx, &new_zone->name); + if (res != ISC_R_SUCCESS) { + isc_mem_put(catzs->mctx, new_zone, sizeof(*new_zone)); + return (res); + } + + res = isc_ht_init(&new_zone->entries, catzs->mctx, 4); + if (res != ISC_R_SUCCESS) { + dns_name_free(&new_zone->name, catzs->mctx); + isc_mem_put(catzs->mctx, new_zone, sizeof(*new_zone)); + return (res); + } + + isc_time_settoepoch(&new_zone->lastupdated); + new_zone->updatepending = ISC_FALSE; + new_zone->db = NULL; + new_zone->dbversion = NULL; + new_zone->updatetimer = NULL; + new_zone->catzs = catzs; + dns_catz_options_init(&new_zone->defoptions); + dns_catz_options_init(&new_zone->zoneoptions); + new_zone->active = ISC_TRUE; + new_zone->version = (isc_uint32_t)(-1); + isc_refcount_init(&new_zone->refs, 1); + + *zonep = new_zone; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name, + dns_catz_zone_t **zonep) +{ + dns_catz_zone_t *new_zone = NULL; + isc_result_t res; + + REQUIRE(catzs != NULL); + REQUIRE(name != NULL); + REQUIRE(zonep != NULL && *zonep == NULL); + + LOCK(&catzs->lock); + + res = dns_catz_new_zone(catzs, &new_zone, name); + if (res != ISC_R_SUCCESS) + goto cleanup; + + res = isc_ht_add(catzs->zones, new_zone->name.ndata, + new_zone->name.length, new_zone); + if (res != ISC_R_SUCCESS) { + dns_catz_zone_detach(&new_zone); + if (res != ISC_R_EXISTS) + goto cleanup; + } + + if (res == ISC_R_EXISTS) { + res = isc_ht_find(catzs->zones, name->ndata, + name->length, (void **) &new_zone); + INSIST(res == ISC_R_SUCCESS && !new_zone->active); + new_zone->active = ISC_TRUE; + } + + *zonep = new_zone; + + cleanup: + UNLOCK(&catzs->lock); + + return (res); +} + +dns_catz_zone_t * +dns_catz_get_zone(dns_catz_zones_t *catzs, const dns_name_t *name) { + isc_result_t result; + dns_catz_zone_t *found; + + result = isc_ht_find(catzs->zones, name->ndata, name->length, + (void **) &found); + if (result != ISC_R_SUCCESS) + return (NULL); + + return (found); +} + +void +dns_catz_catzs_attach(dns_catz_zones_t *catzs, dns_catz_zones_t **catzsp) { + REQUIRE(catzsp != NULL && *catzsp == NULL); + + isc_refcount_increment(&catzs->refs, NULL); + *catzsp = catzs; +} + +static isc_result_t +freewalk(void *udata, const unsigned char *key, isc_uint32_t keysize, + void *data) +{ + dns_catz_zone_t *zone = (dns_catz_zone_t *) udata; + dns_catz_entry_t *entry = (dns_catz_entry_t *) data; + + UNUSED(key); + UNUSED(keysize); + + dns_catz_entry_detach(zone, &entry); + + return (ISC_R_EXISTS); +} + +void +dns_catz_zone_attach(dns_catz_zone_t *zone, dns_catz_zone_t **zonep) { + REQUIRE(zonep != NULL && *zonep == NULL); + isc_refcount_increment(&zone->refs, NULL); + *zonep = zone; +} + +void +dns_catz_zone_detach(dns_catz_zone_t **zonep) { + dns_catz_zone_t *zone; + isc_mem_t *mctx; + unsigned int refs; + + REQUIRE(zonep != NULL && *zonep != NULL); + + zone = *zonep; + *zonep = NULL; + isc_refcount_decrement(&zone->refs, &refs); + if (refs == 0) { + if (zone->entries != NULL) { + isc_result_t res; + + /* + * freewalk always returns ISC_R_EXISTS, triggering + * isc_ht_walk to delete the node. If isc_ht_walk + * returns an error, it is a critical condition + */ + res = isc_ht_walk(zone->entries, freewalk, zone); + INSIST(res == ISC_R_SUCCESS); + + /* the hashtable has to be empty now */ + INSIST(isc_ht_count(zone->entries) == 0); + isc_ht_destroy(&zone->entries); + } + mctx = zone->catzs->mctx; + isc_refcount_destroy(&zone->refs); + dns_name_free(&zone->name, mctx); + dns_catz_options_free(&zone->defoptions, mctx); + dns_catz_options_free(&zone->zoneoptions, mctx); + zone->catzs = NULL; + isc_mem_put(mctx, zone, sizeof(dns_catz_zone_t)); + } +} + +static isc_result_t +catzsfreewalk(void *udata, const unsigned char *key, isc_uint32_t keysize, + void *data) +{ + dns_catz_zones_t *catzs = (dns_catz_zones_t *) udata; + dns_catz_zone_t *zone = (dns_catz_zone_t *) data; + + UNUSED(key); + UNUSED(keysize); + UNUSED(catzs); + + dns_catz_zone_detach(&zone); + + return (ISC_R_EXISTS); +} + +void +dns_catz_catzs_detach(dns_catz_zones_t ** catzsp) { + dns_catz_zones_t *catzs; + isc_result_t res; + unsigned int refs; + + REQUIRE(catzsp != NULL); + catzs = *catzsp; + REQUIRE(catzs != NULL); + + *catzsp = NULL; + isc_refcount_decrement(&catzs->refs, &refs); + + if (refs == 0) { + DESTROYLOCK(&catzs->lock); + if (catzs->zones != NULL) { + res = isc_ht_walk(catzs->zones, catzsfreewalk, catzs); + INSIST(res == ISC_R_SUCCESS); + INSIST(isc_ht_count(catzs->zones) == 0); + isc_ht_destroy(&catzs->zones); + } + isc_refcount_destroy(&catzs->refs); + isc_task_destroy(&catzs->updater); + isc_mem_putanddetach(&catzs->mctx, catzs, sizeof(*catzs)); + } +} + +typedef enum { + CATZ_OPT_NONE, + CATZ_OPT_ZONES, + CATZ_OPT_MASTERS, + CATZ_OPT_ALLOW_QUERY, + CATZ_OPT_ALLOW_TRANSFER, + CATZ_OPT_VERSION, +} catz_opt_t; + +static isc_boolean_t +catz_opt_cmp(const dns_label_t *option, const char *opt) { + unsigned int l = strlen(opt); + if (option->length - 1 == l && + memcmp(opt, option->base + 1, l - 1) == 0) + return (ISC_TRUE); + else + return (ISC_FALSE); +} + +static catz_opt_t +catz_get_option(const dns_label_t *option) { + if (catz_opt_cmp(option, "zones")) + return (CATZ_OPT_ZONES); + else if (catz_opt_cmp(option, "masters")) + return (CATZ_OPT_MASTERS); + else if (catz_opt_cmp(option, "allow-query")) + return (CATZ_OPT_ALLOW_QUERY); + else if (catz_opt_cmp(option, "allow-transfer")) + return (CATZ_OPT_ALLOW_TRANSFER); + else if (catz_opt_cmp(option, "version")) + return (CATZ_OPT_VERSION); + else + return (CATZ_OPT_NONE); +} + +static isc_result_t +catz_process_global_list(dns_catz_zone_t *zone, catz_opt_t opt, + dns_rdataset_t *value, dns_label_t *mhash) +{ + isc_result_t res; + dns_rdata_t rdata; + dns_rdata_ptr_t ptr; + dns_catz_entry_t *entry = NULL; + dns_name_t emptyname; + + REQUIRE(zone != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(mhash != NULL); + + dns_name_init(&emptyname, NULL); + switch (opt) { + case CATZ_OPT_ZONES: + if (value->rdclass != dns_rdataclass_in || + value->type != dns_rdatatype_ptr) { + break; + } + + /* + * We only take -first- value, as mhash must be + * different + */ + dns_rdataset_first(value); + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + + res = dns_rdata_tostruct(&rdata, &ptr, zone->catzs->mctx); + + res = isc_ht_find(zone->entries, mhash->base, + mhash->length, (void **) &entry); + if (res == ISC_R_SUCCESS) { + if (dns_name_countlabels(&entry->name) != 0) { + /* we have a duplicate */ + dns_rdata_freestruct(&ptr); + return (ISC_R_FAILURE); + } else { + res = dns_name_dup(&ptr.ptr, zone->catzs->mctx, + &entry->name); + if (res != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ptr); + return (res); + } + } + } else { + res = dns_catz_entry_new(zone->catzs->mctx, &ptr.ptr, + &entry); + if (res != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ptr); + return (res); + } + + res = isc_ht_add(zone->entries, mhash->base, + mhash->length, entry); + if (res != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ptr); + dns_catz_entry_detach(zone, &entry); + return (res); + } + } + + dns_rdata_freestruct(&ptr); + break; + default: + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_process_version(dns_catz_zone_t *zone, dns_rdataset_t *value) { + isc_result_t res; + dns_rdata_t rdata; + dns_rdata_txt_t rdatatxt; + dns_rdata_txt_string_t rdatastr; + isc_uint32_t tversion; + char t[16]; + + REQUIRE(zone != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + + if (value->rdclass != dns_rdataclass_in || + value->type != dns_rdatatype_txt) + return (ISC_R_FAILURE); + + dns_rdataset_first(value); + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + + res = dns_rdata_tostruct(&rdata, &rdatatxt, zone->catzs->mctx); + if (res != ISC_R_SUCCESS) + return (res); + + res = dns_rdata_txt_first(&rdatatxt); + if (res != ISC_R_SUCCESS) + goto cleanup; + + res = dns_rdata_txt_current(&rdatatxt, &rdatastr); + if (res != ISC_R_SUCCESS) + goto cleanup; + res = dns_rdata_txt_next(&rdatatxt); + if (res != ISC_R_NOMORE) { + res = ISC_R_FAILURE; + goto cleanup; + } + if (rdatastr.length > 15) { + res = ISC_R_BADNUMBER; + goto cleanup; + } + memcpy(t, rdatastr.data, rdatastr.length); + t[rdatastr.length] = 0; + res = isc_parse_uint32(&tversion, t, 10); + if (res != ISC_R_SUCCESS) { + goto cleanup; + } + zone->version = tversion; + res = ISC_R_SUCCESS; + +cleanup: + dns_rdata_freestruct(&rdatatxt); + return (res); +} + +static isc_result_t +catz_process_ipkl(dns_catz_zone_t *zone, dns_ipkeylist_t *ipkl, + dns_rdataset_t *value) +{ + isc_result_t res; + dns_rdata_t rdata; + dns_rdata_in_a_t rdataa; + + REQUIRE(zone != NULL); + REQUIRE(ipkl != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + + if (value->rdclass != dns_rdataclass_in || + value->type != dns_rdatatype_a) + return (ISC_R_FAILURE); + + dns_rdataset_first(value); + do { + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + + res = dns_rdata_tostruct(&rdata, &rdataa, zone->catzs->mctx); + if (res != ISC_R_SUCCESS) + return (res); + + if (ipkl->count == 0) { + ipkl->addrs = isc_mem_allocate(zone->catzs->mctx, + sizeof(isc_sockaddr_t)); + ipkl->keys = isc_mem_allocate(zone->catzs->mctx, + sizeof(dns_name_t *)); + } else { + ipkl->addrs = + isc_mem_reallocate(zone->catzs->mctx, + ipkl->addrs, + ((ipkl->count + 1) * + sizeof(isc_sockaddr_t))); + ipkl->keys = + isc_mem_reallocate(zone->catzs->mctx, + ipkl->keys, + ((ipkl->count + 1) * + sizeof(dns_name_t *))); + } + + /* + * + * port 0 == take the default + */ + isc_sockaddr_fromin(&ipkl->addrs[ipkl->count], + &rdataa.in_addr, 0); + ipkl->keys[ipkl->count] = NULL; + ipkl->count++; + dns_rdata_freestruct(&rdataa); + } while (dns_rdataset_next(value) == ISC_R_SUCCESS); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_process_suboption(dns_catz_zone_t *zone, dns_label_t *mhash, + catz_opt_t subopt, dns_rdataset_t *value) +{ + isc_result_t res; + dns_catz_entry_t *entry = NULL; + + REQUIRE(zone != NULL); + REQUIRE(mhash != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + + /* + * we're adding this entry now, in case the option is invalid we'll get + * rid of in verification phase + */ + res = isc_ht_find(zone->entries, mhash->base, + mhash->length, (void **) &entry); + if (res != ISC_R_SUCCESS) { + res = dns_catz_entry_new(zone->catzs->mctx, NULL, &entry); + if (res != ISC_R_SUCCESS) + return (res); + res = isc_ht_add(zone->entries, mhash->base, mhash->length, + entry); + if (res != ISC_R_SUCCESS) { + dns_catz_entry_detach(zone, &entry); + return (res); + } + } + + switch (subopt) { + case CATZ_OPT_MASTERS: + return (catz_process_ipkl(zone, &entry->opts.masters, value)); + break; + case CATZ_OPT_ALLOW_QUERY: +#if 0 + return (process_apl(zone, &entry->opts)) +#endif + case CATZ_OPT_ALLOW_TRANSFER: + default: + return (ISC_R_FAILURE); + } + + return (ISC_R_FAILURE); +} + +static isc_result_t +catz_process_global_option(dns_catz_zone_t *zone, catz_opt_t option, + dns_rdataset_t *value) +{ + REQUIRE(zone != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + + switch (option) { + case CATZ_OPT_MASTERS: + return (catz_process_ipkl(zone, &zone->zoneoptions.masters, + value)); + break; + case CATZ_OPT_VERSION: + return (catz_process_version(zone, value)); + break; + case CATZ_OPT_ALLOW_QUERY: +#if 0 + return (process_apl(zone, &entry->opts)) +#endif + case CATZ_OPT_ALLOW_TRANSFER: + default: + return (ISC_R_FAILURE); + } + return (ISC_R_FAILURE); +} + +static isc_result_t +catz_process_value(dns_catz_zone_t *zone, dns_name_t *value, + dns_rdataset_t *rdataset) +{ + dns_label_t suboption, option, mhash; + catz_opt_t opt, subopt; + + REQUIRE(zone != NULL); + REQUIRE(value != NULL); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + switch (value->labels) { + case 1: + /* Catalog zone-wide option */ + dns_name_getlabel(value, 0, &option); + opt = catz_get_option(&option); + return (catz_process_global_option(zone, opt, rdataset)); + + case 2: + /* Global list (eg. 'zones') */ + dns_name_getlabel(value, 0, &mhash); + dns_name_getlabel(value, 1, &option); + opt = catz_get_option(&option); + return (catz_process_global_list(zone, opt, rdataset, &mhash)); + + case 3: + /* Zone option */ + dns_name_getlabel(value, 0, &suboption); + dns_name_getlabel(value, 1, &mhash); + dns_name_getlabel(value, 2, &option); + opt = catz_get_option(&option); + subopt = catz_get_option(&suboption); + if (opt == CATZ_OPT_ZONES) + return (catz_process_suboption(zone, &mhash, subopt, + rdataset)); + break; + + default: + break; + } + + return (ISC_R_FAILURE); +} + +isc_result_t +dns_catz_update_process(dns_catz_zones_t *catzs, dns_catz_zone_t *zone, + dns_name_t *src_name, dns_rdataset_t *rdataset) +{ + isc_result_t res; + int order; + unsigned int nlabels; + dns_namereln_t nrres; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_soa_t soa; + dns_name_t prefix; + + REQUIRE(catzs != NULL); + REQUIRE(zone != NULL); + + nrres = dns_name_fullcompare(src_name, &zone->name, &order, &nlabels); + if (nrres == dns_namereln_equal) { + if (rdataset->type == dns_rdatatype_soa) { + res = dns_rdataset_first(rdataset); + if (res != ISC_R_SUCCESS) + return (res); + dns_rdataset_current(rdataset, &rdata); + res = dns_rdata_tostruct(&rdata, &soa, NULL); + /* + * xxxwpk TODO do we want to save something from SOA? + */ + return (res); + + } else if (rdataset->type == dns_rdatatype_ns) { + return (ISC_R_SUCCESS); + } else { + return (ISC_R_UNEXPECTED); + } + } else if (nrres != dns_namereln_subdomain) { + return (ISC_R_UNEXPECTED); + } + + dns_name_init(&prefix, NULL); + dns_name_split(src_name, zone->name.labels, &prefix, NULL); + res = catz_process_value(zone, &prefix, rdataset); + + return (res); +} + +isc_result_t +dns_catz_generate_masterfilename(dns_catz_zone_t *zone, dns_catz_entry_t *entry, + isc_buffer_t **buffer) +{ + isc_buffer_t *tbuf = NULL; + isc_sha256_t sha256; + isc_region_t r; + isc_result_t res; + + REQUIRE(zone != NULL); + REQUIRE(entry != NULL); + REQUIRE(buffer != NULL && *buffer != NULL); + + isc_buffer_allocate(zone->catzs->mctx, &tbuf, + strlen(zone->catzs->view->name) + + 2*DNS_NAME_FORMATSIZE + 2); + INSIST(tbuf != NULL); + isc_buffer_putstr(tbuf, zone->catzs->view->name); + isc_buffer_putstr(tbuf, "_"); + res = dns_name_totext(&zone->name, ISC_TRUE, tbuf); + if (res != ISC_R_SUCCESS) + goto cleanup; + isc_buffer_putstr(tbuf, "_"); + res = dns_name_totext(&entry->name, ISC_TRUE, tbuf); + if (res != ISC_R_SUCCESS) + goto cleanup; + + res = isc_buffer_reserve(buffer, strlen("__catz__") + + ISC_SHA256_DIGESTSTRINGLENGTH + + strlen(".db")); + if (res != ISC_R_SUCCESS) + goto cleanup; + + isc_buffer_usedregion(tbuf, &r); + isc_buffer_putstr(*buffer, "__catz__"); + if (tbuf->used > ISC_SHA256_DIGESTSTRINGLENGTH) { + isc_sha256_init(&sha256); + isc_sha256_update(&sha256, r.base, r.length); + /* we can do that because digest string < 2*DNS_NAME */ + isc_sha256_end(&sha256, (char *) r.base); + isc_buffer_putstr(*buffer, (char *) r.base); + } else { + isc_buffer_copyregion(*buffer, &r); + } + isc_buffer_putstr(*buffer, ".db"); + res = ISC_R_SUCCESS; + +cleanup: + isc_buffer_free(&tbuf); + return (res); +} + +isc_result_t +dns_catz_generate_zonecfg(dns_catz_zone_t *zone, dns_catz_entry_t *entry, + isc_buffer_t **buf) +{ + /* We have to generate a text buffer with regular zone config: + * zone foo.bar { + * type slave; + * masters { ip1 port1; ip2 port2; }; + * } + */ + isc_buffer_t *buffer = NULL; + isc_result_t res; + isc_uint32_t i; + isc_netaddr_t netaddr; + + REQUIRE(zone != NULL); + REQUIRE(entry != NULL); + REQUIRE(buf != NULL && *buf == NULL); + + /* + * The buffer will be reallocated if something won't fit, ISC_BUFFER_INC + * seems like a good start. + */ + res = isc_buffer_allocate(zone->catzs->mctx, &buffer, ISC_BUFFER_INCR); + if (res != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_buffer_setautorealloc(buffer, ISC_TRUE); + isc_buffer_putstr(buffer, "zone "); + dns_name_totext(&entry->name, ISC_TRUE, buffer); + isc_buffer_putstr(buffer, " { type slave; masters { "); + for (i = 0; i < entry->opts.masters.count; i++) { + /* TODO port and DSCP */ + isc_netaddr_fromsockaddr(&netaddr, + &entry->opts.masters.addrs[i]); + isc_buffer_reserve(&buffer, INET6_ADDRSTRLEN); + res = isc_netaddr_totext(&netaddr, buffer); + RUNTIME_CHECK(res == ISC_R_SUCCESS); + + if (entry->opts.masters.keys[i] != NULL) { + isc_buffer_putstr(buffer, " key "); + res = dns_name_totext(entry->opts.masters.keys[i], + ISC_TRUE, buffer); + if (res != ISC_R_SUCCESS) + goto cleanup; + } + isc_buffer_putstr(buffer, "; "); + } + isc_buffer_putstr(buffer, "};"); + if (entry->opts.in_memory == ISC_FALSE) { + isc_buffer_putstr(buffer, "file \""); + dns_catz_generate_masterfilename(zone, entry, &buffer); + isc_buffer_putstr(buffer, "\";"); + + } + isc_buffer_putstr(buffer, "};"); + + *buf = buffer; + return (ISC_R_SUCCESS); + +cleanup: + if (buffer) + isc_buffer_free(&buffer); + return (res); +} + + +void +dns_catz_update_taskaction(isc_task_t *task, isc_event_t *event) { + isc_result_t res; + dns_catz_zone_t * zone; + (void) task; + + REQUIRE(event != NULL); + zone = event->ev_arg; + REQUIRE(zone != NULL); + + LOCK(&zone->catzs->lock); + zone->updatepending = ISC_FALSE; + dns_catz_update_from_db(zone->db, zone->catzs); + dns_db_detach(&zone->db); + if (zone->updatetimer) { + isc_timer_detach(&zone->updatetimer); + } + isc_event_free(&event); + res = isc_time_now(&zone->lastupdated); + RUNTIME_CHECK(res == ISC_R_SUCCESS); + UNLOCK(&zone->catzs->lock); +} + +isc_result_t +dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg) { + dns_catz_zones_t *catzs; + dns_catz_zone_t *zone = NULL; + isc_time_t now; + isc_uint64_t tdiff; + isc_result_t res = ISC_R_SUCCESS; + isc_region_t r; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(fn_arg != NULL); + catzs = (dns_catz_zones_t*) fn_arg; + + dns_name_toregion(&db->origin, &r); + + LOCK(&catzs->lock); + res = isc_ht_find(catzs->zones, r.base, r.length, (void **) &zone); + INSIST(res == ISC_R_SUCCESS); + + if (zone->updatepending == ISC_FALSE) { + isc_time_now(&now); + tdiff = isc_time_microdiff(&now, &zone->lastupdated)/1000000; + if (tdiff < zone->defoptions.min_update_interval) { + isc_interval_t interval; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: new zone version came too soon, " + "deferring update"); + isc_interval_set(&interval, 5 - tdiff, 0); + zone->updatepending = ISC_TRUE; + dns_db_attach(db, &zone->db); + dns_db_currentversion(db, &zone->dbversion); + isc_timer_create(catzs->timermgr, isc_timertype_once, + NULL, &interval, catzs->updater, + dns_catz_update_taskaction, + zone, &zone->updatetimer); + } else { + isc_event_t *event; + zone->updatepending = ISC_TRUE; + dns_db_attach(db, &zone->db); + dns_db_currentversion(db, &zone->dbversion); + ISC_EVENT_INIT(&zone->updateevent, + sizeof(zone->updateevent), 0, NULL, + DNS_EVENT_CATZUPDATED, + dns_catz_update_taskaction, + zone, zone, NULL, NULL); + event = &zone->updateevent; + isc_task_send(catzs->updater, &event); + } + } else { + INSIST(db == zone->db); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "catz: update already queued"); + dns_db_closeversion(zone->db, &zone->dbversion, ISC_FALSE); + dns_db_currentversion(zone->db, &zone->dbversion); + } + + UNLOCK(&catzs->lock); + + return (res); +} + +void +dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs) { + dns_catz_zone_t *oldzone = NULL, *newzone = NULL; + isc_result_t res; + isc_region_t r; + dns_dbnode_t *node = NULL; + dns_dbiterator_t *it = NULL; + dns_fixedname_t fixname; + dns_name_t *name; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t rdataset; + char bname[DNS_NAME_FORMATSIZE]; + isc_buffer_t ibname; + isc_uint32_t vers; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(catzs != NULL); + + /* + * Create a new catz in the same context as current catz + */ + dns_name_toregion(&db->origin, &r); + res = isc_ht_find(catzs->zones, r.base, r.length, (void **) &oldzone); + if (res != ISC_R_SUCCESS) { + /* this can happen if we remove the zone in the meantime */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: zone '%s' not in config", + bname); + return; + } + + isc_buffer_init(&ibname, bname, DNS_NAME_FORMATSIZE); + res = dns_name_totext(&db->origin, ISC_TRUE, &ibname); + INSIST(res == ISC_R_SUCCESS); + + res = dns_db_getsoaserial(db, oldzone->dbversion, &vers); + if (res != ISC_R_SUCCESS) { + /* A zone without SOA record?!? */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: zone '%s' has no SOA record (%s)", + bname, isc_result_totext(res)); + return; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: updating catalog zone '%s' with serial %d", + bname, vers); + + res = dns_catz_new_zone(catzs, &newzone, &db->origin); + if (res != ISC_R_SUCCESS) { + dns_db_closeversion(db, &oldzone->dbversion, ISC_FALSE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to create new zone - %s", + isc_result_totext(res)); + return; + } + + res = dns_db_createiterator(db, DNS_DB_NONSEC3, &it); + if (res != ISC_R_SUCCESS) { + dns_catz_zone_detach(&newzone); + dns_db_closeversion(db, &oldzone->dbversion, ISC_FALSE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to create DB iterator - %s", + isc_result_totext(res)); + return; + } + + dns_fixedname_init(&fixname); + name = dns_fixedname_name(&fixname); + + /* + * Iterate over database to fill the new zone + */ + res = dns_dbiterator_first(it); + if (res != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to get db iterator - %s", + isc_result_totext(res)); + } + + while (res == ISC_R_SUCCESS) { + res = dns_dbiterator_current(it, &node, name); + if (res != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to get db iterator - %s", + isc_result_totext(res)); + break; + } + + res = dns_db_allrdatasets(db, node, oldzone->dbversion, 0, + &rdsiter); + if (res != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to fetch rrdatasets - %s", + isc_result_totext(res)); + dns_db_detachnode(db, &node); + break; + } + + dns_rdataset_init(&rdataset); + res = dns_rdatasetiter_first(rdsiter); + while (res == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, &rdataset); + res = dns_catz_update_process(catzs, newzone, name, + &rdataset); + if (res != ISC_R_SUCCESS) { + char cname[DNS_NAME_FORMATSIZE]; + dns_name_format(name, cname, + DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_WARNING, + "catz: unknown record in catalog " + "zone - %s (%s) - ignoring", + cname, + isc_result_totext(res)); + } + dns_rdataset_disassociate(&rdataset); + if (res != ISC_R_SUCCESS) { + break; + } + + res = dns_rdatasetiter_next(rdsiter); + } + + dns_rdatasetiter_destroy(&rdsiter); + + dns_db_detachnode(db, &node); + res = dns_dbiterator_next(it); + } + + dns_dbiterator_destroy(&it); + dns_db_closeversion(db, &oldzone->dbversion, ISC_FALSE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "catz: update_from_db: iteration finished"); + + /* + * Finally merge new zone into old zone + */ + res = dns_catz_zones_merge(oldzone, newzone); + if (res != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_ERROR, + "catz: failed merging zones: %s", + isc_result_totext(res)); + + return; + } + + dns_catz_zone_detach(&newzone); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "catz: update_from_db: new zone merged"); +} + + +static isc_result_t +resetactivebitwalk(void *udata, const unsigned char *key, isc_uint32_t keysize, + void *data) +{ + dns_catz_zone_t *zone = (dns_catz_zone_t *) data; + + UNUSED(udata); + UNUSED(key); + UNUSED(keysize); + + zone->active = ISC_FALSE; + return (ISC_R_SUCCESS); +} + + +void +dns_catz_prereconfig(dns_catz_zones_t *catzs) { + isc_ht_walk(catzs->zones, resetactivebitwalk, NULL); +} + +static isc_result_t +postreconfigwalk(void *udata, const unsigned char *key, isc_uint32_t keysize, + void *data) +{ + isc_result_t res; + dns_catz_zone_t *newzone = NULL; + dns_catz_zones_t *catzs = (dns_catz_zones_t *) udata; + dns_catz_zone_t *zone = (dns_catz_zone_t *) data; + + UNUSED(key); + UNUSED(keysize); + + REQUIRE(catzs != NULL); + REQUIRE(zone != NULL); + + if (zone->active == ISC_FALSE) { + char cname[DNS_NAME_FORMATSIZE]; + dns_name_format(&zone->name, cname, DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_WARNING, + "catz: removing catalog zone %s", cname); + + /* Merge the old zone with an empty one to remove all members */ + res = dns_catz_new_zone(catzs, &newzone, &zone->name); + INSIST(res == ISC_R_SUCCESS); + dns_catz_zones_merge(zone, newzone); + dns_catz_zone_detach(&newzone); + + /* Make sure that we have an empty catalog zone */ + INSIST(isc_ht_count(zone->entries) == 0); + + dns_catz_zone_detach(&zone); + return (ISC_R_EXISTS); + } + + return (ISC_R_SUCCESS); +} + + +void +dns_catz_postreconfig(dns_catz_zones_t *catzs) { + isc_ht_walk(catzs->zones, postreconfigwalk, catzs); +} + +isc_result_t +dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp) { + return (isc_ht_iter_create(catz->entries, itp)); +} diff --git a/lib/dns/db.c b/lib/dns/db.c index 4193b2f506..2d4e9197e4 100644 --- a/lib/dns/db.c +++ b/lib/dns/db.c @@ -15,8 +15,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id$ */ - /*! \file */ /*** @@ -307,6 +305,8 @@ dns_db_beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { isc_result_t dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + dns_dbonupdatelistener_t *listener; + /* * Finish loading 'db'. */ @@ -315,6 +315,11 @@ dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { REQUIRE(DNS_CALLBACK_VALID(callbacks)); REQUIRE(callbacks->add_private != NULL); + for (listener = ISC_LIST_HEAD(db->update_listeners); + listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + listener->onupdate(db, listener->onupdate_arg); + return ((db->methods->endload)(db, callbacks)); } @@ -330,7 +335,8 @@ dns_db_load2(dns_db_t *db, const char *filename, dns_masterformat_t format) { isc_result_t dns_db_load3(dns_db_t *db, const char *filename, dns_masterformat_t format, - unsigned int options) { + unsigned int options) +{ isc_result_t result, eresult; dns_rdatacallbacks_t callbacks; @@ -445,6 +451,7 @@ void dns_db_closeversion(dns_db_t *db, dns_dbversion_t **versionp, isc_boolean_t commit) { + dns_dbonupdatelistener_t *listener; /* * Close version '*versionp'. @@ -456,6 +463,13 @@ dns_db_closeversion(dns_db_t *db, dns_dbversion_t **versionp, (db->methods->closeversion)(db, versionp, commit); + if (commit == ISC_TRUE) { + for (listener = ISC_LIST_HEAD(db->update_listeners); + listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + listener->onupdate(db, listener->onupdate_arg); + } + ENSURE(*versionp == NULL); } @@ -1043,3 +1057,55 @@ dns_db_rpz_ready(dns_db_t *db) { return (ISC_R_SUCCESS); return ((db->methods->rpz_ready)(db)); } + +/** + * Attach a notify-on-update function the database + */ +isc_result_t +dns_db_updatenotify_register(dns_db_t *db, + dns_dbupdate_callback_t fn, + void *fn_arg) +{ + dns_dbonupdatelistener_t *listener; + + REQUIRE(db != NULL); + REQUIRE(fn != NULL); + + listener = isc_mem_get(db->mctx, sizeof(dns_dbonupdatelistener_t)); + if (listener == NULL) + return (ISC_R_NOMEMORY); + + listener->onupdate = fn; + listener->onupdate_arg = fn_arg; + + ISC_LINK_INIT(listener, link); + ISC_LIST_APPEND(db->update_listeners, listener, link); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_db_updatenotify_unregister(dns_db_t *db, + dns_dbupdate_callback_t fn, + void *fn_arg) +{ + dns_dbonupdatelistener_t *listener; + + REQUIRE(db != NULL); + + for (listener = ISC_LIST_HEAD(db->update_listeners); + listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + { + if ((listener->onupdate == fn) && + (listener->onupdate_arg == fn_arg)) + { + ISC_LIST_UNLINK(db->update_listeners, listener, link); + isc_mem_put(db->mctx, listener, + sizeof(dns_dbonupdatelistener_t)); + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} diff --git a/lib/dns/include/dns/catz.h b/lib/dns/include/dns/catz.h new file mode 100644 index 0000000000..917d958613 --- /dev/null +++ b/lib/dns/include/dns/catz.h @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef DNS_CATZ_H +#define DNS_CATZ_H 1 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +ISC_LANG_BEGINDECLS + +#define DNS_CATZ_ERROR_LEVEL ISC_LOG_WARNING +#define DNS_CATZ_INFO_LEVEL ISC_LOG_INFO +#define DNS_CATZ_DEBUG_LEVEL1 ISC_LOG_DEBUG(1) +#define DNS_CATZ_DEBUG_LEVEL2 ISC_LOG_DEBUG(2) +#define DNS_CATZ_DEBUG_LEVEL3 ISC_LOG_DEBUG(3) +#define DNS_CATZ_DEBUG_QUIET (DNS_CATZ_DEBUG_LEVEL3+1) + +/* + * Catalog Zones functions and structures. + */ + +/* + * Options for a member zone in a catalog + */ +struct dns_catz_entry_options { + /* + * Options that can be overriden in catalog zone + */ + /* masters definition */ + dns_ipkeylist_t masters; + + /* + * Options that are only set in named.conf + */ + /* zone should not be stored on disk (no 'file' statement in def */ + isc_boolean_t in_memory; + /* + * Minimal interval between catalog zone updates, if a new version + * of catalog zone is received before this time the update will be + * postponed. This is a global option for the whole catalog zone. + */ + isc_uint32_t min_update_interval; +}; + +void +dns_catz_options_init(dns_catz_options_t *options); +/*%< + * Initialize 'options' to NULL values. + * + * Requires: + * \li options to be non NULL + */ + +void +dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx); +/*%< + * Free 'options' contents into 'mctx'. ('options' itself is not freed.) + * + * Requires: + * \li options to be non NULL + * \li mctx to be a valid memory context + */ + +isc_result_t +dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *opts, + dns_catz_options_t *nopts); +/*%< + * Duplicate 'opts' into 'nopts', allocating space from 'mctx' + * + * Requires: + * \li 'mctx' to be a valid memory context + * \li 'options' to be non NULL and valid options + * \li 'nopts' to be non NULL + */ + +isc_result_t +dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults, + dns_catz_options_t *opts); +/*%< + * Replace empty values in 'opts' with values from 'defaults' + * + * Requires: + * \li mctx to be a valid memory context + * \li defaults to be non NULL and valid options + * \li opts to be non NULL + */ + +dns_name_t * +dns_catz_entry_getname(dns_catz_entry_t *entry); +/*%< + * Get domain name for 'entry' + * + * Requires: + * \li entry to be non NULL + * + * Returns: + * \li domain name for entry + */ + +isc_result_t +dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain, + dns_catz_entry_t **nentryp); +/*%< + * Allocate a new catz_entry on 'mctx', with the name 'domain' + * + * Requires: + * \li mctx to be a valid memory context + * \li domain to be valid dns_name or NULL + * \li nentryp to be non NULL, *nentryp to be NULL + * + * Returns: + * \li ISC_R_SUCCESS on success + * \li ISC_R_NOMEMORY on allocation failure + */ + +isc_result_t +dns_catz_entry_copy(dns_catz_zone_t *zone, const dns_catz_entry_t *entry, + dns_catz_entry_t **nentryp); +/*%< + * Allocate a new catz_entry and deep copy 'entry' into 'nentryp'. + * + * Requires: + * \li mctx to be a valid memory context + * \li entry to be non NULL + * \li nentryp to be non NULL, *nentryp to be NULL + * + * Returns: + * \li ISC_R_SUCCESS on success + * \li ISC_R_NOMEMORY on allocation failure + */ + +void +dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp); +/*%< + * Attach an entry + * + * Requires: + * \li entry is not NULL + * \li entryp is not NULL, *entryp is NULL + */ + +void +dns_catz_entry_detach(dns_catz_zone_t *zone, dns_catz_entry_t **entryp); +/*%< + * Detach an entry, free if no further references + * + * Requires: + * \li zone is not NULL + * \li entryp is not NULL, *entryp is not NULL + */ + +isc_result_t +dns_catz_entry_validate(const dns_catz_entry_t *entry); +/*%< + * Validate whether entry is correct. + * (NOT YET IMPLEMENTED: always returns true) + */ + +isc_boolean_t +dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb); +/*%< + * Deep compare two entries + * + * Requires: + * \li ea is not NULL + * \li eb is not NULL + * + * Returns: + * \li ISC_TRUE if entries are the same + * \li ISC_FALSE if the entries differ + */ + +void +dns_catz_zone_attach(dns_catz_zone_t *zone, dns_catz_zone_t **zonep); +/*%< + * Attach a catzone + * + * Requires: + * \li zone is not NULL + * \li zonep is not NULL, *zonep is NULL + */ + +void +dns_catz_zone_detach(dns_catz_zone_t** zonep); +/*%< + * Detach a zone, free if no further references + * + * Requires: + * \li zonep is not NULL, *zonep is not NULL + */ + +isc_result_t +dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep, + const dns_name_t *name); +/*%< + * Allocate a new catz zone on catzs mctx + * + * Requires: + * \li catzs is not NULL + * \li zonep is not NULL, *zonep is NULL + * \li name is not NULL + * + */ + +dns_name_t * +dns_catz_zone_getname(dns_catz_zone_t *zone); +/*%< + * Get catalog zone name + * + * Requires: + * \li zone is not NULL + */ + +dns_catz_options_t * +dns_catz_zone_getdefoptions(dns_catz_zone_t *zone); +/*%< + * Get default member zone options for catalog zone 'zone' + * + * Requires: + * \li zone is not NULL + */ + +void +dns_catz_zone_resetdefoptions(dns_catz_zone_t *zone); +/*%< + * Reset the default member zone options for catalog zone 'zone' to + * the default values. + * + * Requires: + * \li zone is not NULL + */ + +isc_result_t +dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone); +/*%< + * Merge 'newzone' into 'target', calling addzone/delzone/modzone + * (from zone->catzs->zmm) for appropriate member zones. + * + * Requires: + * \li orig is not NULL + * \li newzone is not NULL, *newzone is not NULL + * + */ + +isc_result_t +dns_catz_update_process(dns_catz_zones_t *catzs, dns_catz_zone_t *zone, + dns_name_t *src_name, dns_rdataset_t *rdataset); +/*%< + * Process a single rdataset from a catalog zone 'zone' update, src_name is the + * record name. + * + * Requires: + * \li catzs is not NULL + * \li zone is not NULL + * \li src_name is not NULL + * \li rdataset is valid + */ + +isc_result_t +dns_catz_generate_masterfilename(dns_catz_zone_t *zone, dns_catz_entry_t *entry, + isc_buffer_t **buffer); +/*%< + * Generate master file name and put it into *buffer (might be reallocated). + * The general format of the file name is: + * __catz__catalog.zone.name__member_zone_name.db + * But if it's too long it's shortened to: + * __catz__unique_hash_generated_from_the_above.db + * + * Requires: + * \li zone is not NULL + * \li entry is not NULL + * \li buffer is not NULL and *buffer is not NULL + */ + +isc_result_t +dns_catz_generate_zonecfg(dns_catz_zone_t *zone, dns_catz_entry_t *entry, + isc_buffer_t **buf); +/*%< + * Generate a zone config entry (in text form) from dns_catz_entry and puts + * it into *buf. buf might be reallocated. + * + * Requires: + * \li zone is not NULL + * \li entry is not NULL + * \li buf is not NULL + * \li *buf is NULL + * + */ + + +/* Methods provided by named to dynamically modify the member zones */ +/* xxxwpk TODO config! */ +struct dns_catz_zonemodmethods { + isc_result_t (*addzone)(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata); + isc_result_t (*modzone)(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata); + isc_result_t (*delzone)(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *task, void *udata); + void * udata; +}; + + +isc_result_t +dns_catz_new_zones(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm, + isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr); +/*%< + * Allocate a new catz_zones object, a collection storing all catalog zones + * for a view. + * + * Requires: + * \li catzsp is not NULL, *catzsp is NULL + * \li zmm is not NULL + * + */ + +isc_result_t +dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name, + dns_catz_zone_t **catzp); +/*%< + * Allocate a new catz named 'name' and put it in 'catzs' collection. + * + * Requires: + * \li catzs is not NULL + * \li name is not NULL + * \li zonep is not NULL, *zonep is NULL + * + */ + +dns_catz_zone_t * +dns_catz_get_zone(dns_catz_zones_t *catzs, const dns_name_t *name); +/*%< + * Returns a zone named 'name' from collection 'catzs' + * + * Requires: + * \li catzs is not NULL + * \li name is not NULL + */ + +void +dns_catz_catzs_attach(dns_catz_zones_t *catzs, dns_catz_zones_t **catzsp); +/*%< + * Attach 'catzs' to 'catzsp' + * + * Requires: + * \li catzs is not NULL + * \li catzsp is not NULL, *catzsp is NULL + */ + +void +dns_catz_catzs_detach(dns_catz_zones_t **catzsp); +/*%< + * Detach 'catzsp', free if no further references + * + * Requires: + * \li catzsp is not NULL, *catzsp is not NULL + */ + +void +dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view); +/*%< + * Set a view for catzs + * + * Requires: + * \li catzs is not NULL + * \li catzs->view is NULL or catzs->view == view + */ + + +isc_result_t +dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg); +/*%< + * Callback for update of catalog zone database. + * If there was no catalog zone update recently it launches an + * update_taskaction immediately. + * If there was an update recently it schedules update_taskaction for some time + * in the future. + * If there is an update scheduled it replaces old db version with a new one. + * + * Requires: + * \li db is a valid database + * \li fn_arg is not NULL (casted to dns_catz_zones_t*) + */ + +void +dns_catz_update_taskaction(isc_task_t *task, isc_event_t *event); +/*%< + * Task that launches dns_catz_update_from_db + * + * Requires: + * \li event is not NULL + */ + +void +dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs); +/*%< + * Process an updated database for a catalog zone. + * It creates a new catz, iterates over database to fill it with content, and + * then merges new catz into old catz. + * + * Requires: + * \li db is a valid DB + * \li catzs is not NULL + * + */ + +void +dns_catz_prereconfig(dns_catz_zones_t *catzs); +/*%< + * Called before reconfig, clears 'active' flag on all the zones in set + * + * Requires: + * \li catzs is not NULL + * + */ + +void +dns_catz_postreconfig(dns_catz_zones_t *catzs); +/*%< + * Called after reconfig, walks through all zones in set, removes those + * inactive and force reload of those with changed configuration. + * + * Requires: + * \li catzs is not NULL + */ + +isc_result_t +dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp); +/*%< + * Get the hashtable iterator on catalog zone members, point '*itp' to it. + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li Any other value -- failure + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_CATZ_H_ */ diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index 9715249f9a..dfb25d406e 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -203,6 +203,9 @@ typedef isc_result_t unsigned int argc, char *argv[], void *driverarg, dns_db_t **dbp); +typedef isc_result_t +(*dns_dbupdate_callback_t)(dns_db_t *db, void *fn_arg); + #define DNS_DB_MAGIC ISC_MAGIC('D','N','S','D') #define DNS_DB_VALID(db) ISC_MAGIC_VALID(db, DNS_DB_MAGIC) @@ -216,19 +219,26 @@ typedef isc_result_t * invariants. */ struct dns_db { - unsigned int magic; - unsigned int impmagic; - dns_dbmethods_t * methods; - isc_uint16_t attributes; - dns_rdataclass_t rdclass; - dns_name_t origin; - isc_ondestroy_t ondest; - isc_mem_t * mctx; + unsigned int magic; + unsigned int impmagic; + dns_dbmethods_t * methods; + isc_uint16_t attributes; + dns_rdataclass_t rdclass; + dns_name_t origin; + isc_ondestroy_t ondest; + isc_mem_t * mctx; + ISC_LIST(dns_dbonupdatelistener_t) update_listeners; }; #define DNS_DBATTR_CACHE 0x01 #define DNS_DBATTR_STUB 0x02 +struct dns_dbonupdatelistener { + dns_dbupdate_callback_t onupdate; + void * onupdate_arg; + ISC_LINK(dns_dbonupdatelistener_t) link; +}; + /*@{*/ /*% * Options that can be specified for dns_db_find(). @@ -1624,6 +1634,35 @@ dns_db_rpz_ready(dns_db_t *db); * Finish loading a response policy zone. */ +isc_result_t +dns_db_updatenotify_register(dns_db_t *db, + dns_dbupdate_callback_t fn, + void *fn_arg); +/*%< + * Register a notify-on-update callback function to a database. + * + * Requires: + * + * \li 'db' is a valid database + * \li 'db' does not have an update callback registered + * \li 'fn' is not NULL + * + */ + +isc_result_t +dns_db_updatenotify_unregister(dns_db_t *db, + dns_dbupdate_callback_t fn, + void *fn_arg); +/*%< + * Unregister a notify-on-update callback. + * + * Requires: + * + * \li 'db' is a valid database + * \li 'db' has update callback registered + * + */ + ISC_LANG_ENDDECLS #endif /* DNS_DB_H */ diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h index f43d40bacc..45a1709a9e 100644 --- a/lib/dns/include/dns/events.h +++ b/lib/dns/include/dns/events.h @@ -80,6 +80,10 @@ #define DNS_EVENT_KEYDONE (ISC_EVENTCLASS_DNS + 50) #define DNS_EVENT_SETNSEC3PARAM (ISC_EVENTCLASS_DNS + 51) #define DNS_EVENT_SETSERIAL (ISC_EVENTCLASS_DNS + 52) +#define DNS_EVENT_CATZUPDATED (ISC_EVENTCLASS_DNS + 53) +#define DNS_EVENT_CATZADDZONE (ISC_EVENTCLASS_DNS + 54) +#define DNS_EVENT_CATZMODZONE (ISC_EVENTCLASS_DNS + 55) +#define DNS_EVENT_CATZDELZONE (ISC_EVENTCLASS_DNS + 56) #define DNS_EVENT_FIRSTEVENT (ISC_EVENTCLASS_DNS + 0) #define DNS_EVENT_LASTEVENT (ISC_EVENTCLASS_DNS + 65535) diff --git a/lib/dns/include/dns/ipkeylist.h b/lib/dns/include/dns/ipkeylist.h new file mode 100644 index 0000000000..5066181c6e --- /dev/null +++ b/lib/dns/include/dns/ipkeylist.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DNS_IPKEYLIST_H +#define DNS_IPKEYLIST_H 1 + +#include +#include + +/*% + * A structure holding a list of addresses, dscps and keys. Used to + * store masters for a slave zone, created by parsing config options. + */ +struct dns_ipkeylist { + isc_sockaddr_t *addrs; + isc_dscp_t *dscps; + dns_name_t **keys; + isc_uint32_t count; +}; + +void +dns_ipkeylist_clear(isc_mem_t *mctx, dns_ipkeylist_t *ipkl); +/*%< + * Free `ipkl` contents using `mctx`. + * + * After this call, `ipkl` is a freshly cleared structure with all + * pointers set to `NULL` and count set to 0. + * + * Requires: + *\li 'mctx' to be a valid memory context. + *\li 'ipkl' to be non NULL and have its members `addrs` and `keys` + * allocated. 'dscps' might be NULL. + */ + +void +dns_ipkeylist_copy(isc_mem_t *mctx, const dns_ipkeylist_t *src, + dns_ipkeylist_t *dst); +/*%< + * Deep copy `src` into empty `dst`, allocating `dst`'s contents. + * + * Requires: + *\li 'mctx' to be a valid memory context. + *\li 'src' to be non NULL + *\li 'dst' to be non NULL and point to an empty \ref dns_ipkeylist_t + * with all pointers set to `NULL` and count set to 0. + */ + +#endif diff --git a/lib/dns/include/dns/rbt.h b/lib/dns/include/dns/rbt.h index ef45840d12..8129450d5b 100644 --- a/lib/dns/include/dns/rbt.h +++ b/lib/dns/include/dns/rbt.h @@ -391,7 +391,7 @@ dns_rbt_addnode(dns_rbt_t *rbt, dns_name_t *name, dns_rbtnode_t **nodep); */ isc_result_t -dns_rbt_findname(dns_rbt_t *rbt, dns_name_t *name, unsigned int options, +dns_rbt_findname(dns_rbt_t *rbt, const dns_name_t *name, unsigned int options, dns_name_t *foundname, void **data); /*%< * Get the data pointer associated with 'name'. @@ -430,7 +430,7 @@ dns_rbt_findname(dns_rbt_t *rbt, dns_name_t *name, unsigned int options, */ isc_result_t -dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, +dns_rbt_findnode(dns_rbt_t *rbt, const dns_name_t *name, dns_name_t *foundname, dns_rbtnode_t **node, dns_rbtnodechain_t *chain, unsigned int options, dns_rbtfindcallback_t callback, void *callback_arg); diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 54fe547e15..e29d28d359 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -45,6 +45,12 @@ typedef struct dns_adbfind dns_adbfind_t; typedef ISC_LIST(dns_adbfind_t) dns_adbfindlist_t; typedef struct dns_badcache dns_badcache_t; typedef struct dns_byaddr dns_byaddr_t; +typedef struct dns_catz_zonemodmethods dns_catz_zonemodmethods_t; +typedef struct dns_catz_entry_options dns_catz_options_t; +typedef struct dns_catz_entry dns_catz_entry_t; +typedef struct dns_catz_zone dns_catz_zone_t; +typedef struct dns_catz_changed dns_catz_changed_t; +typedef struct dns_catz_zones dns_catz_zones_t; typedef struct dns_client dns_client_t; typedef void dns_clientrestrans_t; typedef void dns_clientreqtrans_t; @@ -57,6 +63,7 @@ typedef struct dns_dbimplementation dns_dbimplementation_t; typedef struct dns_dbiterator dns_dbiterator_t; typedef void dns_dbload_t; typedef void dns_dbnode_t; +typedef struct dns_dbonupdatelistener dns_dbonupdatelistener_t; typedef struct dns_dbtable dns_dbtable_t; typedef void dns_dbversion_t; typedef struct dns_dlzimplementation dns_dlzimplementation_t; @@ -146,6 +153,7 @@ typedef struct dns_zone dns_zone_t; typedef ISC_LIST(dns_zone_t) dns_zonelist_t; typedef struct dns_zonemgr dns_zonemgr_t; typedef struct dns_zt dns_zt_t; +typedef struct dns_ipkeylist dns_ipkeylist_t; /* * If we are not using GSSAPI, define the types we use as opaque types here. diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index 9156e82e0a..c2e7e8d557 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -70,6 +70,7 @@ #include #include +#include #include #include #include @@ -178,6 +179,7 @@ struct dns_view { dns_dns64list_t dns64; unsigned int dns64cnt; dns_rpz_zones_t *rpzs; + dns_catz_zones_t *catzs; dns_dlzdblist_t dlz_searched; dns_dlzdblist_t dlz_unsearched; isc_uint32_t fail_ttl; diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 8837ea655e..b992047d02 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -15,8 +15,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id$ */ - #ifndef DNS_ZONE_H #define DNS_ZONE_H 1 @@ -32,6 +30,7 @@ #include #include +#include #include #include #include @@ -2393,6 +2392,30 @@ dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db); dns_rpz_num_t dns_zone_get_rpz_num(dns_zone_t *zone); +void +dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs); +/*%< + * Enable zone as catalog zone. + * + * Requires: + * + * \li 'zone' is a valid zone object + * \li 'catzs' is not NULL + * \li prior to calling, zone->catzs is NULL or is equal to 'catzs' + */ + +void +dns_zone_catz_enable_db(dns_zone_t *zone, dns_db_t *db); +/*%< + * If 'zone' is a catalog zone, then set up a notify-on-update trigger + * in its database. (If not a catalog zone, this function has no effect.) + * + * Requires: + * + * \li 'zone' is a valid zone object + * \li 'db' is not NULL + */ + void dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level); diff --git a/lib/dns/include/dns/zt.h b/lib/dns/include/dns/zt.h index f91d7e8dbe..6e5868ce71 100644 --- a/lib/dns/include/dns/zt.h +++ b/lib/dns/include/dns/zt.h @@ -90,7 +90,7 @@ dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone); */ isc_result_t -dns_zt_find(dns_zt_t *zt, dns_name_t *name, unsigned int options, +dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options, dns_name_t *foundname, dns_zone_t **zone); /*%< * Find the best match for 'name' in 'zt'. If foundname is non NULL diff --git a/lib/dns/ipkeylist.c b/lib/dns/ipkeylist.c new file mode 100644 index 0000000000..a3bef8ac9f --- /dev/null +++ b/lib/dns/ipkeylist.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +void +dns_ipkeylist_clear(isc_mem_t *mctx, dns_ipkeylist_t *ipkl) { + isc_uint32_t i; + + REQUIRE(ipkl != NULL); + REQUIRE(ipkl->addrs != NULL); + REQUIRE(ipkl->keys != NULL); + + if (ipkl->count == 0) + return; + + isc_mem_put(mctx, ipkl->addrs, ipkl->count * sizeof(isc_sockaddr_t)); + if (ipkl->dscps != NULL) + isc_mem_put(mctx, ipkl->dscps, + ipkl->count * sizeof(isc_dscp_t)); + for (i = 0; i < ipkl->count; i++) { + if (ipkl->keys[i] == NULL) + continue; + if (dns_name_dynamic(ipkl->keys[i])) + dns_name_free(ipkl->keys[i], mctx); + isc_mem_put(mctx, ipkl->keys[i], sizeof(dns_name_t)); + } + isc_mem_put(mctx, ipkl->keys, ipkl->count * sizeof(dns_name_t *)); + ipkl->count = 0; + ipkl->addrs = NULL; + ipkl->dscps = NULL; + ipkl->keys = NULL; +} + +void +dns_ipkeylist_copy(isc_mem_t *mctx, const dns_ipkeylist_t *src, + dns_ipkeylist_t *dst) +{ + isc_uint32_t i; + + REQUIRE(dst != NULL); + REQUIRE(dst->count == 0 && + dst->addrs == NULL && dst->keys == NULL && dst->dscps == NULL); + + if (src->count == 0) + return; + + dst->count = src->count; + + dst->addrs = isc_mem_get(mctx, + src->count * sizeof(isc_sockaddr_t)); + memmove(dst->addrs, src->addrs, + src->count * sizeof(isc_sockaddr_t)); + + if (src->dscps != NULL) { + dst->dscps = isc_mem_get(mctx, + src->count * sizeof(isc_dscp_t)); + memmove(dst->dscps, src->dscps, + src->count * sizeof(isc_dscp_t)); + } + + if (src->keys != NULL) { + dst->keys = isc_mem_get(mctx, src->count * sizeof(dns_name_t *)); + for (i = 0; i < src->count; i++) { + if (src->keys[i] != NULL) { + dst->keys[i] = isc_mem_get(mctx, + sizeof(dns_name_t)); + dns_name_dup(src->keys[i], mctx, dst->keys[i]); + } else { + dst->keys[i] = NULL; + } + } + } +} diff --git a/lib/dns/name.c b/lib/dns/name.c index dcb7ad6e6f..85345bcb07 100644 --- a/lib/dns/name.c +++ b/lib/dns/name.c @@ -1566,9 +1566,13 @@ dns_name_totext2(const dns_name_t *name, unsigned int options, if (nlen != 0 && trem == 0) return (ISC_R_NOSPACE); - if (!saw_root || omit_final_dot) + if (!saw_root || omit_final_dot) { trem++; - + tdata--; + } + if (trem > 0) { + *tdata = 0; + } isc_buffer_add(target, tlen - trem); #ifdef ISC_PLATFORM_USETHREADS diff --git a/lib/dns/rbt.c b/lib/dns/rbt.c index 8b6a699216..92d4aca885 100644 --- a/lib/dns/rbt.c +++ b/lib/dns/rbt.c @@ -1457,7 +1457,7 @@ dns_rbt_addname(dns_rbt_t *rbt, dns_name_t *name, void *data) { * Find the node for "name" in the tree of trees. */ isc_result_t -dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, +dns_rbt_findnode(dns_rbt_t *rbt, const dns_name_t *name, dns_name_t *foundname, dns_rbtnode_t **node, dns_rbtnodechain_t *chain, unsigned int options, dns_rbtfindcallback_t callback, void *callback_arg) @@ -1988,7 +1988,7 @@ dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, * Get the data pointer associated with 'name'. */ isc_result_t -dns_rbt_findname(dns_rbt_t *rbt, dns_name_t *name, unsigned int options, +dns_rbt_findname(dns_rbt_t *rbt, const dns_name_t *name, unsigned int options, dns_name_t *foundname, void **data) { dns_rbtnode_t *node = NULL; isc_result_t result; diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index cb98eeac90..e6954f51cd 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -1159,6 +1159,7 @@ free_rbtdb(dns_rbtdb_t *rbtdb, isc_boolean_t log, isc_event_t *event) { char buf[DNS_NAME_FORMATSIZE]; dns_rbt_t **treep; isc_time_t start; + dns_dbonupdatelistener_t *listener, *listener_next; if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) overmem((dns_db_t *)rbtdb, (isc_boolean_t)-1); @@ -1317,6 +1318,16 @@ free_rbtdb(dns_rbtdb_t *rbtdb, isc_boolean_t log, isc_event_t *event) { isc_file_munmap(rbtdb->mmap_location, (size_t) rbtdb->mmap_size); + for (listener = ISC_LIST_HEAD(rbtdb->common.update_listeners); + listener != NULL; + listener = listener_next) + { + listener_next = ISC_LIST_NEXT(listener, link); + ISC_LIST_UNLINK(rbtdb->common.update_listeners, listener, link); + isc_mem_put(rbtdb->common.mctx, listener, + sizeof(dns_dbonupdatelistener_t)); + } + isc_mem_putanddetach(&rbtdb->common.mctx, rbtdb, sizeof(*rbtdb)); isc_ondestroy_notify(&ondest, rbtdb); } @@ -8166,6 +8177,8 @@ dns_rbtdb_create rbtdb->common.rdclass = rdclass; rbtdb->common.mctx = NULL; + ISC_LIST_INIT(rbtdb->common.update_listeners); + result = RBTDB_INITLOCK(&rbtdb->lock); if (result != ISC_R_SUCCESS) goto cleanup_rbtdb; diff --git a/lib/dns/view.c b/lib/dns/view.c index 402c89b63b..bbce16c16b 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -229,6 +229,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, view->v6_aaaa = dns_aaaa_ok; view->aaaa_acl = NULL; view->rpzs = NULL; + view->catzs = NULL; dns_fixedname_init(&view->dlv_fixed); view->managed_keys = NULL; view->redirect = NULL; @@ -379,6 +380,8 @@ destroy(dns_view_t *view) { dns_rrl_view_destroy(view); if (view->rpzs != NULL) dns_rpz_detach_rpzs(&view->rpzs); + if (view->catzs != NULL) + dns_catz_catzs_detach(&view->catzs); for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL; dlzdb = ISC_LIST_HEAD(view->dlz_searched)) { @@ -578,6 +581,9 @@ view_flushanddetach(dns_view_t **viewp, isc_boolean_t flush) { if (view->flush) dns_zone_flush(rdzone); } + if (view->catzs != NULL) { + dns_catz_catzs_detach(&view->catzs); + } done = all_done(view); UNLOCK(&view->lock); diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 4c8230e24a..03651e5fca 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -109,6 +109,28 @@ dns_cache_setcachesize dns_cache_setcleaninginterval dns_cache_setfilename dns_cache_updatestats +dns_catz_catzs_attach +dns_catz_catzs_detach +dns_catz_catzs_set_view +dns_catz_dbupdate_callback +dns_catz_entry_attach +dns_catz_entry_cmp +dns_catz_entry_detach +dns_catz_entry_getname +dns_catz_entry_validate +dns_catz_get_iterator +dns_catz_get_zone +dns_catz_options_clear +dns_catz_options_init +dns_catz_postreconfig +dns_catz_prereconfig +dns_catz_update_from_db +dns_catz_update_taskaction +dns_catz_zone_attach +dns_catz_zone_detach +dns_catz_zone_getdefoptions +dns_catz_zone_getname +dns_catz_zones_merge dns_cert_fromtext dns_cert_totext dns_client_addtrustedkey @@ -209,6 +231,8 @@ dns_db_settask dns_db_subtractrdataset dns_db_transfernode dns_db_unregister +dns_db_updatenotify_register +dns_db_updatenotify_unregister dns_dbiterator_current dns_dbiterator_destroy dns_dbiterator_first diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index 63fd960edf..f49d1d8a13 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -279,8 +280,10 @@ axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp) { xfr->rdclass, 0, NULL, /* XXX guess */ dbp); - if (result == ISC_R_SUCCESS) + if (result == ISC_R_SUCCESS) { dns_zone_rpz_enable_db(xfr->zone, *dbp); + dns_zone_catz_enable_db(xfr->zone, *dbp); + } return (result); } diff --git a/lib/dns/zone.c b/lib/dns/zone.c index b09df67e62..74ae734f8e 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -380,6 +381,11 @@ struct dns_zone { dns_rpz_zones_t *rpzs; dns_rpz_num_t rpz_num; + /*% + * catalog zone data + */ + dns_catz_zones_t *catzs; + /*% * Serial number update method. */ @@ -1036,6 +1042,9 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->automatic = ISC_FALSE; zone->rpzs = NULL; zone->rpz_num = DNS_RPZ_INVALID_NUM; + + zone->catzs = NULL; + ISC_LIST_INIT(zone->forwards); zone->raw = NULL; zone->secure = NULL; @@ -1170,6 +1179,9 @@ zone_free(dns_zone_t *zone) { dns_rpz_detach_rpzs(&zone->rpzs); zone->rpz_num = DNS_RPZ_INVALID_NUM; } + if (zone->catzs != NULL) { + dns_catz_catzs_detach(&zone->catzs); + } zone_freedbargs(zone); RUNTIME_CHECK(dns_zone_setmasterswithkeys(zone, NULL, NULL, 0) == ISC_R_SUCCESS); @@ -1748,6 +1760,34 @@ dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db) { } } +void +dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(catzs != NULL); + + LOCK_ZONE(zone); + INSIST(zone->catzs == NULL || zone->catzs == catzs); + dns_catz_catzs_set_view(catzs, zone->view); + if (zone->catzs == NULL) + dns_catz_catzs_attach(catzs, &zone->catzs); + UNLOCK_ZONE(zone); +} + +/* + * If a zone is a catalog zone, attach it to update notification in database. + */ +void +dns_zone_catz_enable_db(dns_zone_t *zone, dns_db_t *db) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(db != NULL); + + if (zone->catzs != NULL) { + dns_db_updatenotify_register(db, dns_catz_dbupdate_callback, + zone->catzs); + } +} + + static isc_boolean_t zone_touched(dns_zone_t *zone) { isc_result_t result; @@ -2344,6 +2384,7 @@ zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime) { unsigned int options; dns_zone_rpz_enable_db(zone, db); + dns_zone_catz_enable_db(zone, db); options = get_master_options(zone); if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MANYERRORS)) options |= DNS_MASTER_MANYERRORS; diff --git a/lib/dns/zt.c b/lib/dns/zt.c index ffc76bbeb5..9079d367f7 100644 --- a/lib/dns/zt.c +++ b/lib/dns/zt.c @@ -155,7 +155,7 @@ dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone) { } isc_result_t -dns_zt_find(dns_zt_t *zt, dns_name_t *name, unsigned int options, +dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options, dns_name_t *foundname, dns_zone_t **zonep) { isc_result_t result; diff --git a/lib/isc/Makefile.in b/lib/isc/Makefile.in index d3a5de8299..26a1954545 100644 --- a/lib/isc/Makefile.in +++ b/lib/isc/Makefile.in @@ -57,8 +57,8 @@ OBJS = @ISC_EXTRA_OBJS@ @ISC_PK11_O@ @ISC_PK11_RESULT_O@ \ aes.@O@ assertions.@O@ backtrace.@O@ base32.@O@ base64.@O@ \ bind9.@O@ buffer.@O@ bufferlist.@O@ \ commandline.@O@ counter.@O@ crc64.@O@ error.@O@ event.@O@ \ - hash.@O@ heap.@O@ hex.@O@ hmacmd5.@O@ hmacsha.@O@ \ - httpd.@O@ inet_aton.@O@ iterated_hash.@O@ \ + hash.@O@ ht.@O@ heap.@O@ hex.@O@ hmacmd5.@O@ \ + hmacsha.@O@ httpd.@O@ inet_aton.@O@ iterated_hash.@O@ \ lex.@O@ lfsr.@O@ lib.@O@ log.@O@ \ md5.@O@ mem.@O@ mutexblock.@O@ \ netaddr.@O@ netscope.@O@ pool.@O@ ondestroy.@O@ \ @@ -77,8 +77,8 @@ CHACHASRCS = chacha_private.h SRCS = @ISC_EXTRA_SRCS@ @ISC_PK11_C@ @ISC_PK11_RESULT_C@ \ aes.c assertions.c backtrace.c base32.c base64.c bind9.c \ buffer.c bufferlist.c commandline.c counter.c crc64.c \ - error.c event.c heap.c hex.c hmacmd5.c hmacsha.c \ - httpd.c inet_aton.c iterated_hash.c \ + error.c event.c hash.c ht.c heap.c hex.c hmacmd5.c \ + hmacsha.c httpd.c inet_aton.c iterated_hash.c \ lex.c lfsr.c lib.c log.c \ md5.c mem.c mutexblock.c \ netaddr.c netscope.c pool.c ondestroy.c \ diff --git a/lib/isc/buffer.c b/lib/isc/buffer.c index b23601c387..2cf128f9a1 100644 --- a/lib/isc/buffer.c +++ b/lib/isc/buffer.c @@ -60,6 +60,7 @@ isc_buffer_reinit(isc_buffer_t *b, void *base, unsigned int length) { */ REQUIRE(b->length <= length); REQUIRE(base != NULL); + REQUIRE(b->autore = ISC_FALSE); (void)memmove(base, b->base, b->length); b->base = base; @@ -79,6 +80,13 @@ isc__buffer_invalidate(isc_buffer_t *b) { ISC__BUFFER_INVALIDATE(b); } +void +isc_buffer_setautorealloc(isc_buffer_t *b, isc_boolean_t enable) { + REQUIRE(ISC_BUFFER_VALID(b)); + REQUIRE(b->mctx != NULL); + b->autore = enable; +} + void isc__buffer_region(isc_buffer_t *b, isc_region_t *r) { /* @@ -279,8 +287,13 @@ isc_buffer_getuint8(isc_buffer_t *b) { void isc__buffer_putuint8(isc_buffer_t *b, isc_uint8_t val) { + isc_result_t result; REQUIRE(ISC_BUFFER_VALID(b)); - REQUIRE(b->used + 1 <= b->length); + if (b->autore) { + result = isc_buffer_reserve(&b, 1); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= 1); ISC__BUFFER_PUTUINT8(b, val); } @@ -308,16 +321,26 @@ isc_buffer_getuint16(isc_buffer_t *b) { void isc__buffer_putuint16(isc_buffer_t *b, isc_uint16_t val) { + isc_result_t result; REQUIRE(ISC_BUFFER_VALID(b)); - REQUIRE(b->used + 2 <= b->length); + if (b->autore) { + result = isc_buffer_reserve(&b, 2); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= 2); ISC__BUFFER_PUTUINT16(b, val); } void isc__buffer_putuint24(isc_buffer_t *b, isc_uint32_t val) { + isc_result_t result; REQUIRE(ISC_BUFFER_VALID(b)); - REQUIRE(b->used + 3 <= b->length); + if (b->autore) { + result = isc_buffer_reserve(&b, 3); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= 3); ISC__BUFFER_PUTUINT24(b, val); } @@ -347,8 +370,13 @@ isc_buffer_getuint32(isc_buffer_t *b) { void isc__buffer_putuint32(isc_buffer_t *b, isc_uint32_t val) { + isc_result_t result; REQUIRE(ISC_BUFFER_VALID(b)); - REQUIRE(b->used + 4 <= b->length); + if (b->autore == ISC_TRUE) { + result = isc_buffer_reserve(&b, 4); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= 4); ISC__BUFFER_PUTUINT32(b, val); } @@ -380,11 +408,16 @@ isc_buffer_getuint48(isc_buffer_t *b) { void isc__buffer_putuint48(isc_buffer_t *b, isc_uint64_t val) { + isc_result_t result; isc_uint16_t valhi; isc_uint32_t vallo; REQUIRE(ISC_BUFFER_VALID(b)); - REQUIRE(b->used + 6 <= b->length); + if (b->autore == ISC_TRUE) { + result = isc_buffer_reserve(&b, 6); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= 6); valhi = (isc_uint16_t)(val >> 32); vallo = (isc_uint32_t)(val & 0xFFFFFFFF); @@ -396,8 +429,13 @@ void isc__buffer_putmem(isc_buffer_t *b, const unsigned char *base, unsigned int length) { + isc_result_t result; REQUIRE(ISC_BUFFER_VALID(b)); - REQUIRE(b->used + length <= b->length); + if (b->autore == ISC_TRUE) { + result = isc_buffer_reserve(&b, length); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= length); ISC__BUFFER_PUTMEM(b, base, length); } @@ -406,6 +444,7 @@ void isc__buffer_putstr(isc_buffer_t *b, const char *source) { unsigned int l; unsigned char *cp; + isc_result_t result; REQUIRE(ISC_BUFFER_VALID(b)); REQUIRE(source != NULL); @@ -414,8 +453,11 @@ isc__buffer_putstr(isc_buffer_t *b, const char *source) { * Do not use ISC__BUFFER_PUTSTR(), so strlen is only done once. */ l = strlen(source); - - REQUIRE(l <= isc_buffer_availablelength(b)); + if (b->autore == ISC_TRUE) { + result = isc_buffer_reserve(&b, l); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= l); cp = isc_buffer_used(b); memmove(cp, source, l); @@ -448,16 +490,21 @@ isc_buffer_allocate(isc_mem_t *mctx, isc_buffer_t **dynbuffer, unsigned int length) { isc_buffer_t *dbuf; - + unsigned char * bdata; REQUIRE(dynbuffer != NULL); REQUIRE(*dynbuffer == NULL); - dbuf = isc_mem_get(mctx, length + sizeof(isc_buffer_t)); + dbuf = isc_mem_get(mctx, sizeof(isc_buffer_t)); if (dbuf == NULL) return (ISC_R_NOMEMORY); - isc_buffer_init(dbuf, ((unsigned char *)dbuf) + sizeof(isc_buffer_t), - length); + bdata = isc_mem_get(mctx, length); + if (bdata == NULL) { + isc_mem_put(mctx, dbuf, sizeof(isc_buffer_t)); + return (ISC_R_NOMEMORY); + } + + isc_buffer_init(dbuf, bdata, length); dbuf->mctx = mctx; ENSURE(ISC_BUFFER_VALID(dbuf)); @@ -469,7 +516,7 @@ isc_buffer_allocate(isc_mem_t *mctx, isc_buffer_t **dynbuffer, isc_result_t isc_buffer_reallocate(isc_buffer_t **dynbuffer, unsigned int length) { - isc_buffer_t *dbuf; + unsigned char *bdata; REQUIRE(dynbuffer != NULL); REQUIRE(ISC_BUFFER_VALID(*dynbuffer)); @@ -483,18 +530,16 @@ isc_buffer_reallocate(isc_buffer_t **dynbuffer, unsigned int length) { * it doesn't remap pages, but does ordinary copy. So is * isc_mem_reallocate(), which has additional issues. */ - dbuf = isc_mem_get((*dynbuffer)->mctx, length + sizeof(isc_buffer_t)); - if (dbuf == NULL) + bdata = isc_mem_get((*dynbuffer)->mctx, length); + if (bdata == NULL) return (ISC_R_NOMEMORY); - memmove(dbuf, *dynbuffer, (*dynbuffer)->length + sizeof(isc_buffer_t)); - isc_mem_put(dbuf->mctx, *dynbuffer, - (*dynbuffer)->length + sizeof(isc_buffer_t)); + memmove(bdata, (*dynbuffer)->base, (*dynbuffer)->length); + isc_mem_put((*dynbuffer)->mctx, (*dynbuffer)->base, + (*dynbuffer)->length); - dbuf->base = ((unsigned char *)dbuf) + sizeof(isc_buffer_t); - dbuf->length = length; - - *dynbuffer = dbuf; + (*dynbuffer)->base = bdata; + (*dynbuffer)->length = length; return (ISC_R_SUCCESS); } @@ -530,7 +575,6 @@ isc_buffer_reserve(isc_buffer_t **dynbuffer, unsigned int size) { void isc_buffer_free(isc_buffer_t **dynbuffer) { - unsigned int real_length; isc_buffer_t *dbuf; isc_mem_t *mctx; @@ -540,11 +584,10 @@ isc_buffer_free(isc_buffer_t **dynbuffer) { dbuf = *dynbuffer; *dynbuffer = NULL; /* destroy external reference */ - - real_length = dbuf->length + sizeof(isc_buffer_t); mctx = dbuf->mctx; dbuf->mctx = NULL; - isc_buffer_invalidate(dbuf); - isc_mem_put(mctx, dbuf, real_length); + isc_mem_put(mctx, dbuf->base, dbuf->length); + isc_buffer_invalidate(dbuf); + isc_mem_put(mctx, dbuf, sizeof(isc_buffer_t)); } diff --git a/lib/isc/ht.c b/lib/isc/ht.c new file mode 100644 index 0000000000..6d710cb4ba --- /dev/null +++ b/lib/isc/ht.c @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + + +typedef struct isc_ht_node isc_ht_node_t; + +#define ISC_HT_MAGIC ISC_MAGIC('H', 'T', 'a', 'b') +#define ISC_HT_VALID(ht) ISC_MAGIC_VALID(ht, ISC_HT_MAGIC) + +struct isc_ht_node { + void *value; + isc_ht_node_t *next; + size_t keysize; + unsigned char key[]; +}; + +struct isc_ht { + unsigned int magic; + isc_mem_t *mctx; + size_t size; + size_t mask; + unsigned int count; + isc_ht_node_t **table; +}; + +struct isc_ht_iter { + isc_ht_t *ht; + isc_uint32_t i; + isc_ht_node_t *cur; +}; + +isc_result_t +isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, isc_uint8_t bits) { + isc_ht_t *ht = NULL; + isc_uint32_t i; + + REQUIRE(htp != NULL && *htp == NULL); + REQUIRE(mctx != NULL); + REQUIRE(bits >= 1 && bits <= (sizeof(size_t)*8 - 1)); + + ht = isc_mem_get(mctx, sizeof(struct isc_ht)); + if (ht == NULL) { + return (ISC_R_NOMEMORY); + } + + ht->mctx = NULL; + isc_mem_attach(mctx, &ht->mctx); + + ht->size = (1<mask = (1<count = 0; + + ht->table = isc_mem_get(ht->mctx, ht->size * sizeof(isc_ht_node_t*)); + if (ht->table == NULL) { + isc_mem_putanddetach(&ht->mctx, ht, sizeof(struct isc_ht)); + return (ISC_R_NOMEMORY); + } + + for (i = 0; i < ht->size; i++) { + ht->table[i] = NULL; + } + + ht->magic = ISC_HT_MAGIC; + + *htp = ht; + return (ISC_R_SUCCESS); +} + +void +isc_ht_destroy(isc_ht_t **htp) { + isc_ht_t *ht; + isc_uint32_t i; + + REQUIRE(htp != NULL); + + ht = *htp; + REQUIRE(ISC_HT_VALID(ht)); + + ht->magic = 0; + + for (i = 0; i < ht->size; i++) { + isc_ht_node_t *node = ht->table[i]; + while (node != NULL) { + isc_ht_node_t *next = node->next; + ht->count--; + isc_mem_put(ht->mctx, node, + sizeof(isc_ht_node_t) + node->keysize); + node = next; + } + } + + INSIST(ht->count == 0); + + isc_mem_put(ht->mctx, ht->table, ht->size * sizeof(isc_ht_node_t*)); + isc_mem_putanddetach(&ht->mctx, ht, sizeof(struct isc_ht)); + + *htp = NULL; +} + +isc_result_t +isc_ht_add(isc_ht_t *ht, const unsigned char *key, + isc_uint32_t keysize, void *value) +{ + isc_ht_node_t *node; + isc_uint32_t hash; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + + hash = isc_hash_function(key, keysize, ISC_TRUE, NULL); + node = ht->table[hash & ht->mask]; + while (node != NULL) { + if (keysize == node->keysize && + memcmp(key, node->key, keysize) == 0) + { + return (ISC_R_EXISTS); + } + node = node->next; + } + + node = isc_mem_get(ht->mctx, sizeof(isc_ht_node_t) + keysize); + if (node == NULL) { + return (ISC_R_NOMEMORY); + } + + memmove(node->key, key, keysize); + node->keysize = keysize; + node->next = ht->table[hash & ht->mask]; + node->value = value; + + ht->count++; + ht->table[hash & ht->mask] = node; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_ht_find(const isc_ht_t *ht, const unsigned char *key, + isc_uint32_t keysize, void **valuep) +{ + isc_ht_node_t *node; + isc_uint32_t hash; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + REQUIRE(valuep != NULL); + + hash = isc_hash_function(key, keysize, ISC_TRUE, NULL); + node = ht->table[hash & ht->mask]; + while (node != NULL) { + if (keysize == node->keysize && + memcmp(key, node->key, keysize) == 0) + { + *valuep = node->value; + return (ISC_R_SUCCESS); + } + node = node->next; + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +isc_ht_delete(isc_ht_t *ht, const unsigned char *key, isc_uint32_t keysize) { + isc_ht_node_t *node, *prev; + isc_uint32_t hash; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + + prev = NULL; + hash = isc_hash_function(key, keysize, ISC_TRUE, NULL); + node = ht->table[hash & ht->mask]; + while (node != NULL) { + if (keysize == node->keysize && + memcmp(key, node->key, keysize) == 0) + { + if (prev == NULL) { + ht->table[hash & ht->mask] = node->next; + } else { + prev->next = node->next; + } + isc_mem_put(ht->mctx, node, + sizeof(isc_ht_node_t) + node->keysize); + ht->count--; + + return (ISC_R_SUCCESS); + } + + prev = node; + node = node->next; + } + return (ISC_R_NOTFOUND); +} + +isc_result_t +isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp) { + isc_ht_iter_t *it; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(itp != NULL && *itp == NULL); + + if (ht->count == 0) + return (ISC_R_NOMORE); + + it = isc_mem_get(ht->mctx, sizeof(isc_ht_iter_t)); + if (it == NULL) + return (ISC_R_NOMEMORY); + + it->ht = ht; + it->i = 0; + it->cur = NULL; + + *itp = it; + + return (ISC_R_SUCCESS); +} + +void +isc_ht_iter_destroy(isc_ht_iter_t **itp) { + isc_ht_iter_t *it; + isc_ht_t *ht; + + REQUIRE(itp != NULL && *itp != NULL); + + it = *itp; + ht = it->ht; + isc_mem_put(ht->mctx, it, sizeof(isc_ht_iter_t)); + + *itp = NULL; +} + +isc_result_t +isc_ht_iter_first(isc_ht_iter_t *it) { + REQUIRE(it != NULL); + + it->i = 0; + while (it->i < it->ht->size && it->ht->table[it->i] == NULL) + it->i++; + + if(it->i == it->ht->size) + return (ISC_R_NOMORE); + + it->cur = it->ht->table[it->i]; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_ht_iter_next(isc_ht_iter_t *it) { + REQUIRE(it != NULL); + REQUIRE(it->cur != NULL); + + it->cur = it->cur->next; + if (it->cur == NULL) { + while (it->i < it->ht->size && it->ht->table[it->i] == NULL) + it->i++; + if (it->i < it->ht->size) + return (ISC_R_NOMORE); + it->cur = it->ht->table[it->i]; + } + + return (ISC_R_SUCCESS); +} + +void +isc_ht_iter_current(isc_ht_iter_t *it, void **valuep) { + REQUIRE(it != NULL); + REQUIRE(it->cur != NULL); + *valuep = it->cur->value; +} + +unsigned int +isc_ht_count(isc_ht_t *ht) { + REQUIRE(ISC_HT_VALID(ht)); + + return(ht->count); +} + +isc_result_t +isc_ht_walk(isc_ht_t *ht, isc_ht_walkfn walkfn, void *udata) { + isc_uint32_t i; + isc_result_t res; + + REQUIRE(ISC_HT_VALID(ht)); + + for (i = 0; i < ht->size; i++) { + isc_ht_node_t *cur = ht->table[i]; + isc_ht_node_t *prev = NULL; + while (cur != NULL) { + if (walkfn == NULL) { + continue; + } + res = walkfn(udata, cur->key, cur->keysize, cur->value); + if (res != ISC_R_SUCCESS && res != ISC_R_EXISTS) { + return (res); + } + if (res == ISC_R_EXISTS) { /* remove this node */ + isc_ht_node_t *tmp = cur; + cur = cur->next; + if (prev == NULL) { + ht->table[i] = cur; + } else { + prev->next = cur; + } + isc_mem_put(ht->mctx, tmp, + sizeof(isc_ht_node_t) + tmp->keysize); + ht->count--; + } else { + prev = cur; + cur = cur->next; + } + } + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/include/isc/buffer.h b/lib/isc/include/isc/buffer.h index a6193a53c2..275d7ec664 100644 --- a/lib/isc/include/isc/buffer.h +++ b/lib/isc/include/isc/buffer.h @@ -184,6 +184,8 @@ struct isc_buffer { ISC_LINK(isc_buffer_t) link; /*! private internal elements */ isc_mem_t *mctx; + /* automatically realloc buffer at put* */ + isc_boolean_t autore; }; /*** @@ -316,6 +318,16 @@ isc__buffer_invalidate(isc_buffer_t *b); * calling isc_buffer_init() on it will cause an assertion failure. */ +void +isc_buffer_setautorealloc(isc_buffer_t *b, isc_boolean_t enable); +/*!< + * \brief Enable or disable autoreallocation on 'b'. + * + * Requires: + *\li 'b' is a valid dynamic buffer (b->mctx != NULL). + * + */ + void isc__buffer_region(isc_buffer_t *b, isc_region_t *r); /*!< @@ -530,7 +542,8 @@ isc__buffer_putuint8(isc_buffer_t *b, isc_uint8_t val); * Requires: *\li 'b' is a valid buffer. * - *\li The length of the unused region of 'b' is at least 1. + *\li The length of the unused region of 'b' is at least 1 + * or the buffer has autoreallocation enabled. * * Ensures: *\li The used pointer in 'b' is advanced by 1. @@ -546,7 +559,8 @@ isc_buffer_getuint16(isc_buffer_t *b); * *\li 'b' is a valid buffer. * - *\li The length of the available region of 'b' is at least 2. + *\li The length of the available region of 'b' is at least 2 + * or the buffer has autoreallocation enabled. * * Ensures: * @@ -566,7 +580,8 @@ isc__buffer_putuint16(isc_buffer_t *b, isc_uint16_t val); * Requires: *\li 'b' is a valid buffer. * - *\li The length of the unused region of 'b' is at least 2. + *\li The length of the unused region of 'b' is at least 2 + * or the buffer has autoreallocation enabled. * * Ensures: *\li The used pointer in 'b' is advanced by 2. @@ -602,7 +617,8 @@ isc__buffer_putuint32(isc_buffer_t *b, isc_uint32_t val); * Requires: *\li 'b' is a valid buffer. * - *\li The length of the unused region of 'b' is at least 4. + *\li The length of the unused region of 'b' is at least 4 + * or the buffer has autoreallocation enabled. * * Ensures: *\li The used pointer in 'b' is advanced by 4. @@ -638,7 +654,8 @@ isc__buffer_putuint48(isc_buffer_t *b, isc_uint64_t val); * Requires: *\li 'b' is a valid buffer. * - *\li The length of the unused region of 'b' is at least 6. + *\li The length of the unused region of 'b' is at least 6 + * or the buffer has autoreallocation enabled. * * Ensures: *\li The used pointer in 'b' is advanced by 6. @@ -653,7 +670,8 @@ isc__buffer_putuint24(isc_buffer_t *b, isc_uint32_t val); * Requires: *\li 'b' is a valid buffer. * - * The length of the unused region of 'b' is at least 3. + * The length of the unused region of 'b' is at least 3 + * or the buffer has autoreallocation enabled. * * Ensures: *\li The used pointer in 'b' is advanced by 3. @@ -666,7 +684,8 @@ isc__buffer_putmem(isc_buffer_t *b, const unsigned char *base, * \brief Copy 'length' bytes of memory at 'base' into 'b'. * * Requires: - *\li 'b' is a valid buffer. + *\li 'b' is a valid buffer, and it has at least 'length' + * or the buffer has autoreallocation enabled. * *\li 'base' points to 'length' bytes of valid memory. * @@ -682,7 +701,7 @@ isc__buffer_putstr(isc_buffer_t *b, const char *source); * *\li 'source' to be a valid NULL terminated string. * - *\li strlen(source) <= isc_buffer_available(b) + *\li strlen(source) <= isc_buffer_available(b) || b->mctx != NULL */ isc_result_t @@ -738,6 +757,7 @@ ISC_LANG_ENDDECLS (_b)->mctx = NULL; \ ISC_LINK_INIT(_b, link); \ (_b)->magic = ISC_BUFFER_MAGIC; \ + (_b)->autore = ISC_FALSE; \ } while (0) #define ISC__BUFFER_INITNULL(_b) ISC__BUFFER_INIT(_b, NULL, 0) diff --git a/lib/isc/include/isc/hash.h b/lib/isc/include/isc/hash.h index 571e74fd5c..d9b76bac2d 100644 --- a/lib/isc/include/isc/hash.h +++ b/lib/isc/include/isc/hash.h @@ -19,6 +19,7 @@ #define ISC_HASH_H 1 #include +#include /***** ***** Module Info @@ -105,7 +106,8 @@ isc_hash_create(isc_mem_t *mctx, isc_entropy_t *entropy, size_t limit); */ void -isc_hash_ctxattach(isc_hash_t *hctx, isc_hash_t **hctxp); +isc_hash_ctxattach(isc_hash_t *hctx, isc_hash_t **hctxp) + ISC_DEPRECATED; /*!< * \brief Attach to a hash object. * @@ -113,7 +115,8 @@ isc_hash_ctxattach(isc_hash_t *hctx, isc_hash_t **hctxp); */ void -isc_hash_ctxdetach(isc_hash_t **hctxp); +isc_hash_ctxdetach(isc_hash_t **hctxp) + ISC_DEPRECATED; /*!< * \brief Detach from a hash object. * @@ -158,10 +161,12 @@ isc_hash_init(void); /*@{*/ unsigned int isc_hash_ctxcalc(isc_hash_t *hctx, const unsigned char *key, - unsigned int keylen, isc_boolean_t case_sensitive); + unsigned int keylen, isc_boolean_t case_sensitive) + ISC_DEPRECATED; unsigned int isc_hash_calc(const unsigned char *key, unsigned int keylen, - isc_boolean_t case_sensitive); + isc_boolean_t case_sensitive) + ISC_DEPRECATED; /*!< * \brief Calculate a hash value. * @@ -183,7 +188,8 @@ isc_hash_calc(const unsigned char *key, unsigned int keylen, /*@}*/ void -isc__hash_setvec(const isc_uint16_t *vec); +isc__hash_setvec(const isc_uint16_t *vec) + ISC_DEPRECATED; /*!< * \brief Set the contents of the random vector used in hashing. diff --git a/lib/isc/include/isc/ht.h b/lib/isc/include/isc/ht.h new file mode 100644 index 0000000000..2d9d60c616 --- /dev/null +++ b/lib/isc/include/isc/ht.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* ! \file */ + +#ifndef ISC_HT_H +#define ISC_HT_H 1 + +#include +#include +#include + +typedef struct isc_ht isc_ht_t; +typedef struct isc_ht_iter isc_ht_iter_t; + +/*% + * Initialize hashtable at *htp, using memory context and size of (1<=1 && bits <=32 + * + * Returns: + *\li #ISC_R_NOMEMORY -- not enough memory to create pool + *\li #ISC_R_SUCCESS -- all is well. + */ +isc_result_t +isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, isc_uint8_t bits); + +/*% + * Destroy hashtable, freeing everything + * + * Requires: + * \li *htp is valid hashtable + */ +void +isc_ht_destroy(isc_ht_t **htp); + +/*% + * Add a node to hashtable, pointed by binary key 'key' of size 'keysize'; + * set its value to 'value' + * + * Requires: + *\li ht is a valid hashtable + * + * Returns: + *\li #ISC_R_NOMEMORY -- not enough memory to create pool + *\li #ISC_R_EXISTS -- node of the same key already exists + *\li #ISC_R_SUCCESS -- all is well. + */ +isc_result_t +isc_ht_add(isc_ht_t *ht, const unsigned char *key, isc_uint32_t keysize, + void *value); + +/*% + * Find a node matching 'key'/'keysize' in hashtable 'ht'; + * if found, set 'value' to its value + * + * Requires: + * \li 'ht' is a valid hashtable + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li #ISC_R_NOTFOUND -- key not found + */ +isc_result_t +isc_ht_find(const isc_ht_t *ht, const unsigned char *key, + isc_uint32_t keysize, void **valuep); + +/*% + * Delete node from hashtable + * Requires: + *\li ht is a valid hashtable + * + * Returns: + *\li #ISC_R_NOTFOUND -- key not found + *\li #ISC_R_SUCCESS -- all is well + */ +isc_result_t +isc_ht_delete(isc_ht_t *ht, const unsigned char *key, isc_uint32_t keysize); + + +typedef isc_result_t (*isc_ht_walkfn)(void *udata, const unsigned char *key, + isc_uint32_t keysize, void *data); + +/*% + * Create an iterator for the hashtable; point '*itp' to it. + */ +isc_result_t +isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp); + +/*% + * Destroy the iterator '*itp', set it to NULL + */ +void +isc_ht_iter_destroy(isc_ht_iter_t **itp); + +/*% + * Set an iterator to the first entry. + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li #ISC_R_NOMORE -- no data in the hashtable + */ +isc_result_t +isc_ht_iter_first(isc_ht_iter_t *it); + +/*% + * Set an iterator to the next entry. + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li #ISC_R_NOMORE -- end of hashtable reached + */ +isc_result_t +isc_ht_iter_next(isc_ht_iter_t *it); + +/*% + * Set 'value' to the current value under the iterator + */ +void +isc_ht_iter_current(isc_ht_iter_t *it, void **valuep); + +/*% + * Walks the hashtable, calling 'walkfn' on each node + * + * \li If 'walkfn' returns ISC_R_SUCCESS, walk is continued + * \li If 'walkfn' returns ISC_R_EXISTS, walk is continued but the + * node is removed + * \li If 'walkfn' returns anything else, walk is aborted and function returns + * this value + * + * Requires: + * \li 'ht' is a valid hashtable + * \li 'walkfn' is not NULL + * + * Returns: + * \li #ISC_R_SUCCESS -- all is well + * \li Any other, as returned by 'walkfn' + */ +isc_result_t +isc_ht_walk(isc_ht_t *ht, isc_ht_walkfn walkfn, void *udata); + +/*% + * Returns the number of items in the hashtable. + * + * Requires: + *\li 'ht' is a valid hashtable + */ +unsigned int +isc_ht_count(isc_ht_t *ht); +#endif diff --git a/lib/isc/include/isc/util.h b/lib/isc/include/isc/util.h index fc2e188264..0c7733b4a9 100644 --- a/lib/isc/include/isc/util.h +++ b/lib/isc/include/isc/util.h @@ -248,4 +248,13 @@ */ #define TIME_NOW(tp) RUNTIME_CHECK(isc_time_now((tp)) == ISC_R_SUCCESS) +/*% + * Misc. + */ +#ifdef __GNUC__ +#define ISC_DEPRECATED __attribute__((deprecated)) +#else +#define ISC_DEPRECATED /* none */ +#endif /* __GNUC __ */ + #endif /* ISC_UTIL_H */ diff --git a/lib/isc/mem.c b/lib/isc/mem.c index e190d452d1..ee77f9caa2 100644 --- a/lib/isc/mem.c +++ b/lib/isc/mem.c @@ -675,7 +675,6 @@ mem_getunlocked(isc__mem_t *ctx, size_t size) { new_size = size; goto done; } - /* * If there are no blocks in the free list for this size, get a chunk * of memory and then break it up into "new_size"-sized blocks, adding @@ -687,9 +686,11 @@ mem_getunlocked(isc__mem_t *ctx, size_t size) { /* * The free list uses the "rounded-up" size "new_size". */ + ret = ctx->freelists[new_size]; ctx->freelists[new_size] = ctx->freelists[new_size]->next; + /* * The stats[] uses the _actual_ "size" requested by the * caller, with the caveat (in the code above) that "size" >= the diff --git a/lib/isc/task.c b/lib/isc/task.c index ac049a7552..26b259a884 100644 --- a/lib/isc/task.c +++ b/lib/isc/task.c @@ -1723,7 +1723,10 @@ isc__task_beginexclusive(isc_task_t *task0) { isc__taskmgr_t *manager = task->manager; REQUIRE(task->state == task_state_running); - /* XXX: Require task == manager->excl? */ +/* + * TODO REQUIRE(task == task->manager->excl); + * it should be here, it fails on shutdown server->task + */ LOCK(&manager->lock); if (manager->exclusive_requested) { diff --git a/lib/isc/tests/Makefile.in b/lib/isc/tests/Makefile.in index b418366554..f918c29efb 100644 --- a/lib/isc/tests/Makefile.in +++ b/lib/isc/tests/Makefile.in @@ -41,7 +41,7 @@ SRCS = isctest.c taskpool_test.c socket_test.c hash_test.c \ parse_test.c pool_test.c print_test.c regex_test.c \ socket_test.c safe_test.c time_test.c aes_test.c \ file_test.c buffer_test.c counter_test.c mem_test.c \ - result_test.c + result_test.c ht_test.c SUBDIRS = TARGETS = taskpool_test@EXEEXT@ socket_test@EXEEXT@ hash_test@EXEEXT@ \ @@ -51,7 +51,7 @@ TARGETS = taskpool_test@EXEEXT@ socket_test@EXEEXT@ hash_test@EXEEXT@ \ print_test@EXEEXT@ regex_test@EXEEXT@ socket_test@EXEEXT@ \ safe_test@EXEEXT@ time_test@EXEEXT@ aes_test@EXEEXT@ \ file_test@EXEEXT@ buffer_test@EXEEXT@ counter_test@EXEEXT@ \ - mem_test@EXEEXT@ result_test@EXEEXT@ + mem_test@EXEEXT@ result_test@EXEEXT@ ht_test@EXEEXT@ @BIND9_MAKE_RULES@ @@ -144,6 +144,10 @@ result_test@EXEEXT@: result_test.@O@ ${ISCDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ result_test.@O@ ${ISCLIBS} ${LIBS} +ht_test@EXEEXT@: ht_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + ht_test.@O@ ${ISCLIBS} ${LIBS} + unit:: sh ${top_srcdir}/unit/unittest.sh diff --git a/lib/isc/tests/buffer_test.c b/lib/isc/tests/buffer_test.c index 51a9b4cec3..af0b76a628 100644 --- a/lib/isc/tests/buffer_test.c +++ b/lib/isc/tests/buffer_test.c @@ -135,11 +135,74 @@ ATF_TC_BODY(isc_buffer_reallocate, tc) { isc_test_end(); } +ATF_TC(isc_buffer_dynamic); +ATF_TC_HEAD(isc_buffer_dynamic, tc) { + atf_tc_set_md_var(tc, "descr", "dynamic buffer automatic reallocation"); +} + +ATF_TC_BODY(isc_buffer_dynamic, tc) { + isc_result_t result; + isc_buffer_t *b; + size_t last_length = 10; + int i; + + result = isc_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + b = NULL; + result = isc_buffer_allocate(mctx, &b, last_length); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE(b != NULL); + ATF_CHECK_EQ(b->length, last_length); + + isc_buffer_setautorealloc(b, ISC_TRUE); + + isc_buffer_putuint8(b, 1); + + for (i = 0; i < 1000; i++) { + isc_buffer_putstr(b, "thisisa24charslongstring"); + } + ATF_CHECK(b->length-last_length >= 1000*24); + last_length+=1000*24; + + for (i = 0; i < 10000; i++) { + isc_buffer_putuint8(b, 1); + } + + ATF_CHECK(b->length-last_length >= 10000*1); + last_length += 10000*1; + + for (i = 0; i < 10000; i++) { + isc_buffer_putuint16(b, 1); + } + + ATF_CHECK(b->length-last_length >= 10000*2); + + last_length += 10000*2; + for (i = 0; i < 10000; i++) { + isc_buffer_putuint24(b, 1); + } + ATF_CHECK(b->length-last_length >= 10000*3); + + last_length+=10000*3; + + for (i = 0; i < 10000; i++) { + isc_buffer_putuint32(b, 1); + } + ATF_CHECK(b->length-last_length >= 10000*4); + + + isc_buffer_free(&b); + + isc_test_end(); +} + /* * Main */ ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, isc_buffer_reserve); ATF_TP_ADD_TC(tp, isc_buffer_reallocate); + ATF_TP_ADD_TC(tp, isc_buffer_dynamic); return (atf_no_error()); } diff --git a/lib/isc/tests/ht_test.c b/lib/isc/tests/ht_test.c new file mode 100644 index 0000000000..cc5fe3eed6 --- /dev/null +++ b/lib/isc/tests/ht_test.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* ! \file */ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +static void * +default_memalloc(void *arg, size_t size) { + UNUSED(arg); + if (size == 0U) + size = 1; + return (malloc(size)); +} + +static void +default_memfree(void *arg, void *ptr) { + UNUSED(arg); + free(ptr); +} + + +static void test_ht_full(int bits, int count) { + isc_ht_t *ht = NULL; + isc_result_t result; + isc_mem_t *mctx = NULL; + isc_int64_t i; + + result = isc_mem_createx2(0, 0, default_memalloc, default_memfree, + NULL, &mctx, 0); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_ht_init(&ht, mctx, bits); + for (i = 1; i < count; i++) { + /* + * Note that the string we're snprintfing is always > 16 bytes + * so we are always filling the key. + */ + unsigned char key[16]; + snprintf((char *)key, 16, "%lld key of a raw hashtable!!", i); + result = isc_ht_add(ht, key, 16, (void *) i); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + snprintf((char *)key, 16, "%lld key of a raw hashtable!!", i); + result = isc_ht_find(ht, key, 16, &f); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(i, (isc_int64_t) f); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + snprintf((char *)key, 16, "%lld key of a raw hashtable!!", i); + result = isc_ht_add(ht, key, 16, (void *) i); + ATF_REQUIRE_EQ(result, ISC_R_EXISTS); + } + + for (i = 1; i < count; i++) { + char key[64]; + snprintf((char *)key, 64, "%lld key of a str hashtable!!", i); + result = isc_ht_add(ht, (const unsigned char *) key, + strlen(key), (void *) i); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + snprintf((char *)key, 16, "%lld KEY of a raw hashtable!!", i); + result = isc_ht_find(ht, key, 16, &f); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(f, NULL); + } + + for (i = 1; i < count; i++) { + char key[64]; + void *f = NULL; + snprintf((char *)key, 64, "%lld key of a str hashtable!!", i); + result = isc_ht_find(ht, (const unsigned char *) key, + strlen(key), &f); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(f, (void *) i); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + snprintf((char *)key, 16, "%lld key of a raw hashtable!!", i); + result = isc_ht_delete(ht, key, 16); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = isc_ht_find(ht, key, 16, &f); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(f, NULL); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + snprintf((char *)key, 16, "%lld KEY of a raw hashtable!!", i); + result = isc_ht_add(ht, key, 16, (void *) i); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + for (i = 1; i < count; i++) { + char key[64]; + void *f = NULL; + snprintf((char *)key, 64, "%lld key of a str hashtable!!", i); + result = isc_ht_delete(ht, (const unsigned char *) key, + strlen(key)); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = isc_ht_find(ht, (const unsigned char *) key, + strlen(key), &f); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(f, NULL); + } + + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + snprintf((char *)key, 16, "%lld KEY of a raw hashtable!!", i); + result = isc_ht_find(ht, key, 16, &f); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(i, (isc_int64_t) f); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + snprintf((char *)key, 16, "%lld key of a raw hashtable!!", i); + result = isc_ht_find(ht, key, 16, &f); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(f, NULL); + } + + isc_ht_destroy(&ht); + ATF_REQUIRE_EQ(ht, NULL); +} + +static isc_uint32_t walked = 0; + +typedef enum { + REGULAR, + ERASEEVEN, + ERASEODD, + CRASH +} walkmode_t; + +static isc_result_t walker(void *udata, const unsigned char *key, + isc_uint32_t keysize, void *data) +{ + char mykey[16]; + isc_uint64_t ii = (isc_uint64_t) data; + walkmode_t mode = (isc_uint64_t) udata; + ATF_REQUIRE_EQ(keysize, 16); + + snprintf(mykey, 16, "%lld key of a raw hashtable!!", ii); + ATF_REQUIRE_EQ(memcmp(mykey, key, 16), 0); + + walked++; + switch (mode) { + case REGULAR: + break; + case ERASEEVEN: + if (ii % 2 == 0) { + return (ISC_R_EXISTS); + } + break; + case ERASEODD: + if (ii % 2 != 0) { + return (ISC_R_EXISTS); + } + break; + case CRASH: + if (walked == 100) { + /* something as odd as possible */ + return (ISC_R_HOSTUNREACH); + } + break; + } + + return (ISC_R_SUCCESS); +} + +static void test_ht_walk() { + isc_ht_t *ht = NULL; + isc_result_t result; + isc_mem_t *mctx = NULL; + isc_int64_t i; + isc_uint32_t count = 10000; + + result = isc_mem_createx2(0, 0, default_memalloc, default_memfree, + NULL, &mctx, 0); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_ht_init(&ht, mctx, 16); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE(ht != NULL); + for (i = 1; i <= count; i++) { + /* + * Note that the string we're snprintfing is always > 16 bytes + * so we are always filling the key. + */ + unsigned char key[16]; + snprintf((char *)key, 16, "%lld key of a raw hashtable!!", i); + result = isc_ht_add(ht, key, 16, (void *) i); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + walked = 0; + result = isc_ht_walk(ht, walker, (void *) REGULAR); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(walked, count); + + walked = 0; + result = isc_ht_walk(ht, walker, (void *) CRASH); + ATF_REQUIRE_EQ(result, ISC_R_HOSTUNREACH); + ATF_REQUIRE_EQ(walked, 100); + + walked = 0; + result = isc_ht_walk(ht, walker, (void *) ERASEODD); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(walked, count); + + walked = 0; + result = isc_ht_walk(ht, walker, (void *) ERASEEVEN); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(walked, count/2); + + walked = 0; + result = isc_ht_walk(ht, walker, (void *) REGULAR); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(walked, 0); + + isc_ht_destroy(&ht); + ATF_REQUIRE_EQ(ht, NULL); +} + +ATF_TC(isc_ht_20); +ATF_TC_HEAD(isc_ht_20, tc) { + atf_tc_set_md_var(tc, "descr", "20 bit, 2M elements test"); +} + +ATF_TC_BODY(isc_ht_20, tc) { + UNUSED(tc); + test_ht_full(20, 2000000); +} + + +ATF_TC(isc_ht_8); +ATF_TC_HEAD(isc_ht_8, tc) { + atf_tc_set_md_var(tc, "descr", "8 bit, 20000 elements crowded test"); +} + +ATF_TC_BODY(isc_ht_8, tc) { + UNUSED(tc); + test_ht_full(8, 20000); +} + +ATF_TC(isc_ht_1); +ATF_TC_HEAD(isc_ht_1, tc) { + atf_tc_set_md_var(tc, "descr", "1 bit, 100 elements corner case test"); +} + +ATF_TC_BODY(isc_ht_1, tc) { + UNUSED(tc); + test_ht_full(1, 100); +} + +ATF_TC(isc_ht_32); +ATF_TC_HEAD(isc_ht_32, tc) { + atf_tc_set_md_var(tc, "descr", "32 bit, 10000 elements corner case test"); +} + +ATF_TC_BODY(isc_ht_32, tc) { + UNUSED(tc); + test_ht_full(32, 10000); +} + +ATF_TC(isc_ht_walk); +ATF_TC_HEAD(isc_ht_walk, tc) { + atf_tc_set_md_var(tc, "descr", "hashtable walking"); +} + +ATF_TC_BODY(isc_ht_walk, tc) { + UNUSED(tc); + test_ht_walk(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, isc_ht_20); + ATF_TP_ADD_TC(tp, isc_ht_8); + ATF_TP_ADD_TC(tp, isc_ht_1); + ATF_TP_ADD_TC(tp, isc_ht_32); + ATF_TP_ADD_TC(tp, isc_ht_walk); + return (atf_no_error()); +} + diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in index 3084078d4d..c339fc76d1 100644 --- a/lib/isc/win32/libisc.def.in +++ b/lib/isc/win32/libisc.def.in @@ -308,6 +308,13 @@ isc_hmacsha512_invalidate isc_hmacsha512_sign isc_hmacsha512_update isc_hmacsha512_verify +isc_ht_init +isc_ht_destroy +isc_ht_find +isc_ht_delete +isc_ht_iter_get +isc_ht_iter_inc +isc_ht_iter_value isc_httpd_addheader isc_httpd_addheaderuint isc_httpd_response diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index d23365ba27..a3ca7c8163 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -1472,6 +1472,42 @@ static cfg_type_t cfg_type_rpz = { rpz_fields }; +/* + * Catalog zones + */ +static cfg_type_t cfg_type_catz_zone = { + "zone", parse_keyvalue, print_keyvalue, + doc_keyvalue, &cfg_rep_string, + &zone_kw +}; + +static cfg_tuplefielddef_t catz_zone_fields[] = { + { "zone name", &cfg_type_catz_zone, 0 }, + { "default-masters", &cfg_type_namesockaddrkeylist, 0 }, + { "in-memory", &cfg_type_boolean, 0 }, + { "min-update-interval", &cfg_type_uint32, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_catz_tuple = { + "catz tuple", cfg_parse_kv_tuple, + cfg_print_kv_tuple, cfg_doc_kv_tuple, &cfg_rep_tuple, + catz_zone_fields +}; +static cfg_type_t cfg_type_catz_list = { + "zone list", cfg_parse_bracketed_list, cfg_print_bracketed_list, + cfg_doc_bracketed_list, &cfg_rep_list, + &cfg_type_catz_tuple +}; +static cfg_tuplefielddef_t catz_fields[] = { + { "zone list", &cfg_type_catz_list, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_catz = { + "catz", cfg_parse_kv_tuple, cfg_print_kv_tuple, + cfg_doc_kv_tuple, &cfg_rep_tuple, catz_fields +}; + + /* * rate-limit @@ -1627,6 +1663,7 @@ view_clauses[] = { { "attach-cache", &cfg_type_astring, 0 }, { "auth-nxdomain", &cfg_type_boolean, CFG_CLAUSEFLAG_NEWDEFAULT }, { "cache-file", &cfg_type_qstring, 0 }, + { "catalog-zones", &cfg_type_catz, 0 }, { "check-names", &cfg_type_checknames, CFG_CLAUSEFLAG_MULTI }, { "cleaning-interval", &cfg_type_uint32, 0 }, { "clients-per-query", &cfg_type_uint32, 0 },