/****************************************************************************** ** ** FILE NAME : mcast_helper_reg.c ** AUTHOR : ** DESCRIPTION : Multicast Helper Register function ** COPYRIGHT : Copyright (c) 2020-2022, 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 #include #define MCH_SUCCESS (0) #define MCH_FAILURE (-1) #define MCAST_PROCESS_NAME "mcastd" #define IF_INFO_SIZE NLMSG_ALIGN(sizeof(struct ifinfomsg)) #define NLMSG_TOTAL_SIZE IF_INFO_SIZE + 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; 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) { int ret = MCH_FAILURE; struct sk_buff *skb, *skb_v6; struct nlmsghdr *nlh; struct ifinfomsg *ifm; struct net *net = dev_net(dev); if (!dev) return ret; skb = nlmsg_new(NLMSG_TOTAL_SIZE, GFP_ATOMIC); if (!skb) { pr_err("SKB allocation for IPv4 failure.\n"); return ret; } nlh = nlmsg_put(skb, 0, 0, type, IF_INFO_SIZE, 0); if (nlh == NULL) { kfree_skb(skb); pr_err("failed to put NL message.\n"); return ret; } 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 = dev_get_flags(dev) | IFF_UP; if (queryflag) ifm->ifi_flags |= IFF_SLAVE; ifm->ifi_change = 0; if (data) { /* Send STA's MAC which got disconnected */ ret = nla_put(skb, NLA_BINARY, ETH_ALEN, data); if (ret) { kfree_skb(skb); pr_err("failed to put NL binary message.\n"); return ret; } } skb_v6 = skb_clone(skb, GFP_ATOMIC); if (!skb_v6) { pr_err("SKB allocation for IPv6 failure.\n"); kfree_skb(skb); return ret; } /* Always sending via both IPV4 & IPV6, so that daemon can clear. * Because daemon has IPV4 & IPV6 netlink socket per interface. */ ret = nlmsg_multicast(net->rtnl, skb, 0, RTNLGRP_LINK | RTNLGRP_IPV4_IFADDR, GFP_ATOMIC); ret |= nlmsg_multicast(net->rtnl, skb_v6, 0, RTNLGRP_LINK | RTNLGRP_IPV6_IFADDR, GFP_ATOMIC); if (ret) return MCH_FAILURE; else return MCH_SUCCESS; } 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 MCH_FAILURE; mc_cb_rec = kmalloc(sizeof(mcast_callback_t), GFP_ATOMIC); if (mc_cb_rec == NULL) return MCH_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 MCH_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 MCH_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 MCH_SUCCESS; } mutex_unlock(&mch_cb_list_lock); return MCH_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) { struct net_device *vap_dev = dev; mcast_callback_t *mc_cb_rec = NULL; unsigned int passflag = flag; if (dev == NULL) return MCH_FAILURE; if (is_vlan_dev(dev)) vap_dev = vlan_dev_real_dev(dev); rcu_read_lock(); list_for_each_entry_rcu(mc_cb_rec, &mch_callback_list_g, list) { if ((mc_cb_rec->net_dev != dev) && (mc_cb_rec->net_dev != vap_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; /* Device must be VAP interface even for VLAN iface */ mc_cb_rec->cb(grpidx, vap_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 MCH_SUCCESS; } EXPORT_SYMBOL(mcast_helper_invoke_callback); #endif /* CONFIG_MCAST_HELPER_REG */