]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/test-resolved-stream.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / resolve / test-resolved-stream.c
CommitLineData
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 35static 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 */
38static 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};
42static 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};
46static 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};
51static 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 */
62static 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
78static 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 */
84static 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
96static 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
109static 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 */
125static 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
181static const char *TEST_DOMAIN = "example.com";
182static const uint64_t EVENT_TIMEOUT_USEC = 5 * 1000 * 1000;
183
184static 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
198static const size_t MAX_RECEIVED_PACKETS = 2;
199static DnsPacket *received_packets[2] = {};
200static size_t n_received_packets = 0;
201
624f907e 202static 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
208static int on_stream_complete_do_nothing(DnsStream *s, int error) {
209 return 0;
210}
211
726bcd81
JB
212static 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 333static 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 376int 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}