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