summaryrefslogtreecommitdiffstats
path: root/src/knot/events/handlers/ds_push.c
blob: 11aef7573cf625a6d8f50b5e57d3a51e5fa2ba05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*  Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <assert.h>

#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "knot/query/query.h"
#include "knot/query/requestor.h"
#include "knot/zone/zone.h"
#include "libknot/errcode.h"

struct ds_push_data {
	const knot_dname_t *zone;
	const knot_dname_t *parent_query;
	knot_dname_t *parent_soa;
	knot_rrset_t del_old_ds;
	knot_rrset_t new_ds;
	const struct sockaddr *remote;
	query_edns_data_t edns;
};

#define DS_PUSH_RETRY	600

#define DS_PUSH_LOG(priority, zone, remote, reused, fmt, ...) \
	ns_log(priority, zone, LOG_OPERATION_DS_PUSH, LOG_DIRECTION_OUT, remote, \
	       reused, fmt, ## __VA_ARGS__)

static const knot_rdata_t remove_cds = { 5, { 0, 0, 0, 0, 0 } };

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

	return KNOT_STATE_PRODUCE;
}

static int parent_soa_produce(struct ds_push_data *data, knot_pkt_t *pkt)
{
	assert(data->parent_query[0] != '\0');
	data->parent_query = knot_wire_next_label(data->parent_query, NULL);

	int ret = knot_pkt_put_question(pkt, data->parent_query, KNOT_CLASS_IN, KNOT_RRTYPE_SOA);
	if (ret != KNOT_EOK) {
		return KNOT_STATE_FAIL;
	}

	ret = query_put_edns(pkt, &data->edns);
	if (ret != KNOT_EOK) {
		return KNOT_STATE_FAIL;
	}

	return KNOT_STATE_CONSUME;
}

static int ds_push_produce(knot_layer_t *layer, knot_pkt_t *pkt)
{
	struct ds_push_data *data = layer->data;

	query_init_pkt(pkt);

	if (data->parent_soa == NULL) {
		return parent_soa_produce(data, pkt);
	}

	knot_wire_set_opcode(pkt->wire, KNOT_OPCODE_UPDATE);
	int ret = knot_pkt_put_question(pkt, data->parent_soa, KNOT_CLASS_IN, KNOT_RRTYPE_SOA);
	if (ret != KNOT_EOK) {
		return KNOT_STATE_FAIL;
	}

	knot_pkt_begin(pkt, KNOT_AUTHORITY);

	assert(data->del_old_ds.type == KNOT_RRTYPE_DS);
	ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, &data->del_old_ds, 0);
	if (ret != KNOT_EOK) {
		return KNOT_STATE_FAIL;
	}

	assert(data->new_ds.type == KNOT_RRTYPE_DS);
	assert(!knot_rrset_empty(&data->new_ds));
	if (knot_rdata_cmp(data->new_ds.rrs.rdata, &remove_cds) != 0) {
		// Otherwise only remove DS - it was a special "remove CDS".
		ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, &data->new_ds, 0);
		if (ret != KNOT_EOK) {
			return KNOT_STATE_FAIL;
		}
	}

	query_put_edns(pkt, &data->edns);

	return KNOT_STATE_CONSUME;
}

static const knot_rrset_t *sect_soa(const knot_pkt_t *pkt, knot_section_t sect)
{
	const knot_pktsection_t *s = knot_pkt_section(pkt, sect);
	const knot_rrset_t *rr = s->count > 0 ? knot_pkt_rr(s, 0) : NULL;
	if (rr == NULL || rr->type != KNOT_RRTYPE_SOA || rr->rrs.count != 1) {
		return NULL;
	}
	return rr;
}

static int ds_push_consume(knot_layer_t *layer, knot_pkt_t *pkt)
{
	struct ds_push_data *data = layer->data;

	if (data->parent_soa != NULL) {
		// DS push has already been sent, just finish the action.
		return KNOT_STATE_DONE;
	}

	const knot_rrset_t *parent_soa = sect_soa(pkt, KNOT_ANSWER);
	if (parent_soa != NULL) {
		// parent SOA obtained, continue with DS push
		data->parent_soa = knot_dname_copy(parent_soa->owner, NULL);
		return KNOT_STATE_RESET;
	}

	if (data->parent_query[0] == '\0') {
		// query for parent SOA systematically fails
		DS_PUSH_LOG(LOG_WARNING, data->zone, data->remote,
		            layer->flags & KNOT_REQUESTOR_REUSED,
		            "unable to query parent SOA");
		return KNOT_STATE_FAIL;
	}

	return KNOT_STATE_RESET; // cut off one more label and re-query
}

static int ds_push_reset(knot_layer_t *layer)
{
	(void)layer;
	return KNOT_STATE_PRODUCE;
}

static int ds_push_finish(knot_layer_t *layer)
{
	struct ds_push_data *data = layer->data;
	free(data->parent_soa);
	data->parent_soa = NULL;
	return layer->state;
}

