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