]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-mdns.c
Merge pull request #10378 from poettering/json-fuzz-fix
[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 _cleanup_close_ int s = -1;
343 int r;
344
345 assert(m);
346
347 if (m->mdns_ipv4_fd >= 0)
348 return m->mdns_ipv4_fd;
349
350 s = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
351 if (s < 0)
352 return log_error_errno(errno, "mDNS-IPv4: Failed to create socket: %m");
353
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");
357
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");
361
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");
365
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");
369
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");
373
374 /* Disable Don't-Fragment bit in the IP header */
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");
378
379 /* See the section 15.1 of RFC6762 */
380 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
381 r = bind(s, &sa.sa, sizeof(sa.in));
382 if (r < 0) {
383 if (errno != EADDRINUSE)
384 return log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m");
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 */
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");
392
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");
396 } else {
397 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
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");
401 }
402
403 r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, s, EPOLLIN, on_mdns_packet, m);
404 if (r < 0)
405 return log_error_errno(r, "mDNS-IPv4: Failed to create event source: %m");
406
407 return m->mdns_ipv4_fd = TAKE_FD(s);
408 }
409
410 int 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 };
415 _cleanup_close_ int s = -1;
416 int r;
417
418 assert(m);
419
420 if (m->mdns_ipv6_fd >= 0)
421 return m->mdns_ipv6_fd;
422
423 s = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
424 if (s < 0)
425 return log_error_errno(errno, "mDNS-IPv6: Failed to create socket: %m");
426
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");
430
431 /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
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");
435
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");
439
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");
443
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");
447
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");
451
452 /* See the section 15.1 of RFC6762 */
453 /* first try to bind without SO_REUSEADDR to detect another mDNS responder */
454 r = bind(s, &sa.sa, sizeof(sa.in6));
455 if (r < 0) {
456 if (errno != EADDRINUSE)
457 return log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m");
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 */
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");
465
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");
469 } else {
470 /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */
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");
474 }
475
476 r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, s, EPOLLIN, on_mdns_packet, m);
477 if (r < 0)
478 return log_error_errno(r, "mDNS-IPv6: Failed to create event source: %m");
479
480 return m->mdns_ipv6_fd = TAKE_FD(s);
481 }