| .. | .. |
|---|
| 31 | 31 | #include <sys/wait.h> |
|---|
| 32 | 32 | #include <unistd.h> |
|---|
| 33 | 33 | |
|---|
| 34 | +#ifndef UDP_GRO |
|---|
| 35 | +#define UDP_GRO 104 |
|---|
| 36 | +#endif |
|---|
| 37 | + |
|---|
| 34 | 38 | static int cfg_port = 8000; |
|---|
| 35 | 39 | static bool cfg_tcp; |
|---|
| 36 | 40 | static bool cfg_verify; |
|---|
| 41 | +static bool cfg_read_all; |
|---|
| 42 | +static bool cfg_gro_segment; |
|---|
| 43 | +static int cfg_family = PF_INET6; |
|---|
| 44 | +static int cfg_alen = sizeof(struct sockaddr_in6); |
|---|
| 45 | +static int cfg_expected_pkt_nr; |
|---|
| 46 | +static int cfg_expected_pkt_len; |
|---|
| 47 | +static int cfg_expected_gso_size; |
|---|
| 48 | +static int cfg_connect_timeout_ms; |
|---|
| 49 | +static int cfg_rcv_timeout_ms; |
|---|
| 50 | +static struct sockaddr_storage cfg_bind_addr; |
|---|
| 37 | 51 | |
|---|
| 38 | 52 | static bool interrupted; |
|---|
| 39 | 53 | static unsigned long packets, bytes; |
|---|
| .. | .. |
|---|
| 44 | 58 | interrupted = true; |
|---|
| 45 | 59 | } |
|---|
| 46 | 60 | |
|---|
| 61 | +static void setup_sockaddr(int domain, const char *str_addr, void *sockaddr) |
|---|
| 62 | +{ |
|---|
| 63 | + struct sockaddr_in6 *addr6 = (void *) sockaddr; |
|---|
| 64 | + struct sockaddr_in *addr4 = (void *) sockaddr; |
|---|
| 65 | + |
|---|
| 66 | + switch (domain) { |
|---|
| 67 | + case PF_INET: |
|---|
| 68 | + addr4->sin_family = AF_INET; |
|---|
| 69 | + addr4->sin_port = htons(cfg_port); |
|---|
| 70 | + if (inet_pton(AF_INET, str_addr, &(addr4->sin_addr)) != 1) |
|---|
| 71 | + error(1, 0, "ipv4 parse error: %s", str_addr); |
|---|
| 72 | + break; |
|---|
| 73 | + case PF_INET6: |
|---|
| 74 | + addr6->sin6_family = AF_INET6; |
|---|
| 75 | + addr6->sin6_port = htons(cfg_port); |
|---|
| 76 | + if (inet_pton(AF_INET6, str_addr, &(addr6->sin6_addr)) != 1) |
|---|
| 77 | + error(1, 0, "ipv6 parse error: %s", str_addr); |
|---|
| 78 | + break; |
|---|
| 79 | + default: |
|---|
| 80 | + error(1, 0, "illegal domain"); |
|---|
| 81 | + } |
|---|
| 82 | +} |
|---|
| 83 | + |
|---|
| 47 | 84 | static unsigned long gettimeofday_ms(void) |
|---|
| 48 | 85 | { |
|---|
| 49 | 86 | struct timeval tv; |
|---|
| .. | .. |
|---|
| 52 | 89 | return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); |
|---|
| 53 | 90 | } |
|---|
| 54 | 91 | |
|---|
| 55 | | -static void do_poll(int fd) |
|---|
| 92 | +static void do_poll(int fd, int timeout_ms) |
|---|
| 56 | 93 | { |
|---|
| 57 | 94 | struct pollfd pfd; |
|---|
| 58 | 95 | int ret; |
|---|
| .. | .. |
|---|
| 63 | 100 | |
|---|
| 64 | 101 | do { |
|---|
| 65 | 102 | ret = poll(&pfd, 1, 10); |
|---|
| 103 | + if (interrupted) |
|---|
| 104 | + break; |
|---|
| 66 | 105 | if (ret == -1) |
|---|
| 67 | 106 | error(1, errno, "poll"); |
|---|
| 68 | | - if (ret == 0) |
|---|
| 107 | + if (ret == 0) { |
|---|
| 108 | + if (!timeout_ms) |
|---|
| 109 | + continue; |
|---|
| 110 | + |
|---|
| 111 | + timeout_ms -= 10; |
|---|
| 112 | + if (timeout_ms <= 0) { |
|---|
| 113 | + interrupted = true; |
|---|
| 114 | + break; |
|---|
| 115 | + } |
|---|
| 116 | + |
|---|
| 117 | + /* no events and more time to wait, do poll again */ |
|---|
| 69 | 118 | continue; |
|---|
| 119 | + } |
|---|
| 70 | 120 | if (pfd.revents != POLLIN) |
|---|
| 71 | 121 | error(1, errno, "poll: 0x%x expected 0x%x\n", |
|---|
| 72 | 122 | pfd.revents, POLLIN); |
|---|
| 73 | | - } while (!ret && !interrupted); |
|---|
| 123 | + } while (!ret); |
|---|
| 74 | 124 | } |
|---|
| 75 | 125 | |
|---|
| 76 | 126 | static int do_socket(bool do_tcp) |
|---|
| 77 | 127 | { |
|---|
| 78 | | - struct sockaddr_in6 addr = {0}; |
|---|
| 79 | 128 | int fd, val; |
|---|
| 80 | 129 | |
|---|
| 81 | | - fd = socket(PF_INET6, cfg_tcp ? SOCK_STREAM : SOCK_DGRAM, 0); |
|---|
| 130 | + fd = socket(cfg_family, cfg_tcp ? SOCK_STREAM : SOCK_DGRAM, 0); |
|---|
| 82 | 131 | if (fd == -1) |
|---|
| 83 | 132 | error(1, errno, "socket"); |
|---|
| 84 | 133 | |
|---|
| .. | .. |
|---|
| 89 | 138 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val))) |
|---|
| 90 | 139 | error(1, errno, "setsockopt reuseport"); |
|---|
| 91 | 140 | |
|---|
| 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))) |
|---|
| 141 | + if (bind(fd, (void *)&cfg_bind_addr, cfg_alen)) |
|---|
| 96 | 142 | error(1, errno, "bind"); |
|---|
| 97 | 143 | |
|---|
| 98 | 144 | if (do_tcp) { |
|---|
| .. | .. |
|---|
| 101 | 147 | if (listen(accept_fd, 1)) |
|---|
| 102 | 148 | error(1, errno, "listen"); |
|---|
| 103 | 149 | |
|---|
| 104 | | - do_poll(accept_fd); |
|---|
| 150 | + do_poll(accept_fd, cfg_connect_timeout_ms); |
|---|
| 151 | + if (interrupted) |
|---|
| 152 | + exit(0); |
|---|
| 105 | 153 | |
|---|
| 106 | 154 | fd = accept(accept_fd, NULL, NULL); |
|---|
| 107 | 155 | if (fd == -1) |
|---|
| .. | .. |
|---|
| 164 | 212 | } |
|---|
| 165 | 213 | } |
|---|
| 166 | 214 | |
|---|
| 215 | +static int recv_msg(int fd, char *buf, int len, int *gso_size) |
|---|
| 216 | +{ |
|---|
| 217 | + char control[CMSG_SPACE(sizeof(int))] = {0}; |
|---|
| 218 | + struct msghdr msg = {0}; |
|---|
| 219 | + struct iovec iov = {0}; |
|---|
| 220 | + struct cmsghdr *cmsg; |
|---|
| 221 | + int ret; |
|---|
| 222 | + |
|---|
| 223 | + iov.iov_base = buf; |
|---|
| 224 | + iov.iov_len = len; |
|---|
| 225 | + |
|---|
| 226 | + msg.msg_iov = &iov; |
|---|
| 227 | + msg.msg_iovlen = 1; |
|---|
| 228 | + |
|---|
| 229 | + msg.msg_control = control; |
|---|
| 230 | + msg.msg_controllen = sizeof(control); |
|---|
| 231 | + |
|---|
| 232 | + *gso_size = -1; |
|---|
| 233 | + ret = recvmsg(fd, &msg, MSG_TRUNC | MSG_DONTWAIT); |
|---|
| 234 | + if (ret != -1) { |
|---|
| 235 | + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; |
|---|
| 236 | + cmsg = CMSG_NXTHDR(&msg, cmsg)) { |
|---|
| 237 | + if (cmsg->cmsg_level == SOL_UDP |
|---|
| 238 | + && cmsg->cmsg_type == UDP_GRO) { |
|---|
| 239 | + *gso_size = *(int *)CMSG_DATA(cmsg); |
|---|
| 240 | + break; |
|---|
| 241 | + } |
|---|
| 242 | + } |
|---|
| 243 | + } |
|---|
| 244 | + return ret; |
|---|
| 245 | +} |
|---|
| 246 | + |
|---|
| 167 | 247 | /* Flush all outstanding datagrams. Verify first few bytes of each. */ |
|---|
| 168 | 248 | static void do_flush_udp(int fd) |
|---|
| 169 | 249 | { |
|---|
| 170 | | - static char rbuf[ETH_DATA_LEN]; |
|---|
| 171 | | - int ret, len, budget = 256; |
|---|
| 250 | + static char rbuf[ETH_MAX_MTU]; |
|---|
| 251 | + int ret, len, gso_size = 0, budget = 256; |
|---|
| 172 | 252 | |
|---|
| 173 | | - len = cfg_verify ? sizeof(rbuf) : 0; |
|---|
| 253 | + len = cfg_read_all ? sizeof(rbuf) : 0; |
|---|
| 174 | 254 | while (budget--) { |
|---|
| 175 | 255 | /* MSG_TRUNC will make return value full datagram length */ |
|---|
| 176 | | - ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT); |
|---|
| 256 | + if (!cfg_expected_gso_size) |
|---|
| 257 | + ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT); |
|---|
| 258 | + else |
|---|
| 259 | + ret = recv_msg(fd, rbuf, len, &gso_size); |
|---|
| 177 | 260 | if (ret == -1 && errno == EAGAIN) |
|---|
| 178 | | - return; |
|---|
| 261 | + break; |
|---|
| 179 | 262 | if (ret == -1) |
|---|
| 180 | 263 | error(1, errno, "recv"); |
|---|
| 181 | | - if (len) { |
|---|
| 264 | + if (cfg_expected_pkt_len && ret != cfg_expected_pkt_len) |
|---|
| 265 | + error(1, 0, "recv: bad packet len, got %d," |
|---|
| 266 | + " expected %d\n", ret, cfg_expected_pkt_len); |
|---|
| 267 | + if (len && cfg_verify) { |
|---|
| 182 | 268 | if (ret == 0) |
|---|
| 183 | 269 | error(1, errno, "recv: 0 byte datagram\n"); |
|---|
| 184 | 270 | |
|---|
| 185 | 271 | do_verify_udp(rbuf, ret); |
|---|
| 186 | 272 | } |
|---|
| 273 | + if (cfg_expected_gso_size && cfg_expected_gso_size != gso_size) |
|---|
| 274 | + error(1, 0, "recv: bad gso size, got %d, expected %d " |
|---|
| 275 | + "(-1 == no gso cmsg))\n", gso_size, |
|---|
| 276 | + cfg_expected_gso_size); |
|---|
| 187 | 277 | |
|---|
| 188 | 278 | packets++; |
|---|
| 189 | 279 | bytes += ret; |
|---|
| 280 | + if (cfg_expected_pkt_nr && packets >= cfg_expected_pkt_nr) |
|---|
| 281 | + break; |
|---|
| 190 | 282 | } |
|---|
| 191 | 283 | } |
|---|
| 192 | 284 | |
|---|
| 193 | 285 | static void usage(const char *filepath) |
|---|
| 194 | 286 | { |
|---|
| 195 | | - error(1, 0, "Usage: %s [-tv] [-p port]", filepath); |
|---|
| 287 | + error(1, 0, "Usage: %s [-C connect_timeout] [-Grtv] [-b addr] [-p port]" |
|---|
| 288 | + " [-l pktlen] [-n packetnr] [-R rcv_timeout] [-S gsosize]", |
|---|
| 289 | + filepath); |
|---|
| 196 | 290 | } |
|---|
| 197 | 291 | |
|---|
| 198 | 292 | static void parse_opts(int argc, char **argv) |
|---|
| 199 | 293 | { |
|---|
| 294 | + const char *bind_addr = NULL; |
|---|
| 200 | 295 | int c; |
|---|
| 201 | 296 | |
|---|
| 202 | | - while ((c = getopt(argc, argv, "ptv")) != -1) { |
|---|
| 297 | + while ((c = getopt(argc, argv, "4b:C:Gl:n:p:rR:S:tv")) != -1) { |
|---|
| 203 | 298 | switch (c) { |
|---|
| 299 | + case '4': |
|---|
| 300 | + cfg_family = PF_INET; |
|---|
| 301 | + cfg_alen = sizeof(struct sockaddr_in); |
|---|
| 302 | + break; |
|---|
| 303 | + case 'b': |
|---|
| 304 | + bind_addr = optarg; |
|---|
| 305 | + break; |
|---|
| 306 | + case 'C': |
|---|
| 307 | + cfg_connect_timeout_ms = strtoul(optarg, NULL, 0); |
|---|
| 308 | + break; |
|---|
| 309 | + case 'G': |
|---|
| 310 | + cfg_gro_segment = true; |
|---|
| 311 | + break; |
|---|
| 312 | + case 'l': |
|---|
| 313 | + cfg_expected_pkt_len = strtoul(optarg, NULL, 0); |
|---|
| 314 | + break; |
|---|
| 315 | + case 'n': |
|---|
| 316 | + cfg_expected_pkt_nr = strtoul(optarg, NULL, 0); |
|---|
| 317 | + break; |
|---|
| 204 | 318 | case 'p': |
|---|
| 205 | | - cfg_port = htons(strtoul(optarg, NULL, 0)); |
|---|
| 319 | + cfg_port = strtoul(optarg, NULL, 0); |
|---|
| 320 | + break; |
|---|
| 321 | + case 'r': |
|---|
| 322 | + cfg_read_all = true; |
|---|
| 323 | + break; |
|---|
| 324 | + case 'R': |
|---|
| 325 | + cfg_rcv_timeout_ms = strtoul(optarg, NULL, 0); |
|---|
| 326 | + break; |
|---|
| 327 | + case 'S': |
|---|
| 328 | + cfg_expected_gso_size = strtol(optarg, NULL, 0); |
|---|
| 206 | 329 | break; |
|---|
| 207 | 330 | case 't': |
|---|
| 208 | 331 | cfg_tcp = true; |
|---|
| 209 | 332 | break; |
|---|
| 210 | 333 | case 'v': |
|---|
| 211 | 334 | cfg_verify = true; |
|---|
| 335 | + cfg_read_all = true; |
|---|
| 212 | 336 | break; |
|---|
| 337 | + default: |
|---|
| 338 | + exit(1); |
|---|
| 213 | 339 | } |
|---|
| 214 | 340 | } |
|---|
| 341 | + |
|---|
| 342 | + if (!bind_addr) |
|---|
| 343 | + bind_addr = cfg_family == PF_INET6 ? "::" : "0.0.0.0"; |
|---|
| 344 | + |
|---|
| 345 | + setup_sockaddr(cfg_family, bind_addr, &cfg_bind_addr); |
|---|
| 215 | 346 | |
|---|
| 216 | 347 | if (optind != argc) |
|---|
| 217 | 348 | usage(argv[0]); |
|---|
| .. | .. |
|---|
| 222 | 353 | |
|---|
| 223 | 354 | static void do_recv(void) |
|---|
| 224 | 355 | { |
|---|
| 356 | + int timeout_ms = cfg_tcp ? cfg_rcv_timeout_ms : cfg_connect_timeout_ms; |
|---|
| 225 | 357 | unsigned long tnow, treport; |
|---|
| 226 | 358 | int fd; |
|---|
| 227 | 359 | |
|---|
| 228 | 360 | fd = do_socket(cfg_tcp); |
|---|
| 229 | 361 | |
|---|
| 362 | + if (cfg_gro_segment && !cfg_tcp) { |
|---|
| 363 | + int val = 1; |
|---|
| 364 | + if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val))) |
|---|
| 365 | + error(1, errno, "setsockopt UDP_GRO"); |
|---|
| 366 | + } |
|---|
| 367 | + |
|---|
| 230 | 368 | treport = gettimeofday_ms() + 1000; |
|---|
| 231 | 369 | do { |
|---|
| 232 | | - do_poll(fd); |
|---|
| 370 | + do_poll(fd, timeout_ms); |
|---|
| 233 | 371 | |
|---|
| 234 | 372 | if (cfg_tcp) |
|---|
| 235 | 373 | do_flush_tcp(fd); |
|---|
| .. | .. |
|---|
| 247 | 385 | treport = tnow + 1000; |
|---|
| 248 | 386 | } |
|---|
| 249 | 387 | |
|---|
| 388 | + timeout_ms = cfg_rcv_timeout_ms; |
|---|
| 389 | + |
|---|
| 250 | 390 | } while (!interrupted); |
|---|
| 251 | 391 | |
|---|
| 392 | + if (cfg_expected_pkt_nr && (packets != cfg_expected_pkt_nr)) |
|---|
| 393 | + error(1, 0, "wrong packet number! got %ld, expected %d\n", |
|---|
| 394 | + packets, cfg_expected_pkt_nr); |
|---|
| 395 | + |
|---|
| 252 | 396 | if (close(fd)) |
|---|
| 253 | 397 | error(1, errno, "close"); |
|---|
| 254 | 398 | } |
|---|