blob: e2c8957cb3280eae2ef4bb5626f37704f3c82e89 [file] [log] [blame]
/*
** igmpproxy - IGMP proxy based multicast router
** Copyright (C) 2005 Johnny Egeland <johnny@rlo.org>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
**
**----------------------------------------------------------------------------
**
** This software is derived work from the following software. The original
** source code has been modified from it's original state by the author
** of igmpproxy.
**
** smcroute 0.92 - Copyright (C) 2001 Carsten Schill <carsten@cschill.de>
** - Licensed under the GNU General Public License, either version 2 or
** any later version.
**
** mrouted 3.9-beta3 - Copyright (C) 2002 by The Board of Trustees of
** Leland Stanford Junior University.
** - Licensed under the 3-clause BSD license, see Stanford.txt file.
**
*/
/**
* igmpproxy.c - The main file for the IGMP proxy application.
*
* February 2005 - Johnny Egeland
*/
/* getopt() and clock_getime() */
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif
#include "igmpproxy.h"
static const char Usage[] =
"Usage: igmpproxy [-h] [-n] [-d] [-v [-v]] <configfile>\n"
"\n"
" -h Display this help screen\n"
" -n Do not run as a daemon\n"
" -d Run in debug mode. Output all messages on stderr. Implies -n.\n"
" -v Be verbose. Give twice to see even debug messages.\n"
"\n"
PACKAGE_STRING "\n"
;
// Local function Prototypes
static void signalHandler(int);
int igmpProxyInit(void);
void igmpProxyCleanUp(void);
void igmpProxyRun(void);
// Global vars...
static int sighandled = 0;
#define GOT_SIGINT 0x01
#define GOT_SIGHUP 0x02
#define GOT_SIGUSR1 0x04
#define GOT_SIGUSR2 0x08
// Holds the indeces of the upstream IF...
int upStreamIfIdx[MAX_UPS_VIFS];
/**
* Program main method. Is invoked when the program is started
* on commandline. The number of commandline arguments, and a
* pointer to the arguments are received on the line...
*/
int main( int ArgCn, char *ArgVc[] ) {
int c;
bool NotAsDaemon = true;
// Parse the commandline options and setup basic settings..
while ((c = getopt(ArgCn, ArgVc, "vdnh")) != -1) {
switch (c) {
case 'n':
NotAsDaemon = true;
break;
case 'd':
Log2Stderr = true;
NotAsDaemon = true;
break;
case 'v':
if (LogLevel == LOG_INFO)
LogLevel = LOG_DEBUG;
else
LogLevel = LOG_INFO;
break;
case 'h':
fputs(Usage, stderr);
exit(0);
break;
default:
exit(1);
break;
}
}
if (optind != ArgCn - 1) {
fputs("You must specify the configuration file.\n", stderr);
exit(1);
}
char *configFilePath = ArgVc[optind];
openlog("igmpproxy", LOG_PID, LOG_USER);
// Write debug notice with file path...
my_log(LOG_DEBUG, 0, "Searching for config file at '%s'" , configFilePath);
do {
// Loads the config file...
if( ! loadConfig( configFilePath ) ) {
my_log(LOG_ERR, 0, "Unable to load config file...");
break;
}
// Initializes the deamon.
if ( !igmpProxyInit() ) {
my_log(LOG_ERR, 0, "Unable to initialize IGMPproxy.");
break;
}
if ( !NotAsDaemon ) {
// Only daemon goes past this line...
if (fork()) exit(0);
// Detach daemon from terminal
if ( close( 0 ) < 0 || close( 1 ) < 0 || close( 2 ) < 0
|| open( "/dev/null", 0 ) != 0 || dup2( 0, 1 ) < 0 || dup2( 0, 2 ) < 0
|| setpgid( 0, 0 ) < 0
) {
my_log( LOG_ERR, errno, "failed to detach daemon" );
}
}
// Go to the main loop.
igmpProxyRun();
// Clean up
igmpProxyCleanUp();
} while ( false );
// Inform that we are exiting.
my_log(LOG_INFO, 0, "Shutdown complete....");
exit(0);
}
/**
* Handles the initial startup of the daemon.
*/
int igmpProxyInit(void) {
struct sigaction sa;
int Err;
sa.sa_handler = signalHandler;
sa.sa_flags = 0; /* Interrupt system calls */
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
// Loads configuration for Physical interfaces...
buildIfVc();
// Configures IF states and settings
configureVifs();
switch ( Err = enableMRouter() ) {
case 0: break;
case EADDRINUSE: my_log( LOG_ERR, EADDRINUSE, "MC-Router API already in use" ); break;
default: my_log( LOG_ERR, Err, "MRT_INIT failed" );
}
/* create VIFs for all IP, non-loop interfaces
*/
{
unsigned Ix;
struct IfDesc *Dp;
int vifcount = 0, upsvifcount = 0;
// init array to "not set"
for ( Ix = 0; Ix < MAX_UPS_VIFS; Ix++)
{
upStreamIfIdx[Ix] = -1;
}
for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ) {
if ( Dp->InAdr.s_addr && ! (Dp->Flags & IFF_LOOPBACK) ) {
if(Dp->state == IF_STATE_UPSTREAM) {
if (upsvifcount < MAX_UPS_VIFS -1)
{
my_log(LOG_DEBUG, 0, "Found upstrem IF #%d, will assing as upstream Vif %d",
upsvifcount, Ix);
upStreamIfIdx[upsvifcount++] = Ix;
} else {
my_log(LOG_ERR, 0, "Cannot set VIF #%d as upstream as well. Mac upstream Vif count is %d",
Ix, MAX_UPS_VIFS);
}
}
if (Dp->state != IF_STATE_DISABLED) {
addVIF( Dp );
vifcount++;
}
}
}
if(0 == upsvifcount) {
my_log(LOG_ERR, 0, "There must be at least 1 Vif as upstream.");
}
}
// Initialize IGMP
initIgmp();
// Initialize Routing table
initRouteTable();
// Initialize timer
callout_init();
return 1;
}
/**
* Clean up all on exit...
*/
void igmpProxyCleanUp(void) {
my_log( LOG_DEBUG, 0, "clean handler called" );
free_all_callouts(); // No more timeouts.
clearAllRoutes(); // Remove all routes.
disableMRouter(); // Disable the multirout API
}
/**
* Main daemon loop.
*/
void igmpProxyRun(void) {
// Get the config.
struct Config *config = getCommonConfig();
// Set some needed values.
register int recvlen;
int MaxFD, Rt, secs;
fd_set ReadFDS;
socklen_t dummy = 0;
struct timespec curtime, lasttime, difftime, tv;
// The timeout is a pointer in order to set it to NULL if nessecary.
struct timespec *timeout = &tv;
// Initialize timer vars
difftime.tv_nsec = 0;
clock_gettime(CLOCK_MONOTONIC, &curtime);
lasttime = curtime;
// First thing we send a membership query in downstream VIF's...
sendGeneralMembershipQuery();
// Loop until the end...
for (;;) {
// Process signaling...
if (sighandled) {
if (sighandled & GOT_SIGINT) {
sighandled &= ~GOT_SIGINT;
my_log(LOG_NOTICE, 0, "Got a interrupt signal. Exiting.");
break;
}
}
/* aimwang: call rebuildIfVc */
if (config->rescanVif)
rebuildIfVc();
// Prepare timeout...
secs = timer_nextTimer();
if(secs == -1) {
timeout = NULL;
} else {
timeout->tv_nsec = 0;
timeout->tv_sec = (secs > 3) ? 3 : secs; // aimwang: set max timeout
}
// Prepare for select.
MaxFD = MRouterFD;
FD_ZERO( &ReadFDS );
FD_SET( MRouterFD, &ReadFDS );
// wait for input
Rt = pselect( MaxFD +1, &ReadFDS, NULL, NULL, timeout, NULL );
// log and ignore failures
if( Rt < 0 ) {
my_log( LOG_WARNING, errno, "select() failure" );
continue;
}
else if( Rt > 0 ) {
// Read IGMP request, and handle it...
if( FD_ISSET( MRouterFD, &ReadFDS ) ) {
recvlen = recvfrom(MRouterFD, recv_buf, RECV_BUF_SIZE,
0, NULL, &dummy);
if (recvlen < 0) {
if (errno != EINTR) my_log(LOG_ERR, errno, "recvfrom");
continue;
}
acceptIgmp(recvlen);
}
}
// At this point, we can handle timeouts...
do {
/*
* If the select timed out, then there's no other
* activity to account for and we don't need to
* call gettimeofday.
*/
if (Rt == 0) {
curtime.tv_sec = lasttime.tv_sec + ((secs > 3) ? 3 : secs);
curtime.tv_nsec = lasttime.tv_nsec;
Rt = -1; /* don't do this next time through the loop */
} else {
clock_gettime(CLOCK_MONOTONIC, &curtime);
}
difftime.tv_sec = curtime.tv_sec - lasttime.tv_sec;
difftime.tv_nsec += curtime.tv_nsec - lasttime.tv_nsec;
while (difftime.tv_nsec > 1000000000) {
difftime.tv_sec++;
difftime.tv_nsec -= 1000000000;
}
if (difftime.tv_nsec < 0) {
difftime.tv_sec--;
difftime.tv_nsec += 1000000000;
}
lasttime = curtime;
if (secs == 0 || difftime.tv_sec > 0)
age_callout_queue(difftime.tv_sec);
secs = -1;
} while (difftime.tv_sec > 0);
}
}
/*
* Signal handler. Take note of the fact that the signal arrived
* so that the main loop can take care of it.
*/
static void signalHandler(int sig) {
switch (sig) {
case SIGINT:
case SIGTERM:
sighandled |= GOT_SIGINT;
break;
/* XXX: Not in use.
case SIGHUP:
sighandled |= GOT_SIGHUP;
break;
case SIGUSR1:
sighandled |= GOT_SIGUSR1;
break;
case SIGUSR2:
sighandled |= GOT_SIGUSR2;
break;
*/
}
}