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