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
58using namespace std;
59
60//
61// usage - just the program's usage line
62//
63//
64void 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//
76void 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//
96int 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//
223struct 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
236void* 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//
299int 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//
445int 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