]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-zone.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
623a4c97
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
b5efdb8a 21#include "alloc-util.h"
4ad7f276 22#include "dns-domain.h"
07630cea 23#include "list.h"
623a4c97 24#include "resolved-dns-packet.h"
07630cea
LP
25#include "resolved-dns-zone.h"
26#include "string-util.h"
623a4c97
LP
27
28/* Never allow more than 1K entries */
29#define ZONE_MAX 1024
30
3ef64445 31void dns_zone_item_probe_stop(DnsZoneItem *i) {
ec2c5e43
LP
32 DnsTransaction *t;
33 assert(i);
623a4c97 34
ec2c5e43
LP
35 if (!i->probe_transaction)
36 return;
37
38 t = i->probe_transaction;
39 i->probe_transaction = NULL;
40
547973de 41 set_remove(t->notify_zone_items, i);
35aa04e9 42 set_remove(t->notify_zone_items_done, i);
ec2c5e43
LP
43 dns_transaction_gc(t);
44}
623a4c97
LP
45
46static void dns_zone_item_free(DnsZoneItem *i) {
47 if (!i)
48 return;
49
ec2c5e43 50 dns_zone_item_probe_stop(i);
623a4c97 51 dns_resource_record_unref(i->rr);
ec2c5e43 52
623a4c97
LP
53 free(i);
54}
55
56DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
57
58static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
59 DnsZoneItem *first;
60
61 assert(z);
62
63 if (!i)
64 return;
65
66 first = hashmap_get(z->by_key, i->rr->key);
67 LIST_REMOVE(by_key, first, i);
68 if (first)
69 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
70 else
71 hashmap_remove(z->by_key, i->rr->key);
72
1c02e7ba 73 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
623a4c97
LP
74 LIST_REMOVE(by_name, first, i);
75 if (first)
1c02e7ba 76 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
623a4c97 77 else
1c02e7ba 78 hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key));
623a4c97
LP
79
80 dns_zone_item_free(i);
81}
82
83void dns_zone_flush(DnsZone *z) {
84 DnsZoneItem *i;
85
86 assert(z);
87
88 while ((i = hashmap_first(z->by_key)))
89 dns_zone_item_remove_and_free(z, i);
90
91 assert(hashmap_size(z->by_key) == 0);
92 assert(hashmap_size(z->by_name) == 0);
93
525d3cc7
LP
94 z->by_key = hashmap_free(z->by_key);
95 z->by_name = hashmap_free(z->by_name);
623a4c97
LP
96}
97
98static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
99 DnsZoneItem *i;
100
101 assert(z);
102 assert(rr);
103
104 LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
3ef77d04 105 if (dns_resource_record_equal(i->rr, rr) > 0)
623a4c97
LP
106 return i;
107
108 return NULL;
109}
110
111void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
112 DnsZoneItem *i;
113
114 assert(z);
115 assert(rr);
116
117 i = dns_zone_get(z, rr);
118 if (i)
119 dns_zone_item_remove_and_free(z, i);
120}
121
122static int dns_zone_init(DnsZone *z) {
123 int r;
124
125 assert(z);
126
d5099efc 127 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
623a4c97
LP
128 if (r < 0)
129 return r;
130
d5099efc 131 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
623a4c97
LP
132 if (r < 0)
133 return r;
134
135 return 0;
136}
137
138static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
139 DnsZoneItem *first;
140 int r;
141
142 first = hashmap_get(z->by_key, i->rr->key);
143 if (first) {
144 LIST_PREPEND(by_key, first, i);
145 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
146 } else {
147 r = hashmap_put(z->by_key, i->rr->key, i);
148 if (r < 0)
149 return r;
150 }
151
1c02e7ba 152 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
623a4c97
LP
153 if (first) {
154 LIST_PREPEND(by_name, first, i);
1c02e7ba 155 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
623a4c97 156 } else {
1c02e7ba 157 r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
623a4c97
LP
158 if (r < 0)
159 return r;
160 }
161
162 return 0;
163}
164
ec2c5e43 165static int dns_zone_item_probe_start(DnsZoneItem *i) {
ec2c5e43
LP
166 DnsTransaction *t;
167 int r;
168
169 assert(i);
170
171 if (i->probe_transaction)
172 return 0;
173
1c02e7ba 174 t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false);
ec2c5e43 175 if (!t) {
1b4f6e79
LP
176 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
177
1c02e7ba 178 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
1b4f6e79
LP
179 if (!key)
180 return -ENOMEM;
181
f52e61da 182 r = dns_transaction_new(&t, i->scope, key);
ec2c5e43
LP
183 if (r < 0)
184 return r;
185 }
186
547973de 187 r = set_ensure_allocated(&t->notify_zone_items, NULL);
ec2c5e43
LP
188 if (r < 0)
189 goto gc;
190
35aa04e9
LP
191 r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
192 if (r < 0)
193 goto gc;
194
547973de 195 r = set_put(t->notify_zone_items, i);
ec2c5e43
LP
196 if (r < 0)
197 goto gc;
198
199 i->probe_transaction = t;
53fda2bb 200 t->probing = true;
ec2c5e43
LP
201
202 if (t->state == DNS_TRANSACTION_NULL) {
203
204 i->block_ready++;
205 r = dns_transaction_go(t);
206 i->block_ready--;
207
208 if (r < 0) {
209 dns_zone_item_probe_stop(i);
210 return r;
211 }
212 }
213
547973de 214 dns_zone_item_notify(i);
ec2c5e43
LP
215 return 0;
216
217gc:
218 dns_transaction_gc(t);
219 return r;
220}
221
222int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
623a4c97
LP
223 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
224 DnsZoneItem *existing;
225 int r;
226
227 assert(z);
ec2c5e43 228 assert(s);
623a4c97
LP
229 assert(rr);
230
222148b6 231 if (dns_class_is_pseudo(rr->key->class))
1d3b690f 232 return -EINVAL;
222148b6 233 if (dns_type_is_pseudo(rr->key->type))
1d3b690f
LP
234 return -EINVAL;
235
623a4c97
LP
236 existing = dns_zone_get(z, rr);
237 if (existing)
238 return 0;
239
240 r = dns_zone_init(z);
241 if (r < 0)
242 return r;
243
244 i = new0(DnsZoneItem, 1);
245 if (!i)
246 return -ENOMEM;
247
ec2c5e43 248 i->scope = s;
623a4c97 249 i->rr = dns_resource_record_ref(rr);
ec2c5e43 250 i->probing_enabled = probe;
623a4c97
LP
251
252 r = dns_zone_link_item(z, i);
253 if (r < 0)
254 return r;
255
ec2c5e43 256 if (probe) {
cd1b20f9
LP
257 DnsZoneItem *first, *j;
258 bool established = false;
259
260 /* Check if there's already an RR with the same name
261 * established. If so, it has been probed already, and
262 * we don't ned to probe again. */
263
264 LIST_FIND_HEAD(by_name, i, first);
265 LIST_FOREACH(by_name, j, first) {
266 if (i == j)
267 continue;
268
269 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
270 established = true;
ec2c5e43
LP
271 }
272
cd1b20f9
LP
273 if (established)
274 i->state = DNS_ZONE_ITEM_ESTABLISHED;
275 else {
60eb3f7c
LP
276 i->state = DNS_ZONE_ITEM_PROBING;
277
cd1b20f9
LP
278 r = dns_zone_item_probe_start(i);
279 if (r < 0) {
280 dns_zone_item_remove_and_free(z, i);
281 i = NULL;
282 return r;
283 }
cd1b20f9 284 }
ec2c5e43
LP
285 } else
286 i->state = DNS_ZONE_ITEM_ESTABLISHED;
287
623a4c97
LP
288 i = NULL;
289 return 0;
290}
291
97ebebbc 292int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
8bf52d3d 293 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
5032b16d
LP
294 unsigned n_answer = 0;
295 DnsZoneItem *j, *first;
296 bool tentative = true, need_soa = false;
d5323661 297 int r;
623a4c97 298
97ebebbc
LP
299 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
300 * ifindex field in the answer with it */
301
623a4c97 302 assert(z);
5032b16d 303 assert(key);
8bf52d3d 304 assert(ret_answer);
623a4c97 305
5032b16d 306 /* First iteration, count what we have */
ec2c5e43 307
5032b16d
LP
308 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
309 bool found = false, added = false;
310 int k;
623a4c97 311
5032b16d
LP
312 /* If this is a generic match, then we have to
313 * go through the list by the name and look
314 * for everything manually */
623a4c97 315
1c02e7ba 316 first = hashmap_get(z->by_name, dns_resource_key_name(key));
5032b16d
LP
317 LIST_FOREACH(by_name, j, first) {
318 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
319 continue;
d5323661 320
5032b16d 321 found = true;
d5323661 322
801ad6a6 323 k = dns_resource_key_match_rr(key, j->rr, NULL);
5032b16d
LP
324 if (k < 0)
325 return k;
326 if (k > 0) {
327 n_answer++;
328 added = true;
329 }
ec2c5e43 330
5032b16d 331 }
ec2c5e43 332
5032b16d
LP
333 if (found && !added)
334 need_soa = true;
ec2c5e43 335
5032b16d
LP
336 } else {
337 bool found = false;
d5323661 338
5032b16d
LP
339 /* If this is a specific match, then look for
340 * the right key immediately */
ec2c5e43 341
5032b16d
LP
342 first = hashmap_get(z->by_key, key);
343 LIST_FOREACH(by_key, j, first) {
344 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
345 continue;
ec2c5e43 346
5032b16d
LP
347 found = true;
348 n_answer++;
349 }
ec2c5e43 350
5032b16d 351 if (!found) {
1c02e7ba 352 first = hashmap_get(z->by_name, dns_resource_key_name(key));
5032b16d 353 LIST_FOREACH(by_name, j, first) {
ec2c5e43
LP
354 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
355 continue;
356
5032b16d
LP
357 need_soa = true;
358 break;
d5323661 359 }
d5323661 360 }
623a4c97
LP
361 }
362
5032b16d
LP
363 if (n_answer <= 0 && !need_soa)
364 goto return_empty;
623a4c97 365
8bf52d3d
LP
366 if (n_answer > 0) {
367 answer = dns_answer_new(n_answer);
368 if (!answer)
369 return -ENOMEM;
370 }
623a4c97 371
5032b16d
LP
372 if (need_soa) {
373 soa = dns_answer_new(1);
8bf52d3d
LP
374 if (!soa)
375 return -ENOMEM;
376 }
377
378 /* Second iteration, actually add the RRs to the answers */
5032b16d
LP
379 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
380 bool found = false, added = false;
381 int k;
d5323661 382
1c02e7ba 383 first = hashmap_get(z->by_name, dns_resource_key_name(key));
5032b16d
LP
384 LIST_FOREACH(by_name, j, first) {
385 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
386 continue;
ec2c5e43 387
5032b16d 388 found = true;
ec2c5e43 389
5032b16d
LP
390 if (j->state != DNS_ZONE_ITEM_PROBING)
391 tentative = false;
ec2c5e43 392
801ad6a6 393 k = dns_resource_key_match_rr(key, j->rr, NULL);
5032b16d
LP
394 if (k < 0)
395 return k;
396 if (k > 0) {
97ebebbc 397 r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
d5323661
LP
398 if (r < 0)
399 return r;
5032b16d
LP
400
401 added = true;
d5323661 402 }
5032b16d 403 }
d5323661 404
5032b16d 405 if (found && !added) {
97ebebbc 406 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
5032b16d
LP
407 if (r < 0)
408 return r;
409 }
410 } else {
411 bool found = false;
ec2c5e43 412
5032b16d
LP
413 first = hashmap_get(z->by_key, key);
414 LIST_FOREACH(by_key, j, first) {
415 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
416 continue;
ec2c5e43 417
5032b16d 418 found = true;
ec2c5e43 419
5032b16d
LP
420 if (j->state != DNS_ZONE_ITEM_PROBING)
421 tentative = false;
ec2c5e43 422
97ebebbc 423 r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
5032b16d
LP
424 if (r < 0)
425 return r;
426 }
ec2c5e43 427
5032b16d
LP
428 if (!found) {
429 bool add_soa = false;
ec2c5e43 430
1c02e7ba 431 first = hashmap_get(z->by_name, dns_resource_key_name(key));
5032b16d
LP
432 LIST_FOREACH(by_name, j, first) {
433 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
434 continue;
ec2c5e43 435
5032b16d
LP
436 if (j->state != DNS_ZONE_ITEM_PROBING)
437 tentative = false;
ec2c5e43 438
5032b16d
LP
439 add_soa = true;
440 }
441
442 if (add_soa) {
97ebebbc 443 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
5032b16d
LP
444 if (r < 0)
445 return r;
d5323661 446 }
623a4c97
LP
447 }
448 }
449
5032b16d
LP
450 /* If the caller sets ret_tentative to NULL, then use this as
451 * indication to not return tentative entries */
452
453 if (!ret_tentative && tentative)
454 goto return_empty;
455
8bf52d3d 456 *ret_answer = answer;
623a4c97
LP
457 answer = NULL;
458
5032b16d
LP
459 if (ret_soa) {
460 *ret_soa = soa;
461 soa = NULL;
462 }
8bf52d3d 463
ec2c5e43
LP
464 if (ret_tentative)
465 *ret_tentative = tentative;
466
623a4c97 467 return 1;
5032b16d
LP
468
469return_empty:
470 *ret_answer = NULL;
471
472 if (ret_soa)
473 *ret_soa = NULL;
474
475 if (ret_tentative)
476 *ret_tentative = false;
477
478 return 0;
623a4c97 479}
ec2c5e43
LP
480
481void dns_zone_item_conflict(DnsZoneItem *i) {
ec2c5e43
LP
482 assert(i);
483
a4076574
LP
484 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
485 return;
486
7b50eb2e 487 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
ec2c5e43 488
d84b686f
LP
489 dns_zone_item_probe_stop(i);
490
ec2c5e43
LP
491 /* Withdraw the conflict item */
492 i->state = DNS_ZONE_ITEM_WITHDRAWN;
493
494 /* Maybe change the hostname */
1c02e7ba 495 if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)
ec2c5e43
LP
496 manager_next_hostname(i->scope->manager);
497}
498
547973de 499void dns_zone_item_notify(DnsZoneItem *i) {
ec2c5e43
LP
500 assert(i);
501 assert(i->probe_transaction);
502
503 if (i->block_ready > 0)
504 return;
505
547973de 506 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
ec2c5e43
LP
507 return;
508
a4076574
LP
509 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
510 bool we_lost = false;
ec2c5e43 511
a4076574
LP
512 /* The probe got a successful reply. If we so far
513 * weren't established we just give up. If we already
514 * were established, and the peer has the
4d91eec4 515 * lexicographically larger IP address we continue
a4076574
LP
516 * and defend it. */
517
2fb3034c
LP
518 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
519 log_debug("Got a successful probe for not yet established RR, we lost.");
a4076574 520 we_lost = true;
2fb3034c 521 } else {
a4076574 522 assert(i->probe_transaction->received);
4d91eec4 523 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
2fb3034c 524 if (we_lost)
4d91eec4 525 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
a4076574
LP
526 }
527
528 if (we_lost) {
529 dns_zone_item_conflict(i);
530 return;
531 }
532
533 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
534 }
535
7b50eb2e 536 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
ec2c5e43 537
a4076574
LP
538 dns_zone_item_probe_stop(i);
539 i->state = DNS_ZONE_ITEM_ESTABLISHED;
540}
541
542static int dns_zone_item_verify(DnsZoneItem *i) {
543 int r;
544
545 assert(i);
546
547 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
548 return 0;
549
7b50eb2e 550 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
2fb3034c 551
a4076574
LP
552 i->state = DNS_ZONE_ITEM_VERIFYING;
553 r = dns_zone_item_probe_start(i);
554 if (r < 0) {
da927ba9 555 log_error_errno(r, "Failed to start probing for verifying RR: %m");
ec2c5e43 556 i->state = DNS_ZONE_ITEM_ESTABLISHED;
a4076574
LP
557 return r;
558 }
559
560 return 0;
561}
562
563int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
564 DnsZoneItem *i, *first;
bf1594f5 565 int c = 0;
a4076574
LP
566
567 assert(zone);
568 assert(rr);
569
570 /* This checks whether a response RR we received from somebody
571 * else is one that we actually thought was uniquely ours. If
572 * so, we'll verify our RRs. */
573
574 /* No conflict if we don't have the name at all. */
1c02e7ba 575 first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key));
a4076574
LP
576 if (!first)
577 return 0;
578
579 /* No conflict if we have the exact same RR */
580 if (dns_zone_get(zone, rr))
581 return 0;
582
583 /* OK, somebody else has RRs for the same name. Yuck! Let's
584 * start probing again */
585
586 LIST_FOREACH(by_name, i, first) {
587 if (dns_resource_record_equal(i->rr, rr))
588 continue;
589
590 dns_zone_item_verify(i);
591 c++;
592 }
593
594 return c;
595}
596
597int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
598 DnsZoneItem *i, *first;
bf1594f5 599 int c = 0;
a4076574
LP
600
601 assert(zone);
602
603 /* Somebody else notified us about a possible conflict. Let's
604 * verify if that's true. */
605
1c02e7ba 606 first = hashmap_get(zone->by_name, dns_resource_key_name(key));
a4076574
LP
607 if (!first)
608 return 0;
609
610 LIST_FOREACH(by_name, i, first) {
611 dns_zone_item_verify(i);
612 c++;
613 }
614
615 return c;
ec2c5e43 616}
902bb5d8
LP
617
618void dns_zone_verify_all(DnsZone *zone) {
619 DnsZoneItem *i;
620 Iterator iterator;
621
622 assert(zone);
623
624 HASHMAP_FOREACH(i, zone->by_key, iterator) {
625 DnsZoneItem *j;
626
627 LIST_FOREACH(by_key, j, i)
628 dns_zone_item_verify(j);
629 }
630}
4d506d6b
LP
631
632void dns_zone_dump(DnsZone *zone, FILE *f) {
633 Iterator iterator;
634 DnsZoneItem *i;
4d506d6b
LP
635
636 if (!zone)
637 return;
638
639 if (!f)
640 f = stdout;
641
642 HASHMAP_FOREACH(i, zone->by_key, iterator) {
643 DnsZoneItem *j;
644
645 LIST_FOREACH(by_key, j, i) {
7b50eb2e 646 const char *t;
4d506d6b 647
7b50eb2e
LP
648 t = dns_resource_record_to_string(j->rr);
649 if (!t) {
4d506d6b
LP
650 log_oom();
651 continue;
652 }
653
654 fputc('\t', f);
655 fputs(t, f);
656 fputc('\n', f);
657 }
658 }
659}
660
661bool dns_zone_is_empty(DnsZone *zone) {
662 if (!zone)
663 return true;
664
665 return hashmap_isempty(zone->by_key);
666}