From a01c0296e16555bb1ae3db2b1913bb3ae566b0fc Mon Sep 17 00:00:00 2001 From: uglymotha Date: Sun, 7 Jul 2024 16:35:35 +0000 Subject: [PATCH] inux: Enable use of multiple route tables --- src/cli.c | 27 ++- src/config.c | 144 ++++++++++++--- src/ifvc.c | 76 ++++---- src/igmp.c | 15 +- src/igmpv3proxy.c | 440 ++++++++++++++++++++++++++++++++-------------- src/igmpv3proxy.h | 90 ++++++++-- src/kern.c | 6 + src/lib.c | 69 +++++++- src/mctable.c | 5 +- src/querier.c | 13 +- src/timers.c | 2 +- 11 files changed, 652 insertions(+), 235 deletions(-) diff --git a/src/cli.c b/src/cli.c index 4124c6c..0c5d285 100644 --- a/src/cli.c +++ b/src/cli.c @@ -56,7 +56,13 @@ int openCliFd(void) { cli_sa.sun_family = AF_UNIX; // Open the socket, set permissions and mode. - if ( ! strcat(strcpy(cli_sa.sun_path, CONF->runPath), "cli.sock") + if ( ! strcpy(cli_sa.sun_path, CONF->runPath) +#ifdef __linux__ + || ! snprintf(cli_sa.sun_path + strlen(cli_sa.sun_path), PATH_MAX - strlen(cli_sa.sun_path), + mrt_tbl ? "cli-%d.sock" : "cli.sock", mrt_tbl) +#else + || ! strcat(cli_sa.sun_path, "cli.sock") +#endif || (stat(cli_sa.sun_path, &st) == 0 && unlink(cli_sa.sun_path) < 0) || ! (cli_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN @@ -70,7 +76,8 @@ int openCliFd(void) { || chmod(cli_sa.sun_path, 0660)) { LOG(LOG_WARNING, errno, "Cannot open CLI Socket %s. CLI connections will not be available.", cli_sa.sun_path); cli_fd = -1; - } + } else + LOG(LOG_INFO, 0, "openCliFd: Opened CLI socket %s.", cli_sa.sun_path); return cli_fd; } @@ -78,10 +85,11 @@ int openCliFd(void) { /** * Close and unlink CLI socket. */ -void closeCliFd(int fd) { +int closeCliFd(int fd) { shutdown(fd, SHUT_RDWR); close(fd); unlink(cli_sa.sun_path); + return(-1); } /** @@ -118,13 +126,13 @@ void acceptCli(int fd) if (buf[0] == 'r') { logRouteTable("", buf[1] == 'h' ? 0 : 1, cli_fd, addr, mask); - } else if (buf[0] == 'i' && len > 2 && ! (IfDp = getIf(0, &buf[buf[1] == 'h' ? 3 : 2], 2))) { + } else if ((buf[0] == 'i' || buf[0] == 'f') && len > 2 && ! (IfDp = getIf(0, &buf[buf[1] == 'h' ? 3 : 2], 2))) { sprintf(msg, "Interface %s Not Found\n", &buf[buf[1] == 'h' ? 3 : 2]); send(cli_fd, msg, strlen(msg), MSG_DONTWAIT); } else if (buf[0] == 'i') { getIfStats(IfDp, buf[1] == 'h' ? 0 : 1, cli_fd); } else if (buf[0] == 'f') { - getIfFilters(len > 1 && buf[1] == 'h' ? 0 : 1, cli_fd); + getIfFilters(IfDp, len > 1 && buf[1] == 'h' ? 0 : 1, cli_fd); } else if (buf[0] == 't') { debugQueue("", len > 1 && buf[1] == 'h' ? 0 : 1, cli_fd); } else if (buf[0] == 'm') { @@ -144,7 +152,7 @@ static int srv_fd = -1; /** * Sends command to daemon and writes response to stdout. Error exit if socket cannot be connected. */ -void cliCmd(char *cmd) { +void cliCmd(char *cmd, int tbl) { struct sigaction sa; struct stat st; struct sockaddr_un srv_sa; @@ -172,7 +180,10 @@ void cliCmd(char *cmd) { strcpy(srv_sa.sun_path, strcat(tpath, "/cli.sock")); break; } - sprintf(tpath, "%s/%s/cli.sock", path, fileName); + if (tbl) + sprintf(tpath, "%s/%s/cli-%d.sock", path, fileName, tbl); + else + sprintf(tpath, "%s/%s/cli.sock", path, fileName); if (stat(tpath, &st) != -1) { strcpy(srv_sa.sun_path, tpath); break; @@ -183,7 +194,7 @@ void cliCmd(char *cmd) { // Open and bind socket for receiving answers from daemon. if (strcmp(srv_sa.sun_path, "") == 0 || (srv_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0 || connect(srv_fd, (struct sockaddr*)&srv_sa, sizeof(struct sockaddr_un)) != 0) { - fprintf(stderr, "Cannot open daemon socket. %s\n", strerror(errno)); + fprintf(stderr, "Cannot open daemon socket (%s). %s\n", srv_sa.sun_path, strerror(errno)); exit(-1); } diff --git a/src/config.c b/src/config.c index 09def39..6b9da83 100644 --- a/src/config.c +++ b/src/config.c @@ -49,22 +49,22 @@ static inline void parseFilters(char *in, char *token, struct filters ***filP, static inline bool parsePhyintToken(char *token); // All valid configuration options. Prepend whitespace to allow for strstr() exact token matching. -static const char *options = " include phyint user group chroot defaultquickleave quickleave maxorigins hashtablesize routetables defaultdown defaultup defaultupdown defaultthreshold defaultratelimit defaultquerierver defaultquerierip defaultrobustness defaultqueryinterval defaultqueryrepsonseinterval defaultlastmemberinterval defaultlastmembercount bwcontrol rescanvif rescanconf loglevel logfile defaultproxylocalmc defaultnoquerierelection proxylocalmc noproxylocalmc upstream downstream disabled ratelimit threshold querierver querierip robustness queryinterval queryrepsonseinterval lastmemberinterval lastmembercount defaultnocksumverify nocksumverify cksumverify noquerierelection querierelection nocksumverify cksumverify noquerierelection querierelection defaultfilterany nodefaultfilter filter altnet whitelist reqqueuesize kbufsize pbufsize"; -static const char *phyintopt = " updownstream upstream downstream disabled proxylocalmc noproxylocalmc quickleave noquickleave ratelimit threshold nocksumverify cksumverify noquerierelection querierelection querierip querierver robustnessvalue queryinterval queryrepsonseinterval lastmemberinterval lastmembercount defaultfilter filter altnet whitelist"; +static const char *options = " include phyint user group chroot defaultquickleave quickleave maxorigins hashtablesize routetables defaultdown defaultup defaultupdown defaultthreshold defaultratelimit defaultquerierver defaultquerierip defaultrobustness defaultqueryinterval defaultqueryrepsonseinterval defaultlastmemberinterval defaultlastmembercount bwcontrol rescanvif rescanconf loglevel logfile defaultproxylocalmc defaultnoquerierelection proxylocalmc noproxylocalmc upstream downstream disabled ratelimit threshold querierver querierip robustness queryinterval queryrepsonseinterval lastmemberinterval lastmembercount defaultnocksumverify nocksumverify cksumverify noquerierelection querierelection nocksumverify cksumverify noquerierelection querierelection defaultfilterany nodefaultfilter filter altnet whitelist reqqueuesize kbufsize pbufsize maxtbl defaulttable disableipmrules"; +static const char *phyintopt = " table updownstream upstream downstream disabled proxylocalmc noproxylocalmc quickleave noquickleave ratelimit threshold nocksumverify cksumverify noquerierelection querierelection querierip querierver robustnessvalue queryinterval queryrepsonseinterval lastmemberinterval lastmembercount defaultfilter filter altnet whitelist"; // Daemon Configuration. -static struct Config conf, oldconf; +static struct Config conf, oldconf; // Structures to keep vif configuration and black/whitelists. static struct vifConfig *vifConf = NULL, *ovifConf = NULL; uint32_t uVifs; // Keeps timer ids for configurable timed functions. -static struct timers { - uint64_t rescanConf; - uint64_t rescanVif; - uint64_t bwControl; -} timers = { 0, 0, 0 }; +struct timers timers = { 0, 0, 0 }; + +#ifdef __linux__ + int *tbl, ntbl; +#endif // Macro to get a token which should be integer. #define INTTOKEN ((nextToken(token)) && ((intToken = atoll(token + 1)) || !intToken)) @@ -209,14 +209,19 @@ static inline void initCommonConfig(void) { conf.dHostsHTSize = DEFAULT_HASHTABLE_SIZE; // Number of (hashed) route tables. +#ifdef __linux__ + conf.maxtbl = DEFAULT_MAXTBL; + conf.defaultTable = 0; + conf.disableIpMrules = false; +#endif conf.mcTables = STARTUP ? DEFAULT_ROUTE_TABLES : oldconf.mcTables; // Default interface state and parameters. conf.defaultInterfaceState = IF_STATE_DISABLED; - conf.defaultThreshold = DEFAULT_THRESHOLD; - conf.defaultRatelimit = DEFAULT_RATELIMIT; - conf.defaultFilters = NULL; - conf.defaultRates = NULL; + conf.defaultThreshold = DEFAULT_THRESHOLD; + conf.defaultRatelimit = DEFAULT_RATELIMIT; + conf.defaultFilters = NULL; + conf.defaultRates = NULL; // Log to file disabled by default. conf.logLevel = !conf.log2Stderr ? LOG_WARNING : conf.logLevel; @@ -339,7 +344,7 @@ static inline void parseFilters(char *in, char *token, struct filters ***filP, s static inline bool parsePhyintToken(char *token) { struct vifConfig *tmpPtr; struct filters **filP, **rateP; - int64_t intToken; + int64_t intToken, i; if (!nextToken(token)) { // First token should be the interface name. @@ -366,7 +371,9 @@ static inline bool parsePhyintToken(char *token) { for (filP = &tmpPtr->filters; *filP && *filP != conf.defaultFilters; filP = &(*filP)->next); for (rateP = &tmpPtr->rates; *rateP && *rateP != conf.defaultRates; rateP = &(*rateP)->next); } - +#ifdef __linux__ + tmpPtr->tbl = conf.defaultTable; +#endif // Parse the rest of the config. LOG(LOG_NOTICE, 0, "Config (%s): Configuring Interface.", tmpPtr->name); while (!logwarning && nextToken(token)) { @@ -374,11 +381,30 @@ static inline bool parsePhyintToken(char *token) { LOG(LOG_NOTICE, 0, "Config (%s): Parsing ACL '%s'.", tmpPtr->name, token + 1); parseFilters(tmpPtr->name, token, &filP, &rateP); } - if (strcmp(" nodefaultfilter", token) == 0) { tmpPtr->noDefaultFilter = true; LOG(LOG_NOTICE, 0, "Config (%s): Not setting default filters.", tmpPtr->name); + } else if (strcmp(" table", token) == 0 && INTTOKEN) { +#ifdef __linux__ + if (intToken < 0 || intToken > conf.maxtbl) + LOG(LOG_WARNING, 0, "Config (%s): Table id should be between 0 and %d.", tmpPtr->name, conf.maxtbl); + else { + int j; + LOG(LOG_INFO, 0, "Config (%s): Assigning to table %d.", tmpPtr->name, intToken); + tmpPtr->tbl = intToken; + if (intToken > 0) { + // igmpProxyInit() will fork the process for table 0 after config is loaded + igmpProxyFork(intToken); + for (j = 1; j < ntbl && tbl[j] != intToken; j++); + if (j >= ntbl) + tbl[ntbl++] = intToken; + } + } +#else + LOG(LOG_NOTICE, 0, "Config (%s): Table id is only valid on linux.", tmpPtr->name); +#endif + } else if (strcmp(" updownstream", token) == 0) { tmpPtr->state = IF_STATE_UPDOWNSTREAM; LOG(LOG_NOTICE, 0, "Config (%s): Setting to Updownstream.", tmpPtr->name); @@ -540,6 +566,11 @@ bool loadConfig(char *cfgFile) { FILE *confFilePtr = NULL, *fp; char *token = NULL; struct stat st; +#ifdef __linux__ + ntbl = 1; + if (! (tbl = calloc((conf.maxtbl + 1), sizeof(int)))) // Freed by self + LOG(LOG_ERR, errno, "loadConfig: Out of Memory."); +#endif // Initialize common config on first entry. if (conf.cnt++ == 0) { @@ -609,6 +640,15 @@ bool loadConfig(char *cfgFile) { LOG(LOG_WARNING, 0, "Config: Failed to include config from '%s'.", token + 1); configFile(confFilePtr, 2); + } else if (strcmp(" defaulttbl", token) == 0 && INTTOKEN) { +#ifdef __linux__ + if (intToken < 0 || intToken > conf.maxtbl) + LOG(LOG_NOTICE, 0, "Config: Default table id should be between 0 and %d.", conf.maxtbl); + else + conf.defaultTable = intToken; +#else + LOG(LOG_NOTICE, 0, "Config: Default table id is only valid on linux."); +#endif } else if (strcmp(" chroot", token) == 0 && nextToken(token) && (STARTUP || (token[1] = '\0'))) { if (! (conf.chroot = malloc(strlen(token)))) LOG(LOG_ERR, errno, "Config: Out of Memory."); @@ -628,6 +668,32 @@ bool loadConfig(char *cfgFile) { LOG(LOG_NOTICE, 0, "Config: Running daemon as %s (%d)", conf.user->pw_name, conf.user->pw_uid); #else LOG(LOG_NOTICE, 0, "Config: Run as user %s is only valid for linux.", token + 1); +#endif + } else if (strcmp(" maxtbl", token) == 0 && INTTOKEN) { +#ifdef __linux__ + if (intToken < 2 || intToken > 999999999) + LOG(LOG_NOTICE, 0, "Config: maxtbl should be between 2 and 999999999."); + else { + if (!STARTUP && oldconf.maxtbl > intToken) + LOG(LOG_WARNING, 0, "Config: Cannot decrease maxtbl, %d tables in use.", chld.nr); + else if (oldconf.maxtbl != intToken) { + conf.maxtbl = intToken; + if (!STARTUP && ( ! (chld.c = realloc(chld.c, (conf.maxtbl + 1) * sizeof(struct pt))) + || ! (tbl = realloc(tbl, (conf.maxtbl + 1) * sizeof(int))))) + // Freed by igmpProxyCleanUp() + LOG(LOG_ERR, errno, "Config: Out of Memory."); + } + LOG(LOG_NOTICE, 0, "Config: Setting max route tables to %d.", conf.maxtbl); + } +#else + LOG(LOG_NOTICE, 0, "Config: maxtbl is only valid on linux."); +#endif + } else if (strcmp(" disableipmrules", token) == 0) { +#ifdef __linux__ + LOG(LOG_NOTICE, 0, "Config: Will disable ip mrules for mc route tables."); + conf.disableIpMrules = true; +#else + LOG(LOG_NOTICE, 0, "disableipmrules is ony valid for linux."); #endif } else if (strcmp(" group", token) == 0 && nextToken(token) && (STARTUP || (token[1] = '\0'))) { if (! (conf.group = getgrnam(token + 1))) @@ -807,7 +873,7 @@ bool loadConfig(char *cfgFile) { else if ((! ((fp = fopen(token + 1, "w")) && (t = token + 1)) && ! (fp = fopen(t, "w"))) || fclose(fp) != 0) LOG(LOG_WARNING, errno, "Config: Cannot open log file '%s'.", token + 1); else if (! (conf.logFilePath = realloc(conf.logFilePath, strlen(token)))) - // Freed by igmpProxyCleanUp() + // Freed by igmpProxyMonitor() or signalHandler() LOG(LOG_ERR, errno, "loadConfig: Out of Memory."); else { strcpy(conf.logFilePath, t); @@ -838,6 +904,19 @@ bool loadConfig(char *cfgFile) { free(token); // Alloced by self if (confFilePtr && (confFilePtr = configFile(NULL, 0))) LOG(LOG_WARNING, errno, "Config: Failed to close config file (%d) '%s'.", conf.cnt, cfgFile); + +#ifdef __linux__ + if (chld.c && !logwarning) for (int i = 0; i < chld.nr; i++) { + // Check if any proxy needs to be stopped because it is no longer used. + int j = 0; + for (;j < ntbl && chld.c[i].tbl != tbl[j]; j++); + if (j >= ntbl) { + kill(chld.c[i].pid, SIGINT); // SIGINT so process will not ve restarted in SIGCHLD + LOG(LOG_NOTICE, 0, "Stopping PID: %d (%d) for table %d.", chld.c[i].pid, i, chld.c[i].tbl); + } + } + free(tbl); // Alloced by Self +#endif if (--conf.cnt > 0 || logwarning) return !logwarning; @@ -903,31 +982,25 @@ void reloadConfig(uint64_t *tid) { sigstatus = NOSIG ? GOT_CONFREL : sigstatus; ovifConf = vifConf; vifConf = NULL; - oldconf = conf; // Load the new configuration keep reference to the old. + memcpy(&oldconf, &conf, sizeof(struct Config)); conf.cnt = 0; if (!loadConfig(conf.configFilePath)) { LOG(LOG_WARNING, 0, "Failed to reload config from '%s', keeping current.", conf.configFilePath); if (vifConf) freeConfig(0); vifConf = ovifConf; - conf = oldconf; + memcpy(&conf, &oldconf, sizeof(struct Config)); } else { // Rebuild the interfaces config, then free the old configuration. rebuildIfVc(NULL); freeConfig(1); LOG(LOG_WARNING, 0, "Configuration Reloaded from '%s'.", conf.configFilePath); } + getMemStats(0, -1); - LOG(LOG_DEBUG, 0, "Memory Stats: %lldb total, %lldb interfaces, %lldb config, %lldb filters.", - memuse.ifd + memuse.vif + memuse.fil, memuse.ifd, memuse.vif, memuse.fil); - LOG(LOG_DEBUG, 0, " %lld allocs total, %lld interfaces, %lld config, %lld filters.", - memalloc.ifd + memalloc.vif + memalloc.fil, memalloc.ifd, memalloc.vif, memalloc.fil); - LOG(LOG_DEBUG, 0, " %lld frees total, %lld interfaces, %lld config, %lld filters.", - memfree.ifd + memfree.vif + memfree.fil, memfree.ifd, memfree.vif, memfree.fil); - - if (sigstatus == GOT_CONFREL && conf.rescanConf) + if (conf.rescanConf && sigstatus == GOT_CONFREL && tid) *tid = timer_setTimer(conf.rescanConf * 10, "Reload Configuration", reloadConfig, tid); sigstatus = 0; } @@ -939,6 +1012,9 @@ void reloadConfig(uint64_t *tid) { * - Establish correct old and new state of interfaces. * - Control querier process and do route maintenance on interface transitions. * - Add and remove vifs from the kernel if needed. +* - IfDp->state represents the old and new state of interfaces as below. +* 1 2 3 4 5 6 7 8 +* upstream downstream oldup olddown unused unused rebuilt removed */ inline void configureVifs(void) { struct IfDesc *IfDp = NULL; @@ -986,6 +1062,16 @@ inline void configureVifs(void) { } else // Existing interface, oldstate is current state, newstate is configured state. IfDp->state = ((IfDp->state & 0x3) << 2) | (IfDp->mtu && (IfDp->Flags & IFF_MULTICAST) ? IfDp->conf->state : 0); +#ifdef __linux__ + if (mrt_tbl >= 0 && IfDp->conf->tbl != mrt_tbl) { + // Check if Interface is in table for current process. + LOG(LOG_NOTICE, 0, "Not enabling table %d interface %s", IfDp->conf->tbl, IfDp->Name); + IfDp->state &= ~0x3; // Keep old state, new state disabled. + } + if (mrt_tbl < 0) + // Monitor process only needs config and state. + continue; +#endif register uint8_t oldstate = IF_OLDSTATE(IfDp), newstate = IF_NEWSTATE(IfDp); quickLeave |= !IS_DISABLED(IfDp->state) && IfDp->conf->quickLeave; @@ -1044,7 +1130,11 @@ inline void configureVifs(void) { upsvifcount--; } } - +#ifdef __linux__ + if (mrt_tbl < 0) + // Monitor process only needs config and state. + return; +#endif // Set hashtable size to 0 when quickleave is not enabled on any interface. if (!quickLeave) { LOG(LOG_NOTICE, 0, "Disabling quickleave, no interfaces have it enabled."); diff --git a/src/ifvc.c b/src/ifvc.c index 9ec2742..87abebd 100644 --- a/src/ifvc.c +++ b/src/ifvc.c @@ -50,8 +50,8 @@ static void freeIfDescL() { while (IfDp) { if ((IfDp->state & 0x80) || (IfDp->next && (IfDp->next->state & 0x80))) { // Remove interface marked for deletion. - if (!SHUTDOWN) - LOG(LOG_WARNING, 0, "Interface %s was removed.", (IfDp->state & 0x80) ? IfDp->Name : IfDp->next->Name); + LOG(SHUTDOWN ? LOG_NOTICE : LOG_WARNING, 0, + "Interface %s was removed.", (IfDp->state & 0x80) ? IfDp->Name : IfDp->next->Name); fIfDp = (IfDp->state & 0x80) ? IfDescL : IfDp->next; if (IfDp->state & 0x80) IfDescL = IfDp = IfDp->next; @@ -73,7 +73,7 @@ void rebuildIfVc(uint64_t *tid) { sigstatus = NOSIG ? GOT_IFREB : sigstatus; // Build new IfDEsc table on SIGHUP, SIGUSR2 or timed rebuild. - if (!CONFRELOAD && !SHUTDOWN) + if ((!CONFRELOAD && !SHUTDOWN) || (sighandled & GOT_SIGURG)) buildIfVc(); // Call configureVifs to link the new IfDesc table. @@ -114,11 +114,13 @@ void buildIfVc(void) { || (!((tmpIfAddrsP->ifa_flags & IFF_UP) && (tmpIfAddrsP->ifa_flags & IFF_RUNNING))) || s_addr_from_sockaddr(tmpIfAddrsP->ifa_addr) == 0 #ifdef IFF_CANTCONFIG - || (!(tmpIfAddrsP->ifa_flags & IFF_MULTICAST) && (tmpIfAddrsP->ifa_flags & IFF_CANTCONF)) + || (!(tmpIfAddrsP->ifa_flags & IFF_MULTICAST) && (tmpIfAddrsP->ifa_flags & IFF_CANTCONFIG)) #endif - || ((IfDp = getIf(0, tmpIfAddrsP->ifa_name, 2)) && (IfDp->state & 0xC0))) + || ((IfDp = getIf(0, tmpIfAddrsP->ifa_name, 2)) && (IfDp->state & 0xC0))) { // Only build Ifdesc for up & running IP interfaces (no aliases), and can be configured for multicast if not enabled. + LOG(LOG_DEBUG, 0, "buildIfVc: Found unusable interface %s.", tmpIfAddrsP->ifa_name); continue; + } uint32_t addr = s_addr_from_sockaddr(tmpIfAddrsP->ifa_addr), mask = s_addr_from_sockaddr(tmpIfAddrsP->ifa_netmask); if (! IfDp) { @@ -128,9 +130,12 @@ void buildIfVc(void) { *IfDp = DEFAULT_IFDESC; IfDescL = IfDp; memcpy(IfDp->Name, tmpIfAddrsP->ifa_name, strlen(tmpIfAddrsP->ifa_name)); - } else + LOG(LOG_DEBUG, 0, "buildIfVc: Found new interface %s.", IfDp->Name); + } else { // Rebuild Interface. For disappeared interface state is not reset here and configureVifs() can mark it for deletion. IfDp->state |= 0x40; + LOG(LOG_DEBUG, 0, "buildIfVc: Found existing interface %s.", IfDp->Name); + } // Set the interface flags, index and IP. IfDp->sysidx = ix; @@ -140,23 +145,29 @@ void buildIfVc(void) { // Get interface mtu. memset(&ifr, 0, sizeof(struct ifreq)); memcpy(ifr.ifr_name, tmpIfAddrsP->ifa_name, strlen(tmpIfAddrsP->ifa_name)); - if (ioctl(MROUTERFD, SIOCGIFMTU, &ifr) < 0) { - LOG(LOG_WARNING, errno, "Failed to get MTU for %s, disabling.", IfDp->Name); - IfDp->mtu = 0; - } else - IfDp->mtu = ifr.ifr_mtu; - - // Enable multicast if necessary. - if (! (IfDp->Flags & IFF_MULTICAST)) { - ifr.ifr_flags = IfDp->Flags | IFF_MULTICAST; - if (ioctl(MROUTERFD, SIOCSIFFLAGS, &ifr) < 0) - LOG(LOG_WARNING, errno, "Failed to enable multicast on %s, disabling.", IfDp->Name); - else { - IfDp->Flags = ifr.ifr_flags; - LOG(LOG_NOTICE, 0, "Multicast enabled on %s.", IfDp->Name); - } - } +#ifdef __linux__ + if (mrt_tbl >= 0) { +#endif + if (ioctl(MROUTERFD, SIOCGIFMTU, &ifr) < 0) { + LOG(LOG_WARNING, errno, "Failed to get MTU for %s, disabling.", IfDp->Name); + IfDp->mtu = 0; + } else + IfDp->mtu = ifr.ifr_mtu; + // Enable multicast if necessary. + if (! (IfDp->Flags & IFF_MULTICAST)) { + ifr.ifr_flags = IfDp->Flags | IFF_MULTICAST; + if (ioctl(MROUTERFD, SIOCSIFFLAGS, &ifr) < 0) + LOG(LOG_WARNING, errno, "Failed to enable multicast on %s, disabling.", IfDp->Name); + else { + IfDp->Flags = ifr.ifr_flags; + LOG(LOG_NOTICE, 0, "Multicast enabled on %s.", IfDp->Name); + } + } +#ifdef __linux__ + } else + IfDp->mtu = 1; +#endif // Log the result... LOG(LOG_INFO, 0, "buildIfVc: Interface %s, IP: %s/%d, Flags: 0x%04x, MTU: %d", IfDp->Name, inetFmt(IfDp->InAdr.s_addr, 1), 33 - ffs(ntohl(mask)), IfDp->Flags, IfDp->mtu); @@ -208,7 +219,11 @@ void getIfStats(struct IfDesc *IfDp, int h, int fd) { sprintf(buf, "%lu,%lu\n", IfDp->rqCnt, IfDp->sqCnt); send(fd, buf, strlen(buf), MSG_DONTWAIT); return; +#ifdef __linux__ + } else for (IFL(IfDp), i++) if (!mrt_tbl || !IS_DISABLED(IfDp->state)) { +#else } else for (IFL(IfDp), i++) { +#endif if (h) { total = (struct totals){ total.bytes + IfDp->bytes, total.rate + IfDp->rate, total.ratelimit + IfDp->conf->ratelimit }; strcpy(msg, "%4d |%15s| %2d| v%1d|%15s|%12s|%8s|%10s|%15s|%14lld B | %10lld B/s | %10lld B/s\n"); @@ -228,21 +243,22 @@ void getIfStats(struct IfDesc *IfDp, int h, int fd) { /** * Outputs configured filters to socket specified in arguments. */ -void getIfFilters(int h, int fd) { - struct IfDesc *IfDp; - char buf[CLI_CMD_BUF] = "", msg[CLI_CMD_BUF] = ""; +void getIfFilters(struct IfDesc *IfDp, int h, int fd) { + char buf[CLI_CMD_BUF] = "", msg[CLI_CMD_BUF] = "", s[10] = ""; + struct IfDesc *IfDp2 = NULL; int i = 1; if (h) { - sprintf(buf, "Current Active Filters:\n_______Int______|_nr_|__________SRC________|__________DST________|___Dir__|___Action___|______Rate_____\n"); + sprintf(buf, "Current Active Filters%s%s:\n_______Int______|_nr_|__________SRC________|__________DST________|___Dir__|___Action___|______Rate_____\n", IfDp ? " for " : "", IfDp ? IfDp->Name : ""); send(fd, buf, strlen(buf), MSG_DONTWAIT); } - for (IFL(IfDp), i++) { + for (IFL(IfDp2), i++) { struct filters *filter; int i = 1; - for (filter = IfDp->conf->filters; filter; filter = filter->next, i++) { - char s[10] = ""; + if (IfDp && IfDp != IfDp2) + continue; + for (filter = IfDp2->conf->filters; filter; filter = filter->next, i++) { if (filter->action > ALLOW) { strcpy(msg, h ? "%10lld B/s" : "%lld"); sprintf(s, msg, filter->action); @@ -251,7 +267,7 @@ void getIfFilters(int h, int fd) { strcpy(msg, "%15s |%4d| %19s | %19s | %6s | %10s | %s\n"); else strcpy(msg, "%s %d %s %s %s %s %s\n"); - sprintf(buf, msg, !h || i == 1 ? IfDp->Name : "", i, inetFmts(filter->src.ip, filter->src.mask, 1), + sprintf(buf, msg, !h || i == 1 ? IfDp2->Name : "", i, inetFmts(filter->src.ip, filter->src.mask, 1), inetFmts(filter->dst.ip, filter->dst.mask, 2), filter->dir == 1 ? "up" : filter->dir == 2 ? "down" : "both", filter->action == ALLOW ? "Allow" : filter->action == BLOCK ? "Block" : "Ratelimit", s); send(fd, buf, strlen(buf), MSG_DONTWAIT); diff --git a/src/igmp.c b/src/igmp.c index dfc2141..27d4d29 100644 --- a/src/igmp.c +++ b/src/igmp.c @@ -58,14 +58,16 @@ static char msg[TMNAMESZ]; * Open and initialize the igmp socket, and fill in the non-changing IP header fields in the output packet buffer. * Returns pointer to the receive buffer. */ -void initIgmp(bool free) { +int initIgmp(bool activate) { // Allocate and initialize send and receive packet buffers. - if (rcv_buf) + if (!activate) { _free(rcv_buf, rcv, memuse.rcv); // Alloced by Self - if (snd_buf) _free(snd_buf, snd, memuse.snd); // Alloced by Self - if (free) - return; + if (!(sighandled & GOT_SIGURG)) + k_disableMRouter(); + return -1; + } + int fd = k_enableMRouter(); if (! _calloc(rcv_buf, 1, rcv, CONF->pBufsz) || ! _calloc(snd_buf, 1, snd, CONF->pBufsz)) LOG(LOG_ERR, errno, "initIgmp: Out of Memory."); // Freed by Self struct ip *ip = (struct ip *)snd_buf; @@ -98,6 +100,7 @@ void initIgmp(bool free) { LOG(LOG_DEBUG, 0, "Memory Stats: %lldb total buffers, %lld kernel, %lldb receive, %lldb send, %lld allocs, %lld frees.", memuse.rcv + memuse.snd, memuse.rcv - memuse.snd, memuse.rcv - (memuse.rcv - memuse.snd), memuse.snd, memalloc.rcv + memalloc.snd, memfree.rcv + memfree.snd); + return fd; } /** @@ -153,7 +156,7 @@ void acceptIgmp(int recvlen, struct msghdr msgHdr) { #ifdef HAVE_STRUCT_BW_UPCALL_BU_SRC case IGMPMSG_BW_UPCALL: if (CONF->bwControlInterval) - processBwUpcall((struct bw_upcall *)(recv_buf + sizeof(struct igmpmsg)), + processBwUpcall((struct bw_upcall *)(rcv_buf + sizeof(struct igmpmsg)), ((recvlen - sizeof(struct igmpmsg)) / sizeof(struct bw_upcall))); return; #endif diff --git a/src/igmpv3proxy.c b/src/igmpv3proxy.c index 3d4b493..ea836a5 100644 --- a/src/igmpv3proxy.c +++ b/src/igmpv3proxy.c @@ -41,7 +41,7 @@ #include "igmpv3proxy.h" // Socket control message union. -union cmsgU { +union cmsg { struct cmsghdr cmsgHdr; #ifdef IP_PKTINFO char cmsgData[sizeof(struct msghdr) + sizeof(struct in_pktinfo)]; @@ -51,20 +51,24 @@ union cmsgU { }; // Local function Prototypes -static void signalHandler(int sig, siginfo_t *siginfo, void* context); +STRSIG; +static void signalHandler(int sig, siginfo_t* siginfo, void* context); static void igmpProxyInit(void); -static void igmpProxyCleanUp(void); +static void igmpProxyMonitor(void); +static void igmpProxyStart(void); static void igmpProxyRun(void); -// Global Variables Signal Handling / Timekeeping. +// Global Variables Memory / Signal Handling / Timekeeping / Buffers etc. uint8_t sighandled, sigstatus, logwarning; struct timespec curtime, utcoff, starttime; char *fileName, tS[32] = "", tE[32] = ""; struct memstats memuse = { 0 }, memalloc = { 0 }, memfree = { 0 }; - -// Polling and buffering local statics. -static struct pollfd pollFD[2]; +static struct pollfd pollFD[2] = { {-1, POLLIN, 0}, {-1, POLLIN, 0} }; char *rcv_buf = NULL; +#ifdef __linux__ +int mrt_tbl = -1; +struct chld chld = { 0 }; +#endif /** * Program main method. Is invoked when the program is started @@ -72,21 +76,22 @@ char *rcv_buf = NULL; * pointer to the arguments are received on the line... */ int main(int ArgCn, char *ArgVc[]) { - int c = 0, i = 0, j = 0; + int c = 0, i = 0, j = 0, tbl = 0; char *opts[2] = { ArgVc[0], NULL }, cmd[20] = "", paths[sizeof(CFG_PATHS) + 1] = CFG_PATHS, *path = NULL; struct stat st; fileName = basename(ArgVc[0]); // Initialize configuration, syslog and rng. - memset(CONF, 0, sizeof(struct Config)); + memset(CONF, 0, sizeof(struct Config)); + memset(OLDCONF, 0, sizeof(struct Config)); openlog(fileName, LOG_PID, LOG_DAEMON); srand(time(NULL) * getpid()); CONF->hashSeed = ((uint32_t)rand() << 16) | (uint32_t)rand(); CONF->logLevel = LOG_WARNING; // Parse the commandline options and setup basic settings.. - for (c = getopt(ArgCn, ArgVc, "cvVdnh"); c != -1; c = getopt(ArgCn, ArgVc, "cvVdnh")) { + for (c = getopt(ArgCn, ArgVc, "cvVdnht:"); c != -1; c = getopt(ArgCn, ArgVc, "cvVdnht:")) { switch (c) { case 'v': if (CONF->logLevel == LOG_WARNING) @@ -99,17 +104,24 @@ int main(int ArgCn, char *ArgVc[]) { case 'n': CONF->notAsDaemon = true; break; + case 't': +#ifdef __linux__ + tbl = atoll(optarg); +#else + fprintf(stderr, "Only linux supports multiple tables."); +#endif + break; case 'c': - c = getopt(ArgCn, ArgVc, "cbr::i::mfth"); + c = getopt(ArgCn, ArgVc, "cbr::i::mf::th"); while (c != -1 && c != '?') { uint32_t addr, mask, h = 0; memset(cmd, 0, sizeof(cmd)); cmd[0] = c; - if (c != 'r' && c != 'i' && (h = getopt(j ? 2 : ArgCn, j ? opts : ArgVc, "cbr::i::mfth")) == 'h') + if (c != 'r' && c != 'i' && c!= 'f' && (h = getopt(j ? 2 : ArgCn, j ? opts : ArgVc, "cbr::i::mf::th")) == 'h') strcat(cmd, "h"); else if (h == '?') break; - else if ((c == 'r' || c == 'i') && optarg) { + else if ((c == 'r' || c == 'i' || c == 'f') && optarg) { if (optarg[0] == 'h') { strcat(cmd, "h"); optarg++; @@ -129,12 +141,12 @@ int main(int ArgCn, char *ArgVc[]) { strcat(strcat(cmd, " "), optarg); } } - cliCmd(cmd); - c = (h == 'h' || c == 'r' || c == 'i') ? getopt(j ? 2 : ArgCn, j ? opts : ArgVc, "cbr::i::mft") : h; + cliCmd(cmd, tbl); + c = (h == 'h' || c == 'r' || c == 'i' || c == 'f') ? getopt(j ? 2 : ArgCn, j ? opts : ArgVc, "cbr::i::mf::th") : h; if (c == -1 && j == 1) { free(opts[1]); optind = i, j = 0; - c = getopt(ArgCn, ArgVc, "cbr::i::mft"); + c = getopt(ArgCn, ArgVc, "cbr::i::mf::t"); } if (c != -1 && c != '?') fprintf(stdout, "\n"); @@ -155,7 +167,7 @@ int main(int ArgCn, char *ArgVc[]) { fprintf(stderr, "%s: Must be started as root.\n", fileName); exit(-1); } else if (! (CONF->configFilePath = calloc(1, sizeof(CFG_PATHS) + strlen(ArgVc[optind - !(optind == ArgCn - 1)])))) { - // Freed by igmpProxyInit or igmpProxyCleanup(). + // Freed by igmpProxyInit() or signalHandler() fprintf(stderr, "%s. Out of Memory.\n", fileName); exit(-1); } else if (optind == ArgCn - 1 && !(stat(ArgVc[optind], &st) == 0 && (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)))) { @@ -180,54 +192,120 @@ int main(int ArgCn, char *ArgVc[]) { } } - do { - sighandled = sigstatus = 0; - - // Initializes the deamon. - igmpProxyInit(); + // Detach daemon from stdin/out/err, and fork. + if (!CONF->notAsDaemon && ((i = fork()) != 0 || close(0) < 0 || close(1) < 0 || close(2) < 0)) + i < 0 ? LOG(LOG_ERR, errno, "Failed to detach daemon.") : exit(0); + igmpProxyInit(); +} - // Go to the main loop. - igmpProxyRun(); +#ifdef __linux__ +/** +* Start a new igmpv3proxy process for route table. +*/ +void igmpProxyFork(int tbl) +{ + int i, pid; + + // On first start of new process, initialize the child processes table. + if (chld.nr == 0 && ! (chld.c = calloc(CONF->maxtbl + 1, sizeof(struct pt)))) + // Freed by Self or igmpProxyCleanUp() + LOG(LOG_ERR, errno, "igmpProxyFork: Out of Memory."); + if (mrt_tbl < 0) { + // Find a spot for the process in the table, increase child counter. + // Child table is not shifted, but pid and tbl set to -1, after child exits. + for (i = 0; i < chld.nr && i < CONF->maxtbl && chld.c[i].pid != -1 + && chld.c[i].tbl != tbl; i++); + if ((STARTUP && i >= (CONF->maxtbl - 1)) || i >= CONF->maxtbl) + // On startup check max_tbl-1 because the process for main table is forked in igmpProxyInit + LOG(LOG_WARNING, 0, "Not starting new proxy for table %d. Maximum nr. (%d) of tables in use.", tbl, CONF->maxtbl); + else if (i <= chld.nr && chld.c[i].tbl != tbl) { + // If there is an empty spot in the middle (exited child) put it there. + if ((pid = fork()) < 0 || chld.nr + 1 > CONF->maxtbl) { + // Do not increase the nr. childs here if an emtpy spot was found and check validity. + LOG(LOG_ERR, errno, "igmpProxyFork: Cannot fork() child %d.", chld.nr); + } else if (pid == 0) { + // Child initializes its own start time. + clock_gettime(CLOCK_REALTIME, &starttime); + strcpy(tS, asctime(localtime(&starttime.tv_sec))); + tS[strlen(tS) - 1] = '\0'; + mrt_tbl = tbl; + free (chld.c); // Alloced by Self or loadConfig(), child does not need this. + chld.c = NULL; // Set pointer to NULL, good way to detect a child. + chld.nr = i + 1; // Just so that we know who we are. + } else { + // Parent sets the new child info in table. + chld.c[i].tbl = tbl; + chld.c[i].pid = pid; + chld.nr += i == chld.nr ? 1 : 0; + LOG(LOG_INFO, 0, "Forked child: %d PID: %d for table: %d.", i + 1, chld.c[i].pid, chld.c[i].tbl); + } + } else + LOG(LOG_DEBUG, 0, "igmpProxyFork: Proxy for table %d already active.", tbl); + } +} - // Clean up - igmpProxyCleanUp(); +/** +* Monitor process when multiple proxies are running. +* signalHandler will restart processes if the exit. loadConfig may start new procxies if needed. +*/ +static void igmpProxyMonitor(void) { + struct timespec timeout = timer_ageQueue(); + LOG(LOG_NOTICE, 0, "Monitoring %d proxy processes.", chld.nr); - // If a SIGURG was caught try to restart. - } while (sighandled & GOT_SIGURG); + sigstatus = 0; + SETSIGS; + // Simple busy sleeping loop here, it suits our needs. + while (nanosleep(&timeout, NULL) >= 0 || !(sighandled & GOT_SIGTERM)) { + if ((sighandled & GOT_SIGURG) || (sighandled & GOT_SIGHUP) || (sighandled & GOT_SIGUSR1) || (sighandled & GOT_SIGUSR2)) { + // Signal recevied: Restart or reload config. + sighandled & GOT_SIGURG ? LOG(LOG_NOTICE, 0, "SIGURG: Restarting.") : + sighandled & GOT_SIGUSR2 ? LOG(LOG_NOTICE, 0, "SIGUSR1: Rebuilding interfaces.") + : LOG(LOG_NOTICE, 0, "%sReloading config.", sighandled & GOT_SIGHUP ? "SIGHUP: " : "SIGUSR1: "); + if (sighandled & GOT_SIGURG) { + igmpProxyCleanUp(); + SETSIGS; + } + if ((sighandled & GOT_SIGHUP) || (sighandled & GOT_SIGUSR1)) + reloadConfig(NULL); + if ((sighandled & GOT_SIGHUP) || (sighandled & GOT_SIGUSR2)) + rebuildIfVc(NULL); + sighandled &= ~GOT_SIGURG & ~GOT_SIGHUP & ~GOT_SIGUSR1 & ~GOT_SIGUSR2; + } + // SIGCHLD or loadConfig() may have forked new process, it will end up here. + if (mrt_tbl >= 0) { + // New proxy has config now, so can go to igmpProxyStart(). + sigstatus = 1; + pollFD[0].fd = initIgmp(true); + rebuildIfVc(NULL); + igmpProxyStart(); + } + timeout = timer_ageQueue(); + } + LOG(LOG_INFO, 0, "igmpProxyMonitor: All proceses exited."); + free(CONF->runPath); // Alloced by igmpProxyInit() + free(CONF->chroot); // Alloced by loadConfig() + free(CONF->logFilePath); // Alloced by loadConfig() or igmpProxyInit() + free(CONF->configFilePath); // Alloced by loadConfig() or igmpProxyInit() + exit(0); } +#endif /** * Handles the initial startup of the daemon. */ static void igmpProxyInit(void) { - struct sigaction sa = { 0 }; + pid_t pid = 0; sigstatus = 1; // STARTUP umask(S_IROTH | S_IWOTH | S_IXOTH); clock_gettime(CLOCK_REALTIME, &starttime); strcpy(tS, asctime(localtime(&starttime.tv_sec))); tS[strlen(tS) - 1] = '\0'; - LOG(LOG_WARNING, 0, "Initializing IGMPv3 Proxy on %s.", tS); - sa.sa_sigaction = signalHandler; - sa.sa_flags = SA_SIGINFO; - sigemptyset(&sa.sa_mask); - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGHUP, &sa, NULL); - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGUSR2, &sa, NULL); - sigaction(SIGURG, &sa, NULL); - sigaction(SIGPIPE, &sa, NULL); - sa.sa_flags |= SA_NOCLDWAIT | SA_NODEFER; - sigaction(SIGCHLD, &sa, NULL); - - // Load the config file. + // Load the config file. If no socket group was configured set it to configured users's group or root. if (!loadConfig(CONF->configFilePath)) LOG(LOG_ERR, 0, "Failed to load configuration from '%s'.", CONF->configFilePath); - LOG(LOG_WARNING, 0, "Loaded configuration from '%s'. Starting IGMPv3 Proxy.", CONF->configFilePath); - - // If no socket group was configured set it to configured users's group or root. + LOG(LOG_NOTICE, 0, "Loaded configuration from '%s'. Starting IGMPv3 Proxy.", CONF->configFilePath); if (!CONF->group && !(CONF->group = getgrgid(CONF->user ? CONF->user->pw_gid : 0))) LOG(LOG_WARNING, errno, "Config: Failed to get group for %d.", CONF->user ? CONF->user->pw_gid : 0); unsigned int uid = CONF->user ? CONF->user->pw_uid : 0, gid = CONF->group->gr_gid; @@ -243,119 +321,168 @@ static void igmpProxyInit(void) { break; } } - if ( (stat(CONF->runPath, &st) == -1 && mkdir(CONF->runPath, 0770)) + if ( (stat(CONF->runPath, &st) == -1 && (mkdir(CONF->runPath, 0770))) || chown(CONF->runPath, uid, gid) || chmod (CONF->runPath, 01770)) - LOG(LOG_ERR, errno, "Failed to create run ndirectory %s.", CONF->runPath); + if (errno != EEXIST) // Race with possible childs, it's fine let them win. + LOG(LOG_ERR, errno, "Failed to create run directory %s.", CONF->runPath); - // Switch root if chroot is configured. + // Switch root if chroot is configured. The config file must be placed there. if (CONF->chroot) { - char *p = CONF->configFilePath, *b = basename(CONF->configFilePath); - LOG(LOG_WARNING, 0, "Switching root to %s.", CONF->chroot); - - // Truncate config file path to /. - if (! (CONF->configFilePath = malloc(strlen(b) + 1))) - LOG(LOG_ERR, 0, "Out of Memory"); + // Truncate config and log file path to /. + char *b = basename(CONF->configFilePath); strcpy(CONF->configFilePath, b); - free(p); // Alloced by main() - - // Truncate log file path to /. if (CONF->logFilePath) { - p = CONF->logFilePath, b = basename(CONF->logFilePath); - if (! (CONF->logFilePath = malloc(strlen(b) + 1))) - LOG(LOG_ERR, 0, "Out of Memory"); - strcpy(CONF->logFilePath, b); - free(p); // Alloced by loadConfig() + b = basename(CONF->logFilePath); + strcpy(CONF->logFilePath, b); // Alloced by loadConfig() } - // Link the root to the run directory and set runpath to /.. remove(strcat(CONF->runPath, "root")); - if (symlink(CONF->chroot, CONF->runPath) != 0) + if (symlink(CONF->chroot, CONF->runPath) != 0 && errno != EEXIST) // Race with possible childs, it's fine let them win. LOG(LOG_ERR, errno, "Failed to link chroot directory %s to run directory %s.", CONF->chroot, CONF->runPath); strcpy(CONF->runPath, "/"); + // Set permissions and swith root directory. + if (!(stat(CONF->chroot, &st) == 0 && chown("/", uid, gid) == 0 && chmod(CONF->chroot, 0770) == 0 + && chroot(CONF->chroot) == 0 && chdir("/") == 0)) + LOG(LOG_ERR, errno, "Failed to switch root to %s.",CONF->chroot); // Alloced by Self + LOG(LOG_WARNING, 0, "Switched root to %s.", CONF->chroot); + } + // Finally check log file permissions in case we need to run as user. + if (CONF->logFilePath && (chown(CONF->logFilePath, uid, gid) || chmod(CONF->logFilePath, 0640))) + LOG(LOG_WARNING, errno, "Failed to chown log file %s to %s.", CONF->logFilePath, CONF->user->pw_name); - // Set permissions and swith root directory - if (!(stat(CONF->chroot, &st) == 0 && chmod(CONF->chroot, 0770) == 0 && chroot(CONF->chroot) == 0 && chdir("/") == 0)) - LOG(LOG_ERR, errno, "Failed to switch root to %s.",CONF->chroot); +#ifdef __linux__ + if (mrt_tbl < 0 || !chld.nr) { +#endif + // Write PID in main daemon process only. + char pidFile[strlen(CONF->runPath) + strlen(fileName) + 5]; + sprintf(pidFile, "%s/%s.pid", CONF->runPath, fileName); + remove(pidFile); + FILE *pidFilePtr = fopen(pidFile, "w"); + fprintf(pidFilePtr, "%d\n", getpid()); + fclose(pidFilePtr); +#ifdef __linux__ + } + // When multiple tables are in use, process for default table 0 is forked off here. + if (chld.c && (pid = fork()) < 0) { + LOG(LOG_ERR, errno, "igmpProxyInit: Cannot fork()."); + } else if (chld.c && pid > 0) { + // Parent becomes monitor. + chld.c[chld.nr].pid = pid; + chld.c[chld.nr++].tbl = 0; + rebuildIfVc(NULL); + igmpProxyMonitor(); + } else if (chld.c) { + // Child (or only process) becomes proxy for mrt table 0. + free(chld.c); // Alloced by loadConfig() or igmpProxyStart() + chld.c = NULL; + chld.nr++; } + if (mrt_tbl < 0) + mrt_tbl = 0; +#endif + pollFD[0].fd = initIgmp(true); + rebuildIfVc(NULL); + igmpProxyStart(); +} - // Write PID. - char pidFile[strlen(CONF->runPath) + strlen(fileName) + 5]; - sprintf(pidFile, "%s/%s.pid", CONF->runPath, fileName); - remove(pidFile); - FILE *pidFilePtr = fopen(pidFile, "w"); - fprintf(pidFilePtr, "%d\n", getpid()); - fclose(pidFilePtr); +void igmpProxyStart(void) { + LOG(LOG_WARNING, 0, "Initializing IGMPv3 Proxy on %s.", tS); - // Enable mroute and open cli socket while still running as root. - pollFD[0] = (struct pollfd){ k_enableMRouter(), POLLIN, 0 }; - pollFD[1] = (struct pollfd){ openCliFd(), POLLIN, 0 }; + // Enable mroute and open cli socket and add ip mrules while still running as root. + pollFD[1].fd = pollFD[1].fd != -1 ?: openCliFd(); +#ifdef __linux__ + if (mrt_tbl > 0 && !CONF->disableIpMrules) + ipRules(mrt_tbl, true); +#endif // Make sure logfile and chroot directory are owned by configured user and switch ids. - if (CONF->user) { + if (CONF->user && geteuid() == 0) { + unsigned int uid = CONF->user ? CONF->user->pw_uid : 0, gid = CONF->group->gr_gid; LOG(CONF->logLevel, 0, "Switching user to %s.", CONF->user->pw_name); - if (CONF->chroot && chown("/", uid, gid) != 0) - LOG(LOG_WARNING, errno, "Failed to chown chroot diretory to %s.", CONF->user->pw_name); - - if (CONF->logFilePath && (chown(CONF->logFilePath, uid, gid) || chmod(CONF->logFilePath, 0640))) - LOG(LOG_WARNING, errno, "Failed to chown log file %s to %s.", CONF->logFilePath, CONF->user->pw_name); - if (setgroups(1, (gid_t *)&gid) != 0 || setresgid(CONF->user->pw_gid, CONF->user->pw_gid, CONF->user->pw_gid) != 0 || setresuid(uid, uid, uid) != 0) LOG(LOG_ERR, errno, "Failed to switch to user %s.", CONF->user->pw_name); } - // Initialize IGMP. - initIgmp(false); - - // Detach daemon from stdin/out/err, and fork. - int f = -1; - if (!CONF->notAsDaemon && (close(0) < 0 || close(1) < 0 || close(2) < 0 || (f = fork()) != 0)) - f < 0 ? LOG(LOG_ERR, errno, "Failed to detach daemon.") : exit(0); + do { + SETSIGS; + // Go to the main loop. + sighandled = sigstatus = 0; + igmpProxyRun(); + // Clean up + igmpProxyCleanUp(); + // Reload config. + reloadConfig(NULL); + // Itialize IGMP buffers. + initIgmp(true); + // Add physical interfaces and mcast vifs. + rebuildIfVc(NULL); + // If a SIGURG was caught try to restart. + } while (sighandled & GOT_SIGURG); - // Loads configuration for Physical interfaces and mcast vifs. - rebuildIfVc(NULL); + LOG(LOG_ERR, errno, "Main loop exited, this should not happen."); } /** * Clean up all on exit... */ -static void igmpProxyCleanUp(void) { +void igmpProxyCleanUp(void) { struct timespec endtime; sigstatus = 0x20; // Shutdown + BLOCKSIGS; // Shutdown all interfaces, queriers, remove all routes, close sockets. +#ifdef __linux__ + if (mrt_tbl < 0 && chld.c) { + pid_t pid; + // Wait for all childs. Cli processes are not tracked, their fds are closed. + LOG(LOG_INFO, 0, "Waiting for %d processes to finish.", chld.nr); + while ((pid = wait(NULL)) > 0 && --chld.nr) { + FOR_IF(int i = 0; i < chld.nr + 1; i++, chld.c[i].pid == pid && chld.c[i].tbl > 0) { + ipRules(chld.c[i].tbl, false); + break; + } + LOG(LOG_NOTICE, 0, "Still waiting for %d process%s to finish.", chld.nr, chld.nr > 1 ? "es" : ""); + } + free(chld.c); // Alloced by parsePhyIntToken() or igmpProxyStart() + LOG(LOG_NOTICE, 0, "All processes finished."); + } +#endif rebuildIfVc(NULL); - k_disableMRouter(); - if (pollFD[1].fd > 0) - closeCliFd(pollFD[1].fd); - - // Remove CLI socket and PID file and Config. - if (CONF->runPath) { - // Remove socket and PID file. - char rFile[strlen(CONF->runPath) + strlen(fileName) + 5]; - sprintf(rFile, "%s%s.pid", CONF->runPath, fileName); - remove(rFile); - sprintf(rFile, "%scli.sock", CONF->runPath); - remove(rFile); - if (rmdir(CONF->runPath)) - LOG(LOG_DEBUG, errno, "Cannot remove run dir %s.", CONF->runPath); + if (!(sighandled & GOT_SIGURG) && pollFD[1].fd > 0) + pollFD[1].fd = closeCliFd(pollFD[1].fd); + // Remove CLI socket and PID file and Config in main daemon only. + +#ifdef __linux__ + if (mrt_tbl < 0) { +#endif + if (!(sighandled & GOT_SIGURG) && CONF->runPath) { + // Remove socket and PID file. + char rFile[strlen(CONF->runPath) + strlen(fileName) + 5]; + sprintf(rFile, "%s%s.pid", CONF->runPath, fileName); + remove(rFile); + sprintf(rFile, "%scli.sock", CONF->runPath); + remove(rFile); + if (rmdir(CONF->runPath)) + LOG(LOG_DEBUG, errno, "Cannot remove run dir %s.", CONF->runPath); + } +#ifdef __linux__ } - freeConfig(0); + if (mrt_tbl >= 0) +#endif + pollFD[0].fd = initIgmp(false); // Free remaining allocs. - initIgmp(true); + freeConfig(0); getMemStats(0, -1); - free(CONF->logFilePath); // Alloced by loadConfig() or igmpProxyInit() - free(CONF->runPath); // Alloced by openCliSock() - free(CONF->chroot); // Alloced by loadConfig() - free(CONF->configFilePath); // Alloced by main() or igmpProxyInit() // Log shutdown. clock_gettime(CLOCK_REALTIME, &endtime); strcpy(tE, asctime(localtime(&endtime.tv_sec))); tE[strlen(tE) - 1] = '\0'; - LOG(LOG_WARNING, 0, "Shutting down on %s. Running since %s (%ds).", tE, tS, timeDiff(starttime, endtime).tv_sec); + LOG(LOG_WARNING, 0, "%s on %s. Running since %s (%ds).", sighandled & GOT_SIGURG ? "Restarting" : "Shutting down", + tE, tS, timeDiff(starttime, endtime).tv_sec); } /** @@ -406,9 +533,9 @@ static void igmpProxyRun(void) { // Handle incoming IGMP request first. if (pollFD[0].revents & POLLIN) { LOG(LOG_DEBUG, 0, "igmpProxyRun: RECV IGMP Request %d.", i); - union cmsgU cmsgUn; + union cmsg cmsg; struct iovec ioVec[1] = { { rcv_buf, CONF->pBufsz } }; - struct msghdr msgHdr = (struct msghdr){ NULL, 0, ioVec, 1, &cmsgUn, sizeof(cmsgUn), MSG_DONTWAIT }; + struct msghdr msgHdr = (struct msghdr){ NULL, 0, ioVec, 1, &cmsg, sizeof(cmsg), MSG_DONTWAIT }; int recvlen = recvmsg(pollFD[0].fd, &msgHdr, 0); if (recvlen < 0 || recvlen < (int)sizeof(struct ip) || (msgHdr.msg_flags & MSG_TRUNC)) @@ -427,39 +554,80 @@ static void igmpProxyRun(void) { clock_gettime(CLOCK_REALTIME, &curtime); LOG(LOG_DEBUG, 0, "igmpProxyRun: Fnished request %d in %dus.", i, timeDiff(timeout, curtime).tv_nsec / 1000); - // Keep handling request until timeout, signal or max nr of queued requests. + // Keep handling request until timeout, signal or max nr of queued requests to process in 1 loop. } while (i++ <= CONF->reqQsz && (Rt = ppoll(pollFD, 2, &nto, NULL)) > 0 && !sighandled); } + LOG(LOG_NOTICE, 0, "SIGURG: Restarting."); } /** * Signal handler. Take note of the fact that the signal arrived so that the main loop can take care of it. */ -void signalHandler(int sig, siginfo_t* siginfo, void* context) -{ +static void signalHandler(int sig, siginfo_t* siginfo, void* context) { + int i; + +#ifdef __linux__ + // Send SIG to children. + IF_FOR_IF(sig != SIGPIPE && sig !=SIGCHLD && !(sig == SIGINT && !CONF->notAsDaemon) + && mrt_tbl < 0, i = 0; i < chld.nr; i++, chld.c[i].pid > 0) { + LOG(LOG_DEBUG, 0, "%s to PID: %d for table: %d.", SIGS[sig], chld.c[i].pid, chld.c[i].tbl); + kill(chld.c[i].pid, sig); + } +#endif switch (sig) { case SIGINT: - if (!CONF->notAsDaemon) - return; // Forked daemon ignores SIGINT +#ifdef __linux__ + if (mrt_tbl < 0) +#endif + if (!CONF->notAsDaemon) + return; // Daemon / monitor ignores SIGINT case SIGTERM: - LOG(LOG_NOTICE, 0, "%s: Exiting.", sig == SIGINT ? "SIGINT" : "SIGTERM"); - igmpProxyCleanUp(); - exit(1); - case SIGURG: - LOG(LOG_NOTICE, 0, "SIGURG: Trying to restart, memory leaks may occur."); - sighandled |= GOT_SIGURG; - return; + LOG(LOG_NOTICE, 0, "%s: Exiting.", SIGS[sig]); + if (!(sighandled & GOT_SIGTERM)) { + sighandled |= GOT_SIGTERM; + igmpProxyCleanUp(); +#ifdef __linux__ + } else IF_FOR_IF(mrt_tbl < 0, i = 0; i < chld.nr; i++, chld.c[i].pid > 0) { + // If SIGTERM received more than once, send SIGKILL and exit. + LOG(LOG_DEBUG, 0, "%s to PID: %d for table: %d.", SIGS[9], chld.c[i].pid, chld.c[i].tbl); + kill(chld.c[i].pid, SIGKILL); +#endif + } + free(CONF->runPath); // Alloced by igmpProxyInit() + free(CONF->chroot); // Alloced by loadConfig() + free(CONF->logFilePath); // Alloced by loadConfig() + free(CONF->configFilePath); // Alloced by main() + exit(sig); case SIGCHLD: - LOG(LOG_DEBUG, 0, "SIGCHLD: PID %d exited.", siginfo->si_pid); +#ifdef __linux__ + IF_FOR_IF(chld.c, i = 0; i < chld.nr; i++, chld.c[i].pid == siginfo->si_pid) { + int tbl; + LOG(siginfo->si_status == 0 ? LOG_NOTICE : LOG_WARNING, 0, "PID: %d (%d) for table: %d exited (%i)", + siginfo->si_pid, i, chld.c[i].tbl, (int8_t)siginfo->si_status); + tbl = chld.c[i].tbl; + chld.c[i].pid = chld.c[i].tbl = -1; + if (tbl > 0) + ipRules(tbl, false); + if (!SHUTDOWN && (siginfo->si_status == 15 || siginfo->si_status == 6 || + siginfo->si_status == 11 || siginfo->si_status == 9)) + // Start new proxy in case of unexpected shutdown. + igmpProxyFork(tbl); + return; + } + if (! chld.c || i > chld.nr || chld.c[i].pid != -1) +#endif + LOG(LOG_DEBUG, 0, "SIGCHLD: PID %d exited (%d).", siginfo->si_pid, siginfo->si_status); return; case SIGPIPE: - LOG(LOG_NOTICE, 0, "SIGPIPE: Ceci n'est pas un SIGPIPE."); - /* FALLTHRU */ + LOG(LOG_NOTICE, 0, "SIGPIPE: Ceci n'est pas un SIGPIPE."); // FALLTHRU + case SIGURG: case SIGHUP: case SIGUSR1: case SIGUSR2: - sighandled |= sig == SIGHUP ? GOT_SIGHUP : sig == SIGUSR1 ? GOT_SIGUSR1 : sig == SIGUSR2 ? GOT_SIGUSR2 : 0; + sighandled |= sig == SIGURG ? GOT_SIGURG : sig == SIGHUP ? GOT_SIGHUP + : sig == SIGUSR1 ? GOT_SIGUSR1 : sig == SIGUSR2 ? GOT_SIGUSR2 : 0; return; } - LOG(LOG_NOTICE, 0, "Caught unhandled signal %d", sig); + + LOG(LOG_NOTICE, 0, "Caught unhandled signal %s", SIGS[sig]); } diff --git a/src/igmpv3proxy.h b/src/igmpv3proxy.h index e241093..4e66d32 100644 --- a/src/igmpv3proxy.h +++ b/src/igmpv3proxy.h @@ -60,12 +60,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -73,6 +75,9 @@ //################################################################################# // Global definitions and declarations. //################################################################################# +#define IF_FOR(x, y) if (x) for (y) +#define FOR_IF(x, y) for (x) if (y) +#define IF_FOR_IF(x, y, z) if (x) for (y) if (z) // Set type of control message structure for received socket data. #ifdef IP_PKTINFO @@ -88,8 +93,8 @@ struct memstats { int64_t rcv, snd; // Buffers int64_t qry, tmr; // Queries, Timers }; -extern struct memstats memuse, memalloc, memfree; -inline void __free(void *p, int64_t *m, int64_t *f, size_t s) { free(p); *m -= s; ++*f; } +bool getMemStats(int h, int fd); // From lib.c +inline void __free(void *p, int64_t *m, int64_t *f, size_t s) { free(p); if ((*m -= s) < 0 || !++*f) { getMemStats(0, -1); exit(6); }; } #define _free(p, m, s) __free(p, &memuse.m, &memfree.m, s) #define _malloc(p, m, s) (((p = malloc(s)) && (memuse.m += s) > 0 && ++memalloc.m) || getMemStats(0, -1)) #define _calloc(p, n, m, s) (((p = calloc(n, s)) && (memuse.m += (n * s)) > 0 && ++memalloc.m) || getMemStats(0, -1)) @@ -138,6 +143,12 @@ struct Config { uint32_t dHostsHTSize; uint32_t hashSeed; uint16_t mcTables; +#ifdef __linux__ + // Mroute tables only supported on linux + int maxtbl; + int defaultTable; + bool disableIpMrules; +#endif // Max origins for route when bw control is disabled. uint16_t maxOrigins; // Set default interface status and parameters. @@ -161,6 +172,13 @@ struct Config { bool cksumVerify; }; +// Timers for proxy control. +struct timers { + uint64_t rescanConf; + uint64_t rescanVif; + uint64_t bwControl; +}; + // Linked list of filters. struct subnet { uint32_t ip; @@ -196,6 +214,9 @@ struct queryParam { // Structure to keep configuration for VIFs. struct vifConfig { char name[IF_NAMESIZE]; +#ifdef __linux__ + int tbl; // Mroute Table for Interface. +#endif uint8_t state; // Configured interface state uint8_t threshold; // Interface MC TTL uint64_t ratelimit; // Interface ratelimit @@ -209,7 +230,11 @@ struct vifConfig { struct vifConfig *next; }; #define VIFSZ (sizeof(struct vifConfig)) +#ifdef __linux__ +#define DEFAULT_VIFCONF (struct vifConfig){ "", conf.defaultTable, conf.defaultInterfaceState, conf.defaultThreshold, conf.defaultRatelimit, {conf.querierIp, conf.querierVer, conf.querierElection, conf.robustnessValue, conf.queryInterval, conf.queryResponseInterval, conf.lastMemberQueryInterval, conf.lastMemberQueryCount, 0, 0}, false, conf.cksumVerify, conf.quickLeave, conf.proxyLocalMc, NULL, NULL, vifConf } +#else #define DEFAULT_VIFCONF (struct vifConfig){ "", conf.defaultInterfaceState, conf.defaultThreshold, conf.defaultRatelimit, {conf.querierIp, conf.querierVer, conf.querierElection, conf.robustnessValue, conf.queryInterval, conf.queryResponseInterval, conf.lastMemberQueryInterval, conf.lastMemberQueryCount, 0, 0}, false, conf.cksumVerify, conf.quickLeave, conf.proxyLocalMc, NULL, NULL, vifConf } +#endif // Running querier status for interface. struct querier { // igmp querier status for interface @@ -277,6 +302,7 @@ struct IfDesc { #define DEFAULT_MAX_ORIGINS 64 // Maximun nr of group sources. #define DEFAULT_HASHTABLE_SIZE 32 // Default host tracking hashtable size. #define DEFAULT_ROUTE_TABLES 32 // Default hash table size for route table. +#define DEFAULT_MAXTBL 32 // Maximum nr of mroute tables. // Signal Handling. 0 = no signal, 2 = SIGHUP, 4 = SIGUSR1, 8 = SIGUSR2, 5 = Timed Reload, 9 = Timed Rebuild, 32 = SHUTDOWN #define GOT_SIGHUP 0x02 @@ -285,12 +311,32 @@ struct IfDesc { #define GOT_SIGUSR2 0x08 #define GOT_IFREB 0x09 #define GOT_SIGURG 0x10 +#define GOT_SIGTERM 0x20 #define CONFRELOAD (sigstatus & GOT_SIGUSR1) #define IFREBUILD (sigstatus & GOT_SIGUSR2) #define SSIGHUP (sigstatus & GOT_SIGHUP) #define NOSIG (sigstatus == 0) #define STARTUP (sigstatus == 1) #define SHUTDOWN (sigstatus == 32) +#define STRSIG const char *SIGS[32] = { "", "SIGHUP", "SIGINT", "", "", "", "SIGABRT", "", "", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "", "SIGTERM", "SIGURG", "SIGCHLD", "", "", "SIGCHLD", "", "", "SIGURG", "", "", "", "", "", "", "SIGUSR1", "SIGUSR2" }; +#define SETSIGS struct sigaction sa = { 0 }; \ + sa.sa_sigaction = signalHandler; \ + sa.sa_flags = SA_SIGINFO; \ + sigemptyset(&sa.sa_mask); \ + sigaction(SIGTERM, &sa, NULL); \ + sigaction(SIGINT, &sa, NULL); \ + sigaction(SIGHUP, &sa, NULL); \ + sigaction(SIGUSR1, &sa, NULL); \ + sigaction(SIGUSR2, &sa, NULL); \ + sigaction(SIGURG, &sa, NULL); \ + sigaction(SIGPIPE, &sa, NULL); \ + sa.sa_flags |= SA_NOCLDWAIT | SA_NODEFER; \ + sigaction(SIGCHLD, &sa, NULL) +#define BLOCKSIGS signal(SIGUSR1, SIG_DFL); \ + signal(SIGUSR2, SIG_DFL); \ + signal(SIGHUP, SIG_DFL); \ + signal(SIGCHLD, SIG_DFL); \ + signal(SIGPIPE, SIG_DFL) // CLI Defines. #define CLI_CMD_BUF 256 @@ -349,15 +395,30 @@ struct igmpv3_report { // Global Variables. //############A##################################################################### +// Memory Statistics. +extern struct memstats memuse, memalloc, memfree; + // Filename, Help string. -extern char *fileName, Usage[]; +extern char *fileName, Usage[], tS[32]; // Timekeeping. -extern struct timespec curtime, utcoff; +extern struct timespec starttime, curtime, utcoff; // Process Signaling. extern uint8_t sighandled, sigstatus, logwarning; +#ifdef __linux__ +// MRT route table id. Linux only, not supported on FreeBSD. +extern struct chld { + struct pt { + pid_t pid; + int tbl; + } *c; + int nr; +} chld; +extern int mrt_tbl; +#endif + // Upstream vif mask. extern uint32_t uVifs; @@ -369,6 +430,11 @@ extern uint32_t alligmp3_group; // IGMPv3 addr in net ord //################################################################################# // Lib function prototypes. //################################################################################# +/** +* igmpproxy.c +*/ +void igmpProxyFork(int tbl); +void igmpProxyCleanUp(void); /** * config.c @@ -385,9 +451,9 @@ void configureVifs(void); * cli.c */ int openCliFd(void); -void closeCliFd(int fd); +int closeCliFd(int fd); void acceptCli(int fd); -void cliCmd(char *cmd); +void cliCmd(char *cmd, int tbl); /** * ifvc.c @@ -402,12 +468,12 @@ void buildIfVc(void); struct IfDesc *getIfL(void); struct IfDesc *getIf(unsigned int ix, char name[IF_NAMESIZE], int mode); void getIfStats(struct IfDesc *IfDp, int h, int fd); -void getIfFilters(int h, int fd); +void getIfFilters(struct IfDesc *IfDp, int h, int fd); /** * igmp.c */ -void initIgmp(bool free); +int initIgmp(bool activate); void acceptIgmp(int recvlen, struct msghdr msgHdr); void sendIgmp(struct IfDesc *IfDp, struct igmpv3_query *query); void sendGeneralMemberQuery(struct IfDesc *IfDp); @@ -415,7 +481,7 @@ void sendGeneralMemberQuery(struct IfDesc *IfDp); /** * lib.c */ -#define LOG(x, ...) x <= CONF->logLevel ? myLog(x, __VA_ARGS__) : (void)0 +#define LOG(x, ...) ((logwarning |= (x == LOG_WARNING)) || true) && !(x <= CONF->logLevel) ?: myLog(x, __VA_ARGS__) const char *inetFmt(uint32_t addr, int pos); const char *inetFmts(uint32_t addr, uint32_t mask, int pos); uint16_t inetChksum(uint16_t *addr, int len); @@ -436,7 +502,9 @@ uint16_t grecType(struct igmpv3_grec *grec); uint16_t grecNscrs(struct igmpv3_grec *grec); uint16_t getIgmpExp(register int val, register int d); void myLog(int Serverity, int Errno, const char *FmtSt, ...); -bool getMemStats(int h, int fd); +#ifdef __linux +void ipRules(int tbl, bool activate); +#endif /** * kern.c @@ -483,7 +551,7 @@ void delQuery(struct IfDesc *IfDP, void *qry, void *route, void *_src, uint8 */ #define TMNAMESZ 48 #define DEBUGQUEUE(...) if (CONF->logLevel == LOG_DEBUG) debugQueue(__VA_ARGS__) -struct timespec timer_ageQueue(); +struct timespec timer_ageQueue(void); uint64_t timer_setTimer(int delay, const char *name, void (*func)(), void *); void *timer_clearTimer(uint64_t timer_id); void debugQueue(const char *header, int h, int fd); diff --git a/src/kern.c b/src/kern.c index 4494daa..15d7c98 100644 --- a/src/kern.c +++ b/src/kern.c @@ -183,6 +183,11 @@ int k_enableMRouter(void) { LOG(LOG_ERR, errno, "IGMP socket open Failed"); else if (setsockopt(mrouterFD, IPPROTO_IP, IP_HDRINCL, (void *)&Va, sizeof(Va)) < 0) LOG(LOG_ERR, errno, "IGMP socket IP_HDRINCL Failed"); +#ifdef __linux__ + else if (setsockopt(mrouterFD, IPPROTO_IP, MRT_TABLE, &mrt_tbl, sizeof(mrt_tbl)) < 0) + errno == ENOPROTOOPT ? LOG(LOG_ERR, errno, "IGMP socket MRT_TABLE Failed. Make sure your kernel has CONFIG_IP_MROUTE_MULTIPLE_TABLES=y") + : LOG(LOG_ERR, errno, "IGMP socket MRT_TABLE Failed."); +#endif else if (setsockopt(mrouterFD, IPPROTO_IP, MRT_INIT, (void *)&Va, sizeof(Va)) < 0) LOG(LOG_ERR, errno, "IGMP socket MRT_INIT Failed"); else if (setsockopt(mrouterFD, IPPROTO_IP, IFINFO, (void *)&Va, sizeof(Va)) < 0) @@ -194,6 +199,7 @@ int k_enableMRouter(void) { CONF->bwControlInterval = 0; } #endif + LOG(LOG_INFO, 0, "k_enableMRouter: Enabled mroute socket."); fcntl(mrouterFD, F_SETFD, O_NONBLOCK); return mrouterFD; diff --git a/src/lib.c b/src/lib.c index 91237dd..74362d6 100644 --- a/src/lib.c +++ b/src/lib.c @@ -38,10 +38,9 @@ */ #include "igmpv3proxy.h" -#include char Usage[] = -"Usage: %s [-h | -V] [-c [-cbriftm...] [-h]] [[-n | -v | -d] ]\n" +"Usage: %s [-h | -V] [-t table] [-c [-cbriftm...] [-h]] [[-n | -v | -d] ]\n" "\n" " -h Display this help screen\n" " -V Display version.\n" @@ -49,6 +48,7 @@ char Usage[] = " -v Run in verbose mode, Output all messages on stderr. Implies -n.\n" " -vv Run in more verbose mode. Implies -n.\n" " -d Run in debug mode. Implies -vv.\n" +" -t Operate on routing table.\n" " -c Daemon control and statistics.\n" " -c Reload Configuration.\n" " -b Rebuild Interfaces.\n" @@ -309,22 +309,79 @@ void myLog(int Severity, int Errno, const char *FmtSt, ...) { snprintf(LogMsg + Ln, sizeof(LogMsg) - Ln, "; Errno(%d): %s", Errno, strerror(Errno)); va_end(ArgPt); - if (CONF->logFilePath || CONF->log2Stderr) + if ((CONF->logFilePath || CONF->log2Stderr) && lfp) +#ifdef __linux__ + if (!chld.nr || mrt_tbl < 0) + fprintf(lfp, "%02ld:%02ld:%02ld:%04ld %s\n", sec % 86400 / 3600, sec % 3600 / 60, + sec % 3600 % 60, nsec / 100000, LogMsg); + else + fprintf(lfp, "%02ld:%02ld:%02ld:%04ld [%d] %s\n", sec % 86400 / 3600, sec % 3600 / 60, + sec % 3600 % 60, nsec / 100000, mrt_tbl, LogMsg); +#else fprintf(lfp, "%02ld:%02ld:%02ld:%04ld %s\n", sec % 86400 / 3600, sec % 3600 / 60, sec % 3600 % 60, nsec / 100000, LogMsg); +#endif else syslog(Severity, "%s", LogMsg); - if (lfp != stderr) + if (lfp && lfp != stderr) fclose(lfp); - if (Severity <= LOG_ERR) + if (Severity <= LOG_ERR) { +#ifdef __linux__ + if (mrt_tbl < 0) { + sigstatus = 0x20; + IF_FOR_IF(chld.c, Ln = 0; Ln < chld.nr; Ln++, chld.c[Ln].pid != 0) { + LOG(LOG_INFO, 0, "SIGTERM: To PID: %d for table: %d.", chld.c[Ln].pid, chld.c[Ln].tbl); + kill(chld.c[Ln].pid, SIGTERM); + chld.c[Ln].pid = chld.c[Ln].tbl = -1; + } + igmpProxyCleanUp(); + } +#endif exit(-1); - logwarning |= (Severity == LOG_WARNING); + } } +#ifdef __linux__ +/** +* Sets or removes ip mrules for table. +*/ +void ipRules(int tbl, bool activate) +{ + struct IfDesc *IfDp; + char msg[12]; + sprintf(msg, "%d", tbl); + LOG(LOG_INFO, 0, "ipRules: %s mrules%s%s.", activate ? "Adding" : "Removing", activate ? "" : " for table ", activate ? "" : msg); + GETIFLIF(IfDp, IfDp->conf->tbl == tbl && !IS_DISABLED(IfDp->state)) { + pid_t pid; + LOG(LOG_NOTICE, 0, "%s ip mrules for interface %s.", activate ? "Adding" : "Removing", IfDp->Name); + for (int i = 0; i < 2; i++) { + if ((pid = fork()) < 0) { + LOG(LOG_ERR, errno, "ipRules: Cannot fork."); + } else if (pid == 0) { + execlp("ip", "ip", "mrule", activate ? "add" : "del", i ? "iif" : "oif", IfDp->Name, "table", msg, NULL); + LOG(LOG_WARNING, errno, "Failed to exec: ip mrule %s iif %s table %s.", activate ? "add" : "del", IfDp->Name, msg); + exit(-1); + } else { + int status; + waitpid(pid, &status, 0); + if (WEXITSTATUS(status) != 0) + LOG(activate ? LOG_WARNING : LOG_NOTICE, errno, "Failed to ip mrule %s %s %s table %s.", + activate ? "add" : "del", i ? "iif" : "oif", IfDp->Name, msg); + } + } + } +} +#endif + +/** +* Show memory statistics for debugging purposes. +*/ bool getMemStats(int h, int fd) { char buf[1280], msg[1024]; struct rusage usage; + if (fd < 0 && CONF->logLevel < LOG_DEBUG) + return false; if (fd >= 0) { if (h) { diff --git a/src/mctable.c b/src/mctable.c index c39d538..87b5fca 100644 --- a/src/mctable.c +++ b/src/mctable.c @@ -161,8 +161,7 @@ struct ifMct *delGroup(struct mcTable* mct, struct IfDesc *IfDp, struct ifMct *i inetFmt(mct->group, 1), IfDp->Name); // Update the interface group list. - if (! imc) - for (imc = *list; imc && imc->mct != mct; imc = imc->next); + IF_FOR(! imc, imc = *list; imc && imc->mct != mct; imc = imc->next); pimc = imc->prev; if (imc->next) imc->next->prev = imc->prev; @@ -699,7 +698,7 @@ void clearGroups(void *Dp) { // Downstream interface transition. if (((CONFRELOAD || SSIGHUP) && IS_DOWNSTREAM(newstate) && IS_DOWNSTREAM(oldstate)) - || (IS_DOWNSTREAM(oldstate) && !IS_DOWNSTREAM(newstate))) + || (IS_DOWNSTREAM(oldstate) && !IS_DOWNSTREAM(newstate))) for (imc = IfDp->dMct; imc; imc = imc ? imc->next : IfDp->dMct) { if (IS_DOWNSTREAM(oldstate) && !IS_DOWNSTREAM(newstate)) { // Transition to disabled / upstream, remove from group. diff --git a/src/querier.c b/src/querier.c index 688823b..696edd7 100644 --- a/src/querier.c +++ b/src/querier.c @@ -135,13 +135,12 @@ inline void processGroupQuery(struct IfDesc *IfDp, struct igmpv3_query *query, u struct qlst *qlst = NULL; struct src *src = mct->sources; nsrcs = sortArr((uint32_t *)query->igmp_src, nsrcs); - for (uint32_t i = 0; src && i < nsrcs; src = src->next) - if (src->ip >= query->igmp_src[i].s_addr) { - // Do not add denied sources to query list. - if (src->ip == query->igmp_src[i].s_addr && checkFilters(IfDp, 1, src, mct)) - qlst = addSrcToQlst(src, IfDp, qlst, (uint32_t)-1); - i++; - } + FOR_IF(uint32_t i = 0; src && i < nsrcs; src = src->next, src->ip >= query->igmp_src[i].s_addr) { + // Do not add denied sources to query list. + if (src->ip == query->igmp_src[i].s_addr && checkFilters(IfDp, 1, src, mct)) + qlst = addSrcToQlst(src, IfDp, qlst, (uint32_t)-1); + i++; + } LOG(LOG_INFO, 0, "processGroupQuery: Group group and source specific query for %s with %d sources on %s.", inetFmt(mct->group, 1), nsrcs, IfDp->Name); startQuery(IfDp, qlst); diff --git a/src/timers.c b/src/timers.c index eebde81..b4fe415 100644 --- a/src/timers.c +++ b/src/timers.c @@ -55,7 +55,7 @@ static uint64_t id = 1; * Execute at most CONF->tmQsz expired timers, return time difference to next scheduled timer. * Returns -1,-1 if no timer is scheduled, 0, -1 if next timer has already expired. */ -struct timespec timer_ageQueue() { +struct timespec timer_ageQueue(void) { struct timeOutQueue *node = queue; uint64_t i = 1;