mctest.cc revision 179528
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: head/tools/tools/mctest/mctest.cc 179528 2008-06-03 20:54:46Z gnn $"); 37 38// C++ STL and other related includes 39#include <iostream> 40#include <string> 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 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) 407 continue; 408 timersub(&args[client].packets[i], &sent[i], &result); 409 cout << "sec: " << result.tv_sec; 410 cout << " usecs: " << result.tv_usec << endl; 411 } 412 } 413 414 return 0; 415} 416 417 418// 419// main - the main program 420// 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 425// 426// 427int main(int argc, char**argv) 428{ 429 430 const int MAXNSECS = 999999999; ///< Must be < 1.0 x 10**9 nanoseconds 431 432 char ch; ///< character from getopt() 433 extern char* optarg; ///< option argument 434 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 444 445 if (argc < 2 || argc > 16) 446 usage(); 447 448 while ((ch = getopt(argc, argv, "M:m:g:i:n:s:t:b:rh")) != -1) { 449 switch (ch) { 450 case 'g': 451 group = new (struct in_addr ); 452 if (inet_pton(AF_INET, optarg, group) < 1) 453 usage(argv[0] + string(" Error: invalid multicast group") + 454 optarg); 455 break; 456 case 'i': 457 interface = optarg; 458 break; 459 case 'n': 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")); 464 break; 465 case 's': 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")); 470 break; 471 case 't': 472 gap = atoi(optarg); 473 if (gap < 0 || gap > MAXNSECS) 474 usage(argv[0] + string(" Error: ") + optarg + 475 string(" gap out of range")); 476 break; 477 case 'r': 478 server = true; 479 break; 480 case 'm': 481 client = atoi(optarg); 482 break; 483 case 'M': 484 clients = atoi(optarg); 485 break; 486 case 'b': 487 base_port = atoi(optarg); 488 break; 489 case 'h': 490 usage(string("Help\n")); 491 break; 492 } 493 } 494 495 if (server) { 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, 499 base_port); 500 } else { 501 if (clients <= 0) 502 usage("must specify number of clients (-M)"); 503 source(interface, group, pkt_size, number, gap, clients, 504 base_port); 505 } 506 507} 508