]> git.proxmox.com Git - mirror_frr.git/commitdiff
pimd: add support for IGMPv2
authorDaniel Walton <dwalton@cumulusnetworks.com>
Thu, 20 Oct 2016 13:34:29 +0000 (13:34 +0000)
committerDonald Sharp <sharpd@cumulusnetworks.com>
Thu, 22 Dec 2016 01:26:11 +0000 (20:26 -0500)
Signed-off-by: Daniel Walton <dwalton@cumulusnetworks.com>
Ticket: CM-7962

12 files changed:
pimd/Makefile.am
pimd/pim_cmd.c
pimd/pim_iface.c
pimd/pim_iface.h
pimd/pim_igmp.c
pimd/pim_igmp.h
pimd/pim_igmpv2.c [new file with mode: 0644]
pimd/pim_igmpv2.h [new file with mode: 0644]
pimd/pim_igmpv3.c
pimd/pim_igmpv3.h
pimd/pim_mroute.c
pimd/pim_vty.c

index 07679f27942d14a02d25523104750415ea75e95b..bb3d1844c5e8630df73e81a515bbd56caf158433 100644 (file)
@@ -48,7 +48,7 @@ noinst_PROGRAMS = test_igmpv3_join
 libpim_a_SOURCES = \
        pim_memory.c \
        pimd.c pim_version.c pim_cmd.c pim_signals.c pim_iface.c \
-       pim_vty.c pim_igmp.c pim_sock.c pim_zebra.c \
+       pim_vty.c pim_igmp.c pim_sock.c pim_zebra.c pim_igmpv2.c \
        pim_igmpv3.c pim_str.c pim_mroute.c pim_util.c pim_time.c \
        pim_oil.c pim_zlookup.c pim_pim.c pim_tlv.c pim_neighbor.c \
        pim_hello.c pim_ifchannel.c pim_join.c pim_assert.c \
@@ -60,7 +60,7 @@ libpim_a_SOURCES = \
 noinst_HEADERS = \
        pim_memory.h \
        pimd.h pim_version.h pim_cmd.h pim_signals.h pim_iface.h \
-       pim_vty.h pim_igmp.h pim_sock.h pim_zebra.h \
+       pim_vty.h pim_igmp.h pim_sock.h pim_zebra.h pim_igmpv2.h \
        pim_igmpv3.h pim_str.h pim_mroute.h pim_util.h pim_time.h \
        pim_oil.h pim_zlookup.h pim_pim.h pim_tlv.h pim_neighbor.h \
        pim_hello.h pim_ifchannel.h pim_join.h pim_assert.h \
index e9b3a4e131f71a9f9f8e461ba99e8deeb7344b93..c6390271f19fc1c95f88556dde89347a9563c920 100644 (file)
@@ -510,7 +510,7 @@ static void igmp_show_interfaces(struct vty *vty, u_char uj)
     json = json_object_new_object();
   else
     vty_out(vty,
-            "Interface  State          Address  Querier  Query Timer     Uptime%s",
+            "Interface  State          Address  V  Querier  Query Timer    Uptime%s",
             VTY_NEWLINE);
 
   for (ALL_LIST_ELEMENTS_RO (vrf_iflist (VRF_DEFAULT), node, ifp)) {
@@ -534,6 +534,7 @@ static void igmp_show_interfaces(struct vty *vty, u_char uj)
         json_row = json_object_new_object();
         json_object_pim_ifp_add(json_row, ifp);
         json_object_string_add(json_row, "upTime", uptime);
+        json_object_int_add(json_row, "version", pim_ifp->igmp_version);
 
         if (igmp->t_igmp_query_timer) {
           json_object_boolean_true_add(json_row, "querier");
@@ -543,10 +544,11 @@ static void igmp_show_interfaces(struct vty *vty, u_char uj)
         json_object_object_add(json, ifp->name, json_row);
 
       } else {
-        vty_out(vty, "%-9s  %5s  %15s  %7s  %11s  %8s%s",
+        vty_out(vty, "%-9s  %5s  %15s  %d  %7s  %11s  %8s%s",
                 ifp->name,
                 if_is_up(ifp) ? "up" : "down",
                 inet_ntoa(igmp->ifaddr),
+                pim_ifp->igmp_version,
                igmp->t_igmp_query_timer ? "local" : "other",
                query_hhmmss,
                 uptime,
@@ -627,12 +629,12 @@ static void igmp_show_interfaces_single(struct vty *vty, const char *ifname, u_c
       if (uj) {
         json_row = json_object_new_object();
         json_object_pim_ifp_add(json_row, ifp);
-
         json_object_string_add(json_row, "upTime", uptime);
         json_object_string_add(json_row, "querier", igmp->t_igmp_query_timer ? "local" : "other");
         json_object_int_add(json_row, "queryStartCount", igmp->startup_query_count);
         json_object_string_add(json_row, "queryQueryTimer", query_hhmmss);
         json_object_string_add(json_row, "queryOtherTimer", other_hhmmss);
+        json_object_int_add(json_row, "version", pim_ifp->igmp_version);
         json_object_int_add(json_row, "timerGroupMembershipIntervalMsec", gmi_msec);
         json_object_int_add(json_row, "timerLastMemberQueryMsec", lmqt_msec);
         json_object_int_add(json_row, "timerOlderHostPresentIntervalMsec", ohpi_msec);
@@ -649,6 +651,7 @@ static void igmp_show_interfaces_single(struct vty *vty, const char *ifname, u_c
         vty_out(vty, "State     : %s%s", if_is_up(ifp) ? "up" : "down", VTY_NEWLINE);
         vty_out(vty, "Address   : %s%s", inet_ntoa(pim_ifp->primary_address), VTY_NEWLINE);
         vty_out(vty, "Uptime    : %s%s", uptime, VTY_NEWLINE);
+        vty_out(vty, "Version   : %d%s", pim_ifp->igmp_version, VTY_NEWLINE);
         vty_out(vty, "%s", VTY_NEWLINE);
         vty_out(vty, "%s", VTY_NEWLINE);
 
@@ -1974,10 +1977,13 @@ static void igmp_show_groups(struct vty *vty, u_char uj)
             json_row = json_object_new_object();
             json_object_string_add(json_row, "source", ifaddr_str);
             json_object_string_add(json_row, "group", group_str);
-            json_object_string_add(json_row, "mode", grp->group_filtermode_isexcl ? "EXCLUDE" : "INCLUDE");
+
+            if (grp->igmp_version == 3)
+              json_object_string_add(json_row, "mode", grp->group_filtermode_isexcl ? "EXCLUDE" : "INCLUDE");
+
             json_object_string_add(json_row, "timer", hhmmss);
             json_object_int_add(json_row, "sourcesCount", grp->group_source_list ? listcount(grp->group_source_list) : 0);
-            json_object_int_add(json_row, "version", igmp_group_compat_mode(igmp, grp));
+            json_object_int_add(json_row, "version", grp->igmp_version);
             json_object_string_add(json_row, "uptime", uptime);
             json_object_object_add(json_iface, group_str, json_row);
 
@@ -1986,10 +1992,10 @@ static void igmp_show_groups(struct vty *vty, u_char uj)
                   ifp->name,
                   ifaddr_str,
                   group_str,
-                  grp->group_filtermode_isexcl ? "EXCL" : "INCL",
+                  grp->igmp_version == 3 ? (grp->group_filtermode_isexcl ? "EXCL" : "INCL") : "----",
                   hhmmss,
                   grp->group_source_list ? listcount(grp->group_source_list) : 0,
-                  igmp_group_compat_mode(igmp, grp),
+                  grp->igmp_version,
                   uptime,
                   VTY_NEWLINE);
         }
@@ -3914,6 +3920,56 @@ DEFUN (interface_no_ip_igmp_query_interval,
   return CMD_SUCCESS;
 }
 
+DEFUN (interface_ip_igmp_version,
+       interface_ip_igmp_version_cmd,
+       "ip igmp version <2-3>",
+       IP_STR
+       IFACE_IGMP_STR
+       "IGMP version\n"
+       "IGMP version number\n")
+{
+  VTY_DECLVAR_CONTEXT(interface,ifp);
+  struct pim_interface *pim_ifp;
+  int igmp_version;
+
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    vty_out(vty,
+           "IGMP not enabled on interface %s. Please enable IGMP first.%s",
+           ifp->name,
+           VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  igmp_version = atoi(argv[3]->arg);
+  pim_ifp->igmp_version = igmp_version;
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp_version,
+       interface_no_ip_igmp_version_cmd,
+       "no ip igmp version <2-3>",
+       NO_STR
+       IP_STR
+       IFACE_IGMP_STR
+       "IGMP version\n"
+       "IGMP version number\n")
+{
+  VTY_DECLVAR_CONTEXT(interface, ifp);
+  struct pim_interface *pim_ifp;
+
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp)
+    return CMD_SUCCESS;
+
+  pim_ifp->igmp_version = IGMP_DEFAULT_VERSION;
+
+  return CMD_SUCCESS;
+}
+
 #define IGMP_QUERY_MAX_RESPONSE_TIME_MIN (1)
 #define IGMP_QUERY_MAX_RESPONSE_TIME_MAX (25)
 
@@ -5073,7 +5129,7 @@ DEFUN (test_igmp_receive_report,
   igmp_msg = buf + ip_hlen;
   group_record = igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET;
   *igmp_msg = PIM_IGMP_V3_MEMBERSHIP_REPORT; /* type */
-  *(uint16_t *)      (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0; /* for computing checksum */
+  *(uint16_t *)      (igmp_msg + IGMP_CHECKSUM_OFFSET) = 0; /* for computing checksum */
   *(uint16_t *)      (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET) = htons(1); /* one group record */
   *(uint8_t  *)      (group_record + IGMP_V3_GROUP_RECORD_TYPE_OFFSET) = record_type;
   memcpy(group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, &grp_addr, sizeof(struct in_addr));
@@ -5097,7 +5153,7 @@ DEFUN (test_igmp_receive_report,
   igmp_msg_len = IGMP_V3_MSG_MIN_SIZE + (num_sources << 4);   /* v3 report for one single group record */
 
   /* compute checksum */
-  *(uint16_t *)(igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = in_cksum(igmp_msg, igmp_msg_len);
+  *(uint16_t *)(igmp_msg + IGMP_CHECKSUM_OFFSET) = in_cksum(igmp_msg, igmp_msg_len);
 
   /* "receive" message */
 
@@ -5731,6 +5787,8 @@ void pim_cmd_init()
   install_element (INTERFACE_NODE, &interface_no_ip_igmp_cmd); 
   install_element (INTERFACE_NODE, &interface_ip_igmp_join_cmd);
   install_element (INTERFACE_NODE, &interface_no_ip_igmp_join_cmd); 
+  install_element (INTERFACE_NODE, &interface_ip_igmp_version_cmd);
+  install_element (INTERFACE_NODE, &interface_no_ip_igmp_version_cmd);
   install_element (INTERFACE_NODE, &interface_ip_igmp_query_interval_cmd);
   install_element (INTERFACE_NODE, &interface_no_ip_igmp_query_interval_cmd); 
   install_element (INTERFACE_NODE, &interface_ip_igmp_query_max_response_time_cmd);
index b6012d1ed189c7b8565ec0fbac91badd73fd0bd5..ceab93edff500e2ad68f41b11ace677bfdfdad93 100644 (file)
@@ -106,6 +106,7 @@ struct pim_interface *pim_if_new(struct interface *ifp, int igmp, int pim)
   pim_ifp->options                           = 0;
   pim_ifp->mroute_vif_index                  = -1;
 
+  pim_ifp->igmp_version                               = IGMP_DEFAULT_VERSION;
   pim_ifp->igmp_default_robustness_variable           = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
   pim_ifp->igmp_default_query_interval                = IGMP_GENERAL_QUERY_INTERVAL;
   pim_ifp->igmp_query_max_response_time_dsec          = IGMP_QUERY_MAX_RESPONSE_TIME_DSEC;
index 0be4918d726324c9335734683633425aea1d5f2c..87530c60267c460e7e9b7c0cde444f4fc7a6706c 100644 (file)
@@ -65,6 +65,7 @@ struct pim_interface {
   ifindex_t      mroute_vif_index;
   struct in_addr primary_address; /* remember addr to detect change */
 
+  int          igmp_version;                                /* IGMP version */
   int          igmp_default_robustness_variable;            /* IGMPv3 QRV */
   int          igmp_default_query_interval;                 /* IGMPv3 secs between general queries */
   int          igmp_query_max_response_time_dsec;           /* IGMPv3 Max Response Time in dsecs for general queries */
index e14ff292255b3cd38f7f0e9cf77a349e89946abe..56f2457f97b1e68bd1a7e6ef57124fdd83abb642 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "pimd.h"
 #include "pim_igmp.h"
+#include "pim_igmpv2.h"
 #include "pim_igmpv3.h"
 #include "pim_iface.h"
 #include "pim_sock.h"
 #include "pim_time.h"
 #include "pim_zebra.h"
 
-#define IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE        (1)
-#define IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE        (2)
-#define IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE (3)
-#define IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE (4)
-#define IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES      (5)
-#define IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES      (6)
-
 static void group_timer_off(struct igmp_group *group);
 
+/* This socket is used for TXing IGMP packets only, IGMP RX happens
+ * in pim_mroute_msg()
+ */
 static int igmp_sock_open(struct in_addr ifaddr, ifindex_t ifindex, uint32_t pim_options)
 {
   int fd;
   int join = 0;
   struct in_addr group;
 
-  fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, ifindex, 1 /* loop=true */);
+  fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, ifindex, 0 /* loop=false */);
+
   if (fd < 0)
     return -1;
 
   if (PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(pim_options)) {
     if (inet_aton(PIM_ALL_ROUTERS, &group)) {
       if (!pim_socket_join(fd, group, ifaddr, ifindex))
-       ++join;
+       ++join;
     }
     else {
       zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
-               __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
-               PIM_ALL_ROUTERS, errno, safe_strerror(errno));
+               __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
+               PIM_ALL_ROUTERS, errno, safe_strerror(errno));
     }
   }
 
@@ -77,8 +75,8 @@ static int igmp_sock_open(struct in_addr ifaddr, ifindex_t ifindex, uint32_t pim
   }
   else {
     zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
-             __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
-             PIM_ALL_SYSTEMS, errno, safe_strerror(errno));
+             __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
+             PIM_ALL_SYSTEMS, errno, safe_strerror(errno));
   }
 
   if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) {
@@ -88,13 +86,13 @@ static int igmp_sock_open(struct in_addr ifaddr, ifindex_t ifindex, uint32_t pim
   }
   else {
       zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
-               __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
-               PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno));
+               __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
+               PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno));
   }    
 
   if (!join) {
     zlog_err("IGMP socket fd=%d could not join any group on interface address %s",
-            fd, inet_ntoa(ifaddr));
+            fd, inet_ntoa(ifaddr));
     close(fd);
     fd = -1;
   }
