1 /* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 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 #include "knot/catalog/interpret.h"
18 #include "knot/common/log.h"
19 #include "knot/dnssec/zone-events.h"
20 #include "knot/updates/zone-update.h"
21 #include "knot/zone/adds_tree.h"
22 #include "knot/zone/adjust.h"
23 #include "knot/zone/digest.h"
24 #include "knot/zone/serial.h"
25 #include "knot/zone/zone-diff.h"
26 #include "knot/zone/zonefile.h"
27 #include "contrib/trim.h"
28 #include "contrib/ucw/lists.h"
29
30 #include <signal.h>
31 #include <unistd.h>
32 #include <urcu.h>
33
34 // Call mem_trim() whenever accumuled size of updated zones reaches this size.
35 #define UPDATE_MEMTRIM_AT (10 * 1024 * 1024)
36
init_incremental(zone_update_t * update,zone_t * zone,zone_contents_t * old_contents)37 static int init_incremental(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents)
38 {
39 if (old_contents == NULL) {
40 return KNOT_EINVAL;
41 }
42
43 int ret = changeset_init(&update->change, zone->name);
44 if (ret != KNOT_EOK) {
45 return ret;
46 }
47
48 if (update->flags & UPDATE_HYBRID) {
49 update->new_cont = old_contents;
50 } else {
51 ret = zone_contents_cow(old_contents, &update->new_cont);
52 if (ret != KNOT_EOK) {
53 changeset_clear(&update->change);
54 return ret;
55 }
56 }
57
58 uint32_t apply_flags = (update->flags & UPDATE_STRICT) ? APPLY_STRICT : 0;
59 apply_flags |= (update->flags & UPDATE_HYBRID) ? APPLY_UNIFY_FULL : 0;
60 ret = apply_init_ctx(update->a_ctx, update->new_cont, apply_flags);
61 if (ret != KNOT_EOK) {
62 changeset_clear(&update->change);
63 return ret;
64 }
65
66 /* Copy base SOA RR. */
67 update->change.soa_from =
68 node_create_rrset(old_contents->apex, KNOT_RRTYPE_SOA);
69 if (update->change.soa_from == NULL) {
70 zone_contents_free(update->new_cont);
71 changeset_clear(&update->change);
72 return KNOT_ENOMEM;
73 }
74
75 return KNOT_EOK;
76 }
77
init_full(zone_update_t * update,zone_t * zone)78 static int init_full(zone_update_t *update, zone_t *zone)
79 {
80 update->new_cont = zone_contents_new(zone->name, true);
81 if (update->new_cont == NULL) {
82 return KNOT_ENOMEM;
83 }
84
85 int ret = apply_init_ctx(update->a_ctx, update->new_cont, APPLY_UNIFY_FULL);
86 if (ret != KNOT_EOK) {
87 zone_contents_free(update->new_cont);
88 return ret;
89 }
90
91 return KNOT_EOK;
92 }
93
replace_soa(zone_contents_t * contents,const knot_rrset_t * rr)94 static int replace_soa(zone_contents_t *contents, const knot_rrset_t *rr)
95 {
96 /* SOA possible only within apex. */
97 if (!knot_dname_is_equal(rr->owner, contents->apex->owner)) {
98 return KNOT_EDENIED;
99 }
100
101 knot_rrset_t old_soa = node_rrset(contents->apex, KNOT_RRTYPE_SOA);
102 zone_node_t *n = contents->apex;
103 int ret = zone_contents_remove_rr(contents, &old_soa, &n);
104 if (ret != KNOT_EOK && ret != KNOT_EINVAL) {
105 return ret;
106 }
107
108 ret = zone_contents_add_rr(contents, rr, &n);
109 if (ret == KNOT_ETTL) {
110 return KNOT_EOK;
111 }
112
113 return ret;
114 }
115
init_base(zone_update_t * update,zone_t * zone,zone_contents_t * old_contents,zone_update_flags_t flags)116 static int init_base(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents,
117 zone_update_flags_t flags)
118 {
119 if (update == NULL || zone == NULL) {
120 return KNOT_EINVAL;
121 }
122
123 memset(update, 0, sizeof(*update));
124 update->zone = zone;
125 update->flags = flags;
126
127 update->a_ctx = calloc(1, sizeof(*update->a_ctx));
128 if (update->a_ctx == NULL) {
129 return KNOT_ENOMEM;
130 }
131
132 if (zone->control_update != NULL && zone->control_update != update) {
133 log_zone_warning(zone->name, "blocked zone update due to open control transaction");
134 }
135
136 knot_sem_wait(&zone->cow_lock);
137 update->a_ctx->cow_mutex = &zone->cow_lock;
138
139 if (old_contents == NULL) {
140 old_contents = zone->contents; // don't obtain this pointer before any other zone_update ceased to exist!
141 }
142
143 int ret = KNOT_EINVAL;
144 if (flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
145 ret = init_incremental(update, zone, old_contents);
146 } else if (flags & UPDATE_FULL) {
147 ret = init_full(update, zone);
148 }
149 if (ret != KNOT_EOK) {
150 knot_sem_post(&zone->cow_lock);
151 free(update->a_ctx);
152 }
153
154 return ret;
155 }
156
157 /* ------------------------------- API -------------------------------------- */
158
zone_update_init(zone_update_t * update,zone_t * zone,zone_update_flags_t flags)159 int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t flags)
160 {
161 return init_base(update, zone, NULL, flags);
162 }
163
zone_update_from_differences(zone_update_t * update,zone_t * zone,zone_contents_t * old_cont,zone_contents_t * new_cont,zone_update_flags_t flags,bool ignore_dnssec)164 int zone_update_from_differences(zone_update_t *update, zone_t *zone, zone_contents_t *old_cont,
165 zone_contents_t *new_cont, zone_update_flags_t flags, bool ignore_dnssec)
166 {
167 if (update == NULL || zone == NULL || new_cont == NULL ||
168 !(flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) || (flags & UPDATE_FULL)) {
169 return KNOT_EINVAL;
170 }
171
172 changeset_t diff;
173 int ret = changeset_init(&diff, zone->name);
174 if (ret != KNOT_EOK) {
175 return ret;
176 }
177
178 ret = init_base(update, zone, old_cont, flags);
179 if (ret != KNOT_EOK) {
180 changeset_clear(&diff);
181 return ret;
182 }
183
184 if (old_cont == NULL) {
185 old_cont = zone->contents;
186 }
187
188 ret = zone_contents_diff(old_cont, new_cont, &diff, ignore_dnssec);
189 switch (ret) {
190 case KNOT_ENODIFF:
191 case KNOT_ESEMCHECK:
192 case KNOT_EOK:
193 break;
194 case KNOT_ERANGE:
195 additionals_tree_free(update->new_cont->adds_tree);
196 update->new_cont->adds_tree = NULL;
197 update->new_cont = NULL; // Prevent deep_free as old_cont will be used later.
198 update->a_ctx->flags &= ~APPLY_UNIFY_FULL; // Prevent Unify of old_cont that will be used later.
199 // FALLTHROUGH
200 default:
201 changeset_clear(&diff);
202 zone_update_clear(update);
203 return ret;
204 }
205
206 ret = zone_update_apply_changeset(update, &diff);
207 changeset_clear(&diff);
208 if (ret != KNOT_EOK) {
209 zone_update_clear(update);
210 return ret;
211 }
212
213 update->init_cont = new_cont;
214 return KNOT_EOK;
215 }
216
zone_update_from_contents(zone_update_t * update,zone_t * zone_without_contents,zone_contents_t * new_cont,zone_update_flags_t flags)217 int zone_update_from_contents(zone_update_t *update, zone_t *zone_without_contents,
218 zone_contents_t *new_cont, zone_update_flags_t flags)
219 {
220 if (update == NULL || zone_without_contents == NULL || new_cont == NULL) {
221 return KNOT_EINVAL;
222 }
223
224 memset(update, 0, sizeof(*update));
225 update->zone = zone_without_contents;
226 update->flags = flags;
227 update->new_cont = new_cont;
228
229 update->a_ctx = calloc(1, sizeof(*update->a_ctx));
230 if (update->a_ctx == NULL) {
231 return KNOT_ENOMEM;
232 }
233
234 if (zone_without_contents->control_update != NULL) {
235 log_zone_warning(zone_without_contents->name,
236 "blocked zone update due to open control transaction");
237 }
238
239 knot_sem_wait(&update->zone->cow_lock);
240 update->a_ctx->cow_mutex = &update->zone->cow_lock;
241
242 if (flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
243 int ret = changeset_init(&update->change, zone_without_contents->name);
244 if (ret != KNOT_EOK) {
245 free(update->a_ctx);
246 update->a_ctx = NULL;
247 knot_sem_post(&update->zone->cow_lock);
248 return ret;
249 }
250
251 update->change.soa_from = node_create_rrset(new_cont->apex, KNOT_RRTYPE_SOA);
252 if (update->change.soa_from == NULL) {
253 changeset_clear(&update->change);
254 free(update->a_ctx);
255 update->a_ctx = NULL;
256 knot_sem_post(&update->zone->cow_lock);
257 return KNOT_ENOMEM;
258 }
259 }
260
261 uint32_t apply_flags = (update->flags & UPDATE_STRICT) ? APPLY_STRICT : 0;
262 int ret = apply_init_ctx(update->a_ctx, update->new_cont, apply_flags | APPLY_UNIFY_FULL);
263 if (ret != KNOT_EOK) {
264 changeset_clear(&update->change);
265 free(update->a_ctx);
266 update->a_ctx = NULL;
267 knot_sem_post(&update->zone->cow_lock);
268 return ret;
269 }
270
271 return KNOT_EOK;
272 }
273
zone_update_start_extra(zone_update_t * update,conf_t * conf)274 int zone_update_start_extra(zone_update_t *update, conf_t *conf)
275 {
276 assert((update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)));
277
278 int ret = changeset_init(&update->extra_ch, update->new_cont->apex->owner);
279 if (ret != KNOT_EOK) {
280 return ret;
281 }
282
283 if (update->init_cont != NULL) {
284 ret = zone_update_increment_soa(update, conf);
285 if (ret != KNOT_EOK) {
286 return ret;
287 }
288
289 ret = zone_contents_diff(update->init_cont, update->new_cont, &update->extra_ch, false);
290 if (ret != KNOT_EOK) {
291 return ret;
292 }
293 } else {
294 update->extra_ch.soa_from = node_create_rrset(update->new_cont->apex, KNOT_RRTYPE_SOA);
295 if (update->extra_ch.soa_from == NULL) {
296 return KNOT_ENOMEM;
297 }
298
299 ret = zone_update_increment_soa(update, conf);
300 if (ret != KNOT_EOK) {
301 return ret;
302 }
303
304 update->extra_ch.soa_to = node_create_rrset(update->new_cont->apex, KNOT_RRTYPE_SOA);
305 if (update->extra_ch.soa_to == NULL) {
306 return KNOT_ENOMEM;
307 }
308 }
309
310 update->flags |= UPDATE_EXTRA_CHSET;
311 return KNOT_EOK;
312 }
313
zone_update_get_node(zone_update_t * update,const knot_dname_t * dname)314 const zone_node_t *zone_update_get_node(zone_update_t *update, const knot_dname_t *dname)
315 {
316 if (update == NULL || dname == NULL) {
317 return NULL;
318 }
319
320 return zone_contents_node_or_nsec3(update->new_cont, dname);
321 }
322
zone_update_current_serial(zone_update_t * update)323 uint32_t zone_update_current_serial(zone_update_t *update)
324 {
325 const zone_node_t *apex = update->new_cont->apex;
326 if (apex != NULL) {
327 return knot_soa_serial(node_rdataset(apex, KNOT_RRTYPE_SOA)->rdata);
328 } else {
329 return 0;
330 }
331 }
332
zone_update_changed_nsec3param(const zone_update_t * update)333 bool zone_update_changed_nsec3param(const zone_update_t *update)
334 {
335 if (update->zone->contents == NULL) {
336 return true;
337 }
338
339 dnssec_nsec3_params_t *orig = &update->zone->contents->nsec3_params;
340 dnssec_nsec3_params_t *upd = &update->new_cont->nsec3_params;
341 return !dnssec_nsec3_params_match(orig, upd);
342 }
343
zone_update_from(zone_update_t * update)344 const knot_rdataset_t *zone_update_from(zone_update_t *update)
345 {
346 if (update == NULL) {
347 return NULL;
348 }
349
350 if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
351 const zone_node_t *apex = update->zone->contents->apex;
352 return node_rdataset(apex, KNOT_RRTYPE_SOA);
353 }
354
355 return NULL;
356 }
357
zone_update_to(zone_update_t * update)358 const knot_rdataset_t *zone_update_to(zone_update_t *update)
359 {
360 if (update == NULL) {
361 return NULL;
362 }
363
364 if (update->flags & UPDATE_FULL) {
365 const zone_node_t *apex = update->new_cont->apex;
366 return node_rdataset(apex, KNOT_RRTYPE_SOA);
367 } else {
368 if (update->change.soa_to == NULL) {
369 return NULL;
370 }
371 return &update->change.soa_to->rrs;
372 }
373
374 return NULL;
375 }
376
zone_update_clear(zone_update_t * update)377 void zone_update_clear(zone_update_t *update)
378 {
379 if (update == NULL || update->zone == NULL) {
380 return;
381 }
382
383 if (update->new_cont != NULL) {
384 additionals_tree_free(update->new_cont->adds_tree);
385 update->new_cont->adds_tree = NULL;
386 }
387
388 if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
389 changeset_clear(&update->change);
390 changeset_clear(&update->extra_ch);
391 }
392
393 zone_contents_deep_free(update->init_cont);
394
395 if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
396 apply_cleanup(update->a_ctx);
397 zone_contents_deep_free(update->new_cont);
398 } else {
399 apply_rollback(update->a_ctx);
400 }
401
402 free(update->a_ctx);
403 memset(update, 0, sizeof(*update));
404 }
405
update_affected_rrtype(zone_update_t * update,uint16_t rrtype)406 inline static void update_affected_rrtype(zone_update_t *update, uint16_t rrtype)
407 {
408 switch (rrtype) {
409 case KNOT_RRTYPE_NSEC:
410 case KNOT_RRTYPE_NSEC3:
411 update->flags |= UPDATE_CHANGED_NSEC;
412 break;
413 }
414 }
415
solve_add_different_ttl(zone_update_t * update,const knot_rrset_t * add)416 static int solve_add_different_ttl(zone_update_t *update, const knot_rrset_t *add)
417 {
418 if (add->type == KNOT_RRTYPE_RRSIG || add->type == KNOT_RRTYPE_SOA) {
419 return KNOT_EOK;
420 }
421
422 const zone_node_t *exist_node = zone_contents_find_node(update->new_cont, add->owner);
423 const knot_rrset_t exist_rr = node_rrset(exist_node, add->type);
424 if (knot_rrset_empty(&exist_rr) || exist_rr.ttl == add->ttl) {
425 return KNOT_EOK;
426 }
427
428 knot_dname_txt_storage_t buff;
429 char *owner = knot_dname_to_str(buff, add->owner, sizeof(buff));
430 if (owner == NULL) {
431 owner = "";
432 }
433 char type[16] = "";
434 knot_rrtype_to_string(add->type, type, sizeof(type));
435 log_zone_notice(update->zone->name, "TTL mismatch, owner %s, type %s, "
436 "TTL set to %u", owner, type, add->ttl);
437
438 knot_rrset_t *exist_copy = knot_rrset_copy(&exist_rr, NULL);
439 if (exist_copy == NULL) {
440 return KNOT_ENOMEM;
441 }
442 int ret = zone_update_remove(update, exist_copy);
443 if (ret == KNOT_EOK) {
444 exist_copy->ttl = add->ttl;
445 ret = zone_update_add(update, exist_copy);
446 }
447 knot_rrset_free(exist_copy, NULL);
448 return ret;
449 }
450
zone_update_add(zone_update_t * update,const knot_rrset_t * rrset)451 int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset)
452 {
453 if (update == NULL || rrset == NULL) {
454 return KNOT_EINVAL;
455 }
456 if (knot_rrset_empty(rrset)) {
457 return KNOT_EOK;
458 }
459
460 if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
461 int ret = solve_add_different_ttl(update, rrset);
462 if (ret == KNOT_EOK) {
463 ret = changeset_add_addition(&update->change, rrset, CHANGESET_CHECK);
464 }
465 if (ret == KNOT_EOK && (update->flags & UPDATE_EXTRA_CHSET)) {
466 ret = changeset_add_addition(&update->extra_ch, rrset, CHANGESET_CHECK);
467 }
468 if (ret != KNOT_EOK) {
469 return ret;
470 }
471 }
472
473 if (update->flags & UPDATE_INCREMENTAL) {
474 if (rrset->type == KNOT_RRTYPE_SOA) {
475 // replace previous SOA
476 int ret = apply_replace_soa(update->a_ctx, rrset);
477 if (ret != KNOT_EOK) {
478 changeset_remove_addition(&update->change, rrset);
479 }
480 return ret;
481 }
482
483 int ret = apply_add_rr(update->a_ctx, rrset);
484 if (ret != KNOT_EOK) {
485 changeset_remove_addition(&update->change, rrset);
486 return ret;
487 }
488
489 update_affected_rrtype(update, rrset->type);
490 return KNOT_EOK;
491 } else if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
492 if (rrset->type == KNOT_RRTYPE_SOA) {
493 /* replace previous SOA */
494 return replace_soa(update->new_cont, rrset);
495 }
496
497 zone_node_t *n = NULL;
498 int ret = zone_contents_add_rr(update->new_cont, rrset, &n);
499 if (ret == KNOT_ETTL) {
500 knot_dname_txt_storage_t buff;
501 char *owner = knot_dname_to_str(buff, rrset->owner, sizeof(buff));
502 if (owner == NULL) {
503 owner = "";
504 }
505 char type[16] = "";
506 knot_rrtype_to_string(rrset->type, type, sizeof(type));
507 log_zone_notice(update->new_cont->apex->owner,
508 "TTL mismatch, owner %s, type %s, "
509 "TTL set to %u", owner, type, rrset->ttl);
510 return KNOT_EOK;
511 }
512
513 return ret;
514 } else {
515 return KNOT_EINVAL;
516 }
517 }
518
zone_update_remove(zone_update_t * update,const knot_rrset_t * rrset)519 int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset)
520 {
521 if (update == NULL || rrset == NULL) {
522 return KNOT_EINVAL;
523 }
524 if (knot_rrset_empty(rrset)) {
525 return KNOT_EOK;
526 }
527
528 if ((update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) && rrset->type != KNOT_RRTYPE_SOA) {
529 int ret = changeset_add_removal(&update->change, rrset, CHANGESET_CHECK);
530 if (ret == KNOT_EOK && (update->flags & UPDATE_EXTRA_CHSET)) {
531 ret = changeset_add_removal(&update->extra_ch, rrset, CHANGESET_CHECK);
532 }
533 if (ret != KNOT_EOK) {
534 return ret;
535 }
536 }
537
538 if (update->flags & UPDATE_INCREMENTAL) {
539 if (rrset->type == KNOT_RRTYPE_SOA) {
540 /* SOA is replaced with addition */
541 return KNOT_EOK;
542 }
543
544 int ret = apply_remove_rr(update->a_ctx, rrset);
545 if (ret != KNOT_EOK) {
546 changeset_remove_removal(&update->change, rrset);
547 return ret;
548 }
549
550 update_affected_rrtype(update, rrset->type);
551 return KNOT_EOK;
552 } else if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
553 zone_node_t *n = NULL;
554 return zone_contents_remove_rr(update->new_cont, rrset, &n);
555 } else {
556 return KNOT_EINVAL;
557 }
558 }
559
zone_update_remove_rrset(zone_update_t * update,knot_dname_t * owner,uint16_t type)560 int zone_update_remove_rrset(zone_update_t *update, knot_dname_t *owner, uint16_t type)
561 {
562 if (update == NULL || owner == NULL) {
563 return KNOT_EINVAL;
564 }
565
566 const zone_node_t *node = zone_contents_node_or_nsec3(update->new_cont, owner);
567 if (node == NULL) {
568 return KNOT_ENONODE;
569 }
570
571 knot_rrset_t rrset = node_rrset(node, type);
572 if (rrset.owner == NULL) {
573 return KNOT_ENOENT;
574 }
575
576 return zone_update_remove(update, &rrset);
577 }
578
zone_update_remove_node(zone_update_t * update,const knot_dname_t * owner)579 int zone_update_remove_node(zone_update_t *update, const knot_dname_t *owner)
580 {
581 if (update == NULL || owner == NULL) {
582 return KNOT_EINVAL;
583 }
584
585 const zone_node_t *node = zone_contents_node_or_nsec3(update->new_cont, owner);
586 if (node == NULL) {
587 return KNOT_ENONODE;
588 }
589
590 size_t rrset_count = node->rrset_count;
591 for (int i = 0; i < rrset_count; ++i) {
592 knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
593 int ret = zone_update_remove(update, &rrset);
594 if (ret != KNOT_EOK) {
595 return ret;
596 }
597 }
598
599 return KNOT_EOK;
600 }
601
update_chset_step(const knot_rrset_t * rrset,bool addition,void * ctx)602 static int update_chset_step(const knot_rrset_t *rrset, bool addition, void *ctx)
603 {
604 zone_update_t *update = ctx;
605 if (addition) {
606 return zone_update_add(update, rrset);
607 } else {
608 return zone_update_remove(update, rrset);
609 }
610 }
611
zone_update_apply_changeset(zone_update_t * update,const changeset_t * changes)612 int zone_update_apply_changeset(zone_update_t *update, const changeset_t *changes)
613 {
614 return changeset_walk(changes, update_chset_step, update);
615 }
616
zone_update_apply_changeset_reverse(zone_update_t * update,const changeset_t * changes)617 int zone_update_apply_changeset_reverse(zone_update_t *update, const changeset_t *changes)
618 {
619 changeset_t reverse;
620 reverse.remove = changes->add;
621 reverse.add = changes->remove;
622 reverse.soa_from = changes->soa_to;
623 reverse.soa_to = changes->soa_from;
624 return zone_update_apply_changeset(update, &reverse);
625 }
626
set_new_soa(zone_update_t * update,unsigned serial_policy)627 static int set_new_soa(zone_update_t *update, unsigned serial_policy)
628 {
629 assert(update);
630
631 knot_rrset_t *soa_cpy = node_create_rrset(update->new_cont->apex,
632 KNOT_RRTYPE_SOA);
633 if (soa_cpy == NULL) {
634 return KNOT_ENOMEM;
635 }
636
637 int ret = zone_update_remove(update, soa_cpy);
638 if (ret != KNOT_EOK) {
639 knot_rrset_free(soa_cpy, NULL);
640 return ret;
641 }
642
643 uint32_t old_serial = knot_soa_serial(soa_cpy->rrs.rdata);
644 uint32_t new_serial = serial_next(old_serial, serial_policy, 1);
645 if (serial_compare(old_serial, new_serial) != SERIAL_LOWER) {
646 log_zone_warning(update->zone->name, "updated SOA serial is lower "
647 "than current, serial %u -> %u",
648 old_serial, new_serial);
649 ret = KNOT_ESOAINVAL;
650 } else {
651 knot_soa_serial_set(soa_cpy->rrs.rdata, new_serial);
652
653 ret = zone_update_add(update, soa_cpy);
654 }
655 knot_rrset_free(soa_cpy, NULL);
656
657 return ret;
658 }
659
zone_update_increment_soa(zone_update_t * update,conf_t * conf)660 int zone_update_increment_soa(zone_update_t *update, conf_t *conf)
661 {
662 if (update == NULL || conf == NULL) {
663 return KNOT_EINVAL;
664 }
665
666 conf_val_t val = conf_zone_get(conf, C_SERIAL_POLICY, update->zone->name);
667 return set_new_soa(update, conf_opt(&val));
668 }
669
commit_journal(conf_t * conf,zone_update_t * update)670 static int commit_journal(conf_t *conf, zone_update_t *update)
671 {
672 conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
673 unsigned content = conf_opt(&val);
674 int ret = KNOT_EOK;
675 if ((update->flags & UPDATE_INCREMENTAL) ||
676 (update->flags & UPDATE_HYBRID)) {
677 changeset_t *extra = (update->flags & UPDATE_EXTRA_CHSET) ? &update->extra_ch : NULL;
678 if (content != JOURNAL_CONTENT_NONE && !changeset_empty(&update->change)) {
679 ret = zone_change_store(conf, update->zone, &update->change, extra);
680 }
681 } else {
682 if (content == JOURNAL_CONTENT_ALL) {
683 return zone_in_journal_store(conf, update->zone, update->new_cont);
684 } else if (content != JOURNAL_CONTENT_NONE) { // zone_in_journal_store does this automatically
685 return zone_changes_clear(conf, update->zone);
686 }
687 }
688 return ret;
689 }
690
commit_incremental(conf_t * conf,zone_update_t * update)691 static int commit_incremental(conf_t *conf, zone_update_t *update)
692 {
693 assert(update);
694
695 if (zone_update_to(update) == NULL && !changeset_empty(&update->change)) {
696 /* No SOA in the update, create one according to the current policy */
697 int ret = zone_update_increment_soa(update, conf);
698 if (ret != KNOT_EOK) {
699 return ret;
700 }
701 }
702
703 return KNOT_EOK;
704 }
705
commit_full(conf_t * conf,zone_update_t * update)706 static int commit_full(conf_t *conf, zone_update_t *update)
707 {
708 assert(update);
709
710 /* Check if we have SOA. We might consider adding full semantic check here.
711 * But if we wanted full sem-check I'd consider being it controlled by a flag
712 * - to enable/disable it on demand. */
713 if (!node_rrtype_exists(update->new_cont->apex, KNOT_RRTYPE_SOA)) {
714 return KNOT_ESEMCHECK;
715 }
716
717 return KNOT_EOK;
718 }
719
update_catalog(conf_t * conf,zone_update_t * update)720 static int update_catalog(conf_t *conf, zone_update_t *update)
721 {
722 conf_val_t val = conf_zone_get(conf, C_CATALOG_TPL, update->zone->name);
723 if (val.code != KNOT_EOK) {
724 return (val.code == KNOT_ENOENT || val.code == KNOT_YP_EINVAL_ID) ? KNOT_EOK : val.code;
725 }
726
727 zone_set_flag(update->zone, ZONE_IS_CATALOG);
728
729 int ret = catalog_zone_verify(update->new_cont);
730 if (ret != KNOT_EOK) {
731 return ret;
732 }
733
734 ssize_t upd_count = 0;
735 if ((update->flags & UPDATE_INCREMENTAL)) {
736 ret = catalog_update_from_zone(update->zone->catalog_upd,
737 update->change.remove, update->new_cont,
738 true, update->zone->catalog, &upd_count);
739 if (ret == KNOT_EOK) {
740 ret = catalog_update_from_zone(update->zone->catalog_upd,
741 update->change.add, update->new_cont,
742 false, NULL, &upd_count);
743 }
744 } else {
745 ret = catalog_update_del_all(update->zone->catalog_upd,
746 update->zone->catalog,
747 update->zone->name, &upd_count);
748 if (ret == KNOT_EOK) {
749 ret = catalog_update_from_zone(update->zone->catalog_upd,
750 update->new_cont, update->new_cont,
751 false, NULL, &upd_count);
752 }
753 }
754
755 if (ret == KNOT_EOK) {
756 log_zone_info(update->zone->name, "catalog reloaded, %zd updates", upd_count);
757 if (kill(getpid(), SIGUSR1) != 0) {
758 ret = knot_map_errno();
759 }
760 } else {
761 // this cant normally happen, just some ENOMEM or so
762 (void)catalog_update_del_all(update->zone->catalog_upd,
763 update->zone->catalog,
764 update->zone->name, &upd_count);
765 }
766
767 return ret;
768 }
769
770 typedef struct {
771 pthread_mutex_t lock;
772 size_t counter;
773 } counter_reach_t;
774
counter_reach(counter_reach_t * counter,size_t increment,size_t limit)775 static bool counter_reach(counter_reach_t *counter, size_t increment, size_t limit)
776 {
777 bool reach = false;
778 pthread_mutex_lock(&counter->lock);
779 counter->counter += increment;
780 if (counter->counter >= limit) {
781 counter->counter = 0;
782 reach = true;
783 }
784 pthread_mutex_unlock(&counter->lock);
785 return reach;
786 }
787
788 /*! \brief Struct for what needs to be cleared after RCU.
789 *
790 * This can't be zone_update_t structure as this might be already freed at that time.
791 */
792 typedef struct {
793 struct rcu_head rcuhead;
794
795 zone_contents_t *free_contents;
796 void (*free_method)(zone_contents_t *);
797
798 apply_ctx_t *cleanup_apply;
799
800 size_t new_cont_size;
801 } update_clear_ctx_t;
802
update_clear(struct rcu_head * param)803 static void update_clear(struct rcu_head *param)
804 {
805 static counter_reach_t counter = { PTHREAD_MUTEX_INITIALIZER, 0 };
806
807 update_clear_ctx_t *ctx = (update_clear_ctx_t *)param;
808
809 ctx->free_method(ctx->free_contents);
810 apply_cleanup(ctx->cleanup_apply);
811 free(ctx->cleanup_apply);
812
813 if (counter_reach(&counter, ctx->new_cont_size, UPDATE_MEMTRIM_AT)) {
814 mem_trim();
815 }
816
817 free(ctx);
818 }
819
discard_adds_tree(zone_update_t * update)820 static void discard_adds_tree(zone_update_t *update)
821 {
822 additionals_tree_free(update->new_cont->adds_tree);
823 update->new_cont->adds_tree = NULL;
824 }
825
zone_update_semcheck(zone_update_t * update)826 int zone_update_semcheck(zone_update_t *update)
827 {
828 if (update == NULL) {
829 return KNOT_EINVAL;
830 }
831
832 zone_tree_t *node_ptrs = (update->flags & UPDATE_INCREMENTAL) ?
833 update->a_ctx->node_ptrs : NULL;
834
835 // adjust_cb_nsec3_pointer not needed as we don't check DNSSEC here
836 int ret = zone_adjust_contents(update->new_cont, adjust_cb_flags, NULL,
837 false, false, 1, node_ptrs);
838 if (ret != KNOT_EOK) {
839 return ret;
840 }
841
842 sem_handler_t handler = {
843 .cb = err_handler_logger
844 };
845
846 ret = sem_checks_process(update->new_cont, SEMCHECK_MANDATORY_ONLY,
847 &handler, time(NULL));
848 if (ret != KNOT_EOK) {
849 // error is logged by the error handler
850 return ret;
851 }
852
853 return KNOT_EOK;
854 }
855
zone_update_verify_digest(conf_t * conf,zone_update_t * update)856 int zone_update_verify_digest(conf_t *conf, zone_update_t *update)
857 {
858 conf_val_t val = conf_zone_get(conf, C_ZONEMD_VERIFY, update->zone->name);
859 if (!conf_bool(&val)) {
860 return KNOT_EOK;
861 }
862
863 int ret = zone_contents_digest_verify(update->new_cont);
864 if (ret != KNOT_EOK) {
865 log_zone_error(update->zone->name, "ZONEMD, verification failed (%s)",
866 knot_strerror(ret));
867 } else {
868 log_zone_info(update->zone->name, "ZONEMD, verification successful");
869 }
870
871 return ret;
872 }
873
zone_update_commit(conf_t * conf,zone_update_t * update)874 int zone_update_commit(conf_t *conf, zone_update_t *update)
875 {
876 if (conf == NULL || update == NULL) {
877 return KNOT_EINVAL;
878 }
879
880 int ret = KNOT_EOK;
881
882 if ((update->flags & UPDATE_INCREMENTAL) && changeset_empty(&update->change)) {
883 zone_update_clear(update);
884 return KNOT_EOK;
885 }
886
887 if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
888 ret = commit_incremental(conf, update);
889 } else {
890 ret = commit_full(conf, update);
891 }
892 if (ret != KNOT_EOK) {
893 return ret;
894 }
895
896 conf_val_t val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name);
897 bool dnssec = conf_bool(&val);
898
899 conf_val_t thr = conf_zone_get(conf, C_ADJUST_THR, update->zone->name);
900 if ((update->flags & (UPDATE_HYBRID | UPDATE_FULL))) {
901 ret = zone_adjust_full(update->new_cont, conf_int(&thr));
902 } else {
903 ret = zone_adjust_incremental_update(update, conf_int(&thr));
904 }
905 if (ret != KNOT_EOK) {
906 discard_adds_tree(update);
907 return ret;
908 }
909
910 /* Check the zone size. */
911 val = conf_zone_get(conf, C_ZONE_MAX_SIZE, update->zone->name);
912 size_t size_limit = conf_int(&val);
913
914 if (update->new_cont->size > size_limit) {
915 discard_adds_tree(update);
916 return KNOT_EZONESIZE;
917 }
918
919 val = conf_zone_get(conf, C_DNSSEC_VALIDATION, update->zone->name);
920 if (conf_bool(&val)) {
921 bool incr_valid = update->flags & UPDATE_INCREMENTAL;
922 const char *msg_valid = incr_valid ? "incremental " : "";
923
924 ret = knot_dnssec_validate_zone(update, conf, incr_valid);
925 if (ret != KNOT_EOK) {
926 log_zone_error(update->zone->name, "DNSSEC, %svalidation failed (%s)",
927 msg_valid, knot_strerror(ret));
928 char name_str[KNOT_DNAME_TXT_MAXLEN], type_str[16];
929 if (knot_dname_to_str(name_str, update->validation_hint.node, sizeof(name_str)) != NULL &&
930 knot_rrtype_to_string(update->validation_hint.rrtype, type_str, sizeof(type_str)) >= 0) {
931 log_zone_error(update->zone->name, "DNSSEC, validation hint: %s %s",
932 name_str, type_str);
933 }
934 discard_adds_tree(update);
935 return ret;
936 } else {
937 log_zone_info(update->zone->name, "DNSSEC, %svalidation successful", msg_valid);
938 }
939 }
940
941 ret = update_catalog(conf, update);
942 if (ret != KNOT_EOK) {
943 log_zone_error(update->zone->name, "failed to process catalog zone (%s)", knot_strerror(ret));
944 discard_adds_tree(update);
945 return ret;
946 }
947
948 ret = commit_journal(conf, update);
949 if (ret != KNOT_EOK) {
950 discard_adds_tree(update);
951 return ret;
952 }
953
954 if (dnssec && zone_is_slave(conf, update->zone)) {
955 ret = zone_set_lastsigned_serial(update->zone,
956 zone_contents_serial(update->new_cont));
957 if (ret != KNOT_EOK) {
958 log_zone_warning(update->zone->name,
959 "unable to save lastsigned serial, "
960 "future transfers might be broken");
961 }
962 }
963
964 /* Switch zone contents. */
965 zone_contents_t *old_contents;
966 old_contents = zone_switch_contents(update->zone, update->new_cont);
967
968 if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
969 changeset_clear(&update->change);
970 changeset_clear(&update->extra_ch);
971 }
972 zone_contents_deep_free(update->init_cont);
973
974 update_clear_ctx_t *clear_ctx = calloc(1, sizeof(*clear_ctx));
975 if (clear_ctx != NULL) {
976 clear_ctx->free_contents = old_contents;
977 clear_ctx->free_method = (
978 (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) ?
979 zone_contents_deep_free : update_free_zone
980 );
981 clear_ctx->cleanup_apply = update->a_ctx;
982 clear_ctx->new_cont_size = update->new_cont->size;
983
984 call_rcu((struct rcu_head *)clear_ctx, update_clear);
985 } else {
986 log_zone_error(update->zone->name, "failed to deallocate unused memory");
987 }
988
989 /* Sync zonefile immediately if configured. */
990 val = conf_zone_get(conf, C_ZONEFILE_SYNC, update->zone->name);
991 if (conf_int(&val) == 0) {
992 zone_events_schedule_now(update->zone, ZONE_EVENT_FLUSH);
993 }
994
995 memset(update, 0, sizeof(*update));
996
997 return KNOT_EOK;
998 }
999
zone_update_no_change(zone_update_t * update)1000 bool zone_update_no_change(zone_update_t *update)
1001 {
1002 if (update == NULL) {
1003 return true;
1004 }
1005
1006 if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
1007 return changeset_empty(&update->change);
1008 } else {
1009 /* This branch does not make much sense and FULL update will most likely
1010 * be a change every time anyway, just return false. */
1011 return false;
1012 }
1013 }
1014