| /* |
| ************************************************************************** |
| * Copyright (c) 2014-2017, 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. |
| ************************************************************************** |
| */ |
| |
| /* nss_ipsecmgr.c |
| * NSS to HLOS IPSec Manager |
| */ |
| #include <linux/version.h> |
| #include <linux/types.h> |
| #include <linux/ip.h> |
| #include <linux/of.h> |
| #include <linux/ipv6.h> |
| #include <linux/skbuff.h> |
| #include <linux/module.h> |
| #include <linux/bitops.h> |
| #include <linux/netdevice.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/etherdevice.h> |
| #include <linux/vmalloc.h> |
| #include <linux/debugfs.h> |
| #include <linux/atomic.h> |
| #include <net/protocol.h> |
| #include <net/route.h> |
| #include <net/ip6_route.h> |
| #include <net/esp.h> |
| #include <net/xfrm.h> |
| #include <net/icmp.h> |
| |
| #include <nss_api_if.h> |
| #include <nss_ipsec.h> |
| #include <nss_ipsecmgr.h> |
| |
| #include "nss_ipsecmgr_priv.h" |
| #include <nss_tstamp.h> |
| |
| extern bool nss_cmn_get_nss_enabled(void); |
| |
| struct nss_ipsecmgr_drv *ipsecmgr_ctx; |
| |
| static bool gen_pmtu_error = true; |
| module_param(gen_pmtu_error, bool, 0644); |
| MODULE_PARM_DESC(gen_pmtu_error, "Support generation of PMTU error packet"); |
| |
| /* |
| ********************** |
| * Helper Functions |
| ********************** |
| */ |
| |
| /* |
| * nss_ipsecmgr_ref_no_update() |
| * dummy functions for object owner when there is no update |
| */ |
| static void nss_ipsecmgr_ref_no_update(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_ref *child, struct nss_ipsec_msg *nim) |
| { |
| nss_ipsecmgr_trace("ref_no_update triggered for child (%px)\n", child); |
| return; |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_no_free() |
| * dummy functions for object owner when there is no free |
| */ |
| static void nss_ipsecmgr_ref_no_free(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_ref *ref) |
| { |
| nss_ipsecmgr_trace("%px:ref_no_free triggered\n", ref); |
| return; |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_no_overhead() |
| * dummy functions for object owner when there is no overhead |
| */ |
| static uint32_t nss_ipsecmgr_ref_no_overhead(struct nss_ipsecmgr_ref *ref) |
| { |
| nss_ipsecmgr_trace("%px:ref_get_no_overhead triggered\n", ref); |
| return 0; |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_set_overhead() |
| * set the overhead function for reference object |
| */ |
| void nss_ipsecmgr_ref_set_overhead(struct nss_ipsecmgr_ref *ref, nss_ipsecmgr_ref_overhead_t overhead) |
| { |
| ref->overhead = overhead; |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_init() |
| * initiaize the reference object |
| */ |
| void nss_ipsecmgr_ref_init(struct nss_ipsecmgr_ref *ref, nss_ipsecmgr_ref_update_t update, nss_ipsecmgr_ref_free_t free) |
| { |
| INIT_LIST_HEAD(&ref->head); |
| INIT_LIST_HEAD(&ref->node); |
| |
| ref->id = 0; |
| ref->parent = NULL; |
| ref->update = update ? update : nss_ipsecmgr_ref_no_update; |
| ref->free = free ? free : nss_ipsecmgr_ref_no_free; |
| ref->overhead = nss_ipsecmgr_ref_no_overhead; |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_add() |
| * add child reference to parent chain |
| */ |
| void nss_ipsecmgr_ref_add(struct nss_ipsecmgr_ref *child, struct nss_ipsecmgr_ref *parent) |
| { |
| /* |
| * if child is already part of an existing chain then remove it before |
| * adding it to the new one. In case this is a new entry then the list |
| * init during alloc would ensure that the "del_init" operation results |
| * in a no-op |
| */ |
| list_del_init(&child->node); |
| list_add(&child->node, &parent->head); |
| |
| child->parent = parent; |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_update() |
| * update the "ref" object and link it to the parent |
| */ |
| void nss_ipsecmgr_ref_update(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_ref *child, struct nss_ipsec_msg *nim) |
| { |
| struct nss_ipsecmgr_ref *entry; |
| |
| child->id++; |
| child->update(priv, child, nim); |
| |
| /* |
| * If, there are references to associated with this |
| * object then notify them about the change. This allows |
| * the "ref" objects to trigger notifications to NSS for |
| * updates to SA |
| */ |
| list_for_each_entry(entry, &child->head, node) { |
| nss_ipsecmgr_ref_update(priv, entry, nim); |
| } |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_overhead() |
| * Get the SA overhead of the reference passed |
| * |
| * note: ideally this will trigger a chain of callbacks till |
| * the SA |
| */ |
| uint32_t nss_ipsecmgr_ref_overhead(struct nss_ipsecmgr_ref *ref) |
| { |
| if (!ref->parent) |
| return ref->overhead(ref); |
| |
| return nss_ipsecmgr_ref_overhead(ref->parent); |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_free() |
| * Free all references from the "ref" object |
| * |
| * Note: If, the "ref" has child references then it |
| * will walk the child reference chain first and issue |
| * free for each of the associated "child ref" objects. |
| * At the end it will invoke free for the "parent" ref |
| * object. |
| * |
| * +-------+ +-------+ +-------+ |
| * | SA1 +---> SA2 +---> SA3 | |
| * +---+---+ +---+---+ +-------+ |
| * | |
| * +---V---+ +-------+ +-------+ |
| * | Flow1 +---> Sub1 +---> Flow4 | |
| * +-------+ +---+---+ +-------+ |
| * | |
| * +---v---+ |
| * | Flow2 | |
| * +---+---+ |
| * | |
| * +---v---+ |
| * | Flow3 | |
| * +-------+ |
| */ |
| void nss_ipsecmgr_ref_free(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_ref *ref) |
| { |
| struct nss_ipsecmgr_ref *entry; |
| |
| while (!list_empty(&ref->head)) { |
| entry = list_first_entry(&ref->head, struct nss_ipsecmgr_ref, node); |
| nss_ipsecmgr_ref_free(priv, entry); |
| } |
| |
| list_del_init(&ref->node); |
| ref->free(priv, ref); |
| } |
| |
| /* |
| * nss_ipsecmgr_ref_is_child() |
| * return true if the child is direct sibling of parent |
| */ |
| bool nss_ipsecmgr_ref_is_child(struct nss_ipsecmgr_ref *child, struct nss_ipsecmgr_ref *parent) |
| { |
| struct nss_ipsecmgr_ref *entry; |
| |
| list_for_each_entry(entry, &parent->head, node) { |
| if (entry == child) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* |
| ********************** |
| * Netdev ops |
| ********************** |
| */ |
| |
| /* |
| * nss_ipsecmgr_tunnel_open() |
| * open the tunnel for usage |
| */ |
| static int nss_ipsecmgr_tunnel_open(struct net_device *dev) |
| { |
| struct nss_ipsecmgr_priv *priv; |
| |
| priv = netdev_priv(dev); |
| |
| netif_start_queue(dev); |
| |
| return 0; |
| } |
| |
| /* |
| * nss_ipsecmgr_tunnel_stop() |
| * stop the IPsec tunnel |
| */ |
| static int nss_ipsecmgr_tunnel_stop(struct net_device *dev) |
| { |
| struct nss_ipsecmgr_priv *priv; |
| |
| priv = netdev_priv(dev); |
| |
| netif_stop_queue(dev); |
| |
| return 0; |
| } |
| |
| /* |
| * nss_ipsecmgr_tunnel_tx() |
| * tunnel transmit function |
| */ |
| static netdev_tx_t nss_ipsecmgr_tunnel_tx(struct sk_buff *skb, struct net_device *dev) |
| { |
| struct nss_ipsecmgr_flow_data flow_data = {0}; |
| bool process_mtu = gen_pmtu_error; |
| struct nss_ipsecmgr_priv *priv; |
| bool expand_skb = false; |
| int nhead, ntail; |
| bool tstamp_skb; |
| |
| priv = netdev_priv(dev); |
| nhead = dev->needed_headroom; |
| ntail = dev->needed_tailroom; |
| |
| tstamp_skb = skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP; |
| |
| /* |
| * Check if skb is non-linear |
| */ |
| if (skb_is_nonlinear(skb)) { |
| nss_ipsecmgr_trace("%s: NSS IPSEC does not support fragments %px\n", dev->name, skb); |
| goto free; |
| } |
| |
| /* |
| * Check if skb is shared |
| */ |
| if (unlikely(skb_shared(skb))) { |
| nss_ipsecmgr_trace("%s: Shared skb is not supported: %px\n", dev->name, skb); |
| goto free; |
| } |
| |
| /* |
| * Check if packet is given starting from network header |
| */ |
| if (skb->data != skb_network_header(skb)) { |
| nss_ipsecmgr_trace("%s: 'Skb data is not starting from IP header\n", dev->name); |
| goto free; |
| } |
| |
| /* |
| * For all these cases |
| * - create a writable copy of buffer |
| * - increase the head room |
| * - increase the tail room |
| */ |
| if (skb_cloned(skb) || (skb_headroom(skb) < nhead) || (skb_tailroom(skb) < ntail)) { |
| expand_skb = true; |
| } |
| |
| if (expand_skb && pskb_expand_head(skb, nhead, ntail, GFP_KERNEL)) { |
| nss_ipsecmgr_trace("%s: unable to expand buffer\n", dev->name); |
| goto free; |
| } |
| |
| /* |
| * Before proceeding check for the following conditions |
| * For IPv4 packet if DF bit is set then process_mtu |
| */ |
| if (process_mtu && (skb->protocol == htons(ETH_P_IP))) |
| process_mtu = !!(ip_hdr(skb)->frag_off & htons(IP_DF)); |
| |
| /* |
| * check whether the IPsec encapsulation can be offloaded to NSS |
| * - if the flow matches a subnet rule, then a new flow rule |
| * is added to NSS. |
| * - if the flow doesn't match any subnet, then the packet |
| * is dropped |
| */ |
| if (!nss_ipsecmgr_flow_offload(priv, skb, &flow_data)) { |
| nss_ipsecmgr_warn("%px:failed to accelerate flow\n", dev); |
| goto free; |
| } |
| |
| /* |
| * Check if pre-fragmentation is not enabled or already a fragment. |
| * then send the buffer on its way to NSS |
| */ |
| if (process_mtu && nss_ipsecmgr_flow_process_pmtu(priv, skb, &flow_data)) |
| goto free; |
| |
| /* |
| * If the packet needs to timestamped. Send to |
| * timestamping NSS module. |
| */ |
| if (unlikely(tstamp_skb)) { |
| if (nss_tstamp_tx_buf(ipsecmgr_ctx->nss_ctx, skb, NSS_IPSEC_ENCAP_IF_NUMBER)) |
| goto free; |
| |
| return NETDEV_TX_OK; |
| } |
| |
| /* |
| * Send the packet down |
| */ |
| if (nss_ipsec_tx_buf(skb, ipsecmgr_ctx->encap_ifnum) != 0) { |
| /* |
| * TODO: NEED TO STOP THE QUEUE |
| */ |
| goto free; |
| } |
| |
| return NETDEV_TX_OK; |
| |
| free: |
| dev_kfree_skb_any(skb); |
| return NETDEV_TX_OK; |
| } |
| |
| /* |
| * nss_ipsecmgr_tunnel_stats() |
| * get tunnel statistics |
| */ |
| static struct rtnl_link_stats64 *nss_ipsecmgr_tunnel_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats) |
| { |
| struct nss_ipsecmgr_priv *priv = netdev_priv(dev); |
| |
| memset(stats, 0, sizeof(struct rtnl_link_stats64)); |
| |
| /* |
| * trigger a stats update chain |
| */ |
| read_lock_bh(&ipsecmgr_ctx->lock); |
| memcpy(stats, &priv->stats, sizeof(struct rtnl_link_stats64)); |
| read_unlock_bh(&ipsecmgr_ctx->lock); |
| |
| return stats; |
| } |
| |
| /* |
| * nss_ipsecmgr_change_mtu() |
| * change MTU size of IPsec tunnel device |
| */ |
| static int32_t nss_ipsecmgr_change_mtu(struct net_device *dev, int32_t mtu) |
| { |
| dev->mtu = mtu; |
| return 0; |
| } |
| |
| /* NSS IPsec tunnel operation */ |
| static const struct net_device_ops nss_ipsecmgr_tunnel_ops = { |
| .ndo_open = nss_ipsecmgr_tunnel_open, |
| .ndo_stop = nss_ipsecmgr_tunnel_stop, |
| .ndo_start_xmit = nss_ipsecmgr_tunnel_tx, |
| .ndo_get_stats64 = nss_ipsecmgr_tunnel_stats64, |
| .ndo_change_mtu = nss_ipsecmgr_change_mtu, |
| }; |
| |
| /* |
| * nss_ipsecmgr_tunnel_free() |
| * free an existing IPsec tunnel interface |
| */ |
| static void nss_ipsecmgr_tunnel_free(struct net_device *dev) |
| { |
| nss_ipsecmgr_info("IPsec tunnel device(%s) freed\n", dev->name); |
| |
| free_netdev(dev); |
| } |
| |
| /* |
| * nss_ipsecmr_setup_tunnel() |
| * setup the IPsec tunnel |
| */ |
| static void nss_ipsecmgr_tunnel_setup(struct net_device *dev) |
| { |
| dev->addr_len = ETH_ALEN; |
| dev->mtu = NSS_IPSECMGR_TUN_MTU(ETH_DATA_LEN); |
| |
| dev->hard_header_len = NSS_IPSECMGR_TUN_MAX_HDR_LEN; |
| dev->needed_headroom = NSS_IPSECMGR_TUN_HEADROOM; |
| dev->needed_tailroom = NSS_IPSECMGR_TUN_TAILROOM; |
| |
| dev->type = NSS_IPSEC_ARPHRD_IPSEC; |
| |
| dev->ethtool_ops = NULL; |
| dev->header_ops = NULL; |
| dev->netdev_ops = &nss_ipsecmgr_tunnel_ops; |
| |
| dev->destructor = nss_ipsecmgr_tunnel_free; |
| |
| /* |
| * get the MAC address from the ethernet device |
| */ |
| random_ether_addr(dev->dev_addr); |
| |
| memset(dev->broadcast, 0xff, dev->addr_len); |
| memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len); |
| } |
| |
| /* |
| * nss_ipsecmgr_tunnel_get_callback() |
| * get the callback entry for the dev index |
| * |
| * Note: this is typically expected in the RX BH path |
| */ |
| static struct nss_ipsecmgr_callback_entry *nss_ipsecmgr_tunnel_get_callback(int dev_index) |
| { |
| struct nss_ipsecmgr_callback_db *cb_db = &ipsecmgr_ctx->cb_db; |
| struct nss_ipsecmgr_callback_entry *cb_entry; |
| |
| if (!atomic_read(&cb_db->num_entries)) |
| return NULL; |
| |
| BUG_ON(!in_atomic()); |
| |
| /* |
| * search the callback database to find if there |
| * are any registered callback for the net_device |
| */ |
| read_lock(&ipsecmgr_ctx->lock); /* lock */ |
| list_for_each_entry(cb_entry, &cb_db->entries, node) { |
| if (cb_entry->dev_index == dev_index) { |
| read_unlock(&ipsecmgr_ctx->lock); /* unlock */ |
| return cb_entry; |
| } |
| } |
| |
| read_unlock(&ipsecmgr_ctx->lock); /* unlock */ |
| |
| return NULL; |
| } |
| |
| /* |
| * nss_ipsecmgr_tunnel_get_dev() |
| * Get the net_device associated with the packet. |
| */ |
| static struct net_device *nss_ipsecmgr_tunnel_get_dev(struct sk_buff *skb) |
| { |
| struct nss_ipsecmgr_sa_entry *sa_entry; |
| struct nss_ipsec_tuple tuple; |
| struct nss_ipsecmgr_ref *ref; |
| struct nss_ipsecmgr_key key; |
| struct net_device *dev; |
| struct dst_entry *dst; |
| struct ip_esp_hdr *esph; |
| size_t hdr_sz = 0; |
| |
| skb_reset_mac_header(skb); |
| skb_reset_network_header(skb); |
| |
| switch (ip_hdr(skb)->version) { |
| case IPVERSION: |
| { |
| struct iphdr *iph = ip_hdr(skb); |
| struct rtable *rt; |
| bool is_encap; |
| |
| hdr_sz = sizeof(struct iphdr); |
| skb->protocol = cpu_to_be16(ETH_P_IP); |
| |
| /* |
| * Set the transport header skb pointer |
| */ |
| skb_set_transport_header(skb, hdr_sz); |
| |
| is_encap = (iph->protocol == IPPROTO_ESP); |
| if (!is_encap && (iph->protocol == IPPROTO_UDP)) { |
| if (udp_hdr(skb)->dest == NSS_IPSECMGR_NATT_PORT_DATA) { |
| hdr_sz += sizeof(struct udphdr); |
| is_encap = true; |
| } |
| } |
| |
| if (!is_encap) { |
| rt = ip_route_output(&init_net, iph->saddr, 0, 0, 0); |
| if (IS_ERR(rt)) { |
| return NULL; |
| } |
| |
| dst = (struct dst_entry *)rt; |
| dev = dst->dev; |
| dst_release(dst); |
| goto done; |
| } |
| |
| nss_ipsecmgr_v4_hdr2tuple(iph, &tuple); |
| break; |
| } |
| |
| case 6: |
| { |
| struct ipv6hdr *ip6h = ipv6_hdr(skb); |
| struct flowi6 fl6; |
| |
| hdr_sz = sizeof(struct ipv6hdr); |
| skb->protocol = cpu_to_be16(ETH_P_IPV6); |
| |
| nss_ipsecmgr_v6_hdr2tuple(ip6h, &tuple); |
| |
| if (tuple.proto_next_hdr != IPPROTO_ESP) { |
| memset(&fl6, 0, sizeof(fl6)); |
| memcpy(&fl6.daddr, &ip6h->saddr, sizeof(fl6.daddr)); |
| |
| dst = ip6_route_output(&init_net, NULL, &fl6); |
| if (IS_ERR(dst)) { |
| return NULL; |
| } |
| |
| dev = dst->dev; |
| dst_release(dst); |
| goto done; |
| } |
| |
| if (ip6h->nexthdr == NEXTHDR_FRAGMENT) { |
| hdr_sz += sizeof(struct frag_hdr); |
| } |
| break; |
| } |
| default: |
| nss_ipsecmgr_warn("%px:could not get dev for the flow\n", skb); |
| return NULL; |
| } |
| |
| skb_set_transport_header(skb, hdr_sz); |
| esph = ip_esp_hdr(skb); |
| tuple.esp_spi = ntohl(esph->spi); |
| |
| nss_ipsecmgr_sa_tuple2key(&tuple, &key); |
| |
| ref = nss_ipsecmgr_sa_lookup(&key); |
| if (!ref) { |
| nss_ipsecmgr_trace("unable to find SA (%px)\n", skb); |
| return NULL; |
| } |
| |
| sa_entry = container_of(ref, struct nss_ipsecmgr_sa_entry, ref); |
| dev = sa_entry->priv->dev; |
| done: |
| |
| skb->pkt_type = PACKET_HOST; |
| skb->skb_iif = dev->ifindex; |
| skb->dev = dev; |
| return dev; |
| } |
| |
| /* |
| * nss_ipsecmgr_tunnel_rx() |
| * receive NSS exception packets |
| */ |
| static void nss_ipsecmgr_tunnel_rx(struct net_device *dummy, struct sk_buff *skb, __attribute((unused)) struct napi_struct *napi) |
| { |
| struct nss_ipsecmgr_callback_entry *cb_entry; |
| nss_ipsecmgr_data_cb_t cb_fn; |
| struct net_device *dev; |
| |
| BUG_ON(dummy == NULL); |
| BUG_ON(skb == NULL); |
| |
| dev = nss_ipsecmgr_tunnel_get_dev(skb); |
| if (unlikely(!dev)) { |
| nss_ipsecmgr_trace("cannot find a dev(%px)\n", skb); |
| dev_kfree_skb_any(skb); |
| return; |
| } |
| |
| dev_hold(dev); |
| |
| /* |
| * search the device in the registered callback table; |
| * if there is match then load the callback for indication |
| */ |
| cb_entry = nss_ipsecmgr_tunnel_get_callback(dev->ifindex); |
| if (!cb_entry) { |
| netif_receive_skb(skb); |
| goto done; |
| } |
| |
| cb_fn = cb_entry->data; |
| BUG_ON(!cb_fn); |
| |
| cb_fn(cb_entry->app_data, skb); |
| done: |
| dev_put(dev); |
| } |
| |
| /* |
| * nss_ipsecmgr_tunnel_update_stats() |
| * Update tunnel stats |
| */ |
| static void nss_ipsecmgr_tunnel_update_stats(struct nss_ipsecmgr_priv *priv, struct nss_ipsec_msg *nim) |
| { |
| struct rtnl_link_stats64 *tun_stats; |
| struct nss_ipsec_sa_stats *pkts; |
| |
| tun_stats = &priv->stats; |
| pkts = &nim->msg.stats.sa; |
| |
| if (nim->type == NSS_IPSEC_TYPE_ENCAP) { |
| /* |
| * update tunnel specific stats |
| */ |
| tun_stats->tx_bytes += pkts->bytes; |
| tun_stats->tx_packets += pkts->count; |
| |
| tun_stats->tx_dropped += pkts->no_headroom; |
| tun_stats->tx_dropped += pkts->no_tailroom; |
| tun_stats->tx_dropped += pkts->no_resource; |
| tun_stats->tx_dropped += pkts->fail_queue; |
| tun_stats->tx_dropped += pkts->fail_hash; |
| tun_stats->tx_dropped += pkts->fail_replay; |
| return; |
| } |
| |
| /* |
| * update tunnel specific stats |
| */ |
| if (nim->type == NSS_IPSEC_TYPE_ENCAP) { |
| tun_stats->tx_bytes += pkts->bytes; |
| tun_stats->tx_packets += pkts->count; |
| |
| tun_stats->tx_dropped += pkts->no_headroom; |
| tun_stats->tx_dropped += pkts->no_tailroom; |
| tun_stats->tx_dropped += pkts->no_resource; |
| tun_stats->tx_dropped += pkts->fail_queue; |
| tun_stats->tx_dropped += pkts->fail_hash; |
| tun_stats->tx_dropped += pkts->fail_replay; |
| return; |
| } |
| |
| tun_stats->rx_bytes += pkts->bytes; |
| tun_stats->rx_packets += pkts->count; |
| |
| tun_stats->rx_dropped += pkts->no_headroom; |
| tun_stats->rx_dropped += pkts->no_tailroom; |
| tun_stats->rx_dropped += pkts->no_resource; |
| tun_stats->rx_dropped += pkts->fail_queue; |
| tun_stats->rx_dropped += pkts->fail_hash; |
| tun_stats->rx_dropped += pkts->fail_replay; |
| } |
| |
| /* |
| * nss_ipsecmgr_tunnel_notify() |
| * asynchronous event reception |
| */ |
| static void nss_ipsecmgr_tunnel_notify(__attribute((unused))void *app_data, struct nss_ipsec_msg *nim) |
| { |
| struct nss_ipsecmgr_node_stats *drv_stats; |
| struct nss_ipsec_node_stats *node_stats; |
| struct nss_ipsecmgr_sa_stats *sa_stats; |
| struct nss_ipsecmgr_event stats_event; |
| struct nss_ipsecmgr_sa_entry *sa; |
| struct nss_ipsec_sa_stats *pkts; |
| struct nss_ipsecmgr_priv *priv; |
| nss_ipsecmgr_event_cb_t cb_fn; |
| struct nss_ipsecmgr_ref *ref; |
| struct nss_ipsecmgr_key key; |
| bool reset_fail_hash; |
| struct net_device *dev; |
| |
| BUG_ON(nim == NULL); |
| |
| /* |
| * this holds the ref_cnt for the device |
| */ |
| dev = dev_get_by_index(&init_net, nim->tunnel_id); |
| if (!dev) { |
| nss_ipsecmgr_info("event received on deallocated I/F (%d)\n", nim->tunnel_id); |
| return; |
| } |
| |
| priv = netdev_priv(dev); |
| |
| switch (nim->cm.type) { |
| case NSS_IPSEC_MSG_TYPE_SYNC_SA_STATS: |
| |
| /* |
| * prepare and lookup sa based on selector sent from nss |
| */ |
| nss_ipsecmgr_sa_tuple2key(&nim->tuple, &key); |
| |
| write_lock(&ipsecmgr_ctx->lock); |
| |
| ref = nss_ipsecmgr_sa_lookup(&key); |
| if (!ref) { |
| write_unlock(&ipsecmgr_ctx->lock); |
| nss_ipsecmgr_trace("event received on deallocated SA tunnel:(%d)\n", nim->tunnel_id); |
| goto done; |
| } |
| |
| sa = container_of(ref, struct nss_ipsecmgr_sa_entry, ref); |
| |
| /* |
| * update sa stats in the local database |
| */ |
| nss_ipsecmgr_sa_stats_update(nim, sa); |
| |
| sa_stats = &stats_event.data.stats; |
| |
| /* |
| * update tunnel stats |
| */ |
| sa_stats->fail_hash_alarm = false; |
| nss_ipsecmgr_tunnel_update_stats(priv, nim); |
| |
| if ((nim->type == NSS_IPSEC_TYPE_DECAP) && |
| sa->fail_hash_thresh) { |
| pkts = &nim->msg.stats.sa; |
| |
| /* |
| * If the fail_hash_count is zero and packet count is |
| * non-zero. It indicates that the continuous hash |
| * failure was a transient state hence reset the count |
| */ |
| reset_fail_hash = (!pkts->fail_hash_cont && |
| pkts->count); |
| sa->pkts.fail_hash_cont = reset_fail_hash ? 0 |
| : (sa->pkts.fail_hash_cont + |
| pkts->fail_hash_cont); |
| |
| /* |
| * Check the fail_hash_cont hash crossed the threshold, |
| * if yes set the alarm. |
| */ |
| if (sa->pkts.fail_hash_cont >= sa->fail_hash_thresh) { |
| sa_stats->fail_hash_alarm = true; |
| sa->pkts.fail_hash_cont -= sa->fail_hash_thresh; |
| } |
| } |
| |
| memcpy(&sa_stats->sa, &sa->sa_info, |
| sizeof(struct nss_ipsecmgr_sa)); |
| sa_stats->crypto_index = sa->nim.msg.rule.data.crypto_index; |
| write_unlock(&ipsecmgr_ctx->lock); |
| |
| /* |
| * if event callback is available then post the statistics using the callback function |
| */ |
| cb_fn = priv->cb.event; |
| if (cb_fn) { |
| stats_event.type = NSS_IPSECMGR_EVENT_SA_STATS; |
| |
| /* |
| * copy stats and SA information |
| */ |
| sa_stats->seq_num = nim->msg.stats.sa.seq_num; |
| |
| sa_stats->esn_enabled = nim->msg.stats.sa.esn_enabled; |
| sa_stats->window_max = nim->msg.stats.sa.window_max; |
| sa_stats->window_size = nim->msg.stats.sa.window_size; |
| |
| sa_stats->pkts.count = nim->msg.stats.sa.count; |
| sa_stats->pkts.bytes = nim->msg.stats.sa.bytes; |
| |
| cb_fn(priv->cb.app_data, &stats_event); |
| } |
| |
| break; |
| |
| case NSS_IPSEC_MSG_TYPE_SYNC_NODE_STATS: |
| |
| drv_stats = &ipsecmgr_ctx->enc_stats; |
| if (unlikely(nim->type == NSS_IPSEC_TYPE_DECAP)) { |
| drv_stats = &ipsecmgr_ctx->dec_stats; |
| } |
| |
| node_stats = &nim->msg.stats.node; |
| drv_stats->enqueued += node_stats->enqueued; |
| drv_stats->completed += node_stats->completed; |
| drv_stats->linearized += node_stats->linearized; |
| drv_stats->exceptioned += node_stats->exceptioned; |
| drv_stats->fail_enqueue += node_stats->fail_enqueue; |
| |
| break; |
| |
| default: |
| break; |
| } |
| done: |
| dev_put(dev); |
| } |
| |
| /* |
| * nss_ipsecmgr_node_stats_read() |
| * read node statistics |
| */ |
| static ssize_t nss_ipsecmgr_node_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos) |
| { |
| struct nss_ipsecmgr_node_stats *enc_stats = &ipsecmgr_ctx->enc_stats; |
| struct nss_ipsecmgr_node_stats *dec_stats = &ipsecmgr_ctx->dec_stats; |
| ssize_t ret = 0; |
| char *local; |
| int len; |
| |
| local = vmalloc(NSS_IPSECMGR_MAX_BUF_SZ); |
| |
| len = 0; |
| len += snprintf(local + len, NSS_IPSECMGR_MAX_BUF_SZ - len, "\tencap_enqueued: %lld\n", enc_stats->enqueued); |
| len += snprintf(local + len, NSS_IPSECMGR_MAX_BUF_SZ - len, "\tencap_completed: %lld\n", enc_stats->completed); |
| len += snprintf(local + len, NSS_IPSECMGR_MAX_BUF_SZ - len, "\tencap_exceptioned: %lld\n", enc_stats->exceptioned); |
| len += snprintf(local + len, NSS_IPSECMGR_MAX_BUF_SZ - len, "\tencap_enqueue_failed: %lld\n", enc_stats->fail_enqueue); |
| |
| len += snprintf(local + len, NSS_IPSECMGR_MAX_BUF_SZ - len, "\tdecap_enqueued: %lld\n", dec_stats->enqueued); |
| len += snprintf(local + len, NSS_IPSECMGR_MAX_BUF_SZ - len, "\tdecap_completed: %lld\n", dec_stats->completed); |
| len += snprintf(local + len, NSS_IPSECMGR_MAX_BUF_SZ - len, "\tdecap_exceptioned: %lld\n", dec_stats->exceptioned); |
| len += snprintf(local + len, NSS_IPSECMGR_MAX_BUF_SZ - len, "\tdecap_enqueue_failed: %lld\n", dec_stats->fail_enqueue); |
| |
| ret = simple_read_from_buffer(ubuf, sz, ppos, local, len + 1); |
| |
| vfree(local); |
| |
| return ret; |
| } |
| |
| /* |
| * file operation structure instance |
| */ |
| static const struct file_operations node_stats_op = { |
| .open = simple_open, |
| .llseek = default_llseek, |
| .read = nss_ipsecmgr_node_stats_read, |
| }; |
| |
| /* |
| * nss_ipsecmgr_tunnel_add() |
| * add a IPsec pseudo tunnel device |
| */ |
| struct net_device *nss_ipsecmgr_tunnel_add(struct nss_ipsecmgr_callback *cb) |
| { |
| struct nss_ipsecmgr_callback_db *cb_db = &ipsecmgr_ctx->cb_db; |
| struct nss_ipsecmgr_priv *priv; |
| struct net_device *dev; |
| int status; |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) |
| dev = alloc_netdev(sizeof(struct nss_ipsecmgr_priv), NSS_IPSECMGR_TUN_NAME, nss_ipsecmgr_tunnel_setup); |
| #else |
| dev = alloc_netdev(sizeof(struct nss_ipsecmgr_priv), NSS_IPSECMGR_TUN_NAME, NET_NAME_ENUM, nss_ipsecmgr_tunnel_setup); |
| #endif |
| if (!dev) { |
| nss_ipsecmgr_error("unable to allocate a tunnel device\n"); |
| return NULL; |
| } |
| |
| priv = netdev_priv(dev); |
| priv->dev = dev; |
| |
| INIT_LIST_HEAD(&priv->cb.node); |
| priv->cb.dev_index = dev->ifindex; |
| priv->cb.app_data = cb->ctx; |
| priv->cb.data = cb->data_fn; |
| priv->cb.event = cb->event_fn; |
| |
| status = rtnl_is_locked() ? register_netdevice(dev) : register_netdev(dev); |
| if (status < 0) { |
| nss_ipsecmgr_error("register net dev failed :%s\n", dev->name); |
| goto fail; |
| } |
| |
| /* |
| * only register if data callback is available |
| */ |
| if (cb->data_fn){ |
| write_lock_bh(&ipsecmgr_ctx->lock); |
| list_add(&priv->cb.node, &cb_db->entries); |
| atomic_inc(&cb_db->num_entries); |
| write_unlock_bh(&ipsecmgr_ctx->lock); |
| } |
| |
| return dev; |
| fail: |
| free_netdev(dev); |
| return NULL; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_tunnel_add); |
| |
| /* |
| * nss_ipsecmgr_del_tunnel() |
| * delete an existing IPsec tunnel |
| */ |
| bool nss_ipsecmgr_tunnel_del(struct net_device *dev) |
| { |
| struct nss_ipsecmgr_callback_db *cb_db = &ipsecmgr_ctx->cb_db; |
| struct nss_ipsecmgr_priv *priv = netdev_priv(dev); |
| |
| write_lock_bh(&ipsecmgr_ctx->lock); |
| atomic_dec(&cb_db->num_entries); |
| list_del(&priv->cb.node); |
| write_unlock_bh(&ipsecmgr_ctx->lock); |
| |
| nss_ipsecmgr_sa_flush_all(priv); |
| |
| /* |
| * The unregister should start here but the expectation is that the free would |
| * happen when the reference count goes down to '0' |
| */ |
| rtnl_is_locked() ? unregister_netdevice(dev) : unregister_netdev(dev); |
| |
| return true; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_tunnel_del); |
| |
| /* |
| * nss_ipsecmgr_tunnel_update_callback() |
| * update the callback databse with the new device index |
| * |
| * Note: the callback is database that holds callback functions w.r.t a |
| * device index. The tunnel_add would typically load this with the device |
| * index of the tunnel. Overtime the caller can decide to update this with |
| * different device index. In cases where the IPsec stack typically creates |
| * a tunnel device of its own (KLIPS), the callback would then get mapped to |
| * these devices instead of the IPsec manager created tunnel netdevice |
| */ |
| void nss_ipsecmgr_tunnel_update_callback(struct net_device *tun, struct net_device *cur) |
| { |
| struct nss_ipsecmgr_callback_db *cb_db = &ipsecmgr_ctx->cb_db; |
| struct nss_ipsecmgr_callback_entry *cb_entry; |
| int tun_dev_index = tun->ifindex; |
| |
| if (!atomic_read(&cb_db->num_entries)) |
| return; |
| |
| /* |
| * search the old device index in callback table |
| * and replace it with the new index |
| */ |
| write_lock_bh(&ipsecmgr_ctx->lock); |
| list_for_each_entry(cb_entry, &cb_db->entries, node) { |
| if (cb_entry->dev_index == tun_dev_index) { |
| cb_entry->dev_index = cur->ifindex; |
| break; |
| } |
| } |
| |
| write_unlock_bh(&ipsecmgr_ctx->lock); |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_tunnel_update_callback); |
| |
| static const struct net_device_ops nss_ipsecmgr_ipsec_ndev_ops; |
| |
| /* |
| * nss_ipsecmgr_dummy_netdevice_setup() |
| * Setup function for dummy netdevice. |
| */ |
| static void nss_ipsecmgr_dummy_netdevice_setup(struct net_device *dev) |
| { |
| } |
| |
| /* |
| * flow file operation structure instance |
| */ |
| static const struct file_operations flow_stats_op = { |
| .open = simple_open, |
| .llseek = default_llseek, |
| .read = nss_ipsecmgr_flow_stats_read, |
| }; |
| |
| /* |
| * per flow file operation structure instance |
| */ |
| static const struct file_operations per_flow_stats_op = { |
| .open = simple_open, |
| .llseek = default_llseek, |
| .read = nss_ipsecmgr_per_flow_stats_read, |
| .write = nss_ipsecmgr_per_flow_stats_write, |
| }; |
| |
| /* |
| * SA file operation structure instance |
| */ |
| static const struct file_operations sa_stats_op = { |
| .open = simple_open, |
| .llseek = default_llseek, |
| .read = nss_ipsecmgr_sa_stats_read, |
| }; |
| |
| /* |
| * subnet file operation structure instance |
| */ |
| static const struct file_operations subnet_stats_op = { |
| .open = simple_open, |
| .llseek = default_llseek, |
| .read = nss_ipsecmgr_netmask_stats_read, |
| }; |
| |
| /* |
| * nss_ipsecmgr_init_stats_debugfs() |
| * Initialize the debugfs tree. |
| */ |
| static int nss_ipsecmgr_init_stats_debugfs(struct dentry *stats_root) |
| { |
| if (!debugfs_create_file("subnet", S_IRUGO, stats_root, (uint32_t *)NULL, &subnet_stats_op)) { |
| nss_ipsecmgr_error("Debugfs file creation failed for subnet\n"); |
| return -1; |
| } |
| |
| if (!debugfs_create_file("sa", S_IRUGO, stats_root, NULL, &sa_stats_op)) { |
| nss_ipsecmgr_error("Debugfs file creation failed for SA\n"); |
| return -1; |
| } |
| |
| if (!debugfs_create_file("flow", S_IRUGO, stats_root, NULL, &flow_stats_op)) { |
| nss_ipsecmgr_error("Debugfs file creation failed for flow\n"); |
| return -1; |
| } |
| |
| if (!debugfs_create_file("per_flow", S_IRWXUGO, stats_root, NULL, &per_flow_stats_op)) { |
| nss_ipsecmgr_error("Debugfs file creation failed for per flow\n"); |
| return -1; |
| } |
| |
| if (!debugfs_create_file("node", S_IRUGO, stats_root, NULL, &node_stats_op)) { |
| nss_ipsecmgr_error("Debugfs file creation failed for per node\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| #if defined NSS_IPSECMGR_PMTU_SUPPORT |
| |
| /* |
| * nss_ipsecmgr_esp4_rcv() |
| * IPv4 Receive handler for ESP protocol |
| */ |
| static int nss_ipsecmgr_tunnel_rx_esp4(struct sk_buff *skb) |
| { |
| /* |
| * TODO:This can potentially receive ESP packets in when |
| * the outer ESP rule is flushed. In which case the DECAP |
| * packets entering linux must be bounced through offload |
| */ |
| dev_kfree_skb_any(skb); |
| return 0; |
| } |
| |
| /* |
| * nss_ipsecmgr_esp4_err() |
| * IPv4 Error handler for ESP protocol |
| */ |
| static void nss_ipsecmgr_tunnel_error_esp4(struct sk_buff *skb, uint32_t mtu) |
| { |
| struct nss_ipsecmgr_sa_entry *sa_entry; |
| struct nss_ipsecmgr_key key = { {0} }; |
| struct nss_ipsecmgr_sa_v4 sa = {0}; |
| struct nss_ipsecmgr_priv *priv; |
| struct nss_ipsecmgr_ref *ref; |
| struct ip_esp_hdr *esph; |
| struct iphdr *iph; |
| |
| /* |
| * If the ICMP error is not PMTU then return |
| */ |
| if (icmp_hdr(skb)->type != ICMP_DEST_UNREACH) |
| return; |
| |
| if (icmp_hdr(skb)->code != ICMP_FRAG_NEEDED) |
| return; |
| |
| /* |
| * Skb data now points to the IP header present in the |
| * IMCP payload. It will be of the packet which generated the |
| * PMTU error. Extract the ESP header from the payload. |
| */ |
| iph = (struct iphdr *)skb->data; |
| esph = (struct ip_esp_hdr *)(skb->data + (iph->ihl << 2)); |
| |
| sa.src_ip = ntohl(iph->saddr); |
| sa.dst_ip = ntohl(iph->daddr); |
| sa.spi_index = ntohl(esph->spi); |
| |
| nss_ipsecmgr_v4_sa2key(&sa, &key); |
| |
| /* |
| * Get the SA corresponding to the ESP flow |
| */ |
| read_lock(&ipsecmgr_ctx->lock); |
| ref = nss_ipsecmgr_sa_lookup(&key); |
| if (!ref) { |
| read_unlock(&ipsecmgr_ctx->lock); |
| nss_ipsecmgr_trace("unable to find SA (%px)\n", skb); |
| return; |
| } |
| |
| sa_entry = container_of(ref, struct nss_ipsecmgr_sa_entry, ref); |
| priv = sa_entry->priv; |
| BUG_ON(!priv); |
| |
| atomic_set(&priv->outer_dst_mtu, mtu); |
| read_unlock(&ipsecmgr_ctx->lock); |
| |
| /* |
| * update new mtu for this flow |
| */ |
| BUG_ON(!dev_net(skb->dev)); |
| ipv4_update_pmtu(skb, dev_net(skb->dev), mtu, 0, 0, IPPROTO_ESP, 0); |
| return; |
| } |
| |
| /* |
| * protocol handler for IPv4 ESP |
| */ |
| static const struct net_protocol nss_ipsecmgr_proto_esp4 = { |
| .handler = nss_ipsecmgr_tunnel_rx_esp4, |
| .err_handler = nss_ipsecmgr_tunnel_error_esp4, |
| .netns_ok = 1, |
| }; |
| |
| /* |
| * nss_ipsecmgr_esp6_rcv() |
| * IPV6 Receive handler for ESP protocol |
| */ |
| static int nss_ipsecmgr_tunnel_rx_esp6(struct sk_buff *skb) |
| { |
| /* |
| * TODO:This can potentially receive ESP packets in when |
| * the outer ESP rule is flushed. In which case the DECAP |
| * packets entering linux must be bounced through offload |
| */ |
| dev_kfree_skb_any(skb); |
| return 0; |
| } |
| |
| /* |
| * nss_ipsecmgr_esp6_err() |
| * IPV6 Error handler for ESP protocol |
| */ |
| static void nss_ipsecmgr_tunnel_error_esp6(struct sk_buff *skb, struct inet6_skb_parm *opt, uint8_t type, |
| uint8_t code, int32_t offset, uint32_t mtu) |
| { |
| struct nss_ipsecmgr_sa_entry *sa_entry; |
| struct nss_ipsecmgr_sa_v6 sa = { {0} }; |
| struct nss_ipsecmgr_key key = { {0} }; |
| struct nss_ipsecmgr_priv *priv; |
| struct nss_ipsecmgr_ref *ref; |
| struct ip_esp_hdr *esph; |
| struct ipv6hdr *iph; |
| |
| /* |
| * If the ICMP type is not PMTU return |
| */ |
| if (type != ICMPV6_PKT_TOOBIG) |
| return; |
| |
| /* |
| * Skb data now points to the IP header present in the |
| * IMCP payload. It will be of the packet which generated the |
| * PMTU error. Extract the ESP header from the payload. |
| */ |
| esph = (struct ip_esp_hdr *)(skb->data + offset); |
| iph = (struct ipv6hdr *)skb->data; |
| |
| /* |
| * Get the selectors from IPv6 header. Compose the key and |
| * get the decap SA |
| */ |
| nss_ipsecmgr_v6addr_ntoh(iph->daddr.s6_addr32, sa.dst_ip); |
| nss_ipsecmgr_v6addr_ntoh(iph->saddr.s6_addr32, sa.src_ip); |
| sa.spi_index = ntohl(esph->spi); |
| |
| nss_ipsecmgr_v6_sa2key(&sa, &key); |
| |
| read_lock(&ipsecmgr_ctx->lock); |
| ref = nss_ipsecmgr_sa_lookup(&key); |
| if (!ref) { |
| read_unlock(&ipsecmgr_ctx->lock); |
| nss_ipsecmgr_info("%px: unable to find SA\n", skb); |
| return; |
| } |
| |
| sa_entry = container_of(ref, struct nss_ipsecmgr_sa_entry, ref); |
| priv = sa_entry->priv; |
| BUG_ON(!priv); |
| |
| atomic_set(&priv->outer_dst_mtu, ntohl(mtu)); |
| read_unlock(&ipsecmgr_ctx->lock); |
| |
| /* |
| * Update the PMTU |
| */ |
| BUG_ON(!dev_net(skb->dev)); |
| ip6_update_pmtu(skb, dev_net(skb->dev), mtu, 0, 0); |
| return; |
| } |
| |
| /* |
| * protocol handler for IPv6 ESP |
| */ |
| static struct inet6_protocol nss_ipsecmgr_proto_esp6 = { |
| .handler = nss_ipsecmgr_tunnel_rx_esp6, |
| .err_handler = nss_ipsecmgr_tunnel_error_esp6, |
| .flags = INET6_PROTO_NOPOLICY, |
| }; |
| #endif |
| |
| /* |
| * nss_ipsecmgr_init() |
| * module init |
| */ |
| static int __init nss_ipsecmgr_init(void) |
| { |
| int status; |
| |
| if (!nss_cmn_get_nss_enabled()) { |
| nss_ipsecmgr_info_always("NSS is not enabled in this platform\n"); |
| return 0; |
| } |
| |
| ipsecmgr_ctx = vzalloc(sizeof(struct nss_ipsecmgr_drv)); |
| if (!ipsecmgr_ctx) { |
| nss_ipsecmgr_info_always("Allocating ipsecmgr context failed\n"); |
| return 0; |
| } |
| |
| ipsecmgr_ctx->nss_ctx = nss_ipsec_get_context(); |
| if (!ipsecmgr_ctx->nss_ctx) { |
| nss_ipsecmgr_info_always("Getting NSS Context failed\n"); |
| goto free; |
| } |
| |
| ipsecmgr_ctx->data_ifnum = nss_ipsec_get_data_interface(); |
| ipsecmgr_ctx->encap_ifnum = nss_ipsec_get_encap_interface(); |
| ipsecmgr_ctx->decap_ifnum = nss_ipsec_get_decap_interface(); |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) |
| ipsecmgr_ctx->ndev = alloc_netdev(0, NSS_IPSECMGR_DEFAULT_TUN_NAME, nss_ipsecmgr_dummy_netdevice_setup); |
| #else |
| ipsecmgr_ctx->ndev = alloc_netdev(0, NSS_IPSECMGR_DEFAULT_TUN_NAME, NET_NAME_UNKNOWN, nss_ipsecmgr_dummy_netdevice_setup); |
| #endif |
| if (!ipsecmgr_ctx->ndev) { |
| nss_ipsecmgr_info_always("Ipsec: Could not allocate ipsec net_device\n"); |
| goto free; |
| } |
| |
| ipsecmgr_ctx->ndev->netdev_ops = &nss_ipsecmgr_ipsec_ndev_ops; |
| |
| status = rtnl_is_locked() ? register_netdevice(ipsecmgr_ctx->ndev) : register_netdev(ipsecmgr_ctx->ndev); |
| if (status) { |
| nss_ipsecmgr_info_always("IPsec: Could not register ipsec net_device\n"); |
| goto netdev_free; |
| } |
| |
| rwlock_init(&ipsecmgr_ctx->lock); |
| nss_ipsecmgr_init_sa_db(&ipsecmgr_ctx->sa_db); |
| nss_ipsecmgr_init_netmask_db(&ipsecmgr_ctx->net_db); |
| nss_ipsecmgr_init_flow_db(&ipsecmgr_ctx->flow_db); |
| nss_ipsecmgr_init_callback_db(&ipsecmgr_ctx->cb_db); |
| |
| nss_ipsec_data_register(ipsecmgr_ctx->data_ifnum, nss_ipsecmgr_tunnel_rx, ipsecmgr_ctx->ndev, 0); |
| nss_ipsec_notify_register(ipsecmgr_ctx->encap_ifnum, nss_ipsecmgr_tunnel_notify, NULL); |
| nss_ipsec_notify_register(ipsecmgr_ctx->decap_ifnum, nss_ipsecmgr_tunnel_notify, NULL); |
| |
| /* |
| * initialize debugfs. |
| */ |
| ipsecmgr_ctx->dentry = debugfs_create_dir("qca-nss-ipsecmgr", NULL); |
| if (!ipsecmgr_ctx->dentry) { |
| nss_ipsecmgr_info_always("Creating debug directory failed\n"); |
| goto unregister_dev; |
| |
| } |
| |
| ipsecmgr_ctx->stats_dentry = debugfs_create_dir("stats", ipsecmgr_ctx->dentry); |
| if (!ipsecmgr_ctx->stats_dentry) { |
| debugfs_remove_recursive(ipsecmgr_ctx->dentry); |
| nss_ipsecmgr_info("Creating debug directory failed\n"); |
| goto unregister_dev; |
| |
| } |
| |
| /* |
| * Create debugfs entries for SA, flow and subnet |
| */ |
| if (nss_ipsecmgr_init_stats_debugfs(ipsecmgr_ctx->stats_dentry)) { |
| nss_ipsecmgr_info("Creating debug tree failed\n"); |
| debugfs_remove_recursive(ipsecmgr_ctx->dentry); |
| goto unregister_dev; |
| |
| } |
| |
| #if defined NSS_IPSECMGR_PMTU_SUPPORT |
| |
| /* |
| * Register a ESP protocol handler only when XFRM is not loaded |
| */ |
| status = inet_add_protocol(&nss_ipsecmgr_proto_esp4, IPPROTO_ESP); |
| if (status < 0) { |
| nss_ipsecmgr_warn("%px:%d in Registering ESP4 Handler\n", |
| ipsecmgr_ctx->nss_ctx, status); |
| } |
| |
| status = inet6_add_protocol(&nss_ipsecmgr_proto_esp6, IPPROTO_ESP); |
| if (status < 0) { |
| nss_ipsecmgr_warn("%px:%d in Registering ESP6 Handler\n", |
| ipsecmgr_ctx->nss_ctx, status); |
| } |
| #endif |
| |
| init_completion(&ipsecmgr_ctx->complete); |
| sema_init(&ipsecmgr_ctx->sem, 1); |
| atomic_set(&ipsecmgr_ctx->seq_num, 0); |
| |
| nss_ipsecmgr_info_always("NSS IPsec manager loaded: %s\n", NSS_CLIENT_BUILD_ID); |
| return 0; |
| |
| unregister_dev: |
| rtnl_is_locked() ? unregister_netdevice(ipsecmgr_ctx->ndev) : unregister_netdev(ipsecmgr_ctx->ndev); |
| |
| netdev_free: |
| free_netdev(ipsecmgr_ctx->ndev); |
| |
| free: |
| vfree(ipsecmgr_ctx); |
| ipsecmgr_ctx = NULL; |
| |
| return 0; |
| } |
| |
| /* |
| * nss_ipsecmgr_exit() |
| * module exit |
| */ |
| static void __exit nss_ipsecmgr_exit(void) |
| { |
| if (!ipsecmgr_ctx) { |
| nss_ipsecmgr_info_always("Invalid ipsecmgr Context\n"); |
| return; |
| } |
| |
| if (!ipsecmgr_ctx->nss_ctx) { |
| nss_ipsecmgr_info_always("Invalid NSS Context\n"); |
| vfree(ipsecmgr_ctx); |
| ipsecmgr_ctx = NULL; |
| return; |
| } |
| |
| /* |
| * Unregister the callbacks from the HLOS as we are no longer |
| * interested in exception data & async messages |
| */ |
| nss_ipsec_data_unregister(ipsecmgr_ctx->nss_ctx, ipsecmgr_ctx->data_ifnum); |
| |
| nss_ipsec_notify_unregister(ipsecmgr_ctx->nss_ctx, ipsecmgr_ctx->encap_ifnum); |
| nss_ipsec_notify_unregister(ipsecmgr_ctx->nss_ctx, ipsecmgr_ctx->decap_ifnum); |
| |
| if (ipsecmgr_ctx->ndev) { |
| rtnl_is_locked() ? unregister_netdevice(ipsecmgr_ctx->ndev) : unregister_netdev(ipsecmgr_ctx->ndev); |
| } |
| |
| /* |
| * Remove debugfs directory and entries below that. |
| */ |
| if (ipsecmgr_ctx->dentry) { |
| debugfs_remove_recursive(ipsecmgr_ctx->dentry); |
| } |
| |
| /* |
| * Free the ipsecmgr ctx |
| */ |
| vfree(ipsecmgr_ctx); |
| ipsecmgr_ctx = NULL; |
| |
| nss_ipsecmgr_info_always("NSS IPsec manager unloaded\n"); |
| |
| } |
| |
| MODULE_LICENSE("Dual BSD/GPL"); |
| |
| module_init(nss_ipsecmgr_init); |
| module_exit(nss_ipsecmgr_exit); |