@@ -271,31 +269,27 @@ void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp)
   zassert(!igmp->t_other_querier_timer);
 }
 
-static int recv_igmp_query(struct igmp_sock *igmp, int query_version,
-                          int max_resp_code,
-                          struct in_addr from, const char *from_str,
-                          char *igmp_msg, int igmp_msg_len)
+static int
+igmp_recv_query(struct igmp_sock *igmp, int query_version,
+                int max_resp_code,
+                struct in_addr from, const char *from_str,
+                char *igmp_msg, int igmp_msg_len)
 {
   struct interface     *ifp;
   struct pim_interface *pim_ifp;
-  uint8_t               resv_s_qrv = 0;
-  uint8_t               s_flag = 0;
-  uint8_t               qrv = 0;
   struct in_addr        group_addr;
   uint16_t              recv_checksum;
   uint16_t              checksum;
-  int                   i;
 
-  //group_addr = *(struct in_addr *)(igmp_msg + 4);
   memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
 
   ifp = igmp->interface;
   pim_ifp = ifp->info;
 
-  recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET);
+  recv_checksum = *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET);
 
   /* for computing checksum */
-  *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0;
+  *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET) = 0;
 
   checksum = in_cksum(igmp_msg, igmp_msg_len);
   if (checksum != recv_checksum) {
@@ -304,12 +298,33 @@ static int recv_igmp_query(struct igmp_sock *igmp, int query_version,
     return -1;
   }
 
+  /* RFC 3376 defines some guidelines on operating in backwards compatibility
+   * with older versions of IGMP but there are some gaps in the logic:
+   *
+   * - once we drop from say version 3 to version 2 we will never go back to
+   *   version 3 even if the node that TXed an IGMP v2 query upgrades to v3
+   *
+   * - The node with the lowest IP is the querier so we will only know to drop
+   *   from v3 to v2 if the node that is the querier is also the one that is
+   *   running igmp v2.  If a non-querier only supports igmp v2 we will have
+   *   no way of knowing.
+   *
+   * For now we will simplify things and inform the user that they need to
+   * configure all PIM routers to use the same version of IGMP.
+   */
+  if (query_version != pim_ifp->igmp_version) {
+    zlog_warn("Recv IGMP query v%d from %s on %s but we are using v%d, please "
+              "configure all PIM routers on this subnet to use the same "
+              "IGMP version",
+              query_version, from_str, ifp->name, pim_ifp->igmp_version);
+    return 0;
+  }
+
   if (PIM_DEBUG_IGMP_PACKETS) {
     char group_str[100];
     pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
-    zlog_debug("Recv IGMP query v%d from %s on %s: size=%d checksum=%x group=%s",
-              query_version, from_str, ifp->name,
-              igmp_msg_len, checksum, group_str);
+    zlog_debug("Recv IGMP query v%d from %s on %s for group %s",
+              query_version, from_str, ifp->name, group_str);
   }
 
   /*
@@ -321,7 +336,7 @@ static int recv_igmp_query(struct igmp_sock *igmp, int query_version,
     elected querier.
    */
   if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) {
-    
+
     if (PIM_DEBUG_IGMP_TRACE) {
       char ifaddr_str[100];
       pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
@@ -334,272 +349,11 @@ static int recv_igmp_query(struct igmp_sock *igmp, int query_version,
     pim_igmp_other_querier_timer_on(igmp);
   }
 
+  /* IGMP version 3 is the only one where we process the RXed query */
   if (query_version == 3) {
-    /*
-      RFC 3376: 4.1.6. QRV (Querier's Robustness Variable)
-
-      Routers adopt the QRV value from the most recently received Query
-      as their own [Robustness Variable] value, unless that most
-      recently received QRV was zero, in which case the receivers use
-      the default [Robustness Variable] value specified in section 8.1
-      or a statically configured value.
-    */
-    resv_s_qrv = igmp_msg[8];
-    qrv = 7 & resv_s_qrv;
-    igmp->querier_robustness_variable = qrv ? qrv : pim_ifp->igmp_default_robustness_variable;
-  }
-
-  /*
-    RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
-
-    Multicast routers that are not the current querier adopt the QQI
-    value from the most recently received Query as their own [Query
-    Interval] value, unless that most recently received QQI was zero,
-    in which case the receiving routers use the default.
-  */
-  if (igmp->t_other_querier_timer && query_version == 3) {
-    /* other querier present */
-    uint8_t  qqic;
-    uint16_t qqi;
-    qqic = igmp_msg[9];
-    qqi = igmp_msg_decode8to16(qqic);
-    igmp->querier_query_interval = qqi ? qqi : pim_ifp->igmp_default_query_interval;
-
-    if (PIM_DEBUG_IGMP_TRACE) {
-      char ifaddr_str[100];
-      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
-      zlog_debug("Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)",
-                ifaddr_str,
-                qqi ? "recv-non-default" : "default",
-                igmp->querier_query_interval,
-                qqic,
-                from_str);
-    }
-  }
-
-  /*
-    RFC 3376: 6.6.1. Timer Updates
-
-    When a router sends or receives a query with a clear Suppress
-    Router-Side Processing flag, it must update its timers to reflect
-    the correct timeout values for the group or sources being queried.
-
-    General queries don't trigger timer update.
-  */
-  if (query_version == 3) {
-    s_flag = (1 << 3) & resv_s_qrv;
-  }
-  else {
-    /* Neither V1 nor V2 have this field. Pimd should really go into
-     * a compatibility mode here and run as V2 (or V1) but it doesn't
-     * so for now, lets just set the flag to suppress these timer updates.
-     */
-    s_flag = 1;
-  }
-  
-  if (!s_flag) {
-    /* s_flag is clear */
-
-    if (PIM_INADDR_IS_ANY(group_addr)) {
-      /* this is a general query */
-
-      /* log that general query should have the s_flag set */
-      zlog_warn("General IGMP query v%d from %s on %s: Suppress Router-Side Processing flag is clear",
-               query_version, from_str, ifp->name);
-    }
-    else {
-      struct igmp_group *group;
-
-      /* this is a non-general query: perform timer updates */
-
-      group = find_group_by_addr(igmp, group_addr);
-      if (group) {
-       int recv_num_sources = ntohs(*(uint16_t *)(igmp_msg + IGMP_V3_NUMSOURCES_OFFSET));
-
-       /*
-         RFC 3376: 6.6.1. Timer Updates
-         Query Q(G,A): Source Timer for sources in A are lowered to LMQT
-         Query Q(G): Group Timer is lowered to LMQT
-       */
-       if (recv_num_sources < 1) {
-         /* Query Q(G): Group Timer is lowered to LMQT */
-
-         igmp_group_timer_lower_to_lmqt(group);
-       }
-       else {
-         /* Query Q(G,A): Source Timer for sources in A are lowered to LMQT */
-
-         /* Scan sources in query and lower their timers to LMQT */
-         struct in_addr *sources = (struct in_addr *)(igmp_msg + IGMP_V3_SOURCES_OFFSET);
-         for (i = 0; i < recv_num_sources; ++i) {
-           //struct in_addr src_addr = sources[i];
-           //struct igmp_source *src = igmp_find_source_by_addr(group, src_addr);
-           struct in_addr src_addr;
-           struct igmp_source *src;
-            memcpy(&src_addr, sources + i, sizeof(struct in_addr));
-           src = igmp_find_source_by_addr(group, src_addr);
-           if (src) {
-             igmp_source_timer_lower_to_lmqt(src);
-           }
-         }
-       }
-
-      }
-      else {
-       char group_str[100];
-       pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
-       zlog_warn("IGMP query v%d from %s on %s: could not find group %s for timer update",
-                 query_version, from_str, ifp->name, group_str);
-      }
-    }
-  } /* s_flag is clear: timer updates */
-  
-  return 0;
-}
-
-static int igmp_v3_report(struct igmp_sock *igmp,
-                         struct in_addr from, const char *from_str,
-                         char *igmp_msg, int igmp_msg_len)
-{
-  uint16_t          recv_checksum;
-  uint16_t          checksum;
-  int               num_groups;
-  uint8_t          *group_record;
-  uint8_t          *report_pastend = (uint8_t *) igmp_msg + igmp_msg_len;
-  struct interface *ifp = igmp->interface;
-  int               i;
-  int               local_ncb = 0;
-
-  if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) {
-    zlog_warn("Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d",
-             from_str, ifp->name, igmp_msg_len, IGMP_V3_MSG_MIN_SIZE);
-    return -1;
-  }
-
-  recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET);
-
-  /* for computing checksum */
-  *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0;
-
-  checksum = in_cksum(igmp_msg, igmp_msg_len);
-  if (checksum != recv_checksum) {
-    zlog_warn("Recv IGMP report v3 from %s on %s: checksum mismatch: received=%x computed=%x",
-             from_str, ifp->name, recv_checksum, checksum);
-    return -1;
+    igmp_v3_recv_query(igmp, from_str, igmp_msg);
   }
 
