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