]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-mdns.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[thirdparty/systemd.git] / src / resolve / resolved-mdns.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2015 Daniel Mack
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
21 #include <resolv.h>
22 #include <netinet/in.h>
23 #include <arpa/inet.h>
24
25 #include "alloc-util.h"
26 #include "fd-util.h"
27 #include "resolved-manager.h"
28 #include "resolved-mdns.h"
29
30 #define CLEAR_CACHE_FLUSH(x) (~MDNS_RR_CACHE_FLUSH & (x))
31
32 void manager_mdns_stop(Manager *m) {
33 assert(m);
34
35 m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source);
36 m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd);
37
38 m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source);
39 m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd);
40 }
41
42 int manager_mdns_start(Manager *m) {
43 int r;
44
45 assert(m);
46
47 if (m->mdns_support == RESOLVE_SUPPORT_NO)
48 return 0;
49
50 r = manager_mdns_ipv4_fd(m);
51 if (r == -EADDRINUSE)
52 goto eaddrinuse;
53 if (r < 0)
54 return r;
55
56 if (socket_ipv6_is_supported()) {
57 r = manager_mdns_ipv6_fd(m);
58 if (r == -EADDRINUSE)
59 goto eaddrinuse;
60 if (r < 0)
61 return r;
62 }
63
64 return 0;
65
66 eaddrinuse:
67 log_warning("Another mDNS responder prohibits binding the socket to the same port. Turning off mDNS support.");
68 m->mdns_support = RESOLVE_SUPPORT_NO;
69 manager_mdns_stop(m);
70
71 return 0;
72 }
73
74 static int mdns_rr_compare(const void *a, const void *b) {
75 DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
76 size_t m;
77 int r;
78
79 assert(x);
80 assert(*x);
81 assert(y);
82 assert(*y);
83
84 if (CLEAR_CACHE_FLUSH((*x)->key->class) < CLEAR_CACHE_FLUSH((*y)->key->class))
85 return -1;
86 else if (CLEAR_CACHE_FLUSH((*x)->key->class) > CLEAR_CACHE_FLUSH((*y)->key->class))
87 return 1;
88
89 if ((*x)->key->type < (*y)->key->type)
90 return -1;
91 else if ((*x)->key->type > (*y)->key->type)
92 return 1;
93
94 r = dns_resource_record_to_wire_format(*x, false);
95 if (r < 0) {
96 log_warning_errno(r, "Can't wire-format RR: %m");
97 return 0;
98 }
99
100 r = dns_resource_record_to_wire_format(*y, false);
101 if (r < 0) {
102 log_warning_errno(r, "Can't wire-format RR: %m");
103 return 0;
104 }
105
106 m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
107
108 r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
109 if (r != 0)
110 return r;
111
112 if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
113 return -1;
114 else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
115 return 1;
116
117 return 0;
118 }
119
120 static int proposed_rrs_cmp(DnsResourceRecord **x, unsigned x_size, DnsResourceRecord **y, unsigned y_size) {
121 unsigned m;
122 int r;
123
124 m = MIN(x_size, y_size);
125 for (unsigned i = 0; i < m; i++) {
126 r = mdns_rr_compare(&x[i], &y[i]);
127 if (r != 0)
128 return r;
129 }
130
131 if (x_size < y_size)
132 return -1;
133 if (x_size > y_size)
134 return 1;
135
136 return 0;
137 }
138
139 static int mdns_packet_extract_matching_rrs(DnsPacket *p, DnsResourceKey *key, DnsResourceRecord ***ret_rrs) {
140 _cleanup_free_ DnsResourceRecord **list = NULL;
141 unsigned n = 0, size = 0;
142 int r;
143
144 assert(p);
145 assert(key);
146 assert(ret_rrs);
147 assert_return(DNS_PACKET_NSCOUNT(p) > 0, -EINVAL);
148
149 for (unsigned i = DNS_PACKET_ANCOUNT(p); i < (DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)); i++) {
150 r = dns_resource_key_match_rr(key, p->answer->items[i].rr, NULL);
151 if (r < 0)
152 return r;
153 if (r > 0)
154 size++;
155 }
156
157 if (size == 0)
158 return 0;
159
160 list = new(DnsResourceRecord *, size);
161 if (!list)
162 return -ENOMEM;
163
164 for (unsigned i = DNS_PACKET_ANCOUNT(p); i < (DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)); i++) {
165 r = dns_resource_key_match_rr(key, p->answer->items[i].rr, NULL);
166 if (r < 0)
167 return r;
168 if (r > 0)
169 list[n++] = p->answer->items[i].rr;
170 }
171 assert(n == size);
172 qsort_safe(list, size, sizeof(DnsResourceRecord*), mdns_rr_compare);
173
174 *ret_rrs = TAKE_PTR(list);
175
176 return size;
177 }
178
179 static int mdns_do_tiebreak(DnsResourceKey *key, DnsAnswer *answer, DnsPacket *p) {
180 _cleanup_free_ DnsResourceRecord **our = NULL, **remote = NULL;
181 DnsResourceRecord *rr;
182 unsigned i = 0;
183 unsigned size;
184 int r;
185
186 size = dns_answer_size(answer);
187 our = new(DnsResourceRecord *, size);
188 if (!our)
189 return -ENOMEM;
190
191 DNS_ANSWER_FOREACH(rr, answer)
192 our[i++] = rr;
193 qsort_safe(our, size, sizeof(DnsResourceRecord*), mdns_rr_compare);
194
195 r = mdns_packet_extract_matching_rrs(p, key, &remote);
196 if (r < 0)
197 return r;
198
199 assert(r > 0);
200
201 if (proposed_rrs_cmp(remote, r, our, size) > 0)
202 return 1;
203
204 return 0;
205 }
206
207 static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
208 _cleanup_(dns_answer_unrefp) DnsAnswer *full_answer = NULL;
209 _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
210 DnsResourceKey *key = NULL;
211 DnsResourceRecord *rr;
212 bool tentative = false;
213 int r;
214
215 assert(s);
216 assert(p);
217
218 r = dns_packet_extract(p);
219 if (r < 0)
220 return log_debug_errno(r, "Failed to extract resource records from incoming packet: %m");
221
222 assert_return((dns_question_size(p->question) > 0), -EINVAL);
223
224 DNS_QUESTION_FOREACH(key, p->question) {
225 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
226
227 r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative);
228 if (r < 0)
229 return log_debug_errno(r, "Failed to lookup key: %m");
230
231 if (tentative && DNS_PACKET_NSCOUNT(p) > 0) {
232 /*
233 * A race condition detected with the probe packet from
234 * a remote host.
235 * Do simultaneous probe tiebreaking as described in
236 * RFC 6762, Section 8.2. In case we lost don't reply
237 * the question and withdraw conflicting RRs.
238 */
239 r = mdns_do_tiebreak(key, answer, p);
240 if (r < 0)
241 return log_debug_errno(r, "Failed to do tiebreaking");
242
243 if (r > 0) { /* we lost */
244 DNS_ANSWER_FOREACH(rr, answer) {
245 DnsZoneItem *i;
246
247 i = dns_zone_get(&s->zone, rr);
248 if (i)
249 dns_zone_item_conflict(i);
250 }
251
252 continue;
253 }
254 }
255
256 r = dns_answer_extend(&full_answer, answer);
257 if (r < 0)
258 return log_debug_errno(r, "Failed to extend answer: %m");
259 }
260
261 if (dns_answer_isempty(full_answer))
262 return 0;
263
264 r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, NULL, full_answer, NULL, false, &reply);
265 if (r < 0)
266 return log_debug_errno(r, "Failed to build reply packet: %m");
267
268 if (!ratelimit_test(&s->ratelimit))
269 return 0;
270
271 r = dns_scope_emit_udp(s, -1, reply);
272 if (r < 0)
273 return log_debug_errno(r, "Failed to send reply packet: %m");
274
275 return 0;
276 }
277
278 static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
279 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
280 Manager *m = userdata;
281 DnsScope *scope;
282 int r;
283
284 r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p);
285 if (r <= 0)
286 return r;
287
288 if (manager_our_packet(m, p))
289 return 0;
290
291 scope = manager_find_scope(m, p);
292 if (!scope) {
293 log_debug("Got mDNS UDP packet on unknown scope. Ignoring.");
294 return 0;
295 }
296
297 if (dns_packet_validate_reply(p) > 0) {
298 DnsResourceRecord *rr;
299
300 log_debug("Got mDNS reply packet");
301
302 /*
303 * mDNS is different from regular DNS and LLMNR with regard to handling responses.
304 * While on other protocols, we can ignore every answer that doesn't match a question
305 * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all
306 * incoming information, regardless of the DNS packet ID.
307 *
308 * Hence, extract the packet here, and try to find a transaction for answer the we got
309 * and complete it. Also store the new information in scope's cache.
310 */
311 r = dns_packet_extract(p);
312 if (r < 0) {
313 log_debug("mDNS packet extraction failed.");
314 return 0;
315 }
316
317 dns_scope_check_conflicts(scope, p);
318
319 DNS_ANSWER_FOREACH(rr, p->answer) {
320 const char *name = dns_resource_key_name(rr->key);
321 DnsTransaction *t;
322
323 /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa,
324 * we assume someone's playing tricks on us and discard the packet completely. */
325 if (!(dns_name_endswith(name, "in-addr.arpa") > 0 ||
326 dns_name_endswith(name, "local") > 0))
327 return 0;
328
329 if (rr->ttl == 0) {
330 log_debug("Got a goodbye packet");
331 /* See the section 10.1 of RFC6762 */
332 rr->ttl = 1;
333 }
334
335 t = dns_scope_find_transaction(scope, rr->key, false);
336 if (t)
337 dns_transaction_process_reply(t, p);
338
339 /* Also look for the various types of ANY transactions */
340 t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false);
341 if (t)
342 dns_transaction_process_reply(t, p);
343
344 t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), false);
345 if (t)
346 dns_transaction_process_reply(t, p);
347
348 t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false);
349 if (t)
350 dns_transaction_process_reply(t, p);
351 }
352
353 dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender);
354
355 } else if (dns_packet_validate_query(p) > 0) {
356 log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));
357
358 r = mdns_scope_process_query(scope, p);
359 if (r < 0) {
360 log_debug_errno(r, "mDNS query processing failed: %m");
361 return 0;
362 }
363 } else
364 log_debug("Invalid mDNS UDP packet.");
365
366 return 0;
367 }
368
369 int manager_mdns_ipv4_fd(Manager *m) {
370 union sockaddr_union sa = {
371 .in.sin_family = AF_INET,
372 .in.sin_port = htobe16(MDNS_PORT),
373 };
374 static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255;
375 int r;
376
377 assert(m);
378
379 if (m->mdns_ipv4_fd >= 0)
380 return m->mdns_ipv4_fd;
381
382 m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
383 if (m->mdns_ipv4_fd < 0)
384 return log_error_errno(errno, "mDNS-IPv4: Failed to create socket: %m");
385
386 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
387 if (r < 0) {
388 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_TTL: %m");
389 goto fail;
390 }
391
392 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
393 if (r < 0) {
394 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MULTICAST_TTL: %m");
395 goto fail;
396 }
397
398 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
399 if (r < 0) {
400 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MULTICAST_LOOP: %m");
401 goto fail;
402 }
403
404 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
405 if (r < 0) {
406 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_PKTINFO: %m");
407 goto fail;
408 }
409
410 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one));
411 if (r < 0) {
412 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_RECVTTL: %m");
413 goto fail;
414 }
415
416 /* Disable Don't-Fragment bit in the IP header */
417 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu));
418 if (r < 0) {
419 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MTU_DISCOVER: %m");
420 goto fail;
421 }
422
423 /* See the section 15.1 of RFC6762 */
424 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
425 r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in));
426 if (r < 0) {
427 if (errno != EADDRINUSE) {
428 r = log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m");
429 goto fail;
430 }
431
432 log_warning("mDNS-IPv4: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers.");
433
434 /* try again with SO_REUSEADDR */
435 r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
436 if (r < 0) {
437 r = log_error_errno(errno, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m");
438 goto fail;
439 }
440
441 r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in));
442 if (r < 0) {
443 r = log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m");
444 goto fail;
445 }
446 } else {
447 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
448 r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
449 if (r < 0) {
450 r = log_error_errno(errno, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m");
451 goto fail;
452 }
453 }
454
455 r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m);
456 if (r < 0)
457 goto fail;
458
459 return m->mdns_ipv4_fd;
460
461 fail:
462 m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd);
463 return r;
464 }
465
466 int manager_mdns_ipv6_fd(Manager *m) {
467 union sockaddr_union sa = {
468 .in6.sin6_family = AF_INET6,
469 .in6.sin6_port = htobe16(MDNS_PORT),
470 };
471 static const int one = 1, ttl = 255;
472 int r;
473
474 assert(m);
475
476 if (m->mdns_ipv6_fd >= 0)
477 return m->mdns_ipv6_fd;
478
479 m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
480 if (m->mdns_ipv6_fd < 0)
481 return log_error_errno(errno, "mDNS-IPv6: Failed to create socket: %m");
482
483 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
484 if (r < 0) {
485 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_UNICAST_HOPS: %m");
486 goto fail;
487 }
488
489 /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
490 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl));
491 if (r < 0) {
492 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_MULTICAST_HOPS: %m");
493 goto fail;
494 }
495
496 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one));
497 if (r < 0) {
498 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_MULTICAST_LOOP: %m");
499 goto fail;
500 }
501
502 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
503 if (r < 0) {
504 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_V6ONLY: %m");
505 goto fail;
506 }
507
508 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
509 if (r < 0) {
510 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_RECVPKTINFO: %m");
511 goto fail;
512 }
513
514 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one));
515 if (r < 0) {
516 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_RECVHOPLIMIT: %m");
517 goto fail;
518 }
519
520 /* See the section 15.1 of RFC6762 */
521 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
522 r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6));
523 if (r < 0) {
524 if (errno != EADDRINUSE) {
525 r = log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m");
526 goto fail;
527 }
528
529 log_warning("mDNS-IPv6: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers.");
530
531 /* try again with SO_REUSEADDR */
532 r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
533 if (r < 0) {
534 r = log_error_errno(errno, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m");
535 goto fail;
536 }
537
538 r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6));
539 if (r < 0) {
540 r = log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m");
541 goto fail;
542 }
543 } else {
544 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
545 r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
546 if (r < 0) {
547 r = log_error_errno(errno, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m");
548 goto fail;
549 }
550 }
551
552 r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m);
553 if (r < 0)
554 goto fail;
555
556 return m->mdns_ipv6_fd;
557
558 fail:
559 m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd);
560 return r;
561 }