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
42 // Operating System and other C based includes
45 #include <sys/types.h>
47 #include <sys/socket.h>
48 #include <sys/ioctl.h>
49 #include <netinet/in.h>
51 #include <arpa/inet.h>
53 // Private include files
59 // usage - just the program's usage line
64 cout << "mctest [-r] -M clients -m client number -i interface -g multicast group -s packet size -n number -t inter-packet gap\n";
69 // usage - print out the usage with a possible message and exit
71 // \param message optional string
74 void usage(string message)
77 cerr << message << endl;
83 // absorb and record packets
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)
92 // @return 0 for 0K, -1 for error, sets errno
94 int sink(char *interface, struct in_addr *group, int pkt_size, int number,
95 int clients, int client, short base_port) {
100 struct sockaddr_in local, recvd;
103 struct in_addr lgroup;
104 struct timeval timeout;
108 if (inet_pton(AF_INET, DEFAULT_GROUP, group) < 1)
112 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
113 perror("failed to open datagram socket");
117 if ((backchan = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
118 perror("failed to open back channel socket");
122 strncpy(ifreq.ifr_name, interface, IFNAMSIZ);
123 if (ioctl(sock, SIOCGIFADDR, &ifreq) < 0) {
124 perror("no such interface");
128 memcpy(&mreq.imr_interface,
129 &((struct sockaddr_in*) &ifreq.ifr_addr)->sin_addr,
130 sizeof(struct in_addr));
132 mreq.imr_multiaddr.s_addr = group->s_addr;
133 if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
135 perror("failed to add membership");
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");
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);
151 if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0) {
152 perror("could not bind socket");
156 timeval packets[number];
159 packet = new char[pkt_size];
162 timerclear(&timeout);
163 timeout.tv_sec = TIMEOUT;
165 if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout,
166 sizeof(timeout)) < 0)
167 perror("setsockopt failed");
170 recvd_len = sizeof(recvd);
171 if (recvfrom(sock, packet, pkt_size, 0, (struct sockaddr *)&recvd,
173 if (errno == EWOULDBLOCK)
175 perror("recvfrom failed");
179 * Bandwidth limiting. If there are N clients then we want
180 * 1/N packets from each, otherwise the clients will overwhelm
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");
191 gettimeofday(&packets[ntohl(*(int *)packet)], 0);
195 cout << "Packet run complete\n";
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;
212 cout << "maximum gap (usecs): " << maxgap << endl;
213 cout << "minimum gap (usecs): " << mingap << endl;
219 // Structure to hold thread arguments
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
229 // server receives packets sent back from the sink
231 // @param passed ///< Arguments passed from the caller
233 // 0return always NULL
234 void* server(void *passed) {
237 struct timeval timeout;
238 struct sockaddr_in addr;
239 server_args *args = (server_args *)passed;
241 timerclear(&timeout);
242 timeout.tv_sec = TIMEOUT;
244 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
245 perror("could not open server socket");
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);
255 if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
256 perror("could not bind server socket");
260 if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout,
261 sizeof(timeout)) < 0)
262 perror("setsockopt failed");
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)
269 perror("recvfrom failed");
272 gettimeofday(&args->packets[ntohl(*(int *)packet)], 0);
276 cout << "Packet Reflection Complete" << endl;
278 if (n < args->number)
279 cout << "Missed " << args->number - n << " packets." << endl;
286 // transmit packets for the multicast test
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
295 // @return 0 for OK, -1 for error, sets errno
297 int source(char *interface, struct in_addr *group, int pkt_size,
298 int number, int gap, int clients, short base_port) {
301 struct sockaddr_in addr;
304 struct in_addr lgroup;
308 if (inet_pton(AF_INET, DEFAULT_GROUP, group) < 1)
312 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
313 perror("could not open dgram socket");
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);
323 strncpy(ifreq.ifr_name, interface, IFNAMSIZ);
324 if (ioctl(sock, SIOCGIFADDR, &ifreq) < 0) {
325 perror("no such interface");
329 memcpy(&mreq.imr_interface,
330 &((struct sockaddr_in*) &ifreq.ifr_addr)->sin_addr,
331 sizeof(struct in_addr));
333 mreq.imr_multiaddr.s_addr = group->s_addr;
334 if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
336 perror("failed to add membership");
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");
349 if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
350 &ttl, sizeof(ttl)) < 0) {
351 perror("failed to set TTL");
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);
361 struct timeval sent[number];
362 struct timeval received[clients][number];
363 server_args args[clients];
364 pthread_t thread[clients];
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");
377 struct timespec sleeptime;
378 sleeptime.tv_sec = 0;
379 sleeptime.tv_nsec = gap;
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");
387 gettimeofday(&sent[i], 0);
389 if (nanosleep(&sleeptime, NULL) < 0) {
390 perror("nanosleep failed");
395 for (int i = 0; i < clients; i++) {
396 if (pthread_join(thread[i], NULL) < 0) {
397 perror("failed to join thread");
403 for (int client = 0;client < clients; client++) {
404 cout << "Results from client #" << client << endl;
405 for (int i = 0; i < number; i++) {
406 if (i % clients != client)
408 timersub(&args[client].packets[i], &sent[i], &result);
409 cout << "sec: " << result.tv_sec;
410 cout << " usecs: " << result.tv_usec << endl;
419 // main - the main program
421 // \param -g multicast group address to which to send/recv packets on
422 // \param -n the number of packets to send
423 // \param -s packet size in bytes
424 // \param -t inter-packet gap, in nanoseconds
427 int main(int argc, char**argv)
430 const int MAXNSECS = 999999999; ///< Must be < 1.0 x 10**9 nanoseconds
432 char ch; ///< character from getopt()
433 extern char* optarg; ///< option argument
435 char* interface = 0; ///< Name of the interface
436 struct in_addr *group = NULL; ///< the multicast group address
437 int pkt_size = 0; ///< packet size
438 int gap = 0; ///< inter packet gap (in nanoseconds)
439 int number = 0; ///< number of packets to transmit
440 bool server = false; ///< are we on he receiving end of multicast
441 int client = 0; ///< for receivers which client are we
442 int clients = 1; ///< for senders how many clients are there
443 short base_port = SERVER_PORT; ///< to have multiple copies running at once
445 if (argc < 2 || argc > 16)
448 while ((ch = getopt(argc, argv, "M:m:g:i:n:s:t:b:rh")) != -1) {
451 group = new (struct in_addr );
452 if (inet_pton(AF_INET, optarg, group) < 1)
453 usage(argv[0] + string(" Error: invalid multicast group") +
460 number = atoi(optarg);
461 if (number < 0 || number > INT_MAX)
462 usage(argv[0] + string(" Error: ") + optarg +
463 string(" number of packets out of range"));
466 pkt_size = atoi(optarg);
467 if (pkt_size < 0 || pkt_size > 65535)
468 usage(argv[0] + string(" Error: ") + optarg +
469 string(" packet size out of range"));
473 if (gap < 0 || gap > MAXNSECS)
474 usage(argv[0] + string(" Error: ") + optarg +
475 string(" gap out of range"));
481 client = atoi(optarg);
484 clients = atoi(optarg);
487 base_port = atoi(optarg);
490 usage(string("Help\n"));
496 if (clients <= 0 || client < 0)
497 usage("must specify client (-m) and number of clients (-M)");
498 sink(interface, group, pkt_size, number, clients, client,
502 usage("must specify number of clients (-M)");
503 source(interface, group, pkt_size, number, gap, clients,