xref: /netbsd-src/external/mpl/bind/dist/bin/tests/test_client.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: test_client.c,v 1.4 2025/01/26 16:24:35 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 #include <fcntl.h>
17 #include <getopt.h>
18 #include <netdb.h>
19 #include <netinet/in.h>
20 #include <signal.h>
21 #include <stdbool.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <strings.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 
30 #include <isc/loop.h>
31 #include <isc/managers.h>
32 #include <isc/mem.h>
33 #include <isc/netaddr.h>
34 #include <isc/netmgr.h>
35 #include <isc/os.h>
36 #include <isc/sockaddr.h>
37 #include <isc/string.h>
38 #include <isc/util.h>
39 
40 typedef enum {
41 	UDP,
42 	TCP,
43 	DOT,
44 	HTTPS_POST,
45 	HTTPS_GET,
46 	HTTP_POST,
47 	HTTP_GET
48 } protocol_t;
49 
50 static const char *protocols[] = { "udp",	    "tcp",
51 				   "dot",	    "https-post",
52 				   "https-get",	    "http-plain-post",
53 				   "http-plain-get" };
54 
55 static isc_mem_t *mctx = NULL;
56 static isc_loopmgr_t *loopmgr = NULL;
57 static isc_nm_t *netmgr = NULL;
58 
59 static protocol_t protocol;
60 static const char *address;
61 static const char *port;
62 static int family = AF_UNSPEC;
63 static isc_sockaddr_t sockaddr_local;
64 static isc_sockaddr_t sockaddr_remote;
65 static int workers;
66 static int timeout;
67 static uint8_t messagebuf[2 * 65536];
68 static isc_region_t message = { .length = 0, .base = messagebuf };
69 static int out = -1;
70 
71 static isc_tlsctx_t *tls_ctx = NULL;
72 
73 static isc_result_t
74 parse_port(const char *input) {
75 	char *endptr = NULL;
76 	long val = strtol(input, &endptr, 10);
77 
78 	if ((*endptr != '\0') || (val <= 0) || (val >= 65536)) {
79 		return ISC_R_BADNUMBER;
80 	}
81 
82 	port = input;
83 
84 	return ISC_R_SUCCESS;
85 }
86 
87 static isc_result_t
88 parse_protocol(const char *input) {
89 	for (size_t i = 0; i < ARRAY_SIZE(protocols); i++) {
90 		if (!strcasecmp(input, protocols[i])) {
91 			protocol = i;
92 			return ISC_R_SUCCESS;
93 		}
94 	}
95 
96 	return ISC_R_BADNUMBER;
97 }
98 
99 static isc_result_t
100 parse_address(const char *input) {
101 	struct in6_addr in6;
102 	struct in_addr in;
103 
104 	if (inet_pton(AF_INET6, input, &in6) == 1) {
105 		family = AF_INET6;
106 		address = input;
107 		return ISC_R_SUCCESS;
108 	}
109 
110 	if (inet_pton(AF_INET, input, &in) == 1) {
111 		family = AF_INET;
112 		address = input;
113 		return ISC_R_SUCCESS;
114 	}
115 
116 	return ISC_R_BADADDRESSFORM;
117 }
118 
119 static int
120 parse_workers(const char *input) {
121 	char *endptr = NULL;
122 	long val = strtol(input, &endptr, 10);
123 
124 	if ((*endptr != '\0') || (val <= 0) || (val >= 128)) {
125 		return ISC_R_BADNUMBER;
126 	}
127 
128 	workers = val;
129 
130 	return ISC_R_SUCCESS;
131 }
132 
133 static isc_result_t
134 parse_timeout(const char *input) {
135 	char *endptr = NULL;
136 	long val = strtol(input, &endptr, 10);
137 
138 	if ((*endptr != '\0') || (val <= 0) || (val >= 120)) {
139 		return ISC_R_BADNUMBER;
140 	}
141 
142 	timeout = (in_port_t)val * 1000;
143 
144 	return ISC_R_SUCCESS;
145 }
146 
147 static isc_result_t
148 parse_input(const char *input) {
149 	int in = -1;
150 
151 	if (!strcmp(input, "-")) {
152 		in = 0;
153 	} else {
154 		in = open(input, O_RDONLY);
155 	}
156 	RUNTIME_CHECK(in >= 0);
157 
158 	message.length = read(in, message.base, sizeof(messagebuf));
159 
160 	close(in);
161 
162 	return ISC_R_SUCCESS;
163 }
164 
165 static isc_result_t
166 parse_output(const char *input) {
167 	if (!strcmp(input, "-")) {
168 		out = 1;
169 	} else {
170 		out = open(input, O_WRONLY | O_CREAT,
171 			   S_IRUSR | S_IRGRP | S_IROTH);
172 	}
173 	RUNTIME_CHECK(out >= 0);
174 
175 	return ISC_R_SUCCESS;
176 }
177 
178 static void
179 parse_options(int argc, char **argv) {
180 	char buf[ISC_NETADDR_FORMATSIZE];
181 
182 	/* Set defaults */
183 	RUNTIME_CHECK(parse_protocol("UDP") == ISC_R_SUCCESS);
184 	RUNTIME_CHECK(parse_port("53000") == ISC_R_SUCCESS);
185 	RUNTIME_CHECK(parse_address("::0") == ISC_R_SUCCESS);
186 	workers = isc_os_ncpus();
187 
188 	while (true) {
189 		int c;
190 		int option_index = 0;
191 		static struct option long_options[] = {
192 			{ "port", required_argument, NULL, 'p' },
193 			{ "address", required_argument, NULL, 'a' },
194 			{ "protocol", required_argument, NULL, 'P' },
195 			{ "workers", required_argument, NULL, 'w' },
196 			{ "timeout", required_argument, NULL, 't' },
197 			{ "input", required_argument, NULL, 'i' },
198 			{ "output", required_argument, NULL, 'o' },
199 			{ 0, 0, NULL, 0 }
200 		};
201 
202 		c = getopt_long(argc, argv, "a:p:P:w:t:i:o:", long_options,
203 				&option_index);
204 		if (c == -1) {
205 			break;
206 		}
207 
208 		switch (c) {
209 		case 'a':
210 			RUNTIME_CHECK(parse_address(optarg) == ISC_R_SUCCESS);
211 			break;
212 
213 		case 'p':
214 			RUNTIME_CHECK(parse_port(optarg) == ISC_R_SUCCESS);
215 			break;
216 
217 		case 'P':
218 			RUNTIME_CHECK(parse_protocol(optarg) == ISC_R_SUCCESS);
219 			break;
220 
221 		case 'w':
222 			RUNTIME_CHECK(parse_workers(optarg) == ISC_R_SUCCESS);
223 			break;
224 
225 		case 't':
226 			RUNTIME_CHECK(parse_timeout(optarg) == ISC_R_SUCCESS);
227 			break;
228 
229 		case 'i':
230 			RUNTIME_CHECK(parse_input(optarg) == ISC_R_SUCCESS);
231 			break;
232 
233 		case 'o':
234 			RUNTIME_CHECK(parse_output(optarg) == ISC_R_SUCCESS);
235 			break;
236 
237 		default:
238 			UNREACHABLE();
239 		}
240 	}
241 
242 	{
243 		struct addrinfo hints = {
244 			.ai_family = family,
245 			.ai_socktype = (protocol == UDP) ? SOCK_DGRAM
246 							 : SOCK_STREAM,
247 		};
248 		struct addrinfo *result = NULL;
249 		int r = getaddrinfo(address, NULL, &hints, &result);
250 		RUNTIME_CHECK(r == 0);
251 
252 		for (struct addrinfo *rp = result; rp != NULL; rp = rp->ai_next)
253 		{
254 			RUNTIME_CHECK(isc_sockaddr_fromsockaddr(&sockaddr_local,
255 								rp->ai_addr) ==
256 				      ISC_R_SUCCESS);
257 		}
258 		freeaddrinfo(result);
259 	}
260 
261 	{
262 		struct addrinfo hints = {
263 			.ai_family = family,
264 			.ai_socktype = (protocol == UDP) ? SOCK_DGRAM
265 							 : SOCK_STREAM,
266 		};
267 		struct addrinfo *result = NULL;
268 		int r = getaddrinfo(argv[optind], port, &hints, &result);
269 		RUNTIME_CHECK(r == 0);
270 
271 		for (struct addrinfo *rp = result; rp != NULL; rp = rp->ai_next)
272 		{
273 			RUNTIME_CHECK(isc_sockaddr_fromsockaddr(
274 					      &sockaddr_remote, rp->ai_addr) ==
275 				      ISC_R_SUCCESS);
276 		}
277 		freeaddrinfo(result);
278 	}
279 
280 	isc_sockaddr_format(&sockaddr_local, buf, sizeof(buf));
281 
282 	printf("Will connect from %s://%s", protocols[protocol], buf);
283 
284 	isc_sockaddr_format(&sockaddr_remote, buf, sizeof(buf));
285 
286 	printf(" to %s, %d workers\n", buf, workers);
287 }
288 
289 static void
290 setup(void) {
291 	isc_managers_create(&mctx, workers, &loopmgr, &netmgr);
292 }
293 
294 static void
295 teardown(void) {
296 	if (out > 0) {
297 		close(out);
298 	}
299 
300 	if (tls_ctx) {
301 		isc_tlsctx_free(&tls_ctx);
302 	}
303 
304 	isc_managers_destroy(&mctx, &loopmgr, &netmgr);
305 }
306 
307 static void
308 waitforsignal(void) {
309 	sigset_t sset;
310 	int sig;
311 
312 	RUNTIME_CHECK(sigemptyset(&sset) == 0);
313 	RUNTIME_CHECK(sigaddset(&sset, SIGHUP) == 0);
314 	RUNTIME_CHECK(sigaddset(&sset, SIGINT) == 0);
315 	RUNTIME_CHECK(sigaddset(&sset, SIGTERM) == 0);
316 	RUNTIME_CHECK(sigwait(&sset, &sig) == 0);
317 
318 	fprintf(stderr, "Shutting down...\n");
319 }
320 
321 static void
322 read_cb(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
323 	void *cbarg) {
324 	isc_nmhandle_t *readhandle = cbarg;
325 
326 	REQUIRE(handle != NULL);
327 	REQUIRE(eresult == ISC_R_SUCCESS || eresult == ISC_R_CANCELED ||
328 		eresult == ISC_R_EOF);
329 	REQUIRE(cbarg != NULL);
330 
331 	fprintf(stderr, "%s(..., %s, ...)\n", __func__,
332 		isc_result_totext(eresult));
333 
334 	if (eresult == ISC_R_SUCCESS) {
335 		printf("RECEIVED %u bytes\n", region->length);
336 		if (out >= 0) {
337 			ssize_t len = write(out, region->base, region->length);
338 			close(out);
339 			REQUIRE((size_t)len == region->length);
340 		}
341 	}
342 
343 	isc_nmhandle_detach(&readhandle);
344 	kill(getpid(), SIGTERM);
345 }
346 
347 static void
348 send_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
349 	REQUIRE(handle != NULL);
350 	REQUIRE(eresult == ISC_R_SUCCESS || eresult == ISC_R_CANCELED ||
351 		eresult == ISC_R_EOF);
352 	REQUIRE(cbarg == NULL);
353 }
354 
355 static void
356 connect_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
357 	isc_nmhandle_t *readhandle = NULL;
358 
359 	REQUIRE(handle != NULL);
360 	UNUSED(cbarg);
361 
362 	fprintf(stderr, "ECHO_CLIENT:%s:%s\n", __func__,
363 		isc_result_totext(eresult));
364 
365 	if (eresult != ISC_R_SUCCESS) {
366 		kill(getpid(), SIGTERM);
367 		return;
368 	}
369 
370 	isc_nmhandle_attach(handle, &readhandle);
371 	isc_nm_read(handle, read_cb, readhandle);
372 	isc_nm_send(handle, &message, send_cb, NULL);
373 }
374 
375 static void
376 run(void) {
377 	switch (protocol) {
378 	case UDP:
379 		isc_nm_udpconnect(netmgr, &sockaddr_local, &sockaddr_remote,
380 				  connect_cb, NULL, timeout);
381 		break;
382 	case TCP:
383 		isc_nm_streamdnsconnect(
384 			netmgr, &sockaddr_local, &sockaddr_remote, connect_cb,
385 			NULL, timeout, NULL, NULL, ISC_NM_PROXY_NONE, NULL);
386 		break;
387 	case DOT: {
388 		isc_tlsctx_createclient(&tls_ctx);
389 
390 		isc_nm_streamdnsconnect(
391 			netmgr, &sockaddr_local, &sockaddr_remote, connect_cb,
392 			NULL, timeout, tls_ctx, NULL, ISC_NM_PROXY_NONE, NULL);
393 		break;
394 	}
395 #if HAVE_LIBNGHTTP2
396 	case HTTP_GET:
397 	case HTTPS_GET:
398 	case HTTPS_POST:
399 	case HTTP_POST: {
400 		bool is_https = (protocol == HTTPS_POST ||
401 				 protocol == HTTPS_GET);
402 		bool is_post = (protocol == HTTPS_POST ||
403 				protocol == HTTP_POST);
404 		char req_url[256];
405 		isc_nm_http_makeuri(is_https, &sockaddr_remote, NULL, 0,
406 				    ISC_NM_HTTP_DEFAULT_PATH, req_url,
407 				    sizeof(req_url));
408 		if (is_https) {
409 			isc_tlsctx_createclient(&tls_ctx);
410 		}
411 		isc_nm_httpconnect(netmgr, &sockaddr_local, &sockaddr_remote,
412 				   req_url, is_post, connect_cb, NULL, tls_ctx,
413 				   NULL, timeout, ISC_NM_PROXY_NONE, NULL);
414 	} break;
415 #endif
416 	default:
417 		UNREACHABLE();
418 	}
419 
420 	waitforsignal();
421 }
422 
423 int
424 main(int argc, char **argv) {
425 	parse_options(argc, argv);
426 
427 	setup();
428 
429 	run();
430 
431 	teardown();
432 
433 	return EXIT_SUCCESS;
434 }
435