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