]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-mdns.c
Merge pull request #8840 from poettering/unsigned-size_t
[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 (size_t 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 (size_t 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 size_t i = 0, size;
170 int r;
171
172 size = dns_answer_size(answer);
173 our = new(DnsResourceRecord *, size);
174 if (!our)
175 return -ENOMEM;
176
177 DNS_ANSWER_FOREACH(rr, answer)
178 our[i++] = rr;
179 qsort_safe(our, size, sizeof(DnsResourceRecord*), mdns_rr_compare);
180
181 r = mdns_packet_extract_matching_rrs(p, key, &remote);
182 if (r < 0)
183 return r;
184
185 assert(r > 0);
186
187 if (proposed_rrs_cmp(remote, r, our, size) > 0)
188 return 1;
189
190 return 0;
191 }
192
193 static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
194 _cleanup_(dns_answer_unrefp) DnsAnswer *full_answer = NULL;
195 _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
196 DnsResourceKey *key = NULL;
197 DnsResourceRecord *rr;
198 bool tentative = false;
199 int r;
200
201 assert(s);
202 assert(p);
203
204 r = dns_packet_extract(p);
205 if (r < 0)
206 return log_debug_errno(r, "Failed to extract resource records from incoming packet: %m");
207
208 assert_return((dns_question_size(p->question) > 0), -EINVAL);
209
210 DNS_QUESTION_FOREACH(key, p->question) {
211 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
212
213 r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative);
214 if (r < 0)
215 return log_debug_errno(r, "Failed to lookup key: %m");
216
217 if (tentative && DNS_PACKET_NSCOUNT(p) > 0) {
218 /*
219 * A race condition detected with the probe packet from
220 * a remote host.
221 * Do simultaneous probe tiebreaking as described in
222 * RFC 6762, Section 8.2. In case we lost don't reply
223 * the question and withdraw conflicting RRs.
224 */
225 r = mdns_do_tiebreak(key, answer, p);
226 if (r < 0)
227 return log_debug_errno(r, "Failed to do tiebreaking");
228
229 if (r > 0) { /* we lost */
230 DNS_ANSWER_FOREACH(rr, answer) {
231 DnsZoneItem *i;
232
233 i = dns_zone_get(&s->zone, rr);
234 if (i)
235 dns_zone_item_conflict(i);
236 }
237
238 continue;
239 }
240 }
241
242 r = dns_answer_extend(&full_answer, answer);
243 if (r < 0)
244 return log_debug_errno(r, "Failed to extend answer: %m");
245 }
246
247 if (dns_answer_isempty(full_answer))
248 return 0;
249
250 r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, NULL, full_answer, NULL, false, &reply);
251 if (r < 0)
252 return log_debug_errno(r, "Failed to build reply packet: %m");
253
254 if (!ratelimit_test(&s->ratelimit))
255 return 0;
256
257 r = dns_scope_emit_udp(s, -1, reply);
258 if (r < 0)
259 return log_debug_errno(r, "Failed to send reply packet: %m");
260
261 return 0;
262 }
263
264 static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
265 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
266 Manager *m = userdata;
267 DnsScope *scope;
268 int r;
269
270 r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p);
271 if (r <= 0)
272 return r;
273
274 if (manager_our_packet(m, p))
275 return 0;
276
277 scope = manager_find_scope(m, p);
278 if (!scope) {
279 log_debug("Got mDNS UDP packet on unknown scope. Ignoring.");
280 return 0;
281 }
282
283 if (dns_packet_validate_reply(p) > 0) {
284 DnsResourceRecord *rr;
285
286 log_debug("Got mDNS reply packet");
287
288 /*
289 * mDNS is different from regular DNS and LLMNR with regard to handling responses.
290 * While on other protocols, we can ignore every answer that doesn't match a question
291 * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all
292 * incoming information, regardless of the DNS packet ID.
293 *
294 * Hence, extract the packet here, and try to find a transaction for answer the we got
295 * and complete it. Also store the new information in scope's cache.
296 */
297 r = dns_packet_extract(p);
298 if (r < 0) {
299 log_debug("mDNS packet extraction failed.");
300 return 0;
301 }
302
303 dns_scope_check_conflicts(scope, p);
304
305 DNS_ANSWER_FOREACH(rr, p->answer) {
306 const char *name = dns_resource_key_name(rr->key);
307 DnsTransaction *t;
308
309 /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa,
310 * we assume someone's playing tricks on us and discard the packet completely. */
311 if (!(dns_name_endswith(name, "in-addr.arpa") > 0 ||
312 dns_name_endswith(name, "local") > 0))
313 return 0;
314
315 if (rr->ttl == 0) {
316 log_debug("Got a goodbye packet");
317 /* See the section 10.1 of RFC6762 */
318 rr->ttl = 1;
319 }
320
321 t = dns_scope_find_transaction(scope, rr->key, false);
322 if (t)
323 dns_transaction_process_reply(t, p);
324
325 /* Also look for the various types of ANY transactions */
326 t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false);
327 if (t)
328 dns_transaction_process_reply(t, p);
329
330 t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), false);
331 if (t)
332 dns_transaction_process_reply(t, p);
333
334 t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false);
335 if (t)
336 dns_transaction_process_reply(t, p);
337 }
338
339 dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender);
340
341 } else if (dns_packet_validate_query(p) > 0) {
342 log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));
343
344 r = mdns_scope_process_query(scope, p);
345 if (r < 0) {
346 log_debug_errno(r, "mDNS query processing failed: %m");
347 return 0;
348 }
349 } else
350 log_debug("Invalid mDNS UDP packet.");
351
352 return 0;
353 }
354
355 int manager_mdns_ipv4_fd(Manager *m) {
356 union sockaddr_union sa = {
357 .in.sin_family = AF_INET,
358 .in.sin_port = htobe16(MDNS_PORT),
359 };
360 static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255;
361 int r;
362
363 assert(m);
364
365 if (m->mdns_ipv4_fd >= 0)
366 return m->mdns_ipv4_fd;
367
368 m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
369 if (m->mdns_ipv4_fd < 0)
370 return log_error_errno(errno, "mDNS-IPv4: Failed to create socket: %m");
371
372 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
373 if (r < 0) {
374 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_TTL: %m");
375 goto fail;
376 }
377
378 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
379 if (r < 0) {
380 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MULTICAST_TTL: %m");
381 goto fail;
382 }
383
384 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
385 if (r < 0) {
386 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MULTICAST_LOOP: %m");
387 goto fail;
388 }
389
390 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
391 if (r < 0) {
392 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_PKTINFO: %m");
393 goto fail;
394 }
395
396 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one));
397 if (r < 0) {
398 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_RECVTTL: %m");
399 goto fail;
400 }
401
402 /* Disable Don't-Fragment bit in the IP header */
403 r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu));
404 if (r < 0) {
405 r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MTU_DISCOVER: %m");
406 goto fail;
407 }
408
409 /* See the section 15.1 of RFC6762 */
410 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
411 r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in));
412 if (r < 0) {
413 if (errno != EADDRINUSE) {
414 r = log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m");
415 goto fail;
416 }
417
418 log_warning("mDNS-IPv4: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers.");
419
420 /* try again with SO_REUSEADDR */
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 }
426
427 r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in));
428 if (r < 0) {
429 r = log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m");
430 goto fail;
431 }
432 } else {
433 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
434 r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
435 if (r < 0) {
436 r = log_error_errno(errno, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m");
437 goto fail;
438 }
439 }
440
441 r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m);
442 if (r < 0)
443 goto fail;
444
445 return m->mdns_ipv4_fd;
446
447 fail:
448 m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd);
449 return r;
450 }
451
452 int manager_mdns_ipv6_fd(Manager *m) {
453 union sockaddr_union sa = {
454 .in6.sin6_family = AF_INET6,
455 .in6.sin6_port = htobe16(MDNS_PORT),
456 };
457 static const int one = 1, ttl = 255;
458 int r;
459
460 assert(m);
461
462 if (m->mdns_ipv6_fd >= 0)
463 return m->mdns_ipv6_fd;
464
465 m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
466 if (m->mdns_ipv6_fd < 0)
467 return log_error_errno(errno, "mDNS-IPv6: Failed to create socket: %m");
468
469 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
470 if (r < 0) {
471 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_UNICAST_HOPS: %m");
472 goto fail;
473 }
474
475 /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
476 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl));
477 if (r < 0) {
478 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_MULTICAST_HOPS: %m");
479 goto fail;
480 }
481
482 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one));
483 if (r < 0) {
484 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_MULTICAST_LOOP: %m");
485 goto fail;
486 }
487
488 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
489 if (r < 0) {
490 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_V6ONLY: %m");
491 goto fail;
492 }
493
494 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
495 if (r < 0) {
496 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_RECVPKTINFO: %m");
497 goto fail;
498 }
499
500 r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one));
501 if (r < 0) {
502 r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_RECVHOPLIMIT: %m");
503 goto fail;
504 }
505
506 /* See the section 15.1 of RFC6762 */
507 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
508 r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6));
509 if (r < 0) {
510 if (errno != EADDRINUSE) {
511 r = log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m");
512 goto fail;
513 }
514
515 log_warning("mDNS-IPv6: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers.");
516
517 /* try again with SO_REUSEADDR */
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 }
523
524 r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6));
525 if (r < 0) {
526 r = log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m");
527 goto fail;
528 }
529 } else {
530 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
531 r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
532 if (r < 0) {
533 r = log_error_errno(errno, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m");
534 goto fail;
535 }
536 }
537
538 r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m);
539 if (r < 0)
540 goto fail;
541
542 return m->mdns_ipv6_fd;
543
544 fail:
545 m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd);
546 return r;
547 }