]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ | |
2 | ||
3 | #include <net/if_arp.h> | |
4 | ||
5 | #include "errno-util.h" | |
6 | #include "fd-util.h" | |
7 | #include "missing_network.h" | |
8 | #include "missing_socket.h" | |
9 | #include "resolved-dns-stub.h" | |
10 | #include "socket-netlink.h" | |
11 | #include "socket-util.h" | |
12 | #include "string-table.h" | |
13 | ||
14 | /* The MTU of the loopback device is 64K on Linux, advertise that as maximum datagram size, but subtract the Ethernet, | |
15 | * IP and UDP header sizes */ | |
16 | #define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U) | |
17 | ||
18 | static int manager_dns_stub_udp_fd_extra(Manager *m, DnsStubListenerExtra *l); | |
19 | ||
20 | static void dns_stub_listener_extra_hash_func(const DnsStubListenerExtra *a, struct siphash *state) { | |
21 | assert(a); | |
22 | ||
23 | siphash24_compress(&a->mode, sizeof(a->mode), state); | |
24 | siphash24_compress(&a->family, sizeof(a->family), state); | |
25 | siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); | |
26 | siphash24_compress(&a->port, sizeof(a->port), state); | |
27 | } | |
28 | ||
29 | static int dns_stub_listener_extra_compare_func(const DnsStubListenerExtra *a, const DnsStubListenerExtra *b) { | |
30 | int r; | |
31 | ||
32 | assert(a); | |
33 | assert(b); | |
34 | ||
35 | r = CMP(a->mode, b->mode); | |
36 | if (r != 0) | |
37 | return r; | |
38 | ||
39 | r = CMP(a->family, b->family); | |
40 | if (r != 0) | |
41 | return r; | |
42 | ||
43 | r = memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); | |
44 | if (r != 0) | |
45 | return r; | |
46 | ||
47 | return CMP(a->port, b->port); | |
48 | } | |
49 | ||
50 | DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( | |
51 | dns_stub_listener_extra_hash_ops, | |
52 | DnsStubListenerExtra, | |
53 | dns_stub_listener_extra_hash_func, | |
54 | dns_stub_listener_extra_compare_func, | |
55 | dns_stub_listener_extra_free); | |
56 | ||
57 | int dns_stub_listener_extra_new( | |
58 | Manager *m, | |
59 | DnsStubListenerExtra **ret) { | |
60 | ||
61 | DnsStubListenerExtra *l; | |
62 | ||
63 | l = new(DnsStubListenerExtra, 1); | |
64 | if (!l) | |
65 | return -ENOMEM; | |
66 | ||
67 | *l = (DnsStubListenerExtra) { | |
68 | .manager = m, | |
69 | }; | |
70 | ||
71 | *ret = TAKE_PTR(l); | |
72 | return 0; | |
73 | } | |
74 | ||
75 | DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) { | |
76 | if (!p) | |
77 | return NULL; | |
78 | ||
79 | p->udp_event_source = sd_event_source_unref(p->udp_event_source); | |
80 | p->tcp_event_source = sd_event_source_unref(p->tcp_event_source); | |
81 | ||
82 | return mfree(p); | |
83 | } | |
84 | ||
85 | static int dns_stub_make_reply_packet( | |
86 | DnsPacket **p, | |
87 | size_t max_size, | |
88 | DnsQuestion *q, | |
89 | DnsAnswer *answer, | |
90 | bool *ret_truncated) { | |
91 | ||
92 | bool truncated = false; | |
93 | DnsResourceRecord *rr; | |
94 | unsigned c = 0; | |
95 | int r; | |
96 | ||
97 | assert(p); | |
98 | ||
99 | /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence | |
100 | * roundtrips aren't expensive. */ | |
101 | ||
102 | if (!*p) { | |
103 | r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0, max_size); | |
104 | if (r < 0) | |
105 | return r; | |
106 | ||
107 | r = dns_packet_append_question(*p, q); | |
108 | if (r < 0) | |
109 | return r; | |
110 | ||
111 | DNS_PACKET_HEADER(*p)->qdcount = htobe16(dns_question_size(q)); | |
112 | } | |
113 | ||
114 | DNS_ANSWER_FOREACH(rr, answer) { | |
115 | ||
116 | r = dns_question_matches_rr(q, rr, NULL); | |
117 | if (r < 0) | |
118 | return r; | |
119 | if (r > 0) | |
120 | goto add; | |
121 | ||
122 | r = dns_question_matches_cname_or_dname(q, rr, NULL); | |
123 | if (r < 0) | |
124 | return r; | |
125 | if (r > 0) | |
126 | goto add; | |
127 | ||
128 | continue; | |
129 | add: | |
130 | r = dns_packet_append_rr(*p, rr, 0, NULL, NULL); | |
131 | if (r == -EMSGSIZE) { | |
132 | truncated = true; | |
133 | break; | |
134 | } | |
135 | if (r < 0) | |
136 | return r; | |
137 | ||
138 | c++; | |
139 | } | |
140 | ||
141 | if (ret_truncated) | |
142 | *ret_truncated = truncated; | |
143 | else if (truncated) | |
144 | return -EMSGSIZE; | |
145 | ||
146 | DNS_PACKET_HEADER(*p)->ancount = htobe16(be16toh(DNS_PACKET_HEADER(*p)->ancount) + c); | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
151 | static int dns_stub_finish_reply_packet( | |
152 | DnsPacket *p, | |
153 | uint16_t id, | |
154 | int rcode, | |
155 | bool tc, /* set the Truncated bit? */ | |
156 | bool add_opt, /* add an OPT RR to this packet? */ | |
157 | bool edns0_do, /* set the EDNS0 DNSSEC OK bit? */ | |
158 | bool ad) { /* set the DNSSEC authenticated data bit? */ | |
159 | ||
160 | int r; | |
161 | ||
162 | assert(p); | |
163 | ||
164 | if (add_opt) { | |
165 | r = dns_packet_append_opt(p, ADVERTISE_DATAGRAM_SIZE_MAX, edns0_do, rcode, NULL); | |
166 | if (r == -EMSGSIZE) /* Hit the size limit? then indicate truncation */ | |
167 | tc = true; | |
168 | else if (r < 0) | |
169 | return r; | |
170 | ||
171 | } else { | |
172 | /* If the client can't to EDNS0, don't do DO either */ | |
173 | edns0_do = false; | |
174 | ||
175 | /* If the client didn't do EDNS, clamp the rcode to 4 bit */ | |
176 | if (rcode > 0xF) | |
177 | rcode = DNS_RCODE_SERVFAIL; | |
178 | } | |
179 | ||
180 | /* Don't set the AD bit unless DO is on, too */ | |
181 | if (!edns0_do) | |
182 | ad = false; | |
183 | ||
184 | DNS_PACKET_HEADER(p)->id = id; | |
185 | ||
186 | DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( | |
187 | 1 /* qr */, | |
188 | 0 /* opcode */, | |
189 | 0 /* aa */, | |
190 | tc /* tc */, | |
191 | 1 /* rd */, | |
192 | 1 /* ra */, | |
193 | ad /* ad */, | |
194 | 0 /* cd */, | |
195 | rcode)); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | static int dns_stub_send( | |
201 | Manager *m, | |
202 | DnsStubListenerExtra *l, | |
203 | DnsStream *s, | |
204 | DnsPacket *p, | |
205 | DnsPacket *reply) { | |
206 | ||
207 | int r; | |
208 | ||
209 | assert(m); | |
210 | assert(p); | |
211 | assert(reply); | |
212 | ||
213 | if (s) | |
214 | r = dns_stream_write_packet(s, reply); | |
215 | else | |
216 | /* Note that it is essential here that we explicitly choose the source IP address for this packet. This | |
217 | * is because otherwise the kernel will choose it automatically based on the routing table and will | |
218 | * thus pick 127.0.0.1 rather than 127.0.0.53. */ | |
219 | r = manager_send(m, | |
220 | manager_dns_stub_udp_fd_extra(m, l), | |
221 | l ? p->ifindex : LOOPBACK_IFINDEX, /* force loopback iface if this is the main listener stub */ | |
222 | p->family, &p->sender, p->sender_port, &p->destination, | |
223 | reply); | |
224 | if (r < 0) | |
225 | return log_debug_errno(r, "Failed to send reply packet: %m"); | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | static int dns_stub_send_failure( | |
231 | Manager *m, | |
232 | DnsStubListenerExtra *l, | |
233 | DnsStream *s, | |
234 | DnsPacket *p, | |
235 | int rcode, | |
236 | bool authenticated) { | |
237 | ||
238 | _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; | |
239 | int r; | |
240 | ||
241 | assert(m); | |
242 | assert(p); | |
243 | ||
244 | r = dns_stub_make_reply_packet(&reply, DNS_PACKET_PAYLOAD_SIZE_MAX(p), p->question, NULL, NULL); | |
245 | if (r < 0) | |
246 | return log_debug_errno(r, "Failed to make failure packet: %m"); | |
247 | ||
248 | r = dns_stub_finish_reply_packet(reply, DNS_PACKET_ID(p), rcode, false, !!p->opt, DNS_PACKET_DO(p), authenticated); | |
249 | if (r < 0) | |
250 | return log_debug_errno(r, "Failed to build failure packet: %m"); | |
251 | ||
252 | return dns_stub_send(m, l, s, p, reply); | |
253 | } | |
254 | ||
255 | static void dns_stub_query_complete(DnsQuery *q) { | |
256 | int r; | |
257 | ||
258 | assert(q); | |
259 | assert(q->request_dns_packet); | |
260 | ||
261 | switch (q->state) { | |
262 | ||
263 | case DNS_TRANSACTION_SUCCESS: { | |
264 | bool truncated; | |
265 | ||
266 | r = dns_stub_make_reply_packet(&q->reply_dns_packet, DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_dns_packet), q->question_idna, q->answer, &truncated); | |
267 | if (r < 0) { | |
268 | log_debug_errno(r, "Failed to build reply packet: %m"); | |
269 | break; | |
270 | } | |
271 | ||
272 | if (!truncated) { | |
273 | r = dns_query_process_cname(q); | |
274 | if (r == -ELOOP) { | |
275 | (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); | |
276 | break; | |
277 | } | |
278 | if (r < 0) { | |
279 | log_debug_errno(r, "Failed to process CNAME: %m"); | |
280 | break; | |
281 | } | |
282 | if (r == DNS_QUERY_RESTARTED) | |
283 | return; | |
284 | } | |
285 | ||
286 | r = dns_stub_finish_reply_packet( | |
287 | q->reply_dns_packet, | |
288 | DNS_PACKET_ID(q->request_dns_packet), | |
289 | q->answer_rcode, | |
290 | truncated, | |
291 | !!q->request_dns_packet->opt, | |
292 | DNS_PACKET_DO(q->request_dns_packet), | |
293 | dns_query_fully_authenticated(q)); | |
294 | if (r < 0) { | |
295 | log_debug_errno(r, "Failed to finish reply packet: %m"); | |
296 | break; | |
297 | } | |
298 | ||
299 | (void) dns_stub_send(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->reply_dns_packet); | |
300 | break; | |
301 | } | |
302 | ||
303 | case DNS_TRANSACTION_RCODE_FAILURE: | |
304 | (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->answer_rcode, dns_query_fully_authenticated(q)); | |
305 | break; | |
306 | ||
307 | case DNS_TRANSACTION_NOT_FOUND: | |
308 | (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN, dns_query_fully_authenticated(q)); | |
309 | break; | |
310 | ||
311 | case DNS_TRANSACTION_TIMEOUT: | |
312 | case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: | |
313 | /* Propagate a timeout as a no packet, i.e. that the client also gets a timeout */ | |
314 | break; | |
315 | ||
316 | case DNS_TRANSACTION_NO_SERVERS: | |
317 | case DNS_TRANSACTION_INVALID_REPLY: | |
318 | case DNS_TRANSACTION_ERRNO: | |
319 | case DNS_TRANSACTION_ABORTED: | |
320 | case DNS_TRANSACTION_DNSSEC_FAILED: | |
321 | case DNS_TRANSACTION_NO_TRUST_ANCHOR: | |
322 | case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: | |
323 | case DNS_TRANSACTION_NETWORK_DOWN: | |
324 | (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); | |
325 | break; | |
326 | ||
327 | case DNS_TRANSACTION_NULL: | |
328 | case DNS_TRANSACTION_PENDING: | |
329 | case DNS_TRANSACTION_VALIDATING: | |
330 | default: | |
331 | assert_not_reached("Impossible state"); | |
332 | } | |
333 | ||
334 | dns_query_free(q); | |
335 | } | |
336 | ||
337 | static int dns_stub_stream_complete(DnsStream *s, int error) { | |
338 | assert(s); | |
339 | ||
340 | log_debug_errno(error, "DNS TCP connection terminated, destroying queries: %m"); | |
341 | ||
342 | for (;;) { | |
343 | DnsQuery *q; | |
344 | ||
345 | q = set_first(s->queries); | |
346 | if (!q) | |
347 | break; | |
348 | ||
349 | dns_query_free(q); | |
350 | } | |
351 | ||
352 | /* This drops the implicit ref we keep around since it was allocated, as incoming stub connections | |
353 | * should be kept as long as the client wants to. */ | |
354 | dns_stream_unref(s); | |
355 | return 0; | |
356 | } | |
357 | ||
358 | static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStream *s, DnsPacket *p) { | |
359 | _cleanup_(dns_query_freep) DnsQuery *q = NULL; | |
360 | int r; | |
361 | ||
362 | assert(m); | |
363 | assert(p); | |
364 | assert(p->protocol == DNS_PROTOCOL_DNS); | |
365 | ||
366 | if (!l && /* l == NULL if this is the main stub */ | |
367 | (in_addr_is_localhost(p->family, &p->sender) <= 0 || | |
368 | in_addr_is_localhost(p->family, &p->destination) <= 0)) { | |
369 | log_error("Got packet on unexpected IP range, refusing."); | |
370 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); | |
371 | return; | |
372 | } | |
373 | ||
374 | r = dns_packet_extract(p); | |
375 | if (r < 0) { | |
376 | log_debug_errno(r, "Failed to extract resources from incoming packet, ignoring packet: %m"); | |
377 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_FORMERR, false); | |
378 | return; | |
379 | } | |
380 | ||
381 | if (!DNS_PACKET_VERSION_SUPPORTED(p)) { | |
382 | log_debug("Got EDNS OPT field with unsupported version number."); | |
383 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_BADVERS, false); | |
384 | return; | |
385 | } | |
386 | ||
387 | if (dns_type_is_obsolete(p->question->keys[0]->type)) { | |
388 | log_debug("Got message with obsolete key type, refusing."); | |
389 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); | |
390 | return; | |
391 | } | |
392 | ||
393 | if (dns_type_is_zone_transer(p->question->keys[0]->type)) { | |
394 | log_debug("Got request for zone transfer, refusing."); | |
395 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); | |
396 | return; | |
397 | } | |
398 | ||
399 | if (!DNS_PACKET_RD(p)) { | |
400 | /* If the "rd" bit is off (i.e. recursion was not requested), then refuse operation */ | |
401 | log_debug("Got request with recursion disabled, refusing."); | |
402 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false); | |
403 | return; | |
404 | } | |
405 | ||
406 | if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) { | |
407 | log_debug("Got request with DNSSEC CD bit set, refusing."); | |
408 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); | |
409 | return; | |
410 | } | |
411 | ||
412 | r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH); | |
413 | if (r < 0) { | |
414 | log_error_errno(r, "Failed to generate query object: %m"); | |
415 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); | |
416 | return; | |
417 | } | |
418 | ||
419 | /* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */ | |
420 | q->clamp_ttl = true; | |
421 | ||
422 | q->request_dns_packet = dns_packet_ref(p); | |
423 | q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */ | |
424 | q->stub_listener_extra = l; | |
425 | q->complete = dns_stub_query_complete; | |
426 | ||
427 | if (s) { | |
428 | /* Remember which queries belong to this stream, so that we can cancel them when the stream | |
429 | * is disconnected early */ | |
430 | ||
431 | r = set_ensure_put(&s->queries, NULL, q); | |
432 | if (r < 0) { | |
433 | log_oom(); | |
434 | return; | |
435 | } | |
436 | assert(r > 0); | |
437 | } | |
438 | ||
439 | r = dns_query_go(q); | |
440 | if (r < 0) { | |
441 | log_error_errno(r, "Failed to start query: %m"); | |
442 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); | |
443 | return; | |
444 | } | |
445 | ||
446 | log_debug("Processing query..."); | |
447 | TAKE_PTR(q); | |
448 | } | |
449 | ||
450 | static int on_dns_stub_packet_internal(sd_event_source *s, int fd, uint32_t revents, Manager *m, DnsStubListenerExtra *l) { | |
451 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; | |
452 | int r; | |
453 | ||
454 | r = manager_recv(m, fd, DNS_PROTOCOL_DNS, &p); | |
455 | if (r <= 0) | |
456 | return r; | |
457 | ||
458 | if (dns_packet_validate_query(p) > 0) { | |
459 | log_debug("Got DNS stub UDP query packet for id %u", DNS_PACKET_ID(p)); | |
460 | ||
461 | dns_stub_process_query(m, l, NULL, p); | |
462 | } else | |
463 | log_debug("Invalid DNS stub UDP packet, ignoring."); | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
468 | static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
469 | return on_dns_stub_packet_internal(s, fd, revents, userdata, NULL); | |
470 | } | |
471 | ||
472 | static int on_dns_stub_packet_extra(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
473 | DnsStubListenerExtra *l = userdata; | |
474 | ||
475 | assert(l); | |
476 | ||
477 | return on_dns_stub_packet_internal(s, fd, revents, l->manager, l); | |
478 | } | |
479 | ||
480 | static int on_dns_stub_stream_packet(DnsStream *s) { | |
481 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; | |
482 | ||
483 | assert(s); | |
484 | ||
485 | p = dns_stream_take_read_packet(s); | |
486 | assert(p); | |
487 | ||
488 | if (dns_packet_validate_query(p) > 0) { | |
489 | log_debug("Got DNS stub TCP query packet for id %u", DNS_PACKET_ID(p)); | |
490 | ||
491 | dns_stub_process_query(s->manager, s->stub_listener_extra, s, p); | |
492 | } else | |
493 | log_debug("Invalid DNS stub TCP packet, ignoring."); | |
494 | ||
495 | return 0; | |
496 | } | |
497 | ||
498 | static int on_dns_stub_stream_internal(sd_event_source *s, int fd, uint32_t revents, Manager *m, DnsStubListenerExtra *l) { | |
499 | DnsStream *stream; | |
500 | int cfd, r; | |
501 | ||
502 | cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); | |
503 | if (cfd < 0) { | |
504 | if (ERRNO_IS_ACCEPT_AGAIN(errno)) | |
505 | return 0; | |
506 | ||
507 | return -errno; | |
508 | } | |
509 | ||
510 | r = dns_stream_new(m, &stream, DNS_STREAM_STUB, DNS_PROTOCOL_DNS, cfd, NULL); | |
511 | if (r < 0) { | |
512 | safe_close(cfd); | |
513 | return r; | |
514 | } | |
515 | ||
516 | stream->stub_listener_extra = l; | |
517 | stream->on_packet = on_dns_stub_stream_packet; | |
518 | stream->complete = dns_stub_stream_complete; | |
519 | ||
520 | /* We let the reference to the stream dangle here, it will be dropped later by the complete callback. */ | |
521 | ||
522 | return 0; | |
523 | } | |
524 | ||
525 | static int on_dns_stub_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
526 | return on_dns_stub_stream_internal(s, fd, revents, userdata, NULL); | |
527 | } | |
528 | ||
529 | static int on_dns_stub_stream_extra(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
530 | DnsStubListenerExtra *l = userdata; | |
531 | ||
532 | assert(l); | |
533 | return on_dns_stub_stream_internal(s, fd, revents, l->manager, l); | |
534 | } | |
535 | ||
536 | static int set_dns_stub_common_socket_options(int fd, int family) { | |
537 | int r; | |
538 | ||
539 | assert(fd >= 0); | |
540 | assert(IN_SET(family, AF_INET, AF_INET6)); | |
541 | ||
542 | r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); | |
543 | if (r < 0) | |
544 | return r; | |
545 | ||
546 | if (family == AF_INET) { | |
547 | r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); | |
548 | if (r < 0) | |
549 | return r; | |
550 | ||
551 | r = setsockopt_int(fd, IPPROTO_IP, IP_RECVTTL, true); | |
552 | if (r < 0) | |
553 | return r; | |
554 | } else { | |
555 | r = setsockopt_int(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, true); | |
556 | if (r < 0) | |
557 | return r; | |
558 | ||
559 | r = setsockopt_int(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, true); | |
560 | if (r < 0) | |
561 | return r; | |
562 | } | |
563 | ||
564 | return 0; | |
565 | } | |
566 | ||
567 | static int manager_dns_stub_udp_fd(Manager *m) { | |
568 | union sockaddr_union sa = { | |
569 | .in.sin_family = AF_INET, | |
570 | .in.sin_port = htobe16(53), | |
571 | .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB), | |
572 | }; | |
573 | _cleanup_close_ int fd = -1; | |
574 | int r; | |
575 | ||
576 | if (m->dns_stub_udp_event_source) | |
577 | return sd_event_source_get_io_fd(m->dns_stub_udp_event_source); | |
578 | ||
579 | fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); | |
580 | if (fd < 0) | |
581 | return -errno; | |
582 | ||
583 | r = set_dns_stub_common_socket_options(fd, AF_INET); | |
584 | if (r < 0) | |
585 | return r; | |
586 | ||
587 | /* Make sure no traffic from outside the local host can leak to onto this socket */ | |
588 | r = socket_bind_to_ifindex(fd, LOOPBACK_IFINDEX); | |
589 | if (r < 0) | |
590 | return r; | |
591 | ||
592 | if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) | |
593 | return -errno; | |
594 | ||
595 | r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, fd, EPOLLIN, on_dns_stub_packet, m); | |
596 | if (r < 0) | |
597 | return r; | |
598 | ||
599 | r = sd_event_source_set_io_fd_own(m->dns_stub_udp_event_source, true); | |
600 | if (r < 0) | |
601 | return r; | |
602 | ||
603 | (void) sd_event_source_set_description(m->dns_stub_udp_event_source, "dns-stub-udp"); | |
604 | ||
605 | return TAKE_FD(fd); | |
606 | } | |
607 | ||
608 | static int manager_dns_stub_udp_fd_extra(Manager *m, DnsStubListenerExtra *l) { | |
609 | _cleanup_free_ char *pretty = NULL; | |
610 | _cleanup_close_ int fd = -1; | |
611 | union sockaddr_union sa; | |
612 | int r; | |
613 | ||
614 | assert(m); | |
615 | ||
616 | if (!l) | |
617 | return manager_dns_stub_udp_fd(m); | |
618 | ||
619 | if (l->udp_event_source) | |
620 | return 0; | |
621 | ||
622 | if (l->family == AF_INET) | |
623 | sa = (union sockaddr_union) { | |
624 | .in.sin_family = l->family, | |
625 | .in.sin_port = htobe16(l->port != 0 ? l->port : 53U), | |
626 | .in.sin_addr = l->address.in, | |
627 | }; | |
628 | else | |
629 | sa = (union sockaddr_union) { | |
630 | .in6.sin6_family = l->family, | |
631 | .in6.sin6_port = htobe16(l->port != 0 ? l->port : 53U), | |
632 | .in6.sin6_addr = l->address.in6, | |
633 | }; | |
634 | ||
635 | fd = socket(l->family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); | |
636 | if (fd < 0) { | |
637 | r = -errno; | |
638 | goto fail; | |
639 | } | |
640 | ||
641 | if (l->family == AF_INET) { | |
642 | r = setsockopt_int(fd, IPPROTO_IP, IP_FREEBIND, true); | |
643 | if (r < 0) | |
644 | goto fail; | |
645 | } | |
646 | ||
647 | r = set_dns_stub_common_socket_options(fd, l->family); | |
648 | if (r < 0) | |
649 | goto fail; | |
650 | ||
651 | if (bind(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0) { | |
652 | r = -errno; | |
653 | goto fail; | |
654 | } | |
655 | ||
656 | r = sd_event_add_io(m->event, &l->udp_event_source, fd, EPOLLIN, on_dns_stub_packet_extra, l); | |
657 | if (r < 0) | |
658 | goto fail; | |
659 | ||
660 | r = sd_event_source_set_io_fd_own(l->udp_event_source, true); | |
661 | if (r < 0) | |
662 | goto fail; | |
663 | ||
664 | (void) sd_event_source_set_description(l->udp_event_source, "dns-stub-udp-extra"); | |
665 | ||
666 | if (DEBUG_LOGGING) { | |
667 | (void) in_addr_port_to_string(l->family, &l->address, l->port, &pretty); | |
668 | log_debug("Listening on UDP socket %s.", strnull(pretty)); | |
669 | } | |
670 | ||
671 | return TAKE_FD(fd); | |
672 | ||
673 | fail: | |
674 | assert(r < 0); | |
675 | (void) in_addr_port_to_string(l->family, &l->address, l->port, &pretty); | |
676 | if (r == -EADDRINUSE) | |
677 | return log_warning_errno(r, "Another process is already listening on UDP socket %s: %m", strnull(pretty)); | |
678 | return log_warning_errno(r, "Failed to listen on UDP socket %s: %m", strnull(pretty)); | |
679 | } | |
680 | ||
681 | static int manager_dns_stub_tcp_fd(Manager *m) { | |
682 | union sockaddr_union sa = { | |
683 | .in.sin_family = AF_INET, | |
684 | .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB), | |
685 | .in.sin_port = htobe16(53), | |
686 | }; | |
687 | _cleanup_close_ int fd = -1; | |
688 | int r; | |
689 | ||
690 | if (m->dns_stub_tcp_event_source) | |
691 | return sd_event_source_get_io_fd(m->dns_stub_tcp_event_source); | |
692 | ||
693 | fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); | |
694 | if (fd < 0) | |
695 | return -errno; | |
696 | ||
697 | r = set_dns_stub_common_socket_options(fd, AF_INET); | |
698 | if (r < 0) | |
699 | return r; | |
700 | ||
701 | r = setsockopt_int(fd, IPPROTO_IP, IP_TTL, 1); | |
702 | if (r < 0) | |
703 | return r; | |
704 | ||
705 | /* Make sure no traffic from outside the local host can leak to onto this socket */ | |
706 | r = socket_bind_to_ifindex(fd, LOOPBACK_IFINDEX); | |
707 | if (r < 0) | |
708 | return r; | |
709 | ||
710 | if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) | |
711 | return -errno; | |
712 | ||
713 | if (listen(fd, SOMAXCONN) < 0) | |
714 | return -errno; | |
715 | ||
716 | r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, fd, EPOLLIN, on_dns_stub_stream, m); | |
717 | if (r < 0) | |
718 | return r; | |
719 | ||
720 | r = sd_event_source_set_io_fd_own(m->dns_stub_tcp_event_source, true); | |
721 | if (r < 0) | |
722 | return r; | |
723 | ||
724 | (void) sd_event_source_set_description(m->dns_stub_tcp_event_source, "dns-stub-tcp"); | |
725 | ||
726 | return TAKE_FD(fd); | |
727 | } | |
728 | ||
729 | static int manager_dns_stub_tcp_fd_extra(Manager *m, DnsStubListenerExtra *l) { | |
730 | _cleanup_free_ char *pretty = NULL; | |
731 | _cleanup_close_ int fd = -1; | |
732 | union sockaddr_union sa; | |
733 | int r; | |
734 | ||
735 | if (l->tcp_event_source) | |
736 | return sd_event_source_get_io_fd(l->tcp_event_source);; | |
737 | ||
738 | if (l->family == AF_INET) | |
739 | sa = (union sockaddr_union) { | |
740 | .in.sin_family = l->family, | |
741 | .in.sin_port = htobe16(l->port != 0 ? l->port : 53U), | |
742 | .in.sin_addr = l->address.in, | |
743 | }; | |
744 | else | |
745 | sa = (union sockaddr_union) { | |
746 | .in6.sin6_family = l->family, | |
747 | .in6.sin6_port = htobe16(l->port != 0 ? l->port : 53U), | |
748 | .in6.sin6_addr = l->address.in6, | |
749 | }; | |
750 | ||
751 | fd = socket(l->family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); | |
752 | if (fd < 0) { | |
753 | r = -errno; | |
754 | goto fail; | |
755 | } | |
756 | ||
757 | r = set_dns_stub_common_socket_options(fd, l->family); | |
758 | if (r < 0) | |
759 | goto fail; | |
760 | ||
761 | /* Do not set IP_TTL for extra DNS stub listners, as the address may not be local and in that | |
762 | * case people may want ttl > 1. */ | |
763 | ||
764 | if (l->family == AF_INET) | |
765 | r = setsockopt_int(fd, IPPROTO_IP, IP_FREEBIND, true); | |
766 | else | |
767 | r = setsockopt_int(fd, IPPROTO_IPV6, IPV6_FREEBIND, true); | |
768 | if (r < 0) | |
769 | goto fail; | |
770 | ||
771 | if (bind(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0) { | |
772 | r = -errno; | |
773 | goto fail; | |
774 | } | |
775 | ||
776 | if (listen(fd, SOMAXCONN) < 0) { | |
777 | r = -errno; | |
778 | goto fail; | |
779 | } | |
780 | ||
781 | r = sd_event_add_io(m->event, &l->tcp_event_source, fd, EPOLLIN, on_dns_stub_stream_extra, l); | |
782 | if (r < 0) | |
783 | goto fail; | |
784 | ||
785 | r = sd_event_source_set_io_fd_own(l->tcp_event_source, true); | |
786 | if (r < 0) | |
787 | goto fail; | |
788 | ||
789 | (void) sd_event_source_set_description(l->tcp_event_source, "dns-stub-tcp-extra"); | |
790 | ||
791 | if (DEBUG_LOGGING) { | |
792 | (void) in_addr_port_to_string(l->family, &l->address, l->port, &pretty); | |
793 | log_debug("Listening on TCP socket %s.", strnull(pretty)); | |
794 | } | |
795 | ||
796 | return TAKE_FD(fd); | |
797 | ||
798 | fail: | |
799 | assert(r < 0); | |
800 | (void) in_addr_port_to_string(l->family, &l->address, l->port, &pretty); | |
801 | if (r == -EADDRINUSE) | |
802 | return log_warning_errno(r, "Another process is already listening on TCP socket %s: %m", strnull(pretty)); | |
803 | return log_warning_errno(r, "Failed to listen on TCP socket %s: %m", strnull(pretty)); | |
804 | } | |
805 | ||
806 | int manager_dns_stub_start(Manager *m) { | |
807 | const char *t = "UDP"; | |
808 | int r = 0; | |
809 | ||
810 | assert(m); | |
811 | ||
812 | if (m->dns_stub_listener_mode == DNS_STUB_LISTENER_NO) | |
813 | log_debug("Not creating stub listener."); | |
814 | else | |
815 | log_debug("Creating stub listener using %s.", | |
816 | m->dns_stub_listener_mode == DNS_STUB_LISTENER_UDP ? "UDP" : | |
817 | m->dns_stub_listener_mode == DNS_STUB_LISTENER_TCP ? "TCP" : | |
818 | "UDP/TCP"); | |
819 | ||
820 | if (FLAGS_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_UDP)) | |
821 | r = manager_dns_stub_udp_fd(m); | |
822 | ||
823 | if (r >= 0 && | |
824 | FLAGS_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_TCP)) { | |
825 | t = "TCP"; | |
826 | r = manager_dns_stub_tcp_fd(m); | |
827 | } | |
828 | ||
829 | if (IN_SET(r, -EADDRINUSE, -EPERM)) { | |
830 | if (r == -EADDRINUSE) | |
831 | log_warning_errno(r, | |
832 | "Another process is already listening on %s socket 127.0.0.53:53.\n" | |
833 | "Turning off local DNS stub support.", t); | |
834 | else | |
835 | log_warning_errno(r, | |
836 | "Failed to listen on %s socket 127.0.0.53:53: %m.\n" | |
837 | "Turning off local DNS stub support.", t); | |
838 | manager_dns_stub_stop(m); | |
839 | } else if (r < 0) | |
840 | return log_error_errno(r, "Failed to listen on %s socket 127.0.0.53:53: %m", t); | |
841 | ||
842 | if (!ordered_set_isempty(m->dns_extra_stub_listeners)) { | |
843 | DnsStubListenerExtra *l; | |
844 | ||
845 | log_debug("Creating extra stub listeners."); | |
846 | ||
847 | ORDERED_SET_FOREACH(l, m->dns_extra_stub_listeners) { | |
848 | if (FLAGS_SET(l->mode, DNS_STUB_LISTENER_UDP)) | |
849 | (void) manager_dns_stub_udp_fd_extra(m, l); | |
850 | if (FLAGS_SET(l->mode, DNS_STUB_LISTENER_TCP)) | |
851 | (void) manager_dns_stub_tcp_fd_extra(m, l); | |
852 | } | |
853 | } | |
854 | ||
855 | return 0; | |
856 | } | |
857 | ||
858 | void manager_dns_stub_stop(Manager *m) { | |
859 | assert(m); | |
860 | ||
861 | m->dns_stub_udp_event_source = sd_event_source_unref(m->dns_stub_udp_event_source); | |
862 | m->dns_stub_tcp_event_source = sd_event_source_unref(m->dns_stub_tcp_event_source); | |
863 | } | |
864 | ||
865 | static const char* const dns_stub_listener_mode_table[_DNS_STUB_LISTENER_MODE_MAX] = { | |
866 | [DNS_STUB_LISTENER_NO] = "no", | |
867 | [DNS_STUB_LISTENER_UDP] = "udp", | |
868 | [DNS_STUB_LISTENER_TCP] = "tcp", | |
869 | [DNS_STUB_LISTENER_YES] = "yes", | |
870 | }; | |
871 | DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_stub_listener_mode, DnsStubListenerMode, DNS_STUB_LISTENER_YES); |