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