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