Files
m241Neelima/components/ip_net2-6.9/ipmcp/src/ipmcp_igmp.c
T
2026-07-30 11:19:44 +05:30

2715 lines
91 KiB
C

/*
******************************************************************************
* INTERPEAK SOURCE FILE
*
* Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipmcp_igmp.c,v $ $Revision: 1.36.10.7 $
* $Source: /home/interpeak/CVSRoot/ipmcp/src/ipmcp_igmp.c,v $
* $Author: hxu4 $
* $State: Exp $ $Locker: $
*
* INTERPEAK_COPYRIGHT_STRING
* Design and implementation by Kenneth Jonsson <kenneth@interpeak.se>
******************************************************************************
*/
/*
****************************************************************************
* 1 DESCRIPTION
****************************************************************************
*/
/*
****************************************************************************
* 2 CONFIGURATION
****************************************************************************
*/
#include "ipmcp_config.h"
#ifdef IPMCP_USE_IGMP
/*
****************************************************************************
* 3 INCLUDE FILES
****************************************************************************
*/
#define IPCOM_USE_CLIB_PROTO
#include <ipcom_clib.h>
#include <ipcom_cstyle.h>
#include <ipcom_err.h>
#include <ipcom_inet.h>
#include <ipcom_set.h>
#include <ipcom_syslog.h>
#include <ipcom_sysvar.h>
#include <ipcom_sock2.h>
#include <ipcom_type.h>
#include "ipmcp_h.h"
/*
****************************************************************************
* 4 DEFINES
****************************************************************************
*/
#define IPMCP_IGMP_TYPE_MEMBERSHIP_QUERY 0x11
#define IPMCP_IGMP_TYPE_MEMBERSHIP_REPORT 0x12
#define IPMCP_IGMP_TYPE_MEMBERSHIP_REPORT_V2 0x16
#define IPMCP_IGMP_TYPE_LEAVE_GROUP_V2 0x17
#define IPMCP_IGMP_TYPE_MEMBERSHIP_REPORT_V3 0x22
#define IPMCP_IGMP_MIN_PKT_LEN 8
/*
****************************************************************************
* 5 TYPES
****************************************************************************
*/
#include <ipcom_align16.h>
typedef IP_PACKED1 struct Ipmcp_ip4_pkt_struct
{
Ip_u8 v_hl; /* 4bit version and 4bit header length. */
Ip_u8 tos; /* 8bit type of service. */
Ip_u16 len; /* Total length. */
Ip_u16 id; /* Identification. */
Ip_u16 off; /* Fragment offset field. */
Ip_u8 ttl; /* 8bit time-to-live. */
Ip_u8 p; /* 8bit IP protocol. */
Ip_u16 sum; /* Internet Checksum. */
Ip_u16 src[2]; /* Destination IP address. */
Ip_u16 dst[2]; /* Source IP address. */
}
IP_PACKED2 Ipmcp_ip4_pkt_t;
typedef IP_PACKED1 struct Ipmcp_pkt_ip_opt_struct
{
Ip_u8 flag_class_num; /* 1 bit copied flag, 2 bits class, 5 bits option number */
Ip_u8 len; /* Option length in octets included the header */
Ip_u8 data[2]; /* Option data */
}
IP_PACKED2 Ipmcp_pkt_ip_opt;
typedef IP_PACKED1 struct Ipmcp_igmp_query_pkt_struct
{
Ip_u8 type; /* One of the IPMCP_IGMP_TYPE_xxx constants */
Ip_u8 max_resp_time; /* The maximum response time in 1/10 of a second */
Ip_u16 checksum; /* Internet checksum */
struct Ip_in_addr group_address; /* IPv4 multicast address or 0 for general queries */
Ip_u8 res_s_qrv; /* Reserved,
Suppress Router-Side Processing,
Querier's Robustness Variable */
Ip_u8 qqic; /* Querier's Query Interval Code */
Ip_u16 num_sources; /* Number of sources */
struct Ip_in_addr source_addresses[1]; /* Multicast source addresses */
}
IP_PACKED2 Ipmcp_igmp_query_pkt_t;
#define IPMCP_IGMPV3_QUERY_SUPRESS_BIT 0x08
#define IPMCP_IGMPV3_GET_QRV(query) ((query)->res_s_qrv & 0x07)
typedef IP_PACKED1 struct Ipmcp_igmp_group_rec_pkt_struct
{
Ip_u8 type; /* One of the IPMCP_GROUP_REC_TYPE_xxx constants */
Ip_u8 aux_data_len; /* Auxilly data length in number of 32 bit words */
Ip_u16 num_sources; /* Number of sources */
struct Ip_in_addr group_address; /* IPv4 multicast address */
struct Ip_in_addr source_addresses[1]; /* Multicast source addresses */
}
IP_PACKED2 Ipmcp_igmp_group_rec_pkt_t;
typedef IP_PACKED1 struct Ipmcp_igmp_report_pkt_struct
{
Ip_u8 type; /* 0x22 */
Ip_u8 reserved;
Ip_u16 checksum; /* Internet checksum */
Ip_u16 reserved2;
Ip_u16 num_group_rec; /* Number of group records */
Ipmcp_igmp_group_rec_pkt_t recs[1];
}
IP_PACKED2 Ipmcp_igmp_report_pkt_t;
#include <ipcom_align16.h>
/*
*===========================================================================
* Ipmcp_igmp_rec_t
*===========================================================================
* IGMPv3 record class
*/
typedef struct Ipmcp_igmp_rec_struct
{
Ipmcp_rec_t base; /* Base class */
Ipmcp_igmp_group_rec_pkt_t *rec; /* Pointer to an IGMPv3 record */
}
Ipmcp_igmp_rec_t;
/*
*===========================================================================
* Ipmcp_igmp_saddr_struct
*===========================================================================
* IPv4 multicast source address descriptor for a downstream interface.
*/
typedef struct Ipmcp_igmp_saddr_struct
{
Ipmcp_saddr_t base; /* Base class */
struct Ip_in_addr saddr; /* Source address */
} Ipmcp_igmp_saddr_t;
/*
****************************************************************************
* 6 EXTERN PROTOTYPES
****************************************************************************
*/
/*
****************************************************************************
* 7 LOCAL PROTOTYPES
****************************************************************************
*/
IP_STATIC void ipmcp_igmp_maddr_drop(Ipmcp_igmp_maddr_t *maddr_obj, void *unused);
/*
****************************************************************************
* 8 DATA
****************************************************************************
*/
/*
****************************************************************************
* 9 STATIC FUNCTIONS
****************************************************************************
*/
/*
*===========================================================================
* ipmcp_igmp_set_sock_addr
*===========================================================================
* Description: Creates a struct Ip_sockaddr_storage from a struct Ip_in_addr
* Parameters: s - A socket storage buffer.
* a - An inet address
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_set_sock_addr(struct Ip_sockaddr_storage *s, struct Ip_in_addr *a)
{
struct Ip_sockaddr_in *i = (struct Ip_sockaddr_in *)s;
ipcom_memset(i, 0, sizeof(*i));
i->sin_family = IP_AF_INET;
IPCOM_SA_LEN_SET(i, sizeof(*i));
i->sin_addr = *a;
}
/*
*===========================================================================
* ipmcp_igmp_addr_key_hash
*===========================================================================
* Description: Returns a hash value for an IPv4 address.
* Parameters:
* Returns:
*
*/
IP_STATIC unsigned
ipmcp_igmp_addr_key_hash(struct Ip_in_addr *addr)
{
return ipcom_hash_update(addr, sizeof(*addr), 0);
}
/*
*===========================================================================
* ipmcp_igmp_saddr_obj_hash
*===========================================================================
* Description: Returns a hash value for the multicast source address.
* Parameters:
* Returns:
*
*/
IP_STATIC unsigned
ipmcp_igmp_saddr_obj_hash(Ipmcp_igmp_saddr_t *obj)
{
return ipcom_hash_update(&obj->saddr, sizeof(obj->saddr), 0);
}
/*
*===========================================================================
* ipmcp_igmp_saddr_compare
*===========================================================================
* Description: Compares a MCP multicast source address entry with a IPv4 address.
* Parameters:
* Returns:
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_saddr_compare(Ipmcp_igmp_saddr_t *obj , struct Ip_in_addr *saddr)
{
return obj->saddr.s_addr == saddr->s_addr;
}
/*
*===========================================================================
* ipmcp_igmp_maddr_obj_hash
*===========================================================================
* Description: Returns a hash value for the multicast address.
* Parameters:
* Returns:
*
*/
IP_STATIC unsigned
ipmcp_igmp_maddr_obj_hash(Ipmcp_igmp_maddr_t *obj)
{
return ipcom_hash_update(&obj->maddr, sizeof(obj->maddr), 0);
}
/*
*===========================================================================
* ipmcp_igmp_maddr_compare
*===========================================================================
* Description: Compares a MCP multicast address entry with a IPv4 address.
* Parameters:
* Returns:
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_maddr_compare(Ipmcp_igmp_maddr_t *obj , struct Ip_in_addr *maddr)
{
return obj->maddr.s_addr == maddr->s_addr;
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_obj_hash
*===========================================================================
* Description: Returns a hash value for a upstream multicast address.
* Parameters:
* Returns:
*
*/
IP_STATIC unsigned
ipmcp_igmp_upstream_maddr_obj_hash(Ipmcp_igmp_upstream_maddr_t *obj)
{
return ipcom_hash_update(&obj->maddr, sizeof(obj->maddr), 0);
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_compare
*===========================================================================
* Description: Compares an upstream multicast address entry with a IPv4 address.
* Parameters:
* Returns:
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_upstream_maddr_compare(Ipmcp_igmp_upstream_maddr_t *obj , struct Ip_in_addr *maddr)
{
return obj->maddr.s_addr == maddr->s_addr;
}
/*
*===========================================================================
* ipmcp_igmp_is_maddr_link_local
*===========================================================================
* Description: Returns IP_TRUE if the specified address fall inside the
* range 224.0.0.0 to 224.0.0.255.
* Parameters:
* Returns:
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_is_maddr_link_local(struct Ip_in_addr *maddr)
{
Ip_u8 *a = (Ip_u8 *) maddr;
return a[0] == 224 && a[1] == 0 && a[2] == 0;
}
/*
*===========================================================================
* ipmcp_igmp_get_primary_addr
*===========================================================================
* Description: Gets the primary IPv4 address of the specified interface.
* Parameters: ifindex - The index of the interface for which the primary
* address should be returned.
* primary_addr - Will hold the primary address if the call
* succeeds.
* Returns: 0 = success
* <0 = error
*
*/
IP_STATIC int
ipmcp_igmp_get_primary_addr(Ip_u32 ifindex,
struct Ip_in_addr *primary_addr)
{
struct Ip_ifreq ifreq;
if (ipcom_if_indextoname(ifindex, ifreq.ifr_name) == IP_NULL)
{
IPCOM_LOG1(ERR, "ipmcp_igmp_get_primary_addr:"
" Could not get a name for interface with index %d", ifindex);
return -1;
}
#ifdef IP_PORT_LAS
if (ipcom_socketioctl(ipmcp.cfg_fd, IP_SIOCGIFADDR, &ifreq) < 0)
#else
if (ipcom_socketioctl(ipmcp.ipd_fd, IP_SIOCGIFADDR, &ifreq) < 0)
#endif
{
IPCOM_LOG1(ERR,"ipcom_socketioctl IP_SIOCGIFADDR: %s",
ipcom_strerror(ipcom_errno));
return -1;
}
ipcom_memcpy(primary_addr,
&((struct Ip_sockaddr_in *)&ifreq.ip_ifr_addr)->sin_addr,
sizeof(*primary_addr));
if (primary_addr->s_addr == IP_INADDR_ANY)
{
IPCOM_LOG1(NOTICE, "ipmcp_igmp_get_primary_addr:"
" No primary address set on interface %d", ifindex);
}
return 0;
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_get_addr
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void *
ipmcp_igmp_upstream_maddr_get_addr(Ipmcp_igmp_upstream_maddr_t *maddr)
{
return &maddr->maddr;
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_change_mode
*===========================================================================
* Description: Changes filter mode of the upstream address.
* Parameters: maddr - A upstream multicast address.
* to_exclude - IP_TRUE if the mode should be changed to exclude
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_upstream_maddr_change_mode(Ipmcp_igmp_upstream_maddr_t *maddr, Ip_bool to_exclude)
{
struct Ip_group_req req;
/* Leave this group if it has been joined */
req.gr_interface = ipmcp.upstream_if.ifindex;
ipmcp_igmp_set_sock_addr(&req.gr_group, &maddr->maddr);
(void) ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_MCAST_LEAVE_GROUP,
&req, sizeof(req));
ipcom_set_remove_all(maddr->base.filter);
maddr->base.filter->user = (to_exclude ? IPCOM_SET_IS_EXCLUDE : IPCOM_SET_IS_INCLUDE);
if (to_exclude)
{
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_MCAST_JOIN_GROUP,
&req, sizeof(req)) < 0)
{
IPCOM_LOG2(ERR, "ipmcp_igmp_upstream_maddr_change_mode: setsockopt IPPROTO_IP, MCAST_JOIN_GROUP failed on %s: %s",
ipmcp.upstream_if.ifname, ipcom_strerror(ipcom_errno));
IP_PANIC2();
}
}
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_add_source
*===========================================================================
* Description: Adds a new source address to the current filter.
* Parameters: maddr - A upstream multicast address.
* src - The source address to add to the filter.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_upstream_maddr_add_source(Ipmcp_igmp_upstream_maddr_t *maddr, struct Ip_in_addr *src)
{
struct Ip_group_source_req sreq;
if (ipcom_set_add(maddr->base.filter, src) != IPCOM_SUCCESS)
{
IP_PANIC2();
return;
}
sreq.gsr_interface = ipmcp.upstream_if.ifindex;
ipmcp_igmp_set_sock_addr(&sreq.gsr_group, &maddr->maddr);
ipmcp_igmp_set_sock_addr(&sreq.gsr_source, src);
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
(maddr->base.filter->user == IPCOM_SET_IS_EXCLUDE
? IP_MCAST_BLOCK_SOURCE
: IP_MCAST_JOIN_SOURCE_GROUP),
&sreq, sizeof(sreq)) < 0)
{
IPCOM_LOG2(ERR, "ipmcp_igmp_upstream_maddr_add_source: setsockopt failed on %s: %s",
ipmcp.upstream_if.ifname, ipcom_strerror(ipcom_errno));
IP_PANIC2();
}
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_del_source
*===========================================================================
* Description: Deletes a source address to the current filter.
* Parameters: maddr - A upstream multicast address.
* src - The source address to remove from the filter.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_upstream_maddr_del_source(Ipmcp_igmp_upstream_maddr_t *maddr, struct Ip_in_addr *src)
{
struct Ip_group_source_req sreq;
if (ipcom_set_remove(maddr->base.filter, src) != IPCOM_SUCCESS)
{
IP_PANIC2();
return;
}
sreq.gsr_interface = ipmcp.upstream_if.ifindex;
ipmcp_igmp_set_sock_addr(&sreq.gsr_group, &maddr->maddr);
ipmcp_igmp_set_sock_addr(&sreq.gsr_source, src);
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
(maddr->base.filter->user == IPCOM_SET_IS_EXCLUDE
? IP_MCAST_UNBLOCK_SOURCE
: IP_MCAST_LEAVE_SOURCE_GROUP),
&sreq, sizeof(sreq)) < 0)
{
IPCOM_LOG2(ERR, "ipmcp_igmp_upstream_maddr_del_source: setsockopt failed on %s: %s",
ipmcp.upstream_if.ifname, ipcom_strerror(ipcom_errno));
IP_PANIC2();
}
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_flush_mroutes
*===========================================================================
* Description: Removes all multicast forwarding entries for the added for
* the specified multicast group.
* Parameters: maddr_obj - A multicast group.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_upstream_maddr_flush_mroutes(Ipmcp_igmp_upstream_maddr_t *maddr_obj)
{
struct Ip_mfcctl mfctl;
int i;
ipcom_memset(&mfctl, 0, sizeof(mfctl));
mfctl.mfcc_mcastgrp = maddr_obj->maddr;
mfctl.mfcc_parent = ipmcp.upstream_if.igmp.vif_index;
for (i = 0; i < IPMCP_IGMP_MAX_SRC_ADDRS; i++)
{
if (maddr_obj->saddrs[i].s_addr == IP_INADDR_ANY)
continue;
mfctl.mfcc_origin = maddr_obj->saddrs[i];
IPCOM_LOG2(DEBUG, "IGMP: deleting mcast route from %s to group %s",
ipcom_inet_ntop(IP_AF_INET, &mfctl.mfcc_origin, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
ipcom_inet_ntop(IP_AF_INET, &mfctl.mfcc_mcastgrp, ipmcp.log_buf + IP_INET_ADDRSTRLEN, sizeof(ipmcp.log_buf)));
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_MRT_DEL_MFC,
&mfctl,
sizeof(mfctl)) < 0)
{
IPCOM_LOG4(ERR, "ipmcp_igmp_upstream_maddr_flush_mroutes: "
"setsockopt IPPROTO_IP, MRT_DEL_MFC failed on %s [%s %s]: %s",
ipmcp.upstream_if.ifname,
ipcom_inet_ntop(IP_AF_INET, &mfctl.mfcc_mcastgrp,
ipmcp.log_buf, sizeof(ipmcp.log_buf)),
ipcom_inet_ntop(IP_AF_INET, &mfctl.mfcc_mcastgrp,
ipmcp.log_buf + IP_INET_ADDRSTRLEN, sizeof(ipmcp.log_buf)),
ipcom_strerror(ipcom_errno));
}
maddr_obj->saddrs[i].s_addr = IP_INADDR_ANY;
}
maddr_obj->next_src_addr = 0;
}
/*
*===========================================================================
* ipmcp_igmp_flush_all_mroutes_cb
*===========================================================================
* Description: Adapter function for ipmcp_igmp_upstream_maddr_flush_mroutes()
* Parameters: upstream_maddr - An upstream multicast address.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_flush_all_mroutes_cb(Ipmcp_igmp_upstream_maddr_t *upstream_maddr,
void *unused)
{
(void) unused;
ipmcp_igmp_upstream_maddr_flush_mroutes(upstream_maddr);
}
/*
*===========================================================================
* ipmcp_igmp_flush_all_mroutes
*===========================================================================
* Description: Flushes all IPv4 multicast routes.
* Parameters:
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_flush_all_mroutes(void)
{
ipcom_hash_for_each(ipmcp.upstream_if.igmp.maddr_head,
(Ipcom_hash_foreach_cb_func)ipmcp_igmp_flush_all_mroutes_cb,
IP_NULL);
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_get
*===========================================================================
* Description: Returns the upstream multicast address object, it is created
* if needed
* Parameters: maddr - An IPv4 multicast address.
* Returns: Upstream multicast object.
*
*/
IP_STATIC Ipmcp_igmp_upstream_maddr_t *
ipmcp_igmp_upstream_maddr_get(struct Ip_in_addr *maddr)
{
Ipmcp_igmp_upstream_maddr_t *maddr_obj;
maddr_obj = ipcom_hash_get(ipmcp.upstream_if.igmp.maddr_head, maddr);
if (maddr_obj == IP_NULL)
{
maddr_obj = ipcom_calloc(1, sizeof(Ipmcp_igmp_upstream_maddr_t));
if (maddr_obj == IP_NULL)
goto out_of_memory;
maddr_obj->base.filter = ipcom_set_new(sizeof(struct Ip_in_addr));
if (maddr_obj->base.filter == IP_NULL)
goto out_of_memory;
maddr_obj->maddr = *maddr;
maddr_obj->base.ops = &ipmcp.igmp.upstream_maddr_ops;
if (ipcom_hash_add(ipmcp.upstream_if.igmp.maddr_head, maddr_obj) != IPCOM_SUCCESS)
goto out_of_memory;
}
return maddr_obj;
out_of_memory:
if (maddr_obj)
{
ipcom_set_delete(maddr_obj->base.filter);
ipcom_free(maddr_obj);
}
IPCOM_LOG0(ERR, "ipmcp_igmp_upstream_maddr_get: Failed to allocate memory");
IP_PANIC2();
return IP_NULL;
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_delete
*===========================================================================
* Description: Deletes a multicast address from the upstream interface.
* Parameters: maddr_obj - The address to delete from the upstream interface.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_upstream_maddr_delete(Ipmcp_igmp_upstream_maddr_t *maddr_obj)
{
struct Ip_group_req req;
if (ipcom_hash_remove(ipmcp.upstream_if.igmp.maddr_head, maddr_obj) != IPCOM_SUCCESS)
IP_PANIC();
req.gr_interface = ipmcp.upstream_if.ifindex;
ipmcp_igmp_set_sock_addr(&req.gr_group, &maddr_obj->maddr);
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_MCAST_LEAVE_GROUP,
&req, sizeof(req)) < 0)
{
IPCOM_LOG2(ERR, "ipmcp_igmp_upstream_maddr_delete: "
"setsockopt IPPROTO_IP, IP_MCAST_LEAVE_GROUP failed on %s: %s",
ipmcp.upstream_if.ifname, ipcom_strerror(ipcom_errno));
}
ipmcp_igmp_upstream_maddr_flush_mroutes(maddr_obj);
ipcom_set_delete(maddr_obj->base.filter);
ipcom_free(maddr_obj);
}
/*
*===========================================================================
* ipmcp_igmp_upstream_maddr_delete_cb
*===========================================================================
* Description: Deletes a multicast address from the upstream interface.
* Parameters: maddr_obj - The address to destroy.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_upstream_maddr_delete_cb(Ipmcp_igmp_upstream_maddr_t *maddr_obj, void *unused)
{
(void) unused;
ipmcp_igmp_upstream_maddr_delete(maddr_obj);
}
/*
*===========================================================================
* ipmcp_igmp_store_mroute
*===========================================================================
* Description: Stores that a multicast route has been added from the
* specified source to the specified multicast address.
* Parameters: maddr - A multicast address that traffic has been sent to..
* src - The source of the multicast traffic
* Returns:
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_store_mroute(struct Ip_in_addr *maddr, struct Ip_in_addr *src)
{
Ipmcp_igmp_upstream_maddr_t *maddr_obj;
maddr_obj = ipmcp_igmp_upstream_maddr_get(maddr);
if (maddr_obj == IP_NULL)
return IP_FALSE;
if (maddr_obj->saddrs[maddr_obj->next_src_addr].s_addr != IP_INADDR_ANY)
{
/* Ring buffer overflow, remove the oldest entry */
struct Ip_mfcctl mfctl;
IPCOM_LOG2(DEBUG, "IGMP: deleting mcast route from %s to group %s",
ipcom_inet_ntop(IP_AF_INET, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
ipcom_inet_ntop(IP_AF_INET, maddr, ipmcp.log_buf + IP_INET_ADDRSTRLEN, sizeof(ipmcp.log_buf)));
ipcom_memset(&mfctl, 0, sizeof(mfctl));
mfctl.mfcc_mcastgrp = maddr_obj->maddr;
mfctl.mfcc_parent = ipmcp.upstream_if.igmp.vif_index;
mfctl.mfcc_origin = maddr_obj->saddrs[maddr_obj->next_src_addr];
(void)ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_MRT_DEL_MFC,
&mfctl,
sizeof(mfctl));
}
maddr_obj->saddrs[maddr_obj->next_src_addr] = *src;
maddr_obj->next_src_addr = (maddr_obj->next_src_addr + 1) % IPMCP_IGMP_MAX_SRC_ADDRS;
return IP_TRUE;
}
/*
*===========================================================================
* ipmcp_igmp_netif_to_maddr_head
*===========================================================================
* Description: Returns the IPv4 multicast address object head.
* Parameters: netif - A downstream interface.
* Returns:
*
*/
IP_STATIC Ipcom_hash *
ipmcp_igmp_netif_to_maddr_head(Ipmcp_netif_t *netif)
{
return netif->igmp.maddr_head;
}
/*
*===========================================================================
* ipmcp_igmp_update_upstream_if_filter
*===========================================================================
* Description: Recalculates the filter mode for the upstream interface
* for the specified multicast address
* Parameters: maddr - The multicast address this route will target.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_update_upstream_if_filter(Ipmcp_igmp_maddr_t *maddr)
{
Ipmcp_igmp_upstream_maddr_t *maddr_obj;
maddr_obj = ipmcp_igmp_upstream_maddr_get(&maddr->maddr);
if (maddr_obj != IP_NULL)
{
/* Flushing the routes for each call to this function will result in correct
behavior using very simple logic, but it will lead to more IP_IGMPMSG_NOCACHE messages */
ipmcp_igmp_upstream_maddr_flush_mroutes(maddr_obj);
ipmcp_update_upstream_if_filter(&maddr_obj->base, ipmcp_igmp_netif_to_maddr_head);
}
}
/*
*===========================================================================
* ipmcp_igmp_forward_stream
*===========================================================================
* Description: Returns if the (maddr, saddr) stream should be forwared to
* the specified interface.
* Parameters: netif - The interface the stream should be forwared to.
* maddr - A multicast address
* saddr - A multicast source address.
* Returns: IP_TRUE if this stream should be forwared.
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_forward_stream(Ipmcp_netif_t *netif,
struct Ip_in_addr *maddr,
struct Ip_in_addr *saddr)
{
Ipmcp_maddr_t *maddr_obj;
Ipmcp_saddr_t *saddr_obj;
if (netif->igmp.state != IPMCP_LINK_STATE_QUERIER)
return IP_FALSE;
maddr_obj = ipcom_hash_get(netif->igmp.maddr_head, maddr);
if (maddr_obj == IP_NULL)
return IP_FALSE;
saddr_obj = maddr_obj->ops->get_saddr(maddr_obj, saddr, maddr_obj->mode_is_include);
if (maddr_obj->mode_is_include)
return saddr_obj != IP_NULL;
return saddr_obj == IP_NULL;
}
/*
*===========================================================================
* ipmcp_igmp_add_mroute
*===========================================================================
* Description: Adds a multicast route entry.
* Parameters: vifi - The virtual interface the packet was received at.
* maddr - The multicast address this route will target.
* src - The source of the multicast packets.
* Returns:
*
*/
IP_STATIC int
ipmcp_igmp_add_mroute(Ip_vifi_t vifi, struct Ip_in_addr *maddr, struct Ip_in_addr *src)
{
int n_vif = 0;
struct Ip_mfcctl mfctl;
Ipmcp_netif_t *netif;
Ip_bool add_mroute = IP_FALSE;
/* Setup multicast route */
ipcom_memset(&mfctl, 0, sizeof(mfctl));
mfctl.mfcc_parent = vifi;
mfctl.mfcc_mcastgrp = *maddr;
mfctl.mfcc_origin = *src;
if (vifi == ipmcp.upstream_if.igmp.vif_index)
add_mroute = IP_TRUE;
else
{
/* Must always forward to the upstream interface when
multicast traffic entrers at one of the downstream interfaces */
n_vif++;
mfctl.mfcc_ttls[ipmcp.upstream_if.igmp.vif_index] = 1;
}
/* Check MRT table on each interface */
for (netif = IPCOM_LIST_FIRST(&ipmcp.downstream_ifs_head);
netif != IP_NULL;
netif = IPCOM_LIST_NEXT(&netif->list))
{
if (vifi == netif->igmp.vif_index)
add_mroute = (netif->igmp.state == IPMCP_LINK_STATE_QUERIER);
else if (ipmcp_igmp_forward_stream(netif, maddr, src))
{
/* Found an interface to forward on */
n_vif++;
mfctl.mfcc_ttls[netif->igmp.vif_index] = 1;
}
}
if (!add_mroute)
/* Not received on one of the downstream interfaces where the
proxy is querier or at the upstream interface */
return 0;
IPCOM_LOG2(DEBUG, "IGMP: adding mcast route from %s to group %s",
ipcom_inet_ntop(IP_AF_INET, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
ipcom_inet_ntop(IP_AF_INET, maddr, ipmcp.log_buf + IP_INET_ADDRSTRLEN, sizeof(ipmcp.log_buf)));
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_MRT_ADD_MFC,
&mfctl, sizeof(mfctl)) < 0)
{
IPCOM_LOG1(WARNING, "ipmcp_igmp_add_mroute: failed to add mroute: %s",
ipcom_strerror(ipcom_errno));
return -ipcom_errno;
}
ipmcp_igmp_store_mroute(maddr, src);
return 0;
}
/*
*===========================================================================
* ipmcp_igmp_add_saddr_to_query
*===========================================================================
* Description: Adds a source address to a query.
* Parameters: saddr - A source address to add.
* query - The query message buffer.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_add_saddr_to_query(struct Ip_in_addr *saddr, Ipmcp_igmp_query_pkt_t *query)
{
query->source_addresses[query->num_sources++] = *saddr;
}
/*
*===========================================================================
* ipmcp_igmp_query_send
*===========================================================================
* Description: Send IGMP query (general or specific).
* Parameters: netif - The interface to send the query at.
* maddr - The multicast address to send the query for.
* max_response_interval_msec - The maximum response time.
* suppress_router_side_processing - IP_TRUE if the S bit
* should be set.
* saddrs - The set of sources to include in the query,
* IP_NULL is equal to empty set.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_query_send(Ipmcp_netif_t *netif,
struct Ip_in_addr *maddr,
Ip_u32 max_response_interval_msec,
Ip_bool suppress_router_side_processing,
Ipcom_set *saddrs)
{
Ipmcp_igmp_query_pkt_t *pkt = (Ipmcp_igmp_query_pkt_t *) ipmcp.send_buf;
struct Ip_sockaddr_in to;
struct Ip_msghdr m;
struct Ip_iovec iov;
struct Ip_in_pktinfo *pktinfo;
Ip_u8 *has_router_alert;
struct Ip_cmsghdr *c;
Ip_u8 cmsg_buf[IP_CMSG_SPACE(sizeof(struct Ip_in_pktinfo))
+ IP_CMSG_SPACE(sizeof(Ip_u8))];
int version;
unsigned msg_len;
version = ipcom_sysvar_get_as_int("ipmcp.igmp.CompatibilityMode", 3);
ipcom_memset(&to, 0, sizeof(to));
IPCOM_SA_LEN_SET(&to, sizeof(to));
to.sin_family = IP_AF_INET;
to.sin_addr = *maddr;
ipcom_memset(pkt, 0, sizeof(*pkt));
pkt->type = IPMCP_IGMP_TYPE_MEMBERSHIP_QUERY;
if (version > 1)
pkt->max_resp_time = max_response_interval_msec / 100;
/*
* else: RFC1112 specifies this field as unused for IGMPv1
*/
if (maddr->s_addr != ipmcp.igmp.all_hosts_addr.s_addr)
pkt->group_address = *maddr;
/* else: general query */
switch (version)
{
case 1:
case 2:
msg_len = 8;
break;
default:
msg_len = 12;
if (ipmcp.config.robustness_variable <= 7)
/* RFC3376 - 4.1.6.
If non-zero, the QRV field contains the [Robustness Variable] value
used by the querier, i.e., the sender of the Query. If the querier's
[Robustness Variable] exceeds 7, the maximum value of the QRV field,
the QRV is set to zero. */
pkt->res_s_qrv = (Ip_u8) ipmcp.config.robustness_variable;
if (suppress_router_side_processing)
IP_BIT_SET(pkt->res_s_qrv, IPMCP_IGMPV3_QUERY_SUPRESS_BIT);
pkt->qqic = ipmcp_qqic_from_msec(ipmcp.config.query_interval, 3);
break;
}
if (saddrs != IP_NULL)
{
ipcom_set_for_each(saddrs,
(Ipcom_set_foreach_cb_func) ipmcp_igmp_add_saddr_to_query,
pkt);
msg_len += pkt->num_sources * 4;
pkt->num_sources = ip_htons(pkt->num_sources);
}
pkt->checksum = ipcom_in_checksum(pkt, msg_len);
iov.iov_base = pkt;
iov.iov_len = msg_len;
/*
* Specify egress interface
*/
ipcom_memset(cmsg_buf, 0, sizeof(cmsg_buf));
c = (struct Ip_cmsghdr *) cmsg_buf;
c->cmsg_len = IP_CMSG_LEN(sizeof(struct Ip_in_pktinfo));
c->cmsg_level = IP_IPPROTO_IP;
c->cmsg_type = IP_IP_PKTINFO;
pktinfo = IP_CMSG_DATA(c);
pktinfo->ipi_ifindex = netif->ifindex;
/*
* Include router alert IP option
*/
c = (struct Ip_cmsghdr *) ((char*)c + IP_CMSG_SPACE(sizeof(struct Ip_in_pktinfo)));
c->cmsg_len = IP_CMSG_LEN(sizeof(Ip_u8));
c->cmsg_level = IP_IPPROTO_IP;
c->cmsg_type = IP_IP_ROUTER_ALERT;
has_router_alert = IP_CMSG_DATA(c);
*has_router_alert = IP_TRUE;
m.msg_name = &to;
m.msg_namelen = sizeof(to);
m.msg_iov = &iov;
m.msg_iovlen = 1;
m.msg_control = cmsg_buf;
m.msg_controllen = sizeof(cmsg_buf);
m.msg_flags = 0;
if (ipcom_sendmsg(ipmcp.igmp.fd, &m, 0) < 0)
{
IPCOM_LOG2(ERR, "IGMP: Failed to send message on interface %s: %s",
netif->ifname,
ipcom_strerror(ipcom_errno));
}
}
/*
*===========================================================================
* ipmcp_igmp_general_query_tmo
*===========================================================================
* Description: Timeout routine used for repeatedly sending general queries
* on an interface.
* Parameters: netif - The interface the general query should be sent at.
* Returns:
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_general_query_tmo(Ipmcp_netif_t *netif)
{
Ipmcp_igmp_netif_t *i = &netif->igmp;
Ip_bool restart_timer = IP_TRUE;
if (i->state == IPMCP_LINK_STATE_INACTIVE)
/* Retry later (this is probably because of no unicast address) */
return IP_TRUE;
ipmcp_igmp_query_send(netif,
&ipmcp.igmp.all_hosts_addr,
ipmcp.config.query_response_interval,
IP_FALSE,
IP_NULL);
if (i->startup_query_count > 0)
{
Ip_u32 duration;
if (--i->startup_query_count == 0)
/* Normal queries */
duration = ipmcp.config.query_interval;
else
/* Startup queries */
duration = ipmcp.config.startup_query_interval;
(void)ipcom_tmo2_request(ipmcp.tmo_head,
&i->timer,
(Ipcom_tmo2_func) ipmcp_igmp_general_query_tmo,
netif,
duration);
restart_timer = IP_FALSE;
}
return restart_timer;
}
/*
*===========================================================================
* ipmcp_igmp_resume_querier_tmo
*===========================================================================
* Description: Timeout routine called when resuming to querier state.
* Parameters: netif - The link to resume as querier.
* Returns: IP_FALSE - Do not reschedule this timeout.
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_resume_querier_tmo(Ipmcp_netif_t *netif)
{
IPCOM_LOG1(NOTICE, "IGMP: Link state QUERIER on %s", netif->ifname);
/* Must flush all routes since some or all of them might want to
include this interface as an outgoing interface now
RFC4605 chapter 3
...
When the proxy device receives a packet destined for a multicast
group (channel in Source-Specific Multicast (SSM)), it uses a list
consisting of the upstream interface and any downstream interface
that has a subscription pertaining to this packet and on which it is
the IGMP Querier.
...
*/
ipmcp_igmp_flush_all_mroutes();
netif->igmp.state = IPMCP_LINK_STATE_QUERIER;
netif->igmp.robustness_variable = ipmcp.config.robustness_variable;
netif->igmp.query_interval = ipmcp.config.query_interval;
netif->igmp.startup_query_count = ipmcp.config.startup_query_count;
ipmcp_igmp_general_query_tmo(netif);
return IP_FALSE;
}
/*
*===========================================================================
* ipmcp_igmp_version_1_or_2_compat_tmo
*===========================================================================
* Description: Called when IGMPv1 or IGMPv2 compatibilty mode can be exited
* Parameters: maddr - A multicast address.
* Returns: IP_FALSE - The timeout should not be rescheduled.
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_version_1_or_2_compat_tmo(Ipmcp_igmp_maddr_t *maddr)
{
(void) maddr;
IPCOM_LOG2(INFO,
"IGMP: Exit IGMP%s compatibility mode on %s",
IPCOM_TMO2_PENDING(&maddr->v1_host_present_timer) ? "v2, but still in IGMPv1"
: (IPCOM_TMO2_PENDING(&maddr->v2_host_present_timer) ? "v1, but still in IGMPv2"
: ""),
maddr->base.netif->ifname);
return IP_FALSE;
}
/*
*===========================================================================
* ipmcp_igmp_specific_query_tmo
*===========================================================================
* Description: Called when max response period passed since the last
* specific query on 'maddr'
* Parameters: maddr - A multicast address.
* Returns: IP_FALSE - The timeout should not be rescheduled.
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_specific_query_tmo(Ipmcp_igmp_maddr_t *maddr)
{
(void) maddr;
IPCOM_LOG1(DEBUG,
"IGMP: Specific query finished on %s since max response period passed",
maddr->base.netif->ifname);
return IP_FALSE;
}
/*
*===========================================================================
* ipmcp_igmp_other_querier_present_timeout
*===========================================================================
* Description: Returns the other querier present timeout in msec.
* Parameters: i - IGMP specific interface config
* Returns:
*
*/
IP_STATIC Ip_u32
ipmcp_igmp_other_querier_present_timeout(Ipmcp_igmp_netif_t *i)
{
/*
* RFC 3376
*
* 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).
*/
return i->robustness_variable * i->query_interval
+ ipmcp.config.query_response_interval / 2;
}
/*
*===========================================================================
* ipmcp_igmp_handle_query
*===========================================================================
* Description: Handles general or specific query from another multicast router.
* Parameters: netif - The interface the query was received at.
* query - The query message.
* src - The sender of the message.
* version - The version of the query
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_handle_query(Ipmcp_netif_t *netif,
Ipmcp_igmp_query_pkt_t *query,
struct Ip_in_addr *src,
int version)
{
Ipmcp_igmp_netif_t *i = &netif->igmp;
Ipmcp_igmp_maddr_t *maddr_obj;
IPCOM_LOG4(DEBUG, "IGMPv%d membership query received from %s group %s on %s",
version,
ipcom_inet_ntop(IP_AF_INET, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
ipcom_inet_ntop(IP_AF_INET, &query->group_address,
ipmcp.log_buf + IP_INET_ADDRSTRLEN,
sizeof(ipmcp.log_buf) - IP_INET_ADDRSTRLEN),
netif->ifname);
maddr_obj = ipcom_hash_get(netif->igmp.maddr_head, &query->group_address);
/*
* RFC 2236 states that this node should only consider query
* messages from routers with a numerically lower IP-address.
*/
if (ip_ntohl(i->primary_address.s_addr) <= ip_ntohl(src->s_addr))
/*
* This node has a numerically lower address.
*/
return;
if (i->state != IPMCP_LINK_STATE_NON_QUERIER)
{
/*
* else: RFC 3376 - 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 (maddr_obj != IP_NULL && IPCOM_TMO2_PENDING(&maddr_obj->specific_query_pending_timer))
/* Do not exit query state while specific query is pending */
return;
/* Enter non-querier state */
IPCOM_LOG1(NOTICE, "IGMP: Link state NON-QUERIER on %s", netif->ifname);
i->state = IPMCP_LINK_STATE_NON_QUERIER;
/* It would be enough just to remove this interface as an outgoing interface
from all multicast routes, but all routes are flushed for simplicity.
State transition to/from QUERIER should be uncommon.
RFC4605 chapter 3
...
When the proxy device receives a packet destined for a multicast
group (channel in Source-Specific Multicast (SSM)), it uses a list
consisting of the upstream interface and any downstream interface
that has a subscription pertaining to this packet and on which it is
the IGMP Querier.
...
*/
ipmcp_igmp_flush_all_mroutes();
}
if (version == 3)
{
/* RFC3376 - 4.1.6
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.
*/
if (IPMCP_IGMPV3_GET_QRV(query))
netif->igmp.robustness_variable = IPMCP_IGMPV3_GET_QRV(query);
else
netif->igmp.robustness_variable = ipmcp.config.robustness_variable;
/* RFC3376 - 4.1.7
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 [Query Interval]
value specified in section 8.2.
*/
if (query->qqic)
netif->igmp.query_interval = ipmcp_qqic_to_msec(query->qqic);
else
netif->igmp.query_interval = ipmcp.config.query_interval;
if (IP_BIT_ISFALSE(query->res_s_qrv, IPMCP_IGMPV3_QUERY_SUPRESS_BIT)
&& IP_NULL != maddr_obj)
{
/* 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. The
following table describes the timer actions when sending or receiving
a Group-Specific or Group-and-Source Specific Query with the Suppress
Router-Side Processing flag not set.
Query Action
----- ------
Q(G,A) Source Timer for sources in A are lowered to LMQT
Q(G) Group Timer is lowered to LMQT
*/
if (query->num_sources == 0)
maddr_obj->base.ops->set_group_timer(&maddr_obj->base, ipmcp_last_member_query_time());
else
{
Ipmcp_igmp_saddr_t *saddr_obj;
unsigned s;
unsigned num_sources = ip_ntohs(query->num_sources);
for (s = 0; s < num_sources; s++)
{
saddr_obj = ipcom_hash_get(maddr_obj->base.include_set,
&query->source_addresses[s]);
if (saddr_obj != IP_NULL)
saddr_obj->base.ops->set_timer(&saddr_obj->base, ipmcp_last_member_query_time());
}
}
}
}
(void)ipcom_tmo2_cancel(&i->timer);
(void)ipcom_tmo2_request(ipmcp.tmo_head,
&i->timer,
(Ipcom_tmo2_func)ipmcp_igmp_resume_querier_tmo,
netif,
ipmcp_igmp_other_querier_present_timeout(i));
}
/*
*===========================================================================
* ipmcp_igmp_handle_membership_report_rec
*===========================================================================
* Description: Handles v3 membership report record for an address.
* Parameters: netif - The interface the query was received at.
* rec - The membership report record.
* version - The original version, this could be a message that
* has been translated to its correspoding IGMPv3 record.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_handle_membership_report_rec(Ipmcp_netif_t *netif,
Ipmcp_igmp_group_rec_pkt_t *rec,
int version)
{
Ipmcp_igmp_maddr_t *maddr_obj;
Ip_bool new_entry = IP_FALSE;
Ipcom_tmo2 *compat_tmo = IP_NULL;
Ipmcp_igmp_rec_t rec_obj;
if (IP_IN_CLASSD(rec->group_address.s_addr) == IP_FALSE
|| ipmcp_igmp_is_maddr_link_local(&rec->group_address))
/* Never forward link local multicast addresses */
return;
maddr_obj = ipcom_hash_get(netif->igmp.maddr_head, &rec->group_address);
if (maddr_obj == IP_NULL)
{
/* Initialize a new address object */
maddr_obj = ipcom_calloc(1, sizeof(*maddr_obj));
if (maddr_obj == IP_NULL)
{
IPCOM_LOG0(ERR, "ipmcp_igmp_handle_membership_report_rec: ipcom_calloc failed");
IP_PANIC();
return;
}
maddr_obj->base.include_set =
ipcom_hash_new((Ipcom_hash_obj_func)ipmcp_igmp_saddr_obj_hash,
(Ipcom_hash_key_func)ipmcp_igmp_addr_key_hash,
(Ipcom_hash_cmp_func)ipmcp_igmp_saddr_compare);
maddr_obj->base.exclude_set =
ipcom_hash_new((Ipcom_hash_obj_func)ipmcp_igmp_saddr_obj_hash,
(Ipcom_hash_key_func)ipmcp_igmp_addr_key_hash,
(Ipcom_hash_cmp_func)ipmcp_igmp_saddr_compare);
if (maddr_obj->base.include_set == IP_NULL || maddr_obj->base.exclude_set == IP_NULL)
{
ipcom_hash_delete(maddr_obj->base.include_set);
ipcom_hash_delete(maddr_obj->base.exclude_set);
ipcom_free(maddr_obj);
IPCOM_LOG0(ERR, "ipmcp_igmp_handle_membership_report_rec: failed to create hash table");
IP_PANIC();
return;
}
maddr_obj->maddr = rec->group_address;
maddr_obj->base.netif = netif;
maddr_obj->base.mode_is_include = IP_TRUE;
maddr_obj->base.ops = &ipmcp.igmp.maddr_ops;
maddr_obj->base.domain = IP_AF_INET;
maddr_obj->base.addr_len = sizeof(struct Ip_in_addr);
IPCOM_TMO2_INIT(&maddr_obj->base.query_timer);
IPCOM_TMO2_INIT(&maddr_obj->base.group_timer);
IPCOM_TMO2_INIT(&maddr_obj->v1_host_present_timer);
IPCOM_TMO2_INIT(&maddr_obj->v2_host_present_timer);
IPCOM_TMO2_INIT(&maddr_obj->specific_query_pending_timer);
new_entry = IP_TRUE;
}
if (version == 1 || version == 2)
{
/* IGMPv1 or IPGMPv2 host report, start compatibility timer */
if (version == 1)
compat_tmo = &maddr_obj->v1_host_present_timer;
else
compat_tmo = &maddr_obj->v2_host_present_timer;
if (IPCOM_TMO2_PENDING(compat_tmo))
IPCOM_LOG2(INFO, "IGMP: Enter IGMPv%d compatibility mode on %s", version, netif->ifname);
(void)ipcom_tmo2_cancel(compat_tmo);
(void)ipcom_tmo2_request(ipmcp.tmo_head,
compat_tmo,
(Ipcom_tmo2_func)ipmcp_igmp_version_1_or_2_compat_tmo,
maddr_obj,
netif->igmp.robustness_variable
* netif->igmp.query_interval
+ ipmcp.config.query_response_interval);
}
rec_obj.base.ops = &ipmcp.igmp.rec_ops;
rec_obj.rec = rec;
if (ipmcp_handle_membership_report_rec(&maddr_obj->base, &rec_obj.base) != 0)
goto cleanup;
if (new_entry)
{
if (maddr_obj->base.mode_is_include == IP_TRUE
&& maddr_obj->base.include_set->elem == 0)
/* IS_IN, ALLOW, BLOCK or TO_IN with empty set, still no listeners */
goto cleanup;
if (ipcom_hash_add(netif->igmp.maddr_head, maddr_obj) != IPCOM_SUCCESS)
{
ipcom_free(maddr_obj);
IPCOM_LOG0(ERR, "ipmcp_igmp_handle_membership_report_rec: ipcom_hash_addr failed");
IP_PANIC();
return;
}
IPCOM_LOG2(DEBUG, "IGMP: Created entry for %s on %s",
ipcom_inet_ntop(IP_AF_INET, &maddr_obj->maddr, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
netif->ifname);
}
ipmcp_igmp_update_upstream_if_filter(maddr_obj);
return;
cleanup:
if (new_entry)
{
if (compat_tmo != IP_NULL)
(void)ipcom_tmo2_cancel(compat_tmo);
ipcom_free(maddr_obj);
}
}
/*
*===========================================================================
* ipmcp_igmp_handle_membership_report
*===========================================================================
* Description: Handles v1 and v2 membership report for an address.
* Parameters: netif - The interface the query was received at.
* report - The membership report message.
* version - IGMP protocol version for the report (1 or 2).
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_handle_membership_report(Ipmcp_netif_t *netif,
Ipmcp_igmp_query_pkt_t *report,
int version)
{
Ipmcp_igmp_group_rec_pkt_t rec;
ip_assert(version == 1 || version == 2);
/* RFC3376 - 7.3.2: Membership report should be processed as IS_EX( {} ) */
ipcom_memset(&rec, 0, sizeof(rec));
rec.type = IPMCP_GROUP_REC_TYPE_MODE_IS_EXCLUDE;
rec.group_address = report->group_address;
ipmcp_igmp_handle_membership_report_rec(netif, &rec, version);
}
/*
*===========================================================================
* ipmcp_igmp_handle_leave_group_v2
*===========================================================================
* Description: Handles membership report for an address.
* Parameters: netif - The interface the query was received at.
* report - The leave group message.
* Returns: 0 = success,
* <0 = error.
*
*/
IP_STATIC void
ipmcp_igmp_handle_leave_group_v2(Ipmcp_netif_t *netif, Ipmcp_igmp_query_pkt_t *report)
{
Ipmcp_igmp_group_rec_pkt_t rec;
/* RFC3376 - 7.3.2: Leave group should be processed as TO_IN( {} ) */
ipcom_memset(&rec, 0, sizeof(rec));
rec.type = IPMCP_GROUP_REC_TYPE_MODE_CHANGE_TO_INCLUDE_MODE;
rec.group_address = report->group_address;
ipmcp_igmp_handle_membership_report_rec(netif, &rec, 2);
}
/*
*===========================================================================
* ipmcp_igmp_mroute_input
*===========================================================================
* Description: Handles multicast routing events sent from the stack.
* Parameters: m - The message.
* Returns: 0 = success,
* <0 = error.
*
*/
IP_STATIC int
ipmcp_igmp_mroute_input(struct Ip_igmpmsg *m)
{
IPCOM_LOG2(DEBUG2, "IGMP: Got mroute message %s --> %s",
ipcom_inet_ntop(IP_AF_INET, &m->im_src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
ipcom_inet_ntop(IP_AF_INET, &m->im_dst, ipmcp.log_buf + IP_INET_ADDRSTRLEN, sizeof(ipmcp.log_buf)));
if (ipmcp.state != IPMCP_STATE_RUNNING)
/* Uplink interface not active */
return 0;
return ipmcp_igmp_add_mroute(m->im_vif, &m->im_dst, &m->im_src);
}
/*
*===========================================================================
* ipmcp_igmp_is_msg_ok
*===========================================================================
* Description: Basic sanity test on a received IGMP packet.
* Parameters: version - IGMP version the packet is using.
* tos - type-of-service on the packet
* has_ra - IP_TRUE if the packet included a router
* advertisment IP-option
* ucast_dst - IP_TRUE if the packet was sent to a
* unicast address
* netif - interface the packet was received on
* src - address of the sender.
* Returns: IP_TRUE if the packet passes basic sanity, IP_FALSE
* otherwise.
*
*/
IP_STATIC Ip_bool
ipmcp_igmp_is_msg_ok(int version,
Ip_u8 tos,
Ip_bool has_ra,
Ip_bool ucast_dst,
const Ipmcp_netif_t *netif,
const struct Ip_in_addr *src)
{
/* Later v2 specs require RA present in report */
if (has_ra == IP_FALSE)
{
IPCOM_LOG3(NOTICE,
"IGMPv%d membership report received from %s on %s missing Router Alert option",
version,
ipcom_inet_ntop(IP_AF_INET, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
netif->ifname);
/* Ignore this packet */
return IP_FALSE;
}
if (version == 3)
{
/*
* RFC 3376 page 7:
* 4. Message Formats
*
* IGMP messages are encapsulated in IPv4 datagrams, with an IP
* protocol number of 2. Every IGMP message described in this
* document is sent with an IP Time-to-Live of 1, IP Precedence of
* Internetwork Control (e.g., Type of Service 0xc0), and carries
* an IP Router Alert option [RFC-2113] in its IP header.
*/
if (tos != 0xc0)
{
IPCOM_LOG3(NOTICE,
"IGMPv3 membership report received from %s on %s with TOS=%#x, should be 0xc0",
ipcom_inet_ntop(IP_AF_INET, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
netif->ifname,
tos);
/* Ignore this packet */
return IP_FALSE;
}
/*
* IGMPv1/v2 allows for unicast reports, IGMPv3 does not.
*/
if (ucast_dst)
{
IPCOM_LOG2(NOTICE,
"IGMPv3 membership report received from %s on %s with a unicast destination",
ipcom_inet_ntop(IP_AF_INET, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
netif->ifname);
/* Ignore this packet */
return IP_FALSE;
}
}
return version <= ipcom_sysvar_get_as_int("ipmcp.igmp.CompatibilityMode", 3);
}
/*
*===========================================================================
* ipmcp_igmp_igmp_input
*===========================================================================
* Description: Handles multicast routing events sent from the stack.
* Parameters: netif - The interface the message was received at.
* igmp - The IGMP message
* igmp_len - The length of the IGMP message.
* src - The sender of the message.
* tos - value of the type-of-service field in the message
* router_alert - IP_TRUE if the received message
* contained a router alert option.
* ucast_dst - IP_TRUE if the packet was sent to a
* unicast destination.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_igmp_input(Ipmcp_netif_t *netif,
void *igmp,
int igmp_len,
struct Ip_in_addr *src,
Ip_u8 tos,
Ip_bool router_alert,
Ip_bool ucast_dst)
{
int v, num_rec;
Ipmcp_igmp_report_pkt_t *report = igmp;
Ipmcp_igmp_query_pkt_t *query = igmp;
Ipmcp_igmp_group_rec_pkt_t *next_rec;
switch (report->type)
{
case IPMCP_IGMP_TYPE_MEMBERSHIP_QUERY:
if (igmp_len == IPMCP_IGMP_MIN_PKT_LEN)
v = (query->max_resp_time ? 2 : 1);
else
{
if (igmp_len != 12 + ip_htons(query->num_sources) * 4)
/* Invalid query, ignore this message */
return;
v = 3;
}
if (ipmcp_igmp_is_msg_ok(v, tos, router_alert, ucast_dst, netif, src))
ipmcp_igmp_handle_query(netif, query, src, v);
break;
case IPMCP_IGMP_TYPE_MEMBERSHIP_REPORT:
ipmcp_igmp_handle_membership_report(netif, igmp, 1);
break;
case IPMCP_IGMP_TYPE_MEMBERSHIP_REPORT_V2:
if (ipmcp_igmp_is_msg_ok(2, tos, router_alert, ucast_dst, netif, src))
ipmcp_igmp_handle_membership_report(netif, igmp, 2);
break;
case IPMCP_IGMP_TYPE_LEAVE_GROUP_V2:
if(ucast_dst == IP_TRUE)
return ;/*RFC2236 P9 */
if (ipmcp_igmp_is_msg_ok(2, tos, router_alert, ucast_dst, netif, src))
ipmcp_igmp_handle_leave_group_v2(netif, igmp);
break;
case IPMCP_IGMP_TYPE_MEMBERSHIP_REPORT_V3:
if (ipmcp_igmp_is_msg_ok(3, tos, router_alert, ucast_dst, netif, src) == IP_FALSE)
return;
/* Size of mandatory message part */
igmp_len -= 8;
num_rec = ip_ntohs(report->num_group_rec);
next_rec = report->recs;
for (v = 0; v < num_rec; v++)
{
Ipmcp_igmp_group_rec_pkt_t *rec = next_rec;
int rec_len;
rec_len = 8 + (ip_ntohs(rec->num_sources) + rec->aux_data_len) * 4;
if (igmp_len < rec_len)
{
IPCOM_LOG2(NOTICE,
"IGMPv3 invalid membership report received from %s on %s",
ipcom_inet_ntop(IP_AF_INET, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
netif->ifname);
/* Ignore this packet */
return;
}
igmp_len -= rec_len;
next_rec = (Ipmcp_igmp_group_rec_pkt_t *) ((Ip_u8 *) rec + rec_len);
ipmcp_igmp_handle_membership_report_rec(netif, rec, 3);
}
break;
default:
IPCOM_LOG3(NOTICE, "IGMP unknown message type (%d) received from %s on %s",
report->type,
ipcom_inet_ntop(IP_AF_INET, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
netif->ifname);
break;
}
}
/*
*===========================================================================
* ipmcp_igmp_activate
*===========================================================================
* Description: Activates router activities on this interface.
* Parameters: netif - The interface we should start acting as a multicast router.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_activate(Ipmcp_netif_t *netif)
{
struct Ip_ip_mreq mreq;
if (netif->igmp.need_if_create
|| netif->igmp.primary_address.s_addr == IP_INADDR_ANY)
/* Stay in inactive mode until a primary address is set */
return;
/* Enter promiscuous mode on this interface */
ipcom_memset(&mreq, 0, sizeof(mreq));
mreq.imr_interface = netif->igmp.primary_address;
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_IP_JOIN_GROUP,
&mreq, sizeof(mreq)) < 0)
{
IPCOM_LOG2(ERR, "ipmcp_igmp_activate: setsockopt IPPROTO_IP, IP_JOIN_GROUP failed on %s: %s",
netif->ifname, ipcom_strerror(ipcom_errno));
IP_PANIC();
return;
}
/* Start sending general queries */
(void)ipmcp_igmp_resume_querier_tmo(netif);
}
/*
*===========================================================================
* ipmcp_igmp_deactivate
*===========================================================================
* Description: Dectivate IGMP server on specified interface.
* Parameters: .
* Returns: .
*
*/
IP_STATIC void
ipmcp_igmp_deactivate(Ipmcp_netif_t *netif)
{
struct Ip_ip_mreq mreq;
Ipmcp_igmp_netif_t *i = &netif->igmp;
/* Cancel downstream interface timer */
(void)ipcom_tmo2_cancel(&i->timer);
if (i->maddr_head != IP_NULL)
ipcom_hash_for_each(i->maddr_head,
(Ipcom_hash_foreach_cb_func)ipmcp_igmp_maddr_drop,
netif);
if (netif->igmp.state != IPMCP_LINK_STATE_INACTIVE
&& netif->igmp.primary_address.s_addr != IP_INADDR_ANY)
{
/* Leave promiscuous mode on this interface */
ipcom_memset(&mreq, 0, sizeof(mreq));
mreq.imr_interface = netif->igmp.primary_address;
(void)ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_IP_LEAVE_GROUP,
&mreq, sizeof(mreq));
}
/* Change state */
IPCOM_LOG1(NOTICE, "IGMP: Link state INACTIVE on %s", netif->ifname);
netif->igmp.state = IPMCP_LINK_STATE_INACTIVE;
}
/* ########### Ipmcp_igmp_rec_t methods ########### */
/*
*===========================================================================
* ipmcp_igmp_rec_get_type
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC Ip_u8
ipmcp_igmp_rec_get_type(Ipmcp_igmp_rec_t *self)
{
return self->rec->type;
}
/*
*===========================================================================
* ipmcp_igmp_rec_get_maddr
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void *
ipmcp_igmp_rec_get_maddr(Ipmcp_igmp_rec_t *self)
{
return &self->rec->group_address;
}
/*
*===========================================================================
* ipmcp_igmp_rec_get_num_saddrs
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC unsigned
ipmcp_igmp_rec_get_num_saddrs(Ipmcp_igmp_rec_t *self)
{
return ip_ntohs(self->rec->num_sources);
}
/*
*===========================================================================
* ipmcp_igmp_rec_get_saddr
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void *
ipmcp_igmp_rec_get_saddr(Ipmcp_igmp_rec_t *self, unsigned i)
{
ip_assert(ipmcp_igmp_rec_get_num_saddrs(self) > i);
return &self->rec->source_addresses[i];
}
/*
*===========================================================================
* ipmcp_igmp_rec_as_set
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC Ipcom_set *
ipmcp_igmp_rec_as_set(Ipmcp_igmp_rec_t *self)
{
Ipcom_set *set;
unsigned s;
unsigned num = ip_ntohs(self->rec->num_sources);
set = ipcom_set_new(sizeof(struct Ip_in_addr));
if (set == IP_NULL)
return IP_NULL;
for (s = 0; s < num; s++)
if (ipcom_set_add(set, &self->rec->source_addresses[s]) == IPCOM_ERR_NO_MEMORY)
{
IP_PANIC2();
ipcom_set_delete(set);
return IP_NULL;
}
return set;
}
/*
*===========================================================================
* ipmcp_igmp_rec_clear_source_list
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_rec_clear_source_list(Ipmcp_igmp_rec_t *self)
{
self->rec->num_sources = 0;
}
/* ########### Ipmcp_igmp_saddr_t methods ########### */
/*
*===========================================================================
* ipmcp_igmp_saddr_get_addr
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void *
ipmcp_igmp_saddr_get_addr(Ipmcp_igmp_saddr_t *self)
{
return &self->saddr;
}
/* ########### Ipmcp_igmp_maddr_t methods ########### */
/*
*===========================================================================
* ipmcp_igmp_maddr_delete
*===========================================================================
* Description: Drops a multicast address from a specified interface.
* Parameters: maddr_obj - The address to destroy.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_maddr_delete(Ipmcp_igmp_maddr_t *maddr_obj)
{
Ipmcp_netif_t *netif = maddr_obj->base.netif;
/* Remove from hash table */
(void)ipcom_hash_remove(netif->igmp.maddr_head, maddr_obj);
ipmcp_igmp_update_upstream_if_filter(maddr_obj);
/* Cancel timers */
(void)ipcom_tmo2_cancel(&maddr_obj->specific_query_pending_timer);
(void)ipcom_tmo2_cancel(&maddr_obj->v1_host_present_timer);
(void)ipcom_tmo2_cancel(&maddr_obj->v2_host_present_timer);
ipmcp_maddr_cleanup(&maddr_obj->base);
/* Free object */
ipcom_free(maddr_obj);
}
/*
*===========================================================================
* ipmcp_igmp_maddr_drop
*===========================================================================
* Description: Drops a multicast address from a specified interface.
* Parameters: maddr_obj - The address to destroy.
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_maddr_drop(Ipmcp_igmp_maddr_t *maddr_obj, void *unused)
{
(void) unused;
ipmcp_igmp_maddr_delete(maddr_obj);
}
/*
*===========================================================================
* ipmcp_igmp_maddr_get_addr
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void *
ipmcp_igmp_maddr_get_addr(Ipmcp_igmp_maddr_t *self)
{
return &self->maddr;
}
/*
*===========================================================================
* ipmcp_igmp_maddr_compatibility_mode
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC int
ipmcp_igmp_maddr_compatibility_mode(Ipmcp_igmp_maddr_t *self)
{
if (IPCOM_TMO2_PENDING(&self->v1_host_present_timer))
return IPMCP_MCAST_COMPAT_IGMPV1;
if (IPCOM_TMO2_PENDING(&self->v2_host_present_timer))
return IPMCP_MCAST_COMPAT_IGMPV2_MLDV1;
return IPMCP_MCAST_COMPAT_IGMPV3_MLDV2;
}
/*
*===========================================================================
* ipmcp_igmp_maddr_add_saddr
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC Ipmcp_igmp_saddr_t *
ipmcp_igmp_maddr_add_saddr(Ipmcp_maddr_t *self,
struct Ip_in_addr *saddr,
Ip_bool is_include_set)
{
Ipmcp_igmp_saddr_t *saddr_obj;
saddr_obj = ipcom_calloc(1, sizeof(*saddr_obj));
if(saddr_obj == IP_NULL)
{
return IP_NULL;
}
saddr_obj->base.ops = &ipmcp.igmp.saddr_ops;
saddr_obj->saddr = *saddr;
saddr_obj->base.maddr = self;
IPCOM_TMO2_INIT(&saddr_obj->base.timer);
if (ipcom_hash_add(is_include_set ? self->include_set : self->exclude_set,
saddr_obj) != IPCOM_SUCCESS)
{
ipcom_free(saddr_obj);
return IP_NULL;
}
return saddr_obj;
}
/*
*===========================================================================
* ipmcp_igmp_maddr_del_saddrs_not_in_rec_cb
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_maddr_del_saddrs_not_in_rec_cb(Ipmcp_igmp_saddr_t *saddr,
Ipmcp_igmp_rec_t *rec)
{
unsigned s, num = ip_ntohs(rec->rec->num_sources);
for (s = 0; s < num; s++)
if (rec->rec->source_addresses[s].s_addr == saddr->saddr.s_addr)
return;
saddr->base.ops->dtor(&saddr->base);
}
/*
*===========================================================================
* ipmcp_igmp_maddr_del_saddrs_not_in_rec
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_maddr_del_saddrs_not_in_rec(Ipmcp_igmp_maddr_t *self,
Ipmcp_igmp_rec_t *rec,
Ip_bool is_include_set)
{
ipcom_hash_for_each(is_include_set ? self->base.include_set : self->base.exclude_set,
(Ipcom_hash_foreach_cb_func) ipmcp_igmp_maddr_del_saddrs_not_in_rec_cb,
rec);
}
/*
*===========================================================================
* ipmcp_igmp_maddr_send_query
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC void
ipmcp_igmp_maddr_send_query(Ipmcp_igmp_maddr_t *self,
Ip_u32 max_response_interval_msec,
Ipcom_set *saddrs)
{
if (self->base.netif->igmp.state != IPMCP_LINK_STATE_QUERIER)
return;
(void)ipcom_tmo2_cancel(&self->specific_query_pending_timer);
(void)ipcom_tmo2_request(ipmcp.tmo_head,
&self->specific_query_pending_timer,
(Ipcom_tmo2_func)ipmcp_igmp_specific_query_tmo,
self,
max_response_interval_msec);
ipmcp_igmp_query_send(self->base.netif,
&self->maddr,
max_response_interval_msec,
(saddrs == IP_NULL
? self->base.query_suppress_router_side_processing
: self->base.source_query_suppress_router_side_processing),
saddrs);
}
/*
*===========================================================================
* ipmcp_igmp_maddr_group_membership_interval
*===========================================================================
* Description:
* Parameters:
* Returns:
*
*/
IP_STATIC Ip_u32
ipmcp_igmp_maddr_group_membership_interval(Ipmcp_maddr_t *self)
{
/* The Group Membership Interval is the amount of time that must pass
before a multicast router decides there are no more members of a
group or a particular source on a network.
This value MUST be ((the Robustness Variable) times (the Query
Interval)) plus (one Query Response Interval).
*/
return self->netif->igmp.robustness_variable * self->netif->igmp.query_interval
+ ipmcp.config.query_response_interval;
}
/*
****************************************************************************
* 10 GLOBAL FUNCTIONS
****************************************************************************
*/
/*
*===========================================================================
* ipmcp_igmp_input
*===========================================================================
* Description: Processes an incoming IGMP packet or multicast route packet.
* Parameters:
* Returns: 0 = success,
* <0 = error.
*
*/
IP_GLOBAL int
ipmcp_igmp_input(int fd)
{
struct Ip_msghdr m;
struct Ip_iovec iov;
struct Ip_in_pktinfo *pktinfo;
struct Ip_cmsghdr *c;
Ip_u8 cmsg_buf[IP_CMSG_SPACE(sizeof(struct Ip_in_pktinfo))];
int len;
Ipmcp_netif_t *netif;
Ipmcp_ip4_pkt_t *ip_hdr;
int ip_hdr_len, offset;
Ip_bool router_alert = IP_FALSE;
Ip_bool unicast_destination = IP_FALSE;
iov.iov_base = ipmcp.recv_buf;
iov.iov_len = sizeof(ipmcp.recv_buf);
m.msg_name = IP_NULL;
m.msg_namelen = 0;
m.msg_iov = &iov;
m.msg_iovlen = 1;
m.msg_control = cmsg_buf;
m.msg_controllen = sizeof(cmsg_buf);
m.msg_flags = 0;
len = ipcom_recvmsg(fd, &m, 0);
if (len < 0)
{
IPCOM_LOG1(ERR,"ipmcp_igmp_input: ipcom_recvmsg failed: %s",
ipcom_strerror(ipcom_errno));
return -1;
}
ip_hdr = iov.iov_base;
if (ip_hdr->p == 0)
{
if (len < (int) sizeof(struct Ip_igmpmsg))
{
/* This can only happen in the event of a stack bug or memory corruption */
IPCOM_LOG2(CRIT, "ipmcp_igmp_input: got MRT message smaller than %d, must be at least %d",
sizeof(struct Ip_igmpmsg), len);
return -1;
}
return ipmcp_igmp_mroute_input(iov.iov_base);
}
ip_assert(ip_hdr->p == IP_IPPROTO_IGMP);
ip_hdr_len = (ip_hdr->v_hl & 0x0f) << 2;
if (ip_hdr->ttl != 1 || len < ip_hdr_len + IPMCP_IGMP_MIN_PKT_LEN)
{
IPCOM_LOG0(NOTICE, "Invalid IGMP packet detected");
return 0;
}
offset = 20;
while (offset < ip_hdr_len)
{
Ipmcp_pkt_ip_opt *opt = (Ipmcp_pkt_ip_opt *) ((Ip_u8*) ip_hdr + offset);
if (opt->flag_class_num == IP_IPOPT_END)
break;
else if (opt->flag_class_num == IP_IPOPT_RA)
{
router_alert = IP_TRUE;
break;
}
else if (opt->flag_class_num == IP_IPOPT_NOOP)
{
offset++;
continue;
}
offset += opt->len;
}
c = IP_CMSG_FIRSTHDR(&m);
if (c == IP_NULL
|| c->cmsg_level != IP_IPPROTO_IP
|| c->cmsg_type != IP_IP_PKTINFO)
{
IPCOM_LOG0(ERR,"ipmcp_igmp_input: failed to get pktinfo");
return -1;
}
pktinfo = IP_CMSG_DATA(c);
IPCOM_LOG3(DEBUG2, "IGMP message received from %s to %s on ifindex=%u",
ipcom_inet_ntop(IP_AF_INET, ip_hdr->src, ipmcp.log_buf, sizeof(ipmcp.log_buf)),
ipcom_inet_ntop(IP_AF_INET, ip_hdr->dst,
ipmcp.log_buf + IP_INET_ADDRSTRLEN,
sizeof(ipmcp.log_buf) - IP_INET_ADDRSTRLEN),
pktinfo->ipi_ifindex);
netif = ipmcp_if_find(pktinfo->ipi_ifindex);
if (netif == IP_NULL || netif->igmp.state == IPMCP_LINK_STATE_INACTIVE)
{
IPCOM_LOG2(DEBUG, "ipmcp_igmp_input: no router operation defined for interface id %u %s, dropping message",
pktinfo->ipi_ifindex, netif != IP_NULL ? netif->ifname : "");
return 0;
}
if (netif->igmp.primary_address.s_addr == ((struct Ip_in_addr *) ip_hdr->src)->s_addr)
/* Ignore messages sent from this host */
return 0;
/* If not multicast, verify if its destined to a known address on the receiving interface */
if (!IP_IN_CLASSD(pktinfo->ipi_addr.s_addr))
{
struct Ip_ifaddrs *ifaddr;
struct Ip_ifaddrs *ifaddrs;
ipcom_getifaddrs(&ifaddrs);
for (ifaddr = ifaddrs; ifaddr != IP_NULL; ifaddr = ifaddr->ifa_next)
{
if (ifaddr->ifa_addr->sa_family == IP_AF_INET && ipcom_if_nametoindex(ifaddr->ifa_name) == pktinfo->ipi_ifindex)
{
struct Ip_sockaddr_in *s_in = (struct Ip_sockaddr_in *) (ifaddr->ifa_addr);
if (s_in->sin_addr.s_addr == pktinfo->ipi_addr.s_addr)
break;
}
}
ipcom_freeifaddrs(ifaddrs);
/* Unicast address on this interface? */
if (ifaddr == IP_NULL)
return 0;
/* We've got a unicast destination */
unicast_destination = IP_TRUE;
}
ipmcp_igmp_igmp_input(netif,
((Ip_u8 *)ip_hdr + ip_hdr_len),
len - ip_hdr_len,
(struct Ip_in_addr *) ip_hdr->src,
ip_hdr->tos,
router_alert,
unicast_destination);
return 0;
}
/*
*===========================================================================
* ipmcp_igmp_addr_change
*===========================================================================
* Description: An address was added or removed.
* Parameters:
* Returns:
*
*/
IP_GLOBAL void
ipmcp_igmp_addr_change(Ipmcp_netif_t *netif, Ip_bool add)
{
Ipmcp_igmp_netif_t *i = &netif->igmp;
if (add)
{
/* Always update link-local address */
if (ipmcp_igmp_get_primary_addr(netif->ifindex, &i->primary_address) == 0)
if (netif->igmp.state == IPMCP_LINK_STATE_INACTIVE)
{
/* Our link local has now been set. Activate! */
if (netif->igmp.need_if_create)
(void)ipmcp_igmp_if_create(netif);
ipmcp_igmp_activate(netif);
}
}
else
{
if (i->state != IPMCP_LINK_STATE_INACTIVE
&& ipmcp_igmp_get_primary_addr(netif->ifindex, &i->primary_address) != 0)
/* Our link-local has been removed. Deactivate! */
ipmcp_igmp_deactivate(netif);
}
}
/*
*===========================================================================
* ipmcp_igmp_if_create
*===========================================================================
* Description: Creates the IGMP part of an interface.
* Parameters:
* Returns:
*
*/
IP_GLOBAL int
ipmcp_igmp_if_create(Ipmcp_netif_t *netif)
{
struct Ip_vifctl vifctl;
Ipmcp_igmp_data_t *d = &ipmcp.igmp;
Ipmcp_igmp_netif_t *i = &netif->igmp;
int ret;
int x;
i->need_if_create = IP_TRUE;
ret = ipmcp_igmp_get_primary_addr(netif->ifindex, &i->primary_address);
if (ret < 0 || netif->igmp.primary_address.s_addr == IP_INADDR_ANY)
/* Finish creation of this interface when/if a primary address is set */
return 0;
for (x = 0; x < IP_MAXVIFS; x++)
{
d->last_vifi = (Ip_vifi_t) ((d->last_vifi + 1) % IP_MAXVIFS);
i->vif_index = d->last_vifi;
/* Register VIF in forwarding engine */
ipcom_memset(&vifctl, 0, sizeof(vifctl));
vifctl.vifc_vifi = i->vif_index;
vifctl.vifc_lcl_addr = i->primary_address;
if (ipcom_setsockopt(d->fd,
IP_IPPROTO_IP,
IP_MRT_ADD_VIF,
&vifctl,
sizeof(vifctl)) >= 0)
break;
}
if (x == IP_MAXVIFS)
{
IPCOM_LOG1(ERR,"ipcom_setsockopt IP_MRT_ADD_VIF: %s",
ipcom_strerror(ipcom_errno));
return -1;
}
IPCOM_TMO2_INIT(&i->timer);
/* Create multicast address database on this interface */
i->maddr_head = ipcom_hash_new((Ipcom_hash_obj_func)ipmcp_igmp_maddr_obj_hash,
(Ipcom_hash_key_func)ipmcp_igmp_addr_key_hash,
(Ipcom_hash_cmp_func)ipmcp_igmp_maddr_compare);
i->need_if_create = IP_FALSE;
ipmcp_igmp_activate(netif);
return 0;
}
/*
*===========================================================================
* ipmcp_igmp_if_destroy
*===========================================================================
* Description: Destroy an interface descriptor for IGMP
* Parameters:
* Returns:
*
*/
IP_GLOBAL void
ipmcp_igmp_if_destroy(Ipmcp_netif_t *netif)
{
struct Ip_vifctl vifctl;
ipmcp_igmp_deactivate(netif);
ipcom_hash_delete(netif->igmp.maddr_head);
/* Unregister VIF in forwarding engine */
ipcom_memset(&vifctl, 0, sizeof(vifctl));
vifctl.vifc_vifi = netif->igmp.vif_index;
vifctl.vifc_lcl_addr = netif->igmp.primary_address;
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_MRT_DEL_VIF,
&vifctl,
sizeof(vifctl)) < 0)
{
IPCOM_LOG1(ERR,"ipcom_setsockopt IP_MRT_DEL_VIF: %s", ipcom_strerror(ipcom_errno));
}
}
/*
*===========================================================================
* ipmcp_igmp_upstream_if_create
*===========================================================================
* Description: Creates the IGMP part of the upstream interface.
* Parameters:
* Returns:
*
*/
IP_GLOBAL int
ipmcp_igmp_upstream_if_create(Ipmcp_upstream_netif_t *netif)
{
struct Ip_vifctl vifctl;
int var;
if (ipmcp_igmp_get_primary_addr(netif->ifindex, &netif->igmp.primary_address) < 0
|| netif->igmp.primary_address.s_addr == IP_INADDR_ANY)
{
IPCOM_LOG0(WARNING, "IGMP: No primary address set on the uplink interface,"
"IPv4 multicast forwarning disabled");
return 0;
}
/* Create multicast address database on this interface */
netif->igmp.maddr_head =
ipcom_hash_new((Ipcom_hash_obj_func)ipmcp_igmp_upstream_maddr_obj_hash,
(Ipcom_hash_key_func)ipmcp_igmp_addr_key_hash,
(Ipcom_hash_cmp_func)ipmcp_igmp_upstream_maddr_compare);
if (netif->igmp.maddr_head == IP_NULL)
{
IPCOM_LOG0(ERR, "ipmcp_igmp_upstream_if_create: failed to create hash table");
return -1;
}
/* Use this as multicast routing socket */
var = ipcom_setsockopt(ipmcp.igmp.fd, IP_IPPROTO_IP, IP_MRT_INIT, &var, sizeof(var));
if (var < 0)
{
IPCOM_LOG1(ERR,"ipmcp_igmp_upstream_if_create: ipcom_setsockopt IP_MRT_INIT: %s", ipcom_strerror(ipcom_errno));
IP_PANIC();
return -1;
}
netif->igmp.vif_index = 1;
ipmcp.igmp.last_vifi = 1;
/* Register VIF in forwarding engine */
ipcom_memset(&vifctl, 0, sizeof(vifctl));
vifctl.vifc_vifi = netif->igmp.vif_index;
vifctl.vifc_lcl_addr = netif->igmp.primary_address;
if (ipcom_setsockopt(ipmcp.igmp.fd, IP_IPPROTO_IP,
IP_MRT_ADD_VIF, &vifctl, sizeof(vifctl)) < 0)
{
IPCOM_LOG1(ERR,"ipmcp_igmp_upstream_if_create: ipcom_setsockopt IP_MRT_ADD_VIF: %s",
ipcom_strerror(ipcom_errno));
return -1;
}
return 0;
}
/*
*===========================================================================
* ipmcp_igmp_upstream_if_destroy
*===========================================================================
* Description:Destroy an IPv4 upstream interface.
* Parameters:
* Returns:
*
*/
IP_GLOBAL void
ipmcp_igmp_upstream_if_destroy(Ipmcp_upstream_netif_t *netif)
{
if (netif->igmp.maddr_head != IP_NULL)
{
struct Ip_vifctl vifctl;
int i;
/* Unregister VIF in forwarding engine */
ipcom_memset(&vifctl, 0, sizeof(vifctl));
vifctl.vifc_vifi = netif->igmp.vif_index;
vifctl.vifc_lcl_addr = netif->igmp.primary_address;
if (ipcom_setsockopt(ipmcp.igmp.fd,
IP_IPPROTO_IP,
IP_MRT_DEL_VIF,
&vifctl,
sizeof(vifctl)) < 0)
{
IPCOM_LOG1(ERR,"ipcom_setsockopt IP_MRT_DEL_VIF: %s", ipcom_strerror(ipcom_errno));
}
/* Destroy multicast elements in hash table */
ipcom_hash_for_each(netif->igmp.maddr_head,
(Ipcom_hash_foreach_cb_func)ipmcp_igmp_upstream_maddr_delete_cb,
IP_NULL);
ipcom_hash_delete(netif->igmp.maddr_head);
i = 1;
i = ipcom_setsockopt(ipmcp.igmp.fd, IP_IPPROTO_IP, IP_MRT_DONE, &i, sizeof(i));
if (i < 0)
{
IPCOM_LOG1(ERR, "ipmcp_igmp_exit: ipcom_setsockopt IP_MRT_DONE: %s", ipcom_strerror(ipcom_errno));
}
}
ipcom_memset(&netif->igmp, 0, sizeof(netif->igmp));
}
/*
*===========================================================================
* ipmcp_igmp_init
*===========================================================================
* Description: Initializes IGMP specific global data.
* Parameters:
* Returns:
*
*/
IP_GLOBAL int
ipmcp_igmp_init(void)
{
Ipmcp_igmp_data_t *d = &ipmcp.igmp;
int var;
int tos;
Ip_u8 ra_opt;
(void)ipcom_inet_pton(IP_AF_INET, "224.0.0.1", &d->all_hosts_addr);
d->fd = ipcom_socket(IP_AF_INET, IP_SOCK_RAW, IP_IPPROTO_IGMP);
if (d->fd == IP_INVALID_SOCKET)
{
IPCOM_LOG1(ERR, "ipmcp_igmp_init: ipcom_socket(AF_INET,RAW,IPPROTO_IGMP) failed: %s",
ipcom_strerror(ipcom_errno));
IP_PANIC();
return -1;
}
/* Packet info is needed to know which interface a packet was received on */
var = 1;
var = ipcom_setsockopt(d->fd, IP_IPPROTO_IP, IP_IP_PKTINFO, &var, sizeof(var));
if (var < 0)
{
IPCOM_LOG1(ERR,"ipmcp_igmp_init: ipcom_setsockopt IP_IP_PKTINFO: %s", ipcom_strerror(ipcom_errno));
IP_PANIC();
return -1;
}
/*
* RFC 3376 page 7:
* 4. Message Formats
*
* IGMP messages are encapsulated in IPv4 datagrams, with an IP
* protocol number of 2. Every IGMP message described in this
* document is sent with an IP Time-to-Live of 1, IP Precedence of
* Internetwork Control (e.g., Type of Service 0xc0), and carries
* an IP Router Alert option [RFC-2113] in its IP header.
*/
tos = 0xc0;
var = ipcom_setsockopt(d->fd, IP_IPPROTO_IP, IP_IP_TOS, &tos, sizeof(tos));
if (var < 0)
{
IPCOM_LOG1(ERR,"ipmcp_igmp_init: ipcom_setsockopt(IIP_TOS): %s", ipcom_strerror(ipcom_errno));
IP_PANIC();
return -1;
}
ra_opt = 1;
var = ipcom_setsockopt(d->fd, IP_IPPROTO_IP, IP_IP_ROUTER_ALERT, &ra_opt, sizeof(ra_opt));
if (var < 0)
{
IPCOM_LOG1(ERR,"ipmcp_igmp_init: ipcom_setsockopt(IIP_TOS): %s", ipcom_strerror(ipcom_errno));
IP_PANIC();
return -1;
}
/* Create the call table for the Ipmcp_igmp_rec_t class */
d->rec_ops.get_type = (Ip_u8(*)(RTHIS)) ipmcp_igmp_rec_get_type;
d->rec_ops.get_maddr = (void*(*)(RTHIS)) ipmcp_igmp_rec_get_maddr;
d->rec_ops.get_num_saddrs = (unsigned(*)(RTHIS)) ipmcp_igmp_rec_get_num_saddrs;
d->rec_ops.get_saddr = (void*(*)(RTHIS, unsigned)) ipmcp_igmp_rec_get_saddr;
d->rec_ops.as_set = (Ipcom_set*(*)(RTHIS)) ipmcp_igmp_rec_as_set;
d->rec_ops.clear_source_list = (void(*)(RTHIS)) ipmcp_igmp_rec_clear_source_list;
/* Create the call table for the Ipmcp_igmp_saddr_t class */
ipmcp_saddr_ops_init(&d->saddr_ops);
d->saddr_ops.get_addr = (void*(*)(STHIS)) ipmcp_igmp_saddr_get_addr;
/* Create the call table for the Ipmcp_igmp_maddr_t class */
ipmcp_maddr_ops_init(&d->maddr_ops);
d->maddr_ops.dtor = (void(*)(MTHIS)) ipmcp_igmp_maddr_delete;
d->maddr_ops.get_addr = (void*(*)(MTHIS)) ipmcp_igmp_maddr_get_addr;
d->maddr_ops.compatibility_mode = (int(*)(MTHIS)) ipmcp_igmp_maddr_compatibility_mode;
d->maddr_ops.add_saddr = (Ipmcp_saddr_t*(*)(MTHIS, IP_CONST void*, Ip_bool)) ipmcp_igmp_maddr_add_saddr;
d->maddr_ops.del_saddrs_not_in_rec = (void(*)(MTHIS, Ipmcp_rec_t*, Ip_bool)) ipmcp_igmp_maddr_del_saddrs_not_in_rec;
d->maddr_ops.send_query = (void(*)(MTHIS, Ip_u32, Ipcom_set*)) ipmcp_igmp_maddr_send_query;
d->maddr_ops.group_membership_interval = (Ip_u32(*)(MTHIS)) ipmcp_igmp_maddr_group_membership_interval;
d->maddr_ops.update_upstream_if_filter = (void(*)(MTHIS)) ipmcp_igmp_update_upstream_if_filter;
/* Create the call table for the Ipmcp_igmp_upstream_maddr_t class */
d->upstream_maddr_ops.dtor = (void(*)(UTHIS))ipmcp_igmp_upstream_maddr_delete;
d->upstream_maddr_ops.get_addr = (void*(*)(UTHIS)) ipmcp_igmp_upstream_maddr_get_addr;
d->upstream_maddr_ops.change_mode = (void(*)(UTHIS, Ip_bool)) ipmcp_igmp_upstream_maddr_change_mode;
d->upstream_maddr_ops.add_source = (void(*)(UTHIS, void*)) ipmcp_igmp_upstream_maddr_add_source;
d->upstream_maddr_ops.del_source = (void(*)(UTHIS, void*)) ipmcp_igmp_upstream_maddr_del_source;
return 0;
}
/*
*===========================================================================
* ipmcp_igmp_exit
*===========================================================================
* Description: Frees all resources held by the IGMP module.
* Parameters:
* Returns:
*
*/
IP_GLOBAL void
ipmcp_igmp_exit(void)
{
(void)ipcom_socketclose(ipmcp.igmp.fd);
ipmcp.igmp.fd = IP_INVALID_SOCKET;
}
/*
****************************************************************************
* 11 PUBLIC FUNCTIONS
****************************************************************************
*/
#endif /* IPMCP_USE_IGMP */
/*
****************************************************************************
* END OF FILE
****************************************************************************
*/