mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-04 04:50:35 +00:00 
			
		
		
		
	With the change over to using the kernel upcall for igmp messages, we need to add in a read thread for the igmp socket to drain the igmp socket's receive queue. Signed-off-by: Donald Sharp <sharpd@cumulusnetworks.com>
		
			
				
	
	
		
			1148 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1148 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
  PIM for Quagga
 | 
						|
  Copyright (C) 2008  Everton da Silva Marques
 | 
						|
 | 
						|
  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 <zebra.h>
 | 
						|
 | 
						|
#include "memory.h"
 | 
						|
#include "prefix.h"
 | 
						|
#include "if.h"
 | 
						|
 | 
						|
#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_mroute.h"
 | 
						|
#include "pim_str.h"
 | 
						|
#include "pim_util.h"
 | 
						|
#include "pim_time.h"
 | 
						|
#include "pim_zebra.h"
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
  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;
 | 
						|
    }
 | 
						|
    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));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    IGMP routers periodically send IGMP general queries to AllSystems=224.0.0.1
 | 
						|
    IGMP routers must receive general queries for querier election.
 | 
						|
  */
 | 
						|
  if (inet_aton(PIM_ALL_SYSTEMS, &group)) {
 | 
						|
    if (!pim_socket_join(fd, group, ifaddr, ifindex))
 | 
						|
      ++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_SYSTEMS, errno, safe_strerror(errno));
 | 
						|
  }
 | 
						|
 | 
						|
  if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) {
 | 
						|
    if (!pim_socket_join(fd, group, ifaddr, ifindex)) {
 | 
						|
      ++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_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));
 | 
						|
    close(fd);
 | 
						|
    fd = -1;
 | 
						|
  }
 | 
						|
 | 
						|
  return fd;
 | 
						|
}
 | 
						|
 | 
						|
#undef IGMP_SOCK_DUMP
 | 
						|
 | 
						|
#ifdef IGMP_SOCK_DUMP
 | 
						|