-  num_groups = ntohs(*(uint16_t *) (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET));
-  if (num_groups < 1) {
-    zlog_warn("Recv IGMP report v3 from %s on %s: missing group records",
-             from_str, ifp->name);
-    return -1;
-  }
-
-  if (PIM_DEBUG_IGMP_PACKETS) {
-    zlog_debug("Recv IGMP report v3 from %s on %s: size=%d checksum=%x groups=%d",
-              from_str, ifp->name, igmp_msg_len, checksum, num_groups);
-  }
-
-  group_record = (uint8_t *) igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET;
-
-  /* Scan groups */
-  for (i = 0; i < num_groups; ++i) {
-    struct in_addr  rec_group;
-    uint8_t        *sources;
-    uint8_t        *src;
-    int             rec_type;
-    int             rec_auxdatalen;
-    int             rec_num_sources;
-    int             j;
-    struct prefix   lncb;
-    struct prefix   g;
-
-    if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) > report_pastend) {
-      zlog_warn("Recv IGMP report v3 from %s on %s: group record beyond report end",
-               from_str, ifp->name);
-      return -1;
-    }
-
-    rec_type        = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET];
-    rec_auxdatalen  = group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET];
-    rec_num_sources = ntohs(* (uint16_t *) (group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET));
-
-    //rec_group = *(struct in_addr *)(group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET);
-    memcpy(&rec_group, group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, sizeof(struct in_addr));
-
-    if (PIM_DEBUG_IGMP_PACKETS) {
-      zlog_debug("Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%s",
-                from_str, ifp->name, i, rec_type, rec_auxdatalen, rec_num_sources, inet_ntoa(rec_group));
-    }
-
-    /* Scan sources */
-    
-    sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET;
-
-    for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) {
-
-      if ((src + 4) > report_pastend) {
-       zlog_warn("Recv IGMP report v3 from %s on %s: group source beyond report end",
-                 from_str, ifp->name);
-       return -1;
-      }
-
-      if (PIM_DEBUG_IGMP_PACKETS) {
-       char src_str[200];
-
-       if (!inet_ntop(AF_INET, src, src_str , sizeof(src_str)))
-         sprintf(src_str, "<source?>");
-       
-       zlog_debug("Recv IGMP report v3 from %s on %s: record=%d group=%s source=%s",
-                  from_str, ifp->name, i, inet_ntoa(rec_group), src_str);
-      }
-    } /* for (sources) */
-
-
-    lncb.family = AF_INET;
-    lncb.u.prefix4.s_addr = 0x000000E0;
-    lncb.prefixlen = 24;
-
-    g.family = AF_INET;
-    g.u.prefix4 = rec_group;
-    g.prefixlen = 32;
-    /*
-     * If we receive a igmp report with the group in 224.0.0.0/24
-     * then we should ignore it
-     */
-    if (prefix_match(&lncb, &g))
-      local_ncb = 1;
-
-    if (!local_ncb)
-      switch (rec_type) {
-      case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE:
-       igmpv3_report_isin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
-       break;
-      case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE:
-       igmpv3_report_isex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
-       break;
-      case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE:
-       igmpv3_report_toin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
-       break;
-      case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE:
-       igmpv3_report_toex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
-       break;
-      case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES:
-       igmpv3_report_allow(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
-       break;
-      case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES:
-       igmpv3_report_block(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
-       break;
-      default:
-       zlog_warn("Recv IGMP report v3 from %s on %s: unknown record type: type=%d",
-                 from_str, ifp->name, rec_type);
-      }
-
-    group_record += 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2);
-    local_ncb = 0;
-
-  } /* for (group records) */
-
   return 0;
 }
 
@@ -614,66 +368,10 @@ static void on_trace(const char *label,
   }
 }
 
-static int igmp_v2_report(struct igmp_sock *igmp,
-                         struct in_addr from, const char *from_str,
-                         char *igmp_msg, int igmp_msg_len)
-{
-  struct interface *ifp = igmp->interface;
-  struct igmp_group *group;
-  struct in_addr group_addr;
-
-  on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
-
-  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
-    zlog_warn("Recv IGMP report v2 from %s on %s: size=%d other than correct=%d",
-             from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
-    return -1;
-  }
-
-  if (PIM_DEBUG_IGMP_TRACE) {
-    zlog_warn("%s %s: FIXME WRITEME",
-             __FILE__, __PRETTY_FUNCTION__);
-  }
-
-  //group_addr = *(struct in_addr *)(igmp_msg + 4);
-  memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
-
-  /* non-existant group is created as INCLUDE {empty} */
-  group = igmp_add_group_by_addr(igmp, group_addr);
-  if (!group) {
-    return -1;
-  }
-
-  group->last_igmp_v2_report_dsec = pim_time_monotonic_dsec();
-
-  return 0;
-}
-
-static int igmp_v2_leave(struct igmp_sock *igmp,
-                        struct in_addr from, const char *from_str,
-                        char *igmp_msg, int igmp_msg_len)
-{
-  struct interface *ifp = igmp->interface;
-
-  on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
-
-  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
-    zlog_warn("Recv IGMP leave v2 from %s on %s: size=%d other than correct=%d",
-             from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
-    return -1;
-  }
-
-  if (PIM_DEBUG_IGMP_TRACE) {
-    zlog_warn("%s %s: FIXME WRITEME",
-             __FILE__, __PRETTY_FUNCTION__);
-  }
-
-  return 0;
-}
-
-static int igmp_v1_report(struct igmp_sock *igmp,
-                         struct in_addr from, const char *from_str,
-                         char *igmp_msg, int igmp_msg_len)
+static int
+igmp_v1_recv_report (struct igmp_sock *igmp,
+                     struct in_addr from, const char *from_str,
+                     char *igmp_msg, int igmp_msg_len)
 {
   struct interface *ifp = igmp->interface;
   struct igmp_group *group;
@@ -692,7 +390,6 @@ static int igmp_v1_report(struct igmp_sock *igmp,
              __FILE__, __PRETTY_FUNCTION__);
   }
 
