| /* |
| ************************************************************************** |
| * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all copies. |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| ************************************************************************** |
| */ |
| |
| #include <nss_api_if.h> |
| #include <nss_cmn.h> |
| #include <linux/tc_act/tc_nss_mirred.h> |
| #include <net/netfilter/nf_conntrack_core.h> |
| #include "nss_mirred.h" |
| #include "nss_igs.h" |
| #include "nss_ifb.h" |
| |
| static LIST_HEAD(nss_mirred_list); /* List for all nss mirred actions */ |
| static DEFINE_SPINLOCK(nss_mirred_list_lock); /* Lock for the nss mirred list */ |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) |
| static unsigned int nss_mirred_net_id; /* NSS mirror net ID */ |
| static struct tc_action_ops nss_mirred_act_ops; /* NSS action mirror ops */ |
| #endif |
| |
| /* |
| * nss_mirred_release() |
| * Cleanup the resources for nss mirred action. |
| */ |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| static void nss_mirred_release(struct tc_action *tc_act, int bind) |
| #else |
| static void nss_mirred_release(struct tc_action *tc_act) |
| #endif |
| { |
| struct nss_mirred_tcf *act = nss_mirred_get(tc_act); |
| struct net_device *dev = rcu_dereference_protected(act->tcfm_dev, 1); |
| struct nss_ifb_info *ifb_info = nss_ifb_find_dev(dev); |
| |
| if (!ifb_info) { |
| nss_igs_error("IFB device %s not found in the linked list \n", dev->name); |
| return; |
| } |
| |
| /* |
| * Send IFB RESET NEXTHOP configure message to the mapped interface. |
| */ |
| if (!nss_ifb_reset_nexthop(ifb_info)) { |
| nss_igs_error("Error in sending IFB RESET NEXTHOP configure message\n"); |
| } |
| |
| if (!nss_ifb_clear_igs_node(ifb_info)) { |
| nss_igs_error("Error in sending IFB CLEAR configure message\n"); |
| } |
| |
| /* |
| * Delete the nss mirred action list. |
| */ |
| spin_lock_bh(&nss_mirred_list_lock); |
| list_del(&act->tcfm_list); |
| spin_unlock_bh(&nss_mirred_list_lock); |
| if (dev) { |
| dev_put(dev); |
| } |
| } |
| |
| /* |
| * nss_mirred_policy |
| * nss mirred policy structure. |
| */ |
| static const struct nla_policy nss_mirred_policy[TC_NSS_MIRRED_MAX + 1] = { |
| [TC_NSS_MIRRED_PARMS] = { .len = sizeof(struct tc_nss_mirred) }, |
| }; |
| |
| /* |
| * nss_mirred_init() |
| * Initialize the nss mirred action. |
| */ |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| static int nss_mirred_init(struct net *net, struct nlattr *nla, |
| struct nlattr *est, struct tc_action *tc_act, int ovr, |
| int bind) |
| { |
| #else |
| static int nss_mirred_init(struct net *net, struct nlattr *nla, |
| struct nlattr *est, struct tc_action **tc_act, int ovr, |
| int bind, bool rtnl_held, struct tcf_proto *tp, |
| struct netlink_ext_ack *extack) |
| { |
| struct tc_action_net *tn = net_generic(net, nss_mirred_net_id); |
| u32 index; |
| #endif |
| struct nlattr *arr[TC_NSS_MIRRED_MAX + 1]; |
| struct tc_nss_mirred *parm; |
| struct nss_mirred_tcf *act; |
| struct net_device *to_dev, *from_dev; |
| struct nss_ifb_info *ifb_info; |
| int32_t ret, ifb_num; |
| |
| if (!nla) { |
| return -EINVAL; |
| } |
| |
| /* |
| * Parse and validate the user configurations. |
| */ |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)) |
| ret = nla_parse_nested(arr, TC_NSS_MIRRED_MAX, nla, nss_mirred_policy); |
| #else |
| ret = nla_parse_nested_deprecated(arr, TC_NSS_MIRRED_MAX, nla, nss_mirred_policy, extack); |
| #endif |
| if (ret < 0) { |
| return ret; |
| } |
| if (!arr[TC_NSS_MIRRED_PARMS]) { |
| return -EINVAL; |
| } |
| |
| parm = nla_data(arr[TC_NSS_MIRRED_PARMS]); |
| if (!parm->from_ifindex || !parm->to_ifindex) { |
| nss_igs_error("Invalid ifindex: from_ifindex: %u, to_ifindex: %u\n", |
| parm->from_ifindex, parm->to_ifindex); |
| return -EINVAL; |
| } |
| |
| from_dev = __dev_get_by_index(net, parm->from_ifindex); |
| if (!from_dev) { |
| nss_igs_error("No device found for %u ifindex\n", parm->from_ifindex); |
| return -ENODEV; |
| } |
| |
| if (nss_cmn_get_interface_number_by_dev(from_dev) < 0) { |
| nss_igs_error("No NSS FW device found for %s\n", from_dev->name); |
| return -ENODEV; |
| } |
| |
| if (netif_is_ifb_dev(from_dev)) { |
| nss_igs_error("IFB device %s as from_dev\n", from_dev->name); |
| return -ENODEV; |
| } |
| |
| to_dev = __dev_get_by_index(net, parm->to_ifindex); |
| if (!to_dev) { |
| nss_igs_error("No device found for %u ifindex\n", parm->to_ifindex); |
| return -ENODEV; |
| } |
| |
| if (!netif_is_ifb_dev(to_dev)) { |
| nss_igs_error("%s is not an IFB device\n", to_dev->name); |
| return -ENODEV; |
| } |
| |
| ifb_info = nss_ifb_find_map_dev(from_dev); |
| if (ifb_info) { |
| if (nss_ifb_is_mapped(ifb_info)) { |
| nss_igs_error("%s device is already mapped to the other IFB device\n", |
| from_dev->name); |
| return -EEXIST; |
| } |
| } |
| |
| ifb_info = nss_ifb_find_dev(to_dev); |
| if (ifb_info) { |
| if (nss_ifb_is_mapped(ifb_info)) { |
| nss_igs_error("%s IFB device is already mapped to the other device\n", |
| to_dev->name); |
| return -EEXIST; |
| } |
| } |
| |
| ifb_num = nss_cmn_get_interface_number_by_dev_and_type(to_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| if (ifb_num < 0) { |
| /* |
| * Create the IFB instance in the NSS firmware. |
| */ |
| ifb_num = nss_ifb_create_if(to_dev); |
| if (ifb_num < 0) { |
| nss_igs_error("failure in IFB creation\n"); |
| return -EINVAL; |
| } |
| } |
| |
| /* |
| * Send config message to the interface attached to an IFB interface. |
| */ |
| if (nss_ifb_config_msg_tx_sync(from_dev, ifb_num, NSS_IFB_SET_IGS_NODE, NULL) < 0) { |
| nss_igs_error("Sending config to %s dev failed\n", from_dev->name); |
| return -EINVAL; |
| } |
| |
| /* |
| * Send next hop config message to the interface attached to an IFB interface. |
| */ |
| if (nss_ifb_config_msg_tx_sync(from_dev, ifb_num, NSS_IFB_SET_NEXTHOP, NULL) < 0) { |
| nss_igs_error("Sending next hop config to %s dev failed\n", from_dev->name); |
| return -EINVAL; |
| } |
| |
| /* |
| * Bind an IFB device with its requested mapped interface. |
| */ |
| ret = nss_ifb_bind(ifb_info, from_dev, to_dev); |
| if (ret < 0) { |
| nss_igs_error(" Binding an IFB device failed\n"); |
| nss_ifb_delete_if(ifb_num); |
| return ret; |
| } |
| |
| /* |
| * Return error if nss mirred action index is present in the hash. |
| */ |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| if (tcf_hash_check(parm->index, tc_act, bind)) { |
| return -EEXIST; |
| } |
| |
| ret = tcf_hash_create(parm->index, est, tc_act, sizeof(*act), |
| bind, true); |
| if (ret) { |
| return ret; |
| } |
| |
| act = nss_mirred_get(tc_act); |
| #else |
| index = parm->index; |
| ret = tcf_idr_check_alloc(tn, &index, tc_act, bind); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (ret && bind) { |
| return 0; |
| } |
| |
| if (!ret) { |
| ret = tcf_idr_create(tn, index, est, tc_act, &nss_mirred_act_ops, |
| bind, true); |
| if (ret) { |
| tcf_idr_cleanup(tn, index); |
| return ret; |
| } |
| } |
| |
| act = nss_mirred_get(*tc_act); |
| #endif |
| /* |
| * Fill up the nss mirred tc parameters to |
| * its local action structure. |
| */ |
| ASSERT_RTNL(); |
| act->tcf_action = parm->action; |
| act->tcfm_from_ifindex = parm->from_ifindex; |
| act->tcfm_to_ifindex = parm->to_ifindex; |
| dev_hold(to_dev); |
| rcu_assign_pointer(act->tcfm_dev, to_dev); |
| |
| /* |
| * Add the new action instance to the nss mirred action list. |
| */ |
| spin_lock_bh(&nss_mirred_list_lock); |
| list_add(&act->tcfm_list, &nss_mirred_list); |
| spin_unlock_bh(&nss_mirred_list_lock); |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| tcf_hash_insert(tc_act); |
| #endif |
| |
| return ACT_P_CREATED; |
| } |
| |
| /* |
| * nss_mirred_act() |
| * nss mirred action handler. |
| */ |
| static int nss_mirred_act(struct sk_buff *skb, const struct tc_action *tc_act, |
| struct tcf_result *res) |
| { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| struct nss_mirred_tcf *act = tc_act->priv; |
| #else |
| struct nss_mirred_tcf *act = nss_mirred_get(tc_act); |
| #endif |
| struct net_device *dev; |
| struct sk_buff *skb_new; |
| int retval, err; |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| u32 skb_tc_at = G_TC_AT(skb->tc_verd); |
| |
| /* |
| * Return if skb is not at ingress. |
| */ |
| if (!(skb_tc_at & AT_INGRESS)) { |
| return TC_ACT_UNSPEC; |
| } |
| |
| #else |
| if (!skb_at_tc_ingress(skb)) { |
| return TC_ACT_UNSPEC; |
| } |
| #endif |
| |
| /* |
| * Update the last use of action. |
| */ |
| tcf_lastuse_update(&act->tcf_tm); |
| bstats_cpu_update(this_cpu_ptr(act->common.cpu_bstats), skb); |
| |
| rcu_read_lock(); |
| retval = READ_ONCE(act->tcf_action); |
| dev = rcu_dereference(act->tcfm_dev); |
| if (unlikely(!dev)) { |
| nss_igs_error("tc nssmirred: target device is gone\n"); |
| goto out; |
| } |
| |
| if (unlikely(!(dev->flags & IFF_UP))) { |
| nss_igs_error("tc nssmirred: device %s is down\n", dev->name); |
| goto out; |
| } |
| |
| /* |
| * Redirect the packet to the attached IFB interface |
| */ |
| skb_new = skb_clone(skb, GFP_ATOMIC); |
| if (!skb_new) { |
| goto out; |
| } |
| |
| skb_new->skb_iif = skb->dev->ifindex; |
| skb_new->dev = dev; |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| skb_new->tc_verd = SET_TC_FROM(skb_new->tc_verd, skb_tc_at); |
| skb_push_rcsum(skb_new, skb->mac_len); |
| skb_sender_cpu_clear(skb_new); |
| #else |
| skb_set_redirected(skb_new, skb_new->tc_at_ingress); |
| skb_push_rcsum(skb_new, skb->mac_len); |
| #endif |
| |
| err = dev_queue_xmit(skb_new); |
| if (!err) { |
| rcu_read_unlock(); |
| return retval; |
| } |
| out: |
| qstats_overlimit_inc(this_cpu_ptr(act->common.cpu_qstats)); |
| retval = TC_ACT_SHOT; |
| rcu_read_unlock(); |
| return retval; |
| } |
| |
| /* |
| * nss_mirred_dump() |
| * Dump the nssmirred action configurations. |
| */ |
| static int nss_mirred_dump(struct sk_buff *skb, struct tc_action *tc_act, int bind, int ref) |
| { |
| struct tcf_t filter; |
| unsigned char *tail = skb_tail_pointer(skb); |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| struct nss_mirred_tcf *act = tc_act->priv; |
| #else |
| struct nss_mirred_tcf *act = nss_mirred_get(tc_act); |
| #endif |
| struct tc_nss_mirred opt = { |
| .index = act->tcf_index, |
| .action = act->tcf_action, |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| .refcnt = act->tcf_refcnt - ref, |
| .bindcnt = act->tcf_bindcnt - bind, |
| #else |
| .refcnt = refcount_read(&act->tcf_refcnt) - ref, |
| .bindcnt = atomic_read(&act->tcf_bindcnt) - bind, |
| #endif |
| .from_ifindex = act->tcfm_from_ifindex, |
| .to_ifindex = act->tcfm_to_ifindex, |
| }; |
| |
| if (nla_put(skb, TC_NSS_MIRRED_PARMS, sizeof(opt), &opt)) { |
| goto out; |
| } |
| filter.install = jiffies_to_clock_t(jiffies - act->tcf_tm.install); |
| filter.lastuse = jiffies_to_clock_t(jiffies - act->tcf_tm.lastuse); |
| filter.expires = jiffies_to_clock_t(act->tcf_tm.expires); |
| if (nla_put(skb, TC_NSS_MIRRED_TM, sizeof(filter), &filter)) { |
| goto out; |
| } |
| return skb->len; |
| |
| out: |
| nlmsg_trim(skb, tail); |
| return -1; |
| } |
| |
| /* |
| * nss_mirred_unregister_event_handler() |
| * nss mirred un-register event handler. |
| */ |
| static void nss_mirred_unregister_event_handler(struct net_device *dev) |
| { |
| struct nss_ifb_info *ifb_info; |
| |
| /* |
| * IFB interface. |
| */ |
| if (netif_is_ifb_dev(dev)) { |
| ifb_info = nss_ifb_find_dev(dev); |
| } else { |
| /* |
| * Check if the device is an IFB mapped device. |
| */ |
| ifb_info = nss_ifb_find_map_dev(dev); |
| } |
| |
| /* |
| * Device not present in ifb list. |
| */ |
| if (!ifb_info) { |
| return; |
| } |
| |
| /* |
| * Send IFB RESET NEXTHOP configure message to the mapped interface. |
| */ |
| if (!nss_ifb_reset_nexthop(ifb_info)) { |
| nss_igs_error("Error in sending IFB RESET NEXTHOP configure message\n"); |
| } |
| |
| /* |
| * Send IFB CLEAR configure message to the mapped interface. |
| */ |
| if (!nss_ifb_clear_igs_node(ifb_info)) { |
| nss_igs_error("Error in sending IFB CLEAR configure message\n"); |
| } |
| if (netif_is_ifb_dev(dev)) { |
| int32_t ifb_num = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS); |
| |
| if (ifb_num < 0) { |
| nss_igs_error("Invalid %s IFB device\n", dev->name); |
| return; |
| } |
| nss_ifb_delete_if(ifb_num); |
| nss_ifb_list_del(ifb_info); |
| } |
| } |
| |
| /* |
| * nss_mirred_down_event_handler() |
| * nss mirred interface's down event handler. |
| */ |
| static void nss_mirred_down_event_handler(struct net_device *dev) |
| { |
| struct nss_ifb_info *ifb_info; |
| |
| /* |
| * IFB interface. |
| */ |
| if (!netif_is_ifb_dev(dev)) { |
| return; |
| } |
| |
| ifb_info = nss_ifb_find_dev(dev); |
| |
| if (!ifb_info) { |
| return; |
| } |
| |
| if (!nss_ifb_down(ifb_info)) { |
| nss_igs_error("Error in sending IFB DOWN configure message\n"); |
| } |
| } |
| |
| /* |
| * nss_mirred_up_event_handler() |
| * nss mirred interface's up event handler. |
| */ |
| static void nss_mirred_up_event_handler(struct net_device *dev) |
| { |
| struct nss_ifb_info *ifb_info; |
| |
| /* |
| * IFB interface. |
| */ |
| if (!netif_is_ifb_dev(dev)) { |
| return; |
| } |
| |
| ifb_info = nss_ifb_find_dev(dev); |
| |
| if (!ifb_info) { |
| return; |
| } |
| |
| if (!nss_ifb_up(ifb_info)) { |
| nss_igs_error("Error in sending IFB UP configure message\n"); |
| } |
| } |
| |
| /* |
| * nss_mirred_device_event() |
| * nssmirred device event callback. |
| */ |
| static int nss_mirred_device_event(struct notifier_block *unused, |
| unsigned long event, void *ptr) |
| { |
| struct net_device *dev = netdev_notifier_info_to_dev(ptr); |
| struct nss_mirred_tcf *act; |
| |
| switch (event) { |
| case NETDEV_UNREGISTER: |
| nss_mirred_unregister_event_handler(dev); |
| |
| ASSERT_RTNL(); |
| |
| /* |
| * Free up the actions instance present in |
| * the nss mirred list. |
| */ |
| spin_lock_bh(&nss_mirred_list_lock); |
| list_for_each_entry(act, &nss_mirred_list, tcfm_list) { |
| if (rcu_access_pointer(act->tcfm_dev) == dev) { |
| dev_put(dev); |
| RCU_INIT_POINTER(act->tcfm_dev, NULL); |
| } |
| } |
| spin_unlock_bh(&nss_mirred_list_lock); |
| break; |
| case NETDEV_UP: |
| nss_mirred_up_event_handler(dev); |
| break; |
| case NETDEV_DOWN: |
| nss_mirred_down_event_handler(dev); |
| break; |
| } |
| |
| return NOTIFY_DONE; |
| } |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) |
| /* |
| * nss_mirred_walker |
| * nssmirred tcf_action walker |
| */ |
| static int nss_mirred_walker(struct net *net, struct sk_buff *skb, |
| struct netlink_callback *cb, int type, |
| const struct tc_action_ops *ops, |
| struct netlink_ext_ack *extack) |
| { |
| struct tc_action_net *tn = net_generic(net, nss_mirred_net_id); |
| |
| return tcf_generic_walker(tn, skb, cb, type, ops, extack); |
| } |
| |
| /* |
| * nss_mirred_search |
| * nssmirred search idr function. |
| */ |
| static int nss_mirred_search(struct net *net, struct tc_action **a, u32 index) |
| { |
| struct tc_action_net *tn = net_generic(net, nss_mirred_net_id); |
| |
| return tcf_idr_search(tn, a, index); |
| } |
| |
| /* |
| * nss_mirred_dev_put |
| * Release igs dev |
| */ |
| static void nss_mirred_dev_put(void *priv) |
| { |
| struct net_device *dev = priv; |
| |
| dev_put(dev); |
| } |
| |
| /* |
| * nss_mirred_device |
| * Get the igs dev. |
| */ |
| static struct net_device *nss_mirred_device(const struct tc_action *a, tc_action_priv_destructor *destructor) |
| { |
| struct nss_mirred_tcf *m = nss_mirred_get(a); |
| struct net_device *dev; |
| |
| rcu_read_lock(); |
| dev = rcu_dereference(m->tcfm_dev); |
| if (dev) { |
| dev_hold(dev); |
| *destructor = nss_mirred_dev_put; |
| } |
| rcu_read_unlock(); |
| |
| return dev; |
| } |
| #endif |
| |
| /* |
| * nss_mirred_device_notifier |
| * nss mirred device notifier structure. |
| */ |
| static struct notifier_block nss_mirred_device_notifier = { |
| .notifier_call = nss_mirred_device_event, |
| }; |
| |
| /* |
| * nss_mirred_act_ops |
| * Registration structure for nss mirred action. |
| */ |
| static struct tc_action_ops nss_mirred_act_ops = { |
| .kind = "nssmirred", |
| .owner = THIS_MODULE, |
| .act = nss_mirred_act, |
| .dump = nss_mirred_dump, |
| .cleanup = nss_mirred_release, |
| .init = nss_mirred_init, |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| .type = TCA_ACT_MIRRED_NSS, |
| #else |
| .id = TCA_ID_MIRRED_NSS, |
| .walk = nss_mirred_walker, |
| .lookup = nss_mirred_search, |
| .size = sizeof(struct nss_mirred_tcf), |
| .get_dev = nss_mirred_device |
| #endif |
| }; |
| |
| /* |
| * nss_mirred_igs_nf_ops |
| * Pre-routing hooks into netfilter packet monitoring point. |
| */ |
| struct nf_hook_ops nss_mirred_igs_nf_ops[] __read_mostly = { |
| /* |
| * Pre routing hook is used to copy class-id to the ECM rule. |
| */ |
| { |
| .hook = nss_ifb_igs_ip_pre_routing_hook, |
| .pf = NFPROTO_IPV4, |
| .hooknum = NF_INET_PRE_ROUTING, |
| .priority = NF_IP_PRI_CONNTRACK + 1, |
| }, |
| { |
| .hook = nss_ifb_igs_ip_pre_routing_hook, |
| .pf = NFPROTO_IPV6, |
| .hooknum = NF_INET_PRE_ROUTING, |
| .priority = NF_IP_PRI_CONNTRACK + 1, |
| }, |
| }; |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) |
| /* |
| * nss_mirred_init_net |
| * nssmirred net init function. |
| */ |
| static __net_init int nss_mirred_init_net(struct net *net) |
| { |
| struct tc_action_net *tn = net_generic(net, nss_mirred_net_id); |
| nf_register_net_hooks(net, nss_mirred_igs_nf_ops, |
| ARRAY_SIZE(nss_mirred_igs_nf_ops)); |
| |
| return tc_action_net_init(net, tn, &nss_mirred_act_ops); |
| } |
| |
| /* |
| * nss_mirred_exit_net |
| * nssmirred net exit function. |
| */ |
| static void __net_exit nss_mirred_exit_net(struct net *net) |
| { |
| nf_unregister_net_hooks(net, nss_mirred_igs_nf_ops, |
| ARRAY_SIZE(nss_mirred_igs_nf_ops)); |
| } |
| |
| /* |
| * nss_mirred_exit_batch_net |
| * nssmirred exit_batch_net function. |
| */ |
| static void __net_exit nss_mirred_exit_batch_net(struct list_head *net_list) |
| { |
| tc_action_net_exit(net_list, nss_mirred_net_id); |
| } |
| |
| /* |
| * nss_mirred_net_ops |
| * Per netdevice ops. |
| */ |
| static struct pernet_operations nss_mirred_net_ops = { |
| .init = nss_mirred_init_net, |
| .exit = nss_mirred_exit_net, |
| .exit_batch = nss_mirred_exit_batch_net, |
| .id = &nss_mirred_net_id, |
| .size = sizeof(struct tc_action_net), |
| }; |
| #endif |
| |
| /* |
| * nss_mirred_init_module() |
| * nssmirred init function. |
| */ |
| static int __init nss_mirred_init_module(void) |
| { |
| int err = register_netdevice_notifier(&nss_mirred_device_notifier); |
| if (err) { |
| return err; |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| err = tcf_register_action(&nss_mirred_act_ops, NSS_MIRRED_TAB_MASK); |
| if (err) { |
| unregister_netdevice_notifier(&nss_mirred_device_notifier); |
| return err; |
| } |
| |
| err = nf_register_hooks(nss_mirred_igs_nf_ops, ARRAY_SIZE(nss_mirred_igs_nf_ops)); |
| if (err < 0) { |
| nss_igs_error("Registering ingress nf hooks failed, ret: %d\n", err); |
| tcf_unregister_action(&nss_mirred_act_ops); |
| unregister_netdevice_notifier(&nss_mirred_device_notifier); |
| return err; |
| } |
| #else |
| err = tcf_register_action(&nss_mirred_act_ops, &nss_mirred_net_ops); |
| if (err) { |
| unregister_netdevice_notifier(&nss_mirred_device_notifier); |
| return err; |
| } |
| #endif |
| |
| /* |
| * Set the IGS module reference variable. |
| */ |
| nss_igs_module_save(&nss_mirred_act_ops, THIS_MODULE); |
| |
| nss_ifb_init(); |
| return 0; |
| } |
| |
| /* |
| * nss_mirred_cleanup_module() |
| * nssmirred exit function. |
| */ |
| static void __exit nss_mirred_cleanup_module(void) |
| { |
| /* |
| * Reset the IGS module reference variable. |
| */ |
| nss_igs_module_save(&nss_mirred_act_ops, NULL); |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) |
| nf_unregister_hooks(nss_mirred_igs_nf_ops, ARRAY_SIZE(nss_mirred_igs_nf_ops)); |
| |
| /* |
| * Un-register nss mirred action. |
| */ |
| tcf_unregister_action(&nss_mirred_act_ops); |
| #else |
| tcf_unregister_action(&nss_mirred_act_ops, &nss_mirred_net_ops); |
| #endif |
| unregister_netdevice_notifier(&nss_mirred_device_notifier); |
| } |
| |
| module_init(nss_mirred_init_module); |
| module_exit(nss_mirred_cleanup_module); |
| |
| MODULE_LICENSE("Dual BSD/GPL"); |