Commit f5ad57c2 authored by Jan Včelák's avatar Jan Včelák 🚀

Merge branch: TTL mismatch handling

parents 6edd412a 068dfa42
......@@ -61,6 +61,7 @@ const error_table_t knot_error_msgs[] = {
{ KNOT_EPAYLOAD, "Payload in OPT RR larger than max wire size." },
{ KNOT_ECRC, "CRC check failed." },
{ KNOT_EPREREQ, "UPDATE prerequisity not met." },
{ KNOT_ETTL, "TTL mismatch." },
{ KNOT_ENOXFR, "Transfer was not sent." },
{ KNOT_ENOIXFR, "Transfer is not IXFR (is in AXFR format)." },
{ KNOT_EXFRREFUSED, "Zone transfer refused by the server." },
......
......@@ -81,6 +81,7 @@ enum knot_error {
KNOT_EPAYLOAD, /*!< Payload in OPT RR larger than max wire size. */
KNOT_ECRC, /*!< Wrong dump CRC. */
KNOT_EPREREQ, /*!< UPDATE prerequisity not met. */
KNOT_ETTL, /*!< TTL mismatch. */
KNOT_ENOXFR, /*!< Transfer was not sent. */
KNOT_ENOIXFR, /*!< Transfer is not IXFR (is in AXFR format). */
KNOT_EXFRREFUSED, /*!< Zone transfer refused by the server. */
......
......@@ -742,7 +742,7 @@ static int remove_invalid_dnskeys(const knot_rrset_t *soa,
knot_rrset_t *to_remove = NULL;
int result = KNOT_EOK;
if (knot_rrset_rr_ttl(dnskeys, 0) != knot_rrset_rr_ttl(soa, 0)) {
if (!knot_rrset_ttl_equal(dnskeys, soa)) {
dbg_dnssec_detail("removing DNSKEYs (SOA TTL differs)\n");
result = knot_rrset_copy(dnskeys, &to_remove, NULL);
goto done;
......@@ -839,8 +839,7 @@ static int add_missing_dnskeys(const knot_rrset_t *soa,
knot_rrset_t *to_add = NULL;
int result = KNOT_EOK;
bool add_all = (dnskeys == NULL ||
knot_rrset_rr_ttl(dnskeys, 0) != knot_rrset_rr_ttl(soa, 0));
bool add_all = (dnskeys == NULL || !knot_rrset_ttl_equal(dnskeys, soa));
for (int i = 0; i < zone_keys->count; i++) {
const knot_zone_key_t *key = &zone_keys->keys[i];
......
......@@ -117,12 +117,25 @@ zone_t *load_zone_file(conf_zone_t *conf)
return NULL;
}
/* Create the new zone. */
zone_t *zone = zone_new((conf_zone_t *)conf);
if (zone == NULL) {
log_zone_error("Failed to create zone '%s': %s\n",
conf->name, knot_strerror(KNOT_ENOMEM));
return NULL;
}
struct stat st;
if (stat(conf->file, &st) < 0) {
/* Go silently and reset mtime to 0. */
memset(&st, 0, sizeof(struct stat));
}
/* Set the zone type (master/slave). If zone has no master set, we
* are the primary master for this zone (i.e. zone type = master).
*/
zl.creator->master = (zone_master(zone) == NULL);
/* Load the zone contents. */
knot_zone_contents_t *zone_contents = zonefile_load(&zl);
zonefile_close(&zl);
......@@ -130,15 +143,8 @@ zone_t *load_zone_file(conf_zone_t *conf)
/* Check the loader result. */
if (zone_contents == NULL) {
log_zone_error("Failed to load zone file '%s'.\n", conf->file);
return NULL;
}
/* Create the new zone. */
zone_t *zone = zone_new((conf_zone_t *)conf);
if (zone == NULL) {
log_zone_error("Failed to create zone '%s': %s\n",
conf->name, knot_strerror(KNOT_ENOMEM));
knot_zone_contents_deep_free(&zone_contents);
zone->conf = NULL;
zone_free(&zone);
return NULL;
}
......
......@@ -867,6 +867,21 @@ static int knot_ddns_add_rr_merge_normal(knot_rrset_t *node_rrset_copy,
dbg_ddns_verb("Merging normal RR to existing RRSet.\n");
/* First check if the TTL of the new RR is equal to that of the first
* RR in the node's RRSet. If not, refuse the UPDATE.
*/
if (!knot_rrset_ttl_equal(*rr_copy, node_rrset_copy)) {
assert(knot_rrset_type(*rr_copy) != KNOT_RRTYPE_RRSIG);
char type_str[16] = { '\0' };
knot_rrtype_to_string(knot_rrset_type(*rr_copy), type_str,
sizeof(type_str));
char *name = knot_dname_to_str(knot_rrset_owner(*rr_copy));
log_zone_error("UPDATE: TTL mismatch in %s, type %s\n",
name, type_str);
free(name);
return KNOT_ETTL;
}
int rdata_in_copy = knot_rrset_rr_count(*rr_copy);
int merged = 0, deleted_rrs = 0;
int ret = knot_rrset_merge_sort(node_rrset_copy, *rr_copy, &merged,
......
......@@ -267,7 +267,8 @@ int xfrin_process_axfr_packet(knot_pkt_t *pkt, knot_ns_xfr_t *xfr, knot_zone_con
}
// Init zone creator
zcreator_t zc = {.z = *zone, .ret = KNOT_EOK };
zcreator_t zc = {.z = *zone,
.master = false, .ret = KNOT_EOK };
while (ret == KNOT_EOK && rr) {
......@@ -943,6 +944,24 @@ dbg_xfrin_exec_detail(
* TODO: add the 'add' rrset to list of old RRSets?
*/
/* Check if the added RR has the same TTL as the first RR from the
* zone's RRSet. If not, log a warning.
* We assume that the added RRSet has only one RR, but that should be
* the case here.
*/
if (knot_rrset_type(add) != KNOT_RRTYPE_RRSIG
&& !knot_rrset_ttl_equal(add, *rrset)) {
char type_str[16] = { '\0' };
knot_rrtype_to_string(knot_rrset_type(add), type_str,
sizeof(type_str));
char *name = knot_dname_to_str(knot_rrset_owner(add));
char *zname = knot_dname_to_str(knot_node_owner(contents->apex));
log_zone_warning("Changes application to zone %s: TTL mismatch"
" in %s, type %s\n", zname, name, type_str);
free(name);
free(zname);
}
int merged, deleted_rrs;
ret = knot_rrset_merge_sort(*rrset, add, &merged, &deleted_rrs,
NULL);
......
......@@ -132,18 +132,25 @@ int knot_node_add_rrset_replace(knot_node_t *node, knot_rrset_t *rrset)
}
int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset,
knot_rrset_t **out_rrset)
bool *ttl_err)
{
if (node == NULL) {
if (node == NULL || rrset == NULL) {
return KNOT_EINVAL;
}
for (uint16_t i = 0; i < node->rrset_count; ++i) {
if (node->rrset_tree[i]->type == rrset->type) {
if (out_rrset) {
*out_rrset = node->rrset_tree[i];
}
int merged, deleted_rrs;
/* Check if the added RR has the same TTL as the first
* RR in the RRSet.
*/
if (ttl_err && knot_rrset_type(rrset) != KNOT_RRTYPE_RRSIG
&& !knot_rrset_ttl_equal(rrset,
node->rrset_tree[i])) {
*ttl_err = true;
}
int ret = knot_rrset_merge_sort(node->rrset_tree[i],
rrset, &merged,
&deleted_rrs, NULL);
......@@ -157,10 +164,6 @@ int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset,
}
}
// New RRSet (with one RR)
if (out_rrset) {
*out_rrset = rrset;
}
return knot_node_add_rrset_no_merge(node, rrset);
}
......
......@@ -131,7 +131,7 @@ knot_node_t *knot_node_new(const knot_dname_t *owner, knot_node_t *parent,
* \retval KNOT_ERROR if the RRSet could not be inserted.
*/
int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset,
knot_rrset_t **out_rrset);
bool *ttl_err);
int knot_node_add_rrset_replace(knot_node_t *node, knot_rrset_t *rrset);
......
......@@ -248,47 +248,6 @@ void err_handler_log_all(err_handler_t *handler)
}
}
/* TODO: optimize */
static bool rrset_ttls_equal(const knot_rrset_t *rrset)
{
uint16_t rr_count = knot_rrset_rr_count(rrset);
if (rr_count == 0) {
return true;
}
uint32_t prev_ttl = knot_rrset_rr_ttl(rrset, 0);
for (uint16_t i = 1; i < rr_count; ++i) {
uint32_t cur_ttl = knot_rrset_rr_ttl(rrset, i);
if (cur_ttl != prev_ttl) {
return false;
}
prev_ttl = cur_ttl;
}
return true;
}
/*!
* \brief Logs a warning if merging RRs with different TTLs.
*
* \param ttl_first TTL of the first RR in the RRSet.
* \param ttl_new TTL to be inserted.
* \param rr RRSet we're adding into.
* \param zname Zone name for logging.
*/
static void rrset_ttl_check(err_handler_t *handler,
const knot_rrset_t *rr, const knot_node_t *n)
{
if (rr->type != KNOT_RRTYPE_RRSIG && !rrset_ttls_equal(rr)) {
/* Prepare additional info string. */
char info_str[64] = { '\0' };
char type_str[16] = { '\0' };
knot_rrtype_to_string(rr->type, type_str, sizeof(type_str));
snprintf(info_str, sizeof(info_str), "Record type: %s.", type_str);
err_handler_handle_error(handler, n, ZC_ERR_TTL_MISMATCH, info_str);
}
}
/*!
* \brief Check whether DNSKEY rdata are valid.
*
......@@ -519,7 +478,7 @@ static int check_rrsig_in_rrset(err_handler_t *handler,
info_str);
}
if (knot_rrset_rr_ttl(rrset, 0) != knot_rrset_rr_ttl(rrsigs, 0)) {
if (!knot_rrset_ttl_equal(rrset, rrsigs)) {
err_handler_handle_error(handler, node,
ZC_ERR_RRSIG_TTL,
info_str);
......@@ -945,18 +904,6 @@ int sem_check_node_plain(const knot_zone_contents_t *zone,
}
}
int sem_check_rrset(const knot_node_t *node,
const knot_rrset_t *rrset,
err_handler_t *handler)
{
if (node == NULL || rrset == NULL || handler == NULL) {
return KNOT_EINVAL;
}
rrset_ttl_check(handler, rrset, node);
return KNOT_EOK;
}
/*!
* \brief Run semantic checks for node with DNSSEC-related types.
*
......
......@@ -224,19 +224,6 @@ int sem_check_node_plain(const knot_zone_contents_t *zone,
bool only_mandatory,
bool *fatal_error);
/*!
* \brief Checks RRSet for semantic errors. Logs errors via error handler.
*
* \param node Node containg the RRSet.
* \param rrset RRSet to be tested.
* \param handler Error handler.
*
* \return KNOT_E*
*/
int sem_check_rrset(const knot_node_t *node,
const knot_rrset_t *rrset,
err_handler_t *handler);
#endif // _KNOT_SEMANTIC_CHECK_H_
/*! @} */
......@@ -627,9 +627,9 @@ int knot_zone_contents_create_node(knot_zone_contents_t *contents,
/*----------------------------------------------------------------------------*/
static int insert_rr(knot_zone_contents_t *z, knot_rrset_t *rr, knot_node_t **n,
knot_rrset_t **rrset, bool nsec3)
bool nsec3, bool *ttl_err)
{
if (z == NULL || rr == NULL || n == NULL || rrset == NULL) {
if (z == NULL || rr == NULL || n == NULL || ttl_err == NULL) {
return KNOT_EINVAL;
}
......@@ -658,7 +658,7 @@ static int insert_rr(knot_zone_contents_t *z, knot_rrset_t *rr, knot_node_t **n,
}
}
return knot_node_add_rrset(*n, rr, rrset);
return knot_node_add_rrset(*n, rr, ttl_err);
}
static bool to_nsec3_tree(const knot_rrset_t *rr)
......@@ -666,11 +666,10 @@ static bool to_nsec3_tree(const knot_rrset_t *rr)
return knot_rrset_is_nsec3rel(rr);
}
int knot_zone_contents_add_rr(knot_zone_contents_t *z,
knot_rrset_t *rr, knot_node_t **n,
knot_rrset_t **rrset)
int knot_zone_contents_add_rr(knot_zone_contents_t *z, knot_rrset_t *rr,
knot_node_t **n, bool *ttl_err)
{
return insert_rr(z, rr, n, rrset, to_nsec3_tree(rr));
return insert_rr(z, rr, n, to_nsec3_tree(rr), ttl_err);
}
int knot_zone_contents_add_rrset(knot_zone_contents_t *zone,
......
......@@ -128,9 +128,8 @@ int knot_zone_contents_create_node(knot_zone_contents_t *contents,
const knot_rrset_t *rr,
knot_node_t **node);
int knot_zone_contents_add_rr(knot_zone_contents_t *z,
knot_rrset_t *rr, knot_node_t **n,
knot_rrset_t **rrset);
int knot_zone_contents_add_rr(knot_zone_contents_t *z, knot_rrset_t *rr,
knot_node_t **n, bool *ttl_err);
/*!
* \brief Adds a RRSet to the given zone.
......
......@@ -79,7 +79,9 @@ static bool handle_err(zcreator_t *zc,
int zcreator_step(zcreator_t *zc, knot_rrset_t *rr)
{
assert(zc && rr);
if (zc == NULL || rr == NULL || knot_rrset_rr_count(rr) != 1) {
return KNOT_EINVAL;
}
if (rr->type == KNOT_RRTYPE_SOA &&
knot_node_rrset(zc->z->apex, KNOT_RRTYPE_SOA)) {
......@@ -88,9 +90,9 @@ int zcreator_step(zcreator_t *zc, knot_rrset_t *rr)
return KNOT_EOK;
}
bool ttl_err = false;
knot_node_t *node = NULL;
knot_rrset_t *zone_rrset = NULL;
int ret = knot_zone_contents_add_rr(zc->z, rr, &node, &zone_rrset);
int ret = knot_zone_contents_add_rr(zc->z, rr, &node, &ttl_err);
if (ret < 0) {
if (!handle_err(zc, rr, ret)) {
// Fatal error
......@@ -99,20 +101,41 @@ int zcreator_step(zcreator_t *zc, knot_rrset_t *rr)
// Recoverable error, skip record
knot_rrset_free(&rr, NULL);
return KNOT_EOK;
} else if (ret > 0) {
knot_rrset_free(&rr, NULL);
}
assert(node);
assert(zone_rrset);
// Do RRSet and node semantic checks
bool sem_fatal_error = false;
err_handler_t err_handler;
err_handler_init(&err_handler);
ret = sem_check_rrset(node, zone_rrset, &err_handler);
if (ret != KNOT_EOK) {
return ret;
/* Check if TTL of the added RR is equal to the TTL of the RRSet.
* It's sufficient to compare with the first RR, because each RR in the
* RRSet already underwent this check.
*/
if (ttl_err) {
/* Prepare additional info string. */
char info_str[64] = { '\0' };
char type_str[16] = { '\0' };
knot_rrtype_to_string(rr->type, type_str, sizeof(type_str));
snprintf(info_str, sizeof(info_str), "Record type: %s.",
type_str);
if (zc->master) {
/*! \todo REPLACE WITH FATAL ERROR */
err_handler_handle_error(&err_handler, node,
ZC_ERR_TTL_MISMATCH, info_str);
return KNOT_EMALF;
} else {
err_handler_handle_error(&err_handler, node,
ZC_ERR_TTL_MISMATCH, info_str);
}
}
if (ret > 0) {
knot_rrset_free(&rr, NULL);
}
ret = sem_check_node_plain(zc->z, node,
&err_handler, true,
&sem_fatal_error);
......
......@@ -37,6 +37,8 @@
*/
typedef struct zcreator {
knot_zone_contents_t *z; /*!< Created zone. */
bool master; /*!< Master flag. True if server is a primary
master for the zone. */
int ret; /*!< Return value. */
} zcreator_t;
......
......@@ -756,6 +756,16 @@ void knot_rrset_rr_set_ttl(const knot_rrset_t *rrset, size_t pos, uint32_t ttl)
}
}
bool knot_rrset_ttl_equal(const knot_rrset_t *r1, const knot_rrset_t *r2)
{
if (r1 == NULL || r2 == NULL
|| r1->rr_count == 0 || r2->rr_count == 0) {
return false;
}
return (knot_rrset_rr_ttl(r1, 0) == knot_rrset_rr_ttl(r2, 0));
}
const knot_dname_t *knot_rrset_owner(const knot_rrset_t *rrset)
{
return rrset->owner;
......
......@@ -219,6 +219,17 @@ uint32_t knot_rrset_rr_ttl(const knot_rrset_t *rrset, size_t pos);
*/
void knot_rrset_rr_set_ttl(const knot_rrset_t *rrset, size_t pos, uint32_t ttl);
/*!
* \brief Compares TTL of first RRs of two RRSets.
*
* \param r1 First RRSet.
* \param r2 Second RRSet.
*
* \retval True if TTLs are equal.
* \retval False otherwise.
*/
bool knot_rrset_ttl_equal(const knot_rrset_t *r1, const knot_rrset_t *r2);
/*!
* \brief Returns count of RRs in RRSet.
*
......
$ORIGIN ttl-mismatch.
$TTL 3600
@ SOA dns1 hostmaster 2010111201 10800 3600 1209600 7200
NS dns1
NS dns2
MX 10 mail
ttl A 192.0.2.1
A 192.0.3.1
7200 A 192.0.4.1
AAAA 2001:DB8::1
AAAA 2001:DB9::1
#!/usr/bin/env python3
'''Test for loading of zone containing mismatched TTLs'''
from dnstest.test import Test
t = Test()
master = t.server("knot")
zone = t.zone("ttl-mismatch", storage=".")
t.link(zone, master)
t.start()
# Just check if the zone was loaded. It should be refused on master.
resp = master.dig(zone, "SOA")
resp.check(rcode="REFUSED", flags="QR", noflags="AA TC AD RA")
t.end()
$ORIGIN ttl-mismatch.
$TTL 3600
@ SOA dns1 hostmaster 2010111201 10800 3600 1209600 7200
NS dns1
NS dns2
MX 10 mail
ttl A 192.0.2.1
A 192.0.3.1
7200 A 192.0.4.1
AAAA 2001:DB8::1
AAAA 2001:DB9::1
#!/usr/bin/env python3
'''Test for mismatched TTLs handling on slave zone load.'''
'''NOTE: dnspython can't keep different TTLs in one rrset. So we can't check
the slave server properly.'''
from dnstest.test import Test
t = Test()
master = t.server("dummy")
slave = t.server("knot")
zone = t.zone("ttl-mismatch.", storage=".", exists=False)
t.link(zone, master, slave)
# Create invalid zone file.
slave.update_zonefile(zone, version=1)
t.start()
# Check if the zone was loaded.
resp = slave.dig("ttl.ttl-mismatch.", "A")
resp.check(rcode="NOERROR", flags="QR AA", noflags="TC AD RA")
t.end()
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