.. | .. |
---|
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(uint16_t))] = {0}; |
---|
| 218 | + struct msghdr msg = {0}; |
---|
| 219 | + struct iovec iov = {0}; |
---|
| 220 | + struct cmsghdr *cmsg; |
---|
| 221 | + uint16_t *gsosizeptr; |
---|
| 222 | + int ret; |
---|
| 223 | + |
---|
| 224 | + iov.iov_base = buf; |
---|
| 225 | + iov.iov_len = len; |
---|
| 226 | + |
---|
| 227 | + msg.msg_iov = &iov; |
---|
| 228 | + msg.msg_iovlen = 1; |
---|
| 229 | + |
---|
| 230 | + msg.msg_control = control; |
---|
| 231 | + msg.msg_controllen = sizeof(control); |
---|
| 232 | + |
---|
| 233 | + *gso_size = -1; |
---|
| 234 | + ret = recvmsg(fd, &msg, MSG_TRUNC | MSG_DONTWAIT); |
---|
| 235 | + if (ret != -1) { |
---|
| 236 | + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; |
---|
| 237 | + cmsg = CMSG_NXTHDR(&msg, cmsg)) { |
---|
| 238 | + if (cmsg->cmsg_level == SOL_UDP |
---|
| 239 | + && cmsg->cmsg_type == UDP_GRO) { |
---|
| 240 | + gsosizeptr = (uint16_t *) CMSG_DATA(cmsg); |
---|
| 241 | + *gso_size = *gsosizeptr; |
---|
| 242 | + break; |
---|
| 243 | + } |
---|
| 244 | + } |
---|
| 245 | + } |
---|
| 246 | + return ret; |
---|
| 247 | +} |
---|
| 248 | + |
---|
167 | 249 | /* Flush all outstanding datagrams. Verify first few bytes of each. */ |
---|
168 | 250 | static void do_flush_udp(int fd) |
---|
169 | 251 | { |
---|
170 | | - static char rbuf[ETH_DATA_LEN]; |
---|
171 | | - int ret, len, budget = 256; |
---|
| 252 | + static char rbuf[ETH_MAX_MTU]; |
---|
| 253 | + int ret, len, gso_size, budget = 256; |
---|
172 | 254 | |
---|
173 | | - len = cfg_verify ? sizeof(rbuf) : 0; |
---|
| 255 | + len = cfg_read_all ? sizeof(rbuf) : 0; |
---|
174 | 256 | while (budget--) { |
---|
175 | 257 | /* MSG_TRUNC will make return value full datagram length */ |
---|
176 | | - ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT); |
---|
| 258 | + if (!cfg_expected_gso_size) |
---|
| 259 | + ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT); |
---|
| 260 | + else |
---|
| 261 | + ret = recv_msg(fd, rbuf, len, &gso_size); |
---|
177 | 262 | if (ret == -1 && errno == EAGAIN) |
---|
178 | | - return; |
---|
| 263 | + break; |
---|
179 | 264 | if (ret == -1) |
---|
180 | 265 | error(1, errno, "recv"); |
---|
181 | | - if (len) { |
---|
| 266 | + if (cfg_expected_pkt_len && ret != cfg_expected_pkt_len) |
---|
| 267 | + error(1, 0, "recv: bad packet len, got %d," |
---|
| 268 | + " expected %d\n", ret, cfg_expected_pkt_len); |
---|
| 269 | + if (len && cfg_verify) { |
---|
182 | 270 | if (ret == 0) |
---|
183 | 271 | error(1, errno, "recv: 0 byte datagram\n"); |
---|
184 | 272 | |
---|
185 | 273 | do_verify_udp(rbuf, ret); |
---|
186 | 274 | } |
---|
| 275 | + if (cfg_expected_gso_size && cfg_expected_gso_size != gso_size) |
---|
| 276 | + error(1, 0, "recv: bad gso size, got %d, expected %d " |
---|
| 277 | + "(-1 == no gso cmsg))\n", gso_size, |
---|
| 278 | + cfg_expected_gso_size); |
---|
187 | 279 | |
---|
188 | 280 | packets++; |
---|
189 | 281 | bytes += ret; |
---|
| 282 | + if (cfg_expected_pkt_nr && packets >= cfg_expected_pkt_nr) |
---|
| 283 | + break; |
---|
190 | 284 | } |
---|
191 | 285 | } |
---|
192 | 286 | |
---|
193 | 287 | static void usage(const char *filepath) |
---|
194 | 288 | { |
---|
195 | | - error(1, 0, "Usage: %s [-tv] [-p port]", filepath); |
---|
| 289 | + error(1, 0, "Usage: %s [-C connect_timeout] [-Grtv] [-b addr] [-p port]" |
---|
| 290 | + " [-l pktlen] [-n packetnr] [-R rcv_timeout] [-S gsosize]", |
---|
| 291 | + filepath); |
---|
196 | 292 | } |
---|
197 | 293 | |
---|
198 | 294 | static void parse_opts(int argc, char **argv) |
---|
199 | 295 | { |
---|
| 296 | + const char *bind_addr = NULL; |
---|
200 | 297 | int c; |
---|
201 | 298 | |
---|
202 | | - while ((c = getopt(argc, argv, "ptv")) != -1) { |
---|
| 299 | + while ((c = getopt(argc, argv, "4b:C:Gl:n:p:rR:S:tv")) != -1) { |
---|
203 | 300 | switch (c) { |
---|
| 301 | + case '4': |
---|
| 302 | + cfg_family = PF_INET; |
---|
| 303 | + cfg_alen = sizeof(struct sockaddr_in); |
---|
| 304 | + break; |
---|
| 305 | + case 'b': |
---|
| 306 | + bind_addr = optarg; |
---|
| 307 | + break; |
---|
| 308 | + case 'C': |
---|
| 309 | + cfg_connect_timeout_ms = strtoul(optarg, NULL, 0); |
---|
| 310 | + break; |
---|
| 311 | + case 'G': |
---|
| 312 | + cfg_gro_segment = true; |
---|
| 313 | + break; |
---|
| 314 | + case 'l': |
---|
| 315 | + cfg_expected_pkt_len = strtoul(optarg, NULL, 0); |
---|
| 316 | + break; |
---|
| 317 | + case 'n': |
---|
| 318 | + cfg_expected_pkt_nr = strtoul(optarg, NULL, 0); |
---|
| 319 | + break; |
---|
204 | 320 | case 'p': |
---|
205 | | - cfg_port = htons(strtoul(optarg, NULL, 0)); |
---|
| 321 | + cfg_port = strtoul(optarg, NULL, 0); |
---|
| 322 | + break; |
---|
| 323 | + case 'r': |
---|
| 324 | + cfg_read_all = true; |
---|
| 325 | + break; |
---|
| 326 | + case 'R': |
---|
| 327 | + cfg_rcv_timeout_ms = strtoul(optarg, NULL, 0); |
---|
| 328 | + break; |
---|
| 329 | + case 'S': |
---|
| 330 | + cfg_expected_gso_size = strtol(optarg, NULL, 0); |
---|
206 | 331 | break; |
---|
207 | 332 | case 't': |
---|
208 | 333 | cfg_tcp = true; |
---|
209 | 334 | break; |
---|
210 | 335 | case 'v': |
---|
211 | 336 | cfg_verify = true; |
---|
| 337 | + cfg_read_all = true; |
---|
212 | 338 | break; |
---|
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 | } |
---|