diff options
Diffstat (limited to 'src/knot/events/handlers/notify.c')
-rw-r--r-- | src/knot/events/handlers/notify.c | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/src/knot/events/handlers/notify.c b/src/knot/events/handlers/notify.c new file mode 100644 index 0000000..f46c1cf --- /dev/null +++ b/src/knot/events/handlers/notify.c @@ -0,0 +1,158 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "knot/common/log.h" +#include "knot/conf/conf.h" +#include "knot/query/query.h" +#include "knot/query/requestor.h" +#include "knot/zone/zone.h" +#include "libknot/errcode.h" + +/*! + * \brief NOTIFY message processing data. + */ +struct notify_data { + const knot_dname_t *zone; + const knot_rrset_t *soa; + const struct sockaddr *remote; + struct query_edns_data edns; +}; + +static int notify_begin(knot_layer_t *layer, void *params) +{ + layer->data = params; + + return KNOT_STATE_PRODUCE; +} + +static int notify_produce(knot_layer_t *layer, knot_pkt_t *pkt) +{ + struct notify_data *data = layer->data; + + // mandatory: NOTIFY opcode, AA flag, SOA qtype + query_init_pkt(pkt); + knot_wire_set_opcode(pkt->wire, KNOT_OPCODE_NOTIFY); + knot_wire_set_aa(pkt->wire); + knot_pkt_put_question(pkt, data->zone, KNOT_CLASS_IN, KNOT_RRTYPE_SOA); + + // unsecure hint: new SOA + if (data->soa) { + knot_pkt_begin(pkt, KNOT_ANSWER); + knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, data->soa, 0); + } + + query_put_edns(pkt, &data->edns); + + return KNOT_STATE_CONSUME; +} + +static int notify_consume(knot_layer_t *layer, knot_pkt_t *pkt) +{ + return KNOT_STATE_DONE; +} + +static const knot_layer_api_t NOTIFY_API = { + .begin = notify_begin, + .produce = notify_produce, + .consume = notify_consume, +}; + +#define NOTIFY_OUT_LOG(priority, zone, remote, fmt, ...) \ + ns_log(priority, zone, LOG_OPERATION_NOTIFY, LOG_DIRECTION_OUT, remote, \ + fmt, ## __VA_ARGS__) + +static int send_notify(conf_t *conf, zone_t *zone, const knot_rrset_t *soa, + const conf_remote_t *slave, int timeout) +{ + struct notify_data data = { + .zone = zone->name, + .soa = soa, + .remote = (struct sockaddr *)&slave->addr, + }; + + query_edns_data_init(&data.edns, conf, zone->name, slave->addr.ss_family); + + knot_requestor_t requestor; + knot_requestor_init(&requestor, &NOTIFY_API, &data, NULL); + + knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL); + if (!pkt) { + knot_requestor_clear(&requestor); + return KNOT_ENOMEM; + } + + const struct sockaddr_storage *dst = &slave->addr; + const struct sockaddr_storage *src = &slave->via; + knot_request_t *req = knot_request_make(NULL, dst, src, pkt, &slave->key, 0); + if (!req) { + knot_request_free(req, NULL); + knot_requestor_clear(&requestor); + return KNOT_ENOMEM; + } + + int ret = knot_requestor_exec(&requestor, req, timeout); + + if (ret == KNOT_EOK && knot_pkt_ext_rcode(req->resp) == 0) { + NOTIFY_OUT_LOG(LOG_INFO, zone->name, dst, + "serial %u", knot_soa_serial(soa->rrs.rdata)); + } else if (knot_pkt_ext_rcode(req->resp) == 0) { + NOTIFY_OUT_LOG(LOG_WARNING, zone->name, dst, + "failed (%s)", knot_strerror(ret)); + } else { + NOTIFY_OUT_LOG(LOG_WARNING, zone->name, dst, + "server responded with error '%s'", + knot_pkt_ext_rcode_name(req->resp)); + } + + knot_request_free(req, NULL); + knot_requestor_clear(&requestor); + + return ret; +} + +int event_notify(conf_t *conf, zone_t *zone) +{ + assert(zone); + + if (zone_contents_is_empty(zone->contents)) { + return KNOT_EOK; + } + + // NOTIFY content + int timeout = conf->cache.srv_tcp_remote_io_timeout; + knot_rrset_t soa = node_rrset(zone->contents->apex, KNOT_RRTYPE_SOA); + + // send NOTIFY to each remote, use working address + conf_val_t notify = conf_zone_get(conf, C_NOTIFY, zone->name); + while (notify.code == KNOT_EOK) { + conf_val_t addr = conf_id_get(conf, C_RMT, C_ADDR, ¬ify); + size_t addr_count = conf_val_count(&addr); + + for (int i = 0; i < addr_count; i++) { + conf_remote_t slave = conf_remote(conf, ¬ify, i); + int ret = send_notify(conf, zone, &soa, &slave, timeout); + if (ret == KNOT_EOK) { + break; + } + } + + conf_val_next(¬ify); + } + + return KNOT_EOK; +} |