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 <sys/time.h>
31
32#include <netinet/in.h>
33#include <netdb.h>			/* getaddrinfo */
34
35#include <signal.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>			/* close */
40
41static void
42usage(void)
43{
44
45	fprintf(stderr, "netblast [ip] [port] [payloadsize] [duration]\n");
46	exit(-1);
47}
48
49static int	global_stop_flag;
50
51static void
52signal_handler(int signum __unused)
53{
54
55	global_stop_flag = 1;
56}
57
58/*
59 * Loop that blasts packets: begin by recording time information, resetting
60 * stats.  Set the interval timer for when we want to wake up.  Then go.
61 * SIGALRM will set a flag indicating it's time to stop.  Note that there's
62 * some overhead to the signal and timer setup, so the smaller the duration,
63 * the higher the relative overhead.
64 */
65static int
66blast_loop(int s, long duration, u_char *packet, u_int packet_len)
67{
68	struct timespec starttime, tmptime;
69	struct itimerval it;
70	u_int32_t counter;
71	int send_errors, send_calls;
72
73	if (signal(SIGALRM, signal_handler) == SIG_ERR) {
74		perror("signal");
75		return (-1);
76	}
77
78	if (clock_getres(CLOCK_REALTIME, &tmptime) == -1) {
79		perror("clock_getres");
80		return (-1);
81	}
82
83	if (clock_gettime(CLOCK_REALTIME, &starttime) == -1) {
84		perror("clock_gettime");
85		return (-1);
86	}
87
88	it.it_interval.tv_sec = 0;
89	it.it_interval.tv_usec = 0;
90	it.it_value.tv_sec = duration;
91	it.it_value.tv_usec = 0;
92
93	if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
94		perror("setitimer");
95		return (-1);
96	}
97
98	send_errors = send_calls = 0;
99	counter = 0;
100	while (global_stop_flag == 0) {
101		/*
102		 * We maintain and, if there's room, send a counter.  Note
103		 * that even if the error is purely local, we still increment
104		 * the counter, so missing sequence numbers on the receive
105		 * side should not be assumed to be packets lost in transit.
106		 * For example, if the UDP socket gets back an ICMP from a
107		 * previous send, the error will turn up the current send
108		 * operation, causing the current sequence number also to be
109		 * skipped.
110		 */
111		if (packet_len >= 4) {
112			be32enc(packet, counter);
113			counter++;
114		}
115		if (send(s, packet, packet_len, 0) < 0)
116			send_errors++;
117		send_calls++;
118	}
119
120	if (clock_gettime(CLOCK_REALTIME, &tmptime) == -1) {
121		perror("clock_gettime");
122		return (-1);
123	}
124
125	printf("\n");
126	printf("start:             %zd.%09lu\n", starttime.tv_sec,
127	    starttime.tv_nsec);
128	printf("finish:            %zd.%09lu\n", tmptime.tv_sec,
129	    tmptime.tv_nsec);
130	printf("send calls:        %d\n", send_calls);
131	printf("send errors:       %d\n", send_errors);
132	printf("approx send rate:  %ld\n", (send_calls - send_errors) /
133	    duration);
134	printf("approx error rate: %d\n", (send_errors / send_calls));
135
136	return (0);
137}
138
139int
140main(int argc, char *argv[])
141{
142	long payloadsize, duration;
143	struct addrinfo hints, *res, *res0;
144	char *dummy, *packet;
145	int port, s, error;
146	const char *cause = NULL;
147
148	if (argc != 5)
149		usage();
150
151	memset(&hints, 0, sizeof(hints));
152	hints.ai_family = PF_UNSPEC;
153	hints.ai_socktype = SOCK_DGRAM;
154
155	port = strtoul(argv[2], &dummy, 10);
156	if (port < 1 || port > 65535 || *dummy != '\0') {
157		fprintf(stderr, "Invalid port number: %s\n", argv[2]);
158		usage();
159		/*NOTREACHED*/
160	}
161
162	payloadsize = strtoul(argv[3], &dummy, 10);
163	if (payloadsize < 0 || *dummy != '\0')
164		usage();
165	if (payloadsize > 32768) {
166		fprintf(stderr, "payloadsize > 32768\n");
167		return (-1);
168		/*NOTREACHED*/
169	}
170
171	duration = strtoul(argv[4], &dummy, 10);
172	if (duration < 0 || *dummy != '\0') {
173		fprintf(stderr, "Invalid duration time: %s\n", argv[4]);
174		usage();
175		/*NOTREACHED*/
176	}
177
178	packet = malloc(payloadsize);
179	if (packet == NULL) {
180		perror("malloc");
181		return (-1);
182		/*NOTREACHED*/
183	}
184
185	bzero(packet, payloadsize);
186	error = getaddrinfo(argv[1],argv[2], &hints, &res0);
187	if (error) {
188		perror(gai_strerror(error));
189		return (-1);
190		/*NOTREACHED*/
191	}
192	s = -1;
193	for (res = res0; res; res = res->ai_next) {
194		s = socket(res->ai_family, res->ai_socktype, 0);
195		if (s < 0) {
196			cause = "socket";
197			continue;
198		}
199
200		if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
201			cause = "connect";
202			close(s);
203			s = -1;
204			continue;
205		}
206
207		break;  /* okay we got one */
208	}
209	if (s < 0) {
210		perror(cause);
211		return (-1);
212		/*NOTREACHED*/
213	}
214
215	freeaddrinfo(res0);
216
217	return (blast_loop(s, duration, packet, payloadsize));
218
219}
220