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