static void igmp_sock_dump(array_t *igmp_sock_array)
 | 
						|
{
 | 
						|
  int size = array_size(igmp_sock_array);
 | 
						|
  for (int i = 0; i < size; ++i) {
 | 
						|
    
 | 
						|
    struct igmp_sock *igmp = array_get(igmp_sock_array, i);
 | 
						|
    
 | 
						|
    zlog_debug("%s %s: [%d/%d] igmp_addr=%s fd=%d",
 | 
						|
	       __FILE__, __PRETTY_FUNCTION__,
 | 
						|
	       i, size,
 | 
						|
	       inet_ntoa(igmp->ifaddr),
 | 
						|
	       igmp->fd);
 | 
						|
  }
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
struct igmp_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list,
 | 
						|
					      struct in_addr ifaddr)
 | 
						|
{
 | 
						|
  struct listnode  *sock_node;
 | 
						|
  struct igmp_sock *igmp;
 | 
						|
 | 
						|
#ifdef IGMP_SOCK_DUMP
 | 
						|
  igmp_sock_dump(igmp_sock_list);
 | 
						|
#endif
 | 
						|
 | 
						|
  for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
 | 
						|
    if (ifaddr.s_addr == igmp->ifaddr.s_addr)
 | 
						|
      return igmp;
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
struct igmp_sock *igmp_sock_lookup_by_fd(struct list *igmp_sock_list,
 | 
						|
					 int fd)
 | 
						|
{
 | 
						|
  struct listnode  *sock_node;
 | 
						|
  struct igmp_sock *igmp;
 | 
						|
 | 
						|
  for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
 | 
						|
    if (fd == igmp->fd)
 | 
						|
      return igmp;
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int pim_igmp_other_querier_expire(struct thread *t)
 | 
						|
{
 | 
						|
  struct igmp_sock *igmp;
 | 
						|
 | 
						|
  igmp = THREAD_ARG(t);
 | 
						|
 | 
						|
  zassert(igmp->t_other_querier_timer);
 | 
						|
  zassert(!igmp->t_igmp_query_timer);
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char ifaddr_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
 | 
						|
    zlog_debug("%s: Querier %s resuming",
 | 
						|
	       __PRETTY_FUNCTION__,
 | 
						|
	       ifaddr_str);
 | 
						|
  }
 | 
						|
 | 
						|
  igmp->t_other_querier_timer = NULL;
 | 
						|
 | 
						|
  /*
 | 
						|
    We are the current querier, then
 | 
						|
    re-start sending general queries.
 | 
						|
  */
 | 
						|
  pim_igmp_general_query_on(igmp);
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
void pim_igmp_other_querier_timer_on(struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
  long other_querier_present_interval_msec;
 | 
						|
  struct pim_interface *pim_ifp;
 | 
						|
 | 
						|
  zassert(igmp);
 | 
						|
  zassert(igmp->interface);
 | 
						|
  zassert(igmp->interface->info);
 | 
						|
 | 
						|
  pim_ifp = igmp->interface->info;
 | 
						|
 | 
						|
  if (igmp->t_other_querier_timer) {
 | 
						|
    /*
 | 
						|
      There is other querier present already,
 | 
						|
      then reset the other-querier-present timer.
 | 
						|
    */
 | 
						|
 | 
						|
    if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
      char ifaddr_str[INET_ADDRSTRLEN];
 | 
						|
      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
 | 
						|
      zlog_debug("Querier %s resetting TIMER event for Other-Querier-Present",
 | 
						|
		 ifaddr_str);
 | 
						|
    }
 | 
						|
 | 
						|
    THREAD_OFF(igmp->t_other_querier_timer);
 | 
						|
    zassert(!igmp->t_other_querier_timer);
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    /*
 | 
						|
      We are the current querier, then stop sending general queries:
 | 
						|
      igmp->t_igmp_query_timer = NULL;
 | 
						|
    */
 | 
						|
    pim_igmp_general_query_off(igmp);
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    Since this socket is starting the other-querier-present timer,
 | 
						|
    there should not be periodic query timer for this socket.
 | 
						|
   */
 | 
						|
  zassert(!igmp->t_igmp_query_timer);
 | 
						|
 | 
						|
  /*
 | 
						|
    RFC 3376: 8.5. Other Querier Present Interval
 | 
						|
 | 
						|
    The Other Querier Present Interval is the length of time that must
 | 
						|
    pass before a multicast router decides that there is no longer
 | 
						|
    another multicast router which should be the querier.  This value
 | 
						|
    MUST be ((the Robustness Variable) times (the Query Interval)) plus
 | 
						|
    (one half of one Query Response Interval).
 | 
						|
 | 
						|
    other_querier_present_interval_msec = \
 | 
						|
      igmp->querier_robustness_variable * \
 | 
						|
      1000 * igmp->querier_query_interval + \
 | 
						|
      100 * (pim_ifp->query_max_response_time_dsec >> 1);
 | 
						|
  */
 | 
						|
  other_querier_present_interval_msec =
 | 
						|
    PIM_IGMP_OQPI_MSEC(igmp->querier_robustness_variable,
 | 
						|
		       igmp->querier_query_interval,
 | 
						|
		       pim_ifp->igmp_query_max_response_time_dsec);
 | 
						|
  
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char ifaddr_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
 | 
						|
    zlog_debug("Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present",
 | 
						|
	       ifaddr_str,
 | 
						|
	       other_querier_present_interval_msec / 1000,
 | 
						|
	       other_querier_present_interval_msec % 1000);
 | 
						|
  }
 | 
						|
  
 | 
						|
  THREAD_TIMER_MSEC_ON(master, igmp->t_other_querier_timer,
 | 
						|
		       pim_igmp_other_querier_expire,
 | 
						|
		       igmp, other_querier_present_interval_msec);
 | 
						|
}
 | 
						|
 | 
						|
void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
  zassert(igmp);
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    if (igmp->t_other_querier_timer) {
 | 
						|
      char ifaddr_str[INET_ADDRSTRLEN];
 | 
						|
      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
 | 
						|
      zlog_debug("IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s",
 | 
						|
		 ifaddr_str, igmp->fd, igmp->interface->name);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  THREAD_OFF(igmp->t_other_querier_timer);
 | 
						|
  zassert(!igmp->t_other_querier_timer);
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
  struct in_addr        group_addr;
 | 
						|
  uint16_t              recv_checksum;
 | 
						|
  uint16_t              checksum;
 | 
						|
 | 
						|
  memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
 | 
						|
 | 
						|
  ifp = igmp->interface;
 | 
						|
  pim_ifp = ifp->info;
 | 
						|
 | 
						|
  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 query v%d from %s on %s: checksum mismatch: received=%x computed=%x",
 | 
						|
	      query_version, from_str, ifp->name, recv_checksum, checksum);
 | 
						|
    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[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
 | 
						|
    zlog_debug("Recv IGMP query v%d from %s on %s for group %s",
 | 
						|
	       query_version, from_str, ifp->name, group_str);
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    RFC 3376: 6.6.2. Querier Election
 | 
						|
 | 
						|
    When a router receives a query with a lower IP address, it sets
 | 
						|
    the Other-Querier-Present timer to Other Querier Present Interval
 | 
						|
    and ceases to send queries on the network if it was the previously
 | 
						|
    elected querier.
 | 
						|
   */
 | 
						|
  if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) {
 | 
						|
 | 
						|
    if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
      char ifaddr_str[INET_ADDRSTRLEN];
 | 
						|
      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
 | 
						|
      zlog_debug("%s: local address %s (%u) lost querier election to %s (%u)",
 | 
						|
		 ifp->name,
 | 
						|
		 ifaddr_str, ntohl(igmp->ifaddr.s_addr),
 | 
						|
		 from_str, ntohl(from.s_addr));
 | 
						|
    }
 | 
						|
 | 
						|
    pim_igmp_other_querier_timer_on(igmp);
 | 
						|
  }
 | 
						|
 | 
						|
  /* IGMP version 3 is the only one where we process the RXed query */
 | 
						|
  if (query_version == 3) {
 | 
						|
    igmp_v3_recv_query(igmp, from_str, igmp_msg);
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void on_trace(const char *label,
 | 
						|
		     struct interface *ifp, struct in_addr from)
 | 
						|
{
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char from_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
 | 
						|
    zlog_debug("%s: from %s on %s",
 | 
						|
	       label, from_str, ifp->name);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
  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 v1 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__);
 | 
						|
  }
 | 
						|
 | 
						|
  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_v1_report_dsec = pim_time_monotonic_dsec();
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len)
 | 
						|
{
 | 
						|
  struct ip *ip_hdr;
 | 
						|
  size_t ip_hlen; /* ip header length in bytes */
 | 
						|
  char *igmp_msg;
 | 
						|
  int igmp_msg_len;
 | 
						|
  int msg_type;
 | 
						|
  char from_str[INET_ADDRSTRLEN];
 | 
						|
  char to_str[INET_ADDRSTRLEN];
 | 
						|
    
 | 
						|
  if (len < sizeof(*ip_hdr)) {
 | 
						|
    zlog_warn("IGMP packet size=%zu shorter than minimum=%zu",
 | 
						|
	      len, sizeof(*ip_hdr));
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  ip_hdr = (struct ip *) buf;
 | 
						|
 | 
						|
  pim_inet4_dump("<src?>", ip_hdr->ip_src, from_str , sizeof(from_str));
 | 
						|
  pim_inet4_dump("<dst?>", ip_hdr->ip_dst, to_str , sizeof(to_str));
 | 
						|
 | 
						|
  ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_PACKETS) {
 | 
						|
    zlog_debug("Recv IP packet from %s to %s on %s: size=%zu ip_header_size=%zu ip_proto=%d",
 | 
						|
	       from_str, to_str, igmp->interface->name, len, ip_hlen, ip_hdr->ip_p);
 | 
						|
  }
 | 
						|
 | 
						|
  if (ip_hdr->ip_p != PIM_IP_PROTO_IGMP) {
 | 
						|
    zlog_warn("IP packet protocol=%d is not IGMP=%d",
 | 
						|
	      ip_hdr->ip_p, PIM_IP_PROTO_IGMP);
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  if (ip_hlen < PIM_IP_HEADER_MIN_LEN) {
 | 
						|
    zlog_warn("IP packet header size=%zu shorter than minimum=%d",
 | 
						|
	      ip_hlen, PIM_IP_HEADER_MIN_LEN);
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
  if (ip_hlen > PIM_IP_HEADER_MAX_LEN) {
 | 
						|
    zlog_warn("IP packet header size=%zu greater than maximum=%d",
 | 
						|
	      ip_hlen, PIM_IP_HEADER_MAX_LEN);
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  igmp_msg = buf + ip_hlen;
 | 
						|
  msg_type = *igmp_msg;
 | 
						|
  igmp_msg_len = len - ip_hlen;
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_PACKETS) {
 | 
						|
    zlog_debug("Recv IGMP packet from %s to %s on %s: ttl=%d msg_type=%d msg_size=%d",
 | 
						|
	       from_str, to_str, igmp->interface->name, ip_hdr->ip_ttl, msg_type,
 | 
						|
	       igmp_msg_len);
 | 
						|
  }
 | 
						|
 | 
						|
  if (igmp_msg_len < PIM_IGMP_MIN_LEN) {
 | 
						|
    zlog_warn("IGMP message size=%d shorter than minimum=%d",
 | 
						|
	      igmp_msg_len, PIM_IGMP_MIN_LEN);
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  switch (msg_type) {
 | 
						|
  case PIM_IGMP_MEMBERSHIP_QUERY:
 | 
						|
    {
 | 
						|
      int max_resp_code = igmp_msg[1];
 | 
						|
      int query_version;
 | 
						|
 | 
						|
      /*
 | 
						|
	RFC 3376: 7.1. Query Version Distinctions
 | 
						|
	IGMPv1 Query: length = 8 octets AND Max Resp Code field is zero
 | 
						|
	IGMPv2 Query: length = 8 octets AND Max Resp Code field is non-zero
 | 
						|
	IGMPv3 Query: length >= 12 octets
 | 
						|
      */
 | 
						|
 | 
						|
      if (igmp_msg_len == 8) {
 | 
						|
	query_version = max_resp_code ? 2 : 1;
 | 
						|
      }
 | 
						|
      else if (igmp_msg_len >= 12) {
 | 
						|
	query_version = 3;
 | 
						|
      }
 | 
						|
      else {
 | 
						|
	zlog_warn("Unknown IGMP query version");
 | 
						|
	return -1;
 | 
						|
      }
 | 
						|
 | 
						|
      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_recv_report(igmp, ip_hdr->ip_src, from_str,
 | 
						|
                               igmp_msg, igmp_msg_len);
 | 
						|
 | 
						|
  case PIM_IGMP_V2_MEMBERSHIP_REPORT:
 | 
						|
    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_recv_report(igmp, ip_hdr->ip_src, from_str,
 | 
						|
                               igmp_msg, igmp_msg_len);
 | 
						|
 | 
						|
  case PIM_IGMP_V2_LEAVE_GROUP:
 | 
						|
    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);
 | 
						|
 | 
						|
  return -1;
 | 
						|
}
 | 
						|
 | 
						|
static int pim_igmp_general_query(struct thread *t);
 | 
						|
 | 
						|
void pim_igmp_general_query_on(struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
  struct pim_interface *pim_ifp;
 | 
						|
  int startup_mode;
 | 
						|
  int query_interval;
 | 
						|
 | 
						|
  /*
 | 
						|
    Since this socket is starting as querier,
 | 
						|
    there should not exist a timer for other-querier-present.
 | 
						|
   */
 | 
						|
  zassert(!igmp->t_other_querier_timer);
 | 
						|
  pim_ifp = igmp->interface->info;
 | 
						|
  zassert(pim_ifp);
 | 
						|
 | 
						|
  /*
 | 
						|
    RFC 3376: 8.6. Startup Query Interval
 | 
						|
 | 
						|
    The Startup Query Interval is the interval between General Queries
 | 
						|
    sent by a Querier on startup.  Default: 1/4 the Query Interval.
 | 
						|
    The first one should be sent out immediately instead of 125/4
 | 
						|
    seconds from now.
 | 
						|
  */
 | 
						|
  startup_mode = igmp->startup_query_count > 0;
 | 
						|
  if (startup_mode) {
 | 
						|
    /*
 | 
						|
     * If this is the first time we are sending a query on a
 | 
						|
     * newly configured igmp interface send it out in 1 second
 | 
						|
     * just to give the entire world a tiny bit of time to settle
 | 
						|
     * else the query interval is:
 | 
						|
     * query_interval = pim_ifp->igmp_default_query_interval >> 2;
 | 
						|
     */
 | 
						|
    if (igmp->startup_query_count == igmp->querier_robustness_variable)
 | 
						|
      query_interval = 1;
 | 
						|
    else
 | 
						|
      query_interval = PIM_IGMP_SQI(pim_ifp->igmp_default_query_interval);
 | 
						|
 | 
						|
    --igmp->startup_query_count;
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    query_interval = igmp->querier_query_interval;
 | 
						|
  }
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char ifaddr_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
 | 
						|
    zlog_debug("Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
 | 
						|
	       ifaddr_str,
 | 
						|
	       query_interval,
 | 
						|
	       startup_mode ? "startup" : "non-startup",
 | 
						|
	       igmp->fd);
 | 
						|
  }
 | 
						|
  igmp->t_igmp_query_timer = NULL;
 | 
						|
  THREAD_TIMER_ON(master, igmp->t_igmp_query_timer,
 | 
						|
		  pim_igmp_general_query,
 | 
						|
		  igmp, query_interval);
 | 
						|
}
 | 
						|
 | 
						|
void pim_igmp_general_query_off(struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
  zassert(igmp);
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    if (igmp->t_igmp_query_timer) {
 | 
						|
      char ifaddr_str[INET_ADDRSTRLEN];
 | 
						|
      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
 | 
						|
      zlog_debug("IGMP querier %s fd=%d cancelling query TIMER event on %s",
 | 
						|
		 ifaddr_str, igmp->fd, igmp->interface->name);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  THREAD_OFF(igmp->t_igmp_query_timer);
 | 
						|
}
 | 
						|
 | 
						|
/* Issue IGMP general query */
 | 
						|
static int pim_igmp_general_query(struct thread *t)
 | 
						|
{
 | 
						|
  struct igmp_sock *igmp;
 | 
						|
  struct in_addr dst_addr;
 | 
						|
  struct in_addr group_addr;
 | 
						|
  struct pim_interface *pim_ifp;
 | 
						|
  int    query_buf_size;
 | 
						|
 | 
						|
  igmp = THREAD_ARG(t);
 | 
						|
 | 
						|
  zassert(igmp->interface);
 | 
						|
  zassert(igmp->interface->info);
 | 
						|
 | 
						|
  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
 | 
						|
 | 
						|
    In IGMPv3, General Queries are sent with an IP destination address
 | 
						|
    of 224.0.0.1, the all-systems multicast address.  Group-Specific
 | 
						|
    and Group-and-Source-Specific Queries are sent with an IP
 | 
						|
    destination address equal to the multicast address of interest.
 | 
						|
  */
 | 
						|
 | 
						|
  dst_addr.s_addr   = htonl(INADDR_ALLHOSTS_GROUP);
 | 
						|
  group_addr.s_addr = PIM_NET_INADDR_ANY;
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char querier_str[INET_ADDRSTRLEN];
 | 
						|
    char dst_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<querier?>", igmp->ifaddr, querier_str,
 | 
						|
		   sizeof(querier_str));
 | 
						|
    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
 | 
						|
    zlog_debug("Querier %s issuing IGMP general query to %s on %s",
 | 
						|
	       querier_str, dst_str, igmp->interface->name);
 | 
						|
  }
 | 
						|
 | 
						|
  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 void sock_close(struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
  pim_igmp_other_querier_timer_off(igmp);
 | 
						|
  pim_igmp_general_query_off(igmp);
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE_DETAIL) {
 | 
						|
    if (igmp->t_igmp_read) {
 | 
						|
      zlog_debug("Cancelling READ event on IGMP socket %s fd=%d on interface %s",
 | 
						|
		 inet_ntoa(igmp->ifaddr), igmp->fd,
 | 
						|
		 igmp->interface->name);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  THREAD_OFF(igmp->t_igmp_read);
 | 
						|
  
 | 
						|
  if (close(igmp->fd)) {
 | 
						|
    zlog_err("Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s",
 | 
						|
	     inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name,
 | 
						|
	     errno, safe_strerror(errno));
 | 
						|
  }
 | 
						|
  
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE_DETAIL) {
 | 
						|
    zlog_debug("Deleted IGMP socket %s fd=%d on interface %s",
 | 
						|
	       inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void igmp_startup_mode_on(struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
  struct pim_interface *pim_ifp;
 | 
						|
 | 
						|
  pim_ifp = igmp->interface->info;
 | 
						|
 | 
						|
  /*
 | 
						|
    RFC 3376: 8.7. Startup Query Count
 | 
						|
 | 
						|
    The Startup Query Count is the number of Queries sent out on
 | 
						|
    startup, separated by the Startup Query Interval.  Default: the
 | 
						|
    Robustness Variable.
 | 
						|
  */
 | 
						|
  igmp->startup_query_count = igmp->querier_robustness_variable;
 | 
						|
 | 
						|
  /*
 | 
						|
    Since we're (re)starting, reset QQI to default Query Interval
 | 
						|
  */
 | 
						|
  igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
 | 
						|
}
 | 
						|
 | 
						|
static void igmp_group_free(struct igmp_group *group)
 | 
						|
{
 | 
						|
  list_free(group->group_source_list);
 | 
						|
 | 
						|
  XFREE(MTYPE_PIM_IGMP_GROUP, group);
 | 
						|
}
 | 
						|
 | 
						|
static void igmp_group_delete(struct igmp_group *group)
 | 
						|
{
 | 
						|
  struct listnode *src_node;
 | 
						|
  struct listnode *src_nextnode;
 | 
						|
  struct igmp_source *src;
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char group_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
 | 
						|
    zlog_debug("Deleting IGMP group %s from socket %d interface %s",
 | 
						|
	       group_str,
 | 
						|
	       group->group_igmp_sock->fd,
 | 
						|
	       group->group_igmp_sock->interface->name);
 | 
						|
  }
 | 
						|
 | 
						|
  for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode, src)) {
 | 
						|
    igmp_source_delete(src);
 | 
						|
  }
 | 
						|
 | 
						|
  if (group->t_group_query_retransmit_timer) {
 | 
						|
    THREAD_OFF(group->t_group_query_retransmit_timer);
 | 
						|
  }
 | 
						|
 | 
						|
  group_timer_off(group);
 | 
						|
  listnode_delete(group->group_igmp_sock->igmp_group_list, group);
 | 
						|
  igmp_group_free(group);
 | 
						|
}
 | 
						|
 | 
						|
void igmp_group_delete_empty_include(struct igmp_group *group)
 | 
						|
{
 | 
						|
  zassert(!group->group_filtermode_isexcl);
 | 
						|
  zassert(!listcount(group->group_source_list));
 | 
						|
 | 
						|
  igmp_group_delete(group);
 | 
						|
}
 | 
						|
 | 
						|
void igmp_sock_free(struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
  zassert(!igmp->t_igmp_read);
 | 
						|
  zassert(!igmp->t_igmp_query_timer);
 | 
						|
  zassert(!igmp->t_other_querier_timer);
 | 
						|
  zassert(igmp->igmp_group_list);
 | 
						|
  zassert(!listcount(igmp->igmp_group_list));
 | 
						|
 | 
						|
  list_free(igmp->igmp_group_list);
 | 
						|
 | 
						|
  XFREE(MTYPE_PIM_IGMP_SOCKET, igmp);
 | 
						|
}
 | 
						|
 | 
						|
void igmp_sock_delete(struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
  struct pim_interface *pim_ifp;
 | 
						|
  struct listnode      *grp_node;
 | 
						|
  struct listnode      *grp_nextnode;
 | 
						|
  struct igmp_group    *grp;
 | 
						|
 | 
						|
  for (ALL_LIST_ELEMENTS(igmp->igmp_group_list, grp_node, grp_nextnode, grp)) {
 | 
						|
    igmp_group_delete(grp);
 | 
						|
  }
 | 
						|
 | 
						|
  sock_close(igmp);
 | 
						|
 | 
						|
  pim_ifp = igmp->interface->info;
 | 
						|
 | 
						|
  listnode_delete(pim_ifp->igmp_socket_list, igmp);
 | 
						|
 | 
						|
  igmp_sock_free(igmp);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
igmp_sock_delete_all (struct interface *ifp)
 | 
						|
{
 | 
						|
  struct pim_interface *pim_ifp;
 | 
						|
  struct listnode *igmp_node, *igmp_nextnode;
 | 
						|
  struct igmp_sock *igmp;
 | 
						|
 | 
						|
  pim_ifp = ifp->info;
 | 
						|
 | 
						|
  for (ALL_LIST_ELEMENTS (pim_ifp->igmp_socket_list, igmp_node,
 | 
						|
			  igmp_nextnode, igmp))
 | 
						|
    {
 | 
						|
      igmp_sock_delete(igmp);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static struct igmp_sock *igmp_sock_new(int fd,
 | 
						|
				       struct in_addr ifaddr,
 | 
						|
				       struct interface *ifp)
 | 
						|
{
 | 
						|
  struct pim_interface *pim_ifp;
 | 
						|
  struct igmp_sock *igmp;
 | 
						|
 | 
						|
  pim_ifp = ifp->info;
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    zlog_debug("Creating IGMP socket fd=%d for address %s on interface %s",
 | 
						|
	       fd, inet_ntoa(ifaddr), ifp->name);
 | 
						|
  }
 | 
						|
 | 
						|
  igmp = XCALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp));
 | 
						|
  if (!igmp) {
 | 
						|
    zlog_warn("%s %s: XCALLOC() failure",
 | 
						|
              __FILE__, __PRETTY_FUNCTION__);
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  igmp->igmp_group_list = list_new();
 | 
						|
  if (!igmp->igmp_group_list) {
 | 
						|
    zlog_err("%s %s: failure: igmp_group_list = list_new()",
 | 
						|
	     __FILE__, __PRETTY_FUNCTION__);
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
  igmp->igmp_group_list->del = (void (*)(void *)) igmp_group_free;
 | 
						|
 | 
						|
  igmp->fd                          = fd;
 | 
						|
  igmp->interface                   = ifp;
 | 
						|
  igmp->ifaddr                      = ifaddr;
 | 
						|
  igmp->t_igmp_read                 = NULL;
 | 
						|
  igmp->t_igmp_query_timer          = NULL;
 | 
						|
  igmp->t_other_querier_timer       = NULL; /* no other querier present */
 | 
						|
  igmp->querier_robustness_variable = pim_ifp->igmp_default_robustness_variable;
 | 
						|
  igmp->sock_creation               = pim_time_monotonic_sec();
 | 
						|
 | 
						|
  /*
 | 
						|
    igmp_startup_mode_on() will reset QQI:
 | 
						|
 | 
						|
    igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
 | 
						|
  */
 | 
						|
  igmp_startup_mode_on(igmp);
 | 
						|
  pim_igmp_general_query_on(igmp);
 | 
						|
 | 
						|
  return igmp;
 | 
						|
}
 | 
						|
 | 
						|
static void igmp_read_on (struct igmp_sock *igmp);
 | 
						|
 | 
						|
static int
 | 
						|
pim_igmp_read (struct thread *t)
 | 
						|
{
 | 
						|
  uint8_t buf[10000];
 | 
						|
  struct igmp_sock *igmp = (struct igmp_sock *)THREAD_ARG(t);
 | 
						|
  struct sockaddr_in from;
 | 
						|
  struct sockaddr_in to;
 | 
						|
  socklen_t fromlen = sizeof(from);
 | 
						|
  socklen_t tolen = sizeof(to);
 | 
						|
  ifindex_t ifindex = -1;
 | 
						|
  int cont = 1;
 | 
						|
  int len;
 | 
						|
 | 
						|
  while (cont)
 | 
						|
    {
 | 
						|
      len = pim_socket_recvfromto(igmp->fd, buf, sizeof(buf),
 | 
						|
				  &from, &fromlen,
 | 
						|
				  &to, &tolen,
 | 
						|
				  &ifindex);
 | 
						|
      if (len < 0)
 | 
						|
	{
 | 
						|
	  if (errno == EINTR)
 | 
						|
	    continue;
 | 
						|
	  if (errno == EWOULDBLOCK || errno == EAGAIN)
 | 
						|
	  {
 | 
						|
	    cont = 0;
 | 
						|
	    break;
 | 
						|
	  }
 | 
						|
	  goto done;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
 done:
 | 
						|
  igmp_read_on(igmp);
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
igmp_read_on (struct igmp_sock *igmp)
 | 
						|
{
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE_DETAIL) {
 | 
						|
    zlog_debug("Scheduling READ event on IGMP socket fd=%d",
 | 
						|
	       igmp->fd);
 | 
						|
  }
 | 
						|
  igmp->t_igmp_read = NULL;
 | 
						|
  THREAD_READ_ON(master, igmp->t_igmp_read, pim_igmp_read, igmp, igmp->fd);
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
 | 
						|
				    struct in_addr ifaddr,
 | 
						|
				    struct interface *ifp)
 | 
						|
{
 | 
						|
  struct pim_interface *pim_ifp;
 | 
						|
  struct igmp_sock *igmp;
 | 
						|
  int fd;
 | 
						|
 | 
						|
  pim_ifp = ifp->info;
 | 
						|
 | 
						|
  fd = igmp_sock_open(ifaddr, ifp->ifindex, pim_ifp->options);
 | 
						|
  if (fd < 0) {
 | 
						|
    zlog_warn("Could not open IGMP socket for %s on %s",
 | 
						|
	      inet_ntoa(ifaddr), ifp->name);
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  igmp = igmp_sock_new(fd, ifaddr, ifp);
 | 
						|
  if (!igmp) {
 | 
						|
    zlog_err("%s %s: igmp_sock_new() failure",
 | 
						|
	     __FILE__, __PRETTY_FUNCTION__);
 | 
						|
    close(fd);
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  igmp_read_on (igmp);
 | 
						|
 | 
						|
  listnode_add(igmp_sock_list, igmp);
 | 
						|
 | 
						|
#ifdef IGMP_SOCK_DUMP
 | 
						|
  igmp_sock_dump(igmp_sock_array);
 | 
						|
#endif
 | 
						|
 | 
						|
  return igmp;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
  RFC 3376: 6.5. Switching Router Filter-Modes
 | 
						|
 | 
						|
  When a router's filter-mode for a group is EXCLUDE and the group
 | 
						|
  timer expires, the router filter-mode for the group transitions to
 | 
						|
  INCLUDE.
 | 
						|
 | 
						|
  A router uses source records with running source timers as its state
 | 
						|
  for the switch to a filter-mode of INCLUDE.  If there are any source
 | 
						|
  records with source timers greater than zero (i.e., requested to be
 | 
						|
  forwarded), a router switches to filter-mode of INCLUDE using those
 | 
						|
  source records.  Source records whose timers are zero (from the
 | 
						|
  previous EXCLUDE mode) are deleted.
 | 
						|
 */
 | 
						|
static int igmp_group_timer(struct thread *t)
 | 
						|
{
 | 
						|
  struct igmp_group *group;
 | 
						|
 | 
						|
  group = THREAD_ARG(t);
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char group_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
 | 
						|
    zlog_debug("%s: Timer for group %s on interface %s",
 | 
						|
	       __PRETTY_FUNCTION__,
 | 
						|
	       group_str, group->group_igmp_sock->interface->name);
 | 
						|
  }
 | 
						|
 | 
						|
  zassert(group->group_filtermode_isexcl);
 | 
						|
 | 
						|
  group->t_group_timer = NULL;
 | 
						|
  group->group_filtermode_isexcl = 0;
 | 
						|
 | 
						|
  /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
 | 
						|
  igmp_anysource_forward_stop(group);
 | 
						|
 | 
						|
  igmp_source_delete_expired(group->group_source_list);
 | 
						|
 | 
						|
  zassert(!group->t_group_timer);
 | 
						|
  zassert(!group->group_filtermode_isexcl);
 | 
						|
 | 
						|
  /*
 | 
						|
    RFC 3376: 6.2.2. Definition of Group Timers
 | 
						|
 | 
						|
    If there are no more source records for the group, delete group
 | 
						|
    record.
 | 
						|
  */
 | 
						|
  if (listcount(group->group_source_list) < 1) {
 | 
						|
    igmp_group_delete_empty_include(group);
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void group_timer_off(struct igmp_group *group)
 | 
						|
{
 | 
						|
  if (!group->t_group_timer)
 | 
						|
    return;
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char group_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
 | 
						|
    zlog_debug("Cancelling TIMER event for group %s on %s",
 | 
						|
	       group_str, group->group_igmp_sock->interface->name);
 | 
						|
  }
 | 
						|
    
 | 
						|
  THREAD_OFF(group->t_group_timer);
 | 
						|
  zassert(!group->t_group_timer);
 | 
						|
}
 | 
						|
 | 
						|
void igmp_group_timer_on(struct igmp_group *group,
 | 
						|
			 long interval_msec, const char *ifname)
 | 
						|
{
 | 
						|
  group_timer_off(group);
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_EVENTS) {
 | 
						|
    char group_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
 | 
						|
    zlog_debug("Scheduling %ld.%03ld sec TIMER event for group %s on %s",
 | 
						|
	       interval_msec / 1000,
 | 
						|
	       interval_msec % 1000,
 | 
						|
	       group_str, ifname);
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    RFC 3376: 6.2.2. Definition of Group Timers
 | 
						|
 | 
						|
    The group timer is only used when a group is in EXCLUDE mode and
 | 
						|
    it represents the time for the *filter-mode* of the group to
 | 
						|
    expire and switch to INCLUDE mode.
 | 
						|
  */
 | 
						|
  zassert(group->group_filtermode_isexcl);
 | 
						|
 | 
						|
  THREAD_TIMER_MSEC_ON(master, group->t_group_timer,
 | 
						|
		       igmp_group_timer,
 | 
						|
		       group, interval_msec);
 | 
						|
}
 | 
						|
 | 
						|
struct igmp_group *
 | 
						|
find_group_by_addr (struct igmp_sock *igmp,
 | 
						|
		   struct in_addr group_addr)
 | 
						|
{
 | 
						|
  struct igmp_group *group;
 | 
						|
  struct listnode   *node;
 | 
						|
 | 
						|
  for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, node, group))
 | 
						|
    if (group_addr.s_addr == group->group_addr.s_addr)
 | 
						|
      return group;
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
 | 
						|
					  struct in_addr group_addr)
 | 
						|
{
 | 
						|
  struct igmp_group *group;
 | 
						|
 | 
						|
  group = find_group_by_addr(igmp, group_addr);
 | 
						|
  if (group) {
 | 
						|
    return group;
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    Non-existant group is created as INCLUDE {empty}:
 | 
						|
 | 
						|
    RFC 3376 - 5.1. Action on Change of Interface State
 | 
						|
 | 
						|
    If no interface state existed for that multicast address before
 | 
						|
    the change (i.e., the change consisted of creating a new
 | 
						|
    per-interface record), or if no state exists after the change
 | 
						|
    (i.e., the change consisted of deleting a per-interface record),
 | 
						|
    then the "non-existent" state is considered to have a filter mode
 | 
						|
    of INCLUDE and an empty source list.
 | 
						|
  */
 | 
						|
 | 
						|
  group = XCALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group));
 | 
						|
  if (!group) {
 | 
						|
    zlog_warn("%s %s: XCALLOC() failure",
 | 
						|
	      __FILE__, __PRETTY_FUNCTION__);
 | 
						|
    return 0; /* error, not found, could not create */
 | 
						|
  }
 | 
						|
 | 
						|
  group->group_source_list = list_new();
 | 
						|
  if (!group->group_source_list) {
 | 
						|
    zlog_warn("%s %s: list_new() failure",
 | 
						|
	      __FILE__, __PRETTY_FUNCTION__);
 | 
						|
    XFREE(MTYPE_PIM_IGMP_GROUP, group); /* discard group */
 | 
						|
    return 0; /* error, not found, could not initialize */
 | 
						|
  }
 | 
						|
  group->group_source_list->del = (void (*)(void *)) igmp_source_free;
 | 
						|
 | 
						|
  group->t_group_timer                         = NULL;
 | 
						|
  group->t_group_query_retransmit_timer        = NULL;
 | 
						|
  group->group_specific_query_retransmit_count = 0;
 | 
						|
  group->group_addr                            = group_addr;
 | 
						|
  group->group_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 */
 | 
						|
 | 
						|
  listnode_add(igmp->igmp_group_list, group);
 | 
						|
 | 
						|
  if (PIM_DEBUG_IGMP_TRACE) {
 | 
						|
    char group_str[INET_ADDRSTRLEN];
 | 
						|
    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
 | 
						|
    zlog_debug("Creating new IGMP group %s on socket %d interface %s",
 | 
						|
	       group_str, igmp->fd, igmp->interface->name);
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    RFC 3376: 6.2.2. Definition of Group Timers
 | 
						|
 | 
						|
    The group timer is only used when a group is in EXCLUDE mode and
 | 
						|
    it represents the time for the *filter-mode* of the group to
 | 
						|
    expire and switch to INCLUDE mode.
 | 
						|
  */
 | 
						|
  zassert(!group->group_filtermode_isexcl); /* INCLUDE mode */
 | 
						|
  zassert(!group->t_group_timer); /* group timer == 0 */
 | 
						|
 | 
						|
  /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
 | 
						|
  igmp_anysource_forward_stop(group);
 | 
						|
 | 
						|
  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);
 | 
						|
  }
 | 
						|
}
 |