igmpproxy: Multiple downlink interfaces fix.
authorJohn Crispin <john@openwrt.org>
Tue, 17 Mar 2015 09:43:07 +0000 (09:43 +0000)
committerJohn Crispin <john@openwrt.org>
Tue, 17 Mar 2015 09:43:07 +0000 (09:43 +0000)
from Erik Tews <erik@datenzone.de>

This patch has two effects. First, the quickleave feature/behaviour is
disabled for all groups that are used on more than one interface. The
idea of quickleave is to leave a group fast and later figure out whether
there is still somebody interested in that group. For groups used on
more than one interface, it is already known that there is still
somebody interested in that group.

Second, when a leave is received for a group that is used on more than
one interface, igmpproxy sends queries on all interface to discover
remeining listeners for that group. Previously these queries were only
send on the interface the leave was received on, so that listeners on
the other interfaces were not discovered and the group might be left on
the upstream router incorrectly.

This patch can be improved by sending the queries only on the interface
the leave was received on and adapting the algorithm in
internAgeRoute(...) in rttable.c in a way that only one interface is
actually processed and all other interfaces of the route are silently
assumed to be still active.

Signed-off-by: Erik Tews <erik@datenzone.de>
SVN-Revision: 44859

package/network/services/igmpproxy/patches/250-fix_multiple_downlink_interfaces.patch [new file with mode: 0644]

