ds_query.c 6.17 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/*  Copyright (C) 2018 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
14
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
15 16 17 18
 */

#include <assert.h>

19
#include "contrib/macros.h"
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "knot/dnssec/ds_query.h"
#include "knot/dnssec/key-events.h"
#include "knot/query/layer.h"
#include "knot/query/query.h"
#include "knot/query/requestor.h"

static bool match_key_ds(zone_key_t *key, knot_rdata_t *ds)
{
	assert(key);
	assert(ds);

	dnssec_binary_t ds_rdata = {
		.size = ds->len,
		.data = ds->data,
	};

	dnssec_binary_t cds_rdata = { 0 };

	int ret = zone_key_calculate_ds(key, &cds_rdata);
	if (ret != KNOT_EOK) {
		return false;
	}

	return (dnssec_binary_cmp(&cds_rdata, &ds_rdata) == 0);
}

struct ds_query_data {
	const knot_dname_t *zone_name;
	const struct sockaddr *remote;

	zone_key_t *key;

54 55
	uint16_t edns_max_payload;

56 57
	bool ds_ok;
	bool result_logged;
58 59

	uint32_t ttl;
60 61 62 63 64 65 66 67 68 69 70 71
};

static int ds_query_begin(knot_layer_t *layer, void *params)
{
	layer->data = params;

	return KNOT_STATE_PRODUCE;
}

static int ds_query_produce(knot_layer_t *layer, knot_pkt_t *pkt)
{
	struct ds_query_data *data = layer->data;
72
	struct query_edns_data edns = { .max_payload = data->edns_max_payload, .do_flag = true, };
73 74 75 76 77 78 79 80

	query_init_pkt(pkt);

	int r = knot_pkt_put_question(pkt, data->zone_name, KNOT_CLASS_IN, KNOT_RRTYPE_DS);
	if (r != KNOT_EOK) {
		return KNOT_STATE_FAIL;
	}

81 82 83 84 85
	r = query_put_edns(pkt, &edns);
	if (r != KNOT_EOK) {
		return KNOT_STATE_FAIL;
	}

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
	knot_wire_set_rd(pkt->wire);

	return KNOT_STATE_CONSUME;
}

static int ds_query_consume(knot_layer_t *layer, knot_pkt_t *pkt)
{
	struct ds_query_data *data = layer->data;
	data->result_logged = true;

	if (knot_pkt_ext_rcode(pkt) != KNOT_RCODE_NOERROR) {
		ns_log(LOG_WARNING, data->zone_name, LOG_OPERATION_PARENT,
		       LOG_DIRECTION_OUT, data->remote, "failed (%s)", knot_pkt_ext_rcode_name(pkt));
		return KNOT_STATE_FAIL;
	}

	const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER);

	bool match = false;

	for (size_t j = 0; j < answer->count; j++) {
		const knot_rrset_t *rr = knot_pkt_rr(answer, j);
108 109 110 111 112 113 114 115 116 117 118 119 120
		switch ((rr && rr->rrs.count > 0) ? rr->type : 0) {
		case KNOT_RRTYPE_DS:
			if (match_key_ds(data->key, rr->rrs.rdata)) {
				match = true;
				if (data->ttl == 0) { // fallback: if there is no RRSIG
					data->ttl = rr->ttl;
				}
			}
			break;
		case KNOT_RRTYPE_RRSIG:
			data->ttl = knot_rrsig_original_ttl(rr->rrs.rdata);
			break;
		default:
121 122 123 124 125 126 127 128
			break;
		}
	}

	ns_log(LOG_INFO, data->zone_name, LOG_OPERATION_PARENT,
	       LOG_DIRECTION_OUT, data->remote, "KSK submission attempt: %s",
	       (match ? "positive" : "negative"));

129 130 131
	if (match) {
		data->ds_ok = true;
	}
132 133 134 135 136 137 138 139 140 141 142
	return KNOT_STATE_DONE;
}

static const knot_layer_api_t ds_query_api = {
	.begin = ds_query_begin,
	.produce = ds_query_produce,
	.consume = ds_query_consume,
	.reset = NULL,
	.finish = NULL,
};

