From d1b90b243564043c853b19ad6295203925900921 Mon Sep 17 00:00:00 2001 From: Jan Vcelak <jan.vcelak@nic.cz> Date: Tue, 13 Sep 2016 18:30:48 +0200 Subject: [PATCH] huge events refactor --- Knot.files | 3 +- src/Makefile.am | 2 + src/knot/events/handlers/dnssec.c | 2 +- src/knot/events/handlers/notify.c | 130 ++++- src/knot/events/handlers/refresh.c | 873 ++++++++++++++++++++++++---- src/knot/modules/dnsproxy.c | 2 +- src/knot/nameserver/axfr.c | 260 +-------- src/knot/nameserver/axfr.h | 64 +- src/knot/nameserver/internet.c | 71 --- src/knot/nameserver/internet.h | 12 - src/knot/nameserver/ixfr.c | 466 +-------------- src/knot/nameserver/ixfr.h | 45 +- src/knot/nameserver/log.h | 69 ++- src/knot/nameserver/notify.c | 41 +- src/knot/nameserver/notify.h | 8 - src/knot/nameserver/process_query.c | 1 - src/knot/nameserver/update.c | 17 +- src/knot/nameserver/xfr.c | 87 +++ src/knot/nameserver/xfr.h | 69 +++ src/knot/query/query.c | 361 ++---------- src/knot/query/query.h | 69 ++- src/knot/query/requestor.c | 9 + src/knot/zone/zone.c | 24 - src/knot/zone/zone.h | 5 +- src/libknot/packet/pkt.h | 2 + tests/.gitignore | 1 - tests/Makefile.am | 2 - tests/process_answer.c | 167 ------ tests/requestor.c | 2 +- 29 files changed, 1307 insertions(+), 1557 deletions(-) create mode 100644 src/knot/nameserver/xfr.c create mode 100644 src/knot/nameserver/xfr.h delete mode 100644 tests/process_answer.c diff --git a/Knot.files b/Knot.files index 56f55cb23..1e428bcf6 100644 --- a/Knot.files +++ b/Knot.files @@ -292,6 +292,8 @@ src/knot/nameserver/tsig_ctx.c src/knot/nameserver/tsig_ctx.h src/knot/nameserver/update.c src/knot/nameserver/update.h +src/knot/nameserver/xfr.c +src/knot/nameserver/xfr.h src/knot/query/capture.c src/knot/query/capture.h src/knot/query/layer.c @@ -576,7 +578,6 @@ tests/libknot/test_ypscheme.c tests/libknot/test_yptrafo.c tests/modules/online_sign.c tests/node.c -tests/process_answer.c tests/process_query.c tests/query_module.c tests/requestor.c diff --git a/src/Makefile.am b/src/Makefile.am index caea4d8bd..c482c9c6b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -308,6 +308,8 @@ libknotd_la_SOURCES = \ knot/nameserver/tsig_ctx.h \ knot/nameserver/update.c \ knot/nameserver/update.h \ + knot/nameserver/xfr.c \ + knot/nameserver/xfr.h \ knot/query/capture.c \ knot/query/capture.h \ knot/query/layer.c \ diff --git a/src/knot/events/handlers/dnssec.c b/src/knot/events/handlers/dnssec.c index ce63d2e66..d44ad0d01 100644 --- a/src/knot/events/handlers/dnssec.c +++ b/src/knot/events/handlers/dnssec.c @@ -85,7 +85,7 @@ int event_dnssec(conf_t *conf, zone_t *zone) apply_init_ctx(&a_ctx, NULL, APPLY_STRICT); zone_contents_t *new_contents = NULL; - int ret = apply_changeset(&a_ctx, zone, &ch, &new_contents); + int ret = apply_changeset(&a_ctx, zone->contents, &ch, &new_contents); if (ret != KNOT_EOK) { log_zone_error(zone->name, "DNSSEC, failed to sign zone (%s)", knot_strerror(ret)); diff --git a/src/knot/events/handlers/notify.c b/src/knot/events/handlers/notify.c index f0ede603c..bc3d4392b 100644 --- a/src/knot/events/handlers/notify.c +++ b/src/knot/events/handlers/notify.c @@ -19,35 +19,149 @@ #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" -#define NOTIFY_LOG(priority, zone, remote, msg...) \ - ZONE_QUERY_LOG(priority, zone, remote, "NOTIFY, outgoing", msg); +/*! + * \brief NOTIFY message processing data. + */ +struct notify_data { + const knot_dname_t *zone; + const knot_rrset_t *soa; + const struct sockaddr_storage *remote; + uint16_t response_rcode; +}; + +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); + } + + return KNOT_STATE_CONSUME; +} + +static int notify_consume(knot_layer_t *layer, knot_pkt_t *pkt) +{ + struct notify_data *data = layer->data; + + data->response_rcode = knot_pkt_get_ext_rcode(pkt); + if (data->response_rcode != KNOT_RCODE_NOERROR) { + return KNOT_STATE_FAIL; + } + + return KNOT_STATE_DONE; +} + +static const knot_layer_api_t NOTIFY_API = { + .begin = notify_begin, + .produce = notify_produce, + .consume = notify_consume, +}; + +static int send_notify(zone_t *zone, const knot_rrset_t *soa, + const conf_remote_t *slave, int timeout, uint16_t *rcode) +{ + struct notify_data data = { + .zone = zone->name, + .soa = soa, + .remote = &slave->addr, + }; + + struct knot_requestor requestor = { 0 }; + 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 *dst = (struct sockaddr *)&slave->addr; + const struct sockaddr *src = (struct sockaddr *)&slave->via; + struct knot_request *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); + knot_request_free(req, NULL); + knot_requestor_clear(&requestor); + + *rcode = data.response_rcode; + + return ret; +} + +#define NOTIFY_LOG(priority, zone, remote, fmt, ...) \ + ns_log(priority, zone, LOG_OPERATION_NOTIFY, LOG_DIRECTION_OUT, remote, \ + fmt, ## __VA_ARGS__) + +static void log_notify_result(int ret, uint16_t rcode, const knot_dname_t *zone, + const struct sockaddr_storage *_remote, uint32_t serial) +{ + const struct sockaddr *remote = (struct sockaddr *)_remote; + + if (ret == KNOT_EOK) { + NOTIFY_LOG(LOG_INFO, zone, remote, "serial %u", serial); + } else if (rcode == 0) { + NOTIFY_LOG(LOG_WARNING, zone, remote, "failed (%s)", knot_strerror(ret)); + } else { + const knot_lookup_t *lut = knot_lookup_by_id(knot_rcode_names, rcode); + if (lut) { + NOTIFY_LOG(LOG_WARNING, zone, remote, "server responded with %s", lut->name); + } else { + NOTIFY_LOG(LOG_WARNING, zone, remote, "server responded with RCODE %u", rcode); + } + } +} int event_notify(conf_t *conf, zone_t *zone) { assert(zone); - /* Check zone contents. */ if (zone_contents_is_empty(zone->contents)) { return KNOT_EOK; } - /* Walk through configured remotes and send messages. */ + // NOTIFY content + int timeout = conf->cache.srv_tcp_reply_timeout * 1000; + knot_rrset_t soa = node_rrset(zone->contents->apex, KNOT_RRTYPE_SOA); + uint32_t serial = zone_contents_serial(zone->contents); + + // 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++) { + uint16_t rcode = 0; conf_remote_t slave = conf_remote(conf, ¬ify, i); - int ret = zone_query_execute(conf, zone, KNOT_QUERY_NOTIFY, &slave); + int ret = send_notify(zone, &soa, &slave, timeout, &rcode); + log_notify_result(ret, rcode, zone->name, &slave.addr, serial); if (ret == KNOT_EOK) { - NOTIFY_LOG(LOG_INFO, zone, &slave, "serial %u", zone_contents_serial(zone->contents)); break; - } else { - NOTIFY_LOG(LOG_WARNING, zone, &slave, "failed (%s)", knot_strerror(ret)); } } diff --git a/src/knot/events/handlers/refresh.c b/src/knot/events/handlers/refresh.c index c941d55bf..e95214237 100644 --- a/src/knot/events/handlers/refresh.c +++ b/src/knot/events/handlers/refresh.c @@ -16,177 +16,847 @@ #include <assert.h> #include <stdint.h> +#include <urcu.h> #include "contrib/trim.h" #include "dnssec/random.h" #include "knot/common/log.h" #include "knot/conf/conf.h" +#include "knot/query/layer.h" #include "knot/query/query.h" +#include "knot/updates/apply.h" #include "knot/zone/zone.h" #include "libknot/errcode.h" -#define BOOTSTRAP_RETRY (30) /*!< Interval between AXFR bootstrap retries. */ -#define BOOTSTRAP_MAXTIME (24*60*60) /*!< Maximum AXFR retry cap of 24 hours. */ +/// TODO. Memory context. +/// TODO. Adjusting. + +#include "contrib/mempattern.h" // mm_free() +#include "knot/nameserver/ixfr.h" // struct ixfr_proc +#include "knot/zone/zonefile.h" // err_handler_logger_t +#include "knot/zone/serial.h" // serial_compare (move to libknot) + +/*! + * \brief Refresh event processing. + * + * The following diagram represents refresh event processing. + * + * \verbatim + * O + * | + * +-----v-----+ + * | BEGIN | + * +---+---+---+ + * has SOA | | no SOA + * +-------------------+ +------------------------------+ + * | | + * +------v------+ outdated +--------------+ error +-------v------+ + * | SOA query +------------> IXFR query +-----------> AXFR query | + * +-----+---+---+ +------+-------+ +----+----+----+ + * error | | current | success success | | error + * | +-----+ +---------------+ | | + * | | | +--------------------------------------+ | + * | | | | +----------+ +--------------+ + * | | | | | | | + * | +--v-v-v--+ | +--v--v--+ + * | | DONE | | | FAIL | + * | +---------+ | +--------+ + * +----------------------------+ + * + * \endverbatim + */ + +#define REFRESH_LOG(priority, zone, remote, msg...) \ + ns_log(priority, zone, LOG_OPERATION_REFRESH, LOG_DIRECTION_OUT, remote, msg) + +#define _XFRIN_LOG(priority, operation, zone, remote, msg...) \ + ns_log(priority, zone, operation, LOG_DIRECTION_IN, remote, msg) + +#define AXFRIN_LOG(priority, zone, remote, msg...) \ + _XFRIN_LOG(priority, LOG_OPERATION_AXFR, zone, remote, msg) + +#define IXFRIN_LOG(priority, zone, remote, msg...) \ + _XFRIN_LOG(priority, LOG_OPERATION_IXFR, zone, remote, msg) + +enum refresh_state { + REFRESH_STATE_INVALID = 0, + REFRESH_STATE_SOA_QUERY, + REFRESH_STATE_TRANSFER, +}; + +struct refresh_result { + zone_contents_t *zone; //!< AXFR, new zone + list_t changesets; //!< IXFR, zone updates +}; + +struct refresh_data { + enum refresh_state state; //!< Event processing state. + struct refresh_result result; //!< Result of the refresh event. + bool is_ixfr; //!< Transfer is IXFR not AXFR. + + const knot_dname_t *zone; //!< Zone name. + const struct sockaddr *remote; //!< Remote endpoint. + struct query_edns_data edns; //!< EDNS data to be used in queries. + const knot_rrset_t *soa; //!< Local SOA (NULL for AXFR). + + struct xfr_stats stats; //!< Transfer statistics. + + struct { + struct ixfr_proc *proc; //!< IXFR processing context. + knot_rrset_t *final_soa; //!< SOA denoting end of transfer. + } ixfr; + + knot_mm_t *mm; // TODO: check where this should be used +}; + +static const char *rcode_name(uint16_t rcode) +{ + const knot_lookup_t *lut = knot_lookup_by_id(knot_rcode_names, rcode); + return lut ? lut->name : "unknown RCODE"; +} -#define LOG_TRANSFER(severity, pkt_type, msg, ...) \ - if (pkt_type == KNOT_QUERY_AXFR) { \ - ZONE_QUERY_LOG(severity, zone, master, "AXFR, incoming", msg, ##__VA_ARGS__); \ - } else { \ - ZONE_QUERY_LOG(severity, zone, master, "IXFR, incoming", msg, ##__VA_ARGS__); \ +static bool serial_is_current(uint32_t local_serial, uint32_t remote_serial) +{ + return serial_compare(local_serial, remote_serial) >= 0; +} + +static void refresh_result_init(struct refresh_result *result) +{ + result->zone = NULL; + init_list(&result->changesets); +} + +static void refresh_result_cleanup(struct refresh_result *result) +{ + zone_contents_deep_free(&result->zone); + changesets_free(&result->changesets); +} + +static bool refresh_result_empty(const struct refresh_result *result) +{ + return result->zone == NULL && EMPTY_LIST(result->changesets); +} + +static int axfr_consume_packet(knot_pkt_t *pkt, zone_contents_t *zone) +{ + assert(pkt); + assert(zone); + + zcreator_t zc = { .z = zone, .master = false, .ret = KNOT_EOK }; + + const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); + const knot_rrset_t *answer_rr = knot_pkt_rr(answer, 0); + 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; + } + + int ret = zcreator_step(&zc, &answer_rr[i]); + if (ret != KNOT_EOK) { + return KNOT_STATE_FAIL; + } } -/*! \brief Get next bootstrap interval. */ -uint32_t bootstrap_next(uint32_t interval) + return KNOT_STATE_CONSUME; +} + +static int axfr_consume(knot_pkt_t *pkt, struct refresh_data *data) { - interval *= 2; - interval += dnssec_random_uint32_t() % BOOTSTRAP_RETRY; - if (interval > BOOTSTRAP_MAXTIME) { - interval = BOOTSTRAP_MAXTIME; + assert(pkt); + assert(data); + + // Check RCODE + uint16_t rcode = knot_pkt_get_ext_rcode(pkt); + if (rcode != KNOT_RCODE_NOERROR) { + AXFRIN_LOG(LOG_WARNING, data->zone, data->remote, + "server responded with %s", rcode_name(rcode)); + return KNOT_STATE_FAIL; } - return interval; + + // Initialize with first packet + if (data->result.zone == NULL) { + data->result.zone = zone_contents_new(data->zone); + if (!data->result.zone) { + AXFRIN_LOG(LOG_WARNING, data->zone, data->remote, + "failed to initialize (%s)", knot_strerror(KNOT_ENOMEM)); + return KNOT_STATE_FAIL; + } + + AXFRIN_LOG(LOG_INFO, data->zone, data->remote, "starting"); + xfr_stats_begin(&data->stats); + } + + // Process answer packet + xfr_stats_add(&data->stats, pkt->size); + int next = axfr_consume_packet(pkt, data->result.zone); + + // Finalize + if (next == KNOT_STATE_DONE) { + int ret = zone_contents_adjust_full(data->result.zone); + if (ret != KNOT_EOK) { + return KNOT_STATE_FAIL; + } + + xfr_stats_end(&data->stats); + } + + return next; } -/*! \brief Get SOA from zone. */ -static const knot_rdataset_t *zone_soa(zone_t *zone) +/*! \brief Initialize IXFR-in processing context. */ +static int ixfr_init(struct refresh_data *data) { - return node_rdataset(zone->contents->apex, KNOT_RRTYPE_SOA); + struct ixfr_proc *proc = mm_alloc(data->mm, sizeof(*proc)); + if (proc == NULL) { + return KNOT_ENOMEM; + } + memset(proc, 0, sizeof(struct ixfr_proc)); + proc->state = IXFR_START; + proc->mm = data->mm; + + data->ixfr.proc = proc; + data->ixfr.final_soa = NULL; + + return KNOT_EOK; } -static int try_refresh(conf_t *conf, zone_t *zone, const conf_remote_t *master, void *ctx) +/*! \brief Clean up data allocated by IXFR-in processing. */ +static void ixfr_cleanup(struct refresh_data *data) { - assert(zone); - assert(master); + knot_rrset_free(&data->ixfr.final_soa, data->mm); + mm_free(data->mm, data->ixfr.proc); + data->ixfr.proc = NULL; +} - int ret = zone_query_execute(conf, zone, KNOT_QUERY_NORMAL, master); - if (ret != KNOT_EOK && ret != KNOT_LAYER_ERROR) { - ZONE_QUERY_LOG(LOG_WARNING, zone, master, "refresh, outgoing", - "failed (%s)", knot_strerror(ret)); +/*! \brief Stores starting SOA into changesets structure. */ +static int ixfr_solve_start(const knot_rrset_t *rr, struct refresh_data *data) +{ + assert(data->ixfr.final_soa == NULL); + if (rr->type != KNOT_RRTYPE_SOA) { + return KNOT_EMALF; } - return ret; + // Store terminal SOA + data->ixfr.final_soa = knot_rrset_copy(rr, data->mm); + if (data->ixfr.final_soa == NULL) { + return KNOT_ENOMEM; + } + + // Initialize list for changes + init_list(&data->result.changesets); + + return KNOT_EOK; } -/*! \brief Schedule expire event, unless it is already scheduled. */ -static void start_expire_timer(conf_t *conf, zone_t *zone, const knot_rdataset_t *soa) +/*! \brief Decides what to do with a starting SOA (deletions). */ +static int ixfr_solve_soa_del(const knot_rrset_t *rr, struct refresh_data *data) { - if (zone_events_is_scheduled(zone, ZONE_EVENT_EXPIRE)) { - return; + if (rr->type != KNOT_RRTYPE_SOA) { + return KNOT_EMALF; } - zone_events_schedule(zone, ZONE_EVENT_EXPIRE, knot_soa_expire(soa)); + // Create new changeset. + changeset_t *change = changeset_new(data->zone); + if (change == NULL) { + return KNOT_ENOMEM; + } + + // Store SOA into changeset. + change->soa_from = knot_rrset_copy(rr, NULL); + if (change->soa_from == NULL) { + changeset_clear(change); + return KNOT_ENOMEM; + } + + // Add changeset. + add_tail(&data->result.changesets, &change->n); + + return KNOT_EOK; } -int event_refresh(conf_t *conf, zone_t *zone) +/*! \brief Stores ending SOA into changeset. */ +static int ixfr_solve_soa_add(const knot_rrset_t *rr, changeset_t *change, knot_mm_t *mm) { - assert(zone); + if (rr->type != KNOT_RRTYPE_SOA) { + return KNOT_EMALF; + } - /* Ignore if not slave zone. */ - if (!zone_is_slave(conf, zone)) { - return KNOT_EOK; + change->soa_to = knot_rrset_copy(rr, NULL); + if (change->soa_to == NULL) { + return KNOT_ENOMEM; } - 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; +} + +/*! \brief Adds single RR into remove section of changeset. */ +static int ixfr_solve_del(const knot_rrset_t *rr, changeset_t *change, knot_mm_t *mm) +{ + return changeset_add_removal(change, rr, 0); +} + +/*! \brief Adds single RR into add section of changeset. */ +static int ixfr_solve_add(const knot_rrset_t *rr, changeset_t *change, knot_mm_t *mm) +{ + return changeset_add_addition(change, rr, 0); +} + +/*! \brief Decides what the next IXFR-in state should be. */ +static int ixfr_next_state(struct refresh_data *data, const knot_rrset_t *rr) +{ + const bool soa = (rr->type == KNOT_RRTYPE_SOA); + enum ixfr_state state = data->ixfr.proc->state; + + if ((state == IXFR_SOA_ADD || state == IXFR_ADD) && + knot_rrset_equal(rr, data->ixfr.final_soa, KNOT_RRSET_COMPARE_WHOLE)) { + return IXFR_DONE; + } + + switch (state) { + case IXFR_START: + // Final SOA already stored or transfer start. + return data->ixfr.final_soa ? IXFR_SOA_DEL : IXFR_START; + case IXFR_SOA_DEL: + // Empty delete section or start of delete section. + return soa ? IXFR_SOA_ADD : IXFR_DEL; + case IXFR_SOA_ADD: + // Empty add section or start of add section. + return soa ? IXFR_SOA_DEL : IXFR_ADD; + case IXFR_DEL: + // End of delete section or continue. + return soa ? IXFR_SOA_ADD : IXFR_DEL; + case IXFR_ADD: + // End of add section or continue. + return soa ? IXFR_SOA_DEL : IXFR_ADD; + default: + assert(0); + return IXFR_INVALID; + } +} + +/*! + * \brief Processes single RR according to current IXFR-in state. The states + * correspond with IXFR-in message structure, in the order they are + * mentioned in the code. + * + * \param rr RR to process. + * \param proc Processing context. + * + * \return KNOT_E* + */ +static int ixfr_step(const knot_rrset_t *rr, struct refresh_data *data) +{ + data->ixfr.proc->state = ixfr_next_state(data, rr); + changeset_t *change = TAIL(data->result.changesets); + + switch (data->ixfr.proc->state) { + case IXFR_START: + return ixfr_solve_start(rr, data); + case IXFR_SOA_DEL: + return ixfr_solve_soa_del(rr, data); + case IXFR_DEL: + return ixfr_solve_del(rr, change, data->mm); + case IXFR_SOA_ADD: + return ixfr_solve_soa_add(rr, change, data->mm); + case IXFR_ADD: + return ixfr_solve_add(rr, change, data->mm); + case IXFR_DONE: return KNOT_EOK; + default: + return KNOT_ERROR; } +} - int ret = zone_master_try(conf, zone, try_refresh, NULL, "refresh"); - const knot_rdataset_t *soa = zone_soa(zone); - if (ret != KNOT_EOK) { - log_zone_error(zone->name, "refresh, failed (%s)", - knot_strerror(ret)); - /* Schedule next retry. */ - zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_retry(soa)); - start_expire_timer(conf, zone, soa); +/*! + * \brief Processes IXFR reply packet and fills in the changesets structure. + * + * \param pkt Packet containing the IXFR reply in wire format. + * \param adata Answer data, including processing context. + * + * \return KNOT_STATE_CONSUME, KNOT_STATE_DONE, KNOT_STATE_FAIL + */ +static int ixfr_consume_packet(knot_pkt_t *pkt, struct refresh_data *data) +{ + // 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) { + const knot_rrset_t *rr = knot_pkt_rr(answer, i); + if (!knot_dname_in(data->zone, rr->owner)) { + continue; + } + + int ret = ixfr_step(rr, data); + if (ret != KNOT_EOK) { + IXFRIN_LOG(LOG_WARNING, data->zone, data->remote, + "failed (%s)", knot_strerror(ret)); + return KNOT_STATE_FAIL; + } + + if (data->ixfr.proc->state == IXFR_DONE) { + return KNOT_STATE_DONE; + } + } + + return KNOT_STATE_CONSUME; +} + +static bool ixfr_check_header(const knot_pktsection_t *answer) +{ + return answer->count >= 1 && + knot_pkt_rr(answer, 0)->type == KNOT_RRTYPE_SOA; +} + +static bool ixfr_is_axfr(const knot_pktsection_t *answer) +{ + return answer->count >= 2 && + knot_pkt_rr(answer, 0)->type == KNOT_RRTYPE_SOA && + knot_pkt_rr(answer, 1)->type != KNOT_RRTYPE_SOA; +} + +static int ixfr_consume(knot_pkt_t *pkt, struct refresh_data *data) +{ + assert(pkt); + assert(data); + + // Check RCODE + uint8_t rcode = knot_wire_get_rcode(pkt->wire); + if (rcode != KNOT_RCODE_NOERROR) { + const knot_lookup_t *lut = knot_lookup_by_id(knot_rcode_names, rcode); + if (lut != NULL) { + IXFRIN_LOG(LOG_WARNING, data->zone, data->remote, + "server responded with %s", lut->name); + } + return KNOT_STATE_FAIL; + } + + // Initialize with first packet + if (data->ixfr.proc == NULL) { + const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); + + if (!ixfr_check_header(answer)) { + IXFRIN_LOG(LOG_WARNING, data->zone, data->remote, "malformed response"); + return KNOT_STATE_FAIL; + } + + if (ixfr_is_axfr(answer)) { + IXFRIN_LOG(LOG_NOTICE, data->zone, data->remote, "receiving AXFR-style IXFR"); + data->is_ixfr = false; + return axfr_consume(pkt, data); + } + + int ret = ixfr_init(data); + if (ret != KNOT_EOK) { + IXFRIN_LOG(LOG_WARNING, data->zone, data->remote, + "failed to initialize (%s)", knot_strerror(ret)); + return KNOT_STATE_FAIL; + } + + IXFRIN_LOG(LOG_INFO, data->zone, data->remote, "starting"); + xfr_stats_begin(&data->stats); + } + + // Process answer packet + xfr_stats_add(&data->stats, pkt->size); + int next = ixfr_consume_packet(pkt, data); + + // Finalize + if (next == KNOT_STATE_DONE) { + xfr_stats_end(&data->stats); + } + + return next; +} + +static int soa_query_produce(knot_layer_t *layer, knot_pkt_t *pkt) +{ + struct refresh_data *data = layer->data; + + query_init_pkt(pkt); + + int r = knot_pkt_put_question(pkt, data->zone, KNOT_CLASS_IN, KNOT_RRTYPE_SOA); + if (r != KNOT_EOK) { + return KNOT_STATE_FAIL; + } + + r = query_put_edns(pkt, &data->edns); + if (r != KNOT_EOK) { + return KNOT_STATE_FAIL; + } + + return KNOT_STATE_CONSUME; +} + +static int soa_query_consume(knot_layer_t *layer, knot_pkt_t *pkt) +{ + struct refresh_data *data = layer->data; + + uint16_t rcode = knot_pkt_get_ext_rcode(pkt); + if (rcode != KNOT_RCODE_NOERROR) { + REFRESH_LOG(LOG_WARNING, data->zone, data->remote, + "server responded with %s", rcode_name(rcode)); + return KNOT_STATE_FAIL; + } + + const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); + const knot_rrset_t *rr = answer->count == 1 ? knot_pkt_rr(answer, 0) : NULL; + if (!rr || rr->type != KNOT_RRTYPE_SOA || rr->rrs.rr_count != 1) { + REFRESH_LOG(LOG_WARNING, data->zone, data->remote, "malformed message"); + return KNOT_STATE_FAIL; + } + + uint32_t local_serial = knot_soa_serial(&data->soa->rrs); + uint32_t remote_serial = knot_soa_serial(&rr->rrs); + bool current = serial_is_current(local_serial, remote_serial); + + REFRESH_LOG(LOG_INFO, data->zone, data->remote, "remote serial %u, %s", + remote_serial, + current ? "zone is up-to-date" : "zone is outdated"); + + if (current) { + return KNOT_STATE_DONE; } else { - /* SOA query answered, reschedule refresh timer. */ - zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa)); + data->state = REFRESH_STATE_TRANSFER; + return KNOT_STATE_RESET; } +} - return KNOT_EOK; +static int transfer_produce(knot_layer_t *layer, knot_pkt_t *pkt) +{ + struct refresh_data *data = layer->data; + + bool ixfr = data->is_ixfr; + + query_init_pkt(pkt); + knot_pkt_put_question(pkt, data->zone, KNOT_CLASS_IN, + ixfr ? KNOT_RRTYPE_IXFR : KNOT_RRTYPE_AXFR); + + if (ixfr) { + assert(data->soa); + knot_pkt_begin(pkt, KNOT_AUTHORITY); + knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, data->soa, 0); + } + + query_put_edns(pkt, &data->edns); + + return KNOT_STATE_CONSUME; } -/*! \brief Execute zone transfer request. */ -static int zone_query_transfer(conf_t *conf, zone_t *zone, const conf_remote_t *master, - uint16_t pkt_type) +static int transfer_consume(knot_layer_t *layer, knot_pkt_t *pkt) { - assert(zone); - assert(master); + struct refresh_data *data = layer->data; + + int next = data->is_ixfr ? ixfr_consume(pkt, data) : axfr_consume(pkt, data); + + // IXFR to AXFR failover + if (data->is_ixfr && next == KNOT_STATE_FAIL) { + ixfr_cleanup(data); + data->is_ixfr = false; + return KNOT_STATE_RESET; + } - int ret = zone_query_execute(conf, zone, pkt_type, master); + // Log result, no failover after the transfer is complete + if (next == KNOT_STATE_DONE) { + xfr_log_finished(data->zone, + data->is_ixfr ? LOG_OPERATION_IXFR : LOG_OPERATION_AXFR, + LOG_DIRECTION_IN, data->remote, &data->stats); + } + + // Cleanup processing context + if (next == KNOT_STATE_DONE || next == KNOT_STATE_FAIL) { + ixfr_cleanup(data); + } + + return next; +} + +static int refresh_begin(knot_layer_t *layer, void *_data) +{ + layer->data = _data; + struct refresh_data *data = _data; + + if (data->soa) { + data->state = REFRESH_STATE_SOA_QUERY; + data->is_ixfr = true; + } else { + data->state = REFRESH_STATE_TRANSFER; + data->is_ixfr = false; + } + + return KNOT_STATE_PRODUCE; +} + +static int refresh_produce(knot_layer_t *layer, knot_pkt_t *pkt) +{ + struct refresh_data *data = layer->data; + + switch (data->state) { + case REFRESH_STATE_SOA_QUERY: return soa_query_produce(layer, pkt); + case REFRESH_STATE_TRANSFER: return transfer_produce(layer, pkt); + default: + return KNOT_STATE_FAIL; + } +} + +static int refresh_consume(knot_layer_t *layer, knot_pkt_t *pkt) +{ + struct refresh_data *data = layer->data; + + switch (data->state) { + case REFRESH_STATE_SOA_QUERY: return soa_query_consume(layer, pkt); + case REFRESH_STATE_TRANSFER: return transfer_consume(layer, pkt); + default: + return KNOT_STATE_FAIL; + } +} + +static int refresh_reset(knot_layer_t *layer) +{ + return KNOT_STATE_PRODUCE; +} + +static const knot_layer_api_t REFRESH_API = { + .begin = refresh_begin, + .produce = refresh_produce, + .consume = refresh_consume, + .reset = refresh_reset, +}; + +static int publish_zone(conf_t *conf, zone_t *zone, const struct sockaddr *remote, + struct refresh_result *result) +{ + int ret = KNOT_ERROR; + bool axfr = result->zone != NULL; + apply_ctx_t apply_ctx = { 0 }; + + // Construct new zone + + zone_contents_t *new_zone = NULL; + + if (axfr) { + new_zone = result->zone; + } else { + apply_init_ctx(&apply_ctx, NULL, 0); + ret = apply_changesets(&apply_ctx, zone->contents, + &result->changesets, &new_zone); + if (ret != KNOT_EOK) { + goto fail; + } + } + + assert(new_zone != NULL); + + // Run semantic checks + + err_handler_logger_t handler; + handler._cb.cb = err_handler_logger; + ret = zone_do_sem_checks(new_zone, false, &handler._cb); if (ret != KNOT_EOK) { - /* IXFR failed, revert to AXFR. */ - if (pkt_type == KNOT_QUERY_IXFR) { - LOG_TRANSFER(LOG_NOTICE, pkt_type, "fallback to AXFR"); - return zone_query_transfer(conf, zone, master, KNOT_QUERY_AXFR); + goto fail; + } + + // Write journal for IXFR + + if (!axfr) { + ret = zone_changes_store(conf, zone, &result->changesets); + if (ret != KNOT_EOK) { + goto fail; } + } + + // Publish new zone - /* Log connection errors. */ - LOG_TRANSFER(LOG_WARNING, pkt_type, "failed (%s)", knot_strerror(ret)); + zone_contents_t *old_zone = zone_switch_contents(zone, new_zone); + zone->flags &= ~ZONE_EXPIRED; + + if (old_zone) { + REFRESH_LOG(LOG_INFO, zone->name, remote, + "zone updated, serial %u -> %u", + zone_contents_serial(old_zone), + zone_contents_serial(new_zone)); + } else { + REFRESH_LOG(LOG_INFO, zone->name, remote, + "zone updated, serial none -> %u", + zone_contents_serial(new_zone)); + } + + // Clean up old resources + + assert(ret == KNOT_EOK); + synchronize_rcu(); + +fail: + if (axfr) { + if (ret == KNOT_EOK) { + zone_contents_deep_free(&old_zone); + result->zone = NULL; // seized + } + } else { + if (ret == KNOT_EOK) { + update_free_zone(&old_zone); + update_cleanup(&apply_ctx); + } else { + update_rollback(&apply_ctx); + update_free_zone(&new_zone); + } } return ret; } -struct transfer_data { - uint16_t pkt_type; -}; +#include "knot/query/requestor.h" -static int try_xfer(conf_t *conf, zone_t *zone, const conf_remote_t *master, void *_data) +static int try_refresh(conf_t *conf, zone_t *zone, const conf_remote_t *master, void *ctx) { + // XXX: COPY PASTED + assert(zone); assert(master); - assert(_data); - struct transfer_data *data = _data; + knot_rrset_t soa = { 0 }; + if (zone->contents) { + soa = node_rrset(zone->contents->apex, KNOT_RRTYPE_SOA); + } + + struct refresh_data data = { + .remote = (struct sockaddr *)&master->addr, + .zone = zone->name, + .soa = zone->contents ? &soa : NULL, + }; + + refresh_result_init(&data.result); + query_edns_data_init(&data.edns, conf, zone->name, master->addr.ss_family); + + // TODO: temporary until we can get event specific flags + if (zone->flags & ZONE_FORCE_AXFR) { + zone->flags &= ~ZONE_FORCE_AXFR; + data.soa = NULL; + } + + struct knot_requestor requestor; + knot_requestor_init(&requestor, &REFRESH_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 *dst = (struct sockaddr *)&master->addr; + const struct sockaddr *src = (struct sockaddr *)&master->via; + struct knot_request *req = knot_request_make(NULL, dst, src, pkt, &master->key, 0); + if (!req) { + knot_request_free(req, NULL); + knot_requestor_clear(&requestor); + return KNOT_ENOMEM; + } + + // TODO: hardcoded + int timeout = 2000; + + int ret = knot_requestor_exec(&requestor, req, timeout); + knot_request_free(req, NULL); + knot_requestor_clear(&requestor); + + if (ret == KNOT_EOK && !refresh_result_empty(&data.result)) { + ret = publish_zone(conf, zone, data.remote, &data.result); + } + + refresh_result_cleanup(&data.result); + + return ret; +} + +#define BOOTSTRAP_RETRY (30) /*!< Interval between AXFR bootstrap retries. */ +#define BOOTSTRAP_MAXTIME (24*60*60) /*!< Maximum AXFR retry cap of 24 hours. */ + +/*! \brief Get next bootstrap interval. */ +uint32_t bootstrap_next(uint32_t interval) +{ + interval *= 2; + interval += dnssec_random_uint16_t() % BOOTSTRAP_RETRY; + if (interval > BOOTSTRAP_MAXTIME) { + interval = BOOTSTRAP_MAXTIME; + } + return interval; +} + +/*! \brief Get SOA from zone. */ +static const knot_rdataset_t *zone_soa(zone_t *zone) +{ + assert(zone); + + if (zone->contents == NULL) { + return NULL; + } - return zone_query_transfer(conf, zone, master, data->pkt_type); + return node_rdataset(zone->contents->apex, KNOT_RRTYPE_SOA); } -int event_xfer(conf_t *conf, zone_t *zone) +/*! \brief Schedule expire event, unless it is already scheduled. */ +static void start_expire_timer(conf_t *conf, 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, knot_soa_expire(soa)); +} + +int event_refresh(conf_t *conf, zone_t *zone) { assert(zone); - /* Ignore if not slave zone. */ + // slave zones only if (!zone_is_slave(conf, zone)) { + log_zone_debug(zone->name, "%s:%d possibly unreachable", __func__, __LINE__); return KNOT_EOK; } - struct transfer_data data = { 0 }; - const char *err_str = ""; + int ret = zone_master_try(conf, zone, try_refresh, NULL, "refresh"); - /* Determine transfer type. */ - bool is_bootstrap = zone_contents_is_empty(zone->contents); - if (is_bootstrap || zone->flags & ZONE_FORCE_AXFR) { - data.pkt_type = KNOT_QUERY_AXFR; - err_str = "AXFR, incoming"; - } else { - data.pkt_type = KNOT_QUERY_IXFR; - err_str = "IXFR, incoming"; - } + const knot_rdataset_t *soa = zone_soa(zone); + time_t next = 0; - /* Execute zone transfer. */ - int ret = zone_master_try(conf, zone, try_xfer, &data, err_str); - zone_clear_preferred_master(zone); - if (ret != KNOT_EOK) { - log_zone_error(zone->name, "%s, failed (%s)", err_str, - knot_strerror(ret)); - if (is_bootstrap) { - zone->bootstrap_retry = bootstrap_next(zone->bootstrap_retry); - zone_events_schedule(zone, ZONE_EVENT_XFER, zone->bootstrap_retry); + if (ret == KNOT_EOK) { + assert(soa); + next = knot_soa_refresh(soa); + } else { + log_zone_error(zone->name, "refresh, failed (%s)", knot_strerror(ret)); + if (soa) { + next = knot_soa_retry(soa); } else { - const knot_rdataset_t *soa = zone_soa(zone); - zone_events_schedule(zone, ZONE_EVENT_XFER, knot_soa_retry(soa)); - start_expire_timer(conf, zone, soa); + // TODO: boostrap period from timers + next = bootstrap_next(10); } + } - return KNOT_EOK; + zone_events_schedule(zone, ZONE_EVENT_REFRESH, next); + + // TODO: temporary until timers are refactored + if (ret != KNOT_EOK) { + start_expire_timer(conf, zone, soa); } - assert(!zone_contents_is_empty(zone->contents)); - const knot_rdataset_t *soa = zone_soa(zone); + + // TODO: reschedule all events here + // REFRESH + // NOTIFY + // EXPIRE + // FLUSH + + ///* Transfer cleanup. */ + //zone->bootstrap_retry = ZONE_EVENT_NOW; + //zone->flags &= ~ZONE_FORCE_AXFR; + + // MEMORY TRIM? + /* Trim extra heap. */ + //if (!is_bootstrap) { + // mem_trim(); + //} + +#if 0 /* Rechedule events. */ zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa)); zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW); @@ -199,15 +869,6 @@ int event_xfer(conf_t *conf, zone_t *zone) !zone_events_is_scheduled(zone, ZONE_EVENT_FLUSH)) { zone_events_schedule(zone, ZONE_EVENT_FLUSH, sync_timeout); } - - /* Transfer cleanup. */ - zone->bootstrap_retry = ZONE_EVENT_NOW; - zone->flags &= ~ZONE_FORCE_AXFR; - - /* Trim extra heap. */ - if (!is_bootstrap) { - mem_trim(); - } - +#endif return KNOT_EOK; } diff --git a/src/knot/modules/dnsproxy.c b/src/knot/modules/dnsproxy.c index 17c31753d..14929f4f8 100644 --- a/src/knot/modules/dnsproxy.c +++ b/src/knot/modules/dnsproxy.c @@ -80,7 +80,7 @@ static int dnsproxy_fwd(int state, knot_pkt_t *pkt, struct query_data *qdata, vo bool is_tcp = net_is_stream(qdata->param->socket); const struct sockaddr *dst = (const struct sockaddr *)&proxy->remote.addr; const struct sockaddr *src = (const struct sockaddr *)&proxy->remote.via; - struct knot_request *req = knot_request_make(re.mm, dst, src, qdata->query, + struct knot_request *req = knot_request_make(re.mm, dst, src, qdata->query, NULL, is_tcp ? 0 : KNOT_RQ_UDP); if (req == NULL) { knot_requestor_clear(&re); diff --git a/src/knot/nameserver/axfr.c b/src/knot/nameserver/axfr.c index 29d986aff..f74b7d080 100644 --- a/src/knot/nameserver/axfr.c +++ b/src/knot/nameserver/axfr.c @@ -17,15 +17,23 @@ #include <urcu.h> #include "contrib/mempattern.h" -#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/nameserver/log.h" +#include "knot/nameserver/xfr.h" #include "knot/zone/zonefile.h" #include "libknot/libknot.h" +#define ZONE_NAME(qdata) knot_pkt_qname((qdata)->query) +#define REMOTE(qdata) (struct sockaddr *)(qdata)->param->remote + +#define AXFROUT_LOG(priority, qdata, fmt...) \ + ns_log(priority, ZONE_NAME(qdata), LOG_OPERATION_AXFR, \ + LOG_DIRECTION_OUT, REMOTE(qdata), fmt) + /* AXFR context. @note aliasing the generic xfr_proc */ struct axfr_proc { struct xfr_proc proc; @@ -142,7 +150,7 @@ static int axfr_query_init(struct query_data *qdata) init_list(&axfr->proc.nodes); /* Put data to process. */ - gettimeofday(&axfr->proc.tstamp, NULL); + xfr_stats_begin(&axfr->proc.stats); ptrlist_add(&axfr->proc.nodes, zone->nodes, mm); /* Put NSEC3 data if exists. */ if (!zone_tree_is_empty(zone->nsec3_nodes)) { @@ -160,53 +168,6 @@ static int axfr_query_init(struct query_data *qdata) return KNOT_EOK; } -int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb process_item, - struct query_data *qdata) -{ - if (pkt == NULL || qdata == NULL || qdata->ext == NULL) { - return KNOT_EINVAL; - } - - int ret = KNOT_EOK; - knot_mm_t *mm = qdata->mm; - struct xfr_proc *xfer = qdata->ext; - - zone_contents_t *zone = qdata->zone->contents; - knot_rrset_t soa_rr = node_rrset(zone->apex, KNOT_RRTYPE_SOA); - - /* Prepend SOA on first packet. */ - if (xfer->npkts == 0) { - ret = knot_pkt_put(pkt, 0, &soa_rr, KNOT_PF_NOTRUNC); - if (ret != KNOT_EOK) { - return ret; - } - } - - /* Process all items in the list. */ - while (!EMPTY_LIST(xfer->nodes)) { - ptrnode_t *head = HEAD(xfer->nodes); - ret = process_item(pkt, head->d, xfer); - if (ret == KNOT_EOK) { /* Finished. */ - /* Complete change set. */ - rem_node((node_t *)head); - mm_free(mm, head); - } else { /* Packet full or other error. */ - break; - } - } - - /* Append SOA on last packet. */ - if (ret == KNOT_EOK) { - ret = knot_pkt_put(pkt, 0, &soa_rr, KNOT_PF_NOTRUNC); - } - - /* Update counters. */ - xfer->npkts += 1; - xfer->nbytes += pkt->size; - - return ret; -} - int axfr_process_query(knot_pkt_t *pkt, struct query_data *qdata) { if (pkt == NULL || qdata == NULL) { @@ -214,7 +175,6 @@ int axfr_process_query(knot_pkt_t *pkt, struct query_data *qdata) } int ret = KNOT_EOK; - struct timeval now = {0}; /* If AXFR is disabled, respond with NOTIMPL. */ if (qdata->param->proc_flags & NS_QUERY_NO_AXFR) { @@ -227,11 +187,11 @@ int axfr_process_query(knot_pkt_t *pkt, struct query_data *qdata) ret = axfr_query_init(qdata); if (ret != KNOT_EOK) { - AXFROUT_LOG(LOG_ERR, "failed to start (%s)", + AXFROUT_LOG(LOG_ERR, qdata, "failed to start (%s)", knot_strerror(ret)); return KNOT_STATE_FAIL; } else { - AXFROUT_LOG(LOG_INFO, "started, serial %u", + AXFROUT_LOG(LOG_INFO, qdata, "started, serial %u", zone_contents_serial(qdata->zone->contents)); } } @@ -246,201 +206,13 @@ int axfr_process_query(knot_pkt_t *pkt, struct query_data *qdata) case KNOT_ESPACE: /* Couldn't write more, send packet and continue. */ return KNOT_STATE_PRODUCE; /* Check for more. */ case KNOT_EOK: /* Last response. */ - gettimeofday(&now, NULL); - AXFROUT_LOG(LOG_INFO, - "finished, %.02f seconds, %u messages, %u bytes", - time_diff(&axfr->proc.tstamp, &now) / 1000.0, - axfr->proc.npkts, axfr->proc.nbytes); + xfr_stats_end(&axfr->proc.stats); + xfr_log_finished(ZONE_NAME(qdata), LOG_OPERATION_AXFR, LOG_DIRECTION_OUT, + REMOTE(qdata), &axfr->proc.stats); return KNOT_STATE_DONE; break; default: /* Generic error. */ - AXFROUT_LOG(LOG_ERR, "failed (%s)", knot_strerror(ret)); - return KNOT_STATE_FAIL; - } -} - -static void axfr_answer_cleanup(struct answer_data *data) -{ - assert(data != NULL); - - struct xfr_proc *proc = data->ext; - if (proc) { - zone_contents_deep_free(&proc->contents); - mm_free(data->mm, proc); - data->ext = NULL; - } -} - -static int axfr_answer_init(struct answer_data *data) -{ - assert(data); - - /* Create new zone contents. */ - zone_t *zone = data->param->zone; - zone_contents_t *new_contents = zone_contents_new(zone->name); - if (new_contents == NULL) { - return KNOT_ENOMEM; - } - - /* 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); - return KNOT_ENOMEM; - } - - memset(proc, 0, sizeof(struct xfr_proc)); - proc->contents = new_contents; - gettimeofday(&proc->tstamp, NULL); - - /* Set up cleanup callback. */ - data->ext = proc; - data->ext_cleanup = &axfr_answer_cleanup; - - return KNOT_EOK; -} - -static int axfr_answer_finalize(struct answer_data *adata) -{ - struct timeval now; - gettimeofday(&now, NULL); - - /* - * Adjust zone so that node count is set properly and nodes are - * marked authoritative / delegation point. - */ - struct xfr_proc *proc = adata->ext; - int rc = zone_contents_adjust_full(proc->contents); - if (rc != KNOT_EOK) { - return rc; - } - - err_handler_logger_t handler; - handler._cb.cb = err_handler_logger; - rc = zone_do_sem_checks(proc->contents, false, &handler._cb); - - if (rc != KNOT_EOK) { - return rc; - } - - conf_val_t val = conf_zone_get(adata->param->conf, C_MAX_ZONE_SIZE, - proc->contents->apex->owner); - int64_t size_limit = conf_int(&val); - - if (proc->contents->size > size_limit) { - AXFRIN_LOG(LOG_WARNING, "zone size exceeded"); + AXFROUT_LOG(LOG_ERR, qdata, "failed (%s)", knot_strerror(ret)); return KNOT_STATE_FAIL; } - - /* Switch contents. */ - zone_t *zone = adata->param->zone; - zone_contents_t *old_contents = - zone_switch_contents(zone, proc->contents); - zone->flags &= ~ZONE_EXPIRED; - synchronize_rcu(); - - if (old_contents != NULL) { - AXFRIN_LOG(LOG_INFO, "finished, " - "serial %u -> %u, %.02f seconds, %u messages, %u bytes", - zone_contents_serial(old_contents), - zone_contents_serial(proc->contents), - time_diff(&proc->tstamp, &now) / 1000.0, - proc->npkts, proc->nbytes); - } else { - AXFRIN_LOG(LOG_INFO, "finished, " - "serial %u, %.02f seconds, %u messages, %u bytes", - zone_contents_serial(proc->contents), - time_diff(&proc->tstamp, &now) / 1000.0, - proc->npkts, proc->nbytes); - } - - /* Do not free new contents with cleanup. */ - zone_contents_deep_free(&old_contents); - proc->contents = NULL; - - return KNOT_EOK; -} - -static int axfr_answer_packet(knot_pkt_t *pkt, struct answer_data *adata) -{ - assert(adata != NULL); - struct xfr_proc *proc = adata->ext; - assert(pkt != NULL); - assert(proc != NULL); - - /* Update counters. */ - proc->npkts += 1; - proc->nbytes += pkt->size; - - conf_val_t val = conf_zone_get(adata->param->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 }; - - const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); - const knot_rrset_t *answer_rr = knot_pkt_rr(answer, 0); - 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; - } 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) { - AXFRIN_LOG(LOG_WARNING, "zone size exceeded"); - return KNOT_STATE_FAIL; - } - } - - return KNOT_STATE_CONSUME; -} - -int axfr_process_answer(knot_pkt_t *pkt, struct answer_data *adata) -{ - if (pkt == NULL || adata == NULL) { - return KNOT_STATE_FAIL; - } - - /* Check RCODE. */ - uint8_t rcode = knot_wire_get_rcode(pkt->wire); - if (rcode != KNOT_RCODE_NOERROR) { - const knot_lookup_t *lut = knot_lookup_by_id(knot_rcode_names, rcode); - if (lut != NULL) { - AXFRIN_LOG(LOG_WARNING, "server responded with %s", lut->name); - } - return KNOT_STATE_FAIL; - } - - /* Initialize processing with first packet. */ - if (adata->ext == NULL) { - NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 0); - AXFRIN_LOG(LOG_INFO, "starting"); - - int ret = axfr_answer_init(adata); - if (ret != KNOT_EOK) { - AXFRIN_LOG(LOG_WARNING, "failed (%s)", knot_strerror(ret)); - return KNOT_STATE_FAIL; - } - } else { - NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 100); - } - - /* Process answer packet. */ - int ret = axfr_answer_packet(pkt, adata); - if (ret == KNOT_STATE_DONE) { - NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 0); - /* This was the last packet, finalize zone and publish it. */ - int fret = axfr_answer_finalize(adata); - if (fret != KNOT_EOK) { - ret = KNOT_STATE_FAIL; - } - } - - return ret; } diff --git a/src/knot/nameserver/axfr.h b/src/knot/nameserver/axfr.h index 4aa0c5802..c9c66aee5 100644 --- a/src/knot/nameserver/axfr.h +++ b/src/knot/nameserver/axfr.h @@ -13,64 +13,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/*! - * \file - * - * \brief AXFR processing. - * - * \addtogroup query_processing - * @{ - */ #pragma once -#include "libknot/packet/pkt.h" -#include "knot/nameserver/log.h" -#include "knot/query/query.h" #include "knot/nameserver/process_query.h" -#include "knot/zone/contents.h" -#include "contrib/ucw/lists.h" - -/*! - * \brief Transfer-specific logging (internal, expects 'qdata' variable set). - * - * Emits a message in the following format: - * > [zone] type, outgoing, address: custom formatted message - */ -#define TRANSFER_OUT_LOG(type, priority, msg, ...) \ - NS_PROC_LOG(priority, knot_pkt_qname((qdata)->query), (qdata)->param->remote, \ - type ", outgoing", msg, ##__VA_ARGS__) -#define AXFROUT_LOG(args...) TRANSFER_OUT_LOG("AXFR", args) -#define IXFROUT_LOG(args...) TRANSFER_OUT_LOG("IXFR", args) - -/*! - * \brief Transfer-specific logging (internal, expects 'adata' variable set). - */ -#define TRANSFER_IN_LOG(type, priority, msg, ...) \ - NS_PROC_LOG(priority, (adata)->param->zone->name, (adata)->param->remote, \ - type ", incoming", msg, ##__VA_ARGS__) -#define AXFRIN_LOG(args...) TRANSFER_IN_LOG("AXFR", args) -#define IXFRIN_LOG(args...) TRANSFER_IN_LOG("IXFR", args) - - -/*! \brief Generic transfer processing state. */ -struct xfr_proc { - list_t nodes; /* Items to process (ptrnode_t). */ - unsigned npkts; /* Packets processed. */ - unsigned nbytes; /* Bytes processed. */ - struct timeval tstamp; /* Start time. */ - zone_contents_t *contents; /* Processed zone. */ -}; - -/*! \brief Generic transfer processing (reused for IXFR). - * \return KNOT_EOK or an error - */ -typedef int (*xfr_put_cb)(knot_pkt_t *pkt, const void *item, struct xfr_proc *xfer); - -/*! \brief Put all items from xfr_proc.nodes to packet using a callback function. - * \note qdata->ext points to struct xfr_proc* (this is xfer-specific context) - */ -int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb put, struct query_data *qdata); +#include "libknot/packet/pkt.h" /*! * \brief Process an AXFR query message. @@ -78,12 +25,3 @@ int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb put, struct query_data *qdata); * \return KNOT_STATE_* processing states */ int axfr_process_query(knot_pkt_t *pkt, struct query_data *qdata); - -/*! - * \brief Processes an AXFR response message. - * - * \return KNOT_STATE_* processing states - */ -int axfr_process_answer(knot_pkt_t *pkt, struct answer_data *adata); - -/*! @} */ diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c index 527d35e04..c91f71a6c 100644 --- a/src/knot/nameserver/internet.c +++ b/src/knot/nameserver/internet.c @@ -807,8 +807,6 @@ static int answer_query(struct query_plan *plan, knot_pkt_t *response, struct qu return KNOT_STATE_DONE; } -#undef SOLVE_STEP - int internet_process_query(knot_pkt_t *response, struct query_data *qdata) { if (response == NULL || qdata == NULL) { @@ -834,72 +832,3 @@ int internet_process_query(knot_pkt_t *response, struct query_data *qdata) return answer_query(qdata->zone->query_plan, response, qdata); } - -#include "knot/nameserver/log.h" -#define REFRESH_LOG(priority, msg, ...) \ - NS_PROC_LOG(priority, (data)->param->zone->name, (data)->param->remote, \ - "refresh, outgoing", msg, ##__VA_ARGS__) - -/*! \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; - - /* Check RCODE. */ - uint8_t rcode = knot_wire_get_rcode(pkt->wire); - if (rcode != KNOT_RCODE_NOERROR) { - const knot_lookup_t *lut = knot_lookup_by_id(knot_rcode_names, rcode); - if (lut != NULL) { - REFRESH_LOG(LOG_WARNING, "server responded with %s", lut->name); - } - return KNOT_STATE_FAIL; - } - - /* Expect SOA in answer section. */ - const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); - const knot_rrset_t *first_rr = knot_pkt_rr(answer, 0); - if (answer->count < 1 || first_rr->type != KNOT_RRTYPE_SOA) { - REFRESH_LOG(LOG_WARNING, "malformed message"); - return KNOT_STATE_FAIL; - } - - /* Our zone is expired, schedule transfer. */ - if (zone_contents_is_empty(zone->contents)) { - zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW); - return KNOT_STATE_DONE; - } - - /* Check if master has newer zone and schedule transfer. */ - knot_rdataset_t *soa = node_rdataset(zone->contents->apex, KNOT_RRTYPE_SOA); - uint32_t our_serial = knot_soa_serial(soa); - uint32_t their_serial = knot_soa_serial(&first_rr->rrs); - if (serial_compare(our_serial, their_serial) >= 0) { - REFRESH_LOG(LOG_INFO, "zone is up-to-date"); - zone_events_cancel(zone, ZONE_EVENT_EXPIRE); - zone_clear_preferred_master(zone); - return KNOT_STATE_DONE; /* Our zone is up to date. */ - } - - /* Our zone is outdated, schedule zone transfer. */ - REFRESH_LOG(LOG_INFO, "master has newer serial %u -> %u", our_serial, their_serial); - zone_set_preferred_master(zone, data->param->remote); - zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW); - return KNOT_STATE_DONE; -} - -int internet_process_answer(knot_pkt_t *pkt, struct answer_data *data) -{ - if (pkt == NULL || data == NULL) { - return KNOT_STATE_FAIL; - } - - NS_NEED_TSIG_SIGNED(&data->param->tsig_ctx, 0); - - /* As of now, server can only issue SOA queries. */ - switch(knot_pkt_qtype(pkt)) { - case KNOT_RRTYPE_SOA: - return process_soa_answer(pkt, data); - default: - return KNOT_STATE_NOOP; - } -} diff --git a/src/knot/nameserver/internet.h b/src/knot/nameserver/internet.h index b5e035467..0d438386e 100644 --- a/src/knot/nameserver/internet.h +++ b/src/knot/nameserver/internet.h @@ -29,9 +29,6 @@ /* Query data (from query processing). */ struct query_data; -struct query_plan; -struct query_module; -struct answer_data; /*! \brief Internet query processing states. */ enum { @@ -53,15 +50,6 @@ enum { */ int internet_process_query(knot_pkt_t *resp, struct query_data *qdata); -/*! - * \brief Process answer in an IN class zone. - * - * \retval FAIL if it encountered an error. - * \retval DONE if finished. - * \retval NOOP if not supported. - */ -int internet_process_answer(knot_pkt_t *pkt, struct answer_data *data); - /*! * \brief Puts RRSet to packet, will store its RRSIG for later use. * diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c index afc14aec2..d044b9ad5 100644 --- a/src/knot/nameserver/ixfr.c +++ b/src/knot/nameserver/ixfr.c @@ -17,46 +17,25 @@ #include <urcu.h> #include "knot/common/log.h" +#include "knot/nameserver/internet.h" #include "knot/nameserver/axfr.h" #include "knot/nameserver/ixfr.h" -#include "knot/nameserver/internet.h" +#include "knot/nameserver/log.h" +#include "knot/nameserver/xfr.h" #include "knot/updates/apply.h" #include "knot/zone/serial.h" #include "knot/zone/semantic-check.h" #include "knot/zone/zonefile.h" #include "libknot/libknot.h" #include "contrib/mempattern.h" -#include "contrib/print.h" #include "contrib/sockaddr.h" -/* ------------------------ IXFR-out processing ----------------------------- */ - -/*! \brief IXFR-in processing states. */ -enum ixfr_states { - IXFR_START = 0, /* IXFR-in starting, expecting final SOA. */ - IXFR_SOA_DEL, /* Expecting starting SOA. */ - IXFR_SOA_ADD, /* Expecting ending SOA. */ - IXFR_DEL, /* Expecting RR to delete. */ - IXFR_ADD, /* Expecting RR to add. */ - IXFR_DONE /* Processing done, IXFR-in complete. */ -}; - -/*! \brief Extended structure for IXFR-in/IXFR-out processing. */ -struct ixfr_proc { - struct xfr_proc proc; /* Generic transfer processing context. */ - changeset_iter_t cur; /* Current changeset iteration state.*/ - knot_rrset_t cur_rr; /* Currently processed RRSet. */ - int state; /* IXFR-in state. */ - 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 change_size; /* Size of records to add and remove */ - zone_t *zone; /* Modified zone - for journal access. */ - knot_mm_t *mm; /* Memory context for RR allocations. */ - struct query_data *qdata; - const knot_rrset_t *soa_from; - const knot_rrset_t *soa_to; -}; +#define ZONE_NAME(qdata) knot_pkt_qname((qdata)->query) +#define REMOTE(qdata) (struct sockaddr *)(qdata)->param->remote + +#define IXFROUT_LOG(priority, qdata, fmt...) \ + ns_log(priority, ZONE_NAME(qdata), LOG_OPERATION_IXFR, \ + LOG_DIRECTION_OUT, REMOTE(qdata), fmt) /*! \brief Helper macro for putting RRs into packet. */ #define IXFR_SAFE_PUT(pkt, rr) \ @@ -150,10 +129,9 @@ static int ixfr_process_changeset(knot_pkt_t *pkt, const void *item, } /* Finished change set. */ - struct query_data *qdata = ixfr->qdata; /*< Required for IXFROUT_LOG() */ const uint32_t serial_from = knot_soa_serial(&chgset->soa_from->rrs); const uint32_t serial_to = knot_soa_serial(&chgset->soa_to->rrs); - IXFROUT_LOG(LOG_DEBUG, "serial %u -> %u", serial_from, serial_to); + IXFROUT_LOG(LOG_DEBUG, ixfr->qdata, "serial %u -> %u", serial_from, serial_to); return ret; } @@ -259,7 +237,7 @@ static int ixfr_answer_init(struct query_data *qdata) return KNOT_ENOMEM; } memset(xfer, 0, sizeof(struct ixfr_proc)); - gettimeofday(&xfer->proc.tstamp, NULL); + xfr_stats_begin(&xfer->proc.stats); xfer->state = IXFR_SOA_DEL; init_list(&xfer->proc.nodes); init_list(&xfer->changesets); @@ -321,334 +299,6 @@ static int ixfr_answer_soa(knot_pkt_t *pkt, struct query_data *qdata) return KNOT_STATE_DONE; } -/* ------------------------- IXFR-in processing ----------------------------- */ - -/*! \brief Checks whether server responded with AXFR-style IXFR. */ -static bool ixfr_is_axfr(const knot_pkt_t *pkt) -{ - const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); - return answer->count >= 2 && - knot_pkt_rr(answer, 0)->type == KNOT_RRTYPE_SOA && - knot_pkt_rr(answer, 1)->type != KNOT_RRTYPE_SOA; -} - -/*! \brief Cleans up data allocated by IXFR-in processing. */ -static void ixfrin_cleanup(struct answer_data *data) -{ - struct ixfr_proc *proc = data->ext; - if (proc) { - changesets_free(&proc->changesets); - knot_rrset_free(&proc->final_soa, proc->mm); - mm_free(data->mm, proc); - data->ext = NULL; - } -} - -/*! \brief Initializes IXFR-in processing context. */ -static int ixfrin_answer_init(struct answer_data *data) -{ - struct ixfr_proc *proc = mm_alloc(data->mm, sizeof(struct ixfr_proc)); - if (proc == NULL) { - return KNOT_ENOMEM; - } - memset(proc, 0, sizeof(struct ixfr_proc)); - gettimeofday(&proc->proc.tstamp, NULL); - - init_list(&proc->changesets); - - proc->state = IXFR_START; - proc->zone = data->param->zone; - proc->mm = data->mm; - - data->ext = proc; - data->ext_cleanup = &ixfrin_cleanup; - - return KNOT_EOK; -} - -/*! \brief Finalizes IXFR-in processing. */ -static int ixfrin_finalize(struct answer_data *adata) -{ - struct ixfr_proc *ixfr = adata->ext; - assert(ixfr->state == IXFR_DONE); - - apply_ctx_t a_ctx = { 0 }; - apply_init_ctx(&a_ctx, NULL, APPLY_STRICT); - - zone_contents_t *new_contents; - int ret = apply_changesets(&a_ctx, ixfr->zone, &ixfr->changesets, &new_contents); - if (ret != KNOT_EOK) { - IXFRIN_LOG(LOG_WARNING, "failed to apply changes to zone (%s)", - knot_strerror(ret)); - return ret; - } - - err_handler_logger_t handler; - handler._cb.cb = err_handler_logger; - ret = zone_do_sem_checks(new_contents, false, &handler._cb); - - if (ret != KNOT_EOK) { - IXFRIN_LOG(LOG_WARNING, "failed to apply changes to zone (%s)", - knot_strerror(ret)); - update_rollback(&a_ctx); - update_free_zone(&new_contents); - return ret; - } - - conf_val_t val = conf_zone_get(adata->param->conf, C_MAX_ZONE_SIZE, - ixfr->zone->name); - const int64_t size_limit = conf_int(&val); - - if (new_contents->size > size_limit) { - IXFRIN_LOG(LOG_WARNING, "zone size exceeded"); - update_rollback(&a_ctx); - update_free_zone(&new_contents); - return KNOT_EZONESIZE; - } - - /* Write changes to journal. */ - ret = zone_changes_store(adata->param->conf, ixfr->zone, &ixfr->changesets); - if (ret != KNOT_EOK) { - IXFRIN_LOG(LOG_WARNING, "failed to write changes to journal (%s)", - knot_strerror(ret)); - update_rollback(&a_ctx); - update_free_zone(&new_contents); - return ret; - } - - /* Switch zone contents. */ - zone_contents_t *old_contents = zone_switch_contents(ixfr->zone, new_contents); - ixfr->zone->flags &= ~ZONE_EXPIRED; - synchronize_rcu(); - - struct timeval now = {0}; - gettimeofday(&now, NULL); - IXFRIN_LOG(LOG_INFO, "finished, " - "serial %u -> %u, %.02f seconds, %u messages, %u bytes", - zone_contents_serial(old_contents), - zone_contents_serial(new_contents), - time_diff(&ixfr->proc.tstamp, &now) / 1000.0, - ixfr->proc.npkts, ixfr->proc.nbytes); - - update_free_zone(&old_contents); - update_cleanup(&a_ctx); - - return KNOT_EOK; -} - -/*! \brief Stores starting SOA into changesets structure. */ -static int solve_start(const knot_rrset_t *rr, struct ixfr_proc *proc) -{ - assert(proc->final_soa == NULL); - if (rr->type != KNOT_RRTYPE_SOA) { - return KNOT_EMALF; - } - - // Store the first SOA for later use. - proc->final_soa = knot_rrset_copy(rr, proc->mm); - if (proc->final_soa == NULL) { - return KNOT_ENOMEM; - } - - return KNOT_EOK; -} - -/*! \brief Decides what to do with a starting SOA (deletions). */ -static int solve_soa_del(const knot_rrset_t *rr, struct ixfr_proc *proc) -{ - if (rr->type != KNOT_RRTYPE_SOA) { - return KNOT_EMALF; - } - - // Create new changeset. - changeset_t *change = changeset_new(proc->zone->name); - if (change == NULL) { - return KNOT_ENOMEM; - } - - // Store SOA into changeset. - change->soa_from = knot_rrset_copy(rr, NULL); - if (change->soa_from == NULL) { - changeset_clear(change); - return KNOT_ENOMEM; - } - - // Add changeset. - add_tail(&proc->changesets, &change->n); - ++proc->change_count; - - return KNOT_EOK; -} - -/*! \brief Stores ending SOA into changeset. */ -static int solve_soa_add(const knot_rrset_t *rr, changeset_t *change, knot_mm_t *mm) -{ - assert(rr->type == KNOT_RRTYPE_SOA); - change->soa_to = knot_rrset_copy(rr, NULL); - if (change->soa_to == NULL) { - return KNOT_ENOMEM; - } - - return KNOT_EOK; -} - -/*! \brief Adds single RR into remove section of changeset. */ -static int solve_del(const knot_rrset_t *rr, changeset_t *change, knot_mm_t *mm) -{ - return changeset_add_removal(change, rr, 0); -} - -/*! \brief Adds single RR into add section of changeset. */ -static int solve_add(const knot_rrset_t *rr, changeset_t *change, knot_mm_t *mm) -{ - return changeset_add_addition(change, rr, 0); -} - -/*! \brief Decides what the next IXFR-in state should be. */ -static int ixfrin_next_state(struct ixfr_proc *proc, const knot_rrset_t *rr) -{ - const bool soa = (rr->type == KNOT_RRTYPE_SOA); - if (soa && - (proc->state == IXFR_SOA_ADD || proc->state == IXFR_ADD)) { - // Check end of transfer. - if (knot_rrset_equal(rr, proc->final_soa, - KNOT_RRSET_COMPARE_WHOLE)) { - // Final SOA encountered, transfer done. - return IXFR_DONE; - } - } - - switch (proc->state) { - case IXFR_START: - // Final SOA already stored or transfer start. - return proc->final_soa ? IXFR_SOA_DEL : IXFR_START; - case IXFR_SOA_DEL: - // Empty delete section or start of delete section. - return soa ? IXFR_SOA_ADD : IXFR_DEL; - case IXFR_SOA_ADD: - // Empty add section or start of add section. - return soa ? IXFR_SOA_DEL : IXFR_ADD; - case IXFR_DEL: - // End of delete section or continue. - return soa ? IXFR_SOA_ADD : IXFR_DEL; - case IXFR_ADD: - // End of add section or continue. - return soa ? IXFR_SOA_DEL : IXFR_ADD; - default: - assert(0); - return 0; - } -} - -/*! - * \brief Processes single RR according to current IXFR-in state. The states - * correspond with IXFR-in message structure, in the order they are - * mentioned in the code. - * - * \param rr RR to process. - * \param proc Processing context. - * - * \return KNOT_E* - */ -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: - ret = solve_soa_del(rr, proc); - break; - case IXFR_DEL: - ret = solve_del(rr, change, proc->mm); - break; - case IXFR_SOA_ADD: - ret = solve_soa_add(rr, change, proc->mm); - break; - case IXFR_ADD: - ret = solve_add(rr, change, proc->mm); - break; - case IXFR_DONE: - return KNOT_EOK; - default: - return KNOT_ERROR; - } - if (ret == KNOT_EOK) { - proc->change_size += knot_rrset_size(rr); - } - return ret; -} - -/*! \brief Checks whether journal node limit has not been exceeded. */ -static bool journal_limit_exceeded(struct ixfr_proc *proc) -{ - return proc->change_count > JOURNAL_NCOUNT; -} - -/*! \brief Checks whether RR belongs into zone. */ -static bool out_of_zone(const knot_rrset_t *rr, struct ixfr_proc *proc) -{ - return !knot_dname_in(proc->zone->name, rr->owner); -} - -/*! - * \brief Processes IXFR reply packet and fills in the changesets structure. - * - * \param pkt Packet containing the IXFR reply in wire format. - * \param adata Answer data, including processing context. - * - * \return KNOT_STATE_CONSUME, KNOT_STATE_DONE, KNOT_STATE_FAIL - */ -static int process_ixfrin_packet(knot_pkt_t *pkt, struct answer_data *adata) -{ - struct ixfr_proc *ixfr = (struct ixfr_proc *)adata->ext; - - // Update counters. - ixfr->proc.npkts += 1; - ixfr->proc.nbytes += pkt->size; - - conf_val_t val = conf_zone_get(adata->param->conf, C_MAX_ZONE_SIZE, - ixfr->zone->name); - const int64_t size_limit = conf_int(&val); - - // 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) { - if (journal_limit_exceeded(ixfr)) { - IXFRIN_LOG(LOG_WARNING, "journal is full"); - return KNOT_STATE_FAIL; - } - - const knot_rrset_t *rr = knot_pkt_rr(answer, i); - if (out_of_zone(rr, ixfr)) { - continue; - } - - int ret = ixfrin_step(rr, ixfr); - if (ret != KNOT_EOK) { - IXFRIN_LOG(LOG_WARNING, "failed (%s)", knot_strerror(ret)); - return KNOT_STATE_FAIL; - } - - if (ixfr->state == IXFR_DONE) { - // Transfer done, do not consume more RRs. - return KNOT_STATE_DONE; - } - - if (ixfr->change_size > 2 * size_limit) { - IXFRIN_LOG(LOG_WARNING, "transfer size exceeded"); - } - - } - - return KNOT_STATE_CONSUME; -} - -/* --------------------------------- API ------------------------------------ */ - int ixfr_process_query(knot_pkt_t *pkt, struct query_data *qdata) { if (pkt == NULL || qdata == NULL) { @@ -656,7 +306,6 @@ int ixfr_process_query(knot_pkt_t *pkt, struct query_data *qdata) } int ret = KNOT_EOK; - struct timeval now = {0}; struct ixfr_proc *ixfr = (struct ixfr_proc*)qdata->ext; /* If IXFR is disabled, respond with SOA. */ @@ -670,20 +319,20 @@ int ixfr_process_query(knot_pkt_t *pkt, struct query_data *qdata) switch(ret) { case KNOT_EOK: /* OK */ ixfr = (struct ixfr_proc*)qdata->ext; - IXFROUT_LOG(LOG_INFO, "started, serial %u -> %u", + IXFROUT_LOG(LOG_INFO, qdata, "started, serial %u -> %u", knot_soa_serial(&ixfr->soa_from->rrs), knot_soa_serial(&ixfr->soa_to->rrs)); break; case KNOT_EUPTODATE: /* Our zone is same age/older, send SOA. */ - IXFROUT_LOG(LOG_INFO, "zone is up-to-date"); + IXFROUT_LOG(LOG_INFO, qdata, "zone is up-to-date"); return ixfr_answer_soa(pkt, qdata); case KNOT_ERANGE: /* No history -> AXFR. */ case KNOT_ENOENT: - IXFROUT_LOG(LOG_INFO, "incomplete history, fallback to AXFR"); + IXFROUT_LOG(LOG_INFO, qdata, "incomplete history, fallback to AXFR"); qdata->packet_type = KNOT_QUERY_AXFR; /* Solve as AXFR. */ return axfr_process_query(pkt, qdata); default: /* Server errors. */ - IXFROUT_LOG(LOG_ERR, "failed to start (%s)", knot_strerror(ret)); + IXFROUT_LOG(LOG_ERR, qdata, "failed to start (%s)", knot_strerror(ret)); return KNOT_STATE_FAIL; } } @@ -697,93 +346,16 @@ int ixfr_process_query(knot_pkt_t *pkt, struct query_data *qdata) case KNOT_ESPACE: /* Couldn't write more, send packet and continue. */ return KNOT_STATE_PRODUCE; /* Check for more. */ case KNOT_EOK: /* Last response. */ - gettimeofday(&now, NULL); - IXFROUT_LOG(LOG_INFO, - "finished, %.02f seconds, %u messages, %u bytes", - time_diff(&ixfr->proc.tstamp, &now) / 1000.0, - ixfr->proc.npkts, ixfr->proc.nbytes); + xfr_stats_end(&ixfr->proc.stats); + xfr_log_finished(ZONE_NAME(qdata), LOG_OPERATION_IXFR, LOG_DIRECTION_OUT, + REMOTE(qdata), &ixfr->proc.stats); ret = KNOT_STATE_DONE; break; default: /* Generic error. */ - IXFROUT_LOG(LOG_ERR, "failed (%s)", knot_strerror(ret)); + IXFROUT_LOG(LOG_ERR, qdata, "failed (%s)", knot_strerror(ret)); ret = KNOT_STATE_FAIL; break; } return ret; } - -static int check_format(knot_pkt_t *pkt) -{ - const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); - - if (answer->count >= 1 && knot_pkt_rr(answer, 0)->type == KNOT_RRTYPE_SOA) { - return KNOT_EOK; - } else { - return KNOT_EMALF; - } -} - -int ixfr_process_answer(knot_pkt_t *pkt, struct answer_data *adata) -{ - if (pkt == NULL || adata == NULL) { - return KNOT_STATE_FAIL; - } - - /* Check RCODE. */ - uint8_t rcode = knot_wire_get_rcode(pkt->wire); - if (rcode != KNOT_RCODE_NOERROR) { - const knot_lookup_t *lut = knot_lookup_by_id(knot_rcode_names, rcode); - if (lut != NULL) { - IXFRIN_LOG(LOG_WARNING, "server responded with %s", lut->name); - } - return KNOT_STATE_FAIL; - } - - if (adata->ext == NULL) { - if (check_format(pkt) != KNOT_EOK) { - IXFRIN_LOG(LOG_WARNING, "malformed response"); - return KNOT_STATE_FAIL; - } - - /* Check for AXFR-style IXFR. */ - if (ixfr_is_axfr(pkt)) { - IXFRIN_LOG(LOG_NOTICE, "receiving AXFR-style IXFR"); - adata->response_type = KNOT_RESPONSE_AXFR; - return axfr_process_answer(pkt, adata); - } - - /* Initialize processing with first packet. */ - NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 0); - if (!zone_transfer_needed(adata->param->zone, pkt)) { - if (knot_pkt_section(pkt, KNOT_ANSWER)->count > 1) { - IXFRIN_LOG(LOG_WARNING, "old data, ignoring"); - } else { - /* Single-SOA answer. */ - IXFRIN_LOG(LOG_INFO, "zone is up-to-date"); - } - return KNOT_STATE_DONE; - } - - IXFRIN_LOG(LOG_INFO, "starting"); - // First packet with IXFR, init context - int ret = ixfrin_answer_init(adata); - if (ret != KNOT_EOK) { - IXFRIN_LOG(LOG_WARNING, "failed (%s)", knot_strerror(ret)); - return KNOT_STATE_FAIL; - } - } else { - NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 100); - } - - int ret = process_ixfrin_packet(pkt, adata); - if (ret == KNOT_STATE_DONE) { - NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 0); - int fret = ixfrin_finalize(adata); - if (fret != KNOT_EOK) { - ret = KNOT_STATE_FAIL; - } - } - - return ret; -} diff --git a/src/knot/nameserver/ixfr.h b/src/knot/nameserver/ixfr.h index 3bd8dfd18..ec212eb13 100644 --- a/src/knot/nameserver/ixfr.h +++ b/src/knot/nameserver/ixfr.h @@ -24,9 +24,41 @@ #pragma once -#include "libknot/packet/pkt.h" -#include "knot/query/query.h" #include "knot/nameserver/process_query.h" +#include "knot/nameserver/xfr.h" +#include "knot/query/query.h" +#include "libknot/packet/pkt.h" + +/*! \brief IXFR-in processing states. */ +enum ixfr_state { + IXFR_INVALID = 0, + IXFR_START, /* IXFR-in starting, expecting final SOA. */ + IXFR_SOA_DEL, /* Expecting starting SOA. */ + IXFR_SOA_ADD, /* Expecting ending SOA. */ + IXFR_DEL, /* Expecting RR to delete. */ + IXFR_ADD, /* Expecting RR to add. */ + IXFR_DONE /* Processing done, IXFR-in complete. */ +}; + +/*! \brief Extended structure for IXFR-in/IXFR-out processing. */ +struct ixfr_proc { + /* Processing state. */ + struct xfr_proc proc; + enum ixfr_state state; + + /* Changes to be sent. */ + list_t changesets; + + /* Currenty processed changeset. */ + knot_rrset_t cur_rr; + changeset_iter_t cur; + const knot_rrset_t *soa_from; + const knot_rrset_t *soa_to; + + /* Processing context. */ + struct query_data *qdata; + knot_mm_t *mm; +}; /*! * \brief IXFR query processing module. @@ -37,13 +69,4 @@ */ int ixfr_process_query(knot_pkt_t *pkt, struct query_data *qdata); -/*! - * \brief IXFR response processing module. - * - * \retval CONSUME if more data are required. - * \retval FAIL if it encountered an error, retry over AXFR will be done. - * \retval DONE if finished. - */ -int ixfr_process_answer(knot_pkt_t *pkt, struct answer_data *adata); - /*! @} */ diff --git a/src/knot/nameserver/log.h b/src/knot/nameserver/log.h index 3f6268a9f..73d3b0571 100644 --- a/src/knot/nameserver/log.h +++ b/src/knot/nameserver/log.h @@ -18,15 +18,70 @@ #include "contrib/sockaddr.h" #include "knot/common/log.h" +#include "libknot/dname.h" + +enum log_operation { + LOG_OPERATION_AXFR, + LOG_OPERATION_IXFR, + LOG_OPERATION_NOTIFY, + LOG_OPERATION_REFRESH, + LOG_OPERATION_UPDATE, +}; + +enum log_direction { + LOG_DIRECTION_IN, + LOG_DIRECTION_OUT, +}; + +static inline const char *log_operation_name(enum log_operation operation) +{ + switch (operation) { + case LOG_OPERATION_AXFR: + return "AXFR"; + case LOG_OPERATION_IXFR: + return "IXFR"; + case LOG_OPERATION_NOTIFY: + return "notify"; + case LOG_OPERATION_REFRESH: + return "refresh"; + case LOG_OPERATION_UPDATE: + return "DDNS"; + default: + return "?"; + } +} + +static inline const char *log_direction_name(enum log_direction direction) +{ + switch (direction) { + case LOG_DIRECTION_IN: + return "incoming"; + case LOG_DIRECTION_OUT: + return "outgoing"; + default: + return "?"; + } +} /*! - * \brief Base log message format for network communication. + * \brief Generate log message for server communication. + * + * If this macro was a function: + * + * void ns_log(int priority, const knot_dname_t *zone, enum log_operation op, + * enum log_direction dir, const struct sockaddr *remote, + * const char *fmt, ...); + * + * Example output: + * + * [example.com] NOTIFY, outgoing, 2001:db8::1@53: serial 123 * - * Emits a message in the following format: - * > [zone] operation, address: custom formatted message */ -#define NS_PROC_LOG(priority, zone, remote, operation, msg, ...) do { \ - char addr[SOCKADDR_STRLEN] = ""; \ - sockaddr_tostr(addr, sizeof(addr), (struct sockaddr *)remote); \ - log_msg_zone(priority, zone, "%s, %s: " msg, operation, addr, ##__VA_ARGS__); \ +#define ns_log(priority, zone, op, dir, remote, fmt, ...) \ + do { \ + char address[SOCKADDR_STRLEN] = ""; \ + sockaddr_tostr(address, sizeof(address), remote); \ + log_msg_zone(priority, zone, "%s, %s, %s: " fmt, \ + log_operation_name(op), log_direction_name(dir), address, \ + ## __VA_ARGS__); \ } while (0) diff --git a/src/knot/nameserver/notify.c b/src/knot/nameserver/notify.c index 53795c334..59dee7c4f 100644 --- a/src/knot/nameserver/notify.c +++ b/src/knot/nameserver/notify.c @@ -24,15 +24,9 @@ #include "dnssec/random.h" #include "libknot/libknot.h" -/* NOTIFY-specific logging (internal, expects 'qdata' variable set). */ -#define NOTIFY_IN_LOG(priority, msg, ...) \ - NS_PROC_LOG(priority, knot_pkt_qname(qdata->query), qdata->param->remote, \ - "NOTIFY, incoming", msg, ##__VA_ARGS__) - -/* NOTIFY-specific logging (internal, expects 'adata' variable set). */ -#define NOTIFY_OUT_LOG(priority, msg, ...) \ - NS_PROC_LOG(priority, adata->param->zone->name, adata->param->remote, \ - "NOTIFY, outgoing", msg, ##__VA_ARGS__) +#define NOTIFY_IN_LOG(priority, qdata, fmt...) \ + ns_log(priority, knot_pkt_qname(qdata->query), LOG_OPERATION_NOTIFY, \ + LOG_DIRECTION_IN, (struct sockaddr *)qdata->param->remote, fmt) static int notify_check_query(struct query_data *qdata) { @@ -58,7 +52,7 @@ int notify_process_query(knot_pkt_t *pkt, struct query_data *qdata) if (state == KNOT_STATE_FAIL) { switch (qdata->rcode) { case KNOT_RCODE_NOTAUTH: /* Not authoritative or ACL check failed. */ - NOTIFY_IN_LOG(LOG_NOTICE, "unauthorized request"); + NOTIFY_IN_LOG(LOG_NOTICE, qdata, "unauthorized request"); break; case KNOT_RCODE_FORMERR: /* Silently ignore bad queries. */ default: @@ -76,12 +70,12 @@ int notify_process_query(knot_pkt_t *pkt, struct query_data *qdata) const knot_rrset_t *soa = knot_pkt_rr(answer, 0); if (soa->type == KNOT_RRTYPE_SOA) { uint32_t serial = knot_soa_serial(&soa->rrs); - NOTIFY_IN_LOG(LOG_INFO, "received serial %u", serial); + NOTIFY_IN_LOG(LOG_INFO, qdata, "received serial %u", serial); } else { /* Complain, but accept N/A record. */ - NOTIFY_IN_LOG(LOG_NOTICE, "received, bad record in answer section"); + NOTIFY_IN_LOG(LOG_NOTICE, qdata, "received, bad record in answer section"); } } else { - NOTIFY_IN_LOG(LOG_INFO, "received, doesn't have SOA"); + NOTIFY_IN_LOG(LOG_INFO, qdata, "received, doesn't have SOA"); } /* Incoming NOTIFY expires REFRESH timer and renews EXPIRE timer. */ @@ -91,24 +85,3 @@ int notify_process_query(knot_pkt_t *pkt, struct query_data *qdata) return KNOT_STATE_DONE; } - -int notify_process_answer(knot_pkt_t *pkt, struct answer_data *adata) -{ - if (pkt == NULL || adata == NULL) { - return KNOT_STATE_FAIL; - } - - /* Check RCODE. */ - uint8_t rcode = knot_wire_get_rcode(pkt->wire); - if (rcode != KNOT_RCODE_NOERROR) { - const knot_lookup_t *lut = knot_lookup_by_id(knot_rcode_names, rcode); - if (lut != NULL) { - NOTIFY_OUT_LOG(LOG_WARNING, "server responded with %s", lut->name); - } - return KNOT_STATE_FAIL; - } - - NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 0); - - return KNOT_STATE_DONE; /* No processing. */ -} diff --git a/src/knot/nameserver/notify.h b/src/knot/nameserver/notify.h index 6d508ba37..24e0bcced 100644 --- a/src/knot/nameserver/notify.h +++ b/src/knot/nameserver/notify.h @@ -38,12 +38,4 @@ */ int notify_process_query(knot_pkt_t *pkt, struct query_data *qdata); -/*! - * \brief Process an answer to the NOTIFY query. - * - * \retval FAIL if it encountered an error. - * \retval DONE if finished. - */ -int notify_process_answer(knot_pkt_t *pkt, struct answer_data *data); - /*! @} */ diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c index f8fb701de..af18d495e 100644 --- a/src/knot/nameserver/process_query.c +++ b/src/knot/nameserver/process_query.c @@ -689,7 +689,6 @@ int process_query_sign_response(knot_pkt_t *pkt, struct query_data *qdata) /* KEY provided and verified TSIG or BADTIME allows signing. */ if (ctx->tsig_key.name != NULL && knot_tsig_can_sign(qdata->rcode_tsig)) { - /* Sign query response. */ size_t new_digest_len = dnssec_tsig_algorithm_size(ctx->tsig_key.algorithm); if (ctx->pkt_count == 0) { diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c index 9bd48a80c..89815bec7 100644 --- a/src/knot/nameserver/update.c +++ b/src/knot/nameserver/update.c @@ -31,10 +31,9 @@ #include "contrib/net.h" #include "contrib/time.h" -/* UPDATE-specific logging (internal, expects 'qdata' variable set). */ -#define UPDATE_LOG(severity, msg, ...) \ - NS_PROC_LOG(severity, knot_pkt_qname(qdata->query), qdata->param->remote, \ - "DDNS", msg, ##__VA_ARGS__) +#define UPDATE_LOG(priority, qdata, fmt...) \ + ns_log(priority, knot_pkt_qname(qdata->query), LOG_OPERATION_UPDATE, \ + LOG_DIRECTION_IN, (struct sockaddr *)qdata->param->remote, fmt) static void init_qdata_from_request(struct query_data *qdata, const zone_t *zone, @@ -55,7 +54,7 @@ static int check_prereqs(struct knot_request *request, uint16_t rcode = KNOT_RCODE_NOERROR; int ret = ddns_process_prereqs(request->query, update, &rcode); if (ret != KNOT_EOK) { - UPDATE_LOG(LOG_WARNING, "prerequisites not met (%s)", + UPDATE_LOG(LOG_WARNING, qdata, "prerequisites not met (%s)", knot_strerror(ret)); assert(rcode != KNOT_RCODE_NOERROR); knot_wire_set_rcode(request->resp->wire, rcode); @@ -72,7 +71,7 @@ static int process_single_update(struct knot_request *request, uint16_t rcode = KNOT_RCODE_NOERROR; int ret = ddns_process_update(zone, request->query, update, &rcode); if (ret != KNOT_EOK) { - UPDATE_LOG(LOG_WARNING, "failed to apply (%s)", + UPDATE_LOG(LOG_WARNING, qdata, "failed to apply (%s)", knot_strerror(ret)); assert(rcode != KNOT_RCODE_NOERROR); knot_wire_set_rcode(request->resp->wire, rcode); @@ -233,7 +232,7 @@ static int remote_forward(conf_t *conf, struct knot_request *request, conf_remot /* Create a request. */ const struct sockaddr *dst = (const struct sockaddr *)&remote->addr; const struct sockaddr *src = (const struct sockaddr *)&remote->via; - struct knot_request *req = knot_request_make(re.mm, dst, src, query, 0); + struct knot_request *req = knot_request_make(re.mm, dst, src, query, NULL, 0); if (req == NULL) { knot_requestor_clear(&re); knot_pkt_free(&query); @@ -305,14 +304,14 @@ static bool update_tsig_check(conf_t *conf, struct query_data *qdata, struct kno { // Check that ACL is still valid. if (!process_query_acl_check(conf, qdata->zone->name, ACL_ACTION_UPDATE, qdata)) { - UPDATE_LOG(LOG_WARNING, "ACL check failed"); + UPDATE_LOG(LOG_WARNING, qdata, "ACL check failed"); knot_wire_set_rcode(req->resp->wire, qdata->rcode); return false; } else { // Check TSIG validity. int ret = process_query_verify(qdata); if (ret != KNOT_EOK) { - UPDATE_LOG(LOG_WARNING, "failed (%s)", + UPDATE_LOG(LOG_WARNING, qdata, "failed (%s)", knot_strerror(ret)); knot_wire_set_rcode(req->resp->wire, qdata->rcode); return false; diff --git a/src/knot/nameserver/xfr.c b/src/knot/nameserver/xfr.c new file mode 100644 index 000000000..b9419191e --- /dev/null +++ b/src/knot/nameserver/xfr.c @@ -0,0 +1,87 @@ +/* Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. + */ + +#include "knot/nameserver/xfr.h" +#include "contrib/mempattern.h" + +int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb process_item, + struct query_data *qdata) +{ + if (pkt == NULL || qdata == NULL || qdata->ext == NULL) { + return KNOT_EINVAL; + } + + int ret = KNOT_EOK; + knot_mm_t *mm = qdata->mm; + struct xfr_proc *xfer = qdata->ext; + + zone_contents_t *zone = qdata->zone->contents; + knot_rrset_t soa_rr = node_rrset(zone->apex, KNOT_RRTYPE_SOA); + + /* Prepend SOA on first packet. */ + if (xfer->stats.messages == 0) { + ret = knot_pkt_put(pkt, 0, &soa_rr, KNOT_PF_NOTRUNC); + if (ret != KNOT_EOK) { + return ret; + } + } + + /* Process all items in the list. */ + while (!EMPTY_LIST(xfer->nodes)) { + ptrnode_t *head = HEAD(xfer->nodes); + ret = process_item(pkt, head->d, xfer); + if (ret == KNOT_EOK) { /* Finished. */ + /* Complete change set. */ + rem_node((node_t *)head); + mm_free(mm, head); + } else { /* Packet full or other error. */ + break; + } + } + + /* Append SOA on last packet. */ + if (ret == KNOT_EOK) { + ret = knot_pkt_put(pkt, 0, &soa_rr, KNOT_PF_NOTRUNC); + } + + /* Update counters. */ + xfr_stats_add(&xfer->stats, pkt->size); + + return ret; +} + +void xfr_stats_begin(struct xfr_stats *stats) +{ + assert(stats); + + memset(stats, 0, sizeof(*stats)); + stats->begin = time_now(); +} + +void xfr_stats_add(struct xfr_stats *stats, unsigned bytes) +{ + assert(stats); + + stats->messages += 1; + stats->bytes += bytes; +} + +void xfr_stats_end(struct xfr_stats *stats) +{ + assert(stats); + + stats->end = time_now(); +} diff --git a/src/knot/nameserver/xfr.h b/src/knot/nameserver/xfr.h new file mode 100644 index 000000000..605085ff6 --- /dev/null +++ b/src/knot/nameserver/xfr.h @@ -0,0 +1,69 @@ +/* Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "contrib/time.h" +#include "contrib/ucw/lists.h" +#include "knot/nameserver/log.h" +#include "knot/nameserver/process_query.h" +#include "knot/zone/contents.h" +#include "libknot/packet/pkt.h" + +struct xfr_stats { + unsigned messages; + unsigned bytes; + struct timespec begin; + struct timespec end; +}; + +void xfr_stats_begin(struct xfr_stats *stats); +void xfr_stats_add(struct xfr_stats *stats, unsigned bytes); +void xfr_stats_end(struct xfr_stats *stats); + +static inline +void xfr_log_finished(const knot_dname_t *zone, enum log_operation op, + enum log_direction dir, const struct sockaddr *remote, + const struct xfr_stats *stats) +{ + ns_log(LOG_INFO, zone, op, dir, remote, + "finished, %0.2f seconds, %u messages, %u bytes", + time_diff_ms(&stats->begin, &stats->end) / 1000.0, + stats->messages, stats->bytes); +} + +/*! + * \brief Generic transfer processing state. + */ +struct xfr_proc { + list_t nodes; //!< Items to process (ptrnode_t). + zone_contents_t *contents; //!< Processed zone. + struct xfr_stats stats; //!< Packet transfer statistics. +}; + +/*! + * \brief Generic transfer processing. + * + * \return KNOT_EOK or an error + */ +typedef int (*xfr_put_cb)(knot_pkt_t *pkt, const void *item, struct xfr_proc *xfer); + +/*! + * \brief Put all items from xfr_proc.nodes to packet using a callback function. + * + * \note qdata->ext points to struct xfr_proc* (this is xfer-specific context) + */ +int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb put, struct query_data *qdata); diff --git a/src/knot/query/query.c b/src/knot/query/query.c index ae078ee19..f4d0826bd 100644 --- a/src/knot/query/query.c +++ b/src/knot/query/query.c @@ -14,256 +14,100 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "knot/query/query.h" + #include <stdint.h> -#include <string.h> -#include "contrib/mempattern.h" -#include "contrib/ucw/mempool.h" #include "contrib/wire.h" +#include "contrib/wire_ctx.h" #include "dnssec/random.h" -#include "knot/query/query.h" -#include "knot/query/requestor.h" -#include "knot/zone/zone.h" -#include "libknot/mm_ctx.h" -#include "libknot/packet/pkt.h" +#include "knot/conf/conf.h" #include "libknot/yparser/yptrafo.h" +#include "libknot/rrset.h" -// event handlers: -#include "knot/nameserver/axfr.h" -#include "knot/nameserver/internet.h" -#include "knot/nameserver/ixfr.h" -#include "knot/nameserver/notify.h" - -/*! \brief Accessor to query-specific data. */ -#define ANSWER_DATA(ctx) ((struct answer_data *)(ctx)->data) -#define RESPONSE_TYPE_UNSET -1 - -static void answer_data_init(knot_layer_t *ctx, void *module_param) +int query_init_pkt(knot_pkt_t *pkt) { - /* Initialize persistent data. */ - struct answer_data *data = ANSWER_DATA(ctx); - memset(data, 0, sizeof(struct answer_data)); - data->response_type = RESPONSE_TYPE_UNSET; - data->mm = ctx->mm; - data->param = module_param; -} - -/*! \brief Answer is paired to query if MsgId matches. - * @note Zone transfers are deliberate with QUESTION section and may not - * include it in multi-packet responses, therefore the response can be - * paired by MsgId only. - */ -static bool is_answer_to_query(const knot_pkt_t *query, knot_pkt_t *answer) -{ - return knot_wire_get_id(query->wire) == knot_wire_get_id(answer->wire); -} - -static int process_answer_begin(knot_layer_t *ctx, void *module_param) -{ - /* Initialize context. */ - assert(ctx); - ctx->data = mm_alloc(ctx->mm, sizeof(struct answer_data)); - - /* Initialize persistent data. */ - answer_data_init(ctx, module_param); - - /* Issue the query. */ - return KNOT_STATE_PRODUCE; -} - -static int process_answer_reset(knot_layer_t *ctx) -{ - assert(ctx); - struct answer_data *data = ANSWER_DATA(ctx); - - /* Remember persistent parameters. */ - struct process_answer_param *module_param = data->param; - - /* Free allocated data. */ - if (data->ext_cleanup != NULL) { - data->ext_cleanup(data); + if (!pkt) { + return KNOT_EINVAL; } - /* Initialize persistent data. */ - answer_data_init(ctx, module_param); + knot_pkt_clear(pkt); + knot_wire_set_id(pkt->wire, dnssec_random_uint16_t()); - /* Issue the query. */ - return KNOT_STATE_PRODUCE; + return KNOT_EOK; } -static int process_answer_finish(knot_layer_t *ctx) +int query_edns_data_init(struct query_edns_data *edns_ptr, conf_t *conf, + const knot_dname_t *zone, int remote_family) { - process_answer_reset(ctx); - mm_free(ctx->mm, ctx->data); - ctx->data = NULL; - - return KNOT_STATE_NOOP; -} - -/* \note Private helper for process_answer repetitive checks. */ -#define ANSWER_REQUIRES(condition, ret) \ - if (!(condition)) { \ - knot_pkt_free(&pkt); \ - return ret; \ + if (!edns_ptr || !conf || !zone) { + return KNOT_EINVAL; } -static int process_answer(knot_layer_t *ctx, knot_pkt_t *pkt) -{ - assert(pkt && ctx); - struct answer_data *data = ANSWER_DATA(ctx); - - /* Check parse state. */ - ANSWER_REQUIRES(pkt->parsed >= KNOT_WIRE_HEADER_SIZE, KNOT_STATE_FAIL); - ANSWER_REQUIRES(pkt->parsed == pkt->size, KNOT_STATE_FAIL); - /* Accept only responses. */ - ANSWER_REQUIRES(knot_wire_get_qr(pkt->wire), KNOT_STATE_NOOP); - /* Check if we want answer paired to query. */ - const knot_pkt_t *query = data->param->query; - if (!query) { - return KNOT_STATE_FAIL; - } - ANSWER_REQUIRES(is_answer_to_query(query, pkt), KNOT_STATE_NOOP); + struct query_edns_data edns = { 0 }; - /* Verify incoming packet. */ - int ret = tsig_verify_packet(&data->param->tsig_ctx, pkt); - if (ret != KNOT_EOK) { - NS_PROC_LOG(LOG_WARNING, data->param->zone->name, data->param->remote, - "response", "denied (%s)", knot_strerror(ret)); - return KNOT_STATE_FAIL; - } + // Determine max payload - /* Call appropriate processing handler. */ - int next_state = KNOT_STATE_NOOP; - if (data->response_type == RESPONSE_TYPE_UNSET) { - /* @note We can't derive type from response, as it may not contain QUESTION at all. */ - data->response_type = knot_pkt_type(query) | KNOT_RESPONSE; - } - switch(data->response_type) { - case KNOT_RESPONSE_NORMAL: - next_state = internet_process_answer(pkt, data); - break; - case KNOT_RESPONSE_AXFR: - next_state = axfr_process_answer(pkt, data); - break; - case KNOT_RESPONSE_IXFR: - next_state = ixfr_process_answer(pkt, data); + switch (remote_family) { + case AF_INET: + edns.max_payload = conf->cache.srv_max_ipv4_udp_payload; break; - case KNOT_RESPONSE_NOTIFY: - next_state = notify_process_answer(pkt, data); + case AF_INET6: + edns.max_payload = conf->cache.srv_max_ipv6_udp_payload; break; default: - next_state = KNOT_STATE_NOOP; - break; - } - - return next_state; -} -#undef ANSWER_REQUIRES - -static int prepare_query(knot_layer_t *ctx, knot_pkt_t *pkt) -{ - /* \note Don't touch the query, expect answer. */ - return KNOT_STATE_CONSUME; -} - -/*! \brief Module implementation. */ -const knot_layer_api_t *process_answer_layer(void) -{ - static const knot_layer_api_t api = { - .begin = &process_answer_begin, - .reset = &process_answer_reset, - .finish = &process_answer_finish, - .consume = &process_answer, - .produce = &prepare_query, - }; - return &api; -} - - - -/*! \brief Create zone query packet. */ -static knot_pkt_t *zone_query(const zone_t *zone, uint16_t pkt_type, knot_mm_t *mm) -{ - /* Determine query type and opcode. */ - uint16_t query_type = KNOT_RRTYPE_SOA; - uint16_t opcode = KNOT_OPCODE_QUERY; - switch(pkt_type) { - case KNOT_QUERY_AXFR: query_type = KNOT_RRTYPE_AXFR; break; - case KNOT_QUERY_IXFR: query_type = KNOT_RRTYPE_IXFR; break; - case KNOT_QUERY_NOTIFY: opcode = KNOT_OPCODE_NOTIFY; break; - } - - knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, mm); - if (pkt == NULL) { - return NULL; + return KNOT_EINVAL; } - knot_wire_set_id(pkt->wire, dnssec_random_uint16_t()); - knot_wire_set_opcode(pkt->wire, opcode); - if (pkt_type == KNOT_QUERY_NOTIFY) { - knot_wire_set_aa(pkt->wire); - } + // Determine custom option - knot_pkt_put_question(pkt, zone->name, KNOT_CLASS_IN, query_type); - - /* Put current SOA (optional). */ - zone_contents_t *contents = zone->contents; - if (pkt_type == KNOT_QUERY_IXFR) { /* RFC1995, SOA in AUTHORITY. */ - knot_pkt_begin(pkt, KNOT_AUTHORITY); - knot_rrset_t soa_rr = node_rrset(contents->apex, KNOT_RRTYPE_SOA); - knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &soa_rr, 0); - } else if (pkt_type == KNOT_QUERY_NOTIFY) { /* RFC1996, SOA in ANSWER. */ - knot_pkt_begin(pkt, KNOT_ANSWER); - knot_rrset_t soa_rr = node_rrset(contents->apex, KNOT_RRTYPE_SOA); - knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &soa_rr, 0); - } - - return pkt; + conf_val_t val = conf_zone_get(conf, C_REQUEST_EDNS_OPTION, zone); + size_t opt_len = 0; + const uint8_t *opt_data = conf_data(&val, &opt_len); + if (opt_data != NULL) { + wire_ctx_t ctx = wire_ctx_init_const(opt_data, opt_len); + edns.custom_code = wire_ctx_read_u64(&ctx); + #warning No boundary check in the yparser API. + edns.custom_len = yp_bin_len(ctx.position); + edns.custom_data = yp_bin(ctx.position); + if (ctx.error != KNOT_EOK) { + return KNOT_EINVAL; + } + } + + *edns_ptr = edns; + return KNOT_EOK; } -/*! \brief Set EDNS section. */ -static int prepare_edns(conf_t *conf, zone_t *zone, knot_pkt_t *pkt, - const conf_remote_t *remote) +int query_put_edns(knot_pkt_t *pkt, const struct query_edns_data *edns) { - conf_val_t val = conf_zone_get(conf, C_REQUEST_EDNS_OPTION, zone->name); - - /* Check if an extra EDNS option is configured. */ - size_t opt_len; - const uint8_t *opt_data = conf_data(&val, &opt_len); - if (opt_data == NULL) { - return KNOT_EOK; + if (!pkt || !edns) { + return KNOT_EINVAL; } - int16_t max_payload; - switch (remote->addr.ss_family) { - case AF_INET: - max_payload = conf->cache.srv_max_ipv4_udp_payload; - break; - case AF_INET6: - max_payload = conf->cache.srv_max_ipv6_udp_payload; - break; - default: - return KNOT_ERROR; - } - - knot_rrset_t opt_rr; + // Construct EDNS RR - int ret = knot_edns_init(&opt_rr, max_payload, 0, KNOT_EDNS_VERSION, &pkt->mm); + knot_rrset_t opt_rr = { 0 }; + int ret = knot_edns_init(&opt_rr, edns->max_payload, 0, KNOT_EDNS_VERSION, &pkt->mm); if (ret != KNOT_EOK) { return ret; } - ret = knot_edns_add_option(&opt_rr, wire_read_u64(opt_data), - yp_bin_len(opt_data + sizeof(uint64_t)), - yp_bin(opt_data + sizeof(uint64_t)), &pkt->mm); - if (ret != KNOT_EOK) { - knot_rrset_clear(&opt_rr, &pkt->mm); - return ret; + if (edns->custom_code != 0) { + ret = knot_edns_add_option(&opt_rr, edns->custom_code, + edns->custom_len, edns->custom_data, + &pkt->mm); + if (ret != KNOT_EOK) { + knot_rrset_clear(&opt_rr, &pkt->mm); + return ret; + } } + // Add result into the packet + knot_pkt_begin(pkt, KNOT_ADDITIONAL); - ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, &opt_rr, KNOT_PF_FREE); + ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_NOCOMP, &opt_rr, KNOT_PF_FREE); if (ret != KNOT_EOK) { knot_rrset_clear(&opt_rr, &pkt->mm); return ret; @@ -271,94 +115,3 @@ static int prepare_edns(conf_t *conf, zone_t *zone, knot_pkt_t *pkt, return KNOT_EOK; } - -/*! \brief Process query using requestor. */ -static int zone_query_request(knot_pkt_t *query, const conf_remote_t *remote, - struct process_answer_param *param, knot_mm_t *mm) -{ - /* Create requestor instance. */ - const knot_layer_api_t *api = process_answer_layer(); - struct knot_requestor re; - int ret = knot_requestor_init(&re, api, param, mm); - if (ret != KNOT_EOK) { - return ret; - } - - /* Create a request. */ - const struct sockaddr *dst = (const struct sockaddr *)&remote->addr; - const struct sockaddr *src = (const struct sockaddr *)&remote->via; - struct knot_request *req = knot_request_make(re.mm, dst, src, query, 0); - if (req == NULL) { - knot_requestor_clear(&re); - return KNOT_ENOMEM; - } - - /* Send the queries and process responses. */ - int timeout = 1000 * param->conf->cache.srv_tcp_reply_timeout; - ret = knot_requestor_exec(&re, req, timeout); - - /* Cleanup. */ - knot_request_free(req, re.mm); - knot_requestor_clear(&re); - - return ret; -} - -/*! - * \brief Create a zone event query, send it, wait for the response and process it. - * - * \note Everything in this function is executed synchronously, returns when - * the query processing is either complete or an error occurs. - */ -int zone_query_execute(conf_t *conf, zone_t *zone, uint16_t pkt_type, - const conf_remote_t *remote) -{ - /* Create a memory pool for this task. */ - knot_mm_t mm; - mm_ctx_mempool(&mm, MM_DEFAULT_BLKSIZE); - - /* Create a query message. */ - knot_pkt_t *query = zone_query(zone, pkt_type, &mm); - if (query == NULL) { - mp_delete(mm.ctx); - return KNOT_ENOMEM; - } - - /* Set EDNS section. */ - int ret = prepare_edns(conf, zone, query, remote); - if (ret != KNOT_EOK) { - knot_pkt_free(&query); - mp_delete(mm.ctx); - return ret; - } - - /* Answer processing parameters. */ - struct process_answer_param param = { - .zone = zone, - .conf = conf, - .query = query, - .remote = &remote->addr - }; - - const knot_tsig_key_t *key = remote->key.name != NULL ? - &remote->key : NULL; - tsig_init(¶m.tsig_ctx, key); - - ret = tsig_sign_packet(¶m.tsig_ctx, query); - if (ret != KNOT_EOK) { - tsig_cleanup(¶m.tsig_ctx); - knot_pkt_free(&query); - mp_delete(mm.ctx); - return ret; - } - - /* Process the query. */ - ret = zone_query_request(query, remote, ¶m, &mm); - - /* Cleanup. */ - tsig_cleanup(¶m.tsig_ctx); - knot_pkt_free(&query); - mp_delete(mm.ctx); - - return ret; -} diff --git a/src/knot/query/query.h b/src/knot/query/query.h index 4d3a9ca62..cd6029eb3 100644 --- a/src/knot/query/query.h +++ b/src/knot/query/query.h @@ -16,44 +16,51 @@ #pragma once -#include <stdint.h> - -#include "knot/conf/conf.h" -#include "knot/nameserver/tsig_ctx.h" #include "knot/nameserver/log.h" -#include "knot/query/layer.h" -#include "knot/zone/zone.h" - -/* Answer processing module implementation. */ -const knot_layer_api_t *process_answer_layer(void); +#include "libknot/packet/pkt.h" /*! - * \brief Processing module parameters. + * \brief EDNS data. */ -struct process_answer_param { - zone_t *zone; /*!< Answer bailiwick. */ - conf_t *conf; /*!< Configuration. */ - const knot_pkt_t *query; /*!< Query preceding the answer. */ - const struct sockaddr_storage *remote; /*!< Answer origin. */ - tsig_ctx_t tsig_ctx; /*!< Signing context. */ +struct query_edns_data { + uint16_t max_payload; + + // Custom EDNS option: + uint16_t custom_code; + const uint8_t *custom_data; + uint16_t custom_len; }; /*! - * \brief Processing module context. + * \brief Initialize new packet. + * + * Clear the packet and generate random transaction ID. + * + * \param pkt Packet to initialize. + * + * \return Always KNOT_EOK if valid parameters supplied. */ -struct answer_data { - /* Extensions. */ - void *ext; - void (*ext_cleanup)(struct answer_data*); /*!< Extensions cleanup callback. */ - knot_sign_context_t sign; /*!< Signing context. */ - - /* Everything below should be kept on reset. */ - int response_type; /*!< Type of incoming response. */ - struct process_answer_param *param; /*!< Module parameters. */ - knot_mm_t *mm; /*!< Memory context. */ -}; +int query_init_pkt(knot_pkt_t *pkt); -int zone_query_execute(conf_t *conf, zone_t *zone, uint16_t pkt_type, const conf_remote_t *remote); +/*! + * \brief Initialize EDNS parameters from server configuration. + * + * \param[out] edns EDNS parameters to initialize. + * \param[in] conf Server configuration. + * \param[in] zone Zone name. + * \param[in] remote_family Address family for remote host. + * + * \return KNOT_E* + */ +int query_edns_data_init(struct query_edns_data *edns, conf_t *conf, + const knot_dname_t *zone, int remote_family); -#define ZONE_QUERY_LOG(priority, zone, remote, operation, msg, ...) \ - NS_PROC_LOG(priority, zone->name, &(remote)->addr, operation, msg, ##__VA_ARGS__) +/*! + * \brief Append EDNS into the packet. + * + * \param pkt Packet to add EDNS into. + * \param edns EDNS data. + * + * \return KNOT_E* + */ +int query_put_edns(knot_pkt_t *pkt, const struct query_edns_data *edns); diff --git a/src/knot/query/requestor.c b/src/knot/query/requestor.c index d589a3799..54b4e4151 100644 --- a/src/knot/query/requestor.c +++ b/src/knot/query/requestor.c @@ -28,6 +28,11 @@ static bool use_tcp(struct knot_request *request) return (request->flags & KNOT_RQ_UDP) == 0; } +static bool is_answer_to_query(const knot_pkt_t *query, const knot_pkt_t *answer) +{ + return knot_wire_get_id(query->wire) == knot_wire_get_id(answer->wire); +} + /*! \brief Ensure a socket is connected. */ static int request_ensure_connected(struct knot_request *request) { @@ -229,6 +234,10 @@ static int request_consume(struct knot_requestor *req, return ret; } + if (!is_answer_to_query(last->query, last->resp)) { + return KNOT_EMALF; + } + ret = tsig_verify_packet(&last->tsig, last->resp); if (ret != KNOT_EOK) { return ret; diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c index b64223174..351517f35 100644 --- a/src/knot/zone/zone.c +++ b/src/knot/zone/zone.c @@ -444,27 +444,3 @@ size_t zone_update_dequeue(zone_t *zone, list_t *updates) return update_count; } - -bool zone_transfer_needed(const zone_t *zone, const knot_pkt_t *pkt) -{ - if (zone == NULL || pkt == NULL) { - return false; - } - - if (zone_contents_is_empty(zone->contents)) { - return true; - } - - const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); - if (answer->count < 1) { - return false; - } - - const knot_rrset_t *soa = knot_pkt_rr(answer, 0); - if (soa->type != KNOT_RRTYPE_SOA) { - return false; - } - - return serial_compare(zone_contents_serial(zone->contents), - knot_soa_serial(&soa->rrs)) < 0; -} diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h index cde7f3c34..8ebef0caa 100644 --- a/src/knot/zone/zone.h +++ b/src/knot/zone/zone.h @@ -48,6 +48,8 @@ typedef enum zone_flag_t { ZONE_EXPIRED = 1 << 3, /* Zone is expired. */ } zone_flag_t; +/// XXX: ^ remove expired? + /*! * \brief Structure for holding DNS zone. */ @@ -159,7 +161,4 @@ int zone_update_enqueue(zone_t *zone, knot_pkt_t *pkt, struct process_query_para /*! \brief Dequeue UPDATE request. Returns number of queued updates. */ size_t zone_update_dequeue(zone_t *zone, list_t *updates); -/*! \brief Returns true if final SOA in transfer has newer serial than zone */ -bool zone_transfer_needed(const zone_t *zone, const knot_pkt_t *pkt); - /*! @} */ diff --git a/src/libknot/packet/pkt.h b/src/libknot/packet/pkt.h index 2c6d7b79f..6fdf8f49e 100644 --- a/src/libknot/packet/pkt.h +++ b/src/libknot/packet/pkt.h @@ -41,6 +41,7 @@ /* Forward decls */ struct knot_pkt; +/// XXX: server internal, wipe from the library /*! * \brief DNS query types (internal use only). * @@ -181,6 +182,7 @@ int knot_pkt_reserve(knot_pkt_t *pkt, uint16_t size); */ int knot_pkt_reclaim(knot_pkt_t *pkt, uint16_t size); +// XXX: probably useless /*! \brief Classify packet according to the question. * \return see enum knot_pkt_type_t */ diff --git a/tests/.gitignore b/tests/.gitignore index 933a46a2d..6668bb4a6 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -48,7 +48,6 @@ /journal /modules/online_sign /node -/process_answer /process_query /query_module /requestor diff --git a/tests/Makefile.am b/tests/Makefile.am index 97840d5f6..3a24add52 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -61,7 +61,6 @@ check_PROGRAMS += \ fdset \ journal \ node \ - process_answer \ process_query \ query_module \ requestor \ @@ -112,4 +111,3 @@ conf_SOURCES = conf.c test_conf.h confdb_SOURCES = confdb.c test_conf.h confio_SOURCES = confio.c test_conf.h process_query_SOURCES = process_query.c fake_server.h test_conf.h -process_answer_SOURCES = process_answer.c fake_server.h test_conf.h diff --git a/tests/process_answer.c b/tests/process_answer.c deleted file mode 100644 index e8416c9cb..000000000 --- a/tests/process_answer.c +++ /dev/null @@ -1,167 +0,0 @@ -/* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>. - */ - -#include <assert.h> -#include <tap/basic.h> -#include <string.h> -#include <stdlib.h> - -#include "libknot/descriptor.h" -#include "libknot/packet/wire.h" -#include "knot/query/query.h" -#include "fake_server.h" -#include "contrib/ucw/mempool.h" - -/* @note Test helpers. */ -#define TEST_RESET() \ - knot_layer_reset(proc); \ - knot_layer_produce(proc, pkt); \ - knot_pkt_clear(pkt) - -#define TEST_EXEC(expect, info) {\ - knot_pkt_parse(pkt, 0); \ - int state = knot_layer_consume(proc, pkt); \ - is_int((expect), state, "proc_answer: " info); \ - } - -#define INVALID_COUNT 2 -#define SPECIFIC_COUNT 1 -#define INCLASS_COUNT 2 -#define TEST_COUNT INVALID_COUNT + SPECIFIC_COUNT + INCLASS_COUNT - -static void test_invalid(knot_pkt_t *pkt, knot_layer_t *proc) -{ - /* Invalid packet - query. */ - TEST_RESET(); - knot_pkt_put_question(pkt, ROOT_DNAME, KNOT_CLASS_IN, KNOT_RRTYPE_A); - TEST_EXEC(KNOT_STATE_NOOP, "ignored query"); - - /* Invalid packet - mangled. */ - TEST_RESET(); - knot_pkt_put_question(pkt, ROOT_DNAME, KNOT_CLASS_IN, KNOT_RRTYPE_A); - pkt->size += 1; /* Mangle size. */ - TEST_EXEC(KNOT_STATE_FAIL, "malformed query"); -} - -/* Test if context accepts only answer to specific query. */ -static void test_specific(knot_pkt_t *pkt, knot_layer_t *proc, struct process_answer_param *param) -{ - /* Set specific SOA query. */ - uint16_t query_id = 0xBEEF; - knot_pkt_t *query = knot_pkt_new(NULL, KNOT_WIRE_MIN_PKTSIZE, proc->mm); - assert(query); - knot_pkt_put_question(query, ROOT_DNAME, KNOT_CLASS_IN, KNOT_RRTYPE_SOA); - knot_wire_set_id(query->wire, query_id); - param->query = query; - - /* MSGID mismatch */ - TEST_RESET(); - knot_pkt_init_response(pkt, param->query); - knot_wire_set_id(pkt->wire, 0xDEAD); - TEST_EXEC(KNOT_STATE_NOOP, "ignored mismatching MSGID"); - - /* Clear the specific query. */ - knot_pkt_free(&query); - param->query = NULL; -} - -static void test_inclass(knot_pkt_t *pkt, knot_layer_t *proc, struct process_answer_param *param) -{ - /* Set specific SOA query. */ - knot_pkt_t *query = knot_pkt_new(NULL, KNOT_WIRE_MIN_PKTSIZE, proc->mm); - assert(query); - knot_pkt_put_question(query, ROOT_DNAME, KNOT_CLASS_IN, KNOT_RRTYPE_SOA); - param->query = query; - - /* SOA query answer. */ - TEST_RESET(); - zone_node_t *apex = param->zone->contents->apex; - knot_rrset_t soa = node_rrset(apex, KNOT_RRTYPE_SOA); - knot_pkt_put_question(pkt, ROOT_DNAME, KNOT_CLASS_IN, KNOT_RRTYPE_SOA); - knot_wire_set_qr(pkt->wire); - knot_pkt_begin(pkt, KNOT_ANSWER); - knot_pkt_put(pkt, KNOT_COMPR_HINT_OWNER, &soa, 0); - TEST_EXEC(KNOT_STATE_DONE, "IN/SOA answer"); - - /* Unsupported anwer. */ - TEST_RESET(); - knot_pkt_put_question(pkt, ROOT_DNAME, KNOT_CLASS_IN, KNOT_RRTYPE_TXT); - knot_wire_set_qr(pkt->wire); - TEST_EXEC(KNOT_STATE_NOOP, "IN/unsupported answer"); - - /* Clear the specific query. */ - knot_pkt_free(&query); - param->query = NULL; -} - -int main(int argc, char *argv[]) -{ - plan(3 + TEST_COUNT); - - /* Create processing context. */ - knot_mm_t mm; - mm_ctx_mempool(&mm, MM_DEFAULT_BLKSIZE); - - knot_layer_t proc; - memset(&proc, 0, sizeof(knot_layer_t)); - knot_layer_init(&proc, &mm, process_answer_layer()); - - /* Create fake server environment. */ - server_t server; - int ret = create_fake_server(&server, proc.mm); - ok(ret == KNOT_EOK, "proc_answer: fake server initialization"); - - /* Prepare. */ - struct sockaddr_storage remote; - memset(&remote, 0, sizeof(struct sockaddr_storage)); - sockaddr_set(&remote, AF_INET, "127.0.0.1", 53); - struct process_answer_param param = {0}; - param.remote = &remote; - param.zone = knot_zonedb_find(server.zone_db, ROOT_DNAME); - knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, proc.mm); - - /* Begin processing. */ - int state = knot_layer_begin(&proc, ¶m); - ok(state == KNOT_STATE_PRODUCE, "proc_answer: expects query to be sent"); - - /* Invalid generic input tests. */ - test_invalid(pkt, &proc); - - /* Specific input tests (response to given query). */ - test_specific(pkt, &proc, ¶m); - - /* IN_CLASS input tests. */ - test_inclass(pkt, &proc, ¶m); - - /* IXFR input tests. */ - /* AXFR input tests. */ - /* NOTIFY input tests. */ - /* TSIG check tests. */ - - /* Finish. */ - state = knot_layer_finish(&proc); - ok(state == KNOT_STATE_NOOP, "proc_answer: processing end" ); - - /* Cleanup. */ - mp_delete(mm.ctx); - server_deinit(&server); - conf_free(conf()); - - return 0; -} - -#undef TEST_RESET -#undef TEST_EXEC diff --git a/tests/requestor.c b/tests/requestor.c index 13c324385..96a49da7f 100644 --- a/tests/requestor.c +++ b/tests/requestor.c @@ -88,7 +88,7 @@ static struct knot_request *make_query(struct knot_requestor *requestor, knot_pkt_put_question(pkt, root, KNOT_CLASS_IN, KNOT_RRTYPE_SOA); return knot_request_make(requestor->mm, (struct sockaddr *)dst, - (struct sockaddr *)src, pkt, 0); + (struct sockaddr *)src, pkt, NULL, 0); } static void test_disconnected(struct knot_requestor *requestor, -- GitLab