serial-policy: increment | unixtime | dateserial
serial-modulo: INT/INT | +INT | -INT | INT/INT+INT | INT/INT-INT
reverse-generate: DNAME ...
+ include-from: DNAME ...
refresh-min-interval: TIME
refresh-max-interval: TIME
retry-min-interval: TIME
*Default:* none
+.. _zone_include-from:
+
+include-from
+------------
+
+A list of subzones that should be flattened into this zone. The flattening deletes
+all delegation-related records (including NS, SOA, ...) from both zones and copies
+all other records from the subzone to this zone.
+
+This feature works analogously to :ref:`zone_reverse-generate` in the way that subzones'
+records are being imported while loading this zone's zone file, and that it implies
+:ref:`zone_zonefile-load`: *difference-no-serial* and :ref:`zone_journal-content`: *all*.
+
+*Default:* none
+
.. _zone_refresh-min-interval:
refresh-min-interval
{ C_DS_PUSH, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI | CONF_REF_EMPTY | FLAGS, \
{ check_ref } }, \
{ C_REVERSE_GEN, YP_TDNAME,YP_VNONE, YP_FMULTI | FLAGS | CONF_IO_FRLD_ZONES }, \
+ { C_INCLUDE_FROM, YP_TDNAME,YP_VNONE, YP_FMULTI | FLAGS | CONF_IO_FDIFF_ZONES, { check_include_from } }, \
{ C_SERIAL_POLICY, YP_TOPT, YP_VOPT = { serial_policies, SERIAL_POLICY_INCREMENT } }, \
{ C_SERIAL_MODULO, YP_TSTR, YP_VSTR = { "0/1" }, YP_FNONE, { check_modulo_shift } }, \
{ C_ZONEMD_GENERATE, YP_TOPT, YP_VOPT = { zone_digest, ZONE_DIGEST_NONE }, FLAGS }, \
#define C_GLOBAL_MODULE "\x0D""global-module"
#define C_ID "\x02""id"
#define C_IDENT "\x08""identity"
+#define C_INCLUDE_FROM "\x0C""include-from"
#define C_INCL "\x07""include"
#define C_IXFR_BENEVOLENT "\x0F""ixfr-benevolent"
#define C_IXFR_BY_ONE "\x0B""ixfr-by-one"
}
}
+int check_include_from(
+ knotd_conf_check_args_t *args)
+{
+ if (knot_dname_in_bailiwick(args->data, args->id) < 0 ||
+ knot_dname_is_equal(args->data, args->id)) {
+ args->err_str = "not a subzone";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
#define CHECK_ZONE_INTERVALS(low_item, high_item) { \
conf_val_t high = conf_get_wrap(args, high_item); \
if (high.code == KNOT_EOK) { \
}
}
+ conf_val_t inc_from = conf_get_wrap(args, C_INCLUDE_FROM);
+ if (inc_from.code == KNOT_EOK) {
+ conf_val_t rev_from = conf_get_wrap(args, C_REVERSE_GEN);
+ if (rev_from.code == KNOT_EOK) {
+ args->err_str = "include-from not compatible with reverse-from";
+ return KNOT_EINVAL;
+ }
+ }
+
return KNOT_EOK;
}
knotd_conf_check_args_t *args
);
+int check_include_from(
+ knotd_conf_check_args_t *args
+);
+
int check_zone(
knotd_conf_check_args_t *args
);
// Note: zone->reverse_from!=NULL almost works, but we need to check if configured even when failed.
if (conf_zone_get(conf, C_REVERSE_GEN, zone->name).code == KNOT_EOK ||
+ conf_zone_get(conf, C_INCLUDE_FROM, zone->name).code == KNOT_EOK ||
zone->cat_members != NULL) { // This should be equivalent to setting catalog-role:generate.
zf_from = ZONEFILE_LOAD_DIFSE;
load_from = JOURNAL_CONTENT_ALL;
return ret;
}
+static bool flatten_apex_nocopy(uint16_t type)
+{
+ return type == KNOT_RRTYPE_SOA ||
+ type == KNOT_RRTYPE_NS ||
+ type == KNOT_RRTYPE_DNSKEY ||
+ type == KNOT_RRTYPE_NSEC3PARAM ||
+ type == KNOT_RRTYPE_CDNSKEY ||
+ type == KNOT_RRTYPE_CDS;
+}
+
+static bool flatten_apex_delete(uint16_t type)
+{
+ return type == KNOT_RRTYPE_NS ||
+ type == KNOT_RRTYPE_DS;
+}
+
+static int flatten_from_node(zone_node_t *node, void *data)
+{
+ rev_ctx_t *ctx = data;
+
+ bool apex = node_rrtype_exists(node, KNOT_RRTYPE_SOA);
+
+ int ret = KNOT_EOK;
+
+ zone_node_t *target_node = NULL;
+
+ for (int i = 0; i < node->rrset_count && ret == KNOT_EOK; i++) {
+ knot_rrset_t rrset = node_rrset_at(node, i);
+ if (apex && flatten_apex_nocopy(rrset.type)) {
+ continue;
+ }
+
+ assert(ctx->rev_upd == NULL); // not implemented with update in mind
+
+ ret = zone_contents_add_rr(ctx->rev_conts, &rrset, &target_node);
+ }
+
+ if (apex && target_node == NULL) {
+ target_node = (zone_node_t *)zone_contents_find_node(ctx->rev_conts, node->owner);
+ }
+
+ // TODO delete whole subtree from rev_conts BEFORE adding records from included zone?
+ for (int i = 0; apex && target_node != NULL && i < target_node->rrset_count && ret == KNOT_EOK; ) {
+ knot_rrset_t rrset = node_rrset_at(target_node, i);
+ if (flatten_apex_delete(rrset.type)) {
+ ret = zone_contents_remove_rr(ctx->rev_conts, &rrset, &target_node);
+ } else {
+ i++; // NOTE otherwise we jump to next RRSet by deleting the current one
+ }
+ }
+
+ return ret;
+}
+
int zone_reverse(zone_contents_t *from, zone_contents_t *to_conts,
- zone_update_t *to_upd, bool to_upd_rem)
+ zone_update_t *to_upd, bool to_upd_rem,
+ zone_include_method_t method)
{
const knot_dname_t *to_name;
if (to_upd != NULL) {
.ipv6 = (knot_dname_in_bailiwick(to_name, reverse6postfix) >= 0)
};
- return zone_contents_apply(from, reverse_from_node, &ctx);
+ switch (method) {
+ case ZONE_INCLUDE_REVERSE:
+ return zone_contents_apply(from, reverse_from_node, &ctx);
+ case ZONE_INCLUDE_FLATTEN:
+ assert(to_upd == NULL && to_conts != NULL); // flattening from changeset is problematic since SOA is no present in changeset's zone_contents
+ return zone_contents_apply(from, flatten_from_node, &ctx);
+ default:
+ assert(0);
+ return KNOT_ERROR;
+ }
}
int zones_reverse(list_t *zones, zone_contents_t *to_conts, const knot_dname_t **fail_fwd)
zone_include_t *n;
WALK_LIST(n, *zones) {
zone_t *z = n->include;
- assert(n->method == ZONE_INCLUDE_REVERSE);
rcu_read_lock();
if (z->contents == NULL) {
ret = KNOT_ETRYAGAIN;
} else {
- ret = zone_reverse(z->contents, to_conts, NULL, false);
+ ret = zone_reverse(z->contents, to_conts, NULL, false, n->method);
}
rcu_read_unlock();
if (ret != KNOT_EOK) {
* \param to_conts Out/optional: resulting reverse zone.
* \param to_upd Out/optional: resulting update of reverse zone.
* \param to_upd_rem Trigger removal from reverse zone.
+ * \param method Including mode.
*
* \return KNOT_E*
*/
int zone_reverse(zone_contents_t *from, zone_contents_t *to_conts,
- zone_update_t *to_upd, bool to_upd_rem);
+ zone_update_t *to_upd, bool to_upd_rem,
+ zone_include_method_t method);
inline static int changeset_reverse(changeset_t *from, zone_update_t *to)
{
- int ret = zone_reverse(from->remove, NULL, to, true);
+ int ret = zone_reverse(from->remove, NULL, to, true, ZONE_INCLUDE_REVERSE);
if (ret == KNOT_EOK) {
- ret = zone_reverse(from->add, NULL, to, false);
+ ret = zone_reverse(from->add, NULL, to, false, ZONE_INCLUDE_REVERSE);
}
return ret;
}
typedef enum {
ZONE_INCLUDE_REVERSE,
+ ZONE_INCLUDE_FLATTEN,
} zone_include_method_t;
typedef struct {
}
zone_includes_clear(zone);
+ zone_include_method_t method = ZONE_INCLUDE_REVERSE;
conf_val_t val = conf_zone_get(conf, C_REVERSE_GEN, zone->name);
+ if (val.code != KNOT_EOK) {
+ method = ZONE_INCLUDE_FLATTEN;
+ val = conf_zone_get(conf, C_INCLUDE_FROM, zone->name);
+ }
while (val.code == KNOT_EOK) {
const knot_dname_t *forw_name = conf_dname(&val);
zone_t *forw = knot_zonedb_find(db_new, forw_name);
log_zone_warning(zone->name, "zone to reverse %s does not exist",
forw_str);
} else {
- (void)zone_includes_add(zone, forw, ZONE_INCLUDE_REVERSE);
+ (void)zone_includes_add(zone, forw, method);
zone_local_notify_subscribe(forw, zone);
}
conf_val_next(&val);
--- /dev/null
+$ORIGIN com.cz.
+$TTL 3600
+
+@ SOA dns1 hostmaster 2010111201 10800 3600 1209600 7200
+ NS dns1
+ TXT "auth-txt"
+ CDS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3636B
+dns1 A 192.0.2.1
--- /dev/null
+$ORIGIN cz.
+$TTL 3600
+
+@ SOA dns1 hostmaster 2010111201 10800 3600 1209600 7200
+ NS dns1
+dns1 A 192.0.2.1
+com NS dns1
+com DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3636B
+com TXT "nonauth-txt"
+org NS dns1.org
+dns1.org A 192.0.2.2
--- /dev/null
+$ORIGIN net.cz.
+$TTL 3600
+
+@ SOA dns1 hostmaster 2010111201 10800 3600 1209600 7200
+ NS dns1
+dns1 A 192.0.2.1
--- /dev/null
+$ORIGIN org.cz.
+$TTL 3600
+
+@ SOA dns1 hostmaster 2010111201 10800 3600 1209600 7200
+ NS dns1
+dns1 A 192.0.2.1
--- /dev/null
+#!/usr/bin/env python3
+
+"""
+Test of flattening subzones.
+"""
+
+from dnstest.utils import *
+from dnstest.test import Test
+import random
+
+t = Test()
+
+master = t.server("knot") # only providing the subzones
+flattener = t.server("knot")
+slave = t.server("knot") # only slaving the flattened zone
+
+parent = t.zone("cz.", storage=".")
+childs = t.zone("com.cz.", storage=".") + t.zone("net.cz.", storage=".") + t.zone("org.cz.", storage=".")
+
+t.link(childs, master, flattener)
+t.link(parent, flattener, slave)
+
+flattener.zones[parent[0].name].include_from = childs
+
+flattener.dnssec(parent).enable = random.choice([False, True])
+
+t.start()
+serial = slave.zone_wait(parent)
+
+for z in childs:
+ for ty in [ "SOA", "NS", "DS", "CDS" ]:
+ r = slave.dig(z.name, ty)
+ r.check(rcode="NOERROR")
+ r.check_count(0, ty)
+ r = slave.dig("dns1." + z.name, "A")
+ r.check(rcode="NOERROR", rdata="192.0.2.1")
+
+r = slave.dig("dns1.org.cz", "A")
+r.check(rcode="NOERROR", rdata="192.0.2.2")
+
+r = slave.dig("com.cz.", "TXT")
+r.check(rcode="NOERROR", rdata="auth-txt")
+r.check(rcode="NOERROR", rdata="nonauth-txt")
+r.check_count(2, "TXT")
+
+up = master.update(childs[0])
+up.add("dns1", 3600, "AAAA", "1::2")
+up.send("NOERROR")
+
+serial = slave.zone_wait(parent, serial)
+r = slave.dig("dns1.com.cz.", "AAAA")
+r.check(rcode="NOERROR", rdata="1::2")
+
+flattener.zones[parent[0].name].zfile.append_rndTXT("txt.cz.", rdata="added-txt")
+if random.choice([False, True]):
+ flattener.ctl("zone-reload " + parent[0].name)
+else:
+ up = master.update(childs[1])
+ up.add("anything", 3600, "TXT", "dontcare")
+ up.send("NOERROR")
+
+serial = slave.zone_wait(parent, serial)
+r = slave.dig("txt.cz.", "TXT")
+r.check(rcode="NOERROR", rdata="added-txt")
+
+invalid_conf = random.choice(["include_self", "include_parent", "also_reverse"])
+if invalid_conf == "include_self":
+ flattener.zones[parent[0].name].include_from = parent
+elif invalid_conf == "include_parent":
+ flattener.zones[childs[0].name].include_from = parent
+else:
+ flattener.zones[parent[0].name].reverse_from = childs
+flattener.gen_confile()
+try:
+ flattener.reload()
+ set_err("INVALID CONF ACCEPTED: " + invalid_conf)
+except:
+ pass
+
+t.end()
self.journal_content = journal_content # journal contents
self.modules = []
self.reverse_from = None
+ self.include_from = None
self.external = None
self.dnssec = ZoneDnssec()
self.catalog_role = ZoneCatalogRole.NONE
if z.reverse_from:
s.item("reverse-generate", "[ " + ", ".join([ x.name for x in z.reverse_from ]) + " ]")
+ if z.include_from:
+ s.item("include-from", "[ " + ", ".join([ x.name for x in z.include_from ]) + " ]")
self._str(s, "refresh-min-interval", z.refresh_min)
self._str(s, "refresh-max-interval", z.refresh_max)