From 6c6e5af8c736aadc441fc55961c119a5bb3af6af Mon Sep 17 00:00:00 2001
From: Reidar Cederqvist <reidar.cederqvist@iopsys.eu>
Date: Wed, 27 Nov 2019 15:41:45 +0100
Subject: [PATCH] Fix fast leave bug when multiple LAN ports joined one group.

	- In fast leave mode, leave a multicast group on upstream only
	  when all LAN ports have left the group. The member ports
	  checking is via parsing bridge MDB.

    - Removed the specific query during fast leave, which is a known
	  error in the first commit for fast leave.

    - In fast leave mode, the fast leave flag in kernel bridge driver
	  is set on all member interfaces of bridge br-lan.

    - Disabled the IGMP general query on br-lan in kernel bridge
	  driver, because it is taken by mcproxy.
---
 mcproxy/include/parser/configuration.hpp |   2 +-
 mcproxy/include/proxy/timers_values.hpp  |   3 +
 mcproxy/src/proxy/proxy.cpp              |   4 +-
 mcproxy/src/proxy/querier.cpp            | 100 ++++++++++++++++++++++-
 mcproxy/src/proxy/timers_values.cpp      |  13 +++
 5 files changed, 119 insertions(+), 3 deletions(-)

diff --git a/mcproxy/include/parser/configuration.hpp b/mcproxy/include/parser/configuration.hpp
index 801b7fb..5141ca5 100644
--- a/mcproxy/include/parser/configuration.hpp
+++ b/mcproxy/include/parser/configuration.hpp
@@ -45,7 +45,7 @@ private:
     int m_qri = -1; // Query response interval
     int m_lmqi = -1; // Last member Query interval
     int m_rv = -1; // robustness value
-    bool m_fastleave = true; // Fast leave
+    bool m_fastleave = false; // Fast leave
 
     //<line number (for a better error message output), command>
     std::vector<std::pair<unsigned int, std::string>> m_cmds;
diff --git a/mcproxy/include/proxy/timers_values.hpp b/mcproxy/include/proxy/timers_values.hpp
index 03ba313..808c348 100644
--- a/mcproxy/include/proxy/timers_values.hpp
+++ b/mcproxy/include/proxy/timers_values.hpp
@@ -36,6 +36,7 @@ struct timers_values_tank {
     std::chrono::milliseconds last_listener_query_interval = std::chrono::milliseconds(1000);
     unsigned int last_listener_query_count = robustness_variable;
     std::chrono::milliseconds unsolicited_report_interval = std::chrono::milliseconds(1000);
+    bool fastleave = false;
 };
 
 static timers_values_tank default_timers_values_tank = timers_values_tank();
@@ -82,6 +83,7 @@ public:
     std::chrono::milliseconds get_last_listener_query_time() const; //
     std::chrono::milliseconds get_unsolicited_report_interval() const;
     std::chrono::milliseconds get_older_host_present_interval() const; //
+    bool get_fastleave() const;
 
     void set_robustness_variable(unsigned int robustness_variable);
     void set_query_interval(std::chrono::seconds query_interval);
@@ -91,6 +93,7 @@ public:
     void set_last_listener_query_interval(std::chrono::milliseconds last_listener_query_interval);
     void set_last_listener_query_count(unsigned int last_listener_query_count);
     void set_unsolicited_report_interval(std::chrono::milliseconds unsolicited_report_interval);
+    void set_fastleave(bool fastleave);
 
     void reset_to_default_tank();
 
diff --git a/mcproxy/src/proxy/proxy.cpp b/mcproxy/src/proxy/proxy.cpp
index 34b3ee9..7f1a446 100644
--- a/mcproxy/src/proxy/proxy.cpp
+++ b/mcproxy/src/proxy/proxy.cpp
@@ -276,7 +276,9 @@ void proxy::start_proxy_instances()
                     tv.set_startup_query_count(val);
             }
 
-            std::cout << "fastleave :" <<m_configuration->get_fastleave() <<std::endl;
+            bool fastleave = m_configuration->get_fastleave();
+            std::cout << "fastleave :" <<fastleave <<std::endl;
+            tv.set_fastleave(fastleave);
 
             pr_i->add_msg(std::make_shared<config_msg>(config_msg::ADD_DOWNSTREAM, if_index, d, tv));
         }
diff --git a/mcproxy/src/proxy/querier.cpp b/mcproxy/src/proxy/querier.cpp
index 9e6c322..c4f8b9e 100644
--- a/mcproxy/src/proxy/querier.cpp
+++ b/mcproxy/src/proxy/querier.cpp
@@ -35,6 +35,95 @@
 #include <iostream>
 #include <sstream>
 
