Commit dde98863 authored by Vitezslav Kriz's avatar Vitezslav Kriz

zone-size-limit: axfr, ixfr transfer size limit

parent 52385c1a
......@@ -205,6 +205,7 @@ static const yp_item_t desc_remote[] = {
{ C_ZONEFILE_SYNC, YP_TINT, YP_VINT = { -1, INT32_MAX, 0, YP_STIME } }, \
{ C_IXFR_DIFF, YP_TBOOL, YP_VNONE }, \
{ C_MAX_JOURNAL_SIZE, YP_TINT, YP_VINT = { 0, INT64_MAX, INT64_MAX, YP_SSIZE } }, \
{ C_MAX_ZONE_SIZE, YP_TINT, YP_VINT = { 0, INT64_MAX, INT64_MAX, YP_SSIZE } }, \
{ C_KASP_DB, YP_TSTR, YP_VSTR = { "keys" } }, \
{ C_DNSSEC_SIGNING, YP_TBOOL, YP_VNONE }, \
{ C_DNSSEC_POLICY, YP_TREF, YP_VREF = { C_POLICY }, YP_FNONE, { check_ref_dflt } }, \
......
......@@ -63,6 +63,7 @@
#define C_MAX_JOURNAL_SIZE "\x10""max-journal-size"
#define C_MAX_TCP_CLIENTS "\x0F""max-tcp-clients"
#define C_MAX_UDP_PAYLOAD "\x0F""max-udp-payload"
#define C_MAX_ZONE_SIZE "\x0D""max-zone-size"
#define C_MODULE "\x06""module"
#define C_NOTIFY "\x06""notify"
#define C_NSEC3 "\x05""nsec3"
......
......@@ -20,6 +20,7 @@
#include "contrib/print.h"
#include "contrib/sockaddr.h"
#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "knot/nameserver/axfr.h"
#include "knot/nameserver/internet.h"
#include "knot/zone/zonefile.h"
......@@ -265,6 +266,7 @@ static void axfr_answer_cleanup(struct answer_data *data)
struct xfr_proc *proc = data->ext;
if (proc) {
zone_contents_deep_free(&proc->contents);
conf_free(proc->conf);
mm_free(data->mm, proc);
data->ext = NULL;
}
......@@ -281,16 +283,25 @@ static int axfr_answer_init(struct answer_data *data)
return KNOT_ENOMEM;
}
conf_t *conf;
int ret = conf_clone(&conf);
if (ret != KNOT_EOK) {
zone_contents_deep_free(&new_contents);
return ret;
}
/* Create new processing context. */
struct xfr_proc *proc = mm_alloc(data->mm, sizeof(struct xfr_proc));
if (proc == NULL) {
zone_contents_deep_free(&new_contents);
conf_free(conf);
return KNOT_ENOMEM;
}
memset(proc, 0, sizeof(struct xfr_proc));
proc->contents = new_contents;
gettimeofday(&proc->tstamp, NULL);
proc->conf = conf;
/* Set up cleanup callback. */
data->ext = proc;
......@@ -360,6 +371,10 @@ static int axfr_answer_packet(knot_pkt_t *pkt, struct xfr_proc *proc)
proc->npkts += 1;
proc->nbytes += pkt->size;
conf_val_t val = conf_zone_get(proc->conf, C_MAX_ZONE_SIZE,
proc->contents->apex->owner);
int64_t size_limit = conf_int(&val);
/* Init zone creator. */
zcreator_t zc = {.z = proc->contents, .master = false, .ret = KNOT_EOK };
......@@ -368,13 +383,21 @@ static int axfr_answer_packet(knot_pkt_t *pkt, struct xfr_proc *proc)
for (uint16_t i = 0; i < answer->count; ++i) {
if (answer_rr[i].type == KNOT_RRTYPE_SOA &&
node_rrtype_exists(zc.z->apex, KNOT_RRTYPE_SOA)) {
return KNOT_STATE_DONE;
log_zone_debug(proc->contents->apex->owner, "size: %zu", proc->contents->size);
return KNOT_STATE_DONE;
} else {
int ret = zcreator_step(&zc, &answer_rr[i]);
if (ret != KNOT_EOK) {
return KNOT_STATE_FAIL;
}
}
proc->contents->size += knot_rrset_size(&answer_rr[i]);
if (proc->contents->size > size_limit) {
log_zone_warning(proc->contents->apex->owner,
"AXFR, incoming, zone size exceeded, "
"limit %ld", size_limit);
return KNOT_STATE_FAIL;
}
}
return KNOT_STATE_CONSUME;
......
......@@ -25,6 +25,7 @@
#pragma once
#include "libknot/packet/pkt.h"
#include "knot/conf/conf.h"
#include "knot/nameserver/log.h"
#include "knot/query/query.h"
#include "knot/nameserver/process_query.h"
......@@ -60,6 +61,7 @@ struct xfr_proc {
unsigned nbytes; /* Bytes processed. */
struct timeval tstamp; /* Start time. */
zone_contents_t *contents; /* Processed zone. */
conf_t *conf;
};
/*! \brief Generic transfer processing (reused for IXFR).
......
......@@ -50,6 +50,8 @@ struct ixfr_proc {
knot_rrset_t *final_soa; /* First SOA received via IXFR. */
list_t changesets; /* Processed changesets. */
size_t change_count; /* Count of changesets received. */
size_t add_size;
size_t del_size;
zone_t *zone; /* Modified zone - for journal access. */
knot_mm_t *mm; /* Memory context for RR allocations. */
struct query_data *qdata;
......@@ -338,6 +340,7 @@ static void ixfrin_cleanup(struct answer_data *data)
if (proc) {
changesets_free(&proc->changesets);
knot_rrset_free(&proc->final_soa, proc->mm);
conf_free(proc->proc.conf);
mm_free(data->mm, proc);
data->ext = NULL;
}
......@@ -346,12 +349,21 @@ static void ixfrin_cleanup(struct answer_data *data)
/*! \brief Initializes IXFR-in processing context. */
static int ixfrin_answer_init(struct answer_data *data)
{
conf_t *conf;
int ret = conf_clone(&conf);
if (ret != KNOT_EOK) {
return ret;
}
struct ixfr_proc *proc = mm_alloc(data->mm, sizeof(struct ixfr_proc));
if (proc == NULL) {
conf_free(conf);
return KNOT_ENOMEM;
}
memset(proc, 0, sizeof(struct ixfr_proc));
gettimeofday(&proc->proc.tstamp, NULL);
proc->proc.conf = conf;
init_list(&proc->changesets);
......@@ -543,17 +555,34 @@ static int ixfrin_step(const knot_rrset_t *rr, struct ixfr_proc *proc)
proc->state = ixfrin_next_state(proc, rr);
changeset_t *change = TAIL(proc->changesets);
int ret;
switch (proc->state) {
case IXFR_START:
return solve_start(rr, proc);
case IXFR_SOA_DEL:
return solve_soa_del(rr, proc);
ret = solve_soa_del(rr, proc);
if (ret == KNOT_EOK) {
proc->del_size += knot_rrset_size(rr);
}
return ret;
case IXFR_DEL:
return solve_del(rr, change, proc->mm);
ret = solve_del(rr, change, proc->mm);
if (ret == KNOT_EOK) {
proc->del_size += knot_rrset_size(rr);
}
return ret;
case IXFR_SOA_ADD:
return solve_soa_add(rr, change, proc->mm);
ret = solve_soa_add(rr, change, proc->mm);
if (ret == KNOT_EOK) {
proc->add_size += knot_rrset_size(rr);
}
return ret;
case IXFR_ADD:
return solve_add(rr, change, proc->mm);
ret = solve_add(rr, change, proc->mm);
if (ret == KNOT_EOK) {
proc->add_size += knot_rrset_size(rr);
}
return ret;
case IXFR_DONE:
return KNOT_EOK;
default:
......@@ -589,6 +618,11 @@ static int process_ixfrin_packet(knot_pkt_t *pkt, struct answer_data *adata)
ixfr->proc.npkts += 1;
ixfr->proc.nbytes += pkt->size;
conf_val_t val = conf_zone_get(ixfr->proc.conf, C_MAX_ZONE_SIZE,
ixfr->zone->name);
const int64_t size_limit = conf_int(&val);
const size_t zone_size = ixfr->zone->contents->size;
// Process RRs in the message.
const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER);
for (uint16_t i = 0; i < answer->count; ++i) {
......@@ -612,6 +646,13 @@ static int process_ixfrin_packet(knot_pkt_t *pkt, struct answer_data *adata)
// Transfer done, do not consume more RRs.
return KNOT_STATE_DONE;
}
if ((ixfr->add_size + ixfr->del_size > 2 * size_limit) ||
(zone_size + ixfr->add_size - ixfr->del_size > size_limit)) {
IXFRIN_LOG(LOG_WARNING, "zone size exceeded, limit %ld",
size_limit);
return KNOT_STATE_FAIL;
}
}
return KNOT_STATE_CONSUME;
......
......@@ -41,6 +41,7 @@ typedef struct zone_contents {
zone_tree_t *nsec3_nodes;
dnssec_nsec3_params_t nsec3_params;
size_t size;
} zone_contents_t;
/*!
......
......@@ -144,6 +144,7 @@ static int check_rrsig(const zone_node_t *node, semchecks_data_t *data);
static int check_signed_rrsig(const zone_node_t *node, semchecks_data_t *data);
static int check_nsec_bitmap(const zone_node_t *node, semchecks_data_t *data);
static int check_nsec3_presence(const zone_node_t *node, semchecks_data_t *data);
static int measure_size(const zone_node_t *node, semchecks_data_t *data);
struct check_function {
int (*function)(const zone_node_t *, semchecks_data_t *);
......@@ -162,11 +163,24 @@ static const struct check_function CHECK_FUNCTIONS[] = {
{check_nsec3_presence, NSEC3},
{check_nsec3_opt_out, NSEC3},
{check_nsec_bitmap, NSEC | NSEC3},
{measure_size, MANDATORY}
};
static const int CHECK_FUNCTIONS_LEN = sizeof(CHECK_FUNCTIONS)
/ sizeof(struct check_function);
static int measure_size(const zone_node_t *node, semchecks_data_t *data){
int rrset_count = node->rrset_count;
for (int i = 0; i < rrset_count; i++) {
knot_rrset_t rrset = node_rrset_at(node, i);
data->zone->size += knot_rrset_size(&rrset);
}
return KNOT_EOK;
}
/*!
* \brief Check whether DNSKEY rdata are valid.
*
......@@ -916,6 +930,8 @@ int zone_do_sem_checks(zone_contents_t *zone, bool optional,
return KNOT_EINVAL;
}
zone->size = 0;
semchecks_data_t data = {
.handler = handler,
.zone = zone,
......@@ -937,6 +953,8 @@ int zone_do_sem_checks(zone_contents_t *zone, bool optional,
int ret = zone_contents_tree_apply_inorder(zone, do_checks_in_tree,
&data);
log_zone_debug(zone->apex->owner, "semcheck: size of zone: %zu", zone->size);
if (ret != KNOT_EOK) {
return ret;
}
......
......@@ -254,3 +254,22 @@ int knot_rrset_rr_to_canonical(knot_rrset_t *rrset)
return KNOT_EOK;
}
_public_
size_t knot_rrset_size(const knot_rrset_t *rrset)
{
if (rrset == NULL) {
return 0;
}
uint16_t rr_count = rrset->rrs.rr_count;
size_t total_size = knot_dname_size(rrset->owner) * rr_count;
for (size_t i = 0; i < rr_count; ++i) {
const knot_rdata_t *rr = knot_rdataset_at(&rrset->rrs, i);
assert(rr);
total_size += knot_rdata_rdlen(rr) + 10;
}
return total_size;
}
......@@ -194,4 +194,6 @@ bool knot_rrset_is_nsec3rel(const knot_rrset_t *rr);
*/
int knot_rrset_rr_to_canonical(knot_rrset_t *rrset);
size_t knot_rrset_size(const knot_rrset_t *rrset);
/*! @} */
$ORIGIN example.com.
$TTL 3600
@ SOA dns1 hostmaster 2010111200 10800 3600 1209600 7200
NS dns1
dns1 A 192.0.2.1
A 192.0.2.2
$ORIGIN example.com.
$TTL 3600
@ SOA dns1 hostmaster 2010111201 10800 3600 1209600 7200
NS dns1
dns1 A 192.0.2.1
A 192.0.2.2
test TXT "passed"
$ORIGIN example.com.
$TTL 3600
@ SOA dns1 hostmaster 2010111201 10800 3600 1209600 7200
NS dns1
dns1 A 192.0.2.1
A 192.0.2.2
test TXT "FAILED. This zone is larger than limit. More text: Lorem impsum dolor sit a met."
#!/usr/bin/env python3
'''Test for IXFR from Knot to Knot'''
from dnstest.test import Test
t = Test()
master1 = t.server("knot")
master2 = t.server("knot")
slave = t.server("knot")
zone = t.zone("example.com.", storage=".")
slave.zone_size_limit = 230
t.link(zone, master1, slave, ixfr=True)
t.link(zone, master2, slave, ixfr=True)
t.start()
# Wait for zones.
serial = master1.zone_wait(zone)
slave.zone_wait(zone)
# Update master file with the record (new SOA serial).
master1.update_zonefile(zone, version=2)
master1.reload()
# Wait for zones and compare them.
master1.zone_wait(zone, serial)
t.sleep(10)
master2.update_zonefile(zone, version=1) # smaller zone
master2.reload()
slave.zone_wait(zone, serial)
resp = slave.dig("test.example.com.", "TXT")
resp.check("passed")
#t.xfr_diff(master, slave, zone)
t.end()
......@@ -847,6 +847,7 @@ class Knot(Server):
self.daemon_bin = params.knot_bin
self.control_bin = params.knot_ctl
self.inquirer = dnstest.inquirer.Inquirer()
self.zone_size_limit = None
@property
def keydir(self):
......@@ -1033,6 +1034,8 @@ class Knot(Server):
modules += ", "
modules += module.get_conf_ref()
s.item("global-module", "[%s]" % modules)
if self.zone_size_limit:
s.item("max-zone-size", self.zone_size_limit)
s.end()
s.begin("zone")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment