xref: /freebsd/tools/tools/mctest/mctest.cc (revision c697fb7f)
1 //
2 //  Copyright 2008, George V. Neville-Neil
3 //  All rights reserved.
4 //
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions
8 // are met:
9 // 1. Redistributions of source code must retain the above copyright
10 //    notice, this list of conditions and the following disclaimer.
11 // 2. Redistributions in binary form must reproduce the above copyright
12 //    notice, this list of conditions and the following disclaimer in the
13 //    documentation and/or other materials provided with the distribution.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 // ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 // SUCH DAMAGE.
26 //
27 //  This is a relatively simple multicast test which can act as a
28 //  source and sink.  The purpose of this test is to determine the
29 //  latency between two hosts, the source and the sink.  The programs
30 //  expect to be run somewhat unsynchronized hosts.  The source and
31 //  the sink both record the time on their own machine and then the
32 //  sink will correlate the data at the end of the run.
33 //
34 
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
37 
38 // C++ STL and other related includes
39 #include <iostream>
40 #include <string>
41 #include <vector>
42 #include <algorithm>
43 
44 // Operating System and other C based includes
45 #include <unistd.h>
46 #include <errno.h>
47 #include <sys/types.h>
48 #include <sys/time.h>
49 #include <sys/socket.h>
50 #include <sys/ioctl.h>
51 #include <netinet/in.h>
52 #include <net/if.h>
53 #include <arpa/inet.h>
54 
55 // Private include files
56 #include "mctest.h"
57 
58 using namespace std;
59 
60 //
61 // usage - just the program's usage line
62 //
63 //
64 void usage()
65 {
66     cout << "mctest [-r] -M clients -m client number -i interface -g multicast group -s packet size -n number -t inter-packet gap\n";
67     exit(-1);
68 }
69 
70 //
71 // usage - print out the usage with a possible message and exit
72 //
73 // \param message optional string
74 //
75 //
76 void usage(string message)
77 {
78 
79     cerr << message << endl;
80     usage();
81 }
82 
83 
84 //
85 // absorb and record packets
86 //
87 // @param interface             ///< text name of the interface (em0 etc.)
88 // @param group			///< multicast group
89 // @param pkt_size		///< packet Size
90 // @param number                ///< number of packets we're expecting
91 // @param clients               ///< total number of clients  (N)
92 // @param client		///< our client number (0..N)
93 //
94 // @return 0 for 0K, -1 for error, sets errno
95 //
96 int sink(char *interface, struct in_addr *group, int pkt_size, int number,
97 	 int clients, int client, short base_port) {
98 
99 
100     int sock, backchan;
101     socklen_t recvd_len;
102     struct sockaddr_in local, recvd;
103     struct ip_mreq mreq;
104     struct ifreq ifreq;
105     struct in_addr lgroup;
106     struct timeval timeout;
107 
108     if (group == NULL) {
109 	group = &lgroup;
110 	if (inet_pton(AF_INET, DEFAULT_GROUP, group) < 1)
111 	    return (-1);
112     }
113 
114     if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
115 	perror("failed to open datagram socket");
116 	return (-1);
117     }
118 
119     if ((backchan = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
120 	perror("failed to open back channel socket");
121 	return (-1);
122     }
123 
124     strncpy(ifreq.ifr_name, interface, IFNAMSIZ);
125     if (ioctl(sock, SIOCGIFADDR, &ifreq) < 0) {
126 	perror("no such interface");
127 	return (-1);
128     }
129 
130     memcpy(&mreq.imr_interface,
131 	   &((struct sockaddr_in*) &ifreq.ifr_addr)->sin_addr,
132 	   sizeof(struct in_addr));
133 
134     mreq.imr_multiaddr.s_addr = group->s_addr;
135     if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
136 		   sizeof(mreq)) < 0) {
137 	perror("failed to add membership");
138 	return (-1);
139     }
140 
141     if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF,
142 		   &((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr,
143 		   sizeof(struct in_addr)) < 0) {
144 	perror("failed to bind interface");
145 	return (-1);
146     }
147 
148     local.sin_family = AF_INET;
149     local.sin_addr.s_addr = group->s_addr;
150     local.sin_port = htons(DEFAULT_PORT);
151     local.sin_len = sizeof(local);
152 
153     if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0) {
154 	perror("could not bind socket");
155 	return (-1);
156     }
157 
158     timeval packets[number];
159     timeval result;
160     char *packet;
161     packet = new char[pkt_size];
162     int n = 0;
163 
164     timerclear(&timeout);
165     timeout.tv_sec = TIMEOUT;
166 
167     if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout,
168 		   sizeof(timeout)) < 0)
169 	perror("setsockopt failed");
170 
171     while (n < number) {
172 	recvd_len = sizeof(recvd);
173 	if (recvfrom(sock, packet, pkt_size, 0, (struct sockaddr *)&recvd,
174 		     &recvd_len) < 0) {
175 	    if (errno == EWOULDBLOCK)
176 		break;
177 	    perror("recvfrom failed");
178 	    return -1;
179 	}
180 	/*
181 	 * Bandwidth limiting.  If there are N clients then we want
182 	 * 1/N packets from each, otherwise the clients will overwhelm
183 	 * the sender.
184 	 */
185 	if (n % clients == client) {
186 		recvd.sin_port = htons(base_port + client);
187 		if (sendto(backchan, packet, pkt_size, 0,
188 			   (struct sockaddr *)&recvd, sizeof(recvd)) < 0) {
189 		    perror("sendto failed");
190 		    return -1;
191 		}
192 	}
193 	gettimeofday(&packets[ntohl(*(int *)packet)], 0);
194 	n++;
195     }
196 
197     cout << "Packet run complete\n";
198     if (n < number)
199 	cout << "Missed " << number - n << " packets." << endl;
200     long maxgap = 0, mingap= INT_MAX;
201     for (int i = 0; i < number; i++) {
202 	cout << "sec: " << packets[i].tv_sec << "  usec: " <<
203 	    packets[i].tv_usec << endl;
204 	if (i < number - 1) {
205 	    timersub(&packets[i+1], &packets[i], &result);
206 	    long gap = (result.tv_sec * 1000000) + result.tv_usec;
207 	    if (gap > maxgap)
208 		maxgap = gap;
209 	    if (gap < mingap)
210 		mingap = gap;
211 	}
212     }
213 
214     cout << "maximum gap (usecs): " << maxgap << endl;
215     cout << "minimum gap (usecs): " << mingap << endl;
216     return 0;
217 
218 }
219 
220 //
221 // Structure to hold thread arguments
222 //
223 struct server_args {
224     struct timeval *packets; 	///< The timestamps of returning packets
225     int number;			///< Number of packets to expect.
226     int pkt_size;		///< Size of the packets
227     int client;			///< Which client we listen for
228 };
229 
230 //
231 // server receives packets sent back from the sink
232 //
233 // @param passed		///< Arguments passed from the caller
234 //
235 // 0return  always NULL
236 void* server(void *passed) {
237 
238     int sock, n =0;
239     struct timeval timeout;
240     struct sockaddr_in addr;
241     server_args *args = (server_args *)passed;
242 
243     timerclear(&timeout);
244     timeout.tv_sec = TIMEOUT;
245 
246     if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
247 	perror("could not open server socket");
248 	return NULL;
249     }
250 
251     bzero(&addr, sizeof(addr));
252     addr.sin_family = AF_INET;
253     addr.sin_addr.s_addr = INADDR_ANY;
254     addr.sin_port = htons(args->client);
255     addr.sin_len = sizeof(addr);
256 
257     if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
258 	perror("could not bind server socket");
259 	return NULL;
260     }
261 
262     if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout,
263 		   sizeof(timeout)) < 0)
264 	perror("setsockopt failed");
265 
266     char packet[args->pkt_size];
267     while (n < args->number) {
268 	if (recvfrom(sock, &packet, args->pkt_size, 0, NULL, 0) < 0) {
269 	    if (errno == EWOULDBLOCK)
270 		break;
271 	    perror("recvfrom failed");
272 	    return NULL;
273 	}
274 	gettimeofday(&args->packets[ntohl(*(int *)packet)], 0);
275 	n++;
276     }
277 
278     cout << "Packet Reflection Complete" << endl;
279 
280     if (n < args->number)
281 	cout << "Missed " << args->number - n << " packets." << endl;
282 
283     return NULL;
284 
285 }
286 
287 //
288 // transmit packets for the multicast test
289 //
290 // @param interface             ///< text name of the interface (em0 etc.)
291 // @param group			///< multicast group
292 // @param pkt_size		///< packet size
293 // @param number                ///< number of packets
294 // @param gap			///< inter packet gap in nano-seconds
295 // @param clients		///< number of clients we intend to run
296 //
297 // @return 0 for OK, -1 for error, sets errno
298 //
299 int source(char *interface, struct in_addr *group, int pkt_size,
300 	   int number, int gap, int clients, short base_port) {
301 
302     int sock;
303     struct sockaddr_in addr;
304     struct ip_mreq mreq;
305     struct ifreq ifreq;
306     struct in_addr lgroup;
307 
308     if (group == NULL) {
309 	group = &lgroup;
310 	if (inet_pton(AF_INET, DEFAULT_GROUP, group) < 1)
311 	    return (-1);
312     }
313 
314     if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
315 	perror("could not open dgram socket");
316 	return (-1);
317     }
318 
319     bzero(&addr, sizeof(addr));
320     addr.sin_family = AF_INET;
321     addr.sin_port = htons(DEFAULT_PORT);
322     addr.sin_addr.s_addr = group->s_addr;
323     addr.sin_len = sizeof(addr);
324 
325     strncpy(ifreq.ifr_name, interface, IFNAMSIZ);
326     if (ioctl(sock, SIOCGIFADDR, &ifreq) < 0) {
327 	perror("no such interface");
328 	return (-1);
329     }
330 
331     memcpy(&mreq.imr_interface,
332 	   &((struct sockaddr_in*) &ifreq.ifr_addr)->sin_addr,
333 	   sizeof(struct in_addr));
334 
335     mreq.imr_multiaddr.s_addr = group->s_addr;
336     if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
337 		   sizeof(mreq)) < 0) {
338 	perror("failed to add membership");
339 	return (-1);
340     }
341 
342     if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF,
343 		   &((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr,
344 		   sizeof(struct in_addr)) < 0) {
345 	perror("failed to bind interface");
346 	return (-1);
347     }
348 
349     u_char ttl = 64;
350 
351     if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
352 		   &ttl, sizeof(ttl)) < 0) {
353 	perror("failed to set TTL");
354 	return (-1);
355     }
356 
357     char *packets[number];
358     for (int i = 0;i < number; i++) {
359 	packets[i] = new char[pkt_size];
360 	*(int *)packets[i] = htonl(i);
361     }
362 
363     struct timeval sent[number];
364     struct timeval received[clients][number];
365     server_args args[clients];
366     pthread_t thread[clients];
367 
368     for (int i = 0;i < clients; i++) {
369         args[i].pkt_size = pkt_size;
370         args[i].packets = received[i];
371         args[i].number = number / clients;
372 	args[i].client = base_port + i;
373 	if (pthread_create(&thread[i], NULL, server, &args[i]) != 0) {
374 	    perror("failed to create server thread");
375 	    return -1;
376         }
377     }
378 
379     struct timespec sleeptime;
380     sleeptime.tv_sec = 0;
381     sleeptime.tv_nsec = gap;
382 
383     for (int i = 0;i < number; i++) {
384 	if (sendto(sock, (void *)packets[i], pkt_size, 0,
385 		   (struct sockaddr *)&addr, sizeof(addr)) < 0) {
386 	    perror("sendto failed");
387 	    return -1;
388 	}
389 	gettimeofday(&sent[i], 0);
390 	if (gap > 0)
391 	    if (nanosleep(&sleeptime, NULL) < 0) {
392 		perror("nanosleep failed");
393 		return -1;
394 	    }
395     }
396 
397     for (int i = 0; i < clients; i++) {
398         if (pthread_join(thread[i], NULL) != 0) {
399  	    perror("failed to join thread");
400  	    return -1;
401         }
402     }
403 
404     timeval result;
405     vector<int> deltas;
406     double idx[] = { .0001, .001, .01, .1, .5, .9, .99, .999, .9999, 0.0 };
407 
408     for (int client = 0;client < clients; client++) {
409 	deltas.clear();
410 	cout << "Results from client #" << client << endl;
411 	cout << "in usecs" << endl;
412         for (int i = 0; i < number; i++) {
413 // 	    if (i % clients != client)
414 // 		continue;
415             if (&args[client].packets[i].tv_sec == 0)
416 			continue;
417 	    timersub(&args[client].packets[i], &sent[i], &result);
418 	    deltas.push_back(result.tv_usec);
419 // 	    cout << "sec: " << result.tv_sec;
420 // 	    cout << " usecs: " << result.tv_usec << endl;
421             }
422 	cout << "comparing " << long(deltas.size()) << " deltas" << endl;
423 	cout << "number represents usecs of round-trip time" << endl;
424 	sort(deltas.begin(), deltas.end());
425 	for (int i = 0; idx[i] != 0; ++i) {
426 		printf("%s% 5d", (i == 0) ? "" : " ",
427 		       deltas[(int) (idx[i] * deltas.size())]);
428 	}
429 	printf("\n");
430     }
431 
432     return 0;
433 }
434 
435 
436 //
437 // main - the main program
438 //
439 // \param -g multicast group address to which to send/recv packets on
440 // \param -n the number of packets to send
441 // \param -s packet size in bytes
442 // \param -t inter-packet gap, in nanoseconds
443 //
444 //
445 int main(int argc, char**argv)
446 {
447 
448 	const int MAXNSECS = 999999999; ///< Must be < 1.0 x 10**9 nanoseconds
449 
450 	char ch;		///< character from getopt()
451 	extern char* optarg;	///< option argument
452 
453 	char* interface = 0;    ///< Name of the interface
454 	struct in_addr *group = NULL;	///< the multicast group address
455 	int pkt_size = 0;       ///< packet size
456 	int gap = 0;		///< inter packet gap (in nanoseconds)
457 	int number = 0;         ///< number of packets to transmit
458 	bool server = false;	///< are we on he receiving end of multicast
459 	int client = 0;		///< for receivers which client are we
460 	int clients = 1;	///< for senders how many clients are there
461 	short base_port = SERVER_PORT; ///< to have multiple copies running at once
462 
463 	if (argc < 2 || argc > 16)
464 		usage();
465 
466 	while ((ch = getopt(argc, argv, "M:m:g:i:n:s:t:b:rh")) != -1) {
467 		switch (ch) {
468 		case 'g':
469 			group = new (struct in_addr );
470 			if (inet_pton(AF_INET, optarg, group) < 1)
471 				usage(argv[0] + string(" Error: invalid multicast group") +
472 				      optarg);
473 			break;
474 		case 'i':
475 			interface = optarg;
476 			break;
477 		case 'n':
478 			number = atoi(optarg);
479 			if (number < 0 || number > INT_MAX)
480 				usage(argv[0] + string(" Error: ") + optarg +
481 				      string(" number of packets out of range"));
482 			break;
483 		case 's':
484 			pkt_size = atoi(optarg);
485 			if (pkt_size < 0 || pkt_size > 65535)
486 				usage(argv[0] + string(" Error: ") + optarg +
487 				      string(" packet size out of range"));
488 			break;
489 		case 't':
490 			gap = atoi(optarg);
491 			if (gap < 0 || gap > MAXNSECS)
492 				usage(argv[0] + string(" Error: ") + optarg +
493 				      string(" gap out of range"));
494 			break;
495 		case 'r':
496 			server = true;
497 			break;
498 		case 'm':
499 			client = atoi(optarg);
500 			break;
501 		case 'M':
502 			clients = atoi(optarg);
503 			break;
504 		case 'b':
505 			base_port = atoi(optarg);
506 			break;
507 		case 'h':
508 			usage(string("Help\n"));
509 			break;
510 		}
511 	}
512 
513 	if (server) {
514 	    if (clients <= 0 || client < 0)
515 		usage("must specify client (-m) and number of clients (-M)");
516 	    sink(interface, group, pkt_size, number, clients, client,
517 		 base_port);
518 	} else {
519 	    if (clients <= 0)
520 		usage("must specify number of clients (-M)");
521 	    source(interface, group, pkt_size, number, gap, clients,
522 		   base_port);
523 	}
524 
525 }
526