+extern "C" {
+
+#include <netinet/ip.h>
+#include <linux/if_bridge.h>
+#include <asm/types.h>
+#include <libnetlink.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#ifndef MDBA_RTA
+#define MDBA_RTA(r) \
+        ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct br_port_msg))))
+#endif
+
+static unsigned int group_ports = 0;
+
+static void
+check_mdb_entry(struct nlmsghdr *n, struct in_addr *gaddr, struct rtattr *attr)
+{
+    struct rtattr *i;
+    struct br_mdb_entry *e;
+
+    int rem = RTA_PAYLOAD(attr);
+    for (i = (struct rtattr *) RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+        e = (br_mdb_entry*) RTA_DATA(i);
+
+        if (e->addr.proto == htons(ETH_P_IP) && e->addr.u.ip4 == gaddr->s_addr)
+            group_ports++;
+    }
+}
+
+static int parse_mdb(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+{
+    struct in_addr *gaddr = (struct in_addr *) arg;
+    struct br_port_msg *r = (struct br_port_msg *) NLMSG_DATA(n);
+    int len = n->nlmsg_len;
+    struct rtattr *tb[MDBA_MAX + 1];
+
+    if (n->nlmsg_type != RTM_GETMDB)
+        return -1;
+
+    len -= NLMSG_LENGTH(sizeof (*r));
+    if (len < 0) {
+        printf("BUG: wrong nlmsg len %d\n", len);
+        return -1;
+    }
+
+    parse_rtattr(tb, MDBA_MAX, MDBA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof (*r)));
+
+    if (tb[MDBA_MDB]) {
+        struct rtattr *i;
+        int rem = RTA_PAYLOAD(tb[MDBA_MDB]);
+
+        for (i = (struct rtattr*) RTA_DATA(tb[MDBA_MDB]); RTA_OK(i, rem); i = RTA_NEXT(i, rem))
+            check_mdb_entry(n, gaddr, i);
+    }
+
+    return 0;
+}
+
+unsigned int group_mem_port_count(struct in_addr gaddr)
+{
+    struct rtnl_handle rth;
+
+    group_ports = 0;
+
+    if (rtnl_open(&rth, 0) < 0) {
+        printf("rtnl_open() failed\n");
+        goto out;
+    }
+
+    if (rtnl_wilddump_request(&rth, PF_BRIDGE, RTM_GETMDB) < 0) {
+        printf("Cannot send RTM_GETMDG dump request\n");
+        goto out;
+    }
+
+    if (rtnl_dump_filter(&rth, parse_mdb, &gaddr) < 0) {
+        printf("Dump terminated\n");
+        goto out;
+    }
+
+out:
+    rtnl_close(&rth);
+
+    return group_ports;
+}
+
+}
+
 querier::querier(worker* msg_worker, group_mem_protocol querier_version_mode, int if_index, const std::shared_ptr<const sender>& sender, const std::shared_ptr<timing>& timing, const timers_values& tv, callback_querier_state_change cb_state_change)
     : m_msg_worker(msg_worker)
     , m_if_index(if_index)
@@ -163,7 +252,15 @@ void querier::receive_record(const std::shared_ptr<proxy_msg>& msg)
 
         break;
     case EXCLUDE_MODE:
-        receive_record_in_exclude_mode(gr->get_record_type(), gr->get_gaddr(), gr->get_slist(), db_info_it->second);
+        if (m_timers_values.get_fastleave() && gr->get_record_type() == CHANGE_TO_INCLUDE_MODE && gr->get_slist().empty()) {
+            //Only leave the group on upstream, when no any LAN port in the group
+            if (!group_mem_port_count(gr->get_gaddr().get_in_addr())) {
+                m_db.group_info.erase(db_info_it);
+                state_change_notification(gr->get_gaddr());
+            }
+        } else
+            receive_record_in_exclude_mode(gr->get_record_type(), gr->get_gaddr(), gr->get_slist(), db_info_it->second);
+
         break;
     default :
         HC_LOG_ERROR("wrong filter mode: " << db_info_it->second.filter_mode);
@@ -778,6 +875,7 @@ void querier::state_change_notification(const addr_storage& gaddr)
     m_cb_state_change(m_if_index, gaddr);
 }
 
+
 querier::~querier()
 {
     HC_LOG_TRACE("");
diff --git a/mcproxy/src/proxy/timers_values.cpp b/mcproxy/src/proxy/timers_values.cpp
index 164e47a..67959d1 100644
--- a/mcproxy/src/proxy/timers_values.cpp
+++ b/mcproxy/src/proxy/timers_values.cpp
@@ -176,6 +176,12 @@ uint16_t timers_values::maxrespi_to_maxrespc_mldv2(const std::chrono::millisecon
 }
 
 //--------------------------------------
+bool timers_values::get_fastleave() const
+{
+    HC_LOG_TRACE("");
+    return tank->fastleave;
+}
+
 unsigned int timers_values::get_robustness_variable() const
 {
     HC_LOG_TRACE("");
@@ -271,6 +277,13 @@ void timers_values::reset_to_default_tank()
 }
 
 //--------------------------------------
+void timers_values::set_fastleave(bool fastleave)
+{
+    HC_LOG_TRACE("");
+    set_new_tank();
+    tank->fastleave = fastleave;
+}
+
 void timers_values::set_robustness_variable(unsigned int robustness_variable)
 {
     HC_LOG_TRACE("");
-- 
GitLab