static const knot_layer_api_t DS_PUSH_API = {
	.begin = ds_push_begin,
	.produce = ds_push_produce,
	.reset = ds_push_reset,
	.consume = ds_push_consume,
	.finish = ds_push_finish,
};

static int send_ds_push(conf_t *conf, zone_t *zone,
                        const conf_remote_t *parent, int timeout)
{
	knot_rrset_t zone_cds = node_rrset(zone->contents->apex, KNOT_RRTYPE_CDS);
	if (knot_rrset_empty(&zone_cds)) {
		return KNOT_EOK; // No CDS, do nothing.
	}
	zone_cds.type = KNOT_RRTYPE_DS;
	zone_cds.ttl = node_rrset(zone->contents->apex, KNOT_RRTYPE_DNSKEY).ttl;

	struct ds_push_data data = {
		.zone = zone->name,
		.parent_query = zone->name,
		.new_ds = zone_cds,
		.remote = (struct sockaddr *)&parent->addr,
		.edns = query_edns_data_init(conf, parent->addr.ss_family, 0)
	};

	knot_rrset_init(&data.del_old_ds, zone->name, KNOT_RRTYPE_DS, KNOT_CLASS_ANY, 0);
	int ret = knot_rrset_add_rdata(&data.del_old_ds, NULL, 0, NULL);
	if (ret != KNOT_EOK) {
		return ret;
	}

	knot_requestor_t requestor;
	knot_requestor_init(&requestor, &DS_PUSH_API, &data, NULL);

	knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL);
	if (pkt == NULL) {
		knot_rdataset_clear(&data.del_old_ds.rrs, NULL);
		knot_requestor_clear(&requestor);
		return KNOT_ENOMEM;
	}

	const struct sockaddr_storage *dst = &parent->addr;
	const struct sockaddr_storage *src = &parent->via;
	knot_request_t *req = knot_request_make(NULL, dst, src, pkt, &parent->key, 0);
	if (req == NULL) {
		knot_rdataset_clear(&data.del_old_ds.rrs, NULL);
		knot_request_free(req, NULL);
		knot_requestor_clear(&requestor);
		return KNOT_ENOMEM;
	}

	ret = knot_requestor_exec(&requestor, req, timeout);

	if (ret == KNOT_EOK && knot_pkt_ext_rcode(req->resp) == 0) {
		DS_PUSH_LOG(LOG_INFO, zone->name, dst,
		            requestor.layer.flags & KNOT_REQUESTOR_REUSED,
		            "success");
	} else if (knot_pkt_ext_rcode(req->resp) == 0) {
		DS_PUSH_LOG(LOG_WARNING, zone->name, dst,
		            requestor.layer.flags & KNOT_REQUESTOR_REUSED,
		            "failed (%s)", knot_strerror(ret));
	} else {
		DS_PUSH_LOG(LOG_WARNING, zone->name, dst,
		            requestor.layer.flags & KNOT_REQUESTOR_REUSED,
		            "server responded with error '%s'",
		            knot_pkt_ext_rcode_name(req->resp));
	}

	knot_rdataset_clear(&data.del_old_ds.rrs, NULL);
	knot_request_free(req, NULL);
	knot_requestor_clear(&requestor);

	return ret;
}

int event_ds_push(conf_t *conf, zone_t *zone)
{
	assert(zone);

	if (zone_contents_is_empty(zone->contents)) {
		return KNOT_EOK;
	}

	int timeout = conf->cache.srv_tcp_remote_io_timeout;

	conf_val_t ds_push = conf_zone_get(conf, C_DS_PUSH, zone->name);
	if (ds_push.code != KNOT_EOK) {
		conf_val_t policy_id = conf_zone_get(conf, C_DNSSEC_POLICY, zone->name);
		conf_id_fix_default(&policy_id);
		ds_push = conf_id_get(conf, C_POLICY, C_DS_PUSH, &policy_id);
	}
	conf_mix_iter_t iter;
	conf_mix_iter_init(conf, &ds_push, &iter);
	while (iter.id->code == KNOT_EOK) {
		conf_val_t addr = conf_id_get(conf, C_RMT, C_ADDR, iter.id);
		size_t addr_count = conf_val_count(&addr);

		int ret = KNOT_EOK;
		for (int i = 0; i < addr_count; i++) {
			conf_remote_t parent = conf_remote(conf, iter.id, i);
			ret = send_ds_push(conf, zone, &parent, timeout);
			if (ret == KNOT_EOK) {
				zone->timers.next_ds_push = 0;
				break;
			}
		}

		if (ret != KNOT_EOK) {
			time_t next_push = time(NULL) + DS_PUSH_RETRY;
			zone_events_schedule_at(zone, ZONE_EVENT_DS_PUSH, next_push);
			zone->timers.next_ds_push = next_push;
		}

		conf_mix_iter_next(&iter);
	}

	return KNOT_EOK;
}