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