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: src/tools/tools/netrate/netblast/netblast.c,v 1.5 2011/11/08 17:23:43 cognet Exp $
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 
43 static void
44 usage(void)
45 {
46 
47 	fprintf(stderr, "netblast [ip] [port] [payloadsize] [duration]\n");
48 	exit(-1);
49 }
50 
51 static int	global_stop_flag;
52 
53 static void
54 signal_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  */
67 static int
68 blast_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 
141 int
142 main(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