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