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