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