-  //group_addr = *(struct in_addr *)(igmp_msg + 4);
   memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
 
   /* non-existant group is created as INCLUDE {empty} */
@@ -791,26 +488,26 @@ int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len)
        return -1;
       }
 
-      return recv_igmp_query(igmp, query_version, max_resp_code,
+      return igmp_recv_query(igmp, query_version, max_resp_code,
                             ip_hdr->ip_src, from_str,
                             igmp_msg, igmp_msg_len);
     }
 
   case PIM_IGMP_V3_MEMBERSHIP_REPORT:
-    return igmp_v3_report(igmp, ip_hdr->ip_src, from_str,
-                         igmp_msg, igmp_msg_len);
+    return igmp_v3_recv_report(igmp, ip_hdr->ip_src, from_str,
+                               igmp_msg, igmp_msg_len);
 
   case PIM_IGMP_V2_MEMBERSHIP_REPORT:
-    return igmp_v2_report(igmp, ip_hdr->ip_src, from_str,
-                         igmp_msg, igmp_msg_len);
+    return igmp_v2_recv_report(igmp, ip_hdr->ip_src, from_str,
+                               igmp_msg, igmp_msg_len);
 
   case PIM_IGMP_V1_MEMBERSHIP_REPORT:
-    return igmp_v1_report(igmp, ip_hdr->ip_src, from_str,
-                         igmp_msg, igmp_msg_len);
+    return igmp_v1_recv_report(igmp, ip_hdr->ip_src, from_str,
+                               igmp_msg, igmp_msg_len);
 
   case PIM_IGMP_V2_LEAVE_GROUP:
-    return igmp_v2_leave(igmp, ip_hdr->ip_src, from_str,
-                        igmp_msg, igmp_msg_len);
+    return igmp_v2_recv_leave(igmp, ip_hdr->ip_src, from_str,
+                              igmp_msg, igmp_msg_len);
   }
 
   zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type);
@@ -895,11 +592,11 @@ void pim_igmp_general_query_off(struct igmp_sock *igmp)
 /* Issue IGMP general query */
 static int pim_igmp_general_query(struct thread *t)
 {
-  char   query_buf[PIM_IGMP_BUFSIZE_WRITE];
   struct igmp_sock *igmp;
   struct in_addr dst_addr;
   struct in_addr group_addr;
   struct pim_interface *pim_ifp;
+  int    query_buf_size;
 
   zassert(t);
 
@@ -911,6 +608,14 @@ static int pim_igmp_general_query(struct thread *t)
 
   pim_ifp = igmp->interface->info;
 
+  if (pim_ifp->igmp_version == 3) {
+    query_buf_size = PIM_IGMP_BUFSIZE_WRITE;
+  } else {
+    query_buf_size = IGMP_V12_MSG_SIZE;
+  }
+
+  char query_buf[query_buf_size];
+
   /*
     RFC3376: 4.1.12. IP Destination Addresses for Queries
 
@@ -933,124 +638,25 @@ static int pim_igmp_general_query(struct thread *t)
               querier_str, dst_str, igmp->interface->name);
   }
 
-  pim_igmp_send_membership_query(0 /* igmp_group */,
-                                igmp->fd,
-                                igmp->interface->name,
-                                query_buf,
-                                sizeof(query_buf),
-                                0 /* num_sources */,
-                                dst_addr,
-                                group_addr,
-                                pim_ifp->igmp_query_max_response_time_dsec,
-                                1 /* s_flag: always set for general queries */,
-                                igmp->querier_robustness_variable,
-                                igmp->querier_query_interval);
+  igmp_send_query (pim_ifp->igmp_version,
+                   0 /* igmp_group */,
+                   igmp->fd,
+                   igmp->interface->name,
+                   query_buf,
+                   sizeof(query_buf),
+                   0 /* num_sources */,
+                   dst_addr,
+                   group_addr,
+                   pim_ifp->igmp_query_max_response_time_dsec,
+                   1 /* s_flag: always set for general queries */,
+                   igmp->querier_robustness_variable,
+                   igmp->querier_query_interval);
 
   pim_igmp_general_query_on(igmp);
 
   return 0;
 }
 
