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