]>
Commit | Line | Data |
---|---|---|
726bcd81 JB |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <arpa/inet.h> | |
4 | #include <fcntl.h> | |
c76120f1 | 5 | #include <net/if.h> |
3bf175f0 | 6 | #include <net/if_arp.h> |
726bcd81 JB |
7 | #include <pthread.h> |
8 | #include <signal.h> | |
9 | #include <stdlib.h> | |
10 | #include <string.h> | |
c76120f1 | 11 | #include <sys/ioctl.h> |
726bcd81 JB |
12 | #include <sys/prctl.h> |
13 | #include <sys/socket.h> | |
14 | #include <sys/wait.h> | |
15 | #include <unistd.h> | |
16 | ||
17 | #include "fd-util.h" | |
18 | #include "log.h" | |
c76120f1 | 19 | #include "macro.h" |
ed59b443 | 20 | #include "path-util.h" |
726bcd81 JB |
21 | #include "process-util.h" |
22 | #include "resolved-dns-packet.h" | |
23 | #include "resolved-dns-question.h" | |
24 | #include "resolved-dns-rr.h" | |
25 | #if ENABLE_DNS_OVER_TLS | |
26 | #include "resolved-dnstls.h" | |
27 | #endif | |
28 | #include "resolved-dns-server.h" | |
29 | #include "resolved-dns-stream.h" | |
30 | #include "resolved-manager.h" | |
31 | #include "sd-event.h" | |
32 | #include "sparse-endian.h" | |
33 | #include "tests.h" | |
34 | ||
3bf175f0 | 35 | static union sockaddr_union server_address; |
726bcd81 JB |
36 | |
37 | /* Bytes of the questions & answers used in the test, including TCP DNS 2-byte length prefix */ | |
38 | static const uint8_t QUESTION_A[] = { | |
39 | 0x00, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 'e', | |
40 | 'x' , 'a' , 'm' , 'p' , 'l' , 'e' , 0x03, 'c' , 'o' , 'm' , 0x00, 0x00, 0x01, 0x00, 0x01 | |
41 | }; | |
42 | static const uint8_t QUESTION_AAAA[] = { | |
43 | 0x00, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 'e', | |
44 | 'x' , 'a' , 'm' , 'p' , 'l' , 'e' , 0x03, 'c' , 'o' , 'm' , 0x00, 0x00, 0x1C, 0x00, 0x01 | |
45 | }; | |
46 | static const uint8_t ANSWER_A[] = { | |
47 | 0x00, 0x2D, 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 'e', | |
48 | 'x' , 'a' , 'm' , 'p' , 'l' , 'e' , 0x03, 'c' , 'o' , 'm' , 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0, | |
49 | 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x52, 0x8D, 0x00, 0x04, 0x5D, 0xB8, 0xD8, 0x22, | |
50 | }; | |
51 | static const uint8_t ANSWER_AAAA[] = { | |
52 | 0x00, 0x39, 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 'e', | |
53 | 'x' , 'a' , 'm' , 'p' , 'l' , 'e' , 0x03, 'c' , 'o' , 'm' , 0x00, 0x00, 0x1C, 0x00, 0x01, 0xC0, | |
54 | 0x0C, 0x00, 0x1C, 0x00, 0x01, 0x00, 0x00, 0x54, 0x4B, 0x00, 0x10, 0x26, 0x06, 0x28, 0x00, 0x02, | |
55 | 0x20, 0x00, 0x01, 0x02, 0x48, 0x18, 0x93, 0x25, 0xC8, 0x19, 0x46, | |
56 | }; | |
57 | ||
58 | /** | |
59 | * A mock TCP DNS server that asserts certain questions are received | |
60 | * and replies with the same answer every time. | |
61 | */ | |
62 | static void receive_and_check_question(int fd, const uint8_t *expected_question, | |
63 | size_t question_size) { | |
64 | uint8_t *actual_question; | |
65 | size_t n_read = 0; | |
66 | ||
67 | actual_question = newa(uint8_t, question_size); | |
68 | while (n_read < question_size) { | |
69 | ssize_t r = read(fd, actual_question + n_read, question_size - n_read); | |
70 | assert_se(r >= 0); | |
71 | n_read += (size_t)r; | |
72 | } | |
73 | assert_se(n_read == question_size); | |
74 | ||
75 | assert_se(memcmp(expected_question, actual_question, question_size) == 0); | |
76 | } | |
77 | ||
78 | static void send_answer(int fd, const uint8_t *answer, size_t answer_size) { | |
79 | assert_se(write(fd, answer, answer_size) == (ssize_t)answer_size); | |
80 | } | |
81 | ||
82 | /* Sends two answers together in a single write operation, | |
83 | * so they hopefully end up in a single TCP packet / TLS record */ | |
84 | static void send_answers_together(int fd, | |
85 | const uint8_t *answer1, size_t answer1_size, | |
86 | const uint8_t *answer2, size_t answer2_size) { | |
87 | uint8_t *answer; | |
88 | size_t answer_size = answer1_size + answer2_size; | |
89 | ||
90 | answer = newa(uint8_t, answer_size); | |
91 | memcpy(answer, answer1, answer1_size); | |
92 | memcpy(answer + answer1_size, answer2, answer2_size); | |
93 | assert_se(write(fd, answer, answer_size) == (ssize_t)answer_size); | |
94 | } | |
95 | ||
96 | static void server_handle(int fd) { | |
97 | receive_and_check_question(fd, QUESTION_A, sizeof(QUESTION_A)); | |
98 | send_answer(fd, ANSWER_A, sizeof(ANSWER_A)); | |
99 | ||
100 | receive_and_check_question(fd, QUESTION_AAAA, sizeof(QUESTION_AAAA)); | |
101 | send_answer(fd, ANSWER_AAAA, sizeof(ANSWER_AAAA)); | |
102 | ||
103 | receive_and_check_question(fd, QUESTION_A, sizeof(QUESTION_A)); | |
104 | receive_and_check_question(fd, QUESTION_AAAA, sizeof(QUESTION_AAAA)); | |
105 | send_answers_together(fd, ANSWER_A, sizeof(ANSWER_A), | |
106 | ANSWER_AAAA, sizeof(ANSWER_AAAA)); | |
107 | } | |
108 | ||
109 | static void *tcp_dns_server(void *p) { | |
254d1313 | 110 | _cleanup_close_ int bindfd = -EBADF, acceptfd = -EBADF; |
726bcd81 JB |
111 | |
112 | assert_se((bindfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) >= 0); | |
113 | assert_se(setsockopt(bindfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) >= 0); | |
3bf175f0 | 114 | assert_se(bind(bindfd, &server_address.sa, SOCKADDR_LEN(server_address)) >= 0); |
726bcd81 JB |
115 | assert_se(listen(bindfd, 1) >= 0); |
116 | assert_se((acceptfd = accept(bindfd, NULL, NULL)) >= 0); | |
117 | server_handle(acceptfd); | |
118 | return NULL; | |
119 | } | |
120 | ||
121 | #if ENABLE_DNS_OVER_TLS | |
122 | /* | |
123 | * Spawns a DNS TLS server using the command line "openssl s_server" tool. | |
124 | */ | |
125 | static void *tls_dns_server(void *p) { | |
126 | pid_t openssl_pid; | |
127 | int r; | |
254d1313 | 128 | _cleanup_close_ int fd_server = -EBADF, fd_tls = -EBADF; |
726bcd81 | 129 | _cleanup_free_ char *cert_path = NULL, *key_path = NULL; |
84dbb3fd | 130 | _cleanup_free_ char *bind_str = NULL; |
726bcd81 JB |
131 | |
132 | assert_se(get_testdata_dir("test-resolve/selfsigned.cert", &cert_path) >= 0); | |
133 | assert_se(get_testdata_dir("test-resolve/selfsigned.key", &key_path) >= 0); | |
134 | ||
84dbb3fd ZJS |
135 | assert_se(asprintf(&bind_str, "%s:%d", |
136 | IN_ADDR_TO_STRING(server_address.in.sin_family, | |
137 | sockaddr_in_addr(&server_address.sa)), | |
138 | be16toh(server_address.in.sin_port)) >= 0); | |
726bcd81 JB |
139 | |
140 | /* We will hook one of the socketpair ends to OpenSSL's TLS server | |
141 | * stdin/stdout, so we will be able to read and write plaintext | |
142 | * from the other end's file descriptor like an usual TCP server */ | |
143 | { | |
144 | int fd[2]; | |
145 | assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) >= 0); | |
146 | fd_server = fd[0]; | |
147 | fd_tls = fd[1]; | |
148 | } | |
149 | ||
150 | r = safe_fork_full("(test-resolved-stream-tls-openssl)", (int[]) { fd_server, fd_tls }, 2, | |
151 | FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_REOPEN_LOG, &openssl_pid); | |
839a70c3 | 152 | assert_se(r >= 0); |
726bcd81 JB |
153 | if (r == 0) { |
154 | /* Child */ | |
155 | assert_se(dup2(fd_tls, STDIN_FILENO) >= 0); | |
156 | assert_se(dup2(fd_tls, STDOUT_FILENO) >= 0); | |
157 | close(TAKE_FD(fd_server)); | |
158 | close(TAKE_FD(fd_tls)); | |
159 | ||
160 | execlp("openssl", "openssl", "s_server", "-accept", bind_str, | |
161 | "-key", key_path, "-cert", cert_path, | |
162 | "-quiet", "-naccept", "1", NULL); | |
163 | log_error("exec failed, is something wrong with the 'openssl' command?"); | |
164 | _exit(EXIT_FAILURE); | |
165 | } else { | |
166 | pthread_mutex_t *server_lock = (pthread_mutex_t *)p; | |
167 | ||
168 | server_handle(fd_server); | |
169 | ||
170 | /* Once the test is done kill the TLS server to release the port */ | |
171 | assert_se(pthread_mutex_lock(server_lock) == 0); | |
172 | assert_se(kill(openssl_pid, SIGTERM) >= 0); | |
173 | assert_se(waitpid(openssl_pid, NULL, 0) >= 0); | |
174 | assert_se(pthread_mutex_unlock(server_lock) == 0); | |
175 | } | |
176 | ||
177 | return NULL; | |
178 | } | |
179 | #endif | |
180 | ||
181 | static const char *TEST_DOMAIN = "example.com"; | |
182 | static const uint64_t EVENT_TIMEOUT_USEC = 5 * 1000 * 1000; | |
183 | ||
184 | static void send_simple_question(DnsStream *stream, uint16_t type) { | |
185 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; | |
186 | _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; | |
187 | _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; | |
188 | ||
189 | assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX) >= 0); | |
190 | assert_se(question = dns_question_new(1)); | |
191 | assert_se(key = dns_resource_key_new(DNS_CLASS_IN, type, TEST_DOMAIN)); | |
192 | assert_se(dns_question_add(question, key, 0) >= 0); | |
193 | assert_se(dns_packet_append_question(p, question) >= 0); | |
194 | DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(question)); | |
195 | assert_se(dns_stream_write_packet(stream, p) >= 0); | |
196 | } | |
197 | ||
198 | static const size_t MAX_RECEIVED_PACKETS = 2; | |
199 | static DnsPacket *received_packets[2] = {}; | |
200 | static size_t n_received_packets = 0; | |
201 | ||
624f907e | 202 | static int on_stream_packet(DnsStream *stream, DnsPacket *p) { |
726bcd81 | 203 | assert_se(n_received_packets < MAX_RECEIVED_PACKETS); |
624f907e | 204 | assert_se(received_packets[n_received_packets++] = dns_packet_ref(p)); |
726bcd81 JB |
205 | return 0; |
206 | } | |
207 | ||
839a70c3 JB |
208 | static int on_stream_complete_do_nothing(DnsStream *s, int error) { |
209 | return 0; | |
210 | } | |
211 | ||
726bcd81 JB |
212 | static void test_dns_stream(bool tls) { |
213 | Manager manager = {}; | |
214 | _cleanup_(dns_stream_unrefp) DnsStream *stream = NULL; | |
215 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; | |
254d1313 | 216 | _cleanup_close_ int clientfd = -EBADF; |
726bcd81 JB |
217 | int r; |
218 | ||
219 | void *(*server_entrypoint)(void *); | |
220 | pthread_t server_thread; | |
221 | pthread_mutex_t server_lock; | |
222 | ||
223 | log_info("test-resolved-stream: Started %s test", tls ? "TLS" : "TCP"); | |
224 | ||
225 | #if ENABLE_DNS_OVER_TLS | |
803e12f3 LP |
226 | if (tls) |
227 | /* For TLS mode, use DNS_OVER_TLS_OPPORTUNISTIC instead of DNS_OVER_TLS_YES, just to make | |
228 | * certificate validation more lenient, allowing us to use self-signed certificates. We | |
229 | * never downgrade, everything we test always goes over TLS */ | |
726bcd81 | 230 | manager.dns_over_tls_mode = DNS_OVER_TLS_OPPORTUNISTIC; |
726bcd81 JB |
231 | #endif |
232 | ||
233 | assert_se(sd_event_new(&event) >= 0); | |
234 | manager.event = event; | |
235 | ||
236 | /* Set up a mock DNS (over TCP or TLS) server */ | |
237 | server_entrypoint = tcp_dns_server; | |
238 | #if ENABLE_DNS_OVER_TLS | |
239 | if (tls) | |
240 | server_entrypoint = tls_dns_server; | |
241 | #endif | |
242 | assert_se(pthread_mutex_init(&server_lock, NULL) == 0); | |
243 | assert_se(pthread_mutex_lock(&server_lock) == 0); | |
244 | assert_se(pthread_create(&server_thread, NULL, server_entrypoint, &server_lock) == 0); | |
245 | ||
246 | /* Create a socket client and connect to the TCP or TLS server | |
247 | * The server may not be up immediately, so try to connect a few times before failing */ | |
248 | assert_se((clientfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) >= 0); | |
249 | ||
250 | for (int i = 0; i < 100; i++) { | |
3bf175f0 | 251 | r = connect(clientfd, &server_address.sa, SOCKADDR_LEN(server_address)); |
726bcd81 JB |
252 | if (r >= 0) |
253 | break; | |
254 | usleep(EVENT_TIMEOUT_USEC / 100); | |
255 | } | |
256 | assert_se(r >= 0); | |
257 | ||
258 | /* systemd-resolved uses (and requires) the socket to be in nonblocking mode */ | |
259 | assert_se(fcntl(clientfd, F_SETFL, O_NONBLOCK) >= 0); | |
260 | ||
839a70c3 JB |
261 | /* Initialize DNS stream (disabling the default self-destruction |
262 | behaviour when no complete callback is set) */ | |
726bcd81 | 263 | assert_se(dns_stream_new(&manager, &stream, DNS_STREAM_LOOKUP, DNS_PROTOCOL_DNS, |
839a70c3 | 264 | TAKE_FD(clientfd), NULL, on_stream_packet, on_stream_complete_do_nothing, |
18230451 | 265 | DNS_STREAM_DEFAULT_TIMEOUT_USEC) >= 0); |
726bcd81 JB |
266 | #if ENABLE_DNS_OVER_TLS |
267 | if (tls) { | |
268 | DnsServer server = { | |
269 | .manager = &manager, | |
3bf175f0 LP |
270 | .family = server_address.sa.sa_family, |
271 | .address = *sockaddr_in_addr(&server_address.sa), | |
726bcd81 JB |
272 | }; |
273 | ||
274 | assert_se(dnstls_manager_init(&manager) >= 0); | |
275 | assert_se(dnstls_stream_connect_tls(stream, &server) >= 0); | |
276 | } | |
277 | #endif | |
278 | ||
279 | /* Test: Question of type A and associated answer */ | |
280 | log_info("test-resolved-stream: A record"); | |
281 | send_simple_question(stream, DNS_TYPE_A); | |
282 | while (n_received_packets != 1) | |
283 | assert_se(sd_event_run(event, EVENT_TIMEOUT_USEC) >= 1); | |
284 | assert_se(DNS_PACKET_DATA(received_packets[0])); | |
285 | assert_se(memcmp(DNS_PACKET_DATA(received_packets[0]), | |
286 | ANSWER_A + 2, sizeof(ANSWER_A) - 2) == 0); | |
287 | dns_packet_unref(TAKE_PTR(received_packets[0])); | |
288 | n_received_packets = 0; | |
289 | ||
290 | /* Test: Question of type AAAA and associated answer */ | |
291 | log_info("test-resolved-stream: AAAA record"); | |
292 | send_simple_question(stream, DNS_TYPE_AAAA); | |
293 | while (n_received_packets != 1) | |
294 | assert_se(sd_event_run(event, EVENT_TIMEOUT_USEC) >= 1); | |
295 | assert_se(DNS_PACKET_DATA(received_packets[0])); | |
296 | assert_se(memcmp(DNS_PACKET_DATA(received_packets[0]), | |
297 | ANSWER_AAAA + 2, sizeof(ANSWER_AAAA) - 2) == 0); | |
298 | dns_packet_unref(TAKE_PTR(received_packets[0])); | |
299 | n_received_packets = 0; | |
300 | ||
301 | /* Test: Question of type A and AAAA and associated answers | |
302 | * Both answers are sent back in a single packet or TLS record | |
303 | * (tests the fix of PR #22132: "Fix DoT timeout on multiple answer records") */ | |
304 | log_info("test-resolved-stream: A + AAAA record"); | |
305 | send_simple_question(stream, DNS_TYPE_A); | |
306 | send_simple_question(stream, DNS_TYPE_AAAA); | |
307 | ||
308 | while (n_received_packets != 2) | |
309 | assert_se(sd_event_run(event, EVENT_TIMEOUT_USEC) >= 1); | |
310 | assert_se(DNS_PACKET_DATA(received_packets[0])); | |
311 | assert_se(DNS_PACKET_DATA(received_packets[1])); | |
312 | assert_se(memcmp(DNS_PACKET_DATA(received_packets[0]), | |
313 | ANSWER_A + 2, sizeof(ANSWER_A) - 2) == 0); | |
314 | assert_se(memcmp(DNS_PACKET_DATA(received_packets[1]), | |
315 | ANSWER_AAAA + 2, sizeof(ANSWER_AAAA) - 2) == 0); | |
316 | dns_packet_unref(TAKE_PTR(received_packets[0])); | |
317 | dns_packet_unref(TAKE_PTR(received_packets[1])); | |
318 | n_received_packets = 0; | |
319 | ||
320 | #if ENABLE_DNS_OVER_TLS | |
321 | if (tls) | |
322 | dnstls_manager_free(&manager); | |
323 | #endif | |
324 | ||
325 | /* Stop the DNS server */ | |
326 | assert_se(pthread_mutex_unlock(&server_lock) == 0); | |
327 | assert_se(pthread_join(server_thread, NULL) == 0); | |
328 | assert_se(pthread_mutex_destroy(&server_lock) == 0); | |
329 | ||
330 | log_info("test-resolved-stream: Finished %s test", tls ? "TLS" : "TCP"); | |
331 | } | |
332 | ||
c76120f1 | 333 | static void try_isolate_network(void) { |
254d1313 | 334 | _cleanup_close_ int socket_fd = -EBADF; |
ed59b443 | 335 | int r; |
c76120f1 | 336 | |
ed59b443 LP |
337 | /* First test if CLONE_NEWUSER/CLONE_NEWNET can actually work for us, i.e. we can open the namespaces |
338 | * and then still access the build dir we are run from. We do that in a child process since it's | |
339 | * nasty if we have to go back from the namespace once we entered it and realized it cannot work. */ | |
340 | r = safe_fork("(usernstest)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); | |
341 | if (r == 0) { /* child */ | |
342 | _cleanup_free_ char *rt = NULL, *d = NULL; | |
343 | ||
344 | if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) { | |
345 | log_warning_errno(errno, "test-resolved-stream: Can't create user and network ns, running on host: %m"); | |
346 | _exit(EXIT_FAILURE); | |
347 | } | |
348 | ||
349 | assert_se(get_process_exe(0, &rt) >= 0); | |
350 | assert_se(path_extract_directory(rt, &d) >= 0); | |
351 | ||
352 | if (access(d, F_OK) < 0) { | |
353 | log_warning_errno(errno, "test-resolved-stream: Can't access /proc/self/exe from user/network ns, running on host: %m"); | |
354 | _exit(EXIT_FAILURE); | |
355 | } | |
356 | ||
357 | _exit(EXIT_SUCCESS); | |
c76120f1 | 358 | } |
ed59b443 LP |
359 | if (r == -EPROTO) /* EPROTO means nonzero exit code of child, i.e. the tests in the child failed */ |
360 | return; | |
361 | assert_se(r > 0); | |
362 | ||
363 | /* Now that we know that the unshare() is safe, let's actually do it */ | |
364 | assert_se(unshare(CLONE_NEWUSER | CLONE_NEWNET) >= 0); | |
c76120f1 JB |
365 | |
366 | /* Bring up the loopback interfaceon the newly created network namespace */ | |
367 | struct ifreq req = { .ifr_ifindex = 1 }; | |
368 | assert_se((socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) >= 0); | |
72dce85a | 369 | assert_se(ioctl(socket_fd, SIOCGIFNAME, &req) >= 0); |
c76120f1 JB |
370 | assert_se(ioctl(socket_fd, SIOCGIFFLAGS, &req) >= 0); |
371 | assert_se(FLAGS_SET(req.ifr_flags, IFF_LOOPBACK)); | |
372 | req.ifr_flags |= IFF_UP; | |
373 | assert_se(ioctl(socket_fd, SIOCSIFFLAGS, &req) >= 0); | |
374 | } | |
375 | ||
726bcd81 | 376 | int main(int argc, char **argv) { |
3bf175f0 LP |
377 | server_address = (union sockaddr_union) { |
378 | .in.sin_family = AF_INET, | |
379 | .in.sin_port = htobe16(12345), | |
380 | .in.sin_addr.s_addr = htobe32(INADDR_LOOPBACK) | |
726bcd81 JB |
381 | }; |
382 | ||
383 | test_setup_logging(LOG_DEBUG); | |
384 | ||
c76120f1 JB |
385 | try_isolate_network(); |
386 | ||
726bcd81 JB |
387 | test_dns_stream(false); |
388 | #if ENABLE_DNS_OVER_TLS | |
389 | if (system("openssl version >/dev/null 2>&1") != 0) | |
390 | return log_tests_skipped("Skipping TLS test since the 'openssl' command does not seem to be available"); | |
391 | test_dns_stream(true); | |
392 | #endif | |
393 | ||
394 | return 0; | |
395 | } |