1 // SPDX-License-Identifier: GPL-2.0-only 2 /* io_uring tests for vsock 3 * 4 * Copyright (C) 2023 SberDevices. 5 * 6 * Author: Arseniy Krasnov <avkrasnov@salutedevices.com> 7 */ 8 9 #include <getopt.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <liburing.h> 14 #include <unistd.h> 15 #include <sys/mman.h> 16 #include <sys/ioctl.h> 17 #include <linux/kernel.h> 18 #include <linux/sockios.h> 19 #include <errno.h> 20 #include <error.h> 21 22 #include "util.h" 23 #include "control.h" 24 #include "msg_zerocopy_common.h" 25 26 #ifndef PAGE_SIZE 27 #define PAGE_SIZE 4096 28 #endif 29 30 #define RING_ENTRIES_NUM 4 31 32 #define VSOCK_TEST_DATA_MAX_IOV 3 33 34 #define HINT_CHUNK_SIZE 4096 35 #define HINT_BUF_GROUP 1 36 #define HINT_BUF_ENTRIES 4 37 38 struct vsock_io_uring_test { 39 /* Number of valid elements in 'vecs'. */ 40 int vecs_cnt; 41 struct iovec vecs[VSOCK_TEST_DATA_MAX_IOV]; 42 }; 43 44 static struct vsock_io_uring_test test_data_array[] = { 45 /* All elements have page aligned base and size. */ 46 { 47 .vecs_cnt = 3, 48 { 49 { NULL, PAGE_SIZE }, 50 { NULL, 2 * PAGE_SIZE }, 51 { NULL, 3 * PAGE_SIZE }, 52 } 53 }, 54 /* Middle element has both non-page aligned base and size. */ 55 { 56 .vecs_cnt = 3, 57 { 58 { NULL, PAGE_SIZE }, 59 { (void *)1, 200 }, 60 { NULL, 3 * PAGE_SIZE }, 61 } 62 } 63 }; 64 65 static void vsock_io_uring_client(const struct test_opts *opts, 66 const struct vsock_io_uring_test *test_data, 67 bool msg_zerocopy) 68 { 69 struct io_uring_sqe *sqe; 70 struct io_uring_cqe *cqe; 71 struct io_uring ring; 72 struct iovec *iovec; 73 struct msghdr msg; 74 int fd; 75 76 fd = vsock_stream_connect(opts->peer_cid, opts->peer_port); 77 if (fd < 0) { 78 perror("connect"); 79 exit(EXIT_FAILURE); 80 } 81 82 if (msg_zerocopy) 83 enable_so_zerocopy_check(fd); 84 85 iovec = alloc_test_iovec(test_data->vecs, test_data->vecs_cnt); 86 87 if (io_uring_queue_init(RING_ENTRIES_NUM, &ring, 0)) 88 error(1, errno, "io_uring_queue_init"); 89 90 if (io_uring_register_buffers(&ring, iovec, test_data->vecs_cnt)) 91 error(1, errno, "io_uring_register_buffers"); 92 93 memset(&msg, 0, sizeof(msg)); 94 msg.msg_iov = iovec; 95 msg.msg_iovlen = test_data->vecs_cnt; 96 sqe = io_uring_get_sqe(&ring); 97 98 if (msg_zerocopy) 99 io_uring_prep_sendmsg_zc(sqe, fd, &msg, 0); 100 else 101 io_uring_prep_sendmsg(sqe, fd, &msg, 0); 102 103 if (io_uring_submit(&ring) != 1) 104 error(1, errno, "io_uring_submit"); 105 106 if (io_uring_wait_cqe(&ring, &cqe)) 107 error(1, errno, "io_uring_wait_cqe"); 108 109 io_uring_cqe_seen(&ring, cqe); 110 111 control_writeulong(iovec_hash_djb2(iovec, test_data->vecs_cnt)); 112 113 control_writeln("DONE"); 114 io_uring_queue_exit(&ring); 115 free_test_iovec(test_data->vecs, iovec, test_data->vecs_cnt); 116 close(fd); 117 } 118 119 static void vsock_io_uring_server(const struct test_opts *opts, 120 const struct vsock_io_uring_test *test_data) 121 { 122 unsigned long remote_hash; 123 unsigned long local_hash; 124 struct io_uring ring; 125 size_t data_len; 126 size_t recv_len; 127 void *data; 128 int fd; 129 130 fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL); 131 if (fd < 0) { 132 perror("accept"); 133 exit(EXIT_FAILURE); 134 } 135 136 data_len = iovec_bytes(test_data->vecs, test_data->vecs_cnt); 137 138 data = malloc(data_len); 139 if (!data) { 140 perror("malloc"); 141 exit(EXIT_FAILURE); 142 } 143 144 if (io_uring_queue_init(RING_ENTRIES_NUM, &ring, 0)) 145 error(1, errno, "io_uring_queue_init"); 146 147 recv_len = 0; 148 149 while (recv_len < data_len) { 150 struct io_uring_sqe *sqe; 151 struct io_uring_cqe *cqe; 152 struct iovec iovec; 153 154 sqe = io_uring_get_sqe(&ring); 155 iovec.iov_base = data + recv_len; 156 iovec.iov_len = data_len; 157 158 io_uring_prep_readv(sqe, fd, &iovec, 1, 0); 159 160 if (io_uring_submit(&ring) != 1) 161 error(1, errno, "io_uring_submit"); 162 163 if (io_uring_wait_cqe(&ring, &cqe)) 164 error(1, errno, "io_uring_wait_cqe"); 165 166 recv_len += cqe->res; 167 io_uring_cqe_seen(&ring, cqe); 168 } 169 170 if (recv_len != data_len) { 171 fprintf(stderr, "expected %zu, got %zu\n", data_len, 172 recv_len); 173 exit(EXIT_FAILURE); 174 } 175 176 local_hash = hash_djb2(data, data_len); 177 178 remote_hash = control_readulong(); 179 if (remote_hash != local_hash) { 180 fprintf(stderr, "hash mismatch\n"); 181 exit(EXIT_FAILURE); 182 } 183 184 control_expectln("DONE"); 185 io_uring_queue_exit(&ring); 186 free(data); 187 } 188 189 void test_stream_uring_server(const struct test_opts *opts) 190 { 191 int i; 192 193 for (i = 0; i < ARRAY_SIZE(test_data_array); i++) 194 vsock_io_uring_server(opts, &test_data_array[i]); 195 } 196 197 void test_stream_uring_client(const struct test_opts *opts) 198 { 199 int i; 200 201 for (i = 0; i < ARRAY_SIZE(test_data_array); i++) 202 vsock_io_uring_client(opts, &test_data_array[i], false); 203 } 204 205 void test_stream_uring_msg_zc_server(const struct test_opts *opts) 206 { 207 int i; 208 209 for (i = 0; i < ARRAY_SIZE(test_data_array); i++) 210 vsock_io_uring_server(opts, &test_data_array[i]); 211 } 212 213 void test_stream_uring_msg_zc_client(const struct test_opts *opts) 214 { 215 int i; 216 217 for (i = 0; i < ARRAY_SIZE(test_data_array); i++) 218 vsock_io_uring_client(opts, &test_data_array[i], true); 219 } 220 221 struct uring_inq_ctx { 222 struct io_uring ring; 223 int fd; 224 }; 225 226 static void inq_server_init(struct uring_inq_ctx *ctx, 227 const struct test_opts *opts) 228 { 229 ctx->fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL); 230 if (ctx->fd < 0) { 231 perror("accept"); 232 exit(EXIT_FAILURE); 233 } 234 235 if (io_uring_queue_init(RING_ENTRIES_NUM, &ctx->ring, 0)) 236 error(1, errno, "io_uring_queue_init"); 237 } 238 239 static void inq_server_exit(struct uring_inq_ctx *ctx) 240 { 241 io_uring_queue_exit(&ctx->ring); 242 close(ctx->fd); 243 } 244 245 /* Submit a single receive and report both its result and its CQE flags. */ 246 static int inq_recv(struct uring_inq_ctx *ctx, void *buf, size_t len, 247 int flags, unsigned int *cflags) 248 { 249 struct io_uring_sqe *sqe; 250 struct io_uring_cqe *cqe; 251 int res; 252 253 sqe = io_uring_get_sqe(&ctx->ring); 254 io_uring_prep_recv(sqe, ctx->fd, buf, len, flags); 255 256 if (io_uring_submit(&ctx->ring) != 1) 257 error(1, errno, "io_uring_submit"); 258 259 if (io_uring_wait_cqe(&ctx->ring, &cqe)) 260 error(1, errno, "io_uring_wait_cqe"); 261 262 res = cqe->res; 263 *cflags = cqe->flags; 264 io_uring_cqe_seen(&ctx->ring, cqe); 265 266 return res; 267 } 268 269 static void expect_uring_nonempty(unsigned int cflags, bool expected, 270 const char *what) 271 { 272 bool nonempty = !!(cflags & IORING_CQE_F_SOCK_NONEMPTY); 273 274 if (nonempty != expected) { 275 fprintf(stderr, "%s: expected SOCK_NONEMPTY %d, got %d\n", 276 what, expected, nonempty); 277 exit(EXIT_FAILURE); 278 } 279 } 280 281 static void expect_uring_more(unsigned int cflags, bool expected, 282 const char *what) 283 { 284 bool more = !!(cflags & IORING_CQE_F_MORE); 285 286 if (more != expected) { 287 fprintf(stderr, "%s: expected F_MORE %d, got %d\n", 288 what, expected, more); 289 exit(EXIT_FAILURE); 290 } 291 } 292 293 /* Wait until the whole payload is queued, so the hint is deterministic. 294 * Return false if the test has to be skipped. 295 */ 296 static bool inq_wait_queued(int fd, int len) 297 { 298 if (!vsock_ioctl_int(fd, SIOCINQ, len)) { 299 fprintf(stderr, "Test skipped, SIOCINQ not supported.\n"); 300 return false; 301 } 302 303 return true; 304 } 305 306 static void inq_send_chunks(const struct test_opts *opts, int chunks) 307 { 308 char buf[HINT_CHUNK_SIZE]; 309 int fd, i; 310 311 fd = vsock_stream_connect(opts->peer_cid, opts->peer_port); 312 if (fd < 0) { 313 perror("connect"); 314 exit(EXIT_FAILURE); 315 } 316 317 memset(buf, 0xa5, sizeof(buf)); 318 for (i = 0; i < chunks; i++) 319 send_buf(fd, buf, sizeof(buf), 0, sizeof(buf)); 320 321 control_writeln("SENT"); 322 control_expectln("DONE"); 323 close(fd); 324 } 325 326 static void test_stream_uring_inq_client(const struct test_opts *opts) 327 { 328 inq_send_chunks(opts, 2); 329 } 330 331 static void test_stream_uring_inq_server(const struct test_opts *opts) 332 { 333 char buf[HINT_CHUNK_SIZE]; 334 struct uring_inq_ctx ctx; 335 unsigned int cflags; 336 int res; 337 338 inq_server_init(&ctx, opts); 339 340 control_expectln("SENT"); 341 if (!inq_wait_queued(ctx.fd, 2 * HINT_CHUNK_SIZE)) 342 goto out; 343 344 /* Data remains after this receive, so the flag must be set. */ 345 res = inq_recv(&ctx, buf, sizeof(buf), 0, &cflags); 346 expect_res(res, HINT_CHUNK_SIZE, "partial receive"); 347 expect_uring_nonempty(cflags, true, "partial receive"); 348 349 /* This receive drains the queue while the peer stays connected. */ 350 res = inq_recv(&ctx, buf, sizeof(buf), 0, &cflags); 351 expect_res(res, HINT_CHUNK_SIZE, "draining receive"); 352 expect_uring_nonempty(cflags, false, "draining receive"); 353 354 out: 355 control_writeln("DONE"); 356 inq_server_exit(&ctx); 357 } 358 359 static void test_stream_uring_inq_eof_client(const struct test_opts *opts) 360 { 361 char buf[HINT_CHUNK_SIZE]; 362 int fd; 363 364 fd = vsock_stream_connect(opts->peer_cid, opts->peer_port); 365 if (fd < 0) { 366 perror("connect"); 367 exit(EXIT_FAILURE); 368 } 369 370 memset(buf, 0x5a, sizeof(buf)); 371 send_buf(fd, buf, sizeof(buf), 0, sizeof(buf)); 372 control_writeln("SENT"); 373 374 control_expectln("DRAINED"); 375 close(fd); 376 control_writeln("CLOSED"); 377 } 378 379 static void test_stream_uring_inq_eof_server(const struct test_opts *opts) 380 { 381 char buf[HINT_CHUNK_SIZE]; 382 struct uring_inq_ctx ctx; 383 unsigned int cflags; 384 int res; 385 386 inq_server_init(&ctx, opts); 387 388 control_expectln("SENT"); 389 if (!inq_wait_queued(ctx.fd, HINT_CHUNK_SIZE)) { 390 control_writeln("DRAINED"); 391 control_expectln("CLOSED"); 392 goto out; 393 } 394 395 res = inq_recv(&ctx, buf, sizeof(buf), 0, &cflags); 396 expect_res(res, HINT_CHUNK_SIZE, "drain before EOF"); 397 expect_uring_nonempty(cflags, false, "drain before EOF"); 398 399 control_writeln("DRAINED"); 400 control_expectln("CLOSED"); 401 402 /* The queue is empty and the peer is gone. The hint stays non-zero 403 * so that this receive happens and reports EOF, as TCP does after a 404 * FIN. 405 */ 406 res = inq_recv(&ctx, buf, sizeof(buf), 0, &cflags); 407 expect_res(res, 0, "receive at EOF"); 408 expect_uring_nonempty(cflags, true, "receive at EOF"); 409 410 out: 411 inq_server_exit(&ctx); 412 } 413 414 static void test_stream_uring_inq_empty_client(const struct test_opts *opts) 415 { 416 int fd; 417 418 fd = vsock_stream_connect(opts->peer_cid, opts->peer_port); 419 if (fd < 0) { 420 perror("connect"); 421 exit(EXIT_FAILURE); 422 } 423 424 control_writeln("READY"); 425 control_expectln("DONE"); 426 close(fd); 427 } 428 429 static void test_stream_uring_inq_empty_server(const struct test_opts *opts) 430 { 431 char buf[HINT_CHUNK_SIZE]; 432 struct uring_inq_ctx ctx; 433 unsigned int cflags; 434 int res; 435 436 inq_server_init(&ctx, opts); 437 438 control_expectln("READY"); 439 440 /* A failed receive must not leave a stale positive hint. */ 441 res = inq_recv(&ctx, buf, sizeof(buf), MSG_DONTWAIT, &cflags); 442 expect_res(res, -EAGAIN, "empty nonblocking receive"); 443 expect_uring_nonempty(cflags, false, "empty nonblocking receive"); 444 445 control_writeln("DONE"); 446 inq_server_exit(&ctx); 447 } 448 449 static void test_stream_uring_inq_zerolen_client(const struct test_opts *opts) 450 { 451 inq_send_chunks(opts, 1); 452 } 453 454 static void test_stream_uring_inq_zerolen_server(const struct test_opts *opts) 455 { 456 char buf[HINT_CHUNK_SIZE]; 457 struct uring_inq_ctx ctx; 458 unsigned int cflags; 459 int res; 460 461 inq_server_init(&ctx, opts); 462 463 control_expectln("SENT"); 464 if (!inq_wait_queued(ctx.fd, HINT_CHUNK_SIZE)) 465 goto out; 466 467 /* A zero-length request is not an error and still describes the 468 * queue behind it. 469 */ 470 res = inq_recv(&ctx, buf, 0, 0, &cflags); 471 expect_res(res, 0, "zero-length receive"); 472 expect_uring_nonempty(cflags, true, "zero-length receive"); 473 474 out: 475 control_writeln("DONE"); 476 inq_server_exit(&ctx); 477 } 478 479 static void test_stream_uring_inq_mshot_client(const struct test_opts *opts) 480 { 481 char buf[HINT_CHUNK_SIZE]; 482 int fd, i; 483 484 fd = vsock_stream_connect(opts->peer_cid, opts->peer_port); 485 if (fd < 0) { 486 perror("connect"); 487 exit(EXIT_FAILURE); 488 } 489 490 memset(buf, 0x3c, sizeof(buf)); 491 for (i = 0; i < 2; i++) 492 send_buf(fd, buf, sizeof(buf), 0, sizeof(buf)); 493 control_writeln("SENT"); 494 495 control_expectln("DRAINED"); 496 close(fd); 497 control_writeln("CLOSED"); 498 499 control_expectln("DONE"); 500 } 501 502 static void test_stream_uring_inq_mshot_server(const struct test_opts *opts) 503 { 504 static char bufs[HINT_BUF_ENTRIES][HINT_CHUNK_SIZE]; 505 struct io_uring_buf_ring *br; 506 struct uring_inq_ctx ctx; 507 struct io_uring_sqe *sqe; 508 struct io_uring_cqe *cqe; 509 int i, ret; 510 511 inq_server_init(&ctx, opts); 512 513 br = io_uring_setup_buf_ring(&ctx.ring, HINT_BUF_ENTRIES, 514 HINT_BUF_GROUP, 0, &ret); 515 if (!br) { 516 fprintf(stderr, "io_uring_setup_buf_ring: %d\n", ret); 517 exit(EXIT_FAILURE); 518 } 519 520 for (i = 0; i < HINT_BUF_ENTRIES; i++) 521 io_uring_buf_ring_add(br, bufs[i], HINT_CHUNK_SIZE, i, 522 io_uring_buf_ring_mask(HINT_BUF_ENTRIES), 523 i); 524 io_uring_buf_ring_advance(br, HINT_BUF_ENTRIES); 525 526 control_expectln("SENT"); 527 if (!inq_wait_queued(ctx.fd, 2 * HINT_CHUNK_SIZE)) { 528 control_writeln("DRAINED"); 529 control_expectln("CLOSED"); 530 goto out; 531 } 532 533 /* Arm only once both chunks are queued, so every completion knows 534 * what is left behind it. 535 */ 536 sqe = io_uring_get_sqe(&ctx.ring); 537 io_uring_prep_recv_multishot(sqe, ctx.fd, NULL, 0, 0); 538 sqe->flags |= IOSQE_BUFFER_SELECT; 539 sqe->buf_group = HINT_BUF_GROUP; 540 541 if (io_uring_submit(&ctx.ring) != 1) 542 error(1, errno, "io_uring_submit"); 543 544 /* A buffer holds one chunk, so the other one is still queued. */ 545 if (io_uring_wait_cqe(&ctx.ring, &cqe)) 546 error(1, errno, "io_uring_wait_cqe"); 547 548 expect_res(cqe->res, HINT_CHUNK_SIZE, "multishot first chunk"); 549 expect_uring_nonempty(cqe->flags, true, "multishot first chunk"); 550 expect_uring_more(cqe->flags, true, "multishot first chunk"); 551 io_uring_cqe_seen(&ctx.ring, cqe); 552 553 /* This completion drains the queue and keeps the request armed. */ 554 if (io_uring_wait_cqe(&ctx.ring, &cqe)) 555 error(1, errno, "io_uring_wait_cqe"); 556 557 expect_res(cqe->res, HINT_CHUNK_SIZE, "multishot second chunk"); 558 expect_uring_nonempty(cqe->flags, false, "multishot second chunk"); 559 expect_uring_more(cqe->flags, true, "multishot second chunk"); 560 io_uring_cqe_seen(&ctx.ring, cqe); 561 562 control_writeln("DRAINED"); 563 control_expectln("CLOSED"); 564 565 /* EOF ends multishot regardless of the hint. */ 566 if (io_uring_wait_cqe(&ctx.ring, &cqe)) 567 error(1, errno, "io_uring_wait_cqe"); 568 569 expect_res(cqe->res, 0, "multishot EOF"); 570 expect_uring_more(cqe->flags, false, "multishot EOF"); 571 io_uring_cqe_seen(&ctx.ring, cqe); 572 573 out: 574 control_writeln("DONE"); 575 io_uring_free_buf_ring(&ctx.ring, br, HINT_BUF_ENTRIES, 576 HINT_BUF_GROUP); 577 inq_server_exit(&ctx); 578 } 579 580 static struct test_case test_cases[] = { 581 { 582 .name = "SOCK_STREAM io_uring test", 583 .run_server = test_stream_uring_server, 584 .run_client = test_stream_uring_client, 585 }, 586 { 587 .name = "SOCK_STREAM io_uring MSG_ZEROCOPY test", 588 .run_server = test_stream_uring_msg_zc_server, 589 .run_client = test_stream_uring_msg_zc_client, 590 }, 591 { 592 .name = "SOCK_STREAM io_uring receive queue hint", 593 .run_server = test_stream_uring_inq_server, 594 .run_client = test_stream_uring_inq_client, 595 }, 596 { 597 .name = "SOCK_STREAM io_uring receive hint at EOF", 598 .run_server = test_stream_uring_inq_eof_server, 599 .run_client = test_stream_uring_inq_eof_client, 600 }, 601 { 602 .name = "SOCK_STREAM io_uring receive hint on empty queue", 603 .run_server = test_stream_uring_inq_empty_server, 604 .run_client = test_stream_uring_inq_empty_client, 605 }, 606 { 607 .name = "SOCK_STREAM io_uring receive hint zero-length", 608 .run_server = test_stream_uring_inq_zerolen_server, 609 .run_client = test_stream_uring_inq_zerolen_client, 610 }, 611 { 612 .name = "SOCK_STREAM io_uring multishot receive hint", 613 .run_server = test_stream_uring_inq_mshot_server, 614 .run_client = test_stream_uring_inq_mshot_client, 615 }, 616 {}, 617 }; 618 619 static const char optstring[] = ""; 620 static const struct option longopts[] = { 621 { 622 .name = "control-host", 623 .has_arg = required_argument, 624 .val = 'H', 625 }, 626 { 627 .name = "control-port", 628 .has_arg = required_argument, 629 .val = 'P', 630 }, 631 { 632 .name = "mode", 633 .has_arg = required_argument, 634 .val = 'm', 635 }, 636 { 637 .name = "peer-cid", 638 .has_arg = required_argument, 639 .val = 'p', 640 }, 641 { 642 .name = "peer-port", 643 .has_arg = required_argument, 644 .val = 'q', 645 }, 646 { 647 .name = "help", 648 .has_arg = no_argument, 649 .val = '?', 650 }, 651 {}, 652 }; 653 654 static void usage(void) 655 { 656 fprintf(stderr, "Usage: vsock_uring_test [--help] [--control-host=<host>] --control-port=<port> --mode=client|server --peer-cid=<cid> [--peer-port=<port>]\n" 657 "\n" 658 " Server: vsock_uring_test --control-port=1234 --mode=server --peer-cid=3\n" 659 " Client: vsock_uring_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\n" 660 "\n" 661 "Run transmission tests using io_uring. Usage is the same as\n" 662 "in ./vsock_test\n" 663 "\n" 664 "Options:\n" 665 " --help This help message\n" 666 " --control-host <host> Server IP address to connect to\n" 667 " --control-port <port> Server port to listen on/connect to\n" 668 " --mode client|server Server or client mode\n" 669 " --peer-cid <cid> CID of the other side\n" 670 " --peer-port <port> AF_VSOCK port used for the test [default: %d]\n", 671 DEFAULT_PEER_PORT 672 ); 673 exit(EXIT_FAILURE); 674 } 675 676 int main(int argc, char **argv) 677 { 678 const char *control_host = NULL; 679 const char *control_port = NULL; 680 struct test_opts opts = { 681 .mode = TEST_MODE_UNSET, 682 .peer_cid = VMADDR_CID_ANY, 683 .peer_port = DEFAULT_PEER_PORT, 684 }; 685 686 init_signals(); 687 688 for (;;) { 689 int opt = getopt_long(argc, argv, optstring, longopts, NULL); 690 691 if (opt == -1) 692 break; 693 694 switch (opt) { 695 case 'H': 696 control_host = optarg; 697 break; 698 case 'm': 699 if (strcmp(optarg, "client") == 0) { 700 opts.mode = TEST_MODE_CLIENT; 701 } else if (strcmp(optarg, "server") == 0) { 702 opts.mode = TEST_MODE_SERVER; 703 } else { 704 fprintf(stderr, "--mode must be \"client\" or \"server\"\n"); 705 return EXIT_FAILURE; 706 } 707 break; 708 case 'p': 709 opts.peer_cid = parse_cid(optarg); 710 break; 711 case 'q': 712 opts.peer_port = parse_port(optarg); 713 break; 714 case 'P': 715 control_port = optarg; 716 break; 717 case '?': 718 default: 719 usage(); 720 } 721 } 722 723 if (!control_port) 724 usage(); 725 if (opts.mode == TEST_MODE_UNSET) 726 usage(); 727 if (opts.peer_cid == VMADDR_CID_ANY) 728 usage(); 729 730 if (!control_host) { 731 if (opts.mode != TEST_MODE_SERVER) 732 usage(); 733 control_host = "0.0.0.0"; 734 } 735 736 control_init(control_host, control_port, 737 opts.mode == TEST_MODE_SERVER); 738 739 run_tests(test_cases, &opts); 740 741 control_cleanup(); 742 743 return 0; 744 } 745