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