]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-mdns.c
resolved: rework parsing of /etc/hosts
[thirdparty/systemd.git] / src / resolve / resolved-mdns.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
bc7702b0
DM
2
3#include <resolv.h>
4#include <netinet/in.h>
5#include <arpa/inet.h>
6
8d67e72c 7#include "alloc-util.h"
bc7702b0
DM
8#include "fd-util.h"
9#include "resolved-manager.h"
10#include "resolved-mdns.h"
11
8d67e72c
DR
12#define CLEAR_CACHE_FLUSH(x) (~MDNS_RR_CACHE_FLUSH & (x))
13
bc7702b0
DM
14void 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
24int manager_mdns_start(Manager *m) {
25 int r;
26
27 assert(m);
28
af49ca27 29 if (m->mdns_support == RESOLVE_SUPPORT_NO)
bc7702b0
DM
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
48eaddrinuse:
71a047d2 49 log_warning("Another mDNS responder prohibits binding the socket to the same port. Turning off mDNS support.");
af49ca27 50 m->mdns_support = RESOLVE_SUPPORT_NO;
bc7702b0
DM
51 manager_mdns_stop(m);
52
53 return 0;
54}
55
93bab288
YW
56static int mdns_rr_compare(DnsResourceRecord * const *a, DnsResourceRecord * const *b) {
57 DnsResourceRecord *x = *(DnsResourceRecord **) a, *y = *(DnsResourceRecord **) b;
8d67e72c
DR
58 size_t m;
59 int r;
60
61 assert(x);
8d67e72c 62 assert(y);
8d67e72c 63
93bab288
YW
64 r = CMP(CLEAR_CACHE_FLUSH(x->key->class), CLEAR_CACHE_FLUSH(y->key->class));
65 if (r != 0)
66 return r;
8d67e72c 67
93bab288
YW
68 r = CMP(x->key->type, y->key->type);
69 if (r != 0)
70 return r;
8d67e72c 71
93bab288 72 r = dns_resource_record_to_wire_format(x, false);
8d67e72c
DR
73 if (r < 0) {
74 log_warning_errno(r, "Can't wire-format RR: %m");
75 return 0;
76 }
77
93bab288 78 r = dns_resource_record_to_wire_format(y, false);
8d67e72c
DR
79 if (r < 0) {
80 log_warning_errno(r, "Can't wire-format RR: %m");
81 return 0;
82 }
83
93bab288 84 m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(x), DNS_RESOURCE_RECORD_RDATA_SIZE(y));
8d67e72c 85
93bab288 86 r = memcmp(DNS_RESOURCE_RECORD_RDATA(x), DNS_RESOURCE_RECORD_RDATA(y), m);
8d67e72c
DR
87 if (r != 0)
88 return r;
89
93bab288 90 return CMP(DNS_RESOURCE_RECORD_RDATA_SIZE(x), DNS_RESOURCE_RECORD_RDATA_SIZE(y));
8d67e72c
DR
91}
92
93static 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
6dd91b36 104 return CMP(x_size, y_size);
8d67e72c
DR
105}
106
107static 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
da6053d0 117 for (size_t i = DNS_PACKET_ANCOUNT(p); i < (DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)); i++) {
8d67e72c
DR
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
da6053d0 132 for (size_t i = DNS_PACKET_ANCOUNT(p); i < (DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)); i++) {
8d67e72c
DR
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);
93bab288 140 typesafe_qsort(list, size, mdns_rr_compare);
8d67e72c 141
1cc6c93a 142 *ret_rrs = TAKE_PTR(list);
8d67e72c
DR
143
144 return size;
145}
146
147static int mdns_do_tiebreak(DnsResourceKey *key, DnsAnswer *answer, DnsPacket *p) {
148 _cleanup_free_ DnsResourceRecord **our = NULL, **remote = NULL;
149 DnsResourceRecord *rr;
da6053d0 150 size_t i = 0, size;
8d67e72c
DR
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;
93bab288
YW
160
161 typesafe_qsort(our, size, mdns_rr_compare);
8d67e72c
DR
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
3b991089 175static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
9c5e7b73 176 _cleanup_(dns_answer_unrefp) DnsAnswer *full_answer = NULL;
3b991089
DR
177 _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
178 DnsResourceKey *key = NULL;
8d67e72c 179 DnsResourceRecord *rr;
3b991089
DR
180 bool tentative = false;
181 int r;
182
183 assert(s);
184 assert(p);
185
186 r = dns_packet_extract(p);
1a63fc54
LP
187 if (r < 0)
188 return log_debug_errno(r, "Failed to extract resource records from incoming packet: %m");
3b991089 189
3b991089 190 assert_return((dns_question_size(p->question) > 0), -EINVAL);
3b991089 191
9c5e7b73
DR
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
8d67e72c
DR
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
9c5e7b73
DR
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))
3b991089
DR
230 return 0;
231
9c5e7b73 232 r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, NULL, full_answer, NULL, false, &reply);
9886b6b1
LP
233 if (r < 0)
234 return log_debug_errno(r, "Failed to build reply packet: %m");
3b991089 235
7994ac1d 236 if (!ratelimit_below(&s->ratelimit))
3b991089
DR
237 return 0;
238
239 r = dns_scope_emit_udp(s, -1, reply);
9886b6b1
LP
240 if (r < 0)
241 return log_debug_errno(r, "Failed to send reply packet: %m");
3b991089
DR
242
243 return 0;
244}
245
bc7702b0 246static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
124602ae
DM
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
cfb17f45
DR
256 if (manager_our_packet(m, p))
257 return 0;
258
124602ae
DM
259 scope = manager_find_scope(m, p);
260 if (!scope) {
f1b1a5c4 261 log_debug("Got mDNS UDP packet on unknown scope. Ignoring.");
124602ae
DM
262 return 0;
263 }
264
265 if (dns_packet_validate_reply(p) > 0) {
40fa4728 266 DnsResourceRecord *rr;
124602ae
DM
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
40fa4728 287 DNS_ANSWER_FOREACH(rr, p->answer) {
1c02e7ba 288 const char *name = dns_resource_key_name(rr->key);
124602ae
DM
289 DnsTransaction *t;
290
40fa4728
DM
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;
124602ae 296
3755027c
DR
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
124602ae
DM
303 t = dns_scope_find_transaction(scope, rr->key, false);
304 if (t)
48413582
LP
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)
124602ae
DM
318 dns_transaction_process_reply(t, p);
319 }
320
d3760be0 321 dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender);
124602ae
DM
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
3b991089
DR
326 r = mdns_scope_process_query(scope, p);
327 if (r < 0) {
1a63fc54 328 log_debug_errno(r, "mDNS query processing failed: %m");
3b991089
DR
329 return 0;
330 }
124602ae
DM
331 } else
332 log_debug("Invalid mDNS UDP packet.");
333
bc7702b0
DM
334 return 0;
335}
336
337int 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 };
73c76767 342 _cleanup_close_ int s = -1;
bc7702b0
DM
343 int r;
344
345 assert(m);
346
347 if (m->mdns_ipv4_fd >= 0)
348 return m->mdns_ipv4_fd;
349
73c76767
YW
350 s = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
351 if (s < 0)
71a047d2 352 return log_error_errno(errno, "mDNS-IPv4: Failed to create socket: %m");
bc7702b0 353
73c76767
YW
354 r = setsockopt_int(s, IPPROTO_IP, IP_TTL, 255);
355 if (r < 0)
356 return log_error_errno(r, "mDNS-IPv4: Failed to set IP_TTL: %m");
bc7702b0 357
73c76767
YW
358 r = setsockopt_int(s, IPPROTO_IP, IP_MULTICAST_TTL, 255);
359 if (r < 0)
360 return log_error_errno(r, "mDNS-IPv4: Failed to set IP_MULTICAST_TTL: %m");
bc7702b0 361
73c76767
YW
362 r = setsockopt_int(s, IPPROTO_IP, IP_MULTICAST_LOOP, true);
363 if (r < 0)
364 return log_error_errno(r, "mDNS-IPv4: Failed to set IP_MULTICAST_LOOP: %m");
bc7702b0 365
73c76767
YW
366 r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true);
367 if (r < 0)
368 return log_error_errno(r, "mDNS-IPv4: Failed to set IP_PKTINFO: %m");
bc7702b0 369
73c76767
YW
370 r = setsockopt_int(s, IPPROTO_IP, IP_RECVTTL, true);
371 if (r < 0)
372 return log_error_errno(r, "mDNS-IPv4: Failed to set IP_RECVTTL: %m");
bc7702b0
DM
373
374 /* Disable Don't-Fragment bit in the IP header */
73c76767
YW
375 r = setsockopt_int(s, IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DONT);
376 if (r < 0)
377 return log_error_errno(r, "mDNS-IPv4: Failed to set IP_MTU_DISCOVER: %m");
bc7702b0 378
71a047d2
YW
379 /* See the section 15.1 of RFC6762 */
380 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
73c76767 381 r = bind(s, &sa.sa, sizeof(sa.in));
bc7702b0 382 if (r < 0) {
73c76767
YW
383 if (errno != EADDRINUSE)
384 return log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m");
71a047d2
YW
385
386 log_warning("mDNS-IPv4: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers.");
387
388 /* try again with SO_REUSEADDR */
73c76767
YW
389 r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
390 if (r < 0)
391 return log_error_errno(r, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m");
71a047d2 392
73c76767
YW
393 r = bind(s, &sa.sa, sizeof(sa.in));
394 if (r < 0)
395 return log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m");
71a047d2
YW
396 } else {
397 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
73c76767
YW
398 r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
399 if (r < 0)
400 return log_error_errno(r, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m");
bc7702b0
DM
401 }
402
73c76767 403 r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, s, EPOLLIN, on_mdns_packet, m);
bc7702b0 404 if (r < 0)
73c76767 405 return log_error_errno(r, "mDNS-IPv4: Failed to create event source: %m");
bc7702b0 406
73c76767 407 return m->mdns_ipv4_fd = TAKE_FD(s);
bc7702b0
DM
408}
409
410int manager_mdns_ipv6_fd(Manager *m) {
411 union sockaddr_union sa = {
412 .in6.sin6_family = AF_INET6,
413 .in6.sin6_port = htobe16(MDNS_PORT),
414 };
73c76767 415 _cleanup_close_ int s = -1;
bc7702b0
DM
416 int r;
417
418 assert(m);
419
420 if (m->mdns_ipv6_fd >= 0)
421 return m->mdns_ipv6_fd;
422
73c76767
YW
423 s = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
424 if (s < 0)
71a047d2 425 return log_error_errno(errno, "mDNS-IPv6: Failed to create socket: %m");
bc7702b0 426
73c76767
YW
427 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 255);
428 if (r < 0)
429 return log_error_errno(r, "mDNS-IPv6: Failed to set IPV6_UNICAST_HOPS: %m");
bc7702b0
DM
430
431 /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
73c76767
YW
432 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255);
433 if (r < 0)
434 return log_error_errno(r, "mDNS-IPv6: Failed to set IPV6_MULTICAST_HOPS: %m");
bc7702b0 435
73c76767
YW
436 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, true);
437 if (r < 0)
438 return log_error_errno(r, "mDNS-IPv6: Failed to set IPV6_MULTICAST_LOOP: %m");
bc7702b0 439
73c76767
YW
440 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_V6ONLY, true);
441 if (r < 0)
442 return log_error_errno(r, "mDNS-IPv6: Failed to set IPV6_V6ONLY: %m");
bc7702b0 443
73c76767
YW
444 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, true);
445 if (r < 0)
446 return log_error_errno(r, "mDNS-IPv6: Failed to set IPV6_RECVPKTINFO: %m");
bc7702b0 447
73c76767
YW
448 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, true);
449 if (r < 0)
450 return log_error_errno(r, "mDNS-IPv6: Failed to set IPV6_RECVHOPLIMIT: %m");
bc7702b0 451
71a047d2
YW
452 /* See the section 15.1 of RFC6762 */
453 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
73c76767 454 r = bind(s, &sa.sa, sizeof(sa.in6));
bc7702b0 455 if (r < 0) {
73c76767
YW
456 if (errno != EADDRINUSE)
457 return log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m");
71a047d2
YW
458
459 log_warning("mDNS-IPv6: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers.");
460
461 /* try again with SO_REUSEADDR */
73c76767
YW
462 r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
463 if (r < 0)
464 return log_error_errno(r, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m");
71a047d2 465
73c76767
YW
466 r = bind(s, &sa.sa, sizeof(sa.in6));
467 if (r < 0)
468 return log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m");
71a047d2
YW
469 } else {
470 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
73c76767
YW
471 r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
472 if (r < 0)
473 return log_error_errno(r, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m");
bc7702b0
DM
474 }
475
73c76767 476 r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, s, EPOLLIN, on_mdns_packet, m);
ee8d9305 477 if (r < 0)
73c76767 478 return log_error_errno(r, "mDNS-IPv6: Failed to create event source: %m");
bc7702b0 479
73c76767 480 return m->mdns_ipv6_fd = TAKE_FD(s);
bc7702b0 481}