diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c index 491aba4e8a5d14253f0c1f6d928ee2d798886553..b2d729933d29c308af55e1d36ab80d79e204f656 100644 --- a/src/knot/ctl/remote.c +++ b/src/knot/ctl/remote.c @@ -119,7 +119,7 @@ static int remote_zone_refresh(server_t *server, const zone_t *zone) return KNOT_EINVAL; } - zones_schedule_refresh((zone_t *)zone, knot_random_uint32_t() % 1000); + zones_schedule_refresh((zone_t *)zone, REFRESH_NOW); return KNOT_EOK; } @@ -131,7 +131,7 @@ static int remote_zone_flush(server_t *server, const zone_t *zone) return KNOT_EINVAL; } - zones_schedule_zonefile_sync((zone_t *)zone, knot_random_uint32_t() % 1000); + zones_schedule_zonefile_sync((zone_t *)zone, REFRESH_NOW); return KNOT_EOK; } @@ -327,7 +327,7 @@ static int remote_c_refresh(server_t *s, remote_cmdargs_t* a) dbg_server("remote: %s\n", __func__); if (a->argc == 0) { dbg_server_verb("remote: refreshing all zones\n"); - knot_zonedb_foreach(s->zone_db, zones_schedule_refresh, 0); + knot_zonedb_foreach(s->zone_db, zones_schedule_refresh, REFRESH_NOW); return KNOT_EOK; } diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c index e5a1dfe110f3253a9393e6256151f62aebc05ece..b14e410de9fdcccdac9e88a4921cc5c0e5eb27be 100644 --- a/src/knot/nameserver/update.c +++ b/src/knot/nameserver/update.c @@ -9,6 +9,7 @@ #include "knot/updates/ddns.h" #include "common/descriptor.h" #include "knot/server/zones.h" +#include "libknot/tsig-op.h" /* Forward decls. */ static int zones_process_update_auth(zone_t *zone, knot_pkt_t *query, @@ -46,19 +47,32 @@ static int update_forward(knot_pkt_t *pkt, struct query_data *qdata) rq->packet_nr = knot_wire_get_id(query->wire); /* Duplicate query to keep it in memory during forwarding. */ - rq->query = knot_pkt_new(NULL, query->size, NULL); - if (!rq->query) { + rq->query = knot_pkt_new(NULL, query->max_size, NULL); + if (rq->query == NULL) { xfr_task_free(rq); return NS_PROC_FAIL; + } else { + memcpy(rq->query->wire, query->wire, query->size); + rq->query->size = query->size; + } + + /* Copy TSIG. */ + int ret = KNOT_EOK; + if (query->tsig_rr) { + ret = knot_tsig_append(rq->query->wire, &rq->query->size, + rq->query->max_size, query->tsig_rr); + if (ret != KNOT_EOK) { + xfr_task_free(rq); + return NS_PROC_FAIL; + } } - memcpy(rq->query->wire, query->wire, query->size); - rq->query->size = query->size; /* Retain pointer to zone and issue. */ xfrhandler_t *xfr = qdata->param->server->xfr; - int ret = xfr_enqueue(xfr, rq); + ret = xfr_enqueue(xfr, rq); if (ret != KNOT_EOK) { xfr_task_free(rq); + return NS_PROC_FAIL; } /* No immediate response. */ diff --git a/src/knot/server/notify.c b/src/knot/server/notify.c index 4e4a171a75e019784340cf7a01ada025a2fdfe8e..825daf3811fad09bca0f6fad20f9b6cd269e6b26 100644 --- a/src/knot/server/notify.c +++ b/src/knot/server/notify.c @@ -70,19 +70,6 @@ int notify_process_response(knot_pkt_t *notify, int msgid) return KNOT_EOK; } -static int notify_reschedule(const zone_t *zone) -{ - dbg_ns("%s(%p)\n", __func__, zone); - if (zone == NULL) { - return KNOT_EINVAL; - } - - /* Incoming NOTIFY expires REFRESH timer and renews EXPIRE timer. */ - zones_schedule_refresh((zone_t *)zone, 0); - - return KNOT_EOK; -} - /* NOTIFY-specific logging (internal, expects 'qdata' variable set). */ #define NOTIFY_LOG(severity, msg...) \ QUERY_LOG(severity, qdata, "NOTIFY", msg) @@ -117,7 +104,9 @@ int internet_notify(knot_pkt_t *pkt, struct query_data *qdata) } int next_state = NS_PROC_FAIL; - int ret = notify_reschedule(qdata->zone); + + /* Incoming NOTIFY expires REFRESH timer and renews EXPIRE timer. */ + int ret = zones_schedule_refresh((zone_t *)qdata->zone, REFRESH_NOW); /* Format resulting log message. */ if (ret != KNOT_EOK) { diff --git a/src/knot/server/xfr-handler.c b/src/knot/server/xfr-handler.c index 78f50515f7d22ec047fc1e3be3937b7a48727e14..4fdfb73f172a443e84d398aea4c4cac1eb3f32cb 100644 --- a/src/knot/server/xfr-handler.c +++ b/src/knot/server/xfr-handler.c @@ -137,37 +137,45 @@ static int parse_packet(knot_pkt_t *packet, knot_pkt_type_t *type) } /*! \brief Create forwarded query. */ -static int forward_packet(const knot_pkt_t *query, knot_pkt_t *pkt) +static int forward_packet(knot_ns_xfr_t *data, knot_pkt_t *pkt) { - /* Forward UPDATE query: - * assign a new packet id - */ + knot_pkt_t *query = data->query; memcpy(pkt->wire, query->wire, query->size); pkt->size = query->size; + + /* Assign new message id. */ + data->packet_nr = knot_wire_get_id(pkt->wire); knot_wire_set_id(pkt->wire, knot_random_uint16_t()); return KNOT_EOK; } /*! \brief Forwarded packet response. */ -int forward_packet_response(knot_ns_xfr_t *data, uint8_t *rwire, size_t *rsize) +static int forward_packet_response(knot_ns_xfr_t *data, knot_pkt_t *pkt) { - /* Processing of a forwarded response: - * change packet id - */ + /* Restore message id. */ + knot_wire_set_id(pkt->wire, (uint16_t)data->packet_nr); + + /* Restore TSIG. */ int ret = KNOT_EOK; - knot_wire_set_id(rwire, (uint16_t)data->packet_nr); + if (pkt->tsig_rr) { + ret = knot_tsig_append(pkt->wire, &pkt->size, pkt->max_size, + pkt->tsig_rr); + } /* Forward the response. */ - ret = data->send(data->fwd_src_fd, &data->fwd_addr, rwire, *rsize); - if (ret != *rsize) { - ret = KNOT_ECONN; - } else { - ret = KNOT_EOK; + if (ret == KNOT_EOK) { + ret = data->send(data->fwd_src_fd, &data->fwd_addr, + pkt->wire, pkt->size); + if (ret != pkt->size) { + ret = KNOT_ECONN; + } else { + ret = KNOT_EOK; + } } - /* As it is a response, do not reply back. */ - *rsize = 0; + /* Invalidate response => do not reply to master. */ + pkt->size = 0; return ret; } @@ -416,7 +424,7 @@ static int xfr_task_start(knot_ns_xfr_t *rq) ret = notify_create_request(zone, pkt); break; case XFR_TYPE_FORWARD: - ret = forward_packet(rq->query, pkt); + ret = forward_packet(rq, pkt); break; default: ret = KNOT_EINVAL; @@ -719,17 +727,16 @@ static int xfr_task_resp(xfrhandler_t *xfr, knot_ns_xfr_t *rq) } /* Process response. */ - size_t rlen = rq->wire_size; switch(rt) { case KNOT_RESPONSE_NORMAL: ret = zones_process_response(xfr->server, rq->packet_nr, &rq->addr, - re, rq->wire, &rlen); + re); break; case KNOT_RESPONSE_NOTIFY: ret = notify_process_response(re, rq->packet_nr); break; case KNOT_RESPONSE_UPDATE: - ret = forward_packet_response(rq, rq->wire, &rlen); + ret = forward_packet_response(rq, re); if (ret == KNOT_EOK) { log_zone_info("%s Forwarded response.\n", rq->msg); } diff --git a/src/knot/server/zone-load.c b/src/knot/server/zone-load.c index 9799a3a7b1231e1a31a434d9e1e10eecd35fa9cd..f6b64ac86f5c728cd5d80cf7e9d67cbddb236d71 100644 --- a/src/knot/server/zone-load.c +++ b/src/knot/server/zone-load.c @@ -291,6 +291,8 @@ fail: new_zone->contents = NULL; } + /* Disconnect config, caller is responsible for it. */ + new_zone->conf = NULL; zone_free(&new_zone); } diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c index 212ae835a3a5105a163c110d768a14611fce4c01..7914cea8a758e55c14d7235e11a749dfc31c9dd3 100644 --- a/src/knot/server/zones.c +++ b/src/knot/server/zones.c @@ -1036,17 +1036,12 @@ int zones_zonefile_sync(zone_t *zone, journal_t *journal) int zones_process_response(server_t *server, int exp_msgid, sockaddr_t *from, - knot_pkt_t *packet, uint8_t *response_wire, - size_t *rsize) + knot_pkt_t *packet) { - if (!packet || !rsize || server == NULL || from == NULL || - response_wire == NULL) { + if (!packet || server == NULL || from == NULL) { return KNOT_EINVAL; } - /* Declare no response. */ - *rsize = 0; - /* Handle SOA query response, cancel EXPIRE timer * and start AXFR transfer if needed. * Reset REFRESH timer on finish. @@ -1995,7 +1990,7 @@ int zones_do_diff_and_sign(const conf_zone_t *z, zone_t *zone, zone_t *old_zone, /* Even if there's nothing to create the diff from * we can still sign the zone - inconsistencies may happen. */ // TODO consider returning straight away when serial did not change - if (ret != KNOT_EOK && ret != KNOT_ENODIFF) { + if (ret != KNOT_EOK && ret != KNOT_ENODIFF && ret != KNOT_ERANGE) { knot_changesets_free(&diff_chs); rcu_read_unlock(); return ret; diff --git a/src/knot/server/zones.h b/src/knot/server/zones.h index 2786260b7bc6e261f9011ea5f1a04b5cb2a72d63..801c6fd7c76ecd7e71cc40ffeb9dd10b1afa8886 100644 --- a/src/knot/server/zones.h +++ b/src/knot/server/zones.h @@ -45,9 +45,9 @@ #define AXFR_BOOTSTRAP_RETRY (30*1000) /*!< Interval between AXFR BS retries. */ #define AXFR_RETRY_MAXTIME (10*60*1000) /*!< Maximum interval 10mins */ -enum { - REFRESH_DEFAULT = -1 /* Use time value from zone structure. */ -}; +/* Timer special values. */ +#define REFRESH_DEFAULT -1 /* Use time value from zone structure. */ +#define REFRESH_NOW (knot_random_uint16_t() % 1000) /* Now, but with jitter. */ /*! * \brief Sync zone data back to text zonefile. @@ -73,9 +73,6 @@ int zones_zonefile_sync(zone_t *zone, journal_t *journal); * \param server Name server structure to provide the needed data. * \param from Address of the response sender. * \param packet Parsed response packet. - * \param response_wire Place for the response in wire format. - * \param rsize Input: maximum acceptable size of the response. Output: real - * size of the response. * * \retval KNOT_EOK if a valid response was created. * \retval KNOT_EINVAL on invalid parameters or packet. @@ -84,8 +81,7 @@ int zones_zonefile_sync(zone_t *zone, journal_t *journal); int zones_process_response(server_t *server, int exp_msgid, sockaddr_t *from, - knot_pkt_t *packet, uint8_t *response_wire, - size_t *rsize); + knot_pkt_t *packet); /*! * \brief Decides what type of transfer should be used to update the given zone. diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c index 4f9bf8032c1dd81c5cf8c9a098176968e9ef9339..8cd01161ab791e8aab51bd876fc8dc64e9865d74 100644 --- a/src/knot/zone/zone.c +++ b/src/knot/zone/zone.c @@ -130,7 +130,7 @@ int zone_timers_thaw(zone_t *zone) zones_schedule_zonefile_sync(zone, zone->conf->dbsync_timeout); /* Schedule REFRESH. */ - zones_schedule_refresh(zone, 0); + zones_schedule_refresh(zone, REFRESH_NOW); return KNOT_EOK; } diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c index 59c40b5c6c5b792ba3945e062d62cd506be43a83..72dbc28fb2697dc1bc1c1e4cd9d956989494a25a 100644 --- a/src/libknot/tsig-op.c +++ b/src/libknot/tsig-op.c @@ -846,27 +846,35 @@ int knot_tsig_add(uint8_t *msg, size_t *msg_len, size_t msg_max_len, /* Set other len. */ tsig_rdata_set_other_data(tmp_tsig, 0, 0); + + /* Append TSIG RR. */ + int ret = knot_tsig_append(msg, msg_len, msg_max_len, tsig_rr); + + /* key_name already referenced in RRSet, no need to free separately. */ + knot_rrset_deep_free(&tmp_tsig, 1); + + return ret; +} + +int knot_tsig_append(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const knot_rrset_t *tsig_rr) +{ size_t tsig_wire_len = 0; uint16_t rr_count = 0; /* Write RRSet to wire */ int ret = KNOT_ERROR; - ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + ret = knot_rrset_to_wire(tsig_rr, msg + *msg_len, &tsig_wire_len, msg_max_len - *msg_len, &rr_count, NULL); if (ret != KNOT_EOK) { dbg_tsig("TSIG: rrset_to_wire = %s\n", knot_strerror(ret)); - knot_rrset_deep_free(&tmp_tsig, 1, NULL); return ret; } - /* key_name already referenced in RRSet, no need to free separately. */ - knot_rrset_deep_free(&tmp_tsig, 1, NULL); - *msg_len += tsig_wire_len; - uint16_t arcount = knot_wire_get_arcount(msg); - knot_wire_set_arcount(msg, ++arcount); + knot_wire_set_arcount(msg, knot_wire_get_arcount(msg) + 1); return KNOT_EOK; } diff --git a/src/libknot/tsig-op.h b/src/libknot/tsig-op.h index 9351fc2e2079ccd9acc1ee6b98b7914481d29cbd..ce627b5c94f11f17b8f3fc54119108b411896906 100644 --- a/src/libknot/tsig-op.h +++ b/src/libknot/tsig-op.h @@ -164,6 +164,10 @@ int knot_tsig_client_check_next(const knot_rrset_t *tsig_rr, int knot_tsig_add(uint8_t *msg, size_t *msg_len, size_t msg_max_len, uint16_t tsig_rcode, const knot_rrset_t *tsig_rr); +/*! \brief Append TSIG RR to message. */ +int knot_tsig_append(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const knot_rrset_t *tsig_rr); + /*! \brief Return true if the TSIG RCODE allows signing the packet. */ static inline bool knot_tsig_can_sign(uint16_t tsig_rcode) { return (tsig_rcode == KNOT_RCODE_NOERROR || tsig_rcode == KNOT_RCODE_BADTIME); diff --git a/tests-extra/tests/records/badrecord/data/badrecord.zone b/tests-extra/tests/records/badrecord/data/badrecord.zone new file mode 100644 index 0000000000000000000000000000000000000000..dec64cdddc72dc118514f053f6d77a4897ef70e5 --- /dev/null +++ b/tests-extra/tests/records/badrecord/data/badrecord.zone @@ -0,0 +1,15 @@ +$ORIGIN badrecord. +$TTL 3600 + +@ SOA dns1 hostmaster 2010111213 10800 3600 1209600 7200 + NS dns1 + NS dns2 + MX 10 mail + +dns1 A 192.0.2.1 + AAAA 2001:DB8::1 + +dns2 A 192.0.2.2 + AAAA 2001:DB8::2 + +dns3 ; Unfinished record diff --git a/tests-extra/tests/records/badrecord/test.py b/tests-extra/tests/records/badrecord/test.py new file mode 100644 index 0000000000000000000000000000000000000000..7c271d3574d8282fc87dbc45effa074796237af9 --- /dev/null +++ b/tests-extra/tests/records/badrecord/test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +''' Test for loading badly formed record ''' + +from dnstest.test import Test + +t = Test() + +master = t.server("knot") + +zone = t.zone("badrecord.", "badrecord.zone", storage=".") + +t.link(zone, master) + +t.start() + +# Check if the server is answering and zone _isn't_ loaded +resp = master.dig("badrecord.", "SOA", udp=True) +resp.check(rcode="REFUSED") + +# Stop master. +master.stop() + +t.end() diff --git a/tests-extra/tests/zone/notexists/test.py b/tests-extra/tests/zone/notexists/test.py index 74de6a41927e15fdce5008c80a1e4de9b1caf2a8..74740c40bce7c3ec52ca099b9eb230df1aa8c6c9 100644 --- a/tests-extra/tests/zone/notexists/test.py +++ b/tests-extra/tests/zone/notexists/test.py @@ -14,6 +14,14 @@ t.link(zones, master) t.start() +# Check if the server is answering and zone _isn't_ loaded +resp = master.dig("notexist.", "SOA", udp=True) +resp.check(rcode="REFUSED") + +# The other zone should answer without problem +resp = master.dig("wild.", "SOA", udp=True) +resp.check(rcode="NOERROR") + # Stop master. master.stop() diff --git a/tests-extra/tests/zone/reload/data/serial.zone b/tests-extra/tests/zone/reload/data/serial.zone new file mode 100644 index 0000000000000000000000000000000000000000..e1bdba4cb1aa9f93b9f3f9575ed435b6656d5e2c --- /dev/null +++ b/tests-extra/tests/zone/reload/data/serial.zone @@ -0,0 +1,14 @@ +$ORIGIN serial. +$TTL 3600 + +@ SOA dns1 hostmaster 2010111213 10800 3600 1209600 7200 + NS dns1 + NS dns2 + +dns1 A 192.0.2.1 + AAAA 2001:DB8::1 + +dns2 A 192.0.2.2 + AAAA 2001:DB8::2 + +new-record0 A 1.2.3.4 ; RR tagging the zone version (independent on serial) diff --git a/tests-extra/tests/zone/reload/data/serial.zone.1 b/tests-extra/tests/zone/reload/data/serial.zone.1 new file mode 100644 index 0000000000000000000000000000000000000000..56f8802391a0ee2bb4208ce89f464901ce958228 --- /dev/null +++ b/tests-extra/tests/zone/reload/data/serial.zone.1 @@ -0,0 +1,14 @@ +$ORIGIN serial. +$TTL 3600 + +@ SOA dns1 hostmaster 2010111214 10800 3600 1209600 7200 + NS dns1 + NS dns2 + +dns1 A 192.0.2.1 + AAAA 2001:DB8::1 + +dns2 A 192.0.2.2 + AAAA 2001:DB8::2 + +new-record1 A 1.2.3.4 ; RR tagging the zone version (independent on serial) diff --git a/tests-extra/tests/zone/reload/data/serial.zone.2 b/tests-extra/tests/zone/reload/data/serial.zone.2 new file mode 100644 index 0000000000000000000000000000000000000000..5f4c25fca795b83b4460c2d69407f74c04583a17 --- /dev/null +++ b/tests-extra/tests/zone/reload/data/serial.zone.2 @@ -0,0 +1,14 @@ +$ORIGIN serial. +$TTL 3600 + +@ SOA dns1 hostmaster 2010111214 10800 3600 1209600 7200 + NS dns1 + NS dns2 + +dns1 A 192.0.2.1 + AAAA 2001:DB8::1 + +dns2 A 192.0.2.2 + AAAA 2001:DB8::2 + +new-record2 A 1.2.3.4 ; RR tagging the zone version (independent on serial) diff --git a/tests-extra/tests/zone/reload/data/serial.zone.3 b/tests-extra/tests/zone/reload/data/serial.zone.3 new file mode 100644 index 0000000000000000000000000000000000000000..ecc42acd0d28ffb3f32145ae44c62ee5e06d113c --- /dev/null +++ b/tests-extra/tests/zone/reload/data/serial.zone.3 @@ -0,0 +1,14 @@ +$ORIGIN serial. +$TTL 3600 + +@ SOA dns1 hostmaster 2010111212 10800 3600 1209600 7200 + NS dns1 + NS dns2 + +dns1 A 192.0.2.1 + AAAA 2001:DB8::1 + +dns2 A 192.0.2.2 + AAAA 2001:DB8::2 + +new-record3 A 1.2.3.4 ; RR tagging the zone version (independent on serial) diff --git a/tests-extra/tests/zone/reload/data/serial.zone.4 b/tests-extra/tests/zone/reload/data/serial.zone.4 new file mode 100644 index 0000000000000000000000000000000000000000..31f98ee00677d57fe2d6228466d301d487fbc85e --- /dev/null +++ b/tests-extra/tests/zone/reload/data/serial.zone.4 @@ -0,0 +1,14 @@ +$ORIGIN serial. +$TTL 3600 + +@ SOA dns1 hostmaster 2010111212 10800 3600 1209600 7200 + NS dns1 + NS dns2 + +dns1 A 192.0.2.1 + AAAA 2001:DB8::1 + +dns2 A 192.0.2.2 + AAAA 2001:DB8::2 + +new-record4 A 1.2.3.4 ; RR tagging the zone version (independent on serial) diff --git a/tests-extra/tests/zone/reload/test.py b/tests-extra/tests/zone/reload/test.py new file mode 100644 index 0000000000000000000000000000000000000000..3c09103307c2d9a3a5bbf4bc053db227181ec166 --- /dev/null +++ b/tests-extra/tests/zone/reload/test.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +'''Test for reload of a changed zone (serial up, nochange, serial down). ''' + +from dnstest.test import Test +from dnstest.utils import set_err, detail_log + +t = Test() + +master = t.server("knot") + +# Zone setup +zone = t.zone("serial.", storage = ".") + +t.link(zone, master, ixfr = True) + +t.start() + +# Load zones +serial = master.zone_wait(zone) + +def reload_zone(serial, version): + master.update_zonefile(zone, version) + master.reload() + new_serial = master.zone_wait(zone) + if new_serial != serial: + set_err("SOA MISMATCH") + detail_log("!Zone '%s' SOA serial %s != %s" % (zone[0].name, new_serial, serial)) + return + resp = master.dig("new-record%d.%s" % (version, zone[0].name), "A") + resp.check(rcode="NOERROR") + +# Zone changes, serial increases (create changeset) +version = 1 +serial = serial + 1 +reload_zone(serial, version) + +# Zone changes, serial doesn't change (no new changeset) +version += 1 +reload_zone(serial, version) + +# Zone changes, serial jumps out-of-range (journal is not applicable) +version += 1 +serial = serial - 2 +reload_zone(serial, version) + +# Stop master. +master.stop() + +t.end() diff --git a/tests-extra/tools/dnstest/keys.py b/tests-extra/tools/dnstest/keys.py index 532129fcfe79e18d23231205602bda65c96673d3..485eb0eb0af0254175885af131279ff712dec0de 100644 --- a/tests-extra/tools/dnstest/keys.py +++ b/tests-extra/tools/dnstest/keys.py @@ -61,14 +61,14 @@ class Tsig(object): # TSIG preparation for pythondns utils. if self.alg == "hmac-md5": - alg = "hmac-md5.sig-alg.reg.int" + _alg = "hmac-md5.sig-alg.reg.int" else: - alg = self.alg + _alg = self.alg - key = dns.tsigkeyring.from_text({ + _key = dns.tsigkeyring.from_text({ self.name: self.key }) - self.key_params = dict(keyname=self.name, keyalgorithm=alg, keyring=key) + self.key_params = dict(keyname=self.name, keyalgorithm=_alg, keyring=_key) def dump(self, filename): s = dnstest.server.BindConf()