1 // SPDX-License-Identifier: GPL-2.0 2 3 #define _GNU_SOURCE 4 5 #include <arpa/inet.h> 6 #include <error.h> 7 #include <errno.h> 8 #include <limits.h> 9 #include <linux/errqueue.h> 10 #include <linux/if_packet.h> 11 #include <linux/socket.h> 12 #include <linux/sockios.h> 13 #include <net/ethernet.h> 14 #include <net/if.h> 15 #include <netinet/ip.h> 16 #include <netinet/ip6.h> 17 #include <netinet/tcp.h> 18 #include <netinet/udp.h> 19 #include <poll.h> 20 #include <sched.h> 21 #include <stdbool.h> 22 #include <stdio.h> 23 #include <stdint.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <sys/ioctl.h> 27 #include <sys/socket.h> 28 #include <sys/stat.h> 29 #include <sys/time.h> 30 #include <sys/types.h> 31 #include <sys/wait.h> 32 #include <unistd.h> 33 34 static int cfg_port = 8000; 35 static bool cfg_tcp; 36 static bool cfg_verify; 37 38 static bool interrupted; 39 static unsigned long packets, bytes; 40 41 static void sigint_handler(int signum) 42 { 43 if (signum == SIGINT) 44 interrupted = true; 45 } 46 47 static unsigned long gettimeofday_ms(void) 48 { 49 struct timeval tv; 50 51 gettimeofday(&tv, NULL); 52 return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); 53 } 54 55 static void do_poll(int fd) 56 { 57 struct pollfd pfd; 58 int ret; 59 60 pfd.events = POLLIN; 61 pfd.revents = 0; 62 pfd.fd = fd; 63 64 do { 65 ret = poll(&pfd, 1, 10); 66 if (ret == -1) 67 error(1, errno, "poll"); 68 if (ret == 0) 69 continue; 70 if (pfd.revents != POLLIN) 71 error(1, errno, "poll: 0x%x expected 0x%x\n", 72 pfd.revents, POLLIN); 73 } while (!ret && !interrupted); 74 } 75 76 static int do_socket(bool do_tcp) 77 { 78 struct sockaddr_in6 addr = {0}; 79 int fd, val; 80 81 fd = socket(PF_INET6, cfg_tcp ? SOCK_STREAM : SOCK_DGRAM, 0); 82 if (fd == -1) 83 error(1, errno, "socket"); 84 85 val = 1 << 21; 86 if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val))) 87 error(1, errno, "setsockopt rcvbuf"); 88 val = 1; 89 if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val))) 90 error(1, errno, "setsockopt reuseport"); 91 92 addr.sin6_family = PF_INET6; 93 addr.sin6_port = htons(cfg_port); 94 addr.sin6_addr = in6addr_any; 95 if (bind(fd, (void *) &addr, sizeof(addr))) 96 error(1, errno, "bind"); 97 98 if (do_tcp) { 99 int accept_fd = fd; 100 101 if (listen(accept_fd, 1)) 102 error(1, errno, "listen"); 103 104 do_poll(accept_fd); 105 106 fd = accept(accept_fd, NULL, NULL); 107 if (fd == -1) 108 error(1, errno, "accept"); 109 if (close(accept_fd)) 110 error(1, errno, "close accept fd"); 111 } 112 113 return fd; 114 } 115 116 /* Flush all outstanding bytes for the tcp receive queue */ 117 static void do_flush_tcp(int fd) 118 { 119 int ret; 120 121 while (true) { 122 /* MSG_TRUNC flushes up to len bytes */ 123 ret = recv(fd, NULL, 1 << 21, MSG_TRUNC | MSG_DONTWAIT); 124 if (ret == -1 && errno == EAGAIN) 125 return; 126 if (ret == -1) 127 error(1, errno, "flush"); 128 if (ret == 0) { 129 /* client detached */ 130 exit(0); 131 } 132 133 packets++; 134 bytes += ret; 135 } 136 137 } 138 139 static char sanitized_char(char val) 140 { 141 return (val >= 'a' && val <= 'z') ? val : '.'; 142 } 143 144 static void do_verify_udp(const char *data, int len) 145 { 146 char cur = data[0]; 147 int i; 148 149 /* verify contents */ 150 if (cur < 'a' || cur > 'z') 151 error(1, 0, "data initial byte out of range"); 152 153 for (i = 1; i < len; i++) { 154 if (cur == 'z') 155 cur = 'a'; 156 else 157 cur++; 158 159 if (data[i] != cur) 160 error(1, 0, "data[%d]: len %d, %c(%hhu) != %c(%hhu)\n", 161 i, len, 162 sanitized_char(data[i]), data[i], 163 sanitized_char(cur), cur); 164 } 165 } 166 167 /* Flush all outstanding datagrams. Verify first few bytes of each. */ 168 static void do_flush_udp(int fd) 169 { 170 static char rbuf[ETH_DATA_LEN]; 171 int ret, len, budget = 256; 172 173 len = cfg_verify ? sizeof(rbuf) : 0; 174 while (budget--) { 175 /* MSG_TRUNC will make return value full datagram length */ 176 ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT); 177 if (ret == -1 && errno == EAGAIN) 178 return; 179 if (ret == -1) 180 error(1, errno, "recv"); 181 if (len) { 182 if (ret == 0) 183 error(1, errno, "recv: 0 byte datagram\n"); 184 185 do_verify_udp(rbuf, ret); 186 } 187 188 packets++; 189 bytes += ret; 190 } 191 } 192 193 static void usage(const char *filepath) 194 { 195 error(1, 0, "Usage: %s [-tv] [-p port]", filepath); 196 } 197 198 static void parse_opts(int argc, char **argv) 199 { 200 int c; 201 202 while ((c = getopt(argc, argv, "ptv")) != -1) { 203 switch (c) { 204 case 'p': 205 cfg_port = htons(strtoul(optarg, NULL, 0)); 206 break; 207 case 't': 208 cfg_tcp = true; 209 break; 210 case 'v': 211 cfg_verify = true; 212 break; 213 } 214 } 215 216 if (optind != argc) 217 usage(argv[0]); 218 219 if (cfg_tcp && cfg_verify) 220 error(1, 0, "TODO: implement verify mode for tcp"); 221 } 222 223 static void do_recv(void) 224 { 225 unsigned long tnow, treport; 226 int fd; 227 228 fd = do_socket(cfg_tcp); 229 230 treport = gettimeofday_ms() + 1000; 231 do { 232 do_poll(fd); 233 234 if (cfg_tcp) 235 do_flush_tcp(fd); 236 else 237 do_flush_udp(fd); 238 239 tnow = gettimeofday_ms(); 240 if (tnow > treport) { 241 if (packets) 242 fprintf(stderr, 243 "%s rx: %6lu MB/s %8lu calls/s\n", 244 cfg_tcp ? "tcp" : "udp", 245 bytes >> 20, packets); 246 bytes = packets = 0; 247 treport = tnow + 1000; 248 } 249 250 } while (!interrupted); 251 252 if (close(fd)) 253 error(1, errno, "close"); 254 } 255 256 int main(int argc, char **argv) 257 { 258 parse_opts(argc, argv); 259 260 signal(SIGINT, sigint_handler); 261 262 do_recv(); 263 264 return 0; 265 } 266