Commit d20d7251 authored by Jan Kadlec's avatar Jan Kadlec

Merge branch 'fix-expire-noauth-axfr' into '1.6'

expire zone when XFR is failing

fixes #304

See merge request !297
parents 0d3bf5f1 65b3d037
......@@ -864,7 +864,7 @@ int internet_query_plan(struct query_plan *plan)
/*! \brief Process answer to SOA query. */
static int process_soa_answer(knot_pkt_t *pkt, struct answer_data *data)
{
zone_t *zone = data->param->zone;
zone_t *zone = data->param->zone;
/* Expect SOA in answer section. */
const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER);
......@@ -884,6 +884,7 @@ static int process_soa_answer(knot_pkt_t *pkt, struct answer_data *data)
uint32_t their_serial = knot_soa_serial(&answer->rr[0].rrs);
if (knot_serial_compare(our_serial, their_serial) >= 0) {
ANSWER_LOG(LOG_INFO, data, "refresh, outgoing", "zone is up-to-date");
zone_events_cancel(zone, ZONE_EVENT_EXPIRE);
return NS_PROC_DONE; /* Our zone is up to date. */
}
......
......@@ -282,6 +282,11 @@ void zone_events_schedule_at(zone_t *zone, zone_event_type_t type, time_t time)
pthread_mutex_unlock(&events->mx);
}
bool zone_events_is_scheduled(zone_t *zone, zone_event_type_t type)
{
return zone_events_get_time(zone, type) > 0;
}
void zone_events_enqueue(zone_t *zone, zone_event_type_t type)
{
if (!zone || !valid_event(type)) {
......
......@@ -119,6 +119,14 @@ void zone_events_schedule_at(struct zone_t *zone, zone_event_type_t type, time_t
*/
void zone_events_schedule(struct zone_t *zone, zone_event_type_t type, unsigned dt);
/*!
* \brief Check if zone event is scheduled.
*
* \param zone Zone to check event of.
* \param type Type of event.
*/
bool zone_events_is_scheduled(struct zone_t *zone, zone_event_type_t type);
/*!
* \brief Cancel one zone event.
*
......
......@@ -196,7 +196,11 @@ static void schedule_dnssec(zone_t *zone, time_t refresh_at)
zone_events_schedule_at(zone, ZONE_EVENT_DNSSEC, refresh_at);
}
/* -- zone events handling callbacks --------------------------------------- */
/*! \brief Get SOA from zone. */
static const knot_rdataset_t *zone_soa(zone_t *zone)
{
return node_rdataset(zone->contents->apex, KNOT_RRTYPE_SOA);
}
/*! \brief Fetch SOA expire timer and add a timeout grace period. */
static uint32_t soa_graceful_expire(const knot_rdataset_t *soa)
......@@ -206,6 +210,18 @@ static uint32_t soa_graceful_expire(const knot_rdataset_t *soa)
return knot_soa_expire(soa) + 2 * conf()->max_conn_idle;
}
/*! \brief Schedule expire event, unless it is already scheduled. */
static void start_expire_timer(zone_t *zone, const knot_rdataset_t *soa)
{
if (zone_events_is_scheduled(zone, ZONE_EVENT_EXPIRE)) {
return;
}
zone_events_schedule(zone, ZONE_EVENT_EXPIRE, soa_graceful_expire(soa));
}
/* -- zone events handling callbacks --------------------------------------- */
int event_reload(zone_t *zone)
{
assert(zone);
......@@ -283,14 +299,11 @@ fail:
return result;
}
/* -- zone events implementation API ---------------------------------------- */
int event_refresh(zone_t *zone)
{
assert(zone);
zone_contents_t *contents = zone->contents;
if (zone_contents_is_empty(contents)) {
if (zone_contents_is_empty(zone->contents)) {
/* No contents, schedule retransfer now. */
zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW);
return KNOT_EOK;
......@@ -300,8 +313,7 @@ int event_refresh(zone_t *zone)
assert(master);
int ret = zone_query_execute(zone, KNOT_QUERY_NORMAL, master);
const knot_rdataset_t *soa = node_rdataset(contents->apex, KNOT_RRTYPE_SOA);
const knot_rdataset_t *soa = zone_soa(zone);
if (ret != KNOT_EOK) {
/* Log connection errors. */
ZONE_QUERY_LOG(LOG_WARNING, zone, master, "SOA query, outgoing",
......@@ -310,15 +322,10 @@ int event_refresh(zone_t *zone)
zone_master_rotate(zone);
/* Schedule next retry. */
zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_retry(soa));
if (zone_events_get_time(zone, ZONE_EVENT_EXPIRE) <= ZONE_EVENT_NOW) {
/* Schedule zone expiration if not previously planned. */
zone_events_schedule(zone, ZONE_EVENT_EXPIRE, soa_graceful_expire(soa));
}
start_expire_timer(zone, soa);
} else {
/* SOA query answered, reschedule refresh timer. */
zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
/* Cancel possible expire. */
zone_events_cancel(zone, ZONE_EVENT_EXPIRE);
}
return zone_events_write_persistent(zone);
......@@ -329,41 +336,49 @@ int event_xfer(zone_t *zone)
assert(zone);
/* Determine transfer type. */
bool is_bootstrap = false;
bool is_boostrap = zone_contents_is_empty(zone->contents);
uint16_t pkt_type = KNOT_QUERY_IXFR;
if (zone_contents_is_empty(zone->contents) || zone->flags & ZONE_FORCE_AXFR) {
if (is_boostrap || zone->flags & ZONE_FORCE_AXFR) {
pkt_type = KNOT_QUERY_AXFR;
is_bootstrap = true;
}
/* Execute zone transfer and reschedule timers. */
int ret = zone_query_transfer(zone, zone_master(zone), pkt_type);
if (ret == KNOT_EOK) {
assert(!zone_contents_is_empty(zone->contents));
/* New zone transferred, reschedule zone expiration and refresh
* timers and send notifications to slaves. */
const knot_rdataset_t *soa =
node_rdataset(zone->contents->apex, KNOT_RRTYPE_SOA);
zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
/* Sync zonefile immediately if configured. */
if (zone->conf->dbsync_timeout == 0) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
} else if (zone_events_get_time(zone, ZONE_EVENT_FLUSH) <= ZONE_EVENT_NOW) {
/* Plan sync if not previously planned. */
zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone->conf->dbsync_timeout);
}
zone->bootstrap_retry = ZONE_EVENT_NOW;
zone->flags &= ~ZONE_FORCE_AXFR;
/* Trim extra heap. */
if (!is_bootstrap) {
mem_trim();
/* Handle failure during transfer. */
if (ret != KNOT_EOK) {
if (is_boostrap) {
zone->bootstrap_retry = bootstrap_next(zone->bootstrap_retry);
zone_events_schedule(zone, ZONE_EVENT_XFER, zone->bootstrap_retry);
} else {
const knot_rdataset_t *soa = zone_soa(zone);
zone_events_schedule(zone, ZONE_EVENT_XFER, knot_soa_retry(soa));
start_expire_timer(zone, soa);
}
} else {
/* Zone contents is still empty, increment bootstrap retry timer
* and try again. */
zone->bootstrap_retry = bootstrap_next(zone->bootstrap_retry);
zone_events_schedule(zone, ZONE_EVENT_XFER, zone->bootstrap_retry);
return KNOT_EOK;
}
assert(!zone_contents_is_empty(zone->contents));
const knot_rdataset_t *soa = zone_soa(zone);
/* Rechedule events. */
zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
zone_events_cancel(zone, ZONE_EVENT_EXPIRE);
if (zone->conf->dbsync_timeout == 0) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
} else if (!zone_events_is_scheduled(zone, ZONE_EVENT_FLUSH)) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone->conf->dbsync_timeout);
}
/* Transfer cleanup. */
zone->bootstrap_retry = ZONE_EVENT_NOW;
zone->flags &= ~ZONE_FORCE_AXFR;
/* Trim extra heap. */
if (!is_boostrap) {
mem_trim();
}
return KNOT_EOK;
......
$ORIGIN example.
$TTL 1200
@ SOA ns admin 42 1 1 8 600
ns AAAA ::0
$ORIGIN example.
$TTL 1200
@ SOA ns admin 4242 1 1 8 600
ns AAAA ::0
#!/usr/bin/env python3
'''Test zone expiration by master shutdown or broken AXFR.'''
from dnstest.test import Test
def test_expire(zone, server):
resp = server.dig(zone[0].name, "SOA")
resp.check(rcode="SERVFAIL")
def break_xfrout(server):
with open(server.confile, "r+") as f:
config = f.read()
f.seek(0)
f.truncate()
config = config.replace("xfr-out ", "#xfr-out ")
f.write(config)
t = Test(tsig=False)
# this zone has refresh = 1s, retry = 1s and expire = 10s + 2s for connection timeouts
zone = t.zone("example.", storage=".")
EXPIRE_SLEEP = 15
master = t.server("knot")
slave = t.server("knot")
slave.max_conn_idle = "1s"
t.link(zone, master, slave)
t.start()
master.zone_wait(zone)
slave.zone_wait(zone)
# expire by shutting down the master
master.stop()
t.sleep(EXPIRE_SLEEP);
test_expire(zone, slave)
# bring back master (notifies slave)
master.start()
master.zone_wait(zone)
slave.zone_wait(zone)
# expire by breaking AXFR
break_xfrout(master)
master.update_zonefile(zone, version=1)
master.reload()
t.sleep(EXPIRE_SLEEP);
test_expire(zone, slave)
t.stop()
......@@ -391,7 +391,7 @@ class Server(object):
def gen_confile(self):
f = open(self.confile, mode="w")
f.write(self.get_config())
f.close
f.close()
def dig(self, rname, rtype, rclass="IN", udp=None, serial=None,
timeout=None, tries=3, flags="", bufsize=None, edns=None,
......
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