-static int pim_igmp_read(struct thread *t);
-
-static void igmp_read_on(struct igmp_sock *igmp)
-{
-  zassert(igmp);
-
-  if (PIM_DEBUG_IGMP_TRACE_DETAIL) {
-    zlog_debug("Scheduling READ event on IGMP socket fd=%d",
-              igmp->fd);
-  }
-  igmp->t_igmp_read = NULL;
-  zassert(!igmp->t_igmp_read);
-  THREAD_READ_ON(master, igmp->t_igmp_read, pim_igmp_read, igmp, igmp->fd);
-}
-
-static int pim_igmp_read(struct thread *t)
-{
-  struct igmp_sock *igmp;
-  int fd;
-  struct sockaddr_in from;
-  struct sockaddr_in to;
-  socklen_t fromlen = sizeof(from);
-  socklen_t tolen = sizeof(to);
-  uint8_t buf[PIM_IGMP_BUFSIZE_READ];
-  int len;
-  ifindex_t ifindex = -1;
-  int result = -1; /* defaults to bad */
-
-  zassert(t);
-
-  igmp = THREAD_ARG(t);
-
-  zassert(igmp);
-
-  fd = THREAD_FD(t);
-
-  zassert(fd == igmp->fd);
-
-  len = pim_socket_recvfromto(fd, buf, sizeof(buf),
-                             &from, &fromlen,
-                             &to, &tolen,
-                             &ifindex);
-  if (len < 0) {
-    zlog_warn("Failure receiving IP IGMP packet on fd=%d: errno=%d: %s",
-             fd, errno, safe_strerror(errno));
-    goto done;
-  }
-
-  if (PIM_DEBUG_IGMP_PACKETS) {
-    char from_str[100];
-    char to_str[100];
-    
-    if (!inet_ntop(AF_INET, &from.sin_addr, from_str, sizeof(from_str)))
-      sprintf(from_str, "<from?>");
-    if (!inet_ntop(AF_INET, &to.sin_addr, to_str, sizeof(to_str)))
-      sprintf(to_str, "<to?>");
-    
-    zlog_debug("Recv IP IGMP pkt size=%d from %s to %s on fd=%d on ifindex=%d (sock_ifindex=%d)",
-              len, from_str, to_str, fd, ifindex, igmp->interface->ifindex);
-  }
-
-#ifdef PIM_CHECK_RECV_IFINDEX_SANITY
-  /* ifindex sanity check */
-  if (ifindex != igmp->interface->ifindex) {
-    char from_str[100];
-    char to_str[100];
-    struct interface *ifp;
-
-    if (!inet_ntop(AF_INET, &from.sin_addr, from_str , sizeof(from_str)))
-      sprintf(from_str, "<from?>");
-    if (!inet_ntop(AF_INET, &to.sin_addr, to_str , sizeof(to_str)))
-      sprintf(to_str, "<to?>");
-
-    ifp = if_lookup_by_index(ifindex);
-    if (ifp) {
-      zassert(ifindex == ifp->ifindex);
-    }
-
-#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH
-    zlog_warn("Interface mismatch: recv IGMP pkt from %s to %s on fd=%d: recv_ifindex=%d (%s) sock_ifindex=%d (%s)",
-             from_str, to_str, fd,
-             ifindex, ifp ? ifp->name : "<if-notfound>",
-             igmp->interface->ifindex, igmp->interface->name);
-#endif
-    goto done;
-  }
-#endif
-
-  if (pim_igmp_packet(igmp, (char *)buf, len)) {
-    goto done;
-  }
-
-  result = 0; /* good */
-
- done:
-  igmp_read_on(igmp);
-
-  return result;
-}
-
 static void sock_close(struct igmp_sock *igmp)
 {
   pim_igmp_other_querier_timer_off(igmp);
@@ -1219,8 +825,6 @@ static struct igmp_sock *igmp_sock_new(int fd,
     igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
   */
   igmp_startup_mode_on(igmp);
-
-  igmp_read_on(igmp);
   pim_igmp_general_query_on(igmp);
 
   return igmp;
@@ -1421,6 +1025,7 @@ struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
   group->last_igmp_v1_report_dsec              = -1;
   group->last_igmp_v2_report_dsec              = -1;
   group->group_creation                        = pim_time_monotonic_sec();
+  group->igmp_version                          = IGMP_DEFAULT_VERSION;
 
   /* initialize new group as INCLUDE {empty} */
   group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */
@@ -1449,3 +1054,32 @@ struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
 
   return group;
 }
+
+void
+igmp_send_query (int igmp_version,
+                 struct igmp_group *group,
+                 int fd,
+                 const char *ifname,
+                 char *query_buf,
+                 int query_buf_size,
+                 int num_sources,
+                 struct in_addr dst_addr,
+                 struct in_addr group_addr,
+                 int query_max_response_time_dsec,
+                 uint8_t s_flag,
+                 uint8_t querier_robustness_variable,
+                 uint16_t querier_query_interval)
+{
+  if (igmp_version == 3) {
+    igmp_v3_send_query (group, fd, ifname, query_buf,
+                        query_buf_size, num_sources,
+                        dst_addr, group_addr,
+                        query_max_response_time_dsec, s_flag,
+                        querier_robustness_variable,
+                        querier_query_interval);
+  } else if (igmp_version == 2) {
+    igmp_v2_send_query (group, fd, ifname, query_buf,
+                        dst_addr, group_addr,
+                        query_max_response_time_dsec);
+  }
+}
index 2067bb30aade9622e45bd7a1314499f5bdf82af6..fa2c255dd09dcbe60b7c4eee5ae263c471cfd26b 100644 (file)
@@ -52,6 +52,7 @@
 #define IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET (2)
 #define IGMP_V3_GROUP_RECORD_GROUP_OFFSET      (4)
 #define IGMP_V3_GROUP_RECORD_SOURCE_OFFSET     (8)
+#define IGMP_CHECKSUM_OFFSET                   (2)
 
 /* RFC 3376: 8.1. Robustness Variable - Default: 2 */
 #define IGMP_DEFAULT_ROBUSTNESS_VARIABLE           (2)
@@ -65,6 +66,8 @@
 /* RFC 3376: 8.8. Last Member Query Interval - Default: 10 deciseconds */
 #define IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC (10)
 
+#define IGMP_DEFAULT_VERSION (3)
+
 struct igmp_join {
   struct in_addr group_addr;
   struct in_addr source_addr;
@@ -152,6 +155,9 @@ struct igmp_group {
      since sources have their counters) */
   int               group_specific_query_retransmit_count;
 
+  /* compatibility mode - igmp v1, v2 or v3 */
+  int               igmp_version;
+
   struct in_addr    group_addr;
   int               group_filtermode_isexcl;  /* 0=INCLUDE, 1=EXCLUDE */
   struct list      *group_source_list;        /* list of struct igmp_source */
@@ -176,4 +182,18 @@ void igmp_group_timer_on(struct igmp_group *group,
 struct igmp_source *
 source_new (struct igmp_group *group,
            struct in_addr src_addr);
+
+void igmp_send_query(int igmp_version,
+                     struct igmp_group *group,
+                     int fd,
+                     const char *ifname,
+                     char *query_buf,
+                     int query_buf_size,
+                     int num_sources,
+                     struct in_addr dst_addr,
+                     struct in_addr group_addr,
+                     int query_max_response_time_dsec,
+                     uint8_t s_flag,
+                     uint8_t querier_robustness_variable,
+                     uint16_t querier_query_interval);
 #endif /* PIM_IGMP_H */
diff --git a/pimd/pim_igmpv2.c b/pimd/pim_igmpv2.c
new file mode 100644 (file)
index 0000000..cc78f18
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * PIM for Quagga
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Daniel Walton
+ *
+ * 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; see the file COPYING; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include "pimd.h"
+#include "pim_igmp.h"
+#include "pim_igmpv2.h"
+#include "pim_igmpv3.h"
+#include "pim_str.h"
+#include "pim_time.h"
+#include "pim_util.h"
+
+
+static void
+on_trace (const char *label,
+          struct interface *ifp, struct in_addr from)
+{
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char from_str[100];
+    pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
+    zlog_debug("%s: from %s on %s",
+               label, from_str, ifp->name);
+  }
+}
+
+void
+igmp_v2_send_query (struct igmp_group *group,
+                    int fd,
+                    const char *ifname,
+                    char *query_buf,
+                    struct in_addr dst_addr,
+                    struct in_addr group_addr,
+                    int query_max_response_time_dsec)
+{
+  ssize_t             msg_size = 8;
+  uint8_t             max_resp_code;
+  ssize_t             sent;
+  struct sockaddr_in  to;
+  socklen_t           tolen;
+  uint16_t            checksum;
+
+  /* max_resp_code must be non-zero else this will look like an IGMP v1 query */
+  max_resp_code = igmp_msg_encode16to8(query_max_response_time_dsec);
+  zassert(max_resp_code > 0);
+
+  query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY;
+  query_buf[1] = max_resp_code;
+  *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET)   = 0; /* for computing checksum */
+  memcpy(query_buf+4, &group_addr, sizeof(struct in_addr));
+
+  checksum = in_cksum(query_buf, msg_size);
+  *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum;
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    char dst_str[100];
+    char group_str[100];
+    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+    zlog_debug("Send IGMPv2 QUERY to %s on %s for group %s",
+              dst_str, ifname, group_str);
+  }
+
+  memset(&to, 0, sizeof(to));
+  to.sin_family = AF_INET;
+  to.sin_addr = dst_addr;
+  tolen = sizeof(to);
+
+  sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT,
+                (struct sockaddr *)&to, tolen);
+  if (sent != (ssize_t) msg_size) {
+    char dst_str[100];
+    char group_str[100];
+    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+    if (sent < 0) {
+      zlog_warn("Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s",
+               dst_str, ifname, group_str, msg_size, errno, safe_strerror(errno));
+    }
+    else {
+      zlog_warn("Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: sent=%zd",
+               dst_str, ifname, group_str, msg_size, sent);
+    }
+    return;
+  }
+}
+
+int
+igmp_v2_recv_report (struct igmp_sock *igmp,
+                     struct in_addr from, const char *from_str,
+                     char *igmp_msg, int igmp_msg_len)
+{
+  struct interface *ifp = igmp->interface;
+  struct in_addr group_addr;
+  char group_str[100];
+
+  on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
+
+  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
+    zlog_warn("Recv IGMPv2 REPORT from %s on %s: size=%d other than correct=%d",
+              from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
+    return -1;
+  }
+
+  memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    pim_inet4_dump("<dst?>", group_addr, group_str, sizeof(group_str));
+    zlog_debug("Recv IGMPv2 REPORT from %s on %s for %s",
+               from_str, ifp->name, group_str);
+  }
+
+  /*
+   * RFC 3376
+   * 7.3.2. In the Presence of Older Version Group Members
+   *
+   * When Group Compatibility Mode is IGMPv2, a router internally
+   * translates the following IGMPv2 messages for that group to their
+   * IGMPv3 equivalents:
+   *
+   * IGMPv2 Message                IGMPv3 Equivalent
+   * --------------                -----------------
+   * Report                        IS_EX( {} )
+   * Leave                         TO_IN( {} )
+   */
+  igmpv3_report_isex (igmp, from, group_addr, 0, NULL, 1);
+
+  return 0;
+}
+
+int
+igmp_v2_recv_leave (struct igmp_sock *igmp,
+                    struct in_addr from, const char *from_str,
+                    char *igmp_msg, int igmp_msg_len)
+{
+  struct interface *ifp = igmp->interface;
+  struct in_addr group_addr;
+  char group_str[100];
+
+  on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
+
+  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
+    zlog_warn("Recv IGMPv2 LEAVE from %s on %s: size=%d other than correct=%d",
+              from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
+    return -1;
+  }
+
+  memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    pim_inet4_dump("<dst?>", group_addr, group_str, sizeof(group_str));
+    zlog_debug("Recv IGMPv2 LEAVE from %s on %s for %s",
+               from_str, ifp->name, group_str);
+  }
+
+  memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
+
+  /*
+   * RFC 3376
+   * 7.3.2. In the Presence of Older Version Group Members
+   *
+   * When Group Compatibility Mode is IGMPv2, a router internally
+   * translates the following IGMPv2 messages for that group to their
+   * IGMPv3 equivalents:
+   *
+   * IGMPv2 Message                IGMPv3 Equivalent
+   * --------------                -----------------
+   * Report                        IS_EX( {} )
+   * Leave                         TO_IN( {} )
+   */
+  igmpv3_report_toin (igmp, from, group_addr, 0, NULL);
+
+  return 0;
+}
diff --git a/pimd/pim_igmpv2.h b/pimd/pim_igmpv2.h
new file mode 100644 (file)
index 0000000..10a2477
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * PIM for Quagga
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Daniel Walton
+ *
+ * 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; see the file COPYING; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#ifndef PIM_IGMPV2_H
+#define PIM_IGMPV2_H
+
+void igmp_v2_send_query (struct igmp_group *group,
+                         int fd,
+                         const char *ifname,
+                         char *query_buf,
+                         struct in_addr dst_addr,
+                         struct in_addr group_addr,
+                         int query_max_response_time_dsec);
+
+int igmp_v2_recv_report (struct igmp_sock *igmp,
+                         struct in_addr from, const char *from_str,
+                         char *igmp_msg, int igmp_msg_len);
+
+int igmp_v2_recv_leave (struct igmp_sock *igmp,
+                        struct in_addr from, const char *from_str,
+                        char *igmp_msg, int igmp_msg_len);
+
+#endif /* PIM_IGMPV2_H */
index ba75fc221f07aeaff6740926480cc257b02a87ac..21a5176a9d798b63bc3e80b404225148e8914c2f 100644 (file)
@@ -58,50 +58,6 @@ static void on_trace(const char *label,
   }
 }
 
