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