143 144
static int try_ds(const knot_dname_t *zone_name, const conf_remote_t *parent, zone_key_t *key,
                  size_t timeout, uint32_t *ds_ttl)
145 146 147 148 149 150 151 152 153 154 155 156
{
	// TODO: Abstract interface to issue DNS queries. This is almost copy-pasted.

	assert(zone_name);
	assert(parent);

	struct ds_query_data data = {
		.zone_name = zone_name,
		.remote = (struct sockaddr *)&parent->addr,
		.key = key,
		.ds_ok = false,
		.result_logged = false,
157
		.ttl = 0,
158 159
	};

Daniel Salzman's avatar
Daniel Salzman committed
160
	knot_requestor_t requestor;
161 162 163 164 165 166 167 168 169 170
	knot_requestor_init(&requestor, &ds_query_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 *)&parent->addr;
	const struct sockaddr *src = (struct sockaddr *)&parent->via;
Daniel Salzman's avatar
Daniel Salzman committed
171
	knot_request_t *req = knot_request_make(NULL, dst, src, pkt, &parent->key, 0);
172 173 174 175 176 177
	if (!req) {
		knot_request_free(req, NULL);
		knot_requestor_clear(&requestor);
		return KNOT_ENOMEM;
	}

178 179 180 181
	data.edns_max_payload = dst->sa_family == AF_INET6 ?
	                        conf()->cache.srv_max_ipv6_udp_payload :
	                        conf()->cache.srv_max_ipv4_udp_payload;

182 183 184 185 186 187 188 189 190 191 192 193 194 195
	int ret = knot_requestor_exec(&requestor, req, timeout);
	knot_request_free(req, NULL);
	knot_requestor_clear(&requestor);

	// alternative: we could put answer back through ctx instead of errcode
	if (ret == KNOT_EOK && !data.ds_ok) {
		ret = KNOT_ENORECORD;
	}

	if (ret != KNOT_EOK && !data.result_logged) {
		ns_log(LOG_WARNING, zone_name, LOG_OPERATION_PARENT,
		       LOG_DIRECTION_OUT, data.remote, "failed (%s)", knot_strerror(ret));
	}

196 197
	*ds_ttl = data.ttl;

198 199 200
	return ret;
}

201 202
static bool parents_have_ds(kdnssec_ctx_t *kctx, zone_key_t *key, size_t timeout,
                            uint32_t *max_ds_ttl)
203
{
204
	bool success = false;
205
	dynarray_foreach(parent, knot_kasp_parent_t, i, kctx->policy->parents) {
206
		success = false;
207
		for (size_t j = 0; j < i->addrs; j++) {
208 209
			uint32_t ds_ttl = 0;
			int ret = try_ds(kctx->zone->dname, &i->addr[j], key, timeout, &ds_ttl);
210
			if (ret == KNOT_EOK) {
211
				*max_ds_ttl = MAX(*max_ds_ttl, ds_ttl);
212 213
				success = true;
				break;
214 215 216
			} else if (ret == KNOT_ENORECORD) {
				// parent was queried successfully, answer was negative
				break;
217 218
			}
		}
219 220 221 222
		// Each parent must succeed.
		if (!success) {
			return false;
		}
223 224 225 226 227 228
	}
	return success;
}

int knot_parent_ds_query(kdnssec_ctx_t *kctx, zone_keyset_t *keyset, size_t timeout)
{
229 230
	uint32_t max_ds_ttl = 0;

231 232
	for (size_t i = 0; i < keyset->count; i++) {
		zone_key_t *key = &keyset->keys[i];
233 234 235
		if (key->is_ksk && key->cds_priority > 1) {
			if (parents_have_ds(kctx, key, timeout, &max_ds_ttl)) {
				return knot_dnssec_ksk_sbm_confirm(kctx, max_ds_ttl);
236 237 238 239 240 241 242
			} else {
				return KNOT_ENOENT;
			}
		}
	}
	return KNOT_ENOENT;
}