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