-int igmp_group_compat_mode(const struct igmp_sock *igmp,
-                          const struct igmp_group *group)
-{
-  struct pim_interface *pim_ifp;
-  int64_t               now_dsec;
-  long                  older_host_present_interval_dsec;
-
-  zassert(igmp);
-  zassert(igmp->interface);
-  zassert(igmp->interface->info);
-
-  pim_ifp = igmp->interface->info;
-
-  /*
-    RFC 3376: 8.13. Older Host Present Interval
-
-    This value MUST be ((the Robustness Variable) times (the Query
-    Interval)) plus (one Query Response Interval).
-
-    older_host_present_interval_dsec = \
-      igmp->querier_robustness_variable * \
-      10 * igmp->querier_query_interval + \
-      pim_ifp->query_max_response_time_dsec;
-  */
-  older_host_present_interval_dsec =
-    PIM_IGMP_OHPI_DSEC(igmp->querier_robustness_variable,
-                      igmp->querier_query_interval,
-                      pim_ifp->igmp_query_max_response_time_dsec);
-
-  now_dsec = pim_time_monotonic_dsec();
-  if (now_dsec < 1) {
-    /* broken timer logged by pim_time_monotonic_dsec() */
-    return 3;
-  }
-
-  if ((now_dsec - group->last_igmp_v1_report_dsec) < older_host_present_interval_dsec)
-    return 1; /* IGMPv1 */
-
-  if ((now_dsec - group->last_igmp_v2_report_dsec) < older_host_present_interval_dsec)
-    return 2; /* IGMPv2 */
-
-  return 3; /* IGMPv3 */
-}
-
 void igmp_group_reset_gmi(struct igmp_group *group)
 {
   long group_membership_interval_msec;
@@ -705,7 +661,8 @@ static void isex_incl(struct igmp_group *group,
 
 void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from,
                        struct in_addr group_addr,
-                       int num_sources, struct in_addr *sources)
+                       int num_sources, struct in_addr *sources,
+                        int from_igmp_v2_report)
 {
   struct interface *ifp = igmp->interface;
   struct igmp_group *group;
@@ -719,6 +676,10 @@ void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from,
     return;
   }
 
+  /* So we can display how we learned the group in our show command output */
+  if (from_igmp_v2_report)
+    group->igmp_version = 2;
+
   if (group->group_filtermode_isexcl) {
     /* EXCLUDE mode */
     isex_excl(group, num_sources, sources);
@@ -1050,17 +1011,25 @@ void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from,
 */
 static void group_retransmit_group(struct igmp_group *group)
 {
-  char                  query_buf[PIM_IGMP_BUFSIZE_WRITE];
   struct igmp_sock     *igmp;
   struct pim_interface *pim_ifp;
   long                  lmqc;      /* Last Member Query Count */
   long                  lmqi_msec; /* Last Member Query Interval */
   long                  lmqt_msec; /* Last Member Query Time */
   int                   s_flag;
+  int                   query_buf_size;
 
   igmp = group->group_igmp_sock;
   pim_ifp = igmp->interface->info;
 
+  if (pim_ifp->igmp_version == 3) {
+    query_buf_size = PIM_IGMP_BUFSIZE_WRITE;
+  } else {
+    query_buf_size = IGMP_V12_MSG_SIZE;
+  }
+
+  char query_buf[query_buf_size];
+
   lmqc      = igmp->querier_robustness_variable;
   lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec;
   lmqt_msec = lmqc * lmqi_msec;
@@ -1090,18 +1059,19 @@ static void group_retransmit_group(struct igmp_group *group)
     interest.
   */
 
-  pim_igmp_send_membership_query(group,
-                                igmp->fd,
-                                igmp->interface->name,
-                                query_buf,
-                                sizeof(query_buf),
-                                0 /* num_sources_tosend */,
-                                group->group_addr /* dst_addr */,
-                                group->group_addr /* group_addr */,
-                                pim_ifp->igmp_specific_query_max_response_time_dsec,
-                                s_flag,
-                                igmp->querier_robustness_variable,
-                                igmp->querier_query_interval);
+  igmp_send_query(pim_ifp->igmp_version,
+                  group,
+                  igmp->fd,
+                  igmp->interface->name,
+                  query_buf,
+                  sizeof(query_buf),
+                  0 /* num_sources_tosend */,
+                  group->group_addr /* dst_addr */,
+                  group->group_addr /* group_addr */,
+                  pim_ifp->igmp_specific_query_max_response_time_dsec,
+                  s_flag,
+                  igmp->querier_robustness_variable,
+                  igmp->querier_query_interval);
 }
 
 /*
@@ -1208,19 +1178,19 @@ static int group_retransmit_sources(struct igmp_group *group,
          interest.
        */
     
-       pim_igmp_send_membership_query(group,
-                                      igmp->fd,
-                                      igmp->interface->name,
-                                      query_buf1,
-                                      sizeof(query_buf1),
-                                      num_sources_tosend1,
-                                      group->group_addr,
-                                      group->group_addr,
-                                      pim_ifp->igmp_specific_query_max_response_time_dsec,
-                                      1 /* s_flag */,
-                                      igmp->querier_robustness_variable,
-                                      igmp->querier_query_interval);
-    
+        igmp_send_query(pim_ifp->igmp_version,
+                        group,
+                        igmp->fd,
+                        igmp->interface->name,
+                        query_buf1,
+                        sizeof(query_buf1),
+                        num_sources_tosend1,
+                        group->group_addr,
+                        group->group_addr,
+                        pim_ifp->igmp_specific_query_max_response_time_dsec,
+                        1 /* s_flag */,
+                        igmp->querier_robustness_variable,
+                        igmp->querier_query_interval);
       }
 
     } /* send_with_sflag_set */
@@ -1250,19 +1220,19 @@ static int group_retransmit_sources(struct igmp_group *group,
        interest.
       */
 
-      pim_igmp_send_membership_query(group,
-                                    igmp->fd,
-                                    igmp->interface->name,
-                                    query_buf2,
-                                    sizeof(query_buf2),
-                                    num_sources_tosend2,
-                                    group->group_addr,
-                                    group->group_addr,
-                                    pim_ifp->igmp_specific_query_max_response_time_dsec,
-                                    0 /* s_flag */,
-                                    igmp->querier_robustness_variable,
-                                    igmp->querier_query_interval);
-
+      igmp_send_query(pim_ifp->igmp_version,
+                      group,
+                      igmp->fd,
+                      igmp->interface->name,
+                      query_buf2,
+                      sizeof(query_buf2),
+                      num_sources_tosend2,
+                      group->group_addr,
+                      group->group_addr,
+                      pim_ifp->igmp_specific_query_max_response_time_dsec,
+                      0 /* s_flag */,
+                      igmp->querier_robustness_variable,
+                      igmp->querier_query_interval);
     }
   }
 
@@ -1623,32 +1593,19 @@ void igmp_source_timer_lower_to_lmqt(struct igmp_source *source)
   igmp_source_timer_on(group, source, lmqt_msec);
 }
 
-/*
-  Copy sources to message:
-    
-  struct in_addr *sources = (struct in_addr *)(query_buf + IGMP_V3_SOURCES_OFFSET);
-  if (num_sources > 0) {
-  struct listnode    *node;
-  struct igmp_source *src;
-  int                 i = 0;
-
-  for (ALL_LIST_ELEMENTS_RO(source_list, node, src)) {
-  sources[i++] = src->source_addr;
-  }
-  }
-*/
-void pim_igmp_send_membership_query(struct igmp_group *group,
-                                   int fd,
-                                   const char *ifname,
-                                   char *query_buf,
-                                   int query_buf_size,
-                                   int num_sources,
-                                   struct in_addr dst_addr,
-                                   struct in_addr group_addr,
-                                   int query_max_response_time_dsec,
-                                   uint8_t s_flag,
-                                   uint8_t querier_robustness_variable,
-                                   uint16_t querier_query_interval)
+void
+igmp_v3_send_query (struct igmp_group *group,
+                    int fd,
+                    const char *ifname,
+                    char *query_buf,
+                    int query_buf_size,
+                    int num_sources,
+                    struct in_addr dst_addr,
+                    struct in_addr group_addr,
+                    int query_max_response_time_dsec,
+                    uint8_t s_flag,
+                    uint8_t querier_robustness_variable,
+                    uint16_t querier_query_interval)
 {
   ssize_t             msg_size;
   uint8_t             max_resp_code;
@@ -1688,7 +1645,7 @@ void pim_igmp_send_membership_query(struct igmp_group *group,
 
   query_buf[0]                                         = PIM_IGMP_MEMBERSHIP_QUERY;
   query_buf[1]                                         = max_resp_code;
-  *(uint16_t *)(query_buf + IGMP_V3_CHECKSUM_OFFSET)   = 0; /* for computing checksum */
+  *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET)   = 0; /* for computing checksum */
   memcpy(query_buf+4, &group_addr, sizeof(struct in_addr));
 
   query_buf[8]                                         = (s_flag << 3) | querier_robustness_variable;
@@ -1696,18 +1653,17 @@ void pim_igmp_send_membership_query(struct igmp_group *group,
   *(uint16_t *)(query_buf + IGMP_V3_NUMSOURCES_OFFSET) = htons(num_sources);
 
   checksum = in_cksum(query_buf, msg_size);
-  *(uint16_t *)(query_buf + IGMP_V3_CHECKSUM_OFFSET) = checksum;
+  *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum;
 
   if (PIM_DEBUG_IGMP_PACKETS) {
     char dst_str[100];
     char group_str[100];
     pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
     pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
-    zlog_debug("%s: to %s on %s: group=%s sources=%d msg_size=%zd s_flag=%x QRV=%u QQI=%u QQIC=%02x checksum=%x",
-              __PRETTY_FUNCTION__,
-              dst_str, ifname, group_str, num_sources,
-              msg_size, s_flag, querier_robustness_variable,
-              querier_query_interval, qqic, checksum);
+    zlog_debug("Send IGMPv3 query to %s on %s for group %s, sources=%d msg_size=%zd s_flag=%x QRV=%u QQI=%u QQIC=%02x",
+              dst_str, ifname, group_str,
+               num_sources, msg_size, s_flag, querier_robustness_variable,
+               querier_query_interval, qqic);
   }
 
   memset(&to, 0, sizeof(to));
@@ -1723,16 +1679,12 @@ void pim_igmp_send_membership_query(struct igmp_group *group,
     pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
     pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
     if (sent < 0) {
-      zlog_warn("%s: sendto() failure to %s on %s: group=%s msg_size=%zd: errno=%d: %s",
-               __PRETTY_FUNCTION__,
-               dst_str, ifname, group_str, msg_size,
-               errno, safe_strerror(errno));
+      zlog_warn("Send IGMPv3 query failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s",
+               dst_str, ifname, group_str, msg_size, errno, safe_strerror(errno));
     }
     else {
-      zlog_warn("%s: sendto() partial to %s on %s: group=%s msg_size=%zd: sent=%zd",
-               __PRETTY_FUNCTION__,
-               dst_str, ifname, group_str,
-               msg_size, sent);
+      zlog_warn("Send IGMPv3 query failed due to %s on %s: group=%s msg_size=%zd: sent=%zd",
+               dst_str, ifname, group_str, msg_size, sent);
     }
     return;
   }
@@ -1760,5 +1712,268 @@ void pim_igmp_send_membership_query(struct igmp_group *group,
                dst_str, ifname, group_str, num_sources);
     }
   }