diff --git a/package/network/services/igmpproxy/patches/250-fix_multiple_downlink_interfaces.patch b/package/network/services/igmpproxy/patches/250-fix_multiple_downlink_interfaces.patch
new file mode 100644 (file)
index 0000000..24aefce
--- /dev/null
@@ -0,0 +1,154 @@
+--- a/src/igmpproxy.h
++++ b/src/igmpproxy.h
+@@ -251,6 +251,7 @@ int activateRoute(uint32_t group, uint32
+ void ageActiveRoutes();
+ void setRouteLastMemberMode(uint32_t group);
+ int lastMemberGroupAge(uint32_t group);
++int interfaceInRoute(int32_t group, int Ix);
+ /* request.c
+  */
+--- a/src/request.c
++++ b/src/request.c
+@@ -41,10 +41,10 @@
+ // Prototypes...
+ void sendGroupSpecificMemberQuery(void *argument);  
+-    
++
+ typedef struct {
+     uint32_t      group;
+-    uint32_t      vifAddr;
++    // uint32_t      vifAddr;
+     short       started;
+ } GroupVifDesc;
+@@ -142,7 +142,7 @@ void acceptLeaveMessage(uint32_t src, ui
+         // Call the group spesific membership querier...
+         gvDesc->group = group;
+-        gvDesc->vifAddr = sourceVif->InAdr.s_addr;
++        // gvDesc->vifAddr = sourceVif->InAdr.s_addr;
+         gvDesc->started = 0;
+         sendGroupSpecificMemberQuery(gvDesc);
+@@ -159,6 +159,9 @@ void acceptLeaveMessage(uint32_t src, ui
+ */
+ void sendGroupSpecificMemberQuery(void *argument) {
+     struct  Config  *conf = getCommonConfig();
++    struct  IfDesc  *Dp;
++    struct  RouteTable  *croute;
++    int     Ix;
+     // Cast argument to correct type...
+     GroupVifDesc   *gvDesc = (GroupVifDesc*) argument;
+@@ -166,22 +169,38 @@ void sendGroupSpecificMemberQuery(void *
+     if(gvDesc->started) {
+         // If aging returns false, we don't do any further action...
+         if(!lastMemberGroupAge(gvDesc->group)) {
++            // FIXME: Should we free gvDesc here?
+             return;
+         }
+     } else {
+         gvDesc->started = 1;
+     }
+-    // Send a group specific membership query...
+-    sendIgmp(gvDesc->vifAddr, gvDesc->group, 
+-             IGMP_MEMBERSHIP_QUERY,
+-             conf->lastMemberQueryInterval * IGMP_TIMER_SCALE, 
+-             gvDesc->group, 0);
+-
+-    my_log(LOG_DEBUG, 0, "Sent membership query from %s to %s. Delay: %d",
+-        inetFmt(gvDesc->vifAddr,s1), inetFmt(gvDesc->group,s2),
+-        conf->lastMemberQueryInterval);
+-
++    /**
++     * FIXME: This loops through all interfaces the group is active on an sends queries.
++     *        It might be better to send only a query on the interface the leave was accepted on and remove only that interface from the route.
++     */
++
++    // Loop through all downstream interfaces
++    for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ) {
++        if ( Dp->InAdr.s_addr && ! (Dp->Flags & IFF_LOOPBACK) ) {
++            if(Dp->state == IF_STATE_DOWNSTREAM) {
++                // Is that interface used in the group?
++                if (interfaceInRoute(gvDesc->group ,Dp->index)) {
++                        
++                    // Send a group specific membership query... 
++                    sendIgmp(Dp->InAdr.s_addr, gvDesc->group, 
++                            IGMP_MEMBERSHIP_QUERY,
++                            conf->lastMemberQueryInterval * IGMP_TIMER_SCALE, 
++                            gvDesc->group, 0);
++
++                    my_log(LOG_DEBUG, 0, "Sent membership query from %s to %s. Delay: %d",
++                            inetFmt(Dp->InAdr.s_addr,s1), inetFmt(gvDesc->group,s2),
++                            conf->lastMemberQueryInterval);
++                }
++            }
++        }
++    }
+     // Set timeout for next round...
+     timer_setTimer(conf->lastMemberQueryInterval, sendGroupSpecificMemberQuery, gvDesc);
+--- a/src/rttable.c
++++ b/src/rttable.c
+@@ -428,6 +428,25 @@ void ageActiveRoutes() {
+ }
+ /**
++*   Counts the number of interfaces a given route is active on
++*/
++int numberOfInterfaces(struct RouteTable *croute) {
++    int Ix;
++    struct IfDesc *Dp;
++    int result = 0;
++    // Loop through all interfaces
++    for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ) {
++        // If the interface is used by the route, increase counter
++        if(BIT_TST(croute->vifBits, Dp->index)) {
++            result++;
++        }
++    }
++    my_log(LOG_DEBUG, 0, "counted %d interfaces", result);
++    return result;
++}
++
++
++/**
+ *   Should be called when a leave message is recieved, to
+ *   mark a route for the last member probe state.
+ */
+@@ -439,8 +458,11 @@ void setRouteLastMemberMode(uint32_t gro
+     if(croute!=NULL) {
+         // Check for fast leave mode...
+         if(croute->upstrState == ROUTESTATE_JOINED && conf->fastUpstreamLeave) {
+-            // Send a leave message right away..
+-            sendJoinLeaveUpstream(croute, 0);
++            // Send a leave message right away only when the route has been active on only one interface
++            if (numberOfInterfaces(croute) <= 1) {
++              my_log(LOG_DEBUG, 0, "Leaving group %d now", group);
++                sendJoinLeaveUpstream(croute, 0);
++            } 
+         }
+         // Set the routingstate to Last member check...
+         croute->upstrState = ROUTESTATE_CHECK_LAST_MEMBER;
+@@ -677,3 +699,18 @@ void logRouteTable(char *header) {
+     
+         my_log(LOG_DEBUG, 0, "-----------------------------------------------------");
+ }
++
++/**
++*   Returns true when the given group belongs to the given interface
++*/
++int interfaceInRoute(int32_t group, int Ix) {
++    struct RouteTable*  croute;
++    croute = findRoute(group);
++    if (croute != NULL) {
++        my_log(LOG_DEBUG, 0, "Interface id %d is in group $d", Ix, group);
++        return BIT_TST(croute->vifBits, Ix);
++    } else {
++        return 0;
++    }
++}
++