]>
Commit | Line | Data |
---|---|---|
bc7702b0 DM |
1 | /*** |
2 | This file is part of systemd. | |
3 | ||
4 | Copyright 2015 Daniel Mack | |
5 | ||
6 | systemd is free software; you can redistribute it and/or modify it | |
7 | under the terms of the GNU Lesser General Public License as published by | |
8 | the Free Software Foundation; either version 2.1 of the License, or | |
9 | (at your option) any later version. | |
10 | ||
11 | systemd is distributed in the hope that it will be useful, but | |
12 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public License | |
17 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
18 | ***/ | |
19 | ||
20 | #include <resolv.h> | |
21 | #include <netinet/in.h> | |
22 | #include <arpa/inet.h> | |
23 | ||
24 | #include "fd-util.h" | |
25 | #include "resolved-manager.h" | |
26 | #include "resolved-mdns.h" | |
27 | ||
28 | void manager_mdns_stop(Manager *m) { | |
29 | assert(m); | |
30 | ||
31 | m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); | |
32 | m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); | |
33 | ||
34 | m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); | |
35 | m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); | |
36 | } | |
37 | ||
38 | int manager_mdns_start(Manager *m) { | |
39 | int r; | |
40 | ||
41 | assert(m); | |
42 | ||
af49ca27 | 43 | if (m->mdns_support == RESOLVE_SUPPORT_NO) |
bc7702b0 DM |
44 | return 0; |
45 | ||
46 | r = manager_mdns_ipv4_fd(m); | |
47 | if (r == -EADDRINUSE) | |
48 | goto eaddrinuse; | |
49 | if (r < 0) | |
50 | return r; | |
51 | ||
52 | if (socket_ipv6_is_supported()) { | |
53 | r = manager_mdns_ipv6_fd(m); | |
54 | if (r == -EADDRINUSE) | |
55 | goto eaddrinuse; | |
56 | if (r < 0) | |
57 | return r; | |
58 | } | |
59 | ||
60 | return 0; | |
61 | ||
62 | eaddrinuse: | |
71a047d2 | 63 | log_warning("Another mDNS responder prohibits binding the socket to the same port. Turning off mDNS support."); |
af49ca27 | 64 | m->mdns_support = RESOLVE_SUPPORT_NO; |
bc7702b0 DM |
65 | manager_mdns_stop(m); |
66 | ||
67 | return 0; | |
68 | } | |
69 | ||
3b991089 DR |
70 | static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) { |
71 | _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; | |
72 | _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; | |
73 | DnsResourceKey *key = NULL; | |
74 | bool tentative = false; | |
75 | int r; | |
76 | ||
77 | assert(s); | |
78 | assert(p); | |
79 | ||
80 | r = dns_packet_extract(p); | |
1a63fc54 LP |
81 | if (r < 0) |
82 | return log_debug_errno(r, "Failed to extract resource records from incoming packet: %m"); | |
3b991089 DR |
83 | |
84 | /* TODO: there might be more than one question in mDNS queries. */ | |
85 | assert_return((dns_question_size(p->question) > 0), -EINVAL); | |
86 | key = p->question->keys[0]; | |
87 | ||
88 | r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative); | |
9886b6b1 LP |
89 | if (r < 0) |
90 | return log_debug_errno(r, "Failed to lookup key: %m"); | |
3b991089 DR |
91 | if (r == 0) |
92 | return 0; | |
93 | ||
94 | r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, NULL, answer, NULL, false, &reply); | |
9886b6b1 LP |
95 | if (r < 0) |
96 | return log_debug_errno(r, "Failed to build reply packet: %m"); | |
3b991089 DR |
97 | |
98 | if (!ratelimit_test(&s->ratelimit)) | |
99 | return 0; | |
100 | ||
101 | r = dns_scope_emit_udp(s, -1, reply); | |
9886b6b1 LP |
102 | if (r < 0) |
103 | return log_debug_errno(r, "Failed to send reply packet: %m"); | |
3b991089 DR |
104 | |
105 | return 0; | |
106 | } | |
107 | ||
bc7702b0 | 108 | static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { |
124602ae DM |
109 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; |
110 | Manager *m = userdata; | |
111 | DnsScope *scope; | |
112 | int r; | |
113 | ||
114 | r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); | |
115 | if (r <= 0) | |
116 | return r; | |
117 | ||
cfb17f45 DR |
118 | if (manager_our_packet(m, p)) |
119 | return 0; | |
120 | ||
124602ae DM |
121 | scope = manager_find_scope(m, p); |
122 | if (!scope) { | |
123 | log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); | |
124 | return 0; | |
125 | } | |
126 | ||
127 | if (dns_packet_validate_reply(p) > 0) { | |
40fa4728 | 128 | DnsResourceRecord *rr; |
124602ae DM |
129 | |
130 | log_debug("Got mDNS reply packet"); | |
131 | ||
132 | /* | |
133 | * mDNS is different from regular DNS and LLMNR with regard to handling responses. | |
134 | * While on other protocols, we can ignore every answer that doesn't match a question | |
135 | * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all | |
136 | * incoming information, regardless of the DNS packet ID. | |
137 | * | |
138 | * Hence, extract the packet here, and try to find a transaction for answer the we got | |
139 | * and complete it. Also store the new information in scope's cache. | |
140 | */ | |
141 | r = dns_packet_extract(p); | |
142 | if (r < 0) { | |
143 | log_debug("mDNS packet extraction failed."); | |
144 | return 0; | |
145 | } | |
146 | ||
147 | dns_scope_check_conflicts(scope, p); | |
148 | ||
40fa4728 | 149 | DNS_ANSWER_FOREACH(rr, p->answer) { |
1c02e7ba | 150 | const char *name = dns_resource_key_name(rr->key); |
124602ae DM |
151 | DnsTransaction *t; |
152 | ||
40fa4728 DM |
153 | /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, |
154 | * we assume someone's playing tricks on us and discard the packet completely. */ | |
155 | if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || | |
156 | dns_name_endswith(name, "local") > 0)) | |
157 | return 0; | |
124602ae | 158 | |
3755027c DR |
159 | if (rr->ttl == 0) { |
160 | log_debug("Got a goodbye packet"); | |
161 | /* See the section 10.1 of RFC6762 */ | |
162 | rr->ttl = 1; | |
163 | } | |
164 | ||
124602ae DM |
165 | t = dns_scope_find_transaction(scope, rr->key, false); |
166 | if (t) | |
48413582 LP |
167 | dns_transaction_process_reply(t, p); |
168 | ||
169 | /* Also look for the various types of ANY transactions */ | |
170 | t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false); | |
171 | if (t) | |
172 | dns_transaction_process_reply(t, p); | |
173 | ||
174 | t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), false); | |
175 | if (t) | |
176 | dns_transaction_process_reply(t, p); | |
177 | ||
178 | t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false); | |
179 | if (t) | |
124602ae DM |
180 | dns_transaction_process_reply(t, p); |
181 | } | |
182 | ||
d3760be0 | 183 | dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); |
124602ae DM |
184 | |
185 | } else if (dns_packet_validate_query(p) > 0) { | |
186 | log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); | |
187 | ||
3b991089 DR |
188 | r = mdns_scope_process_query(scope, p); |
189 | if (r < 0) { | |
1a63fc54 | 190 | log_debug_errno(r, "mDNS query processing failed: %m"); |
3b991089 DR |
191 | return 0; |
192 | } | |
124602ae DM |
193 | } else |
194 | log_debug("Invalid mDNS UDP packet."); | |
195 | ||
bc7702b0 DM |
196 | return 0; |
197 | } | |
198 | ||
199 | int manager_mdns_ipv4_fd(Manager *m) { | |
200 | union sockaddr_union sa = { | |
201 | .in.sin_family = AF_INET, | |
202 | .in.sin_port = htobe16(MDNS_PORT), | |
203 | }; | |
204 | static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; | |
205 | int r; | |
206 | ||
207 | assert(m); | |
208 | ||
209 | if (m->mdns_ipv4_fd >= 0) | |
210 | return m->mdns_ipv4_fd; | |
211 | ||
212 | m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); | |
213 | if (m->mdns_ipv4_fd < 0) | |
71a047d2 | 214 | return log_error_errno(errno, "mDNS-IPv4: Failed to create socket: %m"); |
bc7702b0 DM |
215 | |
216 | r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); | |
217 | if (r < 0) { | |
71a047d2 | 218 | r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_TTL: %m"); |
bc7702b0 DM |
219 | goto fail; |
220 | } | |
221 | ||
222 | r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); | |
223 | if (r < 0) { | |
71a047d2 | 224 | r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MULTICAST_TTL: %m"); |
bc7702b0 DM |
225 | goto fail; |
226 | } | |
227 | ||
228 | r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); | |
229 | if (r < 0) { | |
71a047d2 | 230 | r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MULTICAST_LOOP: %m"); |
bc7702b0 DM |
231 | goto fail; |
232 | } | |
233 | ||
234 | r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); | |
235 | if (r < 0) { | |
71a047d2 | 236 | r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_PKTINFO: %m"); |
bc7702b0 DM |
237 | goto fail; |
238 | } | |
239 | ||
240 | r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); | |
241 | if (r < 0) { | |
71a047d2 | 242 | r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_RECVTTL: %m"); |
bc7702b0 DM |
243 | goto fail; |
244 | } | |
245 | ||
246 | /* Disable Don't-Fragment bit in the IP header */ | |
247 | r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); | |
248 | if (r < 0) { | |
71a047d2 | 249 | r = log_error_errno(errno, "mDNS-IPv4: Failed to set IP_MTU_DISCOVER: %m"); |
bc7702b0 DM |
250 | goto fail; |
251 | } | |
252 | ||
71a047d2 YW |
253 | /* See the section 15.1 of RFC6762 */ |
254 | /* first try to bind without SO_REUSEADDR to detect another mDNS responder */ | |
bc7702b0 DM |
255 | r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); |
256 | if (r < 0) { | |
71a047d2 YW |
257 | if (errno != EADDRINUSE) { |
258 | r = log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m"); | |
259 | goto fail; | |
260 | } | |
261 | ||
262 | log_warning("mDNS-IPv4: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers."); | |
263 | ||
264 | /* try again with SO_REUSEADDR */ | |
265 | r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | |
266 | if (r < 0) { | |
267 | r = log_error_errno(errno, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m"); | |
268 | goto fail; | |
269 | } | |
270 | ||
271 | r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); | |
272 | if (r < 0) { | |
273 | r = log_error_errno(errno, "mDNS-IPv4: Failed to bind socket: %m"); | |
274 | goto fail; | |
275 | } | |
276 | } else { | |
277 | /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */ | |
278 | r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | |
279 | if (r < 0) { | |
280 | r = log_error_errno(errno, "mDNS-IPv4: Failed to set SO_REUSEADDR: %m"); | |
281 | goto fail; | |
282 | } | |
bc7702b0 DM |
283 | } |
284 | ||
285 | r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); | |
286 | if (r < 0) | |
287 | goto fail; | |
288 | ||
289 | return m->mdns_ipv4_fd; | |
290 | ||
291 | fail: | |
292 | m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); | |
293 | return r; | |
294 | } | |
295 | ||
296 | int manager_mdns_ipv6_fd(Manager *m) { | |
297 | union sockaddr_union sa = { | |
298 | .in6.sin6_family = AF_INET6, | |
299 | .in6.sin6_port = htobe16(MDNS_PORT), | |
300 | }; | |
301 | static const int one = 1, ttl = 255; | |
302 | int r; | |
303 | ||
304 | assert(m); | |
305 | ||
306 | if (m->mdns_ipv6_fd >= 0) | |
307 | return m->mdns_ipv6_fd; | |
308 | ||
309 | m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); | |
310 | if (m->mdns_ipv6_fd < 0) | |
71a047d2 | 311 | return log_error_errno(errno, "mDNS-IPv6: Failed to create socket: %m"); |
bc7702b0 DM |
312 | |
313 | r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); | |
314 | if (r < 0) { | |
71a047d2 | 315 | r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_UNICAST_HOPS: %m"); |
bc7702b0 DM |
316 | goto fail; |
317 | } | |
318 | ||
319 | /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ | |
320 | r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); | |
321 | if (r < 0) { | |
71a047d2 | 322 | r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_MULTICAST_HOPS: %m"); |
bc7702b0 DM |
323 | goto fail; |
324 | } | |
325 | ||
326 | r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); | |
327 | if (r < 0) { | |
71a047d2 | 328 | r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_MULTICAST_LOOP: %m"); |
bc7702b0 DM |
329 | goto fail; |
330 | } | |
331 | ||
332 | r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); | |
333 | if (r < 0) { | |
71a047d2 | 334 | r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_V6ONLY: %m"); |
bc7702b0 DM |
335 | goto fail; |
336 | } | |
337 | ||
338 | r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); | |
339 | if (r < 0) { | |
71a047d2 | 340 | r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_RECVPKTINFO: %m"); |
bc7702b0 DM |
341 | goto fail; |
342 | } | |
343 | ||
344 | r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); | |
345 | if (r < 0) { | |
71a047d2 | 346 | r = log_error_errno(errno, "mDNS-IPv6: Failed to set IPV6_RECVHOPLIMIT: %m"); |
bc7702b0 DM |
347 | goto fail; |
348 | } | |
349 | ||
71a047d2 YW |
350 | /* See the section 15.1 of RFC6762 */ |
351 | /* first try to bind without SO_REUSEADDR to detect another mDNS responder */ | |
bc7702b0 DM |
352 | r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); |
353 | if (r < 0) { | |
71a047d2 YW |
354 | if (errno != EADDRINUSE) { |
355 | r = log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m"); | |
356 | goto fail; | |
357 | } | |
358 | ||
359 | log_warning("mDNS-IPv6: There appears to be another mDNS responder running, or previously systemd-resolved crashed with some outstanding transfers."); | |
360 | ||
361 | /* try again with SO_REUSEADDR */ | |
362 | r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | |
363 | if (r < 0) { | |
364 | r = log_error_errno(errno, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m"); | |
365 | goto fail; | |
366 | } | |
367 | ||
368 | r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); | |
369 | if (r < 0) { | |
370 | r = log_error_errno(errno, "mDNS-IPv6: Failed to bind socket: %m"); | |
371 | goto fail; | |
372 | } | |
373 | } else { | |
374 | /* enable SO_REUSEADDR for the case that the user really wants multiple mDNS responders */ | |
375 | r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | |
376 | if (r < 0) { | |
377 | r = log_error_errno(errno, "mDNS-IPv6: Failed to set SO_REUSEADDR: %m"); | |
378 | goto fail; | |
379 | } | |
bc7702b0 DM |
380 | } |
381 | ||
382 | r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); | |
ee8d9305 | 383 | if (r < 0) |
bc7702b0 | 384 | goto fail; |
bc7702b0 DM |
385 | |
386 | return m->mdns_ipv6_fd; | |
387 | ||
388 | fail: | |
389 | m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); | |
390 | return r; | |
391 | } |