]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
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 | ||
0354029b LP |
18 | static int manager_dns_stub_udp_fd_extra(Manager *m, DnsStubListenerExtra *l); |
19 | ||
ae8f0ec3 LP |
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 | ||
0354029b LP |
57 | int dns_stub_listener_extra_new( |
58 | Manager *m, | |
59 | DnsStubListenerExtra **ret) { | |
ae8f0ec3 | 60 | |
36aaabc3 | 61 | DnsStubListenerExtra *l; |
1f05101f | 62 | |
0354029b | 63 | l = new(DnsStubListenerExtra, 1); |
1f05101f SS |
64 | if (!l) |
65 | return -ENOMEM; | |
66 | ||
0354029b LP |
67 | *l = (DnsStubListenerExtra) { |
68 | .manager = m, | |
69 | }; | |
1f05101f | 70 | |
0354029b | 71 | *ret = TAKE_PTR(l); |
1f05101f SS |
72 | return 0; |
73 | } | |
74 | ||
36aaabc3 | 75 | DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) { |
bf22f231 YW |
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); | |
bf22f231 YW |
81 | |
82 | return mfree(p); | |
83 | } | |
84 | ||
b30bf55d | 85 | static int dns_stub_make_reply_packet( |
e8d23f92 | 86 | DnsPacket **p, |
51027656 | 87 | size_t max_size, |
b30bf55d | 88 | DnsQuestion *q, |
51027656 LP |
89 | DnsAnswer *answer, |
90 | bool *ret_truncated) { | |
b30bf55d | 91 | |
51027656 | 92 | bool truncated = false; |
b30bf55d LP |
93 | DnsResourceRecord *rr; |
94 | unsigned c = 0; | |
95 | int r; | |
96 | ||
e8d23f92 LP |
97 | assert(p); |
98 | ||
b30bf55d LP |
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 | ||
e8d23f92 | 102 | if (!*p) { |
51027656 | 103 | r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0, max_size); |
e8d23f92 LP |
104 | if (r < 0) |
105 | return r; | |
b30bf55d | 106 | |
e8d23f92 LP |
107 | r = dns_packet_append_question(*p, q); |
108 | if (r < 0) | |
109 | return r; | |
b30bf55d | 110 | |
e8d23f92 LP |
111 | DNS_PACKET_HEADER(*p)->qdcount = htobe16(dns_question_size(q)); |
112 | } | |
b30bf55d LP |
113 | |
114 | DNS_ANSWER_FOREACH(rr, answer) { | |
e8d23f92 | 115 | |
b30bf55d LP |
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: | |
01c901e2 | 130 | r = dns_packet_append_rr(*p, rr, 0, NULL, NULL); |
51027656 LP |
131 | if (r == -EMSGSIZE) { |
132 | truncated = true; | |
133 | break; | |
134 | } | |
b30bf55d LP |
135 | if (r < 0) |
136 | return r; | |
137 | ||
138 | c++; | |
139 | } | |
e8d23f92 | 140 | |
51027656 LP |
141 | if (ret_truncated) |
142 | *ret_truncated = truncated; | |
143 | else if (truncated) | |
144 | return -EMSGSIZE; | |
145 | ||
e8d23f92 LP |
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, | |
51027656 | 155 | bool tc, /* set the Truncated bit? */ |
e8d23f92 LP |
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 | ||
ff4caaae LP |
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 { | |
941dd294 LP |
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; | |
e8d23f92 LP |
183 | |
184 | DNS_PACKET_HEADER(p)->id = id; | |
185 | ||
186 | DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( | |
51027656 LP |
187 | 1 /* qr */, |
188 | 0 /* opcode */, | |
189 | 0 /* aa */, | |
190 | tc /* tc */, | |
191 | 1 /* rd */, | |
192 | 1 /* ra */, | |
e8d23f92 | 193 | ad /* ad */, |
51027656 | 194 | 0 /* cd */, |
e8d23f92 | 195 | rcode)); |
b30bf55d | 196 | |
b30bf55d LP |
197 | return 0; |
198 | } | |
199 | ||
0354029b LP |
200 | static int dns_stub_send( |
201 | Manager *m, | |
202 | DnsStubListenerExtra *l, | |
203 | DnsStream *s, | |
204 | DnsPacket *p, | |
205 | DnsPacket *reply) { | |
206 | ||
b30bf55d LP |
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); | |
0354029b | 215 | else |
b30bf55d LP |
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. */ | |
0354029b LP |
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); | |
b30bf55d LP |
224 | if (r < 0) |
225 | return log_debug_errno(r, "Failed to send reply packet: %m"); | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
0354029b LP |
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 | ||
b30bf55d LP |
238 | _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; |
239 | int r; | |
240 | ||
241 | assert(m); | |
242 | assert(p); | |
243 | ||
51027656 | 244 | r = dns_stub_make_reply_packet(&reply, DNS_PACKET_PAYLOAD_SIZE_MAX(p), p->question, NULL, NULL); |
e8d23f92 LP |
245 | if (r < 0) |
246 | return log_debug_errno(r, "Failed to make failure packet: %m"); | |
247 | ||
51027656 | 248 | r = dns_stub_finish_reply_packet(reply, DNS_PACKET_ID(p), rcode, false, !!p->opt, DNS_PACKET_DO(p), authenticated); |
b30bf55d LP |
249 | if (r < 0) |
250 | return log_debug_errno(r, "Failed to build failure packet: %m"); | |
251 | ||
0354029b | 252 | return dns_stub_send(m, l, s, p, reply); |
b30bf55d LP |
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 | ||
51027656 LP |
263 | case DNS_TRANSACTION_SUCCESS: { |
264 | bool truncated; | |
e8d23f92 | 265 | |
51027656 | 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); |
e8d23f92 LP |
267 | if (r < 0) { |
268 | log_debug_errno(r, "Failed to build reply packet: %m"); | |
269 | break; | |
270 | } | |
b30bf55d | 271 | |
aa11cab9 LP |
272 | if (!truncated) { |
273 | r = dns_query_process_cname(q); | |
274 | if (r == -ELOOP) { | |
0354029b | 275 | (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 |
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; | |
e8d23f92 | 284 | } |
e8d23f92 LP |
285 | |
286 | r = dns_stub_finish_reply_packet( | |
287 | q->reply_dns_packet, | |
b30bf55d LP |
288 | DNS_PACKET_ID(q->request_dns_packet), |
289 | q->answer_rcode, | |
51027656 | 290 | truncated, |
b30bf55d LP |
291 | !!q->request_dns_packet->opt, |
292 | DNS_PACKET_DO(q->request_dns_packet), | |
941dd294 | 293 | dns_query_fully_authenticated(q)); |
b30bf55d | 294 | if (r < 0) { |
e8d23f92 | 295 | log_debug_errno(r, "Failed to finish reply packet: %m"); |
b30bf55d LP |
296 | break; |
297 | } | |
298 | ||
0354029b | 299 | (void) dns_stub_send(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->reply_dns_packet); |
b30bf55d | 300 | break; |
51027656 | 301 | } |
b30bf55d LP |
302 | |
303 | case DNS_TRANSACTION_RCODE_FAILURE: | |
0354029b | 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)); |
b30bf55d LP |
305 | break; |
306 | ||
307 | case DNS_TRANSACTION_NOT_FOUND: | |
0354029b | 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)); |
b30bf55d LP |
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: | |
0354029b | 324 | (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 |
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 | ||
b30bf55d LP |
334 | dns_query_free(q); |
335 | } | |
336 | ||
337 | static int dns_stub_stream_complete(DnsStream *s, int error) { | |
338 | assert(s); | |
339 | ||
b412af57 LP |
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; | |
b30bf55d | 348 | |
b412af57 LP |
349 | dns_query_free(q); |
350 | } | |
b30bf55d | 351 | |
b412af57 LP |
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); | |
b30bf55d LP |
355 | return 0; |
356 | } | |
357 | ||
0354029b | 358 | static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStream *s, DnsPacket *p) { |
ceb17827 | 359 | _cleanup_(dns_query_freep) DnsQuery *q = NULL; |
b30bf55d LP |
360 | int r; |
361 | ||
362 | assert(m); | |
363 | assert(p); | |
364 | assert(p->protocol == DNS_PROTOCOL_DNS); | |
365 | ||
0354029b | 366 | if (!l && /* l == NULL if this is the main stub */ |
d1fb8cda YW |
367 | (in_addr_is_localhost(p->family, &p->sender) <= 0 || |
368 | in_addr_is_localhost(p->family, &p->destination) <= 0)) { | |
b30bf55d | 369 | log_error("Got packet on unexpected IP range, refusing."); |
0354029b | 370 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); |
ceb17827 | 371 | return; |
b30bf55d LP |
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"); | |
0354029b | 377 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_FORMERR, false); |
ceb17827 | 378 | return; |
b30bf55d LP |
379 | } |
380 | ||
381 | if (!DNS_PACKET_VERSION_SUPPORTED(p)) { | |
382 | log_debug("Got EDNS OPT field with unsupported version number."); | |
0354029b | 383 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_BADVERS, false); |
ceb17827 | 384 | return; |
b30bf55d LP |
385 | } |
386 | ||
387 | if (dns_type_is_obsolete(p->question->keys[0]->type)) { | |
388 | log_debug("Got message with obsolete key type, refusing."); | |
0354029b | 389 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); |
ceb17827 | 390 | return; |
b30bf55d LP |
391 | } |
392 | ||
393 | if (dns_type_is_zone_transer(p->question->keys[0]->type)) { | |
394 | log_debug("Got request for zone transfer, refusing."); | |
0354029b | 395 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); |
ceb17827 | 396 | return; |
b30bf55d LP |
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."); | |
0354029b | 402 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false); |
ceb17827 | 403 | return; |
b30bf55d LP |
404 | } |
405 | ||
406 | if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) { | |
407 | log_debug("Got request with DNSSEC CD bit set, refusing."); | |
0354029b | 408 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); |
ceb17827 | 409 | return; |
b30bf55d LP |
410 | } |
411 | ||
e8d23f92 | 412 | r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH); |
b30bf55d LP |
413 | if (r < 0) { |
414 | log_error_errno(r, "Failed to generate query object: %m"); | |
0354029b | 415 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); |
ceb17827 | 416 | return; |
b30bf55d LP |
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 */ | |
0354029b | 424 | q->stub_listener_extra = l; |
b30bf55d LP |
425 | q->complete = dns_stub_query_complete; |
426 | ||
427 | if (s) { | |
b412af57 LP |
428 | /* Remember which queries belong to this stream, so that we can cancel them when the stream |
429 | * is disconnected early */ | |
430 | ||
ceb17827 | 431 | r = set_ensure_put(&s->queries, NULL, q); |
b412af57 LP |
432 | if (r < 0) { |
433 | log_oom(); | |
ceb17827 | 434 | return; |
b412af57 | 435 | } |
ceb17827 | 436 | assert(r > 0); |
b30bf55d LP |
437 | } |
438 | ||
439 | r = dns_query_go(q); | |
440 | if (r < 0) { | |
441 | log_error_errno(r, "Failed to start query: %m"); | |
0354029b | 442 | dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); |
ceb17827 | 443 | return; |
b30bf55d LP |
444 | } |
445 | ||
52e63427 | 446 | log_debug("Processing query..."); |
ceb17827 | 447 | TAKE_PTR(q); |
b30bf55d LP |
448 | } |
449 | ||
0354029b | 450 | static int on_dns_stub_packet_internal(sd_event_source *s, int fd, uint32_t revents, Manager *m, DnsStubListenerExtra *l) { |
b30bf55d | 451 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; |
b30bf55d LP |
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 | ||
0354029b | 461 | dns_stub_process_query(m, l, NULL, p); |
b30bf55d LP |
462 | } else |
463 | log_debug("Invalid DNS stub UDP packet, ignoring."); | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
d1fb8cda | 468 | static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { |
0354029b | 469 | return on_dns_stub_packet_internal(s, fd, revents, userdata, NULL); |
d1fb8cda YW |
470 | } |
471 | ||
472 | static int on_dns_stub_packet_extra(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
0354029b LP |
473 | DnsStubListenerExtra *l = userdata; |
474 | ||
475 | assert(l); | |
476 | ||
477 | return on_dns_stub_packet_internal(s, fd, revents, l->manager, l); | |
d1fb8cda YW |
478 | } |
479 | ||
e4bed40f ZJS |
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 | ||
af8b1384 | 536 | static int set_dns_stub_common_socket_options(int fd, int family) { |
1f05101f SS |
537 | int r; |
538 | ||
539 | assert(fd >= 0); | |
af8b1384 | 540 | assert(IN_SET(family, AF_INET, AF_INET6)); |
1f05101f SS |
541 | |
542 | r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); | |
543 | if (r < 0) | |
544 | return r; | |
545 | ||
af8b1384 YW |
546 | if (family == AF_INET) { |
547 | r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); | |
548 | if (r < 0) | |
549 | return r; | |
1f05101f | 550 | |
af8b1384 YW |
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; | |
1f05101f SS |
565 | } |
566 | ||
424e490b | 567 | static int manager_dns_stub_udp_fd(Manager *m) { |
b30bf55d LP |
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 | }; | |
424e490b | 573 | _cleanup_close_ int fd = -1; |
b30bf55d LP |
574 | int r; |
575 | ||
7216a3b5 YW |
576 | if (m->dns_stub_udp_event_source) |
577 | return sd_event_source_get_io_fd(m->dns_stub_udp_event_source); | |
b30bf55d | 578 | |
424e490b ZJS |
579 | fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); |
580 | if (fd < 0) | |
b30bf55d LP |
581 | return -errno; |
582 | ||
af8b1384 | 583 | r = set_dns_stub_common_socket_options(fd, AF_INET); |
2ff48e98 LP |
584 | if (r < 0) |
585 | return r; | |
b30bf55d LP |
586 | |
587 | /* Make sure no traffic from outside the local host can leak to onto this socket */ | |
953a02d1 LP |
588 | r = socket_bind_to_ifindex(fd, LOOPBACK_IFINDEX); |
589 | if (r < 0) | |
590 | return r; | |
b30bf55d | 591 | |
424e490b ZJS |
592 | if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) |
593 | return -errno; | |
b30bf55d | 594 | |
424e490b | 595 | r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, fd, EPOLLIN, on_dns_stub_packet, m); |
b30bf55d | 596 | if (r < 0) |
424e490b | 597 | return r; |
b30bf55d | 598 | |
7216a3b5 YW |
599 | r = sd_event_source_set_io_fd_own(m->dns_stub_udp_event_source, true); |
600 | if (r < 0) | |
601 | return r; | |
602 | ||
b30bf55d LP |
603 | (void) sd_event_source_set_description(m->dns_stub_udp_event_source, "dns-stub-udp"); |
604 | ||
7216a3b5 | 605 | return TAKE_FD(fd); |
b30bf55d LP |
606 | } |
607 | ||
36aaabc3 | 608 | static int manager_dns_stub_udp_fd_extra(Manager *m, DnsStubListenerExtra *l) { |
1f05101f SS |
609 | _cleanup_free_ char *pretty = NULL; |
610 | _cleanup_close_ int fd = -1; | |
ca8b62b5 | 611 | union sockaddr_union sa; |
1f05101f SS |
612 | int r; |
613 | ||
0354029b LP |
614 | assert(m); |
615 | ||
616 | if (!l) | |
617 | return manager_dns_stub_udp_fd(m); | |
618 | ||
7216a3b5 | 619 | if (l->udp_event_source) |
1f05101f SS |
620 | return 0; |
621 | ||
ca8b62b5 YW |
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); | |
1f05101f SS |
636 | if (fd < 0) { |
637 | r = -errno; | |
638 | goto fail; | |
639 | } | |
640 | ||
af8b1384 YW |
641 | if (l->family == AF_INET) { |
642 | r = setsockopt_int(fd, IPPROTO_IP, IP_FREEBIND, true); | |
643 | if (r < 0) | |
644 | goto fail; | |
645 | } | |
1f05101f | 646 | |
af8b1384 | 647 | r = set_dns_stub_common_socket_options(fd, l->family); |
1f05101f SS |
648 | if (r < 0) |
649 | goto fail; | |
650 | ||
ca8b62b5 | 651 | if (bind(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0) { |
1f05101f SS |
652 | r = -errno; |
653 | goto fail; | |
654 | } | |
655 | ||
0354029b | 656 | r = sd_event_add_io(m->event, &l->udp_event_source, fd, EPOLLIN, on_dns_stub_packet_extra, l); |
1f05101f SS |
657 | if (r < 0) |
658 | goto fail; | |
659 | ||
7216a3b5 YW |
660 | r = sd_event_source_set_io_fd_own(l->udp_event_source, true); |
661 | if (r < 0) | |
662 | goto fail; | |
663 | ||
7314b397 | 664 | (void) sd_event_source_set_description(l->udp_event_source, "dns-stub-udp-extra"); |
1f05101f SS |
665 | |
666 | if (DEBUG_LOGGING) { | |
ca8b62b5 | 667 | (void) in_addr_port_to_string(l->family, &l->address, l->port, &pretty); |
1f05101f SS |
668 | log_debug("Listening on UDP socket %s.", strnull(pretty)); |
669 | } | |
670 | ||
7216a3b5 | 671 | return TAKE_FD(fd); |
1f05101f | 672 | |
b4b7ea1b | 673 | fail: |
1c17bcb3 | 674 | assert(r < 0); |
ca8b62b5 | 675 | (void) in_addr_port_to_string(l->family, &l->address, l->port, &pretty); |
b4b7ea1b | 676 | if (r == -EADDRINUSE) |
1c17bcb3 YW |
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)); | |
1f05101f SS |
679 | } |
680 | ||
424e490b | 681 | static int manager_dns_stub_tcp_fd(Manager *m) { |
b30bf55d LP |
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 | }; | |
424e490b | 687 | _cleanup_close_ int fd = -1; |
b30bf55d LP |
688 | int r; |
689 | ||
7216a3b5 YW |
690 | if (m->dns_stub_tcp_event_source) |
691 | return sd_event_source_get_io_fd(m->dns_stub_tcp_event_source); | |
b30bf55d | 692 | |
424e490b ZJS |
693 | fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); |
694 | if (fd < 0) | |
b30bf55d LP |
695 | return -errno; |
696 | ||
af8b1384 | 697 | r = set_dns_stub_common_socket_options(fd, AF_INET); |
2ff48e98 LP |
698 | if (r < 0) |
699 | return r; | |
b30bf55d | 700 | |
5b496410 | 701 | r = setsockopt_int(fd, IPPROTO_IP, IP_TTL, 1); |
2ff48e98 LP |
702 | if (r < 0) |
703 | return r; | |
b30bf55d LP |
704 | |
705 | /* Make sure no traffic from outside the local host can leak to onto this socket */ | |
953a02d1 LP |
706 | r = socket_bind_to_ifindex(fd, LOOPBACK_IFINDEX); |
707 | if (r < 0) | |
708 | return r; | |
b30bf55d | 709 | |
424e490b ZJS |
710 | if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) |
711 | return -errno; | |
b30bf55d | 712 | |
424e490b ZJS |
713 | if (listen(fd, SOMAXCONN) < 0) |
714 | return -errno; | |
b30bf55d | 715 | |
424e490b | 716 | r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, fd, EPOLLIN, on_dns_stub_stream, m); |
b30bf55d | 717 | if (r < 0) |
424e490b | 718 | return r; |
b30bf55d | 719 | |
7216a3b5 YW |
720 | r = sd_event_source_set_io_fd_own(m->dns_stub_tcp_event_source, true); |
721 | if (r < 0) | |
722 | return r; | |
723 | ||
b30bf55d LP |
724 | (void) sd_event_source_set_description(m->dns_stub_tcp_event_source, "dns-stub-tcp"); |
725 | ||
7216a3b5 | 726 | return TAKE_FD(fd); |
b30bf55d LP |
727 | } |
728 | ||
36aaabc3 | 729 | static int manager_dns_stub_tcp_fd_extra(Manager *m, DnsStubListenerExtra *l) { |
1f05101f SS |
730 | _cleanup_free_ char *pretty = NULL; |
731 | _cleanup_close_ int fd = -1; | |
ca8b62b5 | 732 | union sockaddr_union sa; |
1f05101f SS |
733 | int r; |
734 | ||
7216a3b5 YW |
735 | if (l->tcp_event_source) |
736 | return sd_event_source_get_io_fd(l->tcp_event_source);; | |
1f05101f | 737 | |
ca8b62b5 YW |
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); | |
1f05101f SS |
752 | if (fd < 0) { |
753 | r = -errno; | |
754 | goto fail; | |
755 | } | |
756 | ||
af8b1384 | 757 | r = set_dns_stub_common_socket_options(fd, l->family); |
1f05101f SS |
758 | if (r < 0) |
759 | goto fail; | |
760 | ||
d40f00d7 YW |
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. */ | |
1f05101f | 763 | |
af8b1384 YW |
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); | |
1f05101f SS |
768 | if (r < 0) |
769 | goto fail; | |
770 | ||
ca8b62b5 | 771 | if (bind(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0) { |
1f05101f SS |
772 | r = -errno; |
773 | goto fail; | |
774 | } | |
775 | ||
776 | if (listen(fd, SOMAXCONN) < 0) { | |
777 | r = -errno; | |
778 | goto fail; | |
779 | } | |
780 | ||
0354029b | 781 | r = sd_event_add_io(m->event, &l->tcp_event_source, fd, EPOLLIN, on_dns_stub_stream_extra, l); |
1f05101f SS |
782 | if (r < 0) |
783 | goto fail; | |
784 | ||
7216a3b5 YW |
785 | r = sd_event_source_set_io_fd_own(l->tcp_event_source, true); |
786 | if (r < 0) | |
787 | goto fail; | |
788 | ||
7314b397 | 789 | (void) sd_event_source_set_description(l->tcp_event_source, "dns-stub-tcp-extra"); |
1f05101f SS |
790 | |
791 | if (DEBUG_LOGGING) { | |
ca8b62b5 | 792 | (void) in_addr_port_to_string(l->family, &l->address, l->port, &pretty); |
1f05101f SS |
793 | log_debug("Listening on TCP socket %s.", strnull(pretty)); |
794 | } | |
795 | ||
7216a3b5 | 796 | return TAKE_FD(fd); |
1f05101f | 797 | |
b4b7ea1b | 798 | fail: |
1c17bcb3 | 799 | assert(r < 0); |
ca8b62b5 | 800 | (void) in_addr_port_to_string(l->family, &l->address, l->port, &pretty); |
b4b7ea1b | 801 | if (r == -EADDRINUSE) |
1c17bcb3 YW |
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)); | |
1f05101f SS |
804 | } |
805 | ||
b30bf55d | 806 | int manager_dns_stub_start(Manager *m) { |
424e490b | 807 | const char *t = "UDP"; |
01b0669e | 808 | int r = 0; |
b30bf55d LP |
809 | |
810 | assert(m); | |
811 | ||
d5da7707 ZJS |
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 | ||
88d2cb7c | 820 | if (FLAGS_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_UDP)) |
1ae43295 | 821 | r = manager_dns_stub_udp_fd(m); |
b30bf55d | 822 | |
424e490b | 823 | if (r >= 0 && |
88d2cb7c | 824 | FLAGS_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_TCP)) { |
424e490b | 825 | t = "TCP"; |
1ae43295 | 826 | r = manager_dns_stub_tcp_fd(m); |
1ae43295 | 827 | } |
b30bf55d | 828 | |
0f4db364 ZJS |
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); | |
424e490b ZJS |
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); | |
b30bf55d | 841 | |
1f05101f | 842 | if (!ordered_set_isempty(m->dns_extra_stub_listeners)) { |
36aaabc3 | 843 | DnsStubListenerExtra *l; |
1f05101f | 844 | |
dce65cd4 | 845 | log_debug("Creating extra stub listeners."); |
1f05101f | 846 | |
90e74a66 | 847 | ORDERED_SET_FOREACH(l, m->dns_extra_stub_listeners) { |
7314b397 | 848 | if (FLAGS_SET(l->mode, DNS_STUB_LISTENER_UDP)) |
1f05101f | 849 | (void) manager_dns_stub_udp_fd_extra(m, l); |
7314b397 | 850 | if (FLAGS_SET(l->mode, DNS_STUB_LISTENER_TCP)) |
1f05101f | 851 | (void) manager_dns_stub_tcp_fd_extra(m, l); |
7314b397 | 852 | } |
1f05101f SS |
853 | } |
854 | ||
b30bf55d LP |
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); | |
b30bf55d | 863 | } |
ae8f0ec3 LP |
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); |