/****************************************************************************** ** ** FILE NAME : mcast_helper.c ** AUTHOR : ** DESCRIPTION : Multicast Helper module ** COPYRIGHT : Copyright (c) 2020-2021, MaxLinear, Inc. ** Copyright (c) 2014 2015 Lantiq Beteiligungs-GmbH & Co. KG ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** *******************************************************************************/ /**Header files */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mcast_helper.h" #include #include #include #ifdef CONFIG_PROC_FS #include #include #endif #include #include #include #include /** Defines **/ #define MCGID_LOOP_COUNT 1 #define MCGID_MAX_SIZE 64 #define MCH_MAC_STR_LEN 20 #define UDP_HDR_LEN (sizeof(struct udphdr)) #define TOT_HDR_LEN (sizeof(struct iphdr) + UDP_HDR_LEN) #define IP6_HDR_LEN (sizeof(struct ipv6hdr)) #define TOT6_HDR_LEN (IP6_HDR_LEN + UDP_HDR_LEN) #define MCH_UPDATE_TIMER 10 #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) #define setup_timer(t, cb, d) timer_setup(t, cb, 0) #endif /* Log level for debug prints */ static int mcast_debug; #define mch_debug(fmt, ...) \ do { \ if (mcast_debug) \ printk(KERN_DEBUG "MCH: " fmt, ##__VA_ARGS__); \ } while (0) /** Mcast helper MCGID table */ LIST_HEAD(mch_mcgid_table_g); LIST_HEAD(mch_mcgid_table6_g); /* Mcast helper MCGID lock */ static DEFINE_SPINLOCK(mch_mcgid_lock); /** Mcast helper global variables */ static u64 g_mcgid_bitmap[MCGID_LOOP_COUNT]; static struct sk_buff *skb_buff; static struct sk_buff *skb_buff6; static char mch_captured_skb = 1; static char mch_captured_skb6 = 1; static const char mch_signature[] = "mcast1234"; int mch_timerstarted; int mch_timermod; struct timer_list mch_helper_timer; static void mcast_helper_start_helper_timer(void); int mch_iptype; int mch_acl_enabled; int mch_accl_enabled = 1; #ifdef CONFIG_MCAST_HELPER_ACL mch_acl_enabled = 1; #endif /* Mcast device global variables */ static int mch_major = -1; static struct cdev mcast_cdev; static struct class *mcast_class; static bool device_created; #ifdef CONFIG_SYSCTL static struct ctl_table_header *mcast_acl_sysctl_header; static struct ctl_table_header *mcast_accl_sysctl_header; #endif /** Mcast helper function prototype */ static mcast_gid_t *mcast_helper_search_mcgid_record(struct net_device *rx_dev, ip_addr_t *saddr, ip_addr_t *gaddr, struct net_device *mem_dev, struct list_head *head); static mcast_gid_t *mcast_helper_add_mcgid_record(struct net_device *rx_dev, ip_addr_t *saddr, ip_addr_t *gaddr, u32 proto, u32 sport, u32 dport, u8 *src_mac, struct net_device *mem_dev, struct list_head *head); static mcast_member_t *mcast_helper_search_mem_record(mcast_gid_t *mcgid_rec, struct net_device *dev); static mcast_member_t *mcast_helper_add_mem_record(mcast_gid_t *mcgid_rec, struct net_device *mem_dev, u8 *macaddr); static mcast_mac_t *mcast_helper_search_mac_record(mcast_member_t *mem_rec, u8 *mac); static mcast_mac_t *mcast_helper_add_mac_record(mcast_member_t *mem_rec, u8 *mac); static void mcast_helper_update_mac_list(mcast_member_t *mem_rec, mcast_gid_t *mcgid_rec); /*============================================================================= *Function Name: mch_cb_str *Description : Function to return callback option ADD/DEL/UPDATE/DEL_UPDATE *===========================================================================*/ static inline char *mch_cb_str(u32 flag) { switch (flag) { case MCH_CB_F_ADD: return "ADD"; case MCH_CB_F_DEL: return "DEL"; case MCH_CB_F_UPD: return "UPDATE"; case MCH_CB_F_DEL_UPD: return "DEL_UPD"; default: return "?"; } } /*============================================================================= *Function Name: mcast_helper_copy_ipaddr *Description : Wrapper function to copy ip address *===========================================================================*/ static void mcast_helper_copy_ipaddr(ip_addr_t *to, ip_addr_t *from) { if (to == NULL || from == NULL) return; memcpy(to, from, sizeof(ip_addr_t)); } /*============================================================================= *Function Name: mcast_helper_is_same_ipaddr *Description : Wrapper function to compare IP address *===========================================================================*/ static int mcast_helper_is_same_ipaddr(ip_addr_t *addr1, ip_addr_t *addr2) { if (addr1 == NULL || addr2 == NULL) return 0; if (addr1->ip_type == MCH_IPV4 && addr2->ip_type == MCH_IPV4) return addr1->addr.ip4.s_addr == addr2->addr.ip4.s_addr; else if (addr1->ip_type == MCH_IPV6 && addr2->ip_type == MCH_IPV6) return ipv6_addr_equal(&addr1->addr.ip6, &addr2->addr.ip6); return 0; } /*============================================================================= *Function Name: mcast_helper_init_ipaddr *Description : Wrapper function to initialize IP address *===========================================================================*/ static void mcast_helper_init_ipaddr(ip_addr_t *addr, ptype_t type, void *addrp) { if (addr == NULL) return; addr->ip_type = type; if (type == MCH_IPV4) { if (addrp) addr->addr.ip4.s_addr = *((u32 *)addrp); else addr->addr.ip4.s_addr = 0; } else if (type == MCH_IPV6) { struct in6_addr *in6 = (struct in6_addr *)addrp; if (addrp) memcpy(&(addr->addr.ip6), in6, sizeof(struct in6_addr)); else memset(&(addr->addr.ip6), 0, sizeof(struct in6_addr)); } } /*============================================================================= *Function Name: mcast_helper_is_addr_unspecified *Description : Wrapper function to ip address is specified *===========================================================================*/ static int mcast_helper_is_addr_unspecified(ip_addr_t *addr) { if (addr->ip_type == MCH_IPV4) return (addr->addr.ip4.s_addr == 0); else if (addr->ip_type == MCH_IPV6) return ipv6_addr_any(&addr->addr.ip6); return 0; } /*============================================================================= *Function Name: mcast_helper_list_p *Description : Wrapper function to get the membership list pointer *===========================================================================*/ static struct list_head *mcast_helper_list_p(ptype_t type) { if (type == MCH_IPV4) return &mch_mcgid_table_g; else if (type == MCH_IPV6) return &mch_mcgid_table6_g; return NULL; } /*============================================================================= *Function Name: mch_get_netif *Description : Wrapper function to get netif from interface name *===========================================================================*/ static struct net_device *mch_get_netif(char *ifname) { struct net_device *netif; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 23) netif = dev_get_by_name(ifname); #else netif = dev_get_by_name(&init_net, ifname); #endif if (netif) { dev_put(netif); return netif; } else { return NULL; } } /*============================================================================= *Function Name: mch_get_netif_by_idx *Description : Wrapper function to get netif from interface index *===========================================================================*/ static struct net_device *mch_get_netif_by_idx(int ifindex) { struct net_device *netif; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 23) netif = dev_get_by_index(ifindex); #else netif = dev_get_by_index(&init_net, ifindex); #endif if (netif) { dev_put(netif); return netif; } else { return NULL; } } /*============================================================================= *Function Name: mcast_helper_invoke_return_callback *Description : Function which invoke the registerd callback from PPA/WLAN .. *===========================================================================*/ static int mcast_helper_invoke_return_callback(mcast_member_t *mem_rec, mcast_gid_t *mcgid_rec, u32 flag) { int nRet = SUCCESS; struct net_device *mem_dev = mem_rec->mem_dev; mcast_stream_t *stream = &mcgid_rec->mc_stream; if (mch_accl_enabled == 0) return SUCCESS; /* Updates the MAC list before callback */ mcast_helper_update_mac_list(mem_rec, mcgid_rec); if (mcgid_rec->mc_stream.num_joined_macs == 0) { mch_debug("no MAC present, so not invoking callback\n"); return SUCCESS; } nRet = mcast_helper_invoke_callback(mcgid_rec->gid, mem_dev, stream, flag); if (stream->src_ip.ip_type == MCH_IPV4) { mch_debug("CB_%s for member(%s): stream from(%s)# sIP=%pI4" ", dIP=%pI4, proto=%#x, sPort=%d, dPort=%d\n", mch_cb_str(flag), mem_dev->name, stream->rx_dev->name, &stream->src_ip.addr, &stream->dst_ip.addr, stream->proto, stream->src_port, stream->dst_port); } else if (stream->src_ip.ip_type == MCH_IPV6) { mch_debug("CB_%s for member(%s): stream from(%s)# sIP=%pI6c" ", dIP=%pI6c, proto=%#x, sPort=%d, dPort=%d\n", mch_cb_str(flag), mem_dev->name, stream->rx_dev->name, &stream->src_ip.addr, &stream->dst_ip.addr, stream->proto, stream->src_port, stream->dst_port); } return nRet; } /*============================================================================= *Function Name: mcast_helper_ipv6_link_local_word3 *Description : Function to check link local multicast last word *===========================================================================*/ static bool mcast_helper_ipv6_link_local_word3(__be32 addr) { if (addr == 0) return false; /* Validate ranges */ if (addr <= htonl(0x00000013)) return true; if ((addr >= htonl(0x0000006a)) && (addr <= htonl(0x0000006f))) return true; if ((addr >= htonl(0x00010001)) && (addr <= htonl(0x00010007))) return true; /* Validate specific values */ switch (ntohl(addr)) { case 0x00000016: /* All MLDv2-capable routers */ case 0x0000001a: /* all-RPL-nodes */ case 0x000000fb: /* mDNSv6 */ case 0x00010001: /* Link Name */ case 0x00010002: /* All_DHCP_Relay_Agents_and_Servers */ case 0x00010003: /* Link-local Multicast Name Resolution */ case 0x00010004: /* DTCP Announcement */ case 0x00010005: /* afore_vdp */ case 0x00010006: /* Babel */ case 0x00010007: /* DLEP Discovery */ return true; } return false; } /*============================================================================= *Function Name: mcast_helper_is_ll_multicast *Description : Function to check link local multicast address to be skipped *===========================================================================*/ static bool mcast_helper_is_ll_multicast(ip_addr_t *gaddr) { if (gaddr->ip_type == MCH_IPV4) { /* IPv4 link-local multicast space 224.0.0.0/24 * and IPv4 Service Discovery Protocol address 239.255.255.250 */ if (ipv4_is_local_multicast(gaddr->addr.ip4.s_addr) || (gaddr->addr.ip4.s_addr == htonl(0xEFFFFFFAU))) { return true; } } else if (gaddr->ip_type == MCH_IPV6) { struct in6_addr *addr = &gaddr->addr.ip6; /* Validate all link local address - ref IANA */ if ((((addr->s6_addr32[0] ^ htonl(0xff020000)) | addr->s6_addr32[1] | addr->s6_addr32[2]) == 0) && mcast_helper_ipv6_link_local_word3(addr->s6_addr32[3])) { return true; } } return false; } /*============================================================================= *Function Name: mcast_helper_attach_mcgid *Description : Function to attach the multicast GID into skb *===========================================================================*/ static void mcast_helper_attach_mcgid(struct sk_buff *skb, int mc_gid) { if (!mc_gid) return; #if IS_ENABLED(CONFIG_SKB_EXTENSION) /* FIXME: need to attach GID into extension */ #else /* Attach MCGID into skb */ skb->mc_gid = mc_gid; #endif } /*============================================================================= *Function Name: mcast_helper_update_mcgid_table *Description : Function to ADD/UPDATE MCGID entry for a stream and * inform callback *===========================================================================*/ static mcast_gid_t * mcast_helper_update_mcgid_table(struct sk_buff *skb, ip_addr_t *saddr, ip_addr_t *gaddr, u32 proto, u32 sPort, u32 dPort, struct net_device *mem_dev, u8 *host_mac) { mcast_gid_t *mcgid_rec; mcast_member_t *mem_rec = NULL; mcast_mac_t *mac_rec = NULL; struct net_device *rx_dev = NULL; struct list_head *mcgid_list; u32 flag = MCH_CB_F_UPD; u8 *src_mac = eth_hdr(skb)->h_source; int mc_gid = 0; if (skb->protocol == htons(ETH_P_IP)) mcgid_list = mcast_helper_list_p(MCH_IPV4); else mcgid_list = mcast_helper_list_p(MCH_IPV6); /* * T&C for stream creating MCGID: * 1. Host tracking must be supported by Kernel * 2. Callback to ADD/UPDATE (for 2nd host onwards - if already joined) */ rx_dev = mch_get_netif_by_idx(skb->skb_iif); if (rx_dev == NULL) { mch_debug("failed to find RX device\n"); return NULL; } spin_lock_bh(&mch_mcgid_lock); mcgid_rec = mcast_helper_search_mcgid_record(rx_dev, saddr, gaddr, mem_dev, mcgid_list); if (mcgid_rec == NULL) { /* ADD MCGID entry */ flag = MCH_CB_F_ADD; mcgid_rec = mcast_helper_add_mcgid_record(rx_dev, saddr, gaddr, proto, sPort, dPort, src_mac, mem_dev, mcgid_list); if (mcgid_rec == NULL) { mch_debug("failed to add MCGID record\n"); goto done; } } else { /* Update the member table to add new interface into the list */ mem_rec = mcast_helper_search_mem_record(mcgid_rec, mem_dev); } mc_gid = mcgid_rec->gid; if (mem_rec) { /* Host tracking for a member device */ mac_rec = mcast_helper_search_mac_record(mem_rec, host_mac); if (mac_rec) { goto done; } mac_rec = mcast_helper_add_mac_record(mem_rec, host_mac); if (mac_rec == NULL) { mch_debug("failed to add host into member\n"); goto done; } } else { mem_rec = mcast_helper_add_mem_record(mcgid_rec, mem_dev, host_mac); if (mem_rec == NULL) { mch_debug("failed to add member into MCGID record\n"); goto done; } } mcast_helper_invoke_return_callback(mem_rec, mcgid_rec, flag); done: spin_unlock_bh(&mch_mcgid_lock); /* Attach MCGID into skb */ mcast_helper_attach_mcgid(skb, mc_gid); return mcgid_rec; } /*============================================================================= *Function Name: mcast_helper_bridge_learning *Description : Function to retrive 5-tuple info from bridge IPV4/IPV6 stream. * Also, callback to ADD/UPDATE for host already JOINed. *===========================================================================*/ static void mcast_helper_bridge_learning(struct sk_buff *skb, struct net_device *mem_dev, u8 *host_mac) { ip_addr_t saddr, gaddr; if (skb->protocol == htons(ETH_P_IP)) { struct iphdr *iph = ip_hdr(skb); struct udphdr *udph = (struct udphdr *)((u8 *)iph + (iph->ihl << 2)); mcast_helper_init_ipaddr(&gaddr, MCH_IPV4, &iph->daddr); if (mcast_helper_is_ll_multicast(&gaddr)) return; mcast_helper_init_ipaddr(&saddr, MCH_IPV4, &iph->saddr); if (mch_captured_skb && mch_acl_enabled) { skb_buff = skb_copy(skb, GFP_ATOMIC); mch_captured_skb = 0; } /* ADD/UPDATE MCGID table and inform callback for a host JOINed */ mcast_helper_update_mcgid_table(skb, &saddr, &gaddr, iph->protocol, udph->source, udph->dest, mem_dev, host_mac); } else if (skb->protocol == htons(ETH_P_IPV6)) { struct ipv6hdr *iph6 = ipv6_hdr(skb); /* * XXX: IP packet received from bridge hook, * cannot use udp_hdr() directly to get udp details */ struct udphdr *udph6 = (struct udphdr *)((u8 *)iph6 + IP6_HDR_LEN); mcast_helper_init_ipaddr(&gaddr, MCH_IPV6, &iph6->daddr); if (mcast_helper_is_ll_multicast(&gaddr)) return; mcast_helper_init_ipaddr(&saddr, MCH_IPV6, &iph6->saddr); if (mch_captured_skb6 && mch_acl_enabled) { skb_buff6 = skb_copy(skb, GFP_ATOMIC); mch_captured_skb6 = 0; } /* ADD/UPDATE MCGID table and inform callback for a host JOINed */ mcast_helper_update_mcgid_table(skb, &saddr, &gaddr, iph6->nexthdr, udph6->source, udph6->dest, mem_dev, host_mac); } return; } /*============================================================================= * Function Name: mcast_helper_allocate_mcgid * Description : Function to allocate group index *===========================================================================*/ static int mcast_helper_allocate_mcgid(void) { u32 index = 0, i = 0; int gid = -1; for (index = 0; index < MCGID_LOOP_COUNT; index++) { for (i = 0; i < MCGID_MAX_SIZE; i++) { if ((g_mcgid_bitmap[index] & BIT_ULL(i)) == 0) { g_mcgid_bitmap[index] |= BIT_ULL(i); gid = i + 1 + (MCGID_MAX_SIZE * index); return gid; } } } return gid; } /*============================================================================= *Function Name: mcast_helper_release_mcgid *Description : Function to release allocated group index *===========================================================================*/ static void mcast_helper_release_mcgid(int mcgid) { u32 index = 0, index1 = 0; if (mcgid > MCGID_MAX_SIZE) index = 1; /* index 1 not possible as MCGID_MAX_SIZE=64 */ for (index1 = 0; index1 < MCGID_MAX_SIZE; index1++) { if (mcgid == index1) { g_mcgid_bitmap[index] &= ~BIT_ULL(mcgid - 1); break; } } } /*============================================================================= * function name: mcast_helper_update_mac_list * description : Function updates the mac list which will be passed * to registered call back *===========================================================================*/ static void mcast_helper_update_mac_list(mcast_member_t *mem_rec, mcast_gid_t *mcgid_rec) { mcast_stream_t *stream = &mcgid_rec->mc_stream; mcast_mac_t *mac_rec = NULL, *tmp = NULL; memset(stream->macaddr, 0, MAX_MAC * ETH_ALEN); stream->num_joined_macs = 0; list_for_each_entry_safe (mac_rec, tmp, &mem_rec->macaddr_list, list) { if (stream->num_joined_macs < MAX_MAC) { memcpy(stream->macaddr[stream->num_joined_macs], mac_rec->macaddr, ETH_ALEN); stream->num_joined_macs++; } } } /*============================================================================= *function name: mcast_helper_search_mac_record *description : function to search MAC record in a member record *===========================================================================*/ static mcast_mac_t * mcast_helper_search_mac_record(mcast_member_t *mem_rec, u8 *mac) { mcast_mac_t *mac_rec = NULL, *tmp = NULL; list_for_each_entry_safe (mac_rec, tmp, &mem_rec->macaddr_list, list) { if (memcmp(mac_rec->macaddr, mac, ETH_ALEN) == 0) { return mac_rec; } } return NULL; } /*============================================================================= *function name: mcast_helper_add_mac_record *description : function to add MAC address into a member record *===========================================================================*/ static mcast_mac_t * mcast_helper_add_mac_record(mcast_member_t *mem_rec, u8 *macaddr) { mcast_mac_t *mac_rec; if (mem_rec == NULL || macaddr == NULL) return NULL; mac_rec = kmalloc(sizeof(mcast_mac_t), GFP_ATOMIC); if (mac_rec == NULL) return NULL; memcpy(mac_rec->macaddr, macaddr, sizeof(char) * ETH_ALEN); mem_rec->macaddr_count++; INIT_LIST_HEAD(&mac_rec->list); list_add_tail_rcu(&mac_rec->list, &mem_rec->macaddr_list); mch_debug("adding MAC=%pM into member=%s, MAC count=%d\n", macaddr, mem_rec->mem_dev->name, mem_rec->macaddr_count); return mac_rec; } /*============================================================================= *Function Name: mcast_helper_delete_mac_record *Description : Function which delete the entry from MAC record *===========================================================================*/ static void mcast_helper_delete_mac_record(mcast_mac_t *mac_rec, mcast_member_t *mem_rec) { if ((mac_rec == NULL) || (mem_rec == NULL)) return; mem_rec->macaddr_count--; mch_debug("deleting MAC=%pM from member=%s, MAC remains=%d\n", mac_rec->macaddr, mem_rec->mem_dev->name, mem_rec->macaddr_count); list_del_rcu(&mac_rec->list); kfree_rcu(mac_rec, rcu); } /*============================================================================= *Function Name: mcast_helper_search_mem_record *Description : Function to search and get the member recod based on device *===========================================================================*/ static mcast_member_t * mcast_helper_search_mem_record(mcast_gid_t *mcgid_rec, struct net_device *dev) { mcast_member_t *mem_rec = NULL, *tmp = NULL; list_for_each_entry_safe (mem_rec, tmp, &mcgid_rec->mc_mem_list, list) { if (mem_rec->mem_dev == dev) return mem_rec; } return NULL; } /*============================================================================= *Function Name: mcast_helper_add_mem_record *Description : Function which add the entry in member record *===========================================================================*/ static mcast_member_t * mcast_helper_add_mem_record(mcast_gid_t *mcgid_rec, struct net_device *mem_dev, u8 *macaddr) { mcast_member_t *mem_rec = NULL; mcast_mac_t *mac_rec = NULL; if (mcgid_rec == NULL) return NULL; mem_rec = kmalloc(sizeof(mcast_member_t), GFP_ATOMIC); if (mem_rec == NULL) return NULL; mem_rec->mem_dev = mem_dev; mem_rec->macaddr_count = 0; INIT_LIST_HEAD(&mem_rec->macaddr_list); mac_rec = mcast_helper_add_mac_record(mem_rec, macaddr); if (mac_rec == NULL) { kfree(mem_rec); return NULL; } #ifdef CONFIG_MCAST_HELPER_ACL if (mch_acl_enabled) mem_rec->acl_blocked = 0; #endif INIT_LIST_HEAD(&mem_rec->list); list_add_tail_rcu(&mem_rec->list, &mcgid_rec->mc_mem_list); mch_debug("adding member=%s into MCGID record=%p with GID=%d\n", mem_dev->name, mcgid_rec, mcgid_rec->gid); return mem_rec; } /*============================================================================= *Function Name: mcast_helper_delete_mem_record *Description : Function which delete the entry from member record *===========================================================================*/ static void mcast_helper_delete_mem_record(mcast_member_t *mem_rec, mcast_gid_t *mcgid_rec) { if ((mem_rec == NULL) || (mcgid_rec == NULL)) return; if (!list_empty(&mem_rec->macaddr_list)) { mch_debug("cannot delete member, MAC list not empty\n"); return; } mch_debug("deleting member=%s from MCGID record=%p with GID=%d\n", mem_rec->mem_dev->name, mcgid_rec, mcgid_rec->gid); list_del_rcu(&mem_rec->list); kfree_rcu(mem_rec, rcu); } /*============================================================================= *function name: mcast_helper_get_mcgid_record *description : function to get the mcgid record *===========================================================================*/ static mcast_gid_t * mcast_helper_get_mcgid_record(struct list_head *head, int mcgid) { mcast_gid_t *mcgid_rec = NULL, *tmp = NULL; list_for_each_entry_safe (mcgid_rec, tmp, head, list) { if (mcgid_rec->gid == mcgid) return mcgid_rec; } return NULL; } /*============================================================================= *Function Name: mcast_helper_search_mcgid_record *Description : Function to search gaddr and saddr in the gid record list *===========================================================================*/ static mcast_gid_t * mcast_helper_search_mcgid_record(struct net_device *rx_dev, ip_addr_t *saddr, ip_addr_t *gaddr, struct net_device *mem_dev, struct list_head *head) { mcast_gid_t *mcgid_rec = NULL, *tmp = NULL; mcast_stream_t *stream; u16 vid = VLAN_VID_MASK; if (mem_dev && is_vlan_dev(mem_dev)) vid = vlan_dev_vlan_id(mem_dev); list_for_each_entry_safe (mcgid_rec, tmp, head, list) { if (vid != mcgid_rec->eg_vlan) continue; /* EG VLAN matched (for non VLAN, VLAN_VID_MASK matched) */ stream = &mcgid_rec->mc_stream; if (rx_dev && (rx_dev != stream->rx_dev)) continue; /* IG interface matched */ if (!mcast_helper_is_same_ipaddr(&stream->dst_ip, gaddr)) continue; /* ASM: group IP matched */ if (!mcast_helper_is_addr_unspecified(saddr) && !mcast_helper_is_addr_unspecified(&stream->src_ip) && !mcast_helper_is_same_ipaddr(&stream->src_ip, saddr)) continue; /* SSM: source IP matched (if specified) */ return mcgid_rec; } return NULL; } /*============================================================================= *Function Name: mcast_helper_add_mcgid_record *Description : Function to create and add 5 tuple entry into mcgid table *===========================================================================*/ static mcast_gid_t * mcast_helper_add_mcgid_record(struct net_device *rx_dev, ip_addr_t *saddr, ip_addr_t *gaddr, u32 proto, u32 sport, u32 dport, u8 *src_mac, struct net_device *mem_dev, struct list_head *head) { mcast_gid_t *mcgid_rec = NULL; u16 vid = VLAN_VID_MASK; int mcgid = -1; mcgid = mcast_helper_allocate_mcgid(); if (mcgid == -1) return NULL; mcgid_rec = kzalloc(sizeof(mcast_gid_t), GFP_ATOMIC); if (mcgid_rec == NULL) { mch_debug("failed to allocate MCGID\n"); return NULL; } if (mem_dev && is_vlan_dev(mem_dev)) vid = vlan_dev_vlan_id(mem_dev); mcgid_rec->mc_stream.rx_dev = rx_dev; mcast_helper_copy_ipaddr(&(mcgid_rec->mc_stream.src_ip), saddr); mcast_helper_copy_ipaddr(&(mcgid_rec->mc_stream.dst_ip), gaddr); mcgid_rec->mc_stream.proto = proto; mcgid_rec->mc_stream.src_port = sport; mcgid_rec->mc_stream.dst_port = dport; /* FIXME: if RX device is under bridge, pass MAC to HW (fix routed) */ if (netif_is_bridge_port(rx_dev)) memcpy(mcgid_rec->mc_stream.src_mac, src_mac, ETH_ALEN); mcgid_rec->mc_stream.num_joined_macs = 0; mcgid_rec->gid = mcgid; mcgid_rec->eg_vlan = vid; #ifdef CONFIG_MCAST_HELPER_ACL mcgid_rec->oif_bitmap = 0; mcgid_rec->probe_flag = 0; #endif INIT_LIST_HEAD(&mcgid_rec->list); INIT_LIST_HEAD(&mcgid_rec->mc_mem_list); list_add_tail_rcu(&mcgid_rec->list, head); mch_debug("adding MCGID record=%p with GID=%d\n", mcgid_rec, mcgid); return mcgid_rec; } /*============================================================================= *Function Name: mcast_helper_delete_mcgid_record *Description : Function to delete a mcgid table entry *===========================================================================*/ static void mcast_helper_delete_mcgid_record(mcast_gid_t *mcgid_rec) { if (mcgid_rec == NULL) return; if (!list_empty(&mcgid_rec->mc_mem_list)) { mch_debug("cannot delete MCGID record, member list not empty\n"); return; } mch_debug("deleting MCGID record=%p for GID=%d\n", mcgid_rec, mcgid_rec->gid); mcast_helper_release_mcgid(mcgid_rec->gid); list_del_rcu(&mcgid_rec->list); kfree_rcu(mcgid_rec, rcu); } /*============================================================================= *Function Name: mcast_helper_make_skb_writeable *Description : Function to make skb writeable *===========================================================================*/ static int mcast_helper_make_skb_writeable(struct sk_buff *skb, int write_len) { if (!pskb_may_pull(skb, write_len)) return -ENOMEM; if (!skb_cloned(skb) || skb_clone_writable(skb, write_len)) return 0; return pskb_expand_head(skb, 0, 0, GFP_ATOMIC); } /*============================================================================= *Function Name: mcast_helper_set_ip_addr *Description : Function to set ip address in the skb *===========================================================================*/ static void mcast_helper_set_ip_addr(struct sk_buff *skb, struct iphdr *nh, u32 *addr, u32 new_addr) { int transport_len = skb->len - skb_transport_offset(skb); if (nh->protocol == IPPROTO_TCP) { if (likely(transport_len >= sizeof(struct tcphdr))) inet_proto_csum_replace4(&tcp_hdr(skb)->check, skb, *addr, new_addr, 1); } else if (nh->protocol == IPPROTO_UDP) { if (likely(transport_len >= sizeof(struct udphdr))) { struct udphdr *udph = udp_hdr(skb); if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) { inet_proto_csum_replace4(&udph->check, skb, *addr, new_addr, 1); if (!udph->check) udph->check = CSUM_MANGLED_0; } } } csum_replace4(&nh->check, *addr, new_addr); *addr = new_addr; } /*============================================================================= *function name: mcast_helper_update_ipv6_checksum *description : function to update ipv6 checksum in the passed skb *===========================================================================*/ static void mcast_helper_update_ipv6_checksum(struct sk_buff *skb, u32 addr[4], u32 new_addr[4]) { int transport_len = skb->len - skb_transport_offset(skb); if (likely(transport_len >= sizeof(struct udphdr))) { struct udphdr *udph = udp_hdr(skb); if (udph->check || (skb->ip_summed == CHECKSUM_PARTIAL)) { inet_proto_csum_replace16(&udph->check, skb, addr, new_addr, 1); if (!udph->check) udph->check = CSUM_MANGLED_0; } } } /*============================================================================= *function name: mcast_helper_set_ipv6_addr *description : function to set the ipaddress in the skb *===========================================================================*/ static void mcast_helper_set_ipv6_addr(struct sk_buff *skb, u32 addr[4], u32 new_addr[4], bool recalculate_csum) { if (recalculate_csum) mcast_helper_update_ipv6_checksum(skb, addr, new_addr); memcpy(addr, new_addr, sizeof(__be32[4])); } /*============================================================================= *function name: mcast_helper_set_ipv6 *description : function to update the ipv6 address in skb *===========================================================================*/ static int mcast_helper_set_ipv6(struct sk_buff *skb, ip_addr_t *new_saddr, ip_addr_t *new_daddr) { struct ipv6hdr *nh; int err; u32 *saddr, *daddr; err = mcast_helper_make_skb_writeable(skb, skb_network_offset(skb) + sizeof(struct ipv6hdr)); if (unlikely(err)) return err; nh = ipv6_hdr(skb); saddr = (u32 *)&nh->saddr; daddr = (u32 *)&nh->daddr; if (memcmp(&(new_saddr->addr.ip6), saddr, sizeof(struct in6_addr))) mcast_helper_set_ipv6_addr(skb, saddr, (u32 *)&(new_saddr->addr.ip6), true); if (memcmp(&(new_daddr->addr.ip6), daddr, sizeof(struct in6_addr))) mcast_helper_set_ipv6_addr(skb, daddr, (u32 *)&(new_daddr->addr.ip6), true); return 0; } /*============================================================================= *function name: mcast_helper_set_ipv4 *description : function to update the ipv4 address in skb *===========================================================================*/ static int mcast_helper_set_ipv4(struct sk_buff *skb, ip_addr_t *saddr, ip_addr_t *daddr) { struct iphdr *nh; int err; err = mcast_helper_make_skb_writeable(skb, skb_network_offset(skb) + sizeof(struct iphdr)); if (unlikely(err)) return err; nh = ip_hdr(skb); if (saddr->addr.ip4.s_addr != nh->saddr) mcast_helper_set_ip_addr(skb, nh, &nh->saddr, saddr->addr.ip4.s_addr); if (daddr->addr.ip4.s_addr != nh->daddr) mcast_helper_set_ip_addr(skb, nh, &nh->daddr, daddr->addr.ip4.s_addr); return 0; } /*============================================================================= *function name: mcast_helper_set_port *description : function to update the port info in skb *===========================================================================*/ static void mcast_helper_set_port(struct sk_buff *skb, unsigned short *port, unsigned short new_port, unsigned short *check) { inet_proto_csum_replace2(check, skb, *port, new_port, 0); *port = new_port; } /*============================================================================= *function name: mcast_helper_set_udp_port *description : function to update the udp port in skb *===========================================================================*/ static void mcast_helper_set_udp_port(struct sk_buff *skb, unsigned short *port, unsigned short new_port) { struct udphdr *udph = udp_hdr(skb); if (udph->check && skb->ip_summed != CHECKSUM_PARTIAL) { mcast_helper_set_port(skb, port, new_port, &udph->check); if (!udph->check) udph->check = CSUM_MANGLED_0; } else { *port = new_port; } } /*============================================================================= *function name: mcast_helper_set_udp *description : function to update the udp header in skb *===========================================================================*/ static void mcast_helper_set_udp(struct sk_buff *skb, unsigned short udp_src, unsigned short udp_dst) { struct udphdr *udph; int err; err = mcast_helper_make_skb_writeable(skb, skb_transport_offset(skb) + sizeof(struct udphdr)); if (unlikely(err)) return; udph = udp_hdr(skb); if (udp_src != udph->source) mcast_helper_set_udp_port(skb, &udph->source, udp_src); if (udp_dst != udph->dest) mcast_helper_set_udp_port(skb, &udph->dest, udp_dst); return; } /*============================================================================= *function name: mcast_helper_set_sig *description : function to insert the signature in skb *===========================================================================*/ static void mcast_helper_set_sig(struct sk_buff *skb, struct net_device *netdev, int mcgid, int flag) { u8 *data = NULL; u32 data_len = 0, extra_data_len = -1; int index = 0; if (flag == MCH_IPV6) data_len = skb->len - TOT6_HDR_LEN; else data_len = skb->len - TOT_HDR_LEN; extra_data_len = sizeof(mch_signature) + 8; index = sizeof(mch_signature) - 1; data = (u8 *)udp_hdr(skb) + UDP_HDR_LEN; if (data_len > extra_data_len) { memcpy(data, mch_signature, sizeof(mch_signature)); data[index] = (u8)(mcgid & 0xFF); data[index + 1] = (u8)(netdev->ifindex & 0xFF); } } /*============================================================================= *function name: mcast_helper_acl_probe_pckt_send *description : function to send the IPV4 probe packet *===========================================================================*/ static u32 mcast_helper_acl_probe_pckt_send(struct net_device *inetdev, struct net_device *onetdev, int mcgid, ip_addr_t *gaddr, ip_addr_t *saddr, u32 proto, u32 sport, u32 dport) { struct iphdr *iph = NULL; struct sk_buff *newskb = NULL; if (skb_buff == NULL) return 0; if (ip_hdr(skb_buff)->protocol == IPPROTO_UDP) { newskb = skb_copy(skb_buff, GFP_ATOMIC); if (newskb != NULL) { iph = (struct iphdr *)skb_network_header(newskb); mcast_helper_set_ipv4(newskb, saddr, gaddr); mcast_helper_set_udp(newskb, sport, dport); #if 0 mcast_helper_set_eth_addr(newskb, newskb->dev->dev_addr, eth_hdr(newskb)->h_dest); #endif newskb->dev = inetdev; mcast_helper_set_sig(newskb, onetdev, mcgid, MCH_IPV4); /* Insert the skb in to input queue */ netif_receive_skb(newskb); return 1; } } return 0; } /*============================================================================= *function name: mcast_helper_acl_probe_pckt_send6 *description : function to send the IPV6 probe packet *===========================================================================*/ static u32 mcast_helper_acl_probe_pckt_send6(struct net_device *inetdev, struct net_device *onetdev, int mcgid, ip_addr_t *gaddr, ip_addr_t *saddr, u32 proto, u32 sport, u32 dport) { struct sk_buff *newskb = NULL; if (skb_buff6 == NULL) return NF_ACCEPT; if (ipv6_hdr(skb_buff6)->nexthdr == IPPROTO_UDP) { newskb = skb_copy(skb_buff6, GFP_ATOMIC); if (newskb != NULL) { mcast_helper_set_ipv6(newskb, saddr, gaddr); mcast_helper_set_udp(newskb, sport, dport); #if 0 mcast_helper_set_eth_addr(newskb, newskb->dev->dev_addr, eth_hdr(newskb)->h_dest); #endif newskb->dev = inetdev; mcast_helper_set_sig(newskb, onetdev, mcgid, MCH_IPV6); /* Insert the skb in to input queue */ netif_receive_skb(newskb); } } return NF_ACCEPT; } /*============================================================================= *function name: mcast_helper_update_entry *description : function searches the gid record and then updated the member * record and then send the probe packets by starting the * proble pckt expiry timer *===========================================================================*/ /* Call the function to check if GID exist for this group in MCGID table */ static int mcast_helper_update_entry(struct net_device *mem_dev, mcast_rec_t *mc_rec) { int nRet = FAILURE, status = 0; mcast_gid_t *mcgid_rec = NULL; mcast_member_t *mem_rec = NULL; mcast_mac_t *mac_rec = NULL; u32 flag = MCH_CB_F_UPD; struct list_head *mcgid_list = NULL; mcgid_list = mcast_helper_list_p(mc_rec->group_ip.ip_type); /* TODO: there may be multiple entries need to loop for VLAN over WiFi */ spin_lock_bh(&mch_mcgid_lock); mcgid_rec = mcast_helper_search_mcgid_record(NULL, &mc_rec->src_ip, &mc_rec->group_ip, mem_dev, mcgid_list); if (mcgid_rec == NULL) goto done; /* Update the member table to add the new interface into the list */ mem_rec = mcast_helper_search_mem_record(mcgid_rec, mem_dev); if (mem_rec == NULL) { mem_rec = mcast_helper_add_mem_record(mcgid_rec, mem_dev, mc_rec->macaddr); if (mem_rec == NULL) goto done; } else { mac_rec = mcast_helper_search_mac_record(mem_rec, mc_rec->macaddr); if (mac_rec) goto done; mac_rec = mcast_helper_add_mac_record(mem_rec, mc_rec->macaddr); if (mac_rec == NULL) goto done; } if (mch_acl_enabled) { /* Start the timer here */ mcast_helper_start_helper_timer(); #ifdef CONFIG_MCAST_HELPER_ACL mcgid_rec->probe_flag = 1; #endif /*Send the Skb probe packet on interfaces */ if (mcgid_rec->mc_stream.src_ip.ip_type == MCH_IPV6) { mch_iptype = MCH_IPV6; mcast_helper_acl_probe_pckt_send6( mcgid_rec->mc_stream.rx_dev, mem_dev, mcgid_rec->gid, &mcgid_rec->mc_stream.dst_ip, &mcgid_rec->mc_stream.src_ip, mcgid_rec->mc_stream.proto, mcgid_rec->mc_stream.src_port, mcgid_rec->mc_stream.dst_port); } else { mch_iptype = MCH_IPV4; status = mcast_helper_acl_probe_pckt_send( mcgid_rec->mc_stream.rx_dev, mem_dev, mcgid_rec->gid, &mcgid_rec->mc_stream.dst_ip, &mcgid_rec->mc_stream.src_ip, mcgid_rec->mc_stream.proto, mcgid_rec->mc_stream.src_port, mcgid_rec->mc_stream.dst_port); if (status == 0) goto done; } } else { mcast_helper_invoke_return_callback(mem_rec, mcgid_rec, flag); } nRet = SUCCESS; done: spin_unlock_bh(&mch_mcgid_lock); return nRet; } /*============================================================================= *function name: mcast_helper_delete_entry *description : function searches and delted the entry in gid/member record * based on the prameters received from user space *===========================================================================*/ /* Call the function to check if GID exist for this group in MCGID table */ static int mcast_helper_delete_entry(struct net_device *mem_dev, mcast_rec_t *mc_rec) { int nRet = FAILURE; mcast_gid_t *mcgid_rec = NULL; mcast_member_t *mem_rec = NULL; mcast_mac_t *mac_rec = NULL; struct list_head *mcgid_list = NULL; mcgid_list = mcast_helper_list_p(mc_rec->group_ip.ip_type); /* TODO: there may be multiple entries need to loop for VLAN over WiFi */ spin_lock_bh(&mch_mcgid_lock); mcgid_rec = mcast_helper_search_mcgid_record(NULL, &mc_rec->src_ip, &mc_rec->group_ip, mem_dev, mcgid_list); if (mcgid_rec == NULL) goto done; /* Update the member table to del mcast member mapping table */ mem_rec = mcast_helper_search_mem_record(mcgid_rec, mem_dev); if (mem_rec == NULL) goto done; mac_rec = mcast_helper_search_mac_record(mem_rec, mc_rec->macaddr); if (mac_rec && mem_rec->macaddr_count <= 1) { /* MAC count 1 and matching, first inform then remove */ mcast_helper_invoke_return_callback(mem_rec, mcgid_rec, MCH_CB_F_DEL); mcast_helper_delete_mac_record(mac_rec, mem_rec); mcast_helper_delete_mem_record(mem_rec, mcgid_rec); } else if (mac_rec) { mcast_helper_delete_mac_record(mac_rec, mem_rec); mcast_helper_invoke_return_callback(mem_rec, mcgid_rec, MCH_CB_F_DEL_UPD); } if (list_empty(&mcgid_rec->mc_mem_list)) { /* As there is no member, delete entry from MCGID table */ mcast_helper_delete_mcgid_record(mcgid_rec); } nRet = SUCCESS; done: spin_unlock_bh(&mch_mcgid_lock); return nRet; } /*============================================================================= *function name: mcast_helper_ioctl *description : IOCTL handler functon *===========================================================================*/ static long mcast_helper_ioctl(struct file *f, u32 cmd, unsigned long arg) { mcast_rec_t mc_rec; struct net_device *mem_dev = NULL; if (!capable(CAP_NET_ADMIN)) return -EPERM; switch (cmd) { case MCH_MEMBER_ENTRY_ADD: case MCH_SERVER_ENTRY_GET: return -ENOTSUPP; case MCH_MEMBER_ENTRY_UPDATE: if (copy_from_user(&mc_rec, (mcast_rec_t *)arg, sizeof(mcast_rec_t))) { return -EACCES; } mem_dev = mch_get_netif(mc_rec.mem_ifname); if (mem_dev == NULL) return -ENXIO; mcast_helper_update_entry(mem_dev, &mc_rec); break; case MCH_MEMBER_ENTRY_REMOVE: if (copy_from_user(&mc_rec, (mcast_rec_t *)arg, sizeof(mcast_rec_t))) { return -EACCES; } mem_dev = mch_get_netif(mc_rec.mem_ifname); if (mem_dev == NULL) return -ENXIO; mcast_helper_delete_entry(mem_dev, &mc_rec); break; default: return -EINVAL; } return 0; } static int mcast_helper_open(struct inode *i, struct file *f) { return 0; } static int mcast_helper_close(struct inode *i, struct file *f) { return 0; } static struct file_operations mcast_helper_fops = { .owner = THIS_MODULE, .open = mcast_helper_open, .release = mcast_helper_close, .unlocked_ioctl = mcast_helper_ioctl }; /*============================================================================= *function name: mcast_helper_extract_mcgid *description : function to retrieve the group index from the data buffer *===========================================================================*/ static u32 mcast_helper_extract_mcgid(char *data, int offset) { u32 mcgid = 0; mcgid = (u32)(data[offset] & 0xFF); return mcgid; } /*============================================================================= *function name: mcast_helper_extract_intrfidx *description : function to retrieve the interface index from the data buffer *===========================================================================*/ static u32 mcast_helper_extract_intrfidx(char *data, int offset) { u32 intrfidx = 0; intrfidx = (u8)(data[offset] & 0xFF); return intrfidx; } /*============================================================================= *function name: mcast_helper_sig_check *description : function to check the signature in the received probe packt *===========================================================================*/ static inline int mcast_helper_sig_check(u8 *data) { return (memcmp(data, mch_signature, (sizeof(mch_signature) - 1)) == 0); } /*============================================================================= *function name: mcast_helper_sig_check_update_ip *description : function to check signature and update the member table and * invoke registered callbacks for IPv4 packet *===========================================================================*/ static int mcast_helper_sig_check_update_ip(struct sk_buff *skb) { u8 *data; int mcgid = 0; int intrfid = 0; struct list_head *mcgid_list = mcast_helper_list_p(MCH_IPV4); mcast_gid_t *mcgid_rec = NULL; mcast_member_t *mem_rec = NULL; if (ip_hdr(skb)->protocol == IPPROTO_UDP) return 0; data = (u8 *)udp_hdr(skb) + UDP_HDR_LEN; if (mcast_helper_sig_check(data) == 0) return 0; /* * Signature matched now extract the grpindex * and the call update member table */ mcgid = mcast_helper_extract_mcgid(data, sizeof(mch_signature) - 1); intrfid = mcast_helper_extract_intrfidx(data, sizeof(mch_signature)); spin_lock_bh(&mch_mcgid_lock); mcgid_rec = mcast_helper_get_mcgid_record(mcgid_list, mcgid); if (mcgid_rec) { if (skb->dev->ifindex == intrfid) { /* * Update the member table to add the * new interface into the list */ mem_rec = mcast_helper_search_mem_record(mcgid_rec, skb->dev); if (mem_rec) { #ifdef CONFIG_MCAST_HELPER_ACL mem_rec->acl_blocked = 0; #endif mcast_helper_invoke_return_callback(mem_rec, mcgid_rec, MCH_CB_F_ADD); } } #ifdef CONFIG_MCAST_HELPER_ACL /* * Update the oifindex bitmap to be used * for evaluating after timer expires */ mcgid_rec->oif_bitmap |= (1ULL << skb->dev->ifindex); #endif } spin_unlock_bh(&mch_mcgid_lock); return 1; } /*============================================================================= *function name: mcast_helper_sig_check_update_ip6 *description : function to check signature and update the member table * and invoke registered callbacks for the IPv6 packet *===========================================================================*/ static int mcast_helper_sig_check_update_ip6(struct sk_buff *skb) { u8 *data; int mcgid = 0; int intrfid = 0; struct list_head *mcgid_list = mcast_helper_list_p(MCH_IPV6); mcast_gid_t *mcgid_rec = NULL; mcast_member_t *mem_rec = NULL; data = (u8 *)udp_hdr(skb) + UDP_HDR_LEN; if (mcast_helper_sig_check(data) == 0) return 0; /* * Signature matched now extract the grpindex * and the call update member table */ mcgid = mcast_helper_extract_mcgid(data, sizeof(mch_signature) - 1); intrfid = mcast_helper_extract_intrfidx(data, sizeof(mch_signature)); spin_lock_bh(&mch_mcgid_lock); mcgid_rec = mcast_helper_get_mcgid_record(mcgid_list, mcgid); if (mcgid_rec) { if (skb->dev->ifindex == intrfid) { /* * Update the member table to add the * new interface into the list */ mem_rec = mcast_helper_search_mem_record(mcgid_rec, skb->dev); if (mem_rec) { #ifdef CONFIG_MCAST_HELPER_ACL mem_rec->acl_blocked = 0; #endif mcast_helper_invoke_return_callback(mem_rec, mcgid_rec, MCH_CB_F_UPD); } } #ifdef CONFIG_MCAST_HELPER_ACL /* * Update the oifindex bitmap to be used * for evaluating after timer expires */ mcgid_rec->oif_bitmap |= (1ULL << skb->dev->ifindex); #endif } spin_unlock_bh(&mch_mcgid_lock); return 1; } /*============================================================================= *function name: mcast_helper_sig_check_update *description : function to check signature and call corresponding callbacks *===========================================================================*/ int mcast_helper_sig_check_update(struct sk_buff *skb) { struct list_head *mcgid_list; const u8 *dest = eth_hdr(skb)->h_dest; if (!mch_acl_enabled) return 1; if (mch_iptype == MCH_IPV6) mcgid_list = mcast_helper_list_p(MCH_IPV6); else mcgid_list = mcast_helper_list_p(MCH_IPV4); if (mch_timerstarted && is_multicast_ether_addr(dest)) { if (eth_hdr(skb)->h_proto == ETH_P_IP) { if (ip_hdr(skb)->protocol == IPPROTO_UDP) mcast_helper_sig_check_update_ip(skb); } else if (eth_hdr(skb)->h_proto == ETH_P_IPV6) { if (ipv6_hdr(skb)->nexthdr == IPPROTO_UDP) mcast_helper_sig_check_update_ip6(skb); } } return 1; } EXPORT_SYMBOL(mcast_helper_sig_check_update); /*============================================================================= * function name: mcast_helper_delete_all_mac * description : Function deletes all clients and inform callback *===========================================================================*/ static void mcast_helper_delete_all_mac(mcast_member_t *mem_rec, mcast_gid_t *mcgid_rec) { mcast_mac_t *mac_rec = NULL, *tmp = NULL; if ((mem_rec == NULL) || (mcgid_rec == NULL)) return; /* Send delete callback for all MAC */ mcast_helper_invoke_return_callback(mem_rec, mcgid_rec, MCH_CB_F_DEL); list_for_each_entry_safe (mac_rec, tmp, &mem_rec->macaddr_list, list) { mcast_helper_delete_mac_record(mac_rec, mem_rec); } } /*============================================================================= * function name: mcast_helper_delete_mem_and_mac * description : Function searches and deletes all clients and inform callback. * Also delete the member and if no member delete MCGID *===========================================================================*/ static void mcast_helper_delete_mem_and_mac(ptype_t type, struct net_device *netif) { mcast_gid_t *mcgid_rec = NULL, *tmp = NULL; mcast_member_t *mem_rec = NULL; struct list_head *mcgid_list = mcast_helper_list_p(type); /* Loop the MCGID list (IPv4/IPv6) */ spin_lock_bh(&mch_mcgid_lock); list_for_each_entry_safe (mcgid_rec, tmp, mcgid_list, list) { /* Search group membership for the device */ mem_rec = mcast_helper_search_mem_record(mcgid_rec, netif); if (mem_rec == NULL) continue; /* Delete all of its associated clients */ mcast_helper_delete_all_mac(mem_rec, mcgid_rec); /* Delete member from MCGID record */ mcast_helper_delete_mem_record(mem_rec, mcgid_rec); if (list_empty(&mcgid_rec->mc_mem_list)) { /* As there is no member, delete entry from MCGID table */ mcast_helper_delete_mcgid_record(mcgid_rec); } } spin_unlock_bh(&mch_mcgid_lock); } /* Member entry deletion from netdev down/unregister events */ static int mcast_helper_netdevice_event(struct notifier_block *nb, unsigned long action, void *ptr) { struct net_device *netif = NULL; netif = netdev_notifier_info_to_dev((struct netdev_notifier_info *)ptr); switch (action) { case NETDEV_UNREGISTER: case NETDEV_DOWN: /* Search and delete member and all associated clients */ mcast_helper_delete_mem_and_mac(MCH_IPV4, netif); mcast_helper_delete_mem_and_mac(MCH_IPV6, netif); break; } return 0; } struct notifier_block mcast_helper_netdevice_notifier = { .notifier_call = mcast_helper_netdevice_event }; #ifdef CONFIG_MCAST_HELPER_ACL static void mcast_helper_timer_handler_probe(mcast_gid_t *mcgid_rec) { mcast_member_t *mem_rec = NULL, *tmp = NULL; u32 i = 0, delflag = 1; u64 oif_bitmap = 0; list_for_each_entry_safe (mem_rec, tmp, &mcgid_rec->mc_mem_list, list) { oif_bitmap = mcgid_rec->oif_bitmap; i = 0; delflag = 1; do { if (!(oif_bitmap & 0x1)) { i++; continue; } if (mem_rec->mem_dev->ifindex == i) { if (mem_rec->acl_blocked == 1) { mcast_helper_invoke_return_callback( mem_rec, mcgid_rec, MCH_CB_F_ADD); mem_rec->acl_blocked = 0; } delflag = 0; break; } i++; } while (oif_bitmap >>= 1); if (delflag == 1) { /* Delete this interface from the * member list and invoke registered * call back for this if any */ mcast_helper_invoke_return_callback(mem_rec, mcgid_rec, MCH_CB_F_DEL); mem_rec->acl_blocked = 1; } } mcgid_rec->oif_bitmap = 0; mcgid_rec->probe_flag = 0; } #endif /*============================================================================= *function name: mcast_helper_timer_handler *description : function handling mcast herlper timer expiry *===========================================================================*/ static void mcast_helper_timer_handler( #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) unsigned long data #else struct timer_list *timer #endif ) { mcast_gid_t *mcgid_rec = NULL, *tmp = NULL; struct list_head *mcgid_list = NULL; mch_debug("helper timer invoked\n"); if (mch_timermod) { mch_timermod = 0; return; } if (mch_iptype == MCH_IPV6) mcgid_list = mcast_helper_list_p(MCH_IPV6); else mcgid_list = mcast_helper_list_p(MCH_IPV4); spin_lock_bh(&mch_mcgid_lock); list_for_each_entry_safe (mcgid_rec, tmp, mcgid_list, list) { #ifdef CONFIG_MCAST_HELPER_ACL if (mcgid_rec->probe_flag == 1) mcast_helper_timer_handler_probe(mcgid_rec); #endif /* TODO: check if no member then free it */ } spin_unlock_bh(&mch_mcgid_lock); mch_iptype = 0; mch_timerstarted = 0; } /*============================================================================= *function name: mcast_helper_start_helper_timer *description : function starting/modifying helper timer *===========================================================================*/ static void mcast_helper_start_helper_timer(void) { if (!mch_timerstarted) { mch_debug("starting helper timer\n"); mch_helper_timer.expires = jiffies + (MCH_UPDATE_TIMER * HZ); add_timer(&mch_helper_timer); mch_timerstarted = 1; } else { mch_debug("modifying helper timer\n"); mch_timermod = 1; mod_timer(&mch_helper_timer, jiffies + MCH_UPDATE_TIMER * HZ); mch_timermod = 0; } } #ifdef CONFIG_PROC_FS /*============================================================================= *function name: mcast_helper_show_ipv4 *description : proc support to read and output the IPv4 table entries *===========================================================================*/ static void mcast_helper_show_ipv4(struct seq_file *seq, mcast_gid_t *mcgid_rec) { char src_mac[MCH_MAC_STR_LEN] = "(NA) "; mcast_stream_t *stream = &mcgid_rec->mc_stream; if (!is_zero_ether_addr(stream->src_mac)) snprintf(src_mac, sizeof(src_mac), "(%pM)", stream->src_mac); seq_printf(seq, "%3d %15s %15pI4 %15pI4 %5d %5d %5d %19s", mcgid_rec->gid, stream->rx_dev->name, &stream->src_ip.addr, &stream->dst_ip.addr, stream->proto, stream->src_port, stream->dst_port, src_mac); } /*============================================================================= *function name: mcast_helper_show_ipv6 *description : proc support to read and output the IPv6 table entries *===========================================================================*/ static void mcast_helper_show_ipv6(struct seq_file *seq, mcast_gid_t *mcgid_rec) { char src_mac[MCH_MAC_STR_LEN] = "(NA) "; mcast_stream_t *stream = &mcgid_rec->mc_stream; if (!is_zero_ether_addr(stream->src_mac)) snprintf(src_mac, sizeof(src_mac), "(%pM)", stream->src_mac); seq_printf(seq, "%3d %15s %39pI6 %39pI6 %6d %6d %6d %19s", mcgid_rec->gid, stream->rx_dev->name, &stream->src_ip.addr, &stream->dst_ip.addr, stream->proto, stream->src_port, stream->dst_port, src_mac); } /*============================================================================= *function name: mcast_helper_show *description : proc support to read and output the table entries *===========================================================================*/ static int mcast_helper_show_entry(struct seq_file *seq, struct list_head *mcgid_list) { mcast_gid_t *mcgid_rec = NULL; mcast_member_t *mem_rec = NULL; mcast_mac_t *mac_rec = NULL; rcu_read_lock(); list_for_each_entry_rcu (mcgid_rec, mcgid_list, list) { if (mcgid_rec->mc_stream.src_ip.ip_type == MCH_IPV4) mcast_helper_show_ipv4(seq, mcgid_rec); else if (mcgid_rec->mc_stream.src_ip.ip_type == MCH_IPV6) mcast_helper_show_ipv6(seq, mcgid_rec); list_for_each_entry_rcu (mem_rec, &mcgid_rec->mc_mem_list, list) { list_for_each_entry_rcu (mac_rec, &mem_rec->macaddr_list, list) { seq_printf(seq, " %s(%pM)", mem_rec->mem_dev->name, mac_rec->macaddr); #ifdef CONFIG_MCAST_HELPER_ACL if (mch_acl_enabled) seq_printf(seq, "(%d)", mem_rec->acl_blocked); #endif } } seq_printf(seq, "\n"); } rcu_read_unlock(); return 0; } /*============================================================================= *function name: mcast_helper_seq_show *description : proc support to read and output IPV4 mcast helper record *===========================================================================*/ static int mcast_helper_seq_show(struct seq_file *seq, void *v) { if (!capable(CAP_NET_ADMIN)) return -EPERM; seq_printf(seq, "%3s %11s %12s %15s %11s %5s %5s %12s %24s%s\n", "GIdx", "RxIntrf", "SA", "GA", "proto", "sPort", "dPort", "sMAC", "memIntrf(MacAddr)", (mch_acl_enabled) ? "(AclFlag)" : ""); mcast_helper_show_entry(seq, mcast_helper_list_p(MCH_IPV4)); return 0; } /*============================================================================= *function name: mcast_helper_seq_show6 *description : proc support to read and output IPV6 mcast helper record *===========================================================================*/ static int mcast_helper_seq_show6(struct seq_file *seq, void *v) { if (!capable(CAP_NET_ADMIN)) return -EPERM; seq_printf(seq, "%3s %11s %24s %39s %24s %6s %6s %12s %24s%s\n", "GIdx", "RxIntrf", "SA", "GA", "proto", "sPort", "dPort", "sMAC", "memIntrf(MacAddr)", (mch_acl_enabled) ? "(AclFlag)" : ""); mcast_helper_show_entry(seq, mcast_helper_list_p(MCH_IPV6)); return 0; } /*============================================================================= *function name: mcast_debug_seq_show *description : proc support to read and output debug status *===========================================================================*/ static int mcast_debug_seq_show(struct seq_file *m, void *v) { if (!capable(CAP_NET_ADMIN)) return -EPERM; seq_printf(m, "MCH: debug is %sabled\n", (mcast_debug) ? "en" : "dis"); return 0; } /*============================================================================= *function name: mcast_debug_proc_write *description : proc support to enable/disable (1/0) debug level *===========================================================================*/ static ssize_t mcast_debug_proc_write(struct file *file, const char *buffer, size_t length, loff_t *offset) { int ret = 0; char *wbuf = NULL; if (!capable(CAP_NET_ADMIN)) return -EPERM; wbuf = kmalloc(length, GFP_KERNEL); if (wbuf == NULL) { printk(KERN_ERR "MCH: Cannot allocate memory!\n"); return -EFAULT; } if (copy_from_user(wbuf, buffer, length)) { printk(KERN_ERR "MCH: Cannot copy buffer from user space!\n"); kfree(wbuf); return -EFAULT; } wbuf[length - 1] = '\0'; if ((wbuf[0] == '0') || (wbuf[0] == '1')) { mcast_debug = wbuf[0] - '0'; printk(KERN_INFO "MCH: Debug level is now %sabled\n", (mcast_debug) ? "en" : "dis"); } else { printk(KERN_ERR "MCH: invalid value - 1/0 (enable/disable)\n"); } ret = length; kfree(wbuf); *offset += ret; return ret; } /*============================================================================= *function name: helper_proc_open *description : function to open helper proc for ipv4 table entries *===========================================================================*/ int helper_proc_open(struct inode *inode, struct file *file) { return single_open(file, mcast_helper_seq_show, NULL); } /*============================================================================= *function name: helper_proc_open6 *description : function to open helper proc for ipv6 table entries *===========================================================================*/ int helper_proc_open6(struct inode *inode, struct file *file) { return single_open(file, mcast_helper_seq_show6, NULL); } /*============================================================================= *function name: mcast_debug_proc_open *description : function to open debug proc for debug prints *===========================================================================*/ static int mcast_debug_proc_open(struct inode *inode, struct file *file) { return single_open(file, mcast_debug_seq_show, NULL); } static const struct file_operations mcast_helper_seq_fops = { .owner = THIS_MODULE, .open = helper_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations mcast_helper_seq_fops6 = { .owner = THIS_MODULE, .open = helper_proc_open6, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations mcast_debug_fops = { .owner = THIS_MODULE, .open = mcast_debug_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = mcast_debug_proc_write, }; /*============================================================================= *function name: mcast_helper_net_init *description : function to create mcast helper proc entry *===========================================================================*/ static int mcast_helper_net_init(void) { struct proc_dir_entry *pdh = NULL, *pdh6 = NULL; struct proc_dir_entry *pdebug = NULL; pdh = proc_create("mcast_helper", 0, NULL, &mcast_helper_seq_fops); if (!pdh) { goto out_mcast; } pdh6 = proc_create("mcast_helper6", 0, NULL, &mcast_helper_seq_fops6); if (!pdh6) goto out_mcast; pdebug = proc_create("mcast_debug", 0, NULL, &mcast_debug_fops); if (!pdebug) goto out_mcast; return 0; out_mcast: if (pdh) remove_proc_entry("mcast_helper", NULL); if (pdh6) remove_proc_entry("mcast_helper6", NULL); return -ENOMEM; } /*============================================================================= *function name: mcast_helper_net_exit *description : function to remove mcast helper proc entry *===========================================================================*/ #ifdef CONFIG_SYSCTL /**Functions to create a proc for ACL enable/disbale support **/ static int mcast_helper_acl_sysctl_call_tables(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos) { int ret; ret = proc_dointvec(ctl, write, buffer, lenp, ppos); if (write && *(int *)(ctl->data)) *(int *)(ctl->data) = 1; return ret; } static struct ctl_table mcast_helper_acl_table[] = { { .procname = "multicast-acl", .data = &mch_acl_enabled, .maxlen = sizeof(int), .mode = 0644, .proc_handler = mcast_helper_acl_sysctl_call_tables, }, {} }; /*============================================================================= *function name: mcast_helper_accl_sysctl_call_tables *description : Functions to create a proc to enable/disbale * Multicast accleration for WLAN *===========================================================================*/ static int mcast_helper_accl_sysctl_call_tables(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos) { int ret; ret = proc_dointvec(ctl, write, buffer, lenp, ppos); if (write && *(int *)(ctl->data)) *(int *)(ctl->data) = 1; return ret; } static struct ctl_table mcast_helper_accl_table[] = { { .procname = "multicast-accleration", .data = &mch_accl_enabled, .maxlen = sizeof(int), .mode = 0644, .proc_handler = mcast_helper_accl_sysctl_call_tables, }, {} }; #endif /* CONFIG_SYSCTL */ int __init mcast_helper_proc_init(void) { int ret; #ifdef CONFIG_SYSCTL mcast_acl_sysctl_header = register_net_sysctl(&init_net, "net/", mcast_helper_acl_table); if (mcast_acl_sysctl_header == NULL) { printk(KERN_WARNING "Failed to register mcast acl sysctl table.\n"); return 0; } /*Proc to disable multicast accleration for WLAN */ mcast_accl_sysctl_header = register_net_sysctl(&init_net, "net/", mcast_helper_accl_table); if (mcast_accl_sysctl_header == NULL) { printk(KERN_WARNING "Failed to register mcast accl sysctl table.\n"); return 0; } #endif ret = mcast_helper_net_init(); return ret; } #endif /* CONFIG_PROC_FS */ /*============================================================================= *function name: mcast_helper_init_module *description : Multucast helper module initilization *===========================================================================*/ static int __init mcast_helper_init_module(void) { int ret_val; /* * Alloc the chrdev region for mcast helper */ ret_val = alloc_chrdev_region(&mch_major, 0, 1, DEVICE_NAME); /* * Negative values signify an error */ if (ret_val < 0) { printk(KERN_ALERT "%s failed with %d\n", "Sorry, alloc_chrdev_region failed for the mcast device", ret_val); return ret_val; } printk(KERN_INFO "%s The major device number is %d.\n", "Registeration is a success", MAJOR(mch_major)); /* Create device class (before allocation of the array of devices) */ mcast_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(mcast_class)) { ret_val = PTR_ERR(mcast_class); goto fail; } if (device_create(mcast_class, NULL, mch_major, NULL, "mcast") == NULL) goto fail; device_created = 1; cdev_init(&mcast_cdev, &mcast_helper_fops); if (cdev_add(&mcast_cdev, mch_major, 1) == -1) goto fail; register_netdevice_notifier(&mcast_helper_netdevice_notifier); /* Initialize all timers */ setup_timer(&mch_helper_timer, mcast_helper_timer_handler, 0); #ifdef CONFIG_PROC_FS mcast_helper_proc_init(); #endif mcast_helper_br_learning_hook = mcast_helper_bridge_learning; mcast_helper_sig_check_update_ptr = mcast_helper_sig_check_update; return 0; fail: if (device_created) { device_destroy(mcast_class, mch_major); cdev_del(&mcast_cdev); } if (mcast_class) class_destroy(mcast_class); if (mch_major != -1) unregister_chrdev_region(mch_major, 1); return -1; } /*============================================================================= * function name : mcast_helper_exit_module * description : Mcast helper module exit handler *===========================================================================*/ static void __exit mcast_helper_exit_module(void) { /* Cancel all timers */ del_timer(&mch_helper_timer); mch_acl_enabled = 0; mcast_helper_br_learning_hook = NULL; mcast_helper_sig_check_update_ptr = NULL; if (skb_buff) kfree_skb(skb_buff); if (skb_buff6) kfree_skb(skb_buff6); unregister_netdevice_notifier(&mcast_helper_netdevice_notifier); #ifdef CONFIG_SYSCTL unregister_net_sysctl_table(mcast_acl_sysctl_header); unregister_net_sysctl_table(mcast_accl_sysctl_header); #endif remove_proc_entry("mcast_helper", NULL); remove_proc_entry("mcast_helper6", NULL); remove_proc_entry("mcast_debug", NULL); if (device_created) { device_destroy(mcast_class, mch_major); cdev_del(&mcast_cdev); } if (mcast_class) class_destroy(mcast_class); if (mch_major != -1) unregister_chrdev_region(mch_major, 1); } module_init(mcast_helper_init_module); module_exit(mcast_helper_exit_module); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Multicast helper"); MODULE_AUTHOR("Srikanth"); MODULE_AUTHOR("Ujjal Roy");