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