xref: /freebsd/tools/tools/netrate/netsend/netsend.c (revision d6b92ffa)
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