From 5c1a5e28797f9c2eab1300247c052451ccd3be3e Mon Sep 17 00:00:00 2001 From: Libor Peltan <libor.peltan@nic.cz> Date: Tue, 31 Jan 2017 18:07:28 +0100 Subject: [PATCH] XFR: proper handling of single-SOA first response message --- src/knot/events/handlers/refresh.c | 152 +++++++++++++++++++++-------- 1 file changed, 112 insertions(+), 40 deletions(-) diff --git a/src/knot/events/handlers/refresh.c b/src/knot/events/handlers/refresh.c index 1167d79cf3..ba202f5bd1 100644 --- a/src/knot/events/handlers/refresh.c +++ b/src/knot/events/handlers/refresh.c @@ -86,6 +86,14 @@ enum state { STATE_TRANSFER, }; +enum xfr_type { + XFR_TYPE_ERROR = -1, + XFR_TYPE_UNDETERMINED = 0, + XFR_TYPE_UPTODATE, + XFR_TYPE_AXFR, + XFR_TYPE_IXFR, +}; + struct refresh_data { // transfer configuration, initialize appropriately: @@ -99,7 +107,8 @@ struct refresh_data { // internal state, initialize with zeroes: enum state state; //!< Event processing state. - bool is_ixfr; //!< Transfer is IXFR not AXFR. + enum xfr_type xfr_type; //!< Transer type (mostly IXFR versus AXFR). + knot_rrset_t *initial_soa_copy; //!< Copy of the received initial SOA. struct xfr_stats stats; //!< Transfer statistics. size_t change_size; //!< Size of added and removed RRs. @@ -156,7 +165,7 @@ static int xfr_validate(zone_contents_t *zone, struct refresh_data *data) if (zone->size > data->max_zone_size) { ns_log(LOG_WARNING, data->zone->name, - data->is_ixfr ? LOG_OPERATION_IXFR : LOG_OPERATION_AXFR, + data->xfr_type == XFR_TYPE_IXFR ? LOG_OPERATION_IXFR : LOG_OPERATION_AXFR, LOG_DIRECTION_IN, data->remote, "zone size exceeded"); return KNOT_EZONESIZE; } @@ -249,7 +258,7 @@ static int axfr_consume_rr(const knot_rrset_t *rr, struct refresh_data *data) data->change_size += knot_rrset_size(rr); if (data->change_size > data->max_zone_size) { AXFRIN_LOG(LOG_WARNING, data->zone->name, data->remote, - "zone size exceeded"); + "zone size exceeded"); return KNOT_STATE_FAIL; } @@ -297,9 +306,19 @@ static int axfr_consume(knot_pkt_t *pkt, struct refresh_data *data) data->change_size = 0; } + int next; + // Process saved SOA if fallback from IXFR + if (data->initial_soa_copy != NULL) { + next = axfr_consume_rr(data->initial_soa_copy, data); + knot_rrset_free(&data->initial_soa_copy, data->mm); + if (next != KNOT_STATE_CONSUME) { + return next; + } + } + // Process answer packet xfr_stats_add(&data->stats, pkt->size); - int next = axfr_consume_packet(pkt, data); + next = axfr_consume_packet(pkt, data); // Finalize if (next == KNOT_STATE_DONE) { @@ -537,14 +556,14 @@ static int ixfr_consume_rr(const knot_rrset_t *rr, struct refresh_data *data) int ret = ixfr_step(rr, data); if (ret != KNOT_EOK) { IXFRIN_LOG(LOG_WARNING, data->zone->name, data->remote, - "failed (%s)", knot_strerror(ret)); + "failed (%s)", knot_strerror(ret)); return KNOT_STATE_FAIL; } data->change_size += knot_rrset_size(rr); if (data->change_size / 2 > data->max_zone_size) { IXFRIN_LOG(LOG_WARNING, data->zone->name, data->remote, - "transfer size exceeded"); + "transfer size exceeded"); return KNOT_STATE_FAIL; } @@ -574,30 +593,38 @@ static int ixfr_consume_packet(knot_pkt_t *pkt, struct refresh_data *data) return ret; } -static bool ixfr_check_header(const knot_pktsection_t *answer) +static enum xfr_type determine_xfr_type(const knot_pktsection_t *answer, + uint32_t zone_serial, const knot_rrset_t *initial_soa) { - return answer->count >= 1 && - knot_pkt_rr(answer, 0)->type == KNOT_RRTYPE_SOA; -} - -static bool ixfr_is_axfr(const knot_pktsection_t *answer) -{ - if (answer->count < 2) { - return false; + if (answer->count < 1) { + return XFR_TYPE_ERROR; } const knot_rrset_t *rr_one = knot_pkt_rr(answer, 0); + if (initial_soa != NULL) { + if (rr_one->type == KNOT_RRTYPE_SOA) { + return knot_rrset_equal(initial_soa, rr_one, KNOT_RRSET_COMPARE_WHOLE) ? + XFR_TYPE_AXFR : XFR_TYPE_IXFR; + } + return XFR_TYPE_AXFR; + } + + if (answer->count == 1) { + if (rr_one->type == KNOT_RRTYPE_SOA) { + return serial_is_current(zone_serial, knot_soa_serial(&rr_one->rrs)) ? + XFR_TYPE_UPTODATE : XFR_TYPE_UNDETERMINED; + } + return XFR_TYPE_ERROR; + } + const knot_rrset_t *rr_two = knot_pkt_rr(answer, 1); + if (answer->count == 2 && rr_one->type == KNOT_RRTYPE_SOA && + knot_rrset_equal(rr_one, rr_two, KNOT_RRSET_COMPARE_WHOLE)) { + return XFR_TYPE_AXFR; + } - return ( - rr_one->type == KNOT_RRTYPE_SOA && - rr_two->type != KNOT_RRTYPE_SOA - ) || ( - answer->count == 2 && - rr_one->type == KNOT_RRTYPE_SOA && - rr_two->type == KNOT_RRTYPE_SOA && - knot_rrset_equal(rr_one, rr_two, KNOT_RRSET_COMPARE_WHOLE) - ); + return (rr_one->type == KNOT_RRTYPE_SOA && rr_two->type != KNOT_RRTYPE_SOA) ? + XFR_TYPE_AXFR : XFR_TYPE_IXFR; } static int ixfr_consume(knot_pkt_t *pkt, struct refresh_data *data) @@ -617,17 +644,37 @@ static int ixfr_consume(knot_pkt_t *pkt, struct refresh_data *data) if (data->ixfr.proc == NULL) { const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); - if (!ixfr_check_header(answer)) { + data->xfr_type = determine_xfr_type(answer, knot_soa_serial(&data->soa->rrs), + data->initial_soa_copy); + switch (data->xfr_type) { + case XFR_TYPE_ERROR: IXFRIN_LOG(LOG_WARNING, data->zone->name, data->remote, - "malformed response"); + "malformed response SOA"); return KNOT_STATE_FAIL; - } - - if (ixfr_is_axfr(answer)) { - IXFRIN_LOG(LOG_NOTICE, data->zone->name, data->remote, + case XFR_TYPE_UNDETERMINED: + // Store the SOA and check with next packet + data->initial_soa_copy = knot_rrset_copy(knot_pkt_rr(answer, 0), data->mm); + if (data->initial_soa_copy == NULL) { + return KNOT_STATE_FAIL; + } + xfr_stats_add(&data->stats, pkt->size); + return KNOT_STATE_CONSUME; + case XFR_TYPE_AXFR: + IXFRIN_LOG(LOG_INFO, data->zone->name, data->remote, "receiving AXFR-style IXFR"); - data->is_ixfr = false; return axfr_consume(pkt, data); + case XFR_TYPE_UPTODATE: + IXFRIN_LOG(LOG_INFO, data->zone->name, data->remote, + "zone is up-to-date"); + xfr_stats_begin(&data->stats); + xfr_stats_add(&data->stats, pkt->size); + xfr_stats_end(&data->stats); + return KNOT_STATE_DONE; + case XFR_TYPE_IXFR: + break; + default: + assert(0); + return KNOT_STATE_FAIL; } int ret = ixfr_init(data); @@ -642,9 +689,19 @@ static int ixfr_consume(knot_pkt_t *pkt, struct refresh_data *data) data->change_size = 0; } + int next; + // Process saved SOA if existing + if (data->initial_soa_copy != NULL) { + next = ixfr_consume_rr(data->initial_soa_copy, data); + knot_rrset_free(&data->initial_soa_copy, data->mm); + if (next != KNOT_STATE_CONSUME) { + return next; + } + } + // Process answer packet xfr_stats_add(&data->stats, pkt->size); - int next = ixfr_consume_packet(pkt, data); + next = ixfr_consume_packet(pkt, data); // Finalize if (next == KNOT_STATE_DONE) { @@ -712,7 +769,7 @@ static int transfer_produce(knot_layer_t *layer, knot_pkt_t *pkt) { struct refresh_data *data = layer->data; - bool ixfr = data->is_ixfr; + bool ixfr = (data->xfr_type == XFR_TYPE_IXFR); query_init_pkt(pkt); knot_pkt_put_question(pkt, data->zone->name, KNOT_CLASS_IN, @@ -733,13 +790,16 @@ static int transfer_consume(knot_layer_t *layer, knot_pkt_t *pkt) { struct refresh_data *data = layer->data; - int next = data->is_ixfr ? ixfr_consume(pkt, data) : axfr_consume(pkt, data); + int next = (data->xfr_type == XFR_TYPE_AXFR) ? axfr_consume(pkt, data) : + ixfr_consume(pkt, data); // Transfer completed if (next == KNOT_STATE_DONE) { // Log transfer even if we still can fail xfr_log_finished(data->zone->name, - data->is_ixfr ? LOG_OPERATION_IXFR : LOG_OPERATION_AXFR, + data->xfr_type == XFR_TYPE_IXFR || + data->xfr_type == XFR_TYPE_UPTODATE ? + LOG_OPERATION_IXFR : LOG_OPERATION_AXFR, LOG_DIRECTION_IN, data->remote, &data->stats); /* @@ -752,7 +812,17 @@ static int transfer_consume(knot_layer_t *layer, knot_pkt_t *pkt) } // Finalize and publish the zone - int ret = data->is_ixfr ? ixfr_finalize(data) : axfr_finalize(data); + int ret; + switch (data->xfr_type) { + case XFR_TYPE_IXFR: + ret = ixfr_finalize(data); + break; + case XFR_TYPE_AXFR: + ret = axfr_finalize(data); + break; + default: + return next; + } if (ret == KNOT_EOK) { data->updated = true; } else { @@ -761,11 +831,11 @@ static int transfer_consume(knot_layer_t *layer, knot_pkt_t *pkt) } // IXFR to AXFR failover - if (data->is_ixfr && next == KNOT_STATE_FAIL) { + if (data->xfr_type == XFR_TYPE_IXFR && next == KNOT_STATE_FAIL) { REFRESH_LOG(LOG_WARNING, data->zone->name, data->remote, "fallback to AXFR"); ixfr_cleanup(data); - data->is_ixfr = false; + data->xfr_type = XFR_TYPE_AXFR; return KNOT_STATE_RESET; } @@ -779,10 +849,12 @@ static int refresh_begin(knot_layer_t *layer, void *_data) if (data->soa) { data->state = STATE_SOA_QUERY; - data->is_ixfr = true; + data->xfr_type = XFR_TYPE_IXFR; + data->initial_soa_copy = NULL; } else { data->state = STATE_TRANSFER; - data->is_ixfr = false; + data->xfr_type = XFR_TYPE_AXFR; + data->initial_soa_copy = NULL; } return KNOT_STATE_PRODUCE; -- GitLab