/****************************************************************************** ** ** FILE NAME : mcast_helper_reg.c ** AUTHOR : ** DESCRIPTION : Multicast Helper Register function ** 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. ** *******************************************************************************/ #if IS_ENABLED(CONFIG_MCAST_HELPER_REG) #define pr_fmt(fmt) "MCH: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #define SUCCESS (0) #define FAILURE (-1) #define MCAST_PROCESS_NAME "mcastd" #define IF_INFO_SIZE NLMSG_ALIGN(sizeof(struct ifinfomsg)) #define TOTAL_SIZE NLMSG_ALIGN(sizeof(struct ifinfomsg)) + \ nla_total_size(MAX_ADDR_LEN) typedef struct _mcast_callback_t { /* Number of callback */ int num_cb; /* Member netdevice */ struct net_device *net_dev; /* Kernel module name */ struct module *mod_name; /* Module callback function pointer */ Mcast_module_callback_t cb; /* Module callback flag - MCH_F_* */ uint32_t flag; /* mcast_callback interface map list */ struct list_head list; /* RCU head */ struct rcu_head rcu_head; } mcast_callback_t; #if IS_ENABLED(CONFIG_MCAST_SNOOPING) int (*mcast_helper_get_skb_gid_ptr)(struct sk_buff *skb) = NULL; EXPORT_SYMBOL(mcast_helper_get_skb_gid_ptr); #endif static DEFINE_MUTEX(mch_cb_list_lock); LIST_HEAD(mch_callback_list_g); static int mcast_helper_send_netlink_msg_user(struct net_device *dev, int type, int queryflag, void *data) { struct sk_buff *skb; struct nlmsghdr *nlh; struct ifinfomsg *ifm; struct net *net = dev_net(dev); unsigned int pid; bool pid_found = false; int res, res_v6; struct task_struct *task; struct sk_buff *skb_v6; for_each_process (task) { /* Compare the process name with each of the * task struct process name. */ if (strstr(task->comm, MCAST_PROCESS_NAME) == NULL) continue; pid_found = true; pid = task->pid; break; } if (pid_found == false) return FAILURE; skb = nlmsg_new(TOTAL_SIZE, GFP_ATOMIC); if (!skb) { pr_err("SKB allocation failure.\n"); return FAILURE; } nlh = nlmsg_put(skb, 0, 0, type, IF_INFO_SIZE, 0); if (nlh == NULL) { kfree_skb(skb); return -EMSGSIZE; } nlh->nlmsg_type = type; ifm = nlmsg_data(nlh); ifm->ifi_family = AF_UNSPEC; ifm->__ifi_pad = 0; ifm->ifi_type = dev->type; ifm->ifi_index = dev->ifindex; if (type == RTM_NEWLINK) ifm->ifi_flags = IFF_UP; if (queryflag) ifm->ifi_flags = ifm->ifi_flags | IFF_SLAVE; ifm->ifi_change = 0; if (data != NULL) { nla_put(skb, NLA_BINARY, MAX_ADDR_LEN, data); skb_v6 = skb_clone(skb, GFP_ATOMIC); if (!skb_v6) { pr_err("SKB allocation failure.\n"); kfree_skb(skb); return FAILURE; } res = nlmsg_multicast(net->rtnl, skb, 0, RTNLGRP_LINK | RTNLGRP_IPV4_IFADDR, GFP_ATOMIC); res_v6 = nlmsg_multicast(net->rtnl, skb_v6, 0, RTNLGRP_LINK | RTNLGRP_IPV6_IFADDR, GFP_ATOMIC); if (res < 0 || res_v6 < 0) return FAILURE; else return SUCCESS; } res = netlink_unicast(net->rtnl, skb, pid, MSG_DONTWAIT); return res; } static int mcast_helper_reg_callback(struct net_device *dev, Mcast_module_callback_t *cb, struct module *mod_name, unsigned int flags) { mcast_callback_t *mc_cb_rec = NULL; if (dev == NULL) return FAILURE; mc_cb_rec = kmalloc(sizeof(mcast_callback_t), GFP_ATOMIC); if (mc_cb_rec == NULL) return FAILURE; mc_cb_rec->net_dev = dev; mc_cb_rec->cb = (void *)cb; mc_cb_rec->mod_name = mod_name; mc_cb_rec->flag = flags; INIT_LIST_HEAD(&mc_cb_rec->list); mutex_lock(&mch_cb_list_lock); list_add_tail_rcu(&mc_cb_rec->list, &mch_callback_list_g); mutex_unlock(&mch_cb_list_lock); if ((flags & MCH_F_FW_RESET) == MCH_F_FW_RESET) mcast_helper_send_netlink_msg_user(dev, RTM_NEWLINK, 0, NULL); return SUCCESS; } static int mcast_helper_dereg_callback(struct net_device *dev, Mcast_module_callback_t *cb, struct module *mod_name, unsigned int flags) { struct list_head *liter = NULL; struct list_head *gliter = NULL; mcast_callback_t *mc_cb_rec = NULL; if (dev == NULL) return FAILURE; mutex_lock(&mch_cb_list_lock); list_for_each_safe (liter, gliter, &mch_callback_list_g) { mc_cb_rec = list_entry(liter, mcast_callback_t, list); if (mc_cb_rec->net_dev != dev) continue; if (strncmp(mod_name->name, mc_cb_rec->mod_name->name, MODULE_NAME_LEN)) continue; list_del_rcu(&mc_cb_rec->list); kfree_rcu(mc_cb_rec, rcu_head); mutex_unlock(&mch_cb_list_lock); if ((flags & MCH_F_FW_RESET) == MCH_F_FW_RESET) mcast_helper_send_netlink_msg_user(dev, RTM_DELLINK, 0, NULL); return SUCCESS; } mutex_unlock(&mch_cb_list_lock); return FAILURE; } /** Register callback function **/ void mcast_helper_register_module( struct net_device *dev, /* Registered dev e.g. wlan0 */ struct module *mod_name, /* Kernel Module Name */ char *addl_name, /* Optional Additional Name */ Mcast_module_callback_t *cb, /* Callback Function */ void *data, /* Variable input data */ unsigned int flags) /* Flag - MCH_F_* */ { if (dev == NULL) return; if (flags & MCH_F_REGISTER) { mcast_helper_reg_callback(dev, cb, mod_name, flags); } else if (flags & MCH_F_DEREGISTER) { mcast_helper_dereg_callback(dev, cb, mod_name, flags); } else if (flags & MCH_F_NEW_STA) { mcast_helper_send_netlink_msg_user(dev, RTM_NEWLINK, 1, NULL); } else if (flags & MCH_F_DISCONN_MAC) { if (data == NULL) pr_info("MAC is NULL - flag : MCH_F_DISCONN_MAC\n"); else mcast_helper_send_netlink_msg_user(dev, RTM_DELLINK, 0, data); } } EXPORT_SYMBOL(mcast_helper_register_module); int mcast_helper_invoke_callback(unsigned int grpidx, struct net_device *dev, void *mc_stream, unsigned int flag) { mcast_callback_t *mc_cb_rec = NULL; unsigned int passflag = flag; if (dev == NULL) return FAILURE; rcu_read_lock(); list_for_each_entry_rcu (mc_cb_rec, &mch_callback_list_g, list) { if (mc_cb_rec->net_dev != dev) continue; if (mc_cb_rec->cb == NULL) continue; if ((mc_cb_rec->flag & MCH_F_UPDATE_MAC_ADDR)) { /* WLAN driver needs UPDATE for host tracking */ if ((flag & MCH_CB_F_DEL_UPD)) passflag = MCH_CB_F_UPD; /* TODO: Device must be VAP interface even for VLAN iface */ mc_cb_rec->cb(grpidx, dev, mc_stream, passflag); } else { if (flag == MCH_CB_F_UPD) passflag = MCH_CB_F_ADD; if (flag != MCH_CB_F_DEL_UPD) mc_cb_rec->cb(grpidx, dev, mc_stream, passflag); } } rcu_read_unlock(); return SUCCESS; } EXPORT_SYMBOL(mcast_helper_invoke_callback); #if IS_ENABLED(CONFIG_MCAST_SNOOPING) int mcast_helper_get_skb_gid(struct sk_buff *skb) { if (mcast_helper_get_skb_gid_ptr) return mcast_helper_get_skb_gid_ptr(skb); return MCH_GID_ERR; } EXPORT_SYMBOL(mcast_helper_get_skb_gid); #endif #endif