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