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