Newer
Older
/* Copyright (C) 2014 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 <libknot/descriptor.h>
#include <libknot/rrtype/rdname.h>
#include <libknot/processing/requestor.h>
#include <libknot/dnssec/random.h>
#include "lib/layer/iterate.h"
#define DEBUG_MSG(fmt, ...) fprintf(stderr, "[qiter] " fmt, ## __VA_ARGS__)
/* Iterator often walks through packet section, this is an abstraction. */
typedef int (*rr_callback_t)(const knot_rrset_t *, struct kr_layer_param *);
/*! \brief Return minimized QNAME/QTYPE for current zone cut. */
static const knot_dname_t *minimized_qname(struct kr_query *query, uint16_t *qtype)
/* Minimization disabled. */
const knot_dname_t *qname = query->sname;
if (query->flags & QUERY_NO_MINIMIZE) {
return qname;
}
/* Minimize name to contain current zone cut + 1 label. */
int cut_labels = knot_dname_labels(query->zone_cut.name, NULL);
int qname_labels = knot_dname_labels(qname, NULL);
while(qname_labels > cut_labels + 1) {
qname = knot_wire_next_label(qname, NULL);
qname_labels -= 1;
/* Hide QTYPE if minimized. */
if (qname != query->sname) {
*qtype = KNOT_RRTYPE_NS;
return qname;
}
/*! \brief Answer is paired to query. */
static bool is_paired_to_query(const knot_pkt_t *answer, struct kr_query *query)
{
uint16_t qtype = query->stype;
const knot_dname_t *qname = minimized_qname(query, &qtype);
return query->id == knot_wire_get_id(answer->wire) &&
query->sclass == knot_pkt_qclass(answer) &&
qtype == knot_pkt_qtype(answer) &&
knot_dname_is_equal(qname, knot_pkt_qname(answer));
static void follow_cname_chain(const knot_dname_t **cname, const knot_rrset_t *rr,
{
/* Follow chain from SNAME. */
if (knot_dname_is_equal(rr->owner, *cname)) {
*cname = knot_cname_name(&rr->rrs);
static int update_nsaddr(const knot_rrset_t *rr, struct kr_query *query)
{
if (rr == NULL || query == NULL) {
return KNOT_NS_PROC_MORE; /* Ignore */
}
if (rr->type == KNOT_RRTYPE_A || rr->type == KNOT_RRTYPE_AAAA) {
if (knot_dname_is_equal(query->zone_cut.ns, rr->owner)) {
int ret = kr_rrset_to_addr(&query->zone_cut.addr, rr);
if (ret == KNOT_EOK) {
return KNOT_NS_PROC_DONE;
}
}
}
return KNOT_NS_PROC_MORE;
}
static int update_glue(const knot_rrset_t *rr, struct kr_layer_param *param)
{
return update_nsaddr(rr, kr_rplan_current(param->rplan));
}
int rr_update_parent(const knot_rrset_t *rr, struct kr_layer_param *param)
struct kr_query *query = kr_rplan_current(param->rplan);
return update_nsaddr(rr, query->parent);
int rr_update_answer(const knot_rrset_t *rr, struct kr_layer_param *param)
knot_pkt_t *answer = param->answer;
knot_rrset_t *rr_copy = knot_rrset_copy(rr, &answer->mm);
}
/* Write copied RR to the result packet. */
int ret = knot_pkt_put(answer, KNOT_COMPR_HINT_NONE, rr_copy, KNOT_PF_FREE);
if (ret != KNOT_EOK) {
knot_rrset_free(&rr_copy, &answer->mm);
knot_wire_set_tc(answer->wire);
}
/* Free just the allocated container. */
/* Update parent query as well. */
int rr_update_nameserver(const knot_rrset_t *rr, struct kr_layer_param *param)
struct kr_query *query = kr_rplan_current(param->rplan);
const knot_dname_t *ns_name = knot_ns_name(&rr->rrs, 0);
/* Authority MUST be at/below the authority of the nameserver, otherwise
* possible cache injection attempt. */
if (!knot_dname_in(query->zone_cut.name, rr->owner)) {
DEBUG_MSG("NS in query outside of its authority => rejecting\n");
return KNOT_NS_PROC_FAIL;
/* Ignore already resolved zone cut. */
if (knot_dname_is_equal(rr->owner, query->zone_cut.name)) {
return KNOT_NS_PROC_MORE;
}
/* Set zone cut to given name server. */
kr_set_zone_cut(&query->zone_cut, rr->owner, ns_name);
return KNOT_NS_PROC_DONE;
static int process_authority(knot_pkt_t *pkt, struct kr_layer_param *param)
{
int state = KNOT_NS_PROC_MORE;
/* Answer declares AA, can't be referral. */
if (knot_wire_get_aa(pkt->wire)) {
return state;
}
/* Update current zone cut from NS records. */
const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY);
for (unsigned i = 0; i < ns->count; ++i) {
const knot_rrset_t *rr = knot_pkt_rr(ns, i);
if (rr->type != KNOT_RRTYPE_NS) {
continue;
}
state = rr_update_nameserver(rr, param);
if (state != KNOT_NS_PROC_MORE) {
break;
return state;
}
static int process_additional(knot_pkt_t *pkt, struct kr_layer_param *param)
{
struct kr_query *query = kr_rplan_current(param->rplan);
/* Attempt to find glue for current nameserver. */
const knot_pktsection_t *ar = knot_pkt_section(pkt, KNOT_ADDITIONAL);
for (unsigned i = 0; i < ar->count; ++i) {
const knot_rrset_t *rr = knot_pkt_rr(ar, i);
int state = update_glue(rr, param);
if (state != KNOT_NS_PROC_MORE) {
return state;
}
/* Glue not found => resolve NS address. */
(void) kr_rplan_push(param->rplan, query, query->zone_cut.ns, KNOT_CLASS_IN, KNOT_RRTYPE_AAAA);
(void) kr_rplan_push(param->rplan, query, query->zone_cut.ns, KNOT_CLASS_IN, KNOT_RRTYPE_A);
return KNOT_NS_PROC_DONE;
static int process_answer(knot_pkt_t *pkt, struct kr_layer_param *param)
struct kr_query *query = kr_rplan_current(param->rplan);
/* Response for minimized QNAME.
* NODATA => may be empty non-terminal, retry (found zone cut)
* NOERROR => found zone cut, retry
* NXDOMAIN => parent is zone cut, terminate
*/
bool is_minimized = (!knot_dname_is_equal(knot_pkt_qname(pkt), query->sname));
bool is_noerror = (knot_wire_get_rcode(pkt->wire) == KNOT_RCODE_NOERROR);
if (is_minimized && is_noerror) {
query->flags |= QUERY_NO_MINIMIZE;
return KNOT_NS_PROC_DONE;
}
/* Does this answer update the final response? */
rr_callback_t callback = &rr_update_parent;
if (query->parent == NULL) {
knot_wire_set_rcode(param->answer->wire, knot_wire_get_rcode(pkt->wire));
}
/* Process answer section records. */
const knot_dname_t *cname = query->sname;
const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER);
for (unsigned i = 0; i < an->count; ++i) {
int state = callback(rr, param);
if (state == KNOT_NS_PROC_FAIL) {
return state;
follow_cname_chain(&cname, rr, query);
}
/* Follow canonical name as next SNAME. */
if (cname != query->sname) {
(void) kr_rplan_push(param->rplan, query->parent, cname, query->sclass, query->stype);
/* This is either declares AA or not, either way it resolves current query. */
kr_rplan_pop(param->rplan, query);
return KNOT_NS_PROC_DONE;
}
/*! \brief Error handling, RFC1034 5.3.3, 4d. */
static int resolve_error(knot_pkt_t *pkt, struct kr_layer_param *param, int errcode)
DEBUG_MSG("resolution error => %s\n", knot_strerror(errcode));
return KNOT_NS_PROC_FAIL;
/* State-less single resolution iteration step, not needed. */
static int reset(knot_layer_t *ctx) { return KNOT_NS_PROC_FULL; }
static int finish(knot_layer_t *ctx) { return KNOT_NS_PROC_NOOP; }
/* Set resolution context and parameters. */
static int begin(knot_layer_t *ctx, void *module_param)
{
ctx->data = module_param;
return reset(ctx);
}
static int prepare_query(knot_layer_t *ctx, knot_pkt_t *pkt)
{
assert(pkt && ctx);
struct kr_layer_param *param = ctx->data;
struct kr_query *query = kr_rplan_current(param->rplan);
if (query == NULL || ctx->state == KNOT_NS_PROC_DONE) {
/* Minimize QNAME (if possible). */
uint16_t qtype = query->stype;
const knot_dname_t *qname = minimized_qname(query, &qtype);
/* Form a query for the authoritative. */
knot_pkt_clear(pkt);
int ret = knot_pkt_put_question(pkt, qname, query->sclass, qtype);
if (ret != KNOT_EOK) {
return KNOT_NS_PROC_FAIL;
query->id = knot_random_uint16_t();
knot_wire_set_id(pkt->wire, query->id);
/* Declare EDNS0 support. */
knot_rrset_t opt_rr;
ret = knot_edns_init(&opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, &pkt->mm);
if (ret != KNOT_EOK) {
return KNOT_NS_PROC_FAIL;
}
knot_pkt_begin(pkt, KNOT_ADDITIONAL);
ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, &opt_rr, KNOT_PF_FREE);
if (ret != KNOT_EOK) {
knot_rrset_clear(&opt_rr, &pkt->mm);
return KNOT_NS_PROC_FAIL;
}
char name_str[KNOT_DNAME_MAXLEN], zonecut_str[KNOT_DNAME_MAXLEN], ns_str[KNOT_DNAME_MAXLEN], type_str[16];
knot_dname_to_str(ns_str, query->zone_cut.ns, sizeof(ns_str));
knot_dname_to_str(zonecut_str, query->zone_cut.name, sizeof(zonecut_str));
knot_dname_to_str(name_str, qname, sizeof(name_str));
knot_rrtype_to_string(qtype, type_str, sizeof(type_str));
DEBUG_MSG("query '%s %s' zone cut '%s' nameserver '%s'\n", name_str, type_str, zonecut_str, ns_str);
return KNOT_NS_PROC_MORE;
/*! \brief Resolve input query or continue resolution with followups.
*
* This roughly corresponds to RFC1034, 5.3.3 4a-d.
*/
static int resolve(knot_layer_t *ctx, knot_pkt_t *pkt)
{
assert(pkt && ctx);
struct kr_layer_param *param = ctx->data;
struct kr_query *query = kr_rplan_current(param->rplan);
if (query == NULL) {
return ctx->state;
}
/* Check for packet processing errors first. */
if (pkt->parsed < pkt->size) {
} else if (!is_paired_to_query(pkt, query)) {
DEBUG_MSG("ignoring mismatching response\n");
return KNOT_NS_PROC_MORE;
} else if (knot_wire_get_tc(pkt->wire)) {
DEBUG_MSG("truncated response, failover to TCP\n");
struct kr_query *cur = kr_rplan_current(param->rplan);
if (cur) {
/* Fail if already on TCP. */
if (cur->flags & QUERY_TCP) {
return resolve_error(pkt, param, KNOT_EMALF);
}
cur->flags |= QUERY_TCP;
}
return KNOT_NS_PROC_DONE;
}
/* Check response code. */
switch(knot_wire_get_rcode(pkt->wire)) {
case KNOT_RCODE_NOERROR:
case KNOT_RCODE_NXDOMAIN:
break; /* OK */
default:
/* Resolve authority to see if it's referral or authoritative. */
int state = KNOT_NS_PROC_MORE;
state = process_authority(pkt, param);
switch(state) {
case KNOT_NS_PROC_MORE: /* Not referral, process answer. */
state = process_answer(pkt, param);
break;
case KNOT_NS_PROC_DONE: /* Referral, try to find glue. */
state = process_additional(pkt, param);
break;
default:
break;
/*! \brief Module implementation. */
static const knot_layer_api_t LAYER_ITERATE_MODULE = {
&begin,
&reset,
&finish,
&resolve,
&prepare_query,
NULL
const knot_layer_api_t *layer_iterate_module(void)
{
return &LAYER_ITERATE_MODULE;
}