1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2015 Daniel Mack
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <netinet/in.h>
23 #include <arpa/inet.h>
25 #include "alloc-util.h"
27 #include "resolved-manager.h"
28 #include "resolved-mdns.h"
30 #define CLEAR_CACHE_FLUSH(x) (~MDNS_RR_CACHE_FLUSH & (x))
32 void manager_mdns_stop(Manager
*m
) {
35 m
->mdns_ipv4_event_source
= sd_event_source_unref(m
->mdns_ipv4_event_source
);
36 m
->mdns_ipv4_fd
= safe_close(m
->mdns_ipv4_fd
);
38 m
->mdns_ipv6_event_source
= sd_event_source_unref(m
->mdns_ipv6_event_source
);
39 m
->mdns_ipv6_fd
= safe_close(m
->mdns_ipv6_fd
);
42 int manager_mdns_start(Manager
*m
) {
47 if (m
->mdns_support
== RESOLVE_SUPPORT_NO
)
50 r
= manager_mdns_ipv4_fd(m
);
56 if (socket_ipv6_is_supported()) {
57 r
= manager_mdns_ipv6_fd(m
);
67 log_warning("Another mDNS responder prohibits binding the socket to the same port. Turning off mDNS support.");
68 m
->mdns_support
= RESOLVE_SUPPORT_NO
;
74 static int mdns_rr_compare(const void *a
, const void *b
) {
75 DnsResourceRecord
**x
= (DnsResourceRecord
**) a
, **y
= (DnsResourceRecord
**) b
;
84 if (CLEAR_CACHE_FLUSH((*x
)->key
->class) < CLEAR_CACHE_FLUSH((*y
)->key
->class))
86 else if (CLEAR_CACHE_FLUSH((*x
)->key
->class) > CLEAR_CACHE_FLUSH((*y
)->key
->class))
89 if ((*x
)->key
->type
< (*y
)->key
->type
)
91 else if ((*x
)->key
->type
> (*y
)->key
->type
)
94 r
= dns_resource_record_to_wire_format(*x
, false);
96 log_warning_errno(r
, "Can't wire-format RR: %m");
100 r
= dns_resource_record_to_wire_format(*y
, false);
102 log_warning_errno(r
, "Can't wire-format RR: %m");
106 m
= MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x
), DNS_RESOURCE_RECORD_RDATA_SIZE(*y
));
108 r
= memcmp(DNS_RESOURCE_RECORD_RDATA(*x
), DNS_RESOURCE_RECORD_RDATA(*y
), m
);
112 if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x
) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y
))
114 else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x
) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y
))
120 static int proposed_rrs_cmp(DnsResourceRecord
**x
, unsigned x_size
, DnsResourceRecord
**y
, unsigned y_size
) {
124 m
= MIN(x_size
, y_size
);
125 for (unsigned i
= 0; i
< m
; i
++) {
126 r
= mdns_rr_compare(&x
[i
], &y
[i
]);
139 static int mdns_packet_extract_matching_rrs(DnsPacket
*p
, DnsResourceKey
*key
, DnsResourceRecord
***ret_rrs
) {
140 _cleanup_free_ DnsResourceRecord
**list
= NULL
;
141 unsigned n
= 0, size
= 0;
147 assert_return(DNS_PACKET_NSCOUNT(p
) > 0, -EINVAL
);
149 for (unsigned i
= DNS_PACKET_ANCOUNT(p
); i
< (DNS_PACKET_ANCOUNT(p
) + DNS_PACKET_NSCOUNT(p
)); i
++) {
150 r
= dns_resource_key_match_rr(key
, p
->answer
->items
[i
].rr
, NULL
);
160 list
= new(DnsResourceRecord
*, size
);
164 for (unsigned i
= DNS_PACKET_ANCOUNT(p
); i
< (DNS_PACKET_ANCOUNT(p
) + DNS_PACKET_NSCOUNT(p
)); i
++) {
165 r
= dns_resource_key_match_rr(key
, p
->answer
->items
[i
].rr
, NULL
);
169 list
[n
++] = p
->answer
->items
[i
].rr
;
172 qsort_safe(list
, size
, sizeof(DnsResourceRecord
*), mdns_rr_compare
);
174 *ret_rrs
= TAKE_PTR(list
);
179 static int mdns_do_tiebreak(DnsResourceKey
*key
, DnsAnswer
*answer
, DnsPacket
*p
) {
180 _cleanup_free_ DnsResourceRecord
**our
= NULL
, **remote
= NULL
;
181 DnsResourceRecord
*rr
;
186 size
= dns_answer_size(answer
);
187 our
= new(DnsResourceRecord
*, size
);
191 DNS_ANSWER_FOREACH(rr
, answer
)
193 qsort_safe(our
, size
, sizeof(DnsResourceRecord
*), mdns_rr_compare
);
195 r
= mdns_packet_extract_matching_rrs(p
, key
, &remote
);
201 if (proposed_rrs_cmp(remote
, r
, our
, size
) > 0)
207 static int mdns_scope_process_query(DnsScope
*s
, DnsPacket
*p
) {
208 _cleanup_(dns_answer_unrefp
) DnsAnswer
*full_answer
= NULL
;
209 _cleanup_(dns_packet_unrefp
) DnsPacket
*reply
= NULL
;
210 DnsResourceKey
*key
= NULL
;
211 DnsResourceRecord
*rr
;
212 bool tentative
= false;
218 r
= dns_packet_extract(p
);
220 return log_debug_errno(r
, "Failed to extract resource records from incoming packet: %m");
222 assert_return((dns_question_size(p
->question
) > 0), -EINVAL
);
224 DNS_QUESTION_FOREACH(key
, p
->question
) {
225 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
227 r
= dns_zone_lookup(&s
->zone
, key
, 0, &answer
, &soa
, &tentative
);
229 return log_debug_errno(r
, "Failed to lookup key: %m");
231 if (tentative
&& DNS_PACKET_NSCOUNT(p
) > 0) {
233 * A race condition detected with the probe packet from
235 * Do simultaneous probe tiebreaking as described in
236 * RFC 6762, Section 8.2. In case we lost don't reply
237 * the question and withdraw conflicting RRs.
239 r
= mdns_do_tiebreak(key
, answer
, p
);
241 return log_debug_errno(r
, "Failed to do tiebreaking");
243 if (r
> 0) { /* we lost */
244 DNS_ANSWER_FOREACH(rr
, answer
) {
247 i
= dns_zone_get(&s
->zone
, rr
);
249 dns_zone_item_conflict(i
);
256 r
= dns_answer_extend(&full_answer
, answer
);
258 return log_debug_errno(r
, "Failed to extend answer: %m");
261 if (dns_answer_isempty(full_answer
))
264 r
= dns_scope_make_reply_packet(s
, DNS_PACKET_ID(p
), DNS_RCODE_SUCCESS
, NULL
, full_answer
, NULL
, false, &reply
);
266 return log_debug_errno(r
, "Failed to build reply packet: %m");
268 if (!ratelimit_test(&s
->ratelimit
))
271 r
= dns_scope_emit_udp(s
, -1, reply
);
273 return log_debug_errno(r
, "Failed to send reply packet: %m");
278 static int on_mdns_packet(sd_event_source
*s
, int fd
, uint32_t revents
, void *userdata
) {
279 _cleanup_(dns_packet_unrefp
) DnsPacket
*p
= NULL
;
280 Manager
*m
= userdata
;
284 r
= manager_recv(m
, fd
, DNS_PROTOCOL_MDNS
, &p
);
288 if (manager_our_packet(m
, p
))
291 scope
= manager_find_scope(m
, p
);
293 log_debug("Got mDNS UDP packet on unknown scope. Ignoring.");
297 if (dns_packet_validate_reply(p
) > 0) {
298 DnsResourceRecord
*rr
;
300 log_debug("Got mDNS reply packet");
303 * mDNS is different from regular DNS and LLMNR with regard to handling responses.
304 * While on other protocols, we can ignore every answer that doesn't match a question
305 * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all
306 * incoming information, regardless of the DNS packet ID.
308 * Hence, extract the packet here, and try to find a transaction for answer the we got
309 * and complete it. Also store the new information in scope's cache.
311 r
= dns_packet_extract(p
);
313 log_debug("mDNS packet extraction failed.");
317 dns_scope_check_conflicts(scope
, p
);
319 DNS_ANSWER_FOREACH(rr
, p
->answer
) {
320 const char *name
= dns_resource_key_name(rr
->key
);
323 /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa,
324 * we assume someone's playing tricks on us and discard the packet completely. */
325 if (!(dns_name_endswith(name
, "in-addr.arpa") > 0 ||
326 dns_name_endswith(name
, "local") > 0))
330 log_debug("Got a goodbye packet");
331 /* See the section 10.1 of RFC6762 */
335 t
= dns_scope_find_transaction(scope
, rr
->key
, false);
337 dns_transaction_process_reply(t
, p
);
339 /* Also look for the various types of ANY transactions */
340 t
= dns_scope_find_transaction(scope
, &DNS_RESOURCE_KEY_CONST(rr
->key
->class, DNS_TYPE_ANY
, dns_resource_key_name(rr
->key
)), false);
342 dns_transaction_process_reply(t
, p
);
344 t
= dns_scope_find_transaction(scope
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY
, rr
->key
->type
, dns_resource_key_name(rr
->key
)), false);
346 dns_transaction_process_reply(t
, p
);
348 t
= dns_scope_find_transaction(scope
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY
, DNS_TYPE_ANY
, dns_resource_key_name(rr
->key
)), false);
350 dns_transaction_process_reply(t
, p
);
353 dns_cache_put(&scope
->cache
, NULL
, DNS_PACKET_RCODE(p
), p
->answer
, false, (uint32_t) -1, 0, p
->family
, &p
->sender
);
355 } else if (dns_packet_validate_query(p
) > 0) {
356 log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p
));
358 r
= mdns_scope_process_query(scope
, p
);
360 log_debug_errno(r
, "mDNS query processing failed: %m");
364 log_debug("Invalid mDNS UDP packet.");
369 int manager_mdns_ipv4_fd(Manager
*m
) {
370 union sockaddr_union sa
= {
371 .in
.sin_family
= AF_INET
,
372 .in
.sin_port
= htobe16(MDNS_PORT
),
374 static const int one
= 1, pmtu
= IP_PMTUDISC_DONT
, ttl
= 255;
379 if (m
->mdns_ipv4_fd
>= 0)
380 return m
->mdns_ipv4_fd
;
382 m
->mdns_ipv4_fd
= socket(AF_INET
, SOCK_DGRAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
383 if (m
->mdns_ipv4_fd
< 0)
384 return log_error_errno(errno
, "mDNS-IPv4: Failed to create socket: %m");
386 r
= setsockopt(m
->mdns_ipv4_fd
, IPPROTO_IP
, IP_TTL
, &ttl
, sizeof(ttl
));
388 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to set IP_TTL: %m");
392 r
= setsockopt(m
->mdns_ipv4_fd
, IPPROTO_IP
, IP_MULTICAST_TTL
, &ttl
, sizeof(ttl
));
394 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to set IP_MULTICAST_TTL: %m");
398 r
= setsockopt(m
->mdns_ipv4_fd
, IPPROTO_IP
, IP_MULTICAST_LOOP
, &one
, sizeof(one
));
400 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to set IP_MULTICAST_LOOP: %m");
404 r
= setsockopt(m
->mdns_ipv4_fd
, IPPROTO_IP
, IP_PKTINFO
, &one
, sizeof(one
));
406 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to set IP_PKTINFO: %m");
410 r
= setsockopt(m
->mdns_ipv4_fd
, IPPROTO_IP
, IP_RECVTTL
, &one
, sizeof(one
));
412 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to set IP_RECVTTL: %m");
416 /* Disable Don't-Fragment bit in the IP header */
417 r
= setsockopt(m
->mdns_ipv4_fd
, IPPROTO_IP
, IP_MTU_DISCOVER
, &pmtu
, sizeof(pmtu
));
419 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to set IP_MTU_DISCOVER: %m");
423 /* See the section 15.1 of RFC6762 */
424 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
425 r
= bind(m
->mdns_ipv4_fd
, &sa
.sa
, sizeof(sa
.in
));
427 if (errno
!= EADDRINUSE
) {
428 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to bind socket: %m");
432 log_warning("mDNS-IPv4: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers.");
434 /* try again with SO_REUSEADDR */
435 r
= setsockopt(m
->mdns_ipv4_fd
, SOL_SOCKET
, SO_REUSEADDR
, &one
, sizeof(one
));
437 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m");
441 r
= bind(m
->mdns_ipv4_fd
, &sa
.sa
, sizeof(sa
.in
));
443 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to bind socket: %m");
447 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
448 r
= setsockopt(m
->mdns_ipv4_fd
, SOL_SOCKET
, SO_REUSEADDR
, &one
, sizeof(one
));
450 r
= log_error_errno(errno
, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m");
455 r
= sd_event_add_io(m
->event
, &m
->mdns_ipv4_event_source
, m
->mdns_ipv4_fd
, EPOLLIN
, on_mdns_packet
, m
);
459 return m
->mdns_ipv4_fd
;
462 m
->mdns_ipv4_fd
= safe_close(m
->mdns_ipv4_fd
);
466 int manager_mdns_ipv6_fd(Manager
*m
) {
467 union sockaddr_union sa
= {
468 .in6
.sin6_family
= AF_INET6
,
469 .in6
.sin6_port
= htobe16(MDNS_PORT
),
471 static const int one
= 1, ttl
= 255;
476 if (m
->mdns_ipv6_fd
>= 0)
477 return m
->mdns_ipv6_fd
;
479 m
->mdns_ipv6_fd
= socket(AF_INET6
, SOCK_DGRAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
480 if (m
->mdns_ipv6_fd
< 0)
481 return log_error_errno(errno
, "mDNS-IPv6: Failed to create socket: %m");
483 r
= setsockopt(m
->mdns_ipv6_fd
, IPPROTO_IPV6
, IPV6_UNICAST_HOPS
, &ttl
, sizeof(ttl
));
485 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to set IPV6_UNICAST_HOPS: %m");
489 /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
490 r
= setsockopt(m
->mdns_ipv6_fd
, IPPROTO_IPV6
, IPV6_MULTICAST_HOPS
, &ttl
, sizeof(ttl
));
492 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to set IPV6_MULTICAST_HOPS: %m");
496 r
= setsockopt(m
->mdns_ipv6_fd
, IPPROTO_IPV6
, IPV6_MULTICAST_LOOP
, &one
, sizeof(one
));
498 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to set IPV6_MULTICAST_LOOP: %m");
502 r
= setsockopt(m
->mdns_ipv6_fd
, IPPROTO_IPV6
, IPV6_V6ONLY
, &one
, sizeof(one
));
504 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to set IPV6_V6ONLY: %m");
508 r
= setsockopt(m
->mdns_ipv6_fd
, IPPROTO_IPV6
, IPV6_RECVPKTINFO
, &one
, sizeof(one
));
510 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to set IPV6_RECVPKTINFO: %m");
514 r
= setsockopt(m
->mdns_ipv6_fd
, IPPROTO_IPV6
, IPV6_RECVHOPLIMIT
, &one
, sizeof(one
));
516 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to set IPV6_RECVHOPLIMIT: %m");
520 /* See the section 15.1 of RFC6762 */
521 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
522 r
= bind(m
->mdns_ipv6_fd
, &sa
.sa
, sizeof(sa
.in6
));
524 if (errno
!= EADDRINUSE
) {
525 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to bind socket: %m");
529 log_warning("mDNS-IPv6: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers.");
531 /* try again with SO_REUSEADDR */
532 r
= setsockopt(m
->mdns_ipv6_fd
, SOL_SOCKET
, SO_REUSEADDR
, &one
, sizeof(one
));
534 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m");
538 r
= bind(m
->mdns_ipv6_fd
, &sa
.sa
, sizeof(sa
.in6
));
540 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to bind socket: %m");
544 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
545 r
= setsockopt(m
->mdns_ipv6_fd
, SOL_SOCKET
, SO_REUSEADDR
, &one
, sizeof(one
));
547 r
= log_error_errno(errno
, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m");
552 r
= sd_event_add_io(m
->event
, &m
->mdns_ipv6_event_source
, m
->mdns_ipv6_fd
, EPOLLIN
, on_mdns_packet
, m
);
556 return m
->mdns_ipv6_fd
;
559 m
->mdns_ipv6_fd
= safe_close(m
->mdns_ipv6_fd
);