/* ****************************************************************************** * INTERPEAK SOURCE FILE * * Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipmcp_mld.c,v $ $Revision: 1.36.10.6 $ * $Source: /home/interpeak/CVSRoot/ipmcp/src/ipmcp_mld.c,v $ * $Author: svc-cmnet $ * $State: Exp $ $Locker: $ * * INTERPEAK_COPYRIGHT_STRING * Design and implementation by Kenneth Jonsson ****************************************************************************** */ /* **************************************************************************** * 1 DESCRIPTION **************************************************************************** */ /* **************************************************************************** * 2 CONFIGURATION **************************************************************************** */ #include "ipmcp_config.h" /* **************************************************************************** * 3 INCLUDE FILES **************************************************************************** */ #ifdef IPMCP_USE_MLD #define IPCOM_USE_CLIB_PROTO #include #include #include #include #include #include #include #include #include #include "ipmcp_h.h" /* **************************************************************************** * 4 DEFINES **************************************************************************** */ #define IPMCP_MLD_IS_LINK_LOCAL_MCAST_ADDR(maddr) ((maddr)->in6.addr8[1] == 0x02) /* Multicast Listener Discovery Definitions */ #define IPMCP_MLD_LISTENER_QUERY 130 #define IPMCP_MLD_LISTENER_REPORT 131 #define IPMCP_MLD_LISTENER_REDUCTION 132 #define IPMCP_MLD_LISTENER_REPORT_V2 143 #define IPMCP_MLD_MIN_PKT_LEN 24 #define IPMCP_ALIGNED_SIZE(size, align) ((align) * (((size) + (align) - 1) / (align))) /* **************************************************************************** * 5 TYPES **************************************************************************** */ /* *=========================================================================== * MLD header *=========================================================================== */ #include struct IP_PACKED1 Ipmcp_icmp6_hdr { Ip_u8 icmp6_type; /* type field */ Ip_u8 icmp6_code; /* code field */ Ip_u16 icmp6_cksum; /* checksum field */ union IP_PACKED1 { Ip_u32 icmp6_un_data32[1]; /* type-specific field */ Ip_u16 icmp6_un_data16[2]; /* type-specific field */ Ip_u8 icmp6_un_data8[4]; /* type-specific field */ } IP_PACKED2 icmp6_dataun; } IP_PACKED2; #define ipmcp_mld_type icmp6.icmp6_type #define ipmcp_mld_code icmp6.icmp6_code #define ipmcp_mld_cksum icmp6.icmp6_cksum #define ipmcp_mld_maxdelay icmp6.icmp6_dataun.icmp6_un_data16[0] #define ipmcp_mld_reserved icmp6.icmp6_dataun.icmp6_un_data16[1] typedef IP_PACKED1 struct Ipmcp_mld_hdr { struct Ipmcp_icmp6_hdr icmp6; struct Ip_in6_addr group_address; /* multicast address */ } IP_PACKED2 Ipmcp_mld_pkt_t; typedef IP_PACKED1 struct Ipmcp_mld_query_pkt_struct { struct Ipmcp_icmp6_hdr icmp6; struct Ip_in6_addr group_address; 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_in6_addr source_addresses[1]; /* Multicast source addresses */ } IP_PACKED2 Ipmcp_mld_query_pkt_t; #define IPMCP_MLDV2_QUERY_SUPRESS_BIT 0x08 #define IPMCP_MLDV2_GET_QRV(query) ((query)->res_s_qrv & 0x07) typedef IP_PACKED1 struct Ipmcp_mld_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_in6_addr group_address; /* IPv4 multicast address */ struct Ip_in6_addr source_addresses[1]; /* Multicast source addresses */ } IP_PACKED2 Ipmcp_mld_group_rec_pkt_t; typedef IP_PACKED1 struct Ipmcp_mld_report_pkt_struct { Ip_u8 type; /* 143 */ Ip_u8 reserved; Ip_u16 checksum; /* Internet checksum */ Ip_u16 reserved2; Ip_u16 num_group_rec; /* Number of group records */ Ipmcp_mld_group_rec_pkt_t recs[1]; } IP_PACKED2 Ipmcp_mld_report_pkt_t; #include /* *=========================================================================== * Ipmcp_mld_rec_t *=========================================================================== * MLDv2 record class */ typedef struct Ipmcp_mld_rec_struct { Ipmcp_rec_t base; /* Base class */ Ipmcp_mld_group_rec_pkt_t *rec; /* Pointer to an MLDv2 record */ } Ipmcp_mld_rec_t; /* *=========================================================================== * Ipmcp_mld_saddr_struct *=========================================================================== * IPv6 multicast source address descriptor for a downstream interface. */ typedef struct Ipmcp_mld_saddr_struct { Ipmcp_saddr_t base; /* Base class */ struct Ip_in6_addr saddr; /* Source address */ } Ipmcp_mld_saddr_t; /* **************************************************************************** * 6 EXTERN PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 7 LOCAL PROTOTYPES **************************************************************************** */ IP_STATIC void ipmcp_mld_maddr_drop(Ipmcp_mld_maddr_t *self, void *unused); /* **************************************************************************** * 8 DATA **************************************************************************** */ /* **************************************************************************** * 9 STATIC FUNCTIONS **************************************************************************** * */ /* *=========================================================================== * ipmcp_mld_set_sock_addr *=========================================================================== * Description: Sets a sockets address from a inet6 address. * Parameters: * Returns: * */ IP_STATIC void ipmcp_mld_set_sock_addr(struct Ip_sockaddr_storage *s, struct Ip_in6_addr *a) { struct Ip_sockaddr_in6 *i = (struct Ip_sockaddr_in6 *)s; ipcom_memset(i, 0, sizeof(*i)); i->sin6_family = IP_AF_INET6; IPCOM_SA_LEN_SET(i, sizeof(*i)); i->sin6_addr = *a; } /* *=========================================================================== * ipmcp_mld_set_addr *=========================================================================== * Description: Sets a sockets address from a inet6 address. * Parameters: * Returns: * */ IP_STATIC void ipmcp_mld_set_addr(struct Ip_sockaddr_in6 *s, struct Ip_in6_addr *a) { ipmcp_mld_set_sock_addr((struct Ip_sockaddr_storage *) s, a); } /* *=========================================================================== * ipmcp_mld_addr_key_hash *=========================================================================== * Description: Returns a hash value for an IPv6 address. * Parameters: * Returns: * */ IP_STATIC unsigned ipmcp_mld_addr_key_hash(struct Ip_in6_addr *addr) { return ipcom_hash_update(addr, sizeof(*addr), 0); } /* *=========================================================================== * ipmcp_mld_saddr_obj_hash *=========================================================================== * Description: Returns a hash value for the multicast source address. * Parameters: * Returns: * */ IP_STATIC unsigned ipmcp_mld_saddr_obj_hash(Ipmcp_mld_saddr_t *obj) { return ipcom_hash_update(&obj->saddr, sizeof(obj->saddr), 0); } /* *=========================================================================== * ipmcp_mld_saddr_compare *=========================================================================== * Description: Compares a MCP multicast source address entry with a IPv6 address. * Parameters: * Returns: * */ IP_STATIC Ip_bool ipmcp_mld_saddr_compare(Ipmcp_mld_saddr_t *obj , struct Ip_in6_addr *saddr) { return IP_IN6_ARE_ADDR_EQUAL(&obj->saddr, saddr); } /* *=========================================================================== * ipmcp_mld_maddr_obj_hash *=========================================================================== * Description: Returns a hash value for the multicast address. * Parameters: * Returns: * */ IP_STATIC unsigned ipmcp_mld_maddr_obj_hash(Ipmcp_mld_maddr_t *obj) { return ipcom_hash_update(&obj->maddr, sizeof(obj->maddr), 0); } /* *=========================================================================== * ipmcp_mld_maddr_compare *=========================================================================== * Description: Compares two IPv6 addresses. * Parameters: * Returns: * */ IP_STATIC Ip_bool ipmcp_mld_maddr_compare(Ipmcp_mld_maddr_t *obj , struct Ip_in6_addr *maddr) { return IP_IN6_ARE_ADDR_EQUAL(&obj->maddr, maddr); } /* *=========================================================================== * ipmcp_mld_upstream_maddr_obj_hash *=========================================================================== * Description: Returns a hash value for a upstream multicast address. * Parameters: * Returns: * */ IP_STATIC unsigned ipmcp_mld_upstream_maddr_obj_hash(Ipmcp_mld_upstream_maddr_t *obj) { return ipcom_hash_update(&obj->maddr, sizeof(obj->maddr), 0); } /* *=========================================================================== * ipmcp_mld_upstream_maddr_compare *=========================================================================== * Description: Compares an upstream multicast address entry with a IPv6 address. * Parameters: * Returns: * */ IP_STATIC Ip_bool ipmcp_mld_upstream_maddr_compare(Ipmcp_mld_upstream_maddr_t *obj , struct Ip_in6_addr *maddr) { return IP_IN6_ARE_ADDR_EQUAL(&obj->maddr, maddr); } /* *=========================================================================== * ipmcp_mld_get_link_local_addr *=========================================================================== * Description: Finds out the link local address of an specified interface * Parameters: ifindex - * link_local_addr - Buffer where the link local address will * be stored. * Returns: IPMCP_LINK_LOCAL_ADR_TENTATIVE = link local address exist but is tentative * IPMCP_LINK_LOCAL_ADR_AVAIL = success, link local address exist * IPMCP_LINK_LOCAL_ADR_NOTAVAIL = address could not be returned * */ IP_STATIC int ipmcp_mld_get_link_local_addr(Ip_u32 ifindex, struct Ip_in6_addr *link_local_addr) { int name[6]; int ret = IPMCP_LINK_LOCAL_ADR_NOTAVAIL; Ip_u8 *if_data; struct Ipnet_ifa_msghdr *ifa; struct Ipnet_if_msghdr *ifm; struct Ip_sockaddr_in6 *addrs[IPNET_RTAX_MAX]; struct Ip_sockaddr_in6 *myaddr = IP_NULL; Ip_size_t if_data_len; Ip_size_t if_data_offset; Ip_size_t offset; /* Find out our link local address on this interface */ name[0] = IP_CTL_NET; name[1] = IP_AF_ROUTE; name[2] = 0; name[3] = IP_AF_INET6; name[4] = IP_NET_RT_IFLIST; name[5] = ifindex; if (ipcom_sysctl(name, 6, IP_NULL, &if_data_len, IP_NULL, 0) < 0) { IPCOM_LOG1(ERR, "ipmcp_mld_get_link_local_addr: ipcom_sysctl() failed: %s", ipcom_strerror(ipcom_errno)); return IPMCP_LINK_LOCAL_ADR_NOTAVAIL; } if_data = ipcom_malloc(if_data_len); if (if_data == IP_NULL) { IPCOM_LOG0(ERR, "ipmcp_mld_get_link_local_addr: out of memory"); return IPMCP_LINK_LOCAL_ADR_NOTAVAIL; } if (ipcom_sysctl(name, 6, if_data, &if_data_len, IP_NULL, 0) < 0) { IPCOM_LOG1(ERR, "ipmcp_mld_get_link_local_addr: ipcom_sysctl() failed: %s", ipcom_strerror(ipcom_errno)); ret = IPMCP_LINK_LOCAL_ADR_NOTAVAIL; goto cleanup; } ifm = (struct Ipnet_if_msghdr *) &if_data[0]; if_data_offset = ifm->ifm_msglen; offset = 0; while (offset < if_data_len - if_data_offset) { ifa = (struct Ipnet_ifa_msghdr *) &if_data[if_data_offset + offset]; ipmcp_route_init_addrs(ifa + 1, ifa->ifam_addrs, (struct Ip_sockaddr **) addrs); offset += ifa->ifam_msglen; if (IP_IN6_IS_ADDR_LINK_LOCAL(&addrs[IPNET_RTAX_IFA]->sin6_addr)) { /* Found link local addr of this interface */ myaddr = addrs[IPNET_RTAX_IFA]; /* Verify that the link local address isn't still tentative */ struct Ip_in6_aliasreq ifareq; ipcom_memset(&ifareq, 0, sizeof(ifareq)); if (ipcom_if_indextoname(ifa->ifam_index, ifareq.ifra_name) == IP_NULL) { IPCOM_LOG1(ERR, "ipmcp_mld_get_link_local_addr: Could not get interface name for index %d", ifa->ifam_index); goto cleanup; } ipcom_memcpy(&ifareq.ifra_addr, addrs[IPNET_RTAX_IFA], sizeof(ifareq.ifra_addr)); (void)ipcom_socketioctl(ipmcp.ipd_fd, IP_SIOCXGIFADDR_IN6, &ifareq); if (IP_BIT_ISSET(ifareq.ifra_flags, IP_IN6_IFF_TENTATIVE)) { IPCOM_LOG1(DEBUG, "ipmcp_mld_get_link_local_addr: link-local address still tentative on interface %s", ifareq.ifra_name); ret = IPMCP_LINK_LOCAL_ADR_TENTATIVE; } else { ret = IPMCP_LINK_LOCAL_ADR_AVAIL; } break; } } if (myaddr == IP_NULL) { IPCOM_LOG0(INFO, "MLD: no link-local address found on interface"); goto cleanup; } ipcom_memcpy(link_local_addr, &myaddr->sin6_addr, sizeof(struct Ip_in6_addr)); cleanup: ipcom_free(if_data); return ret; } /* *=========================================================================== * ipmcp_mld_upstream_maddr_get_addr *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void * ipmcp_mld_upstream_maddr_get_addr(Ipmcp_mld_upstream_maddr_t *maddr) { return &maddr->maddr; } /* *=========================================================================== * ipmcp_mld_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_mld_upstream_maddr_change_mode(Ipmcp_mld_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_mld_set_sock_addr(&req.gr_group, &maddr->maddr); (void) ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, 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.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MCAST_JOIN_GROUP, &req, sizeof(req)) < 0) { IPCOM_LOG2(ERR, "ipmcp_mld_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_mld_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_mld_upstream_maddr_add_source(Ipmcp_mld_upstream_maddr_t *maddr, struct Ip_in6_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_mld_set_sock_addr(&sreq.gsr_group, &maddr->maddr); ipmcp_mld_set_sock_addr(&sreq.gsr_source, src); if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, (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_mld_upstream_maddr_add_source: setsockopt failed on %s: %s", ipmcp.upstream_if.ifname, ipcom_strerror(ipcom_errno)); IP_PANIC2(); } } /* *=========================================================================== * ipmcp_mld_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_mld_upstream_maddr_del_source(Ipmcp_mld_upstream_maddr_t *maddr, struct Ip_in6_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_mld_set_sock_addr(&sreq.gsr_group, &maddr->maddr); ipmcp_mld_set_sock_addr(&sreq.gsr_source, src); if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, (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_mld_upstream_maddr_del_source: setsockopt failed on %s: %s", ipmcp.upstream_if.ifname, ipcom_strerror(ipcom_errno)); IP_PANIC2(); } } /* *=========================================================================== * ipmcp_mld_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_mld_upstream_maddr_flush_mroutes(Ipmcp_mld_upstream_maddr_t *maddr) { int i; struct Ip_mf6cctl mf6ctl; ipcom_memset(&mf6ctl, 0, sizeof(mf6ctl)); ipmcp_mld_set_addr(&mf6ctl.mf6cc_mcastgrp, &maddr->maddr); mf6ctl.mf6cc_parent = ipmcp.upstream_if.mld.mif_index; for (i = 0; i < IPMCP_MLD_MAX_SRC_ADDRS; i++) { if (IP_IN6_IS_ADDR_UNSPECIFIED(&maddr->saddrs[i])) continue; ipmcp_mld_set_addr(&mf6ctl.mf6cc_origin, &maddr->saddrs[i]); IPCOM_LOG2(DEBUG, "MLD: deleting mcast route from %s to group %s", ipcom_inet_ntop(IP_AF_INET6, &mf6ctl.mf6cc_origin, ipmcp.log_buf, sizeof(ipmcp.log_buf)), ipcom_inet_ntop(IP_AF_INET6, &mf6ctl.mf6cc_mcastgrp, ipmcp.log_buf + IP_INET6_ADDRSTRLEN, sizeof(ipmcp.log_buf))); if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_DEL_MFC, &mf6ctl, sizeof(mf6ctl)) < 0) { IPCOM_LOG4(ERR, "ipmcp_mld_upstream_maddr_flush_mroutes: " "setsockopt IPPROTO_IPV6, MRT6_DEL_MFC failed on %s [%s %s]: %s", ipmcp.upstream_if.ifname, ipcom_inet_ntop(IP_AF_INET6, &mf6ctl.mf6cc_mcastgrp, ipmcp.log_buf, sizeof(ipmcp.log_buf)), ipcom_inet_ntop(IP_AF_INET6, &mf6ctl.mf6cc_mcastgrp, ipmcp.log_buf + IP_INET6_ADDRSTRLEN, sizeof(ipmcp.log_buf)), ipcom_strerror(ipcom_errno)); } maddr->saddrs[i] = ip_in6addr_any; } maddr->next_src_addr = 0; } /* *=========================================================================== * ipmcp_mld_flush_all_mroutes_cb *=========================================================================== * Description: Adapter function for ipmcp_mld_upstream_maddr_flush_mroutes() * Parameters: upstream_maddr - An upstream multicast address. * Returns: * */ IP_STATIC void ipmcp_mld_flush_all_mroutes_cb(Ipmcp_mld_upstream_maddr_t *upstream_maddr, void *unused) { (void) unused; ipmcp_mld_upstream_maddr_flush_mroutes(upstream_maddr); } /* *=========================================================================== * ipmcp_mld_flush_all_mroutes *=========================================================================== * Description: Flushes all IPv6 multicast routes. * Parameters: * Returns: * */ IP_STATIC void ipmcp_mld_flush_all_mroutes(void) { ipcom_hash_for_each(ipmcp.upstream_if.mld.maddr_head, (Ipcom_hash_foreach_cb_func)ipmcp_mld_flush_all_mroutes_cb, IP_NULL); } /* *=========================================================================== * ipmcp_mld_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_mld_upstream_maddr_t * ipmcp_mld_upstream_maddr_get(struct Ip_in6_addr *maddr) { Ipmcp_mld_upstream_maddr_t *maddr_obj; maddr_obj = ipcom_hash_get(ipmcp.upstream_if.mld.maddr_head, maddr); if (maddr_obj == IP_NULL) { maddr_obj = ipcom_calloc(1, sizeof(Ipmcp_mld_upstream_maddr_t)); if (maddr_obj == IP_NULL) goto out_of_memory; maddr_obj->base.filter = ipcom_set_new(sizeof(struct Ip_in6_addr)); if (maddr_obj->base.filter == IP_NULL) goto out_of_memory; maddr_obj->maddr = *maddr; maddr_obj->base.ops = &ipmcp.mld.upstream_maddr_ops; if (ipcom_hash_add(ipmcp.upstream_if.mld.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_mld_upstream_maddr_get: Failed to allocate memory"); IP_PANIC2(); return IP_NULL; } /* *=========================================================================== * ipmcp_mld_upstream_maddr_delete *=========================================================================== * Description: Deletes a multicast address from the upstream interface. * Parameters: maddr - The address to delete from the upstream interface. * Returns: * */ IP_STATIC void ipmcp_mld_upstream_maddr_delete(Ipmcp_mld_upstream_maddr_t *maddr) { struct Ip_group_req req; if (ipcom_hash_remove(ipmcp.upstream_if.mld.maddr_head, maddr) != IPCOM_SUCCESS) IP_PANIC(); req.gr_interface = ipmcp.upstream_if.ifindex; ipmcp_mld_set_sock_addr(&req.gr_group, &maddr->maddr); if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MCAST_LEAVE_GROUP, &req, sizeof(req)) < 0) { IPCOM_LOG2(ERR, "ipmcp_mld_upstream_maddr_delete: " "setsockopt IPPROTO_IP, IP_MCAST_LEAVE_GROUP failed on %s: %s", ipmcp.upstream_if.ifname, ipcom_strerror(ipcom_errno)); } ipmcp_mld_upstream_maddr_flush_mroutes(maddr); ipcom_set_delete(maddr->base.filter); ipcom_free(maddr); } /* *=========================================================================== * ipmcp_mld_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_mld_upstream_maddr_delete_cb(Ipmcp_mld_upstream_maddr_t *maddr_obj, void *unused) { (void) unused; ipmcp_mld_upstream_maddr_delete(maddr_obj); } /* *=========================================================================== * ipmcp_mld_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_mld_store_mroute(struct Ip_in6_addr *maddr, struct Ip_in6_addr *src) { Ipmcp_mld_upstream_maddr_t *maddr_obj; maddr_obj = ipmcp_mld_upstream_maddr_get(maddr); if (maddr_obj == IP_NULL) /* No matching multicast address in the database */ return IP_FALSE; if (IP_FALSE == IP_IN6_IS_ADDR_UNSPECIFIED(&maddr_obj->saddrs[maddr_obj->next_src_addr])) { /* Ring buffer overflow, remove the oldest entry */ struct Ip_mf6cctl mf6ctl; IPCOM_LOG2(DEBUG, "MLD: deleting mcast route from %s to group %s", ipcom_inet_ntop(IP_AF_INET6, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)), ipcom_inet_ntop(IP_AF_INET6, maddr, ipmcp.log_buf + IP_INET6_ADDRSTRLEN, sizeof(ipmcp.log_buf))); ipcom_memset(&mf6ctl, 0, sizeof(mf6ctl)); ipmcp_mld_set_addr(&mf6ctl.mf6cc_origin, &maddr_obj->saddrs[maddr_obj->next_src_addr]); ipmcp_mld_set_addr(&mf6ctl.mf6cc_mcastgrp, &maddr_obj->maddr); mf6ctl.mf6cc_parent = ipmcp.upstream_if.mld.mif_index; (void)ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_DEL_MFC, &mf6ctl, sizeof(mf6ctl)); } maddr_obj->saddrs[maddr_obj->next_src_addr] = *src; maddr_obj->next_src_addr = (maddr_obj->next_src_addr + 1) % IPMCP_MLD_MAX_SRC_ADDRS; return IP_TRUE; } /* *=========================================================================== * ipmcp_mld_netif_to_maddr_head *=========================================================================== * Description: Returns the IPv4 multicast address object head. * Parameters: netif - A downstream interface. * Returns: * */ IP_STATIC Ipcom_hash * ipmcp_mld_netif_to_maddr_head(Ipmcp_netif_t *netif) { return netif->mld.maddr_head; } /* *=========================================================================== * ipmcp_mld_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_mld_update_upstream_if_filter(Ipmcp_mld_maddr_t *maddr) { Ipmcp_mld_upstream_maddr_t *maddr_obj; maddr_obj = ipmcp_mld_upstream_maddr_get(&maddr->maddr); if (maddr_obj != IP_NULL) { ipmcp_mld_upstream_maddr_flush_mroutes(maddr_obj); ipmcp_update_upstream_if_filter(&maddr_obj->base, ipmcp_mld_netif_to_maddr_head); } } /* *=========================================================================== * ipmcp_mld_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_mld_forward_stream(Ipmcp_netif_t *netif, struct Ip_in6_addr *maddr, struct Ip_in6_addr *saddr) { Ipmcp_maddr_t *maddr_obj; Ipmcp_saddr_t *saddr_obj; if (netif->mld.state != IPMCP_LINK_STATE_QUERIER) return IP_FALSE; maddr_obj = ipcom_hash_get(netif->mld.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_mld_add_mroute *=========================================================================== * Description: Adds a multicast route entry. * Parameters: mifi - The multicast interface the packet entered at. * maddr - The multicast address this route will target. * src - The source of the multicast packets. * Returns: * */ IP_STATIC int ipmcp_mld_add_mroute(Ip_mifi_t mifi, struct Ip_in6_addr *maddr, struct Ip_in6_addr *src) { int n_mif = 0; struct Ip_mf6cctl mfctl; Ipmcp_netif_t *netif; Ip_bool add_mroute = IP_FALSE; /* Setup multicast route */ ipcom_memset(&mfctl, 0, sizeof(mfctl)); mfctl.mf6cc_parent = mifi; ipmcp_mld_set_addr(&mfctl.mf6cc_mcastgrp, maddr); ipmcp_mld_set_addr(&mfctl.mf6cc_origin, src); if (mifi == ipmcp.upstream_if.mld.mif_index) add_mroute = IP_TRUE; else { /* Must always forward to the upstream interface when multicast traffic entrers at one of the downstream interfaces */ n_mif++; IP_MRT6_IF_SET(ipmcp.upstream_if.mld.mif_index, &mfctl.mf6cc_ifset); } /* Check MRT table on each interface */ for (netif = IPCOM_LIST_FIRST(&ipmcp.downstream_ifs_head); netif; netif = IPCOM_LIST_NEXT(&netif->list)) { if (mifi == netif->mld.mif_index) add_mroute = (netif->mld.state == IPMCP_LINK_STATE_QUERIER); else if (ipmcp_mld_forward_stream(netif, maddr, src)) { /* Found an interface to forward on */ n_mif++; IP_MRT6_IF_SET(netif->mld.mif_index, &mfctl.mf6cc_ifset); } } 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, "MLD: adding mcast route from %s to group %s", ipcom_inet_ntop(IP_AF_INET6, src, ipmcp.log_buf, sizeof(ipmcp.log_buf)), ipcom_inet_ntop(IP_AF_INET6, maddr, ipmcp.log_buf + IP_INET6_ADDRSTRLEN, sizeof(ipmcp.log_buf))); if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_ADD_MFC, &mfctl, sizeof(mfctl)) < 0) { IPCOM_LOG1(WARNING, "ipmcp_mld_add_mroute: failed to add mroute: %s", ipcom_strerror(ipcom_errno)); return -ipcom_errno; } ipmcp_mld_store_mroute(maddr, src); return 0; } /* *=========================================================================== * ipmcp_mld_encode_duration *=========================================================================== * Description: Encodes query interface to the queriers query interval code. * Parameters: query_interval - Duration in seconds. * Returns: Maximum response code in network byte order. * */ IP_STATIC Ip_u16 ipmcp_mld_encode_duration(Ip_u32 duration, int version) { int expo; Ip_u16 code; if (version == 1 || duration < 32768) return ip_htons((Ip_u16) duration); expo = 0; while ((duration >> (expo + 3)) > 0x1fff) expo++; code = 0x8000 + (expo << 12) + (0x0fff & (duration >> (expo + 3))); return ip_htons(code); } /* *=========================================================================== * ipmcp_mld_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_mld_add_saddr_to_query(struct Ip_in6_addr *saddr, Ipmcp_mld_query_pkt_t *query) { query->source_addresses[query->num_sources++] = *saddr; } /* *=========================================================================== * ipmcp_mld_query_send *=========================================================================== * Description: Send MLD 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_mld_query_send(Ipmcp_netif_t *netif, struct Ip_in6_addr *maddr, Ip_u32 max_response_interval_msec, Ip_bool suppress_router_side_processing, Ipcom_set *saddrs) { Ipmcp_mld_query_pkt_t *pkt = (Ipmcp_mld_query_pkt_t *) ipmcp.send_buf; struct Ip_sockaddr_in6 to; struct Ip_msghdr m; struct Ip_iovec iov; struct Ip_in6_pktinfo *pktinfo; struct Ip_cmsghdr *c; Ip_u8 cmsg_buf[IP_CMSG_SPACE(sizeof(struct Ip_in6_pktinfo))]; int version; unsigned msg_len; version = ipcom_sysvar_get_as_int("ipmcp.mld.CompatibilityMode", 2); ipcom_memset(&to, 0, sizeof(to)); IPCOM_SA_LEN_SET(&to, sizeof(to)); to.sin6_family = IP_AF_INET6; to.sin6_addr = *maddr; if (IP_IN6_IS_ADDR_MC_LINKLOCAL(maddr)) to.sin6_scope_id = netif->ifindex; ipcom_memset(pkt, 0, sizeof(*pkt)); pkt->ipmcp_mld_type = IPMCP_MLD_LISTENER_QUERY; pkt->ipmcp_mld_maxdelay = ipmcp_mld_encode_duration(max_response_interval_msec, version); if (IP_IN6_ARE_ADDR_EQUAL(&ipmcp.mld.all_hosts_addr, maddr) == IP_FALSE) ipcom_memcpy(&pkt->group_address, maddr, sizeof(struct Ip_in6_addr)); if (version == 1) msg_len = 24; else { ip_assert(version == 2); msg_len = 28; if (ipmcp.config.robustness_variable <= 7) /* RFC3810 - 5.1.8. If non-zero, the QRV field contains the [Robustness Variable] value used by the Querier. If the Querier's [Robustness Variable] exceeds 7 (the maximum value of the QRV field), the QRV field 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_MLDV2_QUERY_SUPRESS_BIT); pkt->qqic = ipmcp_qqic_from_msec(ipmcp.config.query_interval, 3); } if (saddrs != IP_NULL) { ipcom_set_for_each(saddrs, (Ipcom_set_foreach_cb_func) ipmcp_mld_add_saddr_to_query, pkt); msg_len += pkt->num_sources * 16; pkt->num_sources = ip_htons(pkt->num_sources); } iov.iov_base = pkt; iov.iov_len = msg_len; ipcom_memset(cmsg_buf, 0, sizeof(cmsg_buf)); c = (struct Ip_cmsghdr *) cmsg_buf; c->cmsg_len = IP_CMSG_LEN(sizeof (struct Ip_in6_pktinfo)); c->cmsg_level = IP_IPPROTO_IPV6; c->cmsg_type = IP_IPV6_PKTINFO; pktinfo = IP_CMSG_DATA(c); pktinfo->ipi6_ifindex = netif->ifindex; /* RFC 3590. MLD queries MUST be sent with a valid link local address */ ipcom_memcpy(pktinfo->ipi6_addr.in6.addr8, netif->mld.link_local_address.in6.addr8, sizeof(pktinfo->ipi6_addr.in6)); m.msg_name = &to; m.msg_namelen = sizeof(to); m.msg_iov = &iov; m.msg_iovlen = 1; m.msg_control = c; m.msg_controllen = sizeof(cmsg_buf); m.msg_flags = 0; IPCOM_LOG2(DEBUG2, "ipmcp_mld_query_send: Send query to %s on interface %s", ipcom_inet_ntop(IP_AF_INET6, &to, ipmcp.log_buf, sizeof(ipmcp.log_buf)), ipcom_if_indextoname(netif->ifindex, ipmcp.log_buf + IP_INET6_ADDRSTRLEN)); if (ipcom_sendmsg(ipmcp.mld.mld_fd, &m, 0) >= 0) netif->mld.no_rt_count = 0; else { /* This construct is just to suppress error message during duplicate address detection */ if (ipcom_errno != IP_ERRNO_EHOSTUNREACH || ++netif->mld.no_rt_count > 3) { IPCOM_LOG2(ERR, "MLD: Failed to send message on interface %s: %s", ipcom_if_indextoname(netif->ifindex, ipmcp.log_buf), ipcom_strerror(ipcom_errno)); } } } /* *=========================================================================== * ipmcp_mld_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_mld_general_query_tmo(Ipmcp_netif_t *netif) { Ipmcp_mld_netif_t *i = &netif->mld; Ip_bool restart_timer = IP_TRUE; if (i->state == IPMCP_LINK_STATE_INACTIVE) /* Retry later (this is probably because of no link layer address) */ return IP_TRUE; ipmcp_mld_query_send(netif, &ipmcp.mld.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_mld_general_query_tmo, netif, duration); restart_timer = IP_FALSE; } return restart_timer; } /* *=========================================================================== * ipmcp_mld_resume_querier_tmo *=========================================================================== * Description: Timeout routine called when resuming to querier state. * Parameters: netif - The link to resume as querier. * Returns: * */ IP_STATIC Ip_bool ipmcp_mld_resume_querier_tmo(Ipmcp_netif_t *netif) { IPCOM_LOG1(INFO, "MLD: 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 MLD Querier. ... */ ipmcp_mld_flush_all_mroutes(); netif->mld.state = IPMCP_LINK_STATE_QUERIER; netif->mld.robustness_variable = ipmcp.config.robustness_variable; netif->mld.query_interval = ipmcp.config.query_interval; netif->mld.startup_query_count = ipmcp.config.startup_query_count; ipmcp_mld_general_query_tmo(netif); return IP_FALSE; } /* *=========================================================================== * ipmcp_mld_version_1_compat_tmo *=========================================================================== * Description: Called when MLDv1 compatibilty mode can be exited * Parameters: maddr - A multicast address. * Returns: IP_FALSE - The timeout should not be rescheduled. * */ IP_STATIC Ip_bool ipmcp_mld_version_1_compat_tmo(Ipmcp_mld_maddr_t *maddr) { (void) maddr; IPCOM_LOG1(INFO, "MLD: Exit MLDv1 compatibility mode on %s", maddr->base.netif->ifname); return IP_FALSE; } /* *=========================================================================== * ipmcp_mld_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_mld_specific_query_tmo(Ipmcp_mld_maddr_t *maddr) { (void) maddr; IPCOM_LOG1(DEBUG, "MLD: Specific query finished on %s since max response period passed", maddr->base.netif->ifname); return IP_FALSE; } /* *=========================================================================== * ipmcp_mld_other_querier_present_timeout *=========================================================================== * Description: Returns the other querier present timeout in msec. * Parameters: i - MLD specific interface config * Returns: * */ IP_STATIC Ip_u32 ipmcp_mld_other_querier_present_timeout(Ipmcp_mld_netif_t *i) { /* * RFC 3810 * * 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_mld_handle_query *=========================================================================== * Description: Handle a received MLD QUERY message * 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_mld_handle_query(Ipmcp_netif_t *netif, Ipmcp_mld_query_pkt_t *query, struct Ip_in6_addr *from, int version) { Ipmcp_mld_netif_t *i = &netif->mld; unsigned x; Ipmcp_mld_maddr_t *maddr_obj; IPCOM_LOG4(DEBUG, "MLDv%d membership query received from %s group %s on %s", version, ipcom_inet_ntop(IP_AF_INET6, from, ipmcp.log_buf, sizeof(ipmcp.log_buf)), ipcom_inet_ntop(IP_AF_INET6, &query->group_address, ipmcp.log_buf + IP_INET6_ADDRSTRLEN, sizeof(ipmcp.log_buf) - IP_INET6_ADDRSTRLEN), netif->ifname); maddr_obj = ipcom_hash_get(netif->mld.maddr_head, &query->group_address); /* * RFC 3810 * * When a router receives a query with a lower IPv6 address than * its own, it sets the Other Querier Present timer to Other * Querier Present Timeout; if it was previously in Querier state, * it switches to Non- Querier state and ceases to send queries on * the link. After the Other Querier Present timer expires, it * should re-enter the Querier state and begin sending General * Queries. */ for (x = 0; x < sizeof(struct Ip_in6_addr); x++) { if (i->link_local_address.in6.addr8[x] == from->in6.addr8[x]) continue; if (i->link_local_address.in6.addr8[x] < from->in6.addr8[x]) /* * This node has the lower IP-address, ignore the query */ return; } if (i->state != IPMCP_LINK_STATE_NON_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; /* * Become a non-querier on this link * * RFC 3810 - When a router receives a query with a lower IPv6 * address than its own, it sets the Other Querier Present * timer to Other Querier Present Timeout; if it was * previously in Querier state, it switches to Non-Querier * state and ceases to send queries on the link. */ IPCOM_LOG1(NOTICE, "MLD: 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 MLD Querier. ... */ ipmcp_mld_flush_all_mroutes(); } if (version == 2) { /* RFC3810 - 5.1.8 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 they use the default [Robustness Variable] value specified in section 9.1, or a statically configured value. */ if (IPMCP_MLDV2_GET_QRV(query)) netif->mld.robustness_variable = IPMCP_MLDV2_GET_QRV(query); else netif->mld.robustness_variable = ipmcp.config.robustness_variable; /* RFC3810 - 5.1.9 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 9.2. */ if (query->qqic) netif->mld.query_interval = ipmcp_qqic_to_msec(query->qqic); else netif->mld.query_interval = ipmcp.config.query_interval; if (IP_BIT_ISFALSE(query->res_s_qrv, IPMCP_MLDV2_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_mld_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_mld_resume_querier_tmo, netif, ipmcp_mld_other_querier_present_timeout(i)); } /* *=========================================================================== * ipmcp_mld_handle_membership_report_rec *=========================================================================== * Description: Handles v2 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 MLDv2 record. * */ IP_STATIC void ipmcp_mld_handle_membership_report_rec(Ipmcp_netif_t *netif, Ipmcp_mld_group_rec_pkt_t *rec, int version) { Ipmcp_mld_maddr_t *maddr_obj; Ip_bool new_entry = IP_FALSE; Ipmcp_mld_rec_t rec_obj; if (IP_IN6_IS_ADDR_MULTICAST(&rec->group_address) == IP_FALSE || IPMCP_MLD_IS_LINK_LOCAL_MCAST_ADDR(&rec->group_address)) /* Never forward link local multicast addresses */ return; maddr_obj = ipcom_hash_get(netif->mld.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_mld_handle_membership_report_rec: ipcom_calloc failed"); IP_PANIC(); return; } maddr_obj->base.include_set = ipcom_hash_new((Ipcom_hash_obj_func)ipmcp_mld_saddr_obj_hash, (Ipcom_hash_key_func)ipmcp_mld_addr_key_hash, (Ipcom_hash_cmp_func)ipmcp_mld_saddr_compare); maddr_obj->base.exclude_set = ipcom_hash_new((Ipcom_hash_obj_func)ipmcp_mld_saddr_obj_hash, (Ipcom_hash_key_func)ipmcp_mld_addr_key_hash, (Ipcom_hash_cmp_func)ipmcp_mld_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_mld_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.mld.maddr_ops; maddr_obj->base.domain = IP_AF_INET6; maddr_obj->base.addr_len = sizeof(struct Ip_in6_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->specific_query_pending_timer); new_entry = IP_TRUE; } if (version == 1) { /* MLDv1 host report, start compatibility timer */ if (IPCOM_TMO2_PENDING(&maddr_obj->v1_host_present_timer) == IP_FALSE) IPCOM_LOG1(INFO, "MLD: Enter MLDv1 compatibility mode on %s", netif->ifname); (void)ipcom_tmo2_cancel(&maddr_obj->v1_host_present_timer); (void)ipcom_tmo2_request(ipmcp.tmo_head, &maddr_obj->v1_host_present_timer, (Ipcom_tmo2_func)ipmcp_mld_version_1_compat_tmo, maddr_obj, netif->mld.robustness_variable * netif->mld.query_interval + ipmcp.config.query_response_interval); } rec_obj.base.ops = &ipmcp.mld.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->mld.maddr_head, maddr_obj) != IPCOM_SUCCESS) { ipcom_free(maddr_obj); IPCOM_LOG0(ERR, "ipmcp_mld_handle_membership_report_rec: ipcom_hash_addr failed"); IP_PANIC(); return; } IPCOM_LOG2(DEBUG, "MLD: Created entry for %s on %s", ipcom_inet_ntop(IP_AF_INET6, &maddr_obj->maddr, ipmcp.log_buf, sizeof(ipmcp.log_buf)), netif->ifname); } ipmcp_mld_update_upstream_if_filter(maddr_obj); return; cleanup: if (new_entry && maddr_obj != IP_NULL) { ipcom_hash_delete(maddr_obj->base.include_set); ipcom_hash_delete(maddr_obj->base.exclude_set); if (maddr_obj->base.report_set != IP_NULL) { ipcom_set_delete(maddr_obj->base.report_set); } (void)ipcom_tmo2_cancel(&maddr_obj->v1_host_present_timer); ipcom_free(maddr_obj); } } /* *=========================================================================== * ipmcp_mld_handle_listener_report *=========================================================================== * Description: Handle a MLD listener report message. * Parameters: netif - The interface the query was received at. * report - The membership report message. * Returns: * */ IP_STATIC void ipmcp_mld_handle_listener_report(Ipmcp_netif_t *netif, Ipmcp_mld_pkt_t *report) { Ipmcp_mld_group_rec_pkt_t rec; /* RFC3810 - 8.3.2: MLDv1 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_mld_handle_membership_report_rec(netif, &rec, 1); } /* *=========================================================================== * ipmcp_mld_handle_listener_done *=========================================================================== * Description: Handle a MLD listener done message. * Parameters: . * Returns: . * */ IP_STATIC void ipmcp_mld_handle_listener_done(Ipmcp_netif_t *netif, Ipmcp_mld_pkt_t* report) { Ipmcp_mld_group_rec_pkt_t rec; /* RFC3810 - 8.3.2: MLDv1 done 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_mld_handle_membership_report_rec(netif, &rec, 1); } /* *=========================================================================== * ipmcp_mld_activate *=========================================================================== * Description: Activate MLD server on specified interface. * Parameters: . * Returns: . * */ IP_STATIC void ipmcp_mld_activate(Ipmcp_netif_t *netif) { struct Ip_ipv6_mreq mreq; /* Enter promiscuous mode on this interface */ ipcom_memset(&mreq.ipv6mr_multiaddr, 0, sizeof(struct Ip_in6_addr)); mreq.ipv6mr_interface = netif->ifindex; if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) { IPCOM_LOG2(ERR, "ipmcp_mld_activate: setsockopt IPPROTO_IPV6, IPV6_JOIN_GROUP failed on %s: %s", ipcom_if_indextoname(netif->ifindex, ipmcp.log_buf), ipcom_strerror(ipcom_errno)); IP_PANIC(); return; } /* Start sending general queries */ (void)ipmcp_mld_resume_querier_tmo(netif); } /* *=========================================================================== * ipmcp_mld_deactivate *=========================================================================== * Description: Dectivate MLD server on specified interface. * Parameters: . * Returns: . * */ IP_STATIC void ipmcp_mld_deactivate(Ipmcp_netif_t *netif) { struct Ip_ipv6_mreq mreq; Ipmcp_mld_netif_t *i = &netif->mld; /* Cancel downstream interface timer */ (void)ipcom_tmo2_cancel(&i->timer); if (i->maddr_head) ipcom_hash_for_each(i->maddr_head, (Ipcom_hash_foreach_cb_func)ipmcp_mld_maddr_drop, netif); if (netif->mld.state != IPMCP_LINK_STATE_INACTIVE) { /* Leave promiscuous mode on this interface */ ipcom_memset(&mreq.ipv6mr_multiaddr, 0, sizeof(struct Ip_in6_addr)); mreq.ipv6mr_interface = netif->ifindex; (void)ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_IPV6_LEAVE_GROUP, &mreq, sizeof(mreq)); } /* Change state */ IPCOM_LOG1(NOTICE, "MLD: Link state INACTIVE on %s", netif->ifname); netif->mld.state = IPMCP_LINK_STATE_INACTIVE; } /* ########### Ipmcp_mld_rec_t methods ########### */ /* *=========================================================================== * ipmcp_mld_rec_get_type *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC Ip_u8 ipmcp_mld_rec_get_type(Ipmcp_mld_rec_t *self) { return self->rec->type; } /* *=========================================================================== * ipmcp_mld_rec_get_maddr *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void * ipmcp_mld_rec_get_maddr(Ipmcp_mld_rec_t *self) { return &self->rec->group_address; } /* *=========================================================================== * ipmcp_mld_rec_get_num_saddrs *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC unsigned ipmcp_mld_rec_get_num_saddrs(Ipmcp_mld_rec_t *self) { return ip_ntohs(self->rec->num_sources); } /* *=========================================================================== * ipmcp_mld_rec_get_saddr *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void * ipmcp_mld_rec_get_saddr(Ipmcp_mld_rec_t *self, unsigned i) { ip_assert(ipmcp_mld_rec_get_num_saddrs(self) > i); return &self->rec->source_addresses[i]; } /* *=========================================================================== * ipmcp_mld_rec_as_set *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC Ipcom_set * ipmcp_mld_rec_as_set(Ipmcp_mld_rec_t *self) { Ipcom_set *set; unsigned s; unsigned num = ip_ntohs(self->rec->num_sources); set = ipcom_set_new(sizeof(struct Ip_in6_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_mld_rec_clear_source_list *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void ipmcp_mld_rec_clear_source_list(Ipmcp_mld_rec_t *self) { self->rec->num_sources = 0; } /* ########### Ipmcp_mld_saddr_t methods ########### */ /* *=========================================================================== * ipmcp_mld_saddr_get_addr *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void * ipmcp_mld_saddr_get_addr(Ipmcp_mld_saddr_t *self) { return &self->saddr; } /* ########### Ipmcp_mld_daddr_t methods ########### */ /* *=========================================================================== * ipmcp_mld_maddr_delete *=========================================================================== * Description: Drops a multicast address from a specified interface. * Parameters: maddr_obj - The address to delete. * Returns: * */ IP_STATIC void ipmcp_mld_maddr_delete(Ipmcp_mld_maddr_t *maddr_obj) { Ipmcp_netif_t *netif = maddr_obj->base.netif; /* Remove from hash table */ (void)ipcom_hash_remove(netif->mld.maddr_head, maddr_obj); ipmcp_mld_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); ipmcp_maddr_cleanup(&maddr_obj->base); /* Free object */ ipcom_free(maddr_obj); } /* *=========================================================================== * ipmcp_mld_maddr_drop *=========================================================================== * Description: Drops a multicast address from a specified interface. * Parameters: maddr_obj - The address to destroy. * Returns: * */ IP_STATIC void ipmcp_mld_maddr_drop(Ipmcp_mld_maddr_t *maddr_obj, void *unused) { (void) unused; ipmcp_mld_maddr_delete(maddr_obj); } /* *=========================================================================== * ipmcp_mld_maddr_get_addr *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void * ipmcp_mld_maddr_get_addr(Ipmcp_mld_maddr_t *self) { return &self->maddr; } /* *=========================================================================== * ipmcp_mld_maddr_compatibility_mode *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC int ipmcp_mld_maddr_compatibility_mode(Ipmcp_mld_maddr_t *self) { if (IPCOM_TMO2_PENDING(&self->v1_host_present_timer)) return IPMCP_MCAST_COMPAT_IGMPV2_MLDV1; return IPMCP_MCAST_COMPAT_IGMPV3_MLDV2; } /* *=========================================================================== * ipmcp_mld_maddr_add_saddr *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC Ipmcp_mld_saddr_t * ipmcp_mld_maddr_add_saddr(Ipmcp_mld_maddr_t *self, struct Ip_in6_addr *saddr, Ip_bool is_include_set) { Ipmcp_mld_saddr_t *saddr_obj; saddr_obj = ipcom_calloc(1, sizeof(*saddr_obj)); saddr_obj->base.ops = &ipmcp.mld.saddr_ops; saddr_obj->saddr = *saddr; saddr_obj->base.maddr = &self->base; IPCOM_TMO2_INIT(&saddr_obj->base.timer); if (ipcom_hash_add(is_include_set ? self->base.include_set : self->base.exclude_set, saddr_obj) != IPCOM_SUCCESS) { ipcom_free(saddr_obj); return IP_NULL; } return saddr_obj; } /* *=========================================================================== * ipmcp_mld_maddr_del_saddrs_not_in_rec_cb *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void ipmcp_mld_maddr_del_saddrs_not_in_rec_cb(Ipmcp_mld_saddr_t *saddr, Ipmcp_mld_rec_t *rec) { unsigned s, num = ip_ntohs(rec->rec->num_sources); for (s = 0; s < num; s++) if (IP_IN6_ARE_ADDR_EQUAL(&rec->rec->source_addresses[s], &saddr->saddr)) return; saddr->base.ops->dtor(&saddr->base); } /* *=========================================================================== * ipmcp_mld_maddr_del_saddrs_not_in_rec *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void ipmcp_mld_maddr_del_saddrs_not_in_rec(Ipmcp_mld_maddr_t *self, Ipmcp_mld_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_mld_maddr_del_saddrs_not_in_rec_cb, rec); } /* *=========================================================================== * ipmcp_mld_maddr_send_query *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void ipmcp_mld_maddr_send_query(Ipmcp_mld_maddr_t *self, Ip_u32 max_response_interval_msec, Ipcom_set *saddrs) { if (self->base.netif->mld.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_mld_specific_query_tmo, self, max_response_interval_msec); ipmcp_mld_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_mld_maddr_group_membership_interval *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC Ip_u32 ipmcp_mld_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->mld.robustness_variable * self->netif->mld.query_interval + ipmcp.config.query_response_interval; } /* *=========================================================================== * ipmcp_mld_is_msg_ok *=========================================================================== * Description: Basic sanity check on the incoming message. * Parameters: version - MLD version of the incoming message. * Returns: IP_TRUE - message passed basic sanity. * */ IP_STATIC Ip_bool ipmcp_mld_is_msg_ok(int version) { return version <= ipcom_sysvar_get_as_int("ipmcp.mld.CompatibilityMode", 2); } /* **************************************************************************** * 10 GLOBAL FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipmcp_mld_mroute_input *=========================================================================== * Description: Receive ICMPv6 message from multicast forwarding engine. * Parameters: . * Returns: . * */ IP_GLOBAL int ipmcp_mld_mroute_input(int fd) { int length; struct Ip_mrt6msg *mrt6msg; char buf[sizeof(struct Ip_mrt6msg)]; length = ipcom_recv(fd, buf, sizeof(buf), 0); if (length < 0) { IPCOM_LOG1(ERR, "MLD: ipcom_socketread() failed: %s", ipcom_strerror(ipcom_errno)); return -1; } if (ipmcp.state != IPMCP_STATE_RUNNING) /* Uplink interface not active */ return 0; /* Too short packet */ if (length < (int) sizeof(*mrt6msg)) return 0; mrt6msg = (struct Ip_mrt6msg*)buf; IPCOM_LOG2(INFO, "MLD: Got mroute message %s --> %s", ipcom_inet_ntop(IP_AF_INET6, &mrt6msg->im6_src, ipmcp.log_buf, sizeof(ipmcp.log_buf)), ipcom_inet_ntop(IP_AF_INET6, &mrt6msg->im6_dst, ipmcp.log_buf + IP_INET6_ADDRSTRLEN, sizeof(ipmcp.log_buf))); return ipmcp_mld_add_mroute(mrt6msg->im6_mif, &mrt6msg->im6_dst, &mrt6msg->im6_src); } /* *=========================================================================== * ipmcp_mld_input *=========================================================================== * Description: Receive MLD message * Parameters: . * Returns: . * */ IP_GLOBAL int ipmcp_mld_input(int fd) { struct Ip_msghdr m; struct Ip_iovec iov; int len; struct Ip_sockaddr_in6 from; Ipmcp_mld_pkt_t *pkt; Ipmcp_mld_query_pkt_t *query; Ipmcp_mld_report_pkt_t *report; Ipmcp_mld_group_rec_pkt_t *next_rec; Ipmcp_netif_t *netif; struct Ip_in6_pktinfo *pktinfo = IP_NULL; int *hoplimit = IP_NULL; struct Ip_ip6_hbh *hbh = IP_NULL; Ip_socklen_t rt_alert_len; Ip_u16 *rt_alert_opt; struct Ip_cmsghdr *c; Ip_u8 cmsg_buf[IP_CMSG_SPACE(sizeof(struct Ip_in6_pktinfo)) + IP_CMSG_SPACE(sizeof(int)) + IP_CMSG_SPACE(8 /* HBH-opts */)]; int v; int num_rec; iov.iov_base = ipmcp.recv_buf; iov.iov_len = sizeof(ipmcp.recv_buf); m.msg_name = &from; m.msg_namelen = sizeof(from); 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, "MLD: ipcom_recvmsg() failed: %s", ipcom_strerror(ipcom_errno)); return -1; } /* Too short packet */ if (len < (int) sizeof(Ipmcp_mld_pkt_t)) return 0; pkt = iov.iov_base; /* Discard packets with non link-local sender address */ if (!IP_IN6_IS_ADDR_LINK_LOCAL(&from.sin6_addr)) { if(pkt->ipmcp_mld_type == IPMCP_MLD_LISTENER_QUERY) { /* RFC3590 ...if a node receives a query message with an IPv6 source address * set to the unspecified address, it MUST silently discard the message and * SHOULD log a warning */ IPCOM_LOG1(NOTICE, "MLD Query message received from non link-local address %s", ipcom_inet_ntop(IP_AF_INET6, &from.sin6_addr, ipmcp.log_buf, sizeof(ipmcp.log_buf))); } return 0; } for (c = IP_CMSG_FIRSTHDR(&m); c != IP_NULL; c = IP_CMSG_NXTHDR(&m, c)) { if (c->cmsg_level == IP_IPPROTO_IPV6) { switch (c->cmsg_type) { case IP_IPV6_PKTINFO: pktinfo = IP_CMSG_DATA(c); break; case IP_IPV6_HOPLIMIT: hoplimit = IP_CMSG_DATA(c); break; case IP_IPV6_HOPOPTS: hbh = IP_CMSG_DATA(c); break; } } } if (pktinfo == IP_NULL || hoplimit == IP_NULL || hbh == IP_NULL) { IPCOM_LOG0(ERR,"ipmcp_mld_input: failed to get pktinfo, hoplimit and/or hop-by-hop ext"); return -1; } if (IP_GET_32ON8(hoplimit) != 1) { IPCOM_LOG0(ERR,"ipmcp_mld_input: hoplimit != 1"); return 0; } /* * Does this message include a hop-by-hop extension header with a * route alert option? */ if (ipcom_inet6_opt_find(hbh, (hbh->ip6h_len + 1) << 3, 0, IP_IP6OPT_ROUTER_ALERT, &rt_alert_len, (void**)&rt_alert_opt) == -1 || rt_alert_len != sizeof(*rt_alert_opt) || *rt_alert_opt != IP_IP6_ALERT_MLD) { IPCOM_LOG0(ERR,"ipmcp_mld_input: missing or invalid router alert option"); return -1; } netif = ipmcp_if_find(pktinfo->ipi6_ifindex); if (IP_NULL == netif || netif->mld.state == IPMCP_LINK_STATE_INACTIVE) /* Packet received on one of the interface where we are not acting as a router */ return 0; /* Discard packets from ourselves or packets not sent to a multicast address */ if (IP_IN6_ARE_ADDR_EQUAL(&netif->mld.link_local_address, &from.sin6_addr)) return 0; pkt = iov.iov_base; if (!IP_IN6_IS_ADDR_MULTICAST(&pktinfo->ipi6_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_INET6 && ipcom_if_nametoindex(ifaddr->ifa_name) == pktinfo->ipi6_ifindex) { struct Ip_sockaddr_in6 *s_in = (struct Ip_sockaddr_in6 *) (ifaddr->ifa_addr); if (IP_IN6_ARE_ADDR_EQUAL(&s_in->sin6_addr, &pktinfo->ipi6_addr)) break; } } ipcom_freeifaddrs(ifaddrs); /* Unicast address on this interface? */ if (ifaddr == IP_NULL) return 0; } pkt = iov.iov_base; switch (pkt->ipmcp_mld_type) { case IPMCP_MLD_LISTENER_QUERY: query = iov.iov_base; if (len == IPMCP_MLD_MIN_PKT_LEN) v = 1; else { if (len < IPMCP_MLD_MIN_PKT_LEN + 4 + ip_htons(query->num_sources) * 16) /* Invalid query, ignore this message */ return 0; v = 2; } if (ipmcp_mld_is_msg_ok(v)) ipmcp_mld_handle_query(netif, query, &from.sin6_addr, v); break; case IPMCP_MLD_LISTENER_REPORT: if (ipmcp_mld_is_msg_ok(1)) ipmcp_mld_handle_listener_report(netif, pkt); break; case IPMCP_MLD_LISTENER_REDUCTION: if (ipmcp_mld_is_msg_ok(1)) ipmcp_mld_handle_listener_done(netif, pkt); break; case IPMCP_MLD_LISTENER_REPORT_V2: if (!ipmcp_mld_is_msg_ok(2)) return 0; report = iov.iov_base; /* Size of mandatory message part */ len -= 8; num_rec = ip_ntohs(report->num_group_rec); next_rec = report->recs; for (v = 0; v < num_rec; v++) { Ipmcp_mld_group_rec_pkt_t *rec = next_rec; int rec_len; rec_len = 20 + ip_ntohs(rec->num_sources) * 16 + rec->aux_data_len * 4; if (len < rec_len) { IPCOM_LOG2(NOTICE, "MLDv2 invalid membership report received from %s on %s", ipcom_inet_ntop(IP_AF_INET6, &from.sin6_addr, ipmcp.log_buf, sizeof(ipmcp.log_buf)), netif->ifname); /* Ignore this packet */ return 0; } len -= rec_len; next_rec = (Ipmcp_mld_group_rec_pkt_t *) ((Ip_u8 *) rec + rec_len); ipmcp_mld_handle_membership_report_rec(netif, rec, 2); } break; default: IPCOM_LOG1(ERR, "MLD got message of unkown MLD type %d", pkt->ipmcp_mld_type); break; } return 0; } /* *=========================================================================== * ipmcp_mld_addr_change *=========================================================================== * Description: An address was added or removed. * Parameters: * Returns: * */ IP_GLOBAL void ipmcp_mld_addr_change(Ipmcp_netif_t *netif, Ip_bool add) { Ipmcp_mld_netif_t *i = &netif->mld; if (add) { /* Always update link-local address */ if (ipmcp_mld_get_link_local_addr(netif->ifindex, &i->link_local_address) == IPMCP_LINK_LOCAL_ADR_AVAIL) if (netif->mld.state == IPMCP_LINK_STATE_INACTIVE) { /* Our link local has now been set and isn't tentative. Activate! */ ipmcp_mld_activate(netif); } } else { if (i->state != IPMCP_LINK_STATE_INACTIVE && (ipmcp_mld_get_link_local_addr(netif->ifindex, &i->link_local_address) == IPMCP_LINK_LOCAL_ADR_NOTAVAIL)) /* Our link-local address has been removed. Deactivate! */ ipmcp_mld_deactivate(netif); } } /* *=========================================================================== * ipmcp_mld_if_create *=========================================================================== * Description: Creates the MLD part of an interface. * Parameters: * Returns: * */ IP_GLOBAL int ipmcp_mld_if_create(Ipmcp_netif_t *netif) { struct Ip_mif6ctl mifctl; Ipmcp_mld_data_t *d = &ipmcp.mld; Ipmcp_mld_netif_t *i = &netif->mld; int x; for (x = 0; x < IP_MAXMIFS; x++) { d->last_mifi = (Ip_mifi_t) ((d->last_mifi + 1) % IP_MAXMIFS); i->mif_index = d->last_mifi; /* Register MIF in forwarding engine */ mifctl.mif6c_mifi = netif->mld.mif_index; mifctl.mif6c_flags = 0; mifctl.mif6c_pifi = (Ip_u16)netif->ifindex; if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_ADD_MIF, &mifctl, sizeof(mifctl)) >= 0) break; } if (x == IP_MAXMIFS) { IPCOM_LOG1(ERR, "ipmcp_mld_if_create: " "ipcom_setsockopt IP_MRT6_ADD_MIF %s", ipcom_strerror(ipcom_errno)); return -1; } i->startup_query_count = ipmcp.config.startup_query_count; IPCOM_TMO2_INIT(&i->timer); /* Create multicast address database on this interface */ i->maddr_head = ipcom_hash_new((Ipcom_hash_obj_func)ipmcp_mld_maddr_obj_hash, (Ipcom_hash_key_func)ipmcp_mld_addr_key_hash, (Ipcom_hash_cmp_func)ipmcp_mld_maddr_compare); ipmcp_mld_addr_change(netif, IP_TRUE); return 0; } /* *=========================================================================== * ipmcp_mld_if_destroy *=========================================================================== * Description: Destroy an interface descriptor for MLD * Parameters: . * Returns: . * */ IP_GLOBAL void ipmcp_mld_if_destroy(Ipmcp_netif_t *netif) { struct Ip_mif6ctl mifctl; ipmcp_mld_deactivate(netif); if (netif->mld.maddr_head) ipcom_hash_delete(netif->mld.maddr_head); /* Unregister MIF in forwarding engine */ mifctl.mif6c_mifi = (Ip_mifi_t)netif->mld.mif_index; mifctl.mif6c_flags = 0; mifctl.mif6c_pifi = (Ip_u16)netif->ifindex; if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_DEL_MIF, &mifctl, sizeof(mifctl)) < 0) { IPCOM_LOG1(ERR,"ipcom_setsockopt IP_MRT6_DEL_MIF: %s", ipcom_strerror(ipcom_errno)); } } /* *=========================================================================== * ipmcp_mld_upstream_if_create *=========================================================================== * Description: Creates the MLD part of the upstream interface. * Parameters: * Returns: * */ IP_GLOBAL int ipmcp_mld_upstream_if_create(Ipmcp_upstream_netif_t *netif) { struct Ip_mif6ctl mifctl; int var; if (ipmcp_mld_get_link_local_addr(netif->ifindex, &netif->mld.link_local_address) == IPMCP_LINK_LOCAL_ADR_NOTAVAIL) { IPCOM_LOG0(ERR, "MLD: Failed to get the link local address of the upstream interface"); return -1; } /* Create multicast address database on this interface */ netif->mld.maddr_head = ipcom_hash_new((Ipcom_hash_obj_func)ipmcp_mld_upstream_maddr_obj_hash, (Ipcom_hash_key_func)ipmcp_mld_addr_key_hash, (Ipcom_hash_cmp_func)ipmcp_mld_upstream_maddr_compare); if (netif->mld.maddr_head == IP_NULL) { IPCOM_LOG0(ERR, "ipmcp_mld_upstream_if_create: failed to create hash table"); return -1; } var = 1; var = ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_INIT, &var, sizeof(var)); if (var < 0) { IPCOM_LOG1(ERR,"ipmcp_mld_upstream_if_create: ipcom_setsockopt IP_MRT6_INIT", ipcom_errno); IP_PANIC(); return -1; } netif->mld.mif_index = 1; ipmcp.mld.last_mifi = 1; /* Register MIF in forwarding engine */ ipcom_memset(&mifctl, 0, sizeof(mifctl)); mifctl.mif6c_mifi = netif->mld.mif_index; mifctl.mif6c_pifi = (Ip_u16) netif->ifindex; if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_ADD_MIF, &mifctl, sizeof(mifctl)) < 0) { IPCOM_LOG1(ERR,"ipmcp_mld_upstream_if_create: ipcom_setsockopt IP_MRT6_ADD_MIF: %s", ipcom_strerror(ipcom_errno)); return -1; } return 0; } /* *=========================================================================== * ipmcp_mld_upstream_if_destroy *=========================================================================== * Description: Destroy an IPv6 upstream interface. * Parameters: * Returns: * */ IP_GLOBAL void ipmcp_mld_upstream_if_destroy(Ipmcp_upstream_netif_t *netif) { struct Ip_mif6ctl mifctl; int i; /* Unregister VIF in forwarding engine */ ipcom_memset(&mifctl, 0, sizeof(mifctl)); mifctl.mif6c_mifi = netif->mld.mif_index; mifctl.mif6c_pifi = (Ip_u16) netif->ifindex; if (ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_DEL_MIF, &mifctl, sizeof(mifctl)) < 0) { IPCOM_LOG1(ERR,"ipcom_setsockopt IP_MRT6_DEL_MIF: %s", ipcom_strerror(ipcom_errno)); } /* Destroy multicast elements in hash table */ ipcom_hash_for_each(netif->mld.maddr_head, (Ipcom_hash_foreach_cb_func)ipmcp_mld_upstream_maddr_delete_cb, IP_NULL); ipcom_hash_delete(netif->mld.maddr_head); ipcom_memset(&netif->mld, 0, sizeof(netif->mld)); i = 1; i = ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_IPV6, IP_MRT6_DONE, &i, sizeof(i)); if (i < 0) { IPCOM_LOG1(ERR, "ipmcp_mld_exit: ipcom_setsockopt IP_MRT6_DONE: %s", ipcom_strerror(ipcom_errno)); } } /* *=========================================================================== * ipmcp_mld_init *=========================================================================== * Description: Initialize MLD. * Parameters: * Returns: * */ IP_GLOBAL int ipmcp_mld_init(void) { int var; int ret; struct Ip_icmp6_filter filter; int offset; Ip_u8 hbh_buf[IPMCP_ALIGNED_SIZE(sizeof(struct Ip_ip6_hbh) + sizeof(struct Ip_ip6_opt), 8)]; Ip_u16 *router_alert_val; Ipmcp_mld_data_t *d = &ipmcp.mld; (void)ipcom_inet_pton(IP_AF_INET6, "ff02::1", &ipmcp.mld.all_hosts_addr); ipmcp.mld.mld_fd = ipcom_socket(IP_AF_INET6, IP_SOCK_RAW, IP_IPPROTO_ICMPV6); if (ipmcp.mld.mld_fd == IP_INVALID_SOCKET) { IPCOM_LOG1(ERR, "ipmcp_mld_init: ipcom_socket(AF_INET6,RAW) failed, errno = %d", ipcom_errno); IP_PANIC(); return -1; } /* Enable receive of hop-limit */ var = 1; ret = ipcom_setsockopt(ipmcp.mld.mld_fd, IP_IPPROTO_IPV6, IP_IPV6_RECVHOPLIMIT, &var, sizeof(var)); if (ret < 0) { IPCOM_LOG1(ERR,"ipmcp_mld_init: ipcom_setsockopt IP_IPV6_RECVHOPLIMIT ", ipcom_errno); IP_PANIC(); return -1; } ret = ipcom_setsockopt(ipmcp.mld.mld_fd, IP_IPPROTO_IPV6, IP_IPV6_RECVPKTINFO, &var, sizeof(var)); if (ret < 0) { IPCOM_LOG1(ERR,"ipmcp_mld_init: ipcom_setsockopt IP_IPV6_RECVPKTINFO: %s", ipcom_strerror(ipcom_errno)); IP_PANIC(); return -1; } ret = ipcom_setsockopt(ipmcp.mld.mld_fd, IP_IPPROTO_IPV6, IP_IPV6_RECVHOPOPTS, &var, sizeof(var)); if (ret < 0) { IPCOM_LOG1(ERR,"ipmcp_mld_init: ipcom_setsockopt IP_IPV6_RECVHOPOPT: %s", ipcom_strerror(ipcom_errno)); IP_PANIC(); return -1; } /* Setup icmp6 filter to block everything but MLD messages */ IP_ICMP6_FILTER_SETBLOCKALL(&filter); IP_ICMP6_FILTER_SETPASS(IPMCP_MLD_LISTENER_QUERY, &filter); IP_ICMP6_FILTER_SETPASS(IPMCP_MLD_LISTENER_REPORT, &filter); IP_ICMP6_FILTER_SETPASS(IPMCP_MLD_LISTENER_REPORT_V2, &filter); IP_ICMP6_FILTER_SETPASS(IPMCP_MLD_LISTENER_REDUCTION, &filter); ret = ipcom_setsockopt(ipmcp.mld.mld_fd, IP_IPPROTO_ICMPV6, IP_ICMP6_FILTER, &filter, sizeof(filter)); if (ret < 0) { IPCOM_LOG1(ERR,"ipcom_setsockopt IP_ICMP6_FILTER ", ipcom_errno); IP_PANIC(); return -1; } /* Create socket to receive multicast routing messages */ ipmcp.mld.mrt_fd = ipcom_socket(IP_AF_INET6, IP_SOCK_RAW, IP_IPPROTO_ICMPV6); if (ipmcp.mld.mrt_fd == IP_INVALID_SOCKET) { IPCOM_LOG1(ERR, "ipcom_socket(AF_INET6,RAW) failed, errno = %d", ipcom_errno); IP_PANIC(); return -1; } /* * Setup icmp6 filter to block everything. * Only internal messages from the MRT forwarding engine will pass through */ IP_ICMP6_FILTER_SETBLOCKALL(&filter); ret = ipcom_setsockopt(ipmcp.mld.mrt_fd, IP_IPPROTO_ICMPV6, IP_ICMP6_FILTER, &filter, sizeof(filter)); if (ret < 0) { IPCOM_LOG1(ERR,"ipcom_setsockopt IP_ICMP6_FILTER: %s", ipcom_errno); IP_PANIC(); return -1; } if ((offset = ipcom_inet6_opt_init(hbh_buf, sizeof(hbh_buf))) < 0) { IPCOM_LOG0(ERR,"ipcom_inet6_opt_init() failed"); IP_PANIC(); return -1; } offset = ipcom_inet6_opt_append(hbh_buf, sizeof(hbh_buf), offset, IP_IP6OPT_ROUTER_ALERT, sizeof(Ip_u16), 2, (void **) &router_alert_val); if (offset < 0) { IPCOM_LOG0(ERR,"ipcom_inet6_opt_append() failed"); IP_PANIC(); return -1; } *router_alert_val = IP_IP6_ALERT_MLD; if ((offset = ipcom_inet6_opt_finish(hbh_buf, sizeof(hbh_buf), offset)) < 0) { IPCOM_LOG0(ERR,"ipcom_inet6_opt_finish() failed"); IP_PANIC(); return -1; } ret = ipcom_setsockopt(ipmcp.mld.mld_fd, IP_IPPROTO_IPV6, IP_IPV6_HOPOPTS, hbh_buf, offset); if (ret < 0) { IPCOM_LOG1(ERR,"ipcom_setsockopt IP_IPV6_HOPOPTS: %s", ipcom_errno); IP_PANIC(); return -1; } /* Create the call table for the Ipmcp_mld_rec_t class */ d->rec_ops.get_type = (Ip_u8(*)(RTHIS)) ipmcp_mld_rec_get_type; d->rec_ops.get_maddr = (void*(*)(RTHIS)) ipmcp_mld_rec_get_maddr; d->rec_ops.get_num_saddrs = (unsigned(*)(RTHIS)) ipmcp_mld_rec_get_num_saddrs; d->rec_ops.get_saddr = (void*(*)(RTHIS, unsigned)) ipmcp_mld_rec_get_saddr; d->rec_ops.as_set = (Ipcom_set*(*)(RTHIS)) ipmcp_mld_rec_as_set; d->rec_ops.clear_source_list = (void(*)(RTHIS)) ipmcp_mld_rec_clear_source_list; /* Create the call table for the Ipmcp_mld_saddr_t class */ ipmcp_saddr_ops_init(&d->saddr_ops); d->saddr_ops.get_addr = (void*(*)(STHIS)) ipmcp_mld_saddr_get_addr; /* Create the call table for the Ipmcp_mld_maddr_t class */ ipmcp_maddr_ops_init(&d->maddr_ops); d->maddr_ops.dtor = (void(*)(MTHIS)) ipmcp_mld_maddr_delete; d->maddr_ops.get_addr = (void*(*)(MTHIS)) ipmcp_mld_maddr_get_addr; d->maddr_ops.compatibility_mode = (int(*)(MTHIS)) ipmcp_mld_maddr_compatibility_mode; d->maddr_ops.add_saddr = (Ipmcp_saddr_t*(*)(MTHIS, IP_CONST void*, Ip_bool)) ipmcp_mld_maddr_add_saddr; d->maddr_ops.del_saddrs_not_in_rec = (void(*)(MTHIS, Ipmcp_rec_t*, Ip_bool)) ipmcp_mld_maddr_del_saddrs_not_in_rec; d->maddr_ops.send_query = (void(*)(MTHIS, Ip_u32, Ipcom_set*)) ipmcp_mld_maddr_send_query; d->maddr_ops.group_membership_interval = (Ip_u32(*)(MTHIS)) ipmcp_mld_maddr_group_membership_interval; d->maddr_ops.update_upstream_if_filter = (void(*)(MTHIS)) ipmcp_mld_update_upstream_if_filter; /* Create the call table for the Ipmcp_mld_upstream_maddr_t class */ d->upstream_maddr_ops.dtor = (void(*)(UTHIS))ipmcp_mld_upstream_maddr_delete; d->upstream_maddr_ops.get_addr = (void*(*)(UTHIS)) ipmcp_mld_upstream_maddr_get_addr; d->upstream_maddr_ops.change_mode = (void(*)(UTHIS, Ip_bool)) ipmcp_mld_upstream_maddr_change_mode; d->upstream_maddr_ops.add_source = (void(*)(UTHIS, void*)) ipmcp_mld_upstream_maddr_add_source; d->upstream_maddr_ops.del_source = (void(*)(UTHIS, void*)) ipmcp_mld_upstream_maddr_del_source; /* Init success. */ return 0; } /* *=========================================================================== * ipmcp_mld_exit *=========================================================================== * Description: Frees resources held by the MLD module. * Parameters: . * Returns: . * */ IP_GLOBAL void ipmcp_mld_exit(void) { (void)ipcom_socketclose(ipmcp.mld.mrt_fd); ipmcp.mld.mrt_fd = IP_INVALID_SOCKET; (void)ipcom_socketclose(ipmcp.mld.mld_fd); ipmcp.mld.mld_fd = IP_INVALID_SOCKET; } /* **************************************************************************** * 11 PUBLIC FUNCTIONS **************************************************************************** */ #else /* Fix the "ISO C forbids an empty source file" warning */ int ipmcp_mld_empty_file; #endif /* IPMCP_USE_MLD */ /* **************************************************************************** * END OF FILE **************************************************************************** */