2 // Copyright 2008, George V. Neville-Neil
3 // All rights reserved.
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions
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.
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
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.
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
38 // C++ STL and other related includes
44 // Operating System and other C based includes
47 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <sys/ioctl.h>
51 #include <netinet/in.h>
53 #include <arpa/inet.h>
55 // Private include files
61 // usage - just the program's usage line
66 cout << "mctest [-r] -M clients -m client number -i interface -g multicast group -s packet size -n number -t inter-packet gap\n";
71 // usage - print out the usage with a possible message and exit
73 // \param message optional string
76 void usage(string message)
79 cerr << message << endl;
85 // absorb and record packets
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)
94 // @return 0 for 0K, -1 for error, sets errno
96 int sink(char *interface, struct in_addr *group, int pkt_size, int number,
97 int clients, int client, short base_port) {
102 struct sockaddr_in local, recvd;
105 struct in_addr lgroup;
106 struct timeval timeout;
110 if (inet_pton(AF_INET, DEFAULT_GROUP, group) < 1)
114 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
115 perror("failed to open datagram socket");
119 if ((backchan = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
120 perror("failed to open back channel socket");
124 strncpy(ifreq.ifr_name, interface, IFNAMSIZ);
125 if (ioctl(sock, SIOCGIFADDR, &ifreq) < 0) {
126 perror("no such interface");
130 memcpy(&mreq.imr_interface,
131 &((struct sockaddr_in*) &ifreq.ifr_addr)->sin_addr,
132 sizeof(struct in_addr));
134 mreq.imr_multiaddr.s_addr = group->s_addr;
135 if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
137 perror("failed to add membership");
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");
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);
153 if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0) {
154 perror("could not bind socket");
158 timeval packets[number];
161 packet = new char[pkt_size];
164 timerclear(&timeout);
165 timeout.tv_sec = TIMEOUT;
167 if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout,
168 sizeof(timeout)) < 0)
169 perror("setsockopt failed");
172 recvd_len = sizeof(recvd);
173 if (recvfrom(sock, packet, pkt_size, 0, (struct sockaddr *)&recvd,
175 if (errno == EWOULDBLOCK)
177 perror("recvfrom failed");
181 * Bandwidth limiting. If there are N clients then we want
182 * 1/N packets from each, otherwise the clients will overwhelm
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");
193 gettimeofday(&packets[ntohl(*(int *)packet)], 0);
197 cout << "Packet run complete\n";
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;
214 cout << "maximum gap (usecs): " << maxgap << endl;
215 cout << "minimum gap (usecs): " << mingap << endl;
221 // Structure to hold thread arguments
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
231 // server receives packets sent back from the sink
233 // @param passed ///< Arguments passed from the caller
235 // 0return always NULL
236 void* server(void *passed) {
239 struct timeval timeout;
240 struct sockaddr_in addr;
241 server_args *args = (server_args *)passed;
243 timerclear(&timeout);
244 timeout.tv_sec = TIMEOUT;
246 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
247 perror("could not open server socket");
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);
257 if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
258 perror("could not bind server socket");
262 if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout,
263 sizeof(timeout)) < 0)
264 perror("setsockopt failed");
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)
271 perror("recvfrom failed");
274 gettimeofday(&args->packets[ntohl(*(int *)packet)], 0);
278 cout << "Packet Reflection Complete" << endl;
280 if (n < args->number)
281 cout << "Missed " << args->number - n << " packets." << endl;
288 // transmit packets for the multicast test
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
297 // @return 0 for OK, -1 for error, sets errno
299 int source(char *interface, struct in_addr *group, int pkt_size,
300 int number, int gap, int clients, short base_port) {
303 struct sockaddr_in addr;
306 struct in_addr lgroup;
310 if (inet_pton(AF_INET, DEFAULT_GROUP, group) < 1)
314 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
315 perror("could not open dgram socket");
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);
325 strncpy(ifreq.ifr_name, interface, IFNAMSIZ);
326 if (ioctl(sock, SIOCGIFADDR, &ifreq) < 0) {
327 perror("no such interface");
331 memcpy(&mreq.imr_interface,
332 &((struct sockaddr_in*) &ifreq.ifr_addr)->sin_addr,
333 sizeof(struct in_addr));
335 mreq.imr_multiaddr.s_addr = group->s_addr;
336 if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
338 perror("failed to add membership");
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");
351 if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
352 &ttl, sizeof(ttl)) < 0) {
353 perror("failed to set TTL");
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);
363 struct timeval sent[number];
364 struct timeval received[clients][number];
365 server_args args[clients];
366 pthread_t thread[clients];
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");
379 struct timespec sleeptime;
380 sleeptime.tv_sec = 0;
381 sleeptime.tv_nsec = gap;
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");
389 gettimeofday(&sent[i], 0);
391 if (nanosleep(&sleeptime, NULL) < 0) {
392 perror("nanosleep failed");
397 for (int i = 0; i < clients; i++) {
398 if (pthread_join(thread[i], NULL) != 0) {
399 perror("failed to join thread");
406 double idx[] = { .0001, .001, .01, .1, .5, .9, .99, .999, .9999, 0.0 };
408 for (int client = 0;client < clients; client++) {
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)
415 if (&args[client].packets[i].tv_sec == 0)
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;
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())]);
437 // main - the main program
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
445 int main(int argc, char**argv)
448 const int MAXNSECS = 999999999; ///< Must be < 1.0 x 10**9 nanoseconds
450 char ch; ///< character from getopt()
451 extern char* optarg; ///< option argument
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
463 if (argc < 2 || argc > 16)
466 while ((ch = getopt(argc, argv, "M:m:g:i:n:s:t:b:rh")) != -1) {
469 group = new (struct in_addr );
470 if (inet_pton(AF_INET, optarg, group) < 1)
471 usage(argv[0] + string(" Error: invalid multicast group") +
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"));
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"));
491 if (gap < 0 || gap > MAXNSECS)
492 usage(argv[0] + string(" Error: ") + optarg +
493 string(" gap out of range"));
499 client = atoi(optarg);
502 clients = atoi(optarg);
505 base_port = atoi(optarg);
508 usage(string("Help\n"));
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,
520 usage("must specify number of clients (-M)");
521 source(interface, group, pkt_size, number, gap, clients,