1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2025 Oxide Computer Company 14 */ 15 16 /* 17 * This is a mini/silent version of netcat. It's used by a few of the networking 18 * tests in this folder to make sure we exercise a full TCP state machine by 19 * calling shutdown(SHUT_RD) specifically to make sure that we can hit the 20 * TCPS_LAST_ACK state. 21 * 22 * It basically sets up an event port and tracks attempting to: 23 * 24 * - Connect to a target source 25 * - Read some data from the target source (but doesn't care how much) 26 * - Call shutdown for read 27 * - Close the socket 28 * 29 * We put a 5 second timeout on all of these as an arbitrary threshold. 30 */ 31 32 #include <err.h> 33 #include <stdlib.h> 34 #include <port.h> 35 #include <sys/socket.h> 36 #include <netdb.h> 37 #include <port.h> 38 #include <sys/debug.h> 39 #include <errno.h> 40 41 uint32_t timeout_sec = 5; 42 const char *hello = "Hello World"; 43 44 static void 45 msnc_wait(int port, int sock, int event) 46 { 47 port_event_t pe; 48 timespec_t ts; 49 50 if (port_associate(port, PORT_SOURCE_FD, sock, event, NULL) != 0) { 51 err(EXIT_FAILURE, "failed to associate for event 0x%x", event); 52 } 53 54 ts.tv_sec = (time_t)timeout_sec; 55 ts.tv_nsec = 0; 56 if (port_get(port, &pe, &ts) != 0) { 57 err(EXIT_FAILURE, "failed to wait for event 0x%x", event); 58 } 59 60 VERIFY3U(pe.portev_source, ==, PORT_SOURCE_FD); 61 VERIFY3U(pe.portev_object, ==, sock); 62 if ((pe.portev_events & event) == 0) { 63 err(EXIT_FAILURE, "got events we weren't going to handle: " 64 "0x%x, wanted 0x%x", event, event); 65 } 66 } 67 68 int 69 main(int argc, char *argv[]) 70 { 71 int ret, cerr, sock, eport; 72 struct addrinfo *res, hints; 73 socklen_t sz = sizeof (cerr); 74 uint8_t buf[16]; 75 76 if (argc != 3) { 77 (void) fprintf(stderr, "missing required arguments\n"); 78 errx(EXIT_FAILURE, "msnc: Usage <ip> <port>"); 79 } 80 81 (void) memset(&hints, 0, sizeof (struct addrinfo)); 82 hints.ai_flags = AI_NUMERICHOST | AI_ADDRCONFIG; 83 hints.ai_family = PF_UNSPEC; 84 hints.ai_socktype = SOCK_STREAM; 85 86 if ((ret = getaddrinfo(argv[1], argv[2], &hints, &res)) != 0) { 87 errx(EXIT_FAILURE, "failed to resolve %s: %s", argv[1], 88 gai_strerror(ret)); 89 } 90 91 sock = socket(res->ai_family, res->ai_socktype | SOCK_NONBLOCK, 92 res->ai_protocol); 93 if (sock < 0) { 94 err(EXIT_FAILURE, "failed to create socket"); 95 } 96 97 eport = port_create(); 98 if (eport < 0) { 99 err(EXIT_FAILURE, "failed to create event port"); 100 } 101 102 ret = connect(sock, res->ai_addr, res->ai_addrlen); 103 if (ret != 0 && errno != EINPROGRESS && errno != EINTR) { 104 err(EXIT_FAILURE, "failed to connect to %s:%s\n", argv[1], 105 argv[2]); 106 } 107 108 msnc_wait(eport, sock, POLLOUT); 109 if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &cerr, &sz) != 0) { 110 err(EXIT_FAILURE, "failed to get connection status sockopt"); 111 } 112 113 if (write(sock, hello, strlen(hello)) < 0) { 114 err(EXIT_FAILURE, "failed to write any data to the socket"); 115 } 116 117 /* 118 * Read some bytes, the exact quantity doesn't matter. But before that 119 * perform our shutdown() to help get the state machine in motion. 120 */ 121 msnc_wait(eport, sock, POLLIN); 122 if (shutdown(sock, SHUT_WR) != 0) { 123 err(EXIT_FAILURE, "failed to shutdown write side socket"); 124 } 125 126 if (read(sock, buf, sizeof (buf)) < 0) { 127 err(EXIT_FAILURE, "failed to read any data from remote side"); 128 } 129 130 VERIFY0(close(sock)); 131 VERIFY0(close(eport)); 132 return (EXIT_SUCCESS); 133 } 134