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