+}
+
+void
+igmp_v3_recv_query (struct igmp_sock *igmp, const char *from_str, char *igmp_msg)
+{
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct in_addr        group_addr;
+  uint8_t resv_s_qrv = 0;
+  uint8_t s_flag = 0;
+  uint8_t qrv = 0;
+  int     i;
+
+  memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
+  ifp = igmp->interface;
+  pim_ifp = ifp->info;
+
+  /*
+   * RFC 3376: 4.1.6. QRV (Querier's Robustness Variable)
+   *
+   * Routers adopt the QRV value from the most recently received Query
+   * as their own [Robustness Variable] value, unless that most
+   * recently received QRV was zero, in which case the receivers use
+   * the default [Robustness Variable] value specified in section 8.1
+   * or a statically configured value.
+   */
+  resv_s_qrv = igmp_msg[8];
+  qrv = 7 & resv_s_qrv;
+  igmp->querier_robustness_variable = qrv ? qrv : pim_ifp->igmp_default_robustness_variable;
+
+  /*
+   * RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
+   *
+   * Multicast routers that are not the current querier adopt the QQI
+   * value from the most recently received Query as their own [Query
+   * Interval] value, unless that most recently received QQI was zero,
+   * in which case the receiving routers use the default.
+   */
+  if (igmp->t_other_querier_timer) {
+    /* other querier present */
+    uint8_t  qqic;
+    uint16_t qqi;
+    qqic = igmp_msg[9];
+    qqi = igmp_msg_decode8to16(qqic);
+    igmp->querier_query_interval = qqi ? qqi : pim_ifp->igmp_default_query_interval;
+
+    if (PIM_DEBUG_IGMP_TRACE) {
+      char ifaddr_str[100];
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+      zlog_debug("Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)",
+                 ifaddr_str,
+                 qqi ? "recv-non-default" : "default",
+                 igmp->querier_query_interval,
+                 qqic,
+                 from_str);
+    }
+  }
+
+  /*
+   * RFC 3376: 6.6.1. Timer Updates
+   *
+   * When a router sends or receives a query with a clear Suppress
+   * Router-Side Processing flag, it must update its timers to reflect
+   * the correct timeout values for the group or sources being queried.
+   *
+   * General queries don't trigger timer update.
+   */
+  s_flag = (1 << 3) & resv_s_qrv;
+
+  if (!s_flag) {
+    /* s_flag is clear */
+
+    if (PIM_INADDR_IS_ANY(group_addr)) {
+      /* this is a general query */
+      /* log that general query should have the s_flag set */
+      zlog_warn("General IGMP query v3 from %s on %s: Suppress Router-Side Processing flag is clear",
+                from_str, ifp->name);
+    } else {
+      struct igmp_group *group;
+
+      /* this is a non-general query: perform timer updates */
+
+      group = find_group_by_addr(igmp, group_addr);
+      if (group) {
+        int recv_num_sources = ntohs(*(uint16_t *)(igmp_msg + IGMP_V3_NUMSOURCES_OFFSET));
+
+        /*
+         * RFC 3376: 6.6.1. Timer Updates
+         * Query Q(G,A): Source Timer for sources in A are lowered to LMQT
+         * Query Q(G): Group Timer is lowered to LMQT
+         */
+        if (recv_num_sources < 1) {
+          /* Query Q(G): Group Timer is lowered to LMQT */
+
+          igmp_group_timer_lower_to_lmqt(group);
+        } else {
+          /* Query Q(G,A): Source Timer for sources in A are lowered to LMQT */
+
+          /* Scan sources in query and lower their timers to LMQT */
+          struct in_addr *sources = (struct in_addr *)(igmp_msg + IGMP_V3_SOURCES_OFFSET);
+          for (i = 0; i < recv_num_sources; ++i) {
+            struct in_addr src_addr;
+            struct igmp_source *src;
+            memcpy(&src_addr, sources + i, sizeof(struct in_addr));
+            src = igmp_find_source_by_addr(group, src_addr);
+            if (src) {
+              igmp_source_timer_lower_to_lmqt(src);
+            }
+          }
+        }
+      } else {
+        char group_str[100];
+        pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+        zlog_warn("IGMP query v3 from %s on %s: could not find group %s for timer update",
+                  from_str, ifp->name, group_str);
+      }
+    }
+  } /* s_flag is clear: timer updates */
+}
+
+int
+igmp_v3_recv_report (struct igmp_sock *igmp,
+                     struct in_addr from, const char *from_str,
+                     char *igmp_msg, int igmp_msg_len)
+{
+  uint16_t          recv_checksum;
+  uint16_t          checksum;
+  int               num_groups;
+  uint8_t          *group_record;
+  uint8_t          *report_pastend = (uint8_t *) igmp_msg + igmp_msg_len;
+  struct interface *ifp = igmp->interface;
+  int               i;
+  int               local_ncb = 0;
+
+  if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) {
+    zlog_warn("Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d",
+              from_str, ifp->name, igmp_msg_len, IGMP_V3_MSG_MIN_SIZE);
+    return -1;
+  }
+
+  recv_checksum = *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET);
+
+  /* for computing checksum */
+  *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET) = 0;
+
+  checksum = in_cksum(igmp_msg, igmp_msg_len);
+  if (checksum != recv_checksum) {
+    zlog_warn("Recv IGMP report v3 from %s on %s: checksum mismatch: received=%x computed=%x",
+              from_str, ifp->name, recv_checksum, checksum);
+    return -1;
+  }
+
+  num_groups = ntohs(*(uint16_t *) (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET));
+  if (num_groups < 1) {
+    zlog_warn("Recv IGMP report v3 from %s on %s: missing group records",
+              from_str, ifp->name);
+    return -1;
+  }
 
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    zlog_debug("Recv IGMP report v3 from %s on %s: size=%d checksum=%x groups=%d",
+               from_str, ifp->name, igmp_msg_len, checksum, num_groups);
+  }
+
+  group_record = (uint8_t *) igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET;
+
+  /* Scan groups */
+  for (i = 0; i < num_groups; ++i) {
+    struct in_addr  rec_group;
+    uint8_t        *sources;
+    uint8_t        *src;
+    int             rec_type;
+    int             rec_auxdatalen;
+    int             rec_num_sources;
+    int             j;
+    struct prefix   lncb;
+    struct prefix   g;
+
+    if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) > report_pastend) {
+      zlog_warn("Recv IGMP report v3 from %s on %s: group record beyond report end",
+                from_str, ifp->name);
+      return -1;
+    }
+
+    rec_type        = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET];
+    rec_auxdatalen  = group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET];
+    rec_num_sources = ntohs(* (uint16_t *) (group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET));
+
+    memcpy(&rec_group, group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, sizeof(struct in_addr));
+
+    if (PIM_DEBUG_IGMP_PACKETS) {
+      zlog_debug("Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%s",
+                 from_str, ifp->name, i, rec_type, rec_auxdatalen, rec_num_sources, inet_ntoa(rec_group));
+    }
+
+    /* Scan sources */
+
+    sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET;
+
+    for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) {
+
+      if ((src + 4) > report_pastend) {
+        zlog_warn("Recv IGMP report v3 from %s on %s: group source beyond report end",
+                  from_str, ifp->name);
+        return -1;
+      }
+
+      if (PIM_DEBUG_IGMP_PACKETS) {
+        char src_str[200];
+
+        if (!inet_ntop(AF_INET, src, src_str , sizeof(src_str)))
+          sprintf(src_str, "<source?>");
+
+        zlog_debug("Recv IGMP report v3 from %s on %s: record=%d group=%s source=%s",
+                   from_str, ifp->name, i, inet_ntoa(rec_group), src_str);
+      }
+    } /* for (sources) */
+
+
+    lncb.family = AF_INET;
+    lncb.u.prefix4.s_addr = 0x000000E0;
+    lncb.prefixlen = 24;
+
+    g.family = AF_INET;
+    g.u.prefix4 = rec_group;
+    g.prefixlen = 32;
+    /*
+     * If we receive a igmp report with the group in 224.0.0.0/24
+     * then we should ignore it
+     */
+    if (prefix_match(&lncb, &g))
+      local_ncb = 1;
+
+    if (!local_ncb)
+      switch (rec_type) {
+      case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE:
+        igmpv3_report_isin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+        break;
+      case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE:
+        igmpv3_report_isex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources, 0);
+        break;
+      case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE:
+        igmpv3_report_toin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+        break;
+      case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE:
+        igmpv3_report_toex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+        break;
+      case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES:
+        igmpv3_report_allow(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+        break;
+      case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES:
+        igmpv3_report_block(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+        break;
+      default:
+        zlog_warn("Recv IGMP report v3 from %s on %s: unknown record type: type=%d",
+                  from_str, ifp->name, rec_type);
+      }
+
+    group_record += 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2);
+    local_ncb = 0;
+
+  } /* for (group records) */
+
+  return 0;
 }
