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