1 /*- 2 * Copyright (c) 2004 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD$ 27 */ 28 29 #include <sys/endian.h> 30 #include <sys/types.h> 31 #include <sys/socket.h> 32 #include <net/if.h> /* if_nametoindex() */ 33 #include <sys/time.h> 34 35 #include <netinet/in.h> 36 37 #include <arpa/inet.h> 38 39 #include <stdio.h> 40 #include <stdint.h> 41 #include <stdlib.h> 42 #include <string.h> 43 44 #include <netdb.h> 45 46 /* program arguments */ 47 struct _a { 48 int s; 49 int ipv6; 50 struct timespec interval; 51 int port, port_max; 52 long duration; 53 struct sockaddr_in sin; 54 struct sockaddr_in6 sin6; 55 int packet_len; 56 void *packet; 57 }; 58 59 static void 60 usage(void) 61 { 62 63 fprintf(stderr, 64 "netsend [ip] [port[-port_max]] [payloadsize] [packet_rate] [duration]\n"); 65 exit(-1); 66 } 67 68 #define MAX_RATE 100000000 69 70 static __inline void 71 timespec_add(struct timespec *tsa, struct timespec *tsb) 72 { 73 74 tsa->tv_sec += tsb->tv_sec; 75 tsa->tv_nsec += tsb->tv_nsec; 76 if (tsa->tv_nsec >= 1000000000) { 77 tsa->tv_sec++; 78 tsa->tv_nsec -= 1000000000; 79 } 80 } 81 82 static __inline int 83 timespec_ge(struct timespec *a, struct timespec *b) 84 { 85 86 if (a->tv_sec > b->tv_sec) 87 return (1); 88 if (a->tv_sec < b->tv_sec) 89 return (0); 90 if (a->tv_nsec >= b->tv_nsec) 91 return (1); 92 return (0); 93 } 94 95 /* 96 * Busy wait spinning until we reach (or slightly pass) the desired time. 97 * Optionally return the current time as retrieved on the last time check 98 * to the caller. Optionally also increment a counter provided by the 99 * caller each time we loop. 100 */ 101 static int 102 wait_time(struct timespec ts, struct timespec *wakeup_ts, long long *waited) 103 { 104 struct timespec curtime; 105 106 curtime.tv_sec = 0; 107 curtime.tv_nsec = 0; 108 109 if (clock_gettime(CLOCK_REALTIME, &curtime) == -1) { 110 perror("clock_gettime"); 111 return (-1); 112 } 113 #if 0 114 if (timespec_ge(&curtime, &ts)) 115 printf("warning: wait_time missed deadline without spinning\n"); 116 #endif 117 while (timespec_ge(&ts, &curtime)) { 118 if (waited != NULL) 119 (*waited)++; 120 if (clock_gettime(CLOCK_REALTIME, &curtime) == -1) { 121 perror("clock_gettime"); 122 return (-1); 123 } 124 } 125 if (wakeup_ts != NULL) 126 *wakeup_ts = curtime; 127 return (0); 128 } 129 130 /* 131 * Calculate a second-aligned starting time for the packet stream. Busy 132 * wait between our calculated interval and dropping the provided packet 133 * into the socket. If we hit our duration limit, bail. 134 * We sweep the ports from a->port to a->port_max included. 135 * If the two ports are the same we connect() the socket upfront, which 136 * almost halves the cost of the sendto() call. 137 */ 138 static int 139 timing_loop(struct _a *a) 140 { 141 struct timespec nexttime, starttime, tmptime; 142 long long waited; 143 u_int32_t counter; 144 long finishtime; 145 long send_errors, send_calls; 146 /* do not call gettimeofday more than every 20us */ 147 long minres_ns = 200000; 148 int ic, gettimeofday_cycles; 149 int cur_port; 150 uint64_t n, ns; 151 152 if (clock_getres(CLOCK_REALTIME, &tmptime) == -1) { 153 perror("clock_getres"); 154 return (-1); 155 } 156 157 ns = a->interval.tv_nsec; 158 if (timespec_ge(&tmptime, &a->interval)) 159 fprintf(stderr, 160 "warning: interval (%jd.%09ld) less than resolution (%jd.%09ld)\n", 161 (intmax_t)a->interval.tv_sec, a->interval.tv_nsec, 162 (intmax_t)tmptime.tv_sec, tmptime.tv_nsec); 163 /* interval too short, limit the number of gettimeofday() 164 * calls, but also make sure there is at least one every 165 * some 100 packets. 166 */ 167 if ((long)ns < minres_ns/100) 168 gettimeofday_cycles = 100; 169 else 170 gettimeofday_cycles = minres_ns/ns; 171 fprintf(stderr, 172 "calling time every %d cycles\n", gettimeofday_cycles); 173 174 if (clock_gettime(CLOCK_REALTIME, &starttime) == -1) { 175 perror("clock_gettime"); 176 return (-1); 177 } 178 tmptime.tv_sec = 2; 179 tmptime.tv_nsec = 0; 180 timespec_add(&starttime, &tmptime); 181 starttime.tv_nsec = 0; 182 if (wait_time(starttime, NULL, NULL) == -1) 183 return (-1); 184 nexttime = starttime; 185 finishtime = starttime.tv_sec + a->duration; 186 187 send_errors = send_calls = 0; 188 counter = 0; 189 waited = 0; 190 ic = gettimeofday_cycles; 191 cur_port = a->port; 192 if (a->port == a->port_max) { 193 if (a->ipv6) { 194 if (connect(a->s, (struct sockaddr *)&a->sin6, sizeof(a->sin6))) { 195 perror("connect (ipv6)"); 196 return (-1); 197 } 198 } else { 199 if (connect(a->s, (struct sockaddr *)&a->sin, sizeof(a->sin))) { 200 perror("connect (ipv4)"); 201 return (-1); 202 } 203 } 204 } 205 while (1) { 206 int ret; 207 208 timespec_add(&nexttime, &a->interval); 209 if (--ic <= 0) { 210 ic = gettimeofday_cycles; 211 if (wait_time(nexttime, &tmptime, &waited) == -1) 212 return (-1); 213 } 214 /* 215 * We maintain and, if there's room, send a counter. Note 216 * that even if the error is purely local, we still increment 217 * the counter, so missing sequence numbers on the receive 218 * side should not be assumed to be packets lost in transit. 219 * For example, if the UDP socket gets back an ICMP from a 220 * previous send, the error will turn up the current send 221 * operation, causing the current sequence number also to be 222 * skipped. 223 * The counter is incremented only on the initial port number, 224 * so all destinations will see the same set of packets. 225 */ 226 if (cur_port == a->port && a->packet_len >= 4) { 227 be32enc(a->packet, counter); 228 counter++; 229 } 230 if (a->port == a->port_max) { /* socket already bound */ 231 ret = send(a->s, a->packet, a->packet_len, 0); 232 } else { 233 a->sin.sin_port = htons(cur_port++); 234 if (cur_port > a->port_max) 235 cur_port = a->port; 236 if (a->ipv6) { 237 ret = sendto(a->s, a->packet, a->packet_len, 0, 238 (struct sockaddr *)&a->sin6, sizeof(a->sin6)); 239 } else { 240 ret = sendto(a->s, a->packet, a->packet_len, 0, 241 (struct sockaddr *)&a->sin, sizeof(a->sin)); 242 } 243 } 244 if (ret < 0) 245 send_errors++; 246 send_calls++; 247 if (a->duration != 0 && tmptime.tv_sec >= finishtime) 248 goto done; 249 } 250 251 done: 252 if (clock_gettime(CLOCK_REALTIME, &tmptime) == -1) { 253 perror("clock_gettime"); 254 return (-1); 255 } 256 257 printf("\n"); 258 printf("start: %jd.%09ld\n", (intmax_t)starttime.tv_sec, 259 starttime.tv_nsec); 260 printf("finish: %jd.%09ld\n", (intmax_t)tmptime.tv_sec, 261 tmptime.tv_nsec); 262 printf("send calls: %ld\n", send_calls); 263 printf("send errors: %ld\n", send_errors); 264 printf("approx send rate: %ld pps\n", (send_calls - send_errors) / 265 a->duration); 266 n = send_calls - send_errors; 267 if (n > 0) { 268 ns = (tmptime.tv_sec - starttime.tv_sec) * 1000000000UL + 269 (tmptime.tv_nsec - starttime.tv_nsec); 270 n = ns / n; 271 } 272 printf("time/packet: %u ns\n", (u_int)n); 273 printf("approx error rate: %ld\n", (send_errors / send_calls)); 274 printf("waited: %lld\n", waited); 275 printf("approx waits/sec: %lld\n", (long long)(waited / a->duration)); 276 printf("approx wait rate: %lld\n", (long long)(waited / send_calls)); 277 278 return (0); 279 } 280 281 int 282 main(int argc, char *argv[]) 283 { 284 long rate, payloadsize, port; 285 char *dummy; 286 struct _a a; /* arguments */ 287 struct addrinfo hints, *res, *ressave; 288 289 bzero(&a, sizeof(a)); 290 291 if (argc != 6) 292 usage(); 293 294 memset(&hints, 0, sizeof(hints)); 295 hints.ai_family = AF_UNSPEC; 296 297 if (getaddrinfo(argv[1], NULL, &hints, &res) != 0) { 298 fprintf(stderr, "Couldn't resolv %s\n", argv[1]); 299 return (-1); 300 } 301 ressave = res; 302 while (res) { 303 if (res->ai_family == AF_INET) { 304 memcpy(&a.sin, res->ai_addr, res->ai_addrlen); 305 a.ipv6 = 0; 306 break; 307 } else if (res->ai_family == AF_INET6) { 308 memcpy(&a.sin6, res->ai_addr, res->ai_addrlen); 309 a.ipv6 = 1; 310 break; 311 } 312 res = res->ai_next; 313 } 314 if (!res) { 315 fprintf(stderr, "Couldn't resolv %s\n", argv[1]); 316 exit(1); 317 } 318 freeaddrinfo(ressave); 319 320 port = strtoul(argv[2], &dummy, 10); 321 if (port < 1 || port > 65535) 322 usage(); 323 if (*dummy != '\0' && *dummy != '-') 324 usage(); 325 if (a.ipv6) 326 a.sin6.sin6_port = htons(port); 327 else 328 a.sin.sin_port = htons(port); 329 a.port = a.port_max = port; 330 if (*dummy == '-') { /* set high port */ 331 port = strtoul(dummy + 1, &dummy, 10); 332 if (port < a.port || port > 65535) 333 usage(); 334 a.port_max = port; 335 } 336 337 payloadsize = strtoul(argv[3], &dummy, 10); 338 if (payloadsize < 0 || *dummy != '\0') 339 usage(); 340 if (payloadsize > 32768) { 341 fprintf(stderr, "payloadsize > 32768\n"); 342 return (-1); 343 } 344 a.packet_len = payloadsize; 345 346 /* 347 * Specify an arbitrary limit. It's exactly that, not selected by 348 * any particular strategy. '0' is a special value meaning "blast", 349 * and avoids the cost of a timing loop. 350 */ 351 rate = strtoul(argv[4], &dummy, 10); 352 if (rate < 0 || *dummy != '\0') 353 usage(); 354 if (rate > MAX_RATE) { 355 fprintf(stderr, "packet rate at most %d\n", MAX_RATE); 356 return (-1); 357 } 358 359 a.duration = strtoul(argv[5], &dummy, 10); 360 if (a.duration < 0 || *dummy != '\0') 361 usage(); 362 363 a.packet = malloc(payloadsize); 364 if (a.packet == NULL) { 365 perror("malloc"); 366 return (-1); 367 } 368 bzero(a.packet, payloadsize); 369 if (rate == 0) { 370 a.interval.tv_sec = 0; 371 a.interval.tv_nsec = 0; 372 } else if (rate == 1) { 373 a.interval.tv_sec = 1; 374 a.interval.tv_nsec = 0; 375 } else { 376 a.interval.tv_sec = 0; 377 a.interval.tv_nsec = ((1 * 1000000000) / rate); 378 } 379 380 printf("Sending packet of payload size %ld every %jd.%09lds for %ld " 381 "seconds\n", payloadsize, (intmax_t)a.interval.tv_sec, 382 a.interval.tv_nsec, a.duration); 383 384 if (a.ipv6) 385 a.s = socket(PF_INET6, SOCK_DGRAM, 0); 386 else 387 a.s = socket(PF_INET, SOCK_DGRAM, 0); 388 if (a.s == -1) { 389 perror("socket"); 390 return (-1); 391 } 392 393 return (timing_loop(&a)); 394 } 395