index 37baca5c8d4d6f24c40f6f0c54ec895ac42b24f9..33a918bad11a276b0df2bd79e22b49802189b311 100644 (file)
 #define IGMP_V3_NUMSOURCES_OFFSET          (10)
 #define IGMP_V3_SOURCES_OFFSET             (12)
 
+#define IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE        (1)
+#define IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE        (2)
+#define IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE (3)
+#define IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE (4)
+#define IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES      (5)
+#define IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES      (6)
+
 /* GMI: Group Membership Interval */
 #define PIM_IGMP_GMI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * (qri_dsec))
 
@@ -55,15 +62,13 @@ void igmp_source_free(struct igmp_source *source);
 void igmp_source_delete(struct igmp_source *source);
 void igmp_source_delete_expired(struct list *source_list);
 
-int igmp_group_compat_mode(const struct igmp_sock *igmp,
-                          const struct igmp_group *group);
-
 void igmpv3_report_isin(struct igmp_sock *igmp, struct in_addr from,
                        struct in_addr group_addr,
                        int num_sources, struct in_addr *sources);
 void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from,
                        struct in_addr group_addr,
-                       int num_sources, struct in_addr *sources);
+                        int num_sources, struct in_addr *sources,
+                        int from_igmp_v2_report);
 void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from,
                        struct in_addr group_addr,
                        int num_sources, struct in_addr *sources);
@@ -83,17 +88,24 @@ void igmp_source_timer_lower_to_lmqt(struct igmp_source *source);
 struct igmp_source *igmp_find_source_by_addr(struct igmp_group *group,
                                             struct in_addr src_addr);
 
-void pim_igmp_send_membership_query(struct igmp_group *group,
-                                   int fd,
-                                   const char *ifname,
-                                   char *query_buf,
-                                   int query_buf_size,
-                                   int num_sources,
-                                   struct in_addr dst_addr,
-                                   struct in_addr group_addr,
-                                   int query_max_response_time_dsec,
-                                   uint8_t s_flag,
-                                   uint8_t querier_robustness_variable,
-                                   uint16_t querier_query_interval);
+void igmp_v3_send_query (struct igmp_group *group,
+                         int fd,
+                         const char *ifname,
+                         char *query_buf,
+                         int query_buf_size,
+                         int num_sources,
+                         struct in_addr dst_addr,
+                         struct in_addr group_addr,
+                         int query_max_response_time_dsec,
+                         uint8_t s_flag,
+                         uint8_t querier_robustness_variable,
+                         uint16_t querier_query_interval);
+
+void igmp_v3_recv_query (struct igmp_sock *igmp, const char *from_str,
+                         char *igmp_msg);
+
+int igmp_v3_recv_report (struct igmp_sock *igmp,
+                         struct in_addr from, const char *from_str,
+                         char *igmp_msg, int igmp_msg_len);
 
 #endif /* PIM_IGMPV3_H */
index b9c97557c5cec1e5364d2326061248506129a80e..341d733361a7ec1cf64b5e0382ba90904f57ad9d 100644 (file)
@@ -399,59 +399,97 @@ pim_mroute_msg_wrvifwhole (int fd, struct interface *ifp, const char *buf)
 int pim_mroute_msg(int fd, const char *buf, int buf_size)
 {
   struct interface     *ifp;
+  struct pim_interface *pim_ifp;
   const struct ip      *ip_hdr;
   const struct igmpmsg *msg;
+  char ip_src_str[100] = "";
+  char ip_dst_str[100] = "";
   char src_str[100] = "<src?>";
   char grp_str[100] = "<grp?>";
+  struct in_addr ifaddr;
+  struct igmp_sock *igmp;
 
   ip_hdr = (const struct ip *) buf;
 
-  /* kernel upcall must have protocol=0 */
-  if (ip_hdr->ip_p) {
-    /* this is not a kernel upcall */
+  if (ip_hdr->ip_p == IPPROTO_IGMP) {
+
+    /* We have the IP packet but we do not know which interface this packet was
+     * received on. Find the interface that is on the same subnet as the source
+     * of the IP packet.
+     */
+    ifp = if_lookup_address_vrf((void *) &ip_hdr->ip_src, AF_INET, VRF_DEFAULT);
+
+    if (!ifp) {
+      if (PIM_DEBUG_MROUTE_DETAIL) {
+        pim_inet4_dump("<src?>", ip_hdr->ip_src, ip_src_str, sizeof(ip_src_str));
+        pim_inet4_dump("<dst?>", ip_hdr->ip_dst, ip_dst_str, sizeof(ip_dst_str));
+
+        zlog_warn("%s: igmp kernel upcall could not find interface for %s -> %s",
+                __PRETTY_FUNCTION__,
+                ip_src_str,
+                ip_dst_str);
+      }
+      return 0;
+    }
+
+    pim_ifp = ifp->info;
+    ifaddr = pim_find_primary_addr(ifp);
+    igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list, ifaddr);
+
+    if (PIM_DEBUG_MROUTE_DETAIL) {
+      pim_inet4_dump("<src?>", ip_hdr->ip_src, ip_src_str, sizeof(ip_src_str));
+      pim_inet4_dump("<dst?>", ip_hdr->ip_dst, ip_dst_str, sizeof(ip_dst_str));
+
+      zlog_warn("%s: igmp kernel upcall on %s for %s -> %s",
+                __PRETTY_FUNCTION__, ifp->name, ip_src_str, ip_dst_str);
+    }
+
+    pim_igmp_packet(igmp, (char *)buf, buf_size);
+
+  } else if (ip_hdr->ip_p) {
     if (PIM_DEBUG_MROUTE_DETAIL) {
       pim_inet4_dump("<src?>", ip_hdr->ip_src, src_str, sizeof(src_str));
       pim_inet4_dump("<grp?>", ip_hdr->ip_dst, grp_str, sizeof(grp_str));
-      zlog_debug("%s: not a kernel upcall proto=%d src: %s dst: %s msg_size=%d",
-                __PRETTY_FUNCTION__, ip_hdr->ip_p, src_str, grp_str, buf_size);
+      zlog_debug("%s: no kernel upcall proto=%d src: %s dst: %s msg_size=%d",
+                 __PRETTY_FUNCTION__, ip_hdr->ip_p, src_str, grp_str, buf_size);
     }
-    return 0;
-  }
 
-  msg = (const struct igmpmsg *) buf;
+  } else {
+    msg = (const struct igmpmsg *) buf;
 
-  ifp = pim_if_find_by_vif_index(msg->im_vif);
+    ifp = pim_if_find_by_vif_index(msg->im_vif);
 
-  if (PIM_DEBUG_MROUTE) {
-    pim_inet4_dump("<src?>", msg->im_src, src_str, sizeof(src_str));
-    pim_inet4_dump("<grp?>", msg->im_dst, grp_str, sizeof(grp_str));
-    zlog_warn("%s: kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%s,%s) on %s vifi=%d  size=%d",
-             __PRETTY_FUNCTION__,
-             igmpmsgtype2str[msg->im_msgtype],
-             msg->im_msgtype,
-             ip_hdr->ip_p,
-             fd,
-             src_str,
-             grp_str,
-             ifp->name,
-             msg->im_vif, buf_size);
-  }
+    if (PIM_DEBUG_MROUTE) {
+      pim_inet4_dump("<src?>", msg->im_src, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", msg->im_dst, grp_str, sizeof(grp_str));
+      zlog_warn("%s: pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%s,%s) on %s vifi=%d  size=%d",
+                __PRETTY_FUNCTION__,
+                igmpmsgtype2str[msg->im_msgtype],
+                msg->im_msgtype,
+                ip_hdr->ip_p,
+                fd,
+                src_str,
+                grp_str,
+                ifp->name,
+                msg->im_vif, buf_size);
+    }
 
-  switch (msg->im_msgtype) {
-  case IGMPMSG_WRONGVIF:
-    return pim_mroute_msg_wrongvif(fd, ifp, msg);
-    break;
-  case IGMPMSG_NOCACHE:
-    return pim_mroute_msg_nocache(fd, ifp, msg);
-    break;
-  case IGMPMSG_WHOLEPKT:
-    return pim_mroute_msg_wholepkt(fd, ifp, (const char *)msg);
-    break;
-  case IGMPMSG_WRVIFWHOLE:
-    return pim_mroute_msg_wrvifwhole (fd, ifp, (const char *)msg);
-    break;
-  default:
-    break;
+    switch (msg->im_msgtype) {
+    case IGMPMSG_WRONGVIF:
+      return pim_mroute_msg_wrongvif(fd, ifp, msg);
+      break;
+    case IGMPMSG_NOCACHE:
+      return pim_mroute_msg_nocache(fd, ifp, msg);
+      break;
+    case IGMPMSG_WHOLEPKT:
+      return pim_mroute_msg_wholepkt(fd, ifp, (const char *)msg);
+      break;
+    case IGMPMSG_WRVIFWHOLE:
+      return pim_mroute_msg_wrvifwhole (fd, ifp, (const char *)msg);
+      break;
+    default:
+      break;
+    }
   }
 
   return 0;
index 3eb04d347e658306bf2414a1e3a09d4ac4852acb..644fb8e69133fde69b6a2aed5436c8b0cec33f52 100644 (file)
@@ -215,6 +215,15 @@ int pim_interface_config_write(struct vty *vty)
        ++writes;
       }
 
+      /* ip igmp version */
+      if (pim_ifp->igmp_version != IGMP_DEFAULT_VERSION)
+        {
+          vty_out(vty, " ip igmp version %d%s",
+                  pim_ifp->igmp_version,
+                  VTY_NEWLINE);
+          ++writes;
+        }
+
       /* IF ip igmp query-interval */
       if (pim_ifp->igmp_default_query_interval != IGMP_GENERAL_QUERY_INTERVAL)
        {