ns-igmp_querier.c revision df3eb16e38c6a163b0a7367c885679eed6140964
1/******************************************************************************/ 2/* */ 3/* Copyright (c) International Business Machines Corp., 2006 */ 4/* */ 5/* This program is free software; you can redistribute it and/or modify */ 6/* it under the terms of the GNU General Public License as published by */ 7/* the Free Software Foundation; either version 2 of the License, or */ 8/* (at your option) any later version. */ 9/* */ 10/* This program is distributed in the hope that it will be useful, */ 11/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See */ 13/* the GNU General Public License for more details. */ 14/* */ 15/* You should have received a copy of the GNU General Public License */ 16/* along with this program; if not, write to the Free Software */ 17/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ 18/* */ 19/******************************************************************************/ 20 21 22/* 23 * File: 24 * ns-igmp_querier.c 25 * 26 * Description: 27 * This utiltity sends IGMP queries. 28 * (General Query, Multicast Address Specific Query 29 * or Multicast Address and Source Specific Query) 30 * 31 * Author: 32 * Mitsuru Chinen <mitch@jp.ibm.com> 33 * 34 * History: 35 * Apr 24 2006 - Created (Mitsuru Chinen) 36 *---------------------------------------------------------------------------*/ 37 38/* 39 * Header Files 40 */ 41#include <stdio.h> 42#include <stdlib.h> 43#include <string.h> 44#include <errno.h> 45#include <netdb.h> 46#include <signal.h> 47#include <time.h> 48#include <unistd.h> 49#include <arpa/inet.h> 50#include <net/if.h> 51#include <netinet/in.h> 52#include <netinet/igmp.h> 53#include <sys/ioctl.h> 54#include <sys/socket.h> 55#include <sys/types.h> 56 57#include "ns-mcast.h" 58#include "ns-traffic.h" 59 60/* 61 * Structure Definitions 62 */ 63struct igmp_info { 64 uint32_t ifindex; 65 struct igmpv3_query *query; 66 double timeout; 67 struct timespec interval; 68}; 69 70 71/* 72 * Gloval variables 73 */ 74char *program_name; /* program name */ 75struct sigaction handler; /* Behavior for a signal */ 76int catch_sighup; /* When catch the SIGHUP, set to non-zero */ 77 78 79/* 80 * Function: usage() 81 * 82 * Descripton: 83 * Print the usage of this program. Then, terminate this program with 84 * the specified exit value. 85 * 86 * Argument: 87 * exit_value: exit value 88 * 89 * Return value: 90 * This function does not return. 91 */ 92void 93usage (char *program_name, int exit_value) 94{ 95 FILE *stream = stdout; /* stream where the usage is output */ 96 97 if (exit_value == EXIT_FAILURE) 98 stream = stderr; 99 100 fprintf (stream, "%s [OPTION]\n" 101 "\t-I ifname\tname of listening interface\n" 102 "\t-m addr\tmulticast address\n" 103 "\t-s addrs\tcomma separated array of Source Addresses\n" 104 "\t-r value\tMax Resp Code\n" 105 "\t-i value\tinterval [nanosec]\n" 106 "\t-t value\ttimeout [sec]\n" 107 "\t-o\t\tsend only one query\n" 108 "\t-b\t\twork in the background\n" 109 "\t-d\t\tdisplay debug informations\n" 110 "\t-h\t\tdisplay this usage\n" 111 , program_name); 112 exit (exit_value); 113} 114 115 116/* 117 * Function: set_signal_flag() 118 * 119 * Description: 120 * This function sets global variables accordig to signal 121 * 122 * Argument: 123 * type: type of signal 124 * 125 * Return value: 126 * None 127 */ 128void 129set_signal_flag(int type) 130{ 131 if (debug) 132 fprintf(stderr, "Catch signal. type is %d\n", type); 133 134 switch (type) { 135 case SIGHUP: 136 catch_sighup = 1; 137 handler.sa_handler = SIG_IGN; 138 if (sigaction(type, &handler, NULL) < 0) 139 fatal_error("sigaction()"); 140 break; 141 142 default: 143 fprintf(stderr, "Unexpected signal (%d) is caught\n", type); 144 exit(EXIT_FAILURE); 145 } 146} 147 148 149/* 150 * Function: create_query() 151 * 152 * Description: 153 * This function create a igmpv3 query information. 154 * This function allocates memory to store the information. 155 * 156 * Argument: 157 * code: Max Resp Code 158 * maddr: multicast address 159 * saddrs: comma separated array of the source addresses 160 * 161 * Return value: 162 * pointer to allocated igmpv3_query structure 163 */ 164struct igmpv3_query * 165create_query(uint8_t code, char *maddr, char *saddrs) 166{ 167 struct igmpv3_query *query; /* pointer to igmpv3_query structure */ 168 uint16_t numsrc; /* number of source address */ 169 size_t query_size; /* size of igmpv3_query */ 170 struct in_addr ip; 171 uint32_t idx; 172 char *sp, *ep; 173 174 /* calculate the number of source address */ 175 if (saddrs == NULL) { 176 numsrc = 0; 177 } else { 178 numsrc = 1; 179 for (sp = saddrs; *sp != '\0'; sp++) 180 if (*sp == ',') 181 numsrc++; 182 } 183 if (debug) 184 fprintf(stderr, "number of source address is %u\n", numsrc); 185 186 /* allocate memory for igmpv3_query structure */ 187 query_size = MY_IGMPV3_QUERY_SIZE(numsrc); 188 query = (struct igmpv3_query *)calloc(1, query_size); 189 if (query == NULL) 190 fatal_error("calloc()"); 191 192 /* substitute paramaters */ 193 query->type = IGMP_HOST_MEMBERSHIP_QUERY; 194 query->code = code; 195 query->csum = 0; /* Calculate later */ 196 query->resv = 0; 197 query->suppress = 0; 198 query->qrv = 0; 199 query->qqic = 0; 200 query->nsrcs = htons(numsrc); 201 202 /* substitute multicast address */ 203 if (maddr == NULL) { 204 query->group = htonl(INADDR_ANY); 205 } else { 206 if (inet_pton(AF_INET, maddr, &ip) <= 0) { 207 fprintf(stderr, "multicast address is something wrong\n"); 208 return NULL; 209 } 210 query->group = ip.s_addr; 211 } 212 213 /* substitute source addresses */ 214 sp = saddrs; 215 for (idx = 0; idx < numsrc; idx++) { 216 ep = strchr(sp, ','); 217 if (ep != NULL) 218 *ep = '\0'; 219 if (debug) 220 fprintf(stderr, "source address[%u]: %s\n", idx, sp); 221 222 if (inet_pton(AF_INET, sp, &ip) <= 0) { 223 fprintf(stderr, "source address list is something wrong\n"); 224 return NULL; 225 } 226 query->srcs[idx] = ip.s_addr; 227 sp = ep + 1; 228 } 229 230 /* Calculate checksum */ 231 query->csum = calc_checksum((u_int16_t *)query, query_size); 232 233 return query; 234} 235 236 237/* 238 * Function: parse_options() 239 * 240 * Description: 241 * This function parse the options 242 * 243 * Argument: 244 * argc: the number of argument 245 * argv: arguments 246 * info_p: pointer to data of querier information 247 * bg_p: pointer to the flag of working in backgrond 248 * 249 * Return value: 250 * None 251 */ 252void 253parse_options(int argc, char *argv[], struct igmp_info *info_p, int *bg_p) 254{ 255 int optc; /* option */ 256 unsigned long opt_ul; /* option value in unsigned long */ 257 double opt_d; /* option value in double */ 258 uint8_t max_resp; /* Max Resp Code */ 259 char *maddr; /* multicast address */ 260 char *saddrs; /* comma separated array of source addresses */ 261 262 max_resp = IGMP_MAX_HOST_REPORT_DELAY; 263 maddr = NULL; 264 saddrs = NULL; 265 266 while ((optc = getopt(argc, argv, "I:m:s:r:t:i:obdh")) != EOF ) { 267 switch (optc) { 268 case 'I': 269 info_p->ifindex = if_nametoindex(optarg); 270 if (info_p->ifindex == 0) { 271 fprintf(stderr, "specified interface is incorrect\n"); 272 usage(program_name, EXIT_FAILURE); 273 } 274 break; 275 276 case 'm': 277 maddr = strdup(optarg); 278 if (maddr == NULL) 279 fatal_error("strdup()"); 280 break; 281 282 case 's': 283 saddrs = strdup(optarg); 284 if (saddrs == NULL) 285 fatal_error("strdup()"); 286 break; 287 288 case 'r': 289 opt_ul=strtoul(optarg, NULL, 0); 290 if (opt_ul > 255) { 291 fprintf(stderr, "Max Resp Code should be less then 256\n"); 292 usage(program_name, EXIT_FAILURE); 293 } 294 max_resp = opt_ul; 295 break; 296 297 case 't': 298 opt_d = strtod(optarg, NULL); 299 if (opt_d < 0.0) { 300 fprintf(stderr, "Timeout should be positive value\n"); 301 usage(program_name, EXIT_FAILURE); 302 } 303 info_p->timeout = opt_d; 304 break; 305 306 case 'i': 307 if (strtotimespec(optarg, &info_p->interval)) { 308 fprintf(stderr, "Interval is something wrong\n"); 309 usage(program_name, EXIT_FAILURE); 310 } 311 break; 312 313 case 'o': 314 info_p->timeout = -1.0; 315 break; 316 317 case 'b': 318 *bg_p = 1; 319 break; 320 321 case 'd': 322 debug = 1; 323 break; 324 325 case 'h': 326 usage(program_name, EXIT_SUCCESS); 327 break; 328 329 default: 330 usage(program_name, EXIT_FAILURE); 331 } 332 } 333 334 if (info_p->ifindex == 0) { 335 fprintf(stderr, "specified interface seems incorrect\n"); 336 usage(program_name, EXIT_FAILURE); 337 } 338 339 if ((info_p->query = create_query(max_resp, maddr, saddrs)) == NULL) 340 usage(program_name, EXIT_FAILURE); 341 342 if (maddr) 343 free(maddr); 344 if (saddrs) 345 free(saddrs); 346} 347 348 349/* 350 * Function: create_socket() 351 * 352 * Description: 353 * This function creates a socket to send 354 * 355 * Argument: 356 * info_p: pointer to data of igmp query information 357 * 358 * Return value: 359 * file descriptor referencing the socket 360 */ 361int 362create_socket(struct igmp_info *info_p) 363{ 364 int sd; /* socket file descriptor */ 365 int on; 366 unsigned char opt[4] = {0x94, 0x04, 0x00, 0x00}; /* Router Alert */ 367 struct ip_mreqn mcast_req, *req_p = &mcast_req; 368 369 /* Create a socket */ 370 sd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); 371 if (sd < 0) 372 fatal_error("socket()"); 373 374 /* Enable to reuse the socket */ 375 on = 1; 376 if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int))) 377 fatal_error("setsockopt(): enable to reuse the socket"); 378 379 /* Add router alert option */ 380 if (setsockopt(sd, IPPROTO_IP, IP_OPTIONS, opt, sizeof(opt))) 381 fatal_error("setsockopt(): socket options"); 382 383 /* Specify the interface for outgoing datagrams */ 384 req_p->imr_multiaddr.s_addr = info_p->query->group; 385 req_p->imr_address.s_addr = htonl(INADDR_ANY); 386 req_p->imr_ifindex = info_p->ifindex; 387 if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, 388 req_p, sizeof(struct ip_mreqn))) { 389 fatal_error("setsockopt(): specify the interface"); 390 } 391 392 return sd; 393} 394 395 396/* 397 * Function: send_query() 398 * 399 * Description: 400 * This function sends IGMP query 401 * 402 * Argument: 403 * info_p: pointer to data of igmp query information 404 * 405 * Return value: 406 * None 407 */ 408void 409send_query(struct igmp_info *info_p) 410{ 411 int sd; 412 int retval; 413 double start_time; 414 struct sockaddr_in to; 415 size_t query_size; 416 417 /* Set singal hander for SIGHUP */ 418 handler.sa_handler = set_signal_flag; 419 handler.sa_flags = 0; 420 if (sigfillset(&handler.sa_mask) < 0) 421 fatal_error("sigfillset()"); 422 if (sigaction(SIGHUP, &handler, NULL) < 0) 423 fatal_error("sigaction()"); 424 425 /* Specify multicast address to send */ 426 to.sin_family = AF_INET; 427 to.sin_port = IPPROTO_IGMP; 428 if (info_p->query->group == htonl(INADDR_ANY)) 429 to.sin_addr.s_addr = IGMP_ALL_HOSTS; 430 else 431 to.sin_addr.s_addr = info_p->query->group; 432 433 /* Create a socket */ 434 sd = create_socket(info_p); 435 436 /* loop for sending queries */ 437 start_time = time(NULL); 438 query_size = MY_IGMPV3_QUERY_SIZE(ntohs(info_p->query->nsrcs)); 439 if (debug) 440 fprintf (stderr, "query size is %zu\n", query_size); 441 442 for (;;) { 443 retval = sendto(sd, info_p->query, query_size, 0, 444 (struct sockaddr *)&to, sizeof(struct sockaddr_in)); 445 if (retval != query_size) { 446 if (catch_sighup) 447 break; 448 else 449 fatal_error("sendto()"); 450 } 451 452 /* Check timeout: 453 If timeout value is negative only send one datagram */ 454 if (info_p->timeout) 455 if (info_p->timeout < difftime(time(NULL), start_time)) 456 break; 457 458 /* Wait in specified interval */ 459 nanosleep(&info_p->interval, NULL); 460 461 /* catch SIGHUP */ 462 if (catch_sighup) 463 break; 464 465 } 466 467 close(sd); 468} 469 470 471/* 472 * 473 * Function: main() 474 * 475 */ 476int 477main(int argc, char *argv[]) 478{ 479 struct igmp_info mcast_rcv; 480 int background = 0; 481 482 debug = 0; 483 program_name = strdup(argv[0]); 484 485 memset(&mcast_rcv, '\0', sizeof(struct igmp_info)); 486 parse_options(argc, argv, &mcast_rcv, &background); 487 488 if (background) /* Work in the background */ 489 if (daemon(0, 0) < 0) 490 fatal_error("daemon()"); 491 492 send_query(&mcast_rcv); 493 494 exit(EXIT_SUCCESS); 495} 496