]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-stub.c
resolved: add cache-flush bit to answers in mDNS announcements
[thirdparty/systemd.git] / src / resolve / resolved-dns-stub.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2016 Lennart Poettering
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 "fd-util.h"
21 #include "resolved-dns-stub.h"
22 #include "socket-util.h"
23
24 /* The MTU of the loopback device is 64K on Linux, advertise that as maximum datagram size, but subtract the Ethernet,
25 * IP and UDP header sizes */
26 #define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U)
27
28 static int manager_dns_stub_udp_fd(Manager *m);
29 static int manager_dns_stub_tcp_fd(Manager *m);
30
31 static int dns_stub_make_reply_packet(
32 uint16_t id,
33 int rcode,
34 DnsQuestion *q,
35 DnsAnswer *answer,
36 bool add_opt, /* add an OPT RR to this packet */
37 bool edns0_do, /* set the EDNS0 DNSSEC OK bit */
38 bool ad, /* set the DNSSEC authenticated data bit */
39 DnsPacket **ret) {
40
41 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
42 DnsResourceRecord *rr;
43 unsigned c = 0;
44 int r;
45
46 /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence
47 * roundtrips aren't expensive. */
48
49 r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
50 if (r < 0)
51 return r;
52
53 /* If the client didn't do EDNS, clamp the rcode to 4 bit */
54 if (!add_opt && rcode > 0xF)
55 rcode = DNS_RCODE_SERVFAIL;
56
57 DNS_PACKET_HEADER(p)->id = id;
58 DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
59 1 /* qr */,
60 0 /* opcode */,
61 0 /* aa */,
62 0 /* tc */,
63 1 /* rd */,
64 1 /* ra */,
65 ad /* ad */,
66 0 /* cd */,
67 rcode));
68
69 r = dns_packet_append_question(p, q);
70 if (r < 0)
71 return r;
72 DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q));
73
74 DNS_ANSWER_FOREACH(rr, answer) {
75 r = dns_question_matches_rr(q, rr, NULL);
76 if (r < 0)
77 return r;
78 if (r > 0)
79 goto add;
80
81 r = dns_question_matches_cname_or_dname(q, rr, NULL);
82 if (r < 0)
83 return r;
84 if (r > 0)
85 goto add;
86
87 continue;
88 add:
89 r = dns_packet_append_rr(p, rr, 0, NULL, NULL);
90 if (r < 0)
91 return r;
92
93 c++;
94 }
95 DNS_PACKET_HEADER(p)->ancount = htobe16(c);
96
97 if (add_opt) {
98 r = dns_packet_append_opt(p, ADVERTISE_DATAGRAM_SIZE_MAX, edns0_do, rcode, NULL);
99 if (r < 0)
100 return r;
101 }
102
103 *ret = p;
104 p = NULL;
105
106 return 0;
107 }
108
109 static void dns_stub_detach_stream(DnsStream *s) {
110 assert(s);
111
112 s->complete = NULL;
113 s->on_packet = NULL;
114 s->query = NULL;
115 }
116
117 static int dns_stub_send(Manager *m, DnsStream *s, DnsPacket *p, DnsPacket *reply) {
118 int r;
119
120 assert(m);
121 assert(p);
122 assert(reply);
123
124 if (s)
125 r = dns_stream_write_packet(s, reply);
126 else {
127 int fd;
128
129 /* Truncate the message to the right size */
130 if (reply->size > DNS_PACKET_PAYLOAD_SIZE_MAX(p)) {
131 dns_packet_truncate(reply, DNS_PACKET_UNICAST_SIZE_MAX);
132 DNS_PACKET_HEADER(reply)->flags = htobe16(be16toh(DNS_PACKET_HEADER(reply)->flags) | DNS_PACKET_FLAG_TC);
133 }
134
135 fd = manager_dns_stub_udp_fd(m);
136 if (fd < 0)
137 return log_debug_errno(fd, "Failed to get reply socket: %m");
138
139 /* Note that it is essential here that we explicitly choose the source IP address for this packet. This
140 * is because otherwise the kernel will choose it automatically based on the routing table and will
141 * thus pick 127.0.0.1 rather than 127.0.0.53. */
142
143 r = manager_send(m, fd, LOOPBACK_IFINDEX, p->family, &p->sender, p->sender_port, &p->destination, reply);
144 }
145 if (r < 0)
146 return log_debug_errno(r, "Failed to send reply packet: %m");
147
148 return 0;
149 }
150
151 static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode) {
152 _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
153 int r;
154
155 assert(m);
156 assert(p);
157
158 r = dns_stub_make_reply_packet(DNS_PACKET_ID(p), rcode, p->question, NULL, !!p->opt, DNS_PACKET_DO(p), false, &reply);
159 if (r < 0)
160 return log_debug_errno(r, "Failed to build failure packet: %m");
161
162 return dns_stub_send(m, s, p, reply);
163 }
164
165 static void dns_stub_query_complete(DnsQuery *q) {
166 int r;
167
168 assert(q);
169 assert(q->request_dns_packet);
170
171 switch (q->state) {
172
173 case DNS_TRANSACTION_SUCCESS: {
174 _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
175
176 r = dns_stub_make_reply_packet(
177 DNS_PACKET_ID(q->request_dns_packet),
178 q->answer_rcode,
179 q->question_idna,
180 q->answer,
181 !!q->request_dns_packet->opt,
182 DNS_PACKET_DO(q->request_dns_packet),
183 DNS_PACKET_DO(q->request_dns_packet) && q->answer_authenticated,
184 &reply);
185 if (r < 0) {
186 log_debug_errno(r, "Failed to build reply packet: %m");
187 break;
188 }
189
190 (void) dns_stub_send(q->manager, q->request_dns_stream, q->request_dns_packet, reply);
191 break;
192 }
193
194 case DNS_TRANSACTION_RCODE_FAILURE:
195 (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode);
196 break;
197
198 case DNS_TRANSACTION_NOT_FOUND:
199 (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN);
200 break;
201
202 case DNS_TRANSACTION_TIMEOUT:
203 case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED:
204 /* Propagate a timeout as a no packet, i.e. that the client also gets a timeout */
205 break;
206
207 case DNS_TRANSACTION_NO_SERVERS:
208 case DNS_TRANSACTION_INVALID_REPLY:
209 case DNS_TRANSACTION_ERRNO:
210 case DNS_TRANSACTION_ABORTED:
211 case DNS_TRANSACTION_DNSSEC_FAILED:
212 case DNS_TRANSACTION_NO_TRUST_ANCHOR:
213 case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED:
214 case DNS_TRANSACTION_NETWORK_DOWN:
215 (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL);
216 break;
217
218 case DNS_TRANSACTION_NULL:
219 case DNS_TRANSACTION_PENDING:
220 case DNS_TRANSACTION_VALIDATING:
221 default:
222 assert_not_reached("Impossible state");
223 }
224
225 /* If there's a packet to write set, let's leave the stream around */
226 if (q->request_dns_stream && DNS_STREAM_QUEUED(q->request_dns_stream)) {
227
228 /* Detach the stream from our query (make it an orphan), but do not drop the reference to it. The
229 * default completion action of the stream will drop the reference. */
230
231 dns_stub_detach_stream(q->request_dns_stream);
232 q->request_dns_stream = NULL;
233 }
234
235 dns_query_free(q);
236 }
237
238 static int dns_stub_stream_complete(DnsStream *s, int error) {
239 assert(s);
240
241 log_debug_errno(error, "DNS TCP connection terminated, destroying query: %m");
242
243 assert(s->query);
244 dns_query_free(s->query);
245
246 return 0;
247 }
248
249 static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) {
250 DnsQuery *q = NULL;
251 int r;
252
253 assert(m);
254 assert(p);
255 assert(p->protocol == DNS_PROTOCOL_DNS);
256
257 /* Takes ownership of the *s stream object */
258
259 if (in_addr_is_localhost(p->family, &p->sender) <= 0 ||
260 in_addr_is_localhost(p->family, &p->destination) <= 0) {
261 log_error("Got packet on unexpected IP range, refusing.");
262 dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
263 goto fail;
264 }
265
266 r = dns_packet_extract(p);
267 if (r < 0) {
268 log_debug_errno(r, "Failed to extract resources from incoming packet, ignoring packet: %m");
269 dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR);
270 goto fail;
271 }
272
273 if (!DNS_PACKET_VERSION_SUPPORTED(p)) {
274 log_debug("Got EDNS OPT field with unsupported version number.");
275 dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS);
276 goto fail;
277 }
278
279 if (dns_type_is_obsolete(p->question->keys[0]->type)) {
280 log_debug("Got message with obsolete key type, refusing.");
281 dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
282 goto fail;
283 }
284
285 if (dns_type_is_zone_transer(p->question->keys[0]->type)) {
286 log_debug("Got request for zone transfer, refusing.");
287 dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
288 goto fail;
289 }
290
291 if (!DNS_PACKET_RD(p)) {
292 /* If the "rd" bit is off (i.e. recursion was not requested), then refuse operation */
293 log_debug("Got request with recursion disabled, refusing.");
294 dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED);
295 goto fail;
296 }
297
298 if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) {
299 log_debug("Got request with DNSSEC CD bit set, refusing.");
300 dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
301 goto fail;
302 }
303
304 r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_NO_CNAME);
305 if (r < 0) {
306 log_error_errno(r, "Failed to generate query object: %m");
307 dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
308 goto fail;
309 }
310
311 /* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */
312 q->clamp_ttl = true;
313
314 q->request_dns_packet = dns_packet_ref(p);
315 q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */
316 q->complete = dns_stub_query_complete;
317
318 if (s) {
319 s->on_packet = NULL;
320 s->complete = dns_stub_stream_complete;
321 s->query = q;
322 }
323
324 r = dns_query_go(q);
325 if (r < 0) {
326 log_error_errno(r, "Failed to start query: %m");
327 dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
328 goto fail;
329 }
330
331 log_info("Processing query...");
332 return;
333
334 fail:
335 if (s && DNS_STREAM_QUEUED(s))
336 dns_stub_detach_stream(s);
337
338 dns_query_free(q);
339 }
340
341 static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
342 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
343 Manager *m = userdata;
344 int r;
345
346 r = manager_recv(m, fd, DNS_PROTOCOL_DNS, &p);
347 if (r <= 0)
348 return r;
349
350 if (dns_packet_validate_query(p) > 0) {
351 log_debug("Got DNS stub UDP query packet for id %u", DNS_PACKET_ID(p));
352
353 dns_stub_process_query(m, NULL, p);
354 } else
355 log_debug("Invalid DNS stub UDP packet, ignoring.");
356
357 return 0;
358 }
359
360 static int manager_dns_stub_udp_fd(Manager *m) {
361 static const int one = 1;
362 union sockaddr_union sa = {
363 .in.sin_family = AF_INET,
364 .in.sin_port = htobe16(53),
365 .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB),
366 };
367 _cleanup_close_ int fd = -1;
368 int r;
369
370 if (m->dns_stub_udp_fd >= 0)
371 return m->dns_stub_udp_fd;
372
373 fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
374 if (fd < 0)
375 return -errno;
376
377 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one) < 0)
378 return -errno;
379
380 if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof one) < 0)
381 return -errno;
382
383 if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof one) < 0)
384 return -errno;
385
386 /* Make sure no traffic from outside the local host can leak to onto this socket */
387 if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3) < 0)
388 return -errno;
389
390 if (bind(fd, &sa.sa, sizeof(sa.in)) < 0)
391 return -errno;
392
393 r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, fd, EPOLLIN, on_dns_stub_packet, m);
394 if (r < 0)
395 return r;
396
397 (void) sd_event_source_set_description(m->dns_stub_udp_event_source, "dns-stub-udp");
398 m->dns_stub_udp_fd = fd;
399 fd = -1;
400
401 return m->dns_stub_udp_fd;
402 }
403
404 static int on_dns_stub_stream_packet(DnsStream *s) {
405 assert(s);
406 assert(s->read_packet);
407
408 if (dns_packet_validate_query(s->read_packet) > 0) {
409 log_debug("Got DNS stub TCP query packet for id %u", DNS_PACKET_ID(s->read_packet));
410
411 dns_stub_process_query(s->manager, s, s->read_packet);
412 } else
413 log_debug("Invalid DNS stub TCP packet, ignoring.");
414
415 /* Drop the reference to the stream. Either a query was created and added its own reference to the stream now,
416 * or that didn't happen in which case we want to free the stream */
417 dns_stream_unref(s);
418
419 return 0;
420 }
421
422 static int on_dns_stub_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
423 DnsStream *stream;
424 Manager *m = userdata;
425 int cfd, r;
426
427 cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
428 if (cfd < 0) {
429 if (errno == EAGAIN || errno == EINTR)
430 return 0;
431
432 return -errno;
433 }
434
435 r = dns_stream_new(m, &stream, DNS_PROTOCOL_DNS, cfd);
436 if (r < 0) {
437 safe_close(cfd);
438 return r;
439 }
440
441 stream->on_packet = on_dns_stub_stream_packet;
442
443 /* We let the reference to the stream dangling here, it will either be dropped by the default "complete" action
444 * of the stream, or by our packet callback, or when the manager is shut down. */
445
446 return 0;
447 }
448
449 static int manager_dns_stub_tcp_fd(Manager *m) {
450 static const int one = 1;
451 union sockaddr_union sa = {
452 .in.sin_family = AF_INET,
453 .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB),
454 .in.sin_port = htobe16(53),
455 };
456 _cleanup_close_ int fd = -1;
457 int r;
458
459 if (m->dns_stub_tcp_fd >= 0)
460 return m->dns_stub_tcp_fd;
461
462 fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
463 if (fd < 0)
464 return -errno;
465
466 if (setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof one) < 0)
467 return -errno;
468
469 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one) < 0)
470 return -errno;
471
472 if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof one) < 0)
473 return -errno;
474
475 if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof one) < 0)
476 return -errno;
477
478 /* Make sure no traffic from outside the local host can leak to onto this socket */
479 if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3) < 0)
480 return -errno;
481
482 if (bind(fd, &sa.sa, sizeof(sa.in)) < 0)
483 return -errno;
484
485 if (listen(fd, SOMAXCONN) < 0)
486 return -errno;
487
488 r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, fd, EPOLLIN, on_dns_stub_stream, m);
489 if (r < 0)
490 return r;
491
492 (void) sd_event_source_set_description(m->dns_stub_tcp_event_source, "dns-stub-tcp");
493 m->dns_stub_tcp_fd = fd;
494 fd = -1;
495
496 return m->dns_stub_tcp_fd;
497 }
498
499 int manager_dns_stub_start(Manager *m) {
500 const char *t = "UDP";
501 int r = 0;
502
503 assert(m);
504
505 if (IN_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_YES, DNS_STUB_LISTENER_UDP))
506 r = manager_dns_stub_udp_fd(m);
507
508 if (r >= 0 &&
509 IN_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_YES, DNS_STUB_LISTENER_TCP)) {
510 t = "TCP";
511 r = manager_dns_stub_tcp_fd(m);
512 }
513
514 if (IN_SET(r, -EADDRINUSE, -EPERM)) {
515 if (r == -EADDRINUSE)
516 log_warning_errno(r,
517 "Another process is already listening on %s socket 127.0.0.53:53.\n"
518 "Turning off local DNS stub support.", t);
519 else
520 log_warning_errno(r,
521 "Failed to listen on %s socket 127.0.0.53:53: %m.\n"
522 "Turning off local DNS stub support.", t);
523 manager_dns_stub_stop(m);
524 } else if (r < 0)
525 return log_error_errno(r, "Failed to listen on %s socket 127.0.0.53:53: %m", t);
526
527 return 0;
528 }
529
530 void manager_dns_stub_stop(Manager *m) {
531 assert(m);
532
533 m->dns_stub_udp_event_source = sd_event_source_unref(m->dns_stub_udp_event_source);
534 m->dns_stub_tcp_event_source = sd_event_source_unref(m->dns_stub_tcp_event_source);
535
536 m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd);
537 m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd);
538 }