| /* |
| * ******************************************************************************** |
| * Copyright (c) 2018-2021, 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 <linux/types.h> |
| #include <linux/ip.h> |
| #include <linux/inet.h> |
| #include <linux/of.h> |
| #include <linux/ipv6.h> |
| #include <linux/kernel.h> |
| #include <linux/skbuff.h> |
| #include <linux/module.h> |
| #include <linux/netdevice.h> |
| #include <linux/rtnetlink.h> |
| #include <asm/atomic.h> |
| #include <linux/debugfs.h> |
| #include <linux/completion.h> |
| #include <linux/vmalloc.h> |
| #include <net/icmp.h> |
| #include <net/route.h> |
| #include <net/ip.h> |
| #include <net/ip6_route.h> |
| |
| #include <nss_api_if.h> |
| #include <nss_ipsec_cmn.h> |
| #include <nss_ipsecmgr.h> |
| |
| #include "nss_ipsecmgr_ref.h" |
| #include "nss_ipsecmgr_flow.h" |
| #include "nss_ipsecmgr_sa.h" |
| #include "nss_ipsecmgr_ctx.h" |
| #include "nss_ipsecmgr_tunnel.h" |
| #include "nss_ipsecmgr_priv.h" |
| |
| /* |
| * Context Host statistics print info |
| */ |
| static const struct nss_ipsecmgr_print ipsecmgr_print_ctx_host_stats[] = { |
| {"\tIPv4 notify", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tIPv4 notify drop", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tIPv4 route", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tIPv4 route drop", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tIPv6 notify", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tIPv6 notify drop", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tIPv6 route", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tIPv6 route drop", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tInner exp", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tInner exp drop", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tInner callbacks", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tInner fail dev", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tInner fail SA", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tInner fail flow", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tOuter exp", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tOuter exp drop", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tOuter callbacks", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tOuter fail dev", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tOuter fail SA", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tOuter fail flow", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tRedir exp", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tRedir exp drop", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tRedir callbacks", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tRedir fail dev", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tRedir fail SA", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tRedir fail flow", NSS_IPSECMGR_PRINT_DWORD}, |
| }; |
| |
| /* |
| * Context statistics print info |
| */ |
| static const struct nss_ipsecmgr_print ipsecmgr_print_ctx_stats[] = { |
| {"\trx_packets", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\trx_bytes", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\ttx_packets", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\ttx_bytes", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\trx_dropped[0]", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\trx_dropped[1]", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\trx_dropped[2]", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\trx_dropped[3]", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\texceptioned", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tlinearized", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tredirected", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tdropped", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_sa", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_flow", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_stats", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_exception", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_transform", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_linearized", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_mdata_ver", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_ctx_active", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_pbuf_crypto", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_queue_crypto", NSS_IPSECMGR_PRINT_DWORD}, |
| }; |
| |
| /* |
| * nss_ipsecmgr_ctx_stats_size() |
| * Calculate size of context stats |
| */ |
| static ssize_t nss_ipsecmgr_ctx_stats_size(void) |
| { |
| const struct nss_ipsecmgr_print *prn = ipsecmgr_print_ctx_stats; |
| ssize_t len = NSS_IPSECMGR_CTX_PRINT_EXTRA; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(ipsecmgr_print_ctx_stats); i++, prn++) |
| len += strlen(prn->str) + prn->var_size; |
| |
| prn = ipsecmgr_print_ctx_host_stats; |
| |
| len += NSS_IPSECMGR_CTX_PRINT_EXTRA; |
| for (i = 0; i < ARRAY_SIZE(ipsecmgr_print_ctx_host_stats); i++, prn++) |
| len += strlen(prn->str) + prn->var_size; |
| |
| return len; |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_print_len() |
| * Return the length of context stats |
| */ |
| static ssize_t nss_ipsecmgr_ctx_print_len(struct nss_ipsecmgr_ref *ref) |
| { |
| struct nss_ipsecmgr_ctx *ctx = container_of(ref, struct nss_ipsecmgr_ctx, ref); |
| return ctx->state.print_len; |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_print() |
| * Print context statistics |
| */ |
| static ssize_t nss_ipsecmgr_ctx_print(struct nss_ipsecmgr_ref *ref, char *buf) |
| { |
| struct nss_ipsecmgr_ctx *ctx = container_of(ref, struct nss_ipsecmgr_ctx, ref); |
| const struct nss_ipsecmgr_print *prn = ipsecmgr_print_ctx_stats; |
| ssize_t max_len = ctx->state.print_len; |
| uint64_t *stats_word = (uint64_t *)&ctx->stats; |
| ssize_t len; |
| int i; |
| |
| /* |
| * This expects a strict order as per the stats structure |
| */ |
| len = snprintf(buf, max_len, "---- Context -----\n"); |
| len += snprintf(buf + len, max_len - len, "stats: {\n"); |
| |
| for (i = 0; i < ARRAY_SIZE(ipsecmgr_print_ctx_stats); i++, prn++) |
| len += snprintf(buf + len, max_len - len, "%s: %llu\n", prn->str, *stats_word++); |
| |
| len += snprintf(buf + len, max_len - len, "}\n"); |
| |
| stats_word = (uint64_t *)&ctx->hstats; |
| len += snprintf(buf + len, max_len - len, "Host stats: {\n"); |
| prn = ipsecmgr_print_ctx_host_stats; |
| |
| for (i = 0; i < ARRAY_SIZE(ipsecmgr_print_ctx_host_stats); i++, prn++) |
| len += snprintf(buf + len, max_len - len, "%s: %llu\n", prn->str, *stats_word++); |
| |
| len += snprintf(buf + len, max_len - len, "}\n"); |
| |
| return len; |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_read() |
| * Read context info |
| */ |
| static ssize_t nss_ipsecmgr_ctx_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos) |
| { |
| struct nss_ipsecmgr_ctx *ctx = fp->private_data; |
| ssize_t print_len = ctx->state.stats_len; |
| ssize_t len = 0, max_len; |
| char *buf; |
| |
| buf = vzalloc(print_len); |
| if (!buf) { |
| nss_ipsecmgr_warn("%px: failed to allocate print buffer (req:%zd)", ctx, print_len); |
| return 0; |
| } |
| |
| read_lock_bh(&ipsecmgr_drv->lock); |
| max_len = nss_ipsecmgr_ref_print_len(&ctx->ref); |
| if (max_len > print_len) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| len += snprintf(buf, print_len, "print buffer size error (need:%zd), retry", max_len); |
| ctx->state.stats_len = max_len; |
| goto done; |
| } |
| |
| /* |
| * Walk the context reference tree and retrieve stats |
| */ |
| len = nss_ipsecmgr_ref_print(&ctx->ref, buf); |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| |
| done: |
| len = simple_read_from_buffer(ubuf, sz, ppos, buf, len); |
| vfree(buf); |
| |
| return len; |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_notify_ipv4() |
| * Notify Linux by route lookup for destination |
| */ |
| static void nss_ipsecmgr_ctx_notify_ipv4(struct sk_buff *skb, struct nss_ipsecmgr_ctx *ctx) |
| { |
| struct iphdr *iph = ip_hdr(skb); |
| struct dst_entry *dst; |
| struct rtable *rt; |
| |
| if (skb->dev != ipsecmgr_drv->dev) |
| goto notify; |
| |
| /* |
| * If, we could not find the flow then this must be a new |
| * flow that coming in for the first time. We should query |
| * the Linux to see the associated NETDEV |
| */ |
| rt = ip_route_output(&init_net, iph->saddr, 0, 0, 0); |
| if (IS_ERR(rt)) { |
| dev_kfree_skb_any(skb); |
| ctx->hstats.v4_notify_drop++; |
| return; |
| } |
| |
| dst = (struct dst_entry *)rt; |
| skb->dev = dst->dev; |
| dst_release(dst); |
| |
| skb->pkt_type = PACKET_HOST; |
| skb->protocol = htons(ETH_P_IP); |
| |
| notify: |
| ctx->hstats.v4_notify++; |
| netif_receive_skb(skb); |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_route_ipv4() |
| * Send IPv4 packet for routing |
| */ |
| static void nss_ipsecmgr_ctx_route_ipv4(struct sk_buff *skb, struct nss_ipsecmgr_ctx *ctx) |
| { |
| struct iphdr *iph = ip_hdr(skb); |
| struct rtable *rt; |
| |
| rt = ip_route_output(&init_net, iph->daddr, iph->saddr, 0, 0); |
| if (unlikely(IS_ERR(rt))) { |
| nss_ipsecmgr_warn("%pK: No route, drop packet.\n", skb); |
| dev_kfree_skb_any(skb); |
| ctx->hstats.v4_route_drop++; |
| return; |
| } |
| |
| /* |
| * Sets the 'dst' entry for SKB and sends the packet out directly to the physical |
| * device associated with the IPsec tunnel interface. |
| */ |
| skb_dst_set(skb, &rt->dst); |
| skb->ip_summed = CHECKSUM_COMPLETE; |
| |
| ctx->hstats.v4_route++; |
| |
| ip_local_out(&init_net, NULL, skb); |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_notify_ipv6() |
| * Notify Linux by route lookup for destination |
| */ |
| static void nss_ipsecmgr_ctx_notify_ipv6(struct sk_buff *skb, struct nss_ipsecmgr_ctx *ctx) |
| { |
| struct ipv6hdr *ip6h = ipv6_hdr(skb); |
| struct dst_entry *dst; |
| struct flowi6 fl6; |
| |
| if (skb->dev != ipsecmgr_drv->dev) |
| goto notify; |
| |
| /* |
| * If, we could not find the flow then this must be a new |
| * flow that coming in for the first time. We should query |
| * the Linux to see the associated NETDEV |
| */ |
| 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)) { |
| dev_kfree_skb_any(skb); |
| ctx->hstats.v6_notify_drop++; |
| return; |
| } |
| |
| skb->dev = dst->dev; |
| dst_release(dst); |
| |
| skb->pkt_type = PACKET_HOST; |
| skb->protocol = htons(ETH_P_IPV6); |
| |
| notify: |
| ctx->hstats.v6_notify++; |
| netif_receive_skb(skb); |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_route_ipv6() |
| * Send IPv6 packet for routing |
| */ |
| static void nss_ipsecmgr_ctx_route_ipv6(struct sk_buff *skb, struct nss_ipsecmgr_ctx *ctx) |
| { |
| struct ipv6hdr *ip6h = ipv6_hdr(skb); |
| struct dst_entry *dst; |
| struct flowi6 fl6; |
| |
| memset(&fl6, 0, sizeof(fl6)); |
| memcpy(&fl6.daddr, &ip6h->daddr, sizeof(fl6.daddr)); |
| memcpy(&fl6.saddr, &ip6h->saddr, sizeof(fl6.saddr)); |
| |
| dst = ip6_route_output(&init_net, NULL, &fl6); |
| if (unlikely(IS_ERR(dst))) { |
| nss_ipsecmgr_warn("%pK: No route, drop packet.\n", skb); |
| dev_kfree_skb_any(skb); |
| |
| ctx->hstats.v6_notify_drop++; |
| return; |
| } |
| |
| /* |
| * Sets the 'dst' entry for SKB and sends the packet out directly to the physical |
| * device associated with the IPsec tunnel interface. |
| */ |
| skb_dst_set(skb, dst); |
| skb->ip_summed = CHECKSUM_COMPLETE; |
| |
| ctx->hstats.v6_route++; |
| |
| ip6_local_out(&init_net, NULL, skb); |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_del_ref() |
| * Delete context from context list |
| */ |
| static void nss_ipsecmgr_ctx_del_ref(struct nss_ipsecmgr_ref *ref) |
| { |
| struct nss_ipsecmgr_ctx *ctx = container_of(ref, struct nss_ipsecmgr_ctx, ref); |
| list_del_init(&ctx->list); |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_free_ref() |
| * Free context |
| */ |
| static void nss_ipsecmgr_ctx_free_ref(struct nss_ipsecmgr_ref *ref) |
| { |
| struct nss_ipsecmgr_ctx *ctx = container_of(ref, struct nss_ipsecmgr_ctx, ref); |
| enum nss_dynamic_interface_type di_type = ctx->state.di_type; |
| bool status; |
| |
| status = nss_ipsec_cmn_unregister_if(ctx->ifnum); |
| if (!status) { |
| nss_ipsecmgr_warn("%px: Failed to unregister, di_type(%u), I/F(%u)", ctx, di_type, ctx->ifnum); |
| return; |
| } |
| |
| nss_ipsecmgr_ctx_free(ctx); |
| } |
| |
| /* |
| * file operation structure instance |
| */ |
| const struct file_operations ipsecmgr_ctx_file_ops = { |
| .open = simple_open, |
| .llseek = default_llseek, |
| .read = nss_ipsecmgr_ctx_read, |
| }; |
| |
| /* |
| * nss_ipsecmgr_ctx_rx_redir() |
| * NSS IPsec manager device receive function |
| */ |
| void nss_ipsecmgr_ctx_rx_redir(struct net_device *dev, struct sk_buff *skb, |
| __attribute__((unused))struct napi_struct *napi) |
| { |
| void (*forward_fn)(struct sk_buff *skb, struct nss_ipsecmgr_ctx *ctx) = NULL; |
| struct nss_ipsec_cmn_sa_tuple sa_tuple = {0}; |
| struct nss_ipsecmgr_tunnel *tun; |
| struct nss_ipsecmgr_sa *sa; |
| struct nss_ipsecmgr_ctx *ctx; |
| int tunnel_id; |
| |
| ctx = nss_ipsecmgr_ctx_find(netdev_priv(dev), NSS_IPSEC_CMN_CTX_TYPE_REDIR); |
| if (!ctx) { |
| dev_kfree_skb_any(skb); |
| nss_ipsecmgr_warn("%px: ctx is NULL", dev); |
| return; |
| } |
| |
| ctx->hstats.redir_exp++; |
| |
| skb_reset_mac_header(skb); |
| skb_reset_network_header(skb); |
| |
| /* |
| * Set the default values |
| */ |
| skb->dev = dev; |
| skb->pkt_type = PACKET_HOST; |
| skb->skb_iif = skb->dev->ifindex; |
| |
| switch (ip_hdr(skb)->version) { |
| case IPVERSION: |
| { |
| struct iphdr *iph = ip_hdr(skb); |
| struct udphdr *udph; |
| |
| skb->protocol = ETH_P_IP; |
| |
| /* |
| * This will happen for exception after de-capsulation. |
| */ |
| if ((iph->protocol != IPPROTO_ESP) && (iph->protocol != IPPROTO_UDP)) { |
| nss_ipsecmgr_ctx_notify_ipv4(skb, ctx); |
| return; |
| } |
| |
| /* |
| * Note: For outer flows check if the SA entry is present. |
| * This will happen for exception after encapsulation |
| */ |
| if (iph->protocol == IPPROTO_ESP) { |
| nss_ipsecmgr_sa_ipv4_outer2tuple(iph, &sa_tuple); |
| |
| read_lock(&ipsecmgr_drv->lock); |
| sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa_tuple); |
| if (!sa) { |
| read_unlock(&ipsecmgr_drv->lock); |
| dev_kfree_skb_any(skb); |
| |
| ctx->hstats.redir_fail_sa++; |
| return; |
| } |
| |
| tunnel_id = sa->tunnel_id; |
| read_unlock(&ipsecmgr_drv->lock); |
| forward_fn = nss_ipsecmgr_ctx_route_ipv4; |
| break; |
| } |
| |
| /* |
| * UDP NAT-T is for outer processing otherwise inner processing |
| */ |
| udph = (struct udphdr *)((uint8_t *)iph + sizeof(*iph)); |
| if (udph->dest != ntohs(NSS_IPSECMGR_NATT_PORT_DATA)) { |
| nss_ipsecmgr_ctx_notify_ipv4(skb, ctx); |
| return; |
| } |
| |
| nss_ipsecmgr_sa_ipv4_outer2tuple(iph, &sa_tuple); |
| |
| read_lock(&ipsecmgr_drv->lock); |
| sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa_tuple); |
| if (!sa) { |
| read_unlock(&ipsecmgr_drv->lock); |
| dev_kfree_skb_any(skb); |
| |
| write_lock_bh(&ipsecmgr_drv->lock); |
| ctx->hstats.redir_fail_sa++; |
| write_unlock_bh(&ipsecmgr_drv->lock); |
| |
| return; |
| } |
| |
| tunnel_id = sa->tunnel_id; |
| read_unlock(&ipsecmgr_drv->lock); |
| forward_fn = nss_ipsecmgr_ctx_route_ipv4; |
| break; |
| } |
| |
| case 6: |
| { |
| struct ipv6hdr *ip6h = ipv6_hdr(skb); |
| skb->protocol = ETH_P_IPV6; |
| |
| if (ip6h->nexthdr == IPPROTO_ESP) { |
| nss_ipsecmgr_sa_ipv6_outer2tuple(ip6h, &sa_tuple); |
| |
| read_lock(&ipsecmgr_drv->lock); |
| sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa_tuple); |
| if (!sa) { |
| read_unlock(&ipsecmgr_drv->lock); |
| dev_kfree_skb_any(skb); |
| |
| ctx->hstats.redir_fail_sa++; |
| |
| return; |
| } |
| |
| tunnel_id = sa->tunnel_id; |
| read_unlock(&ipsecmgr_drv->lock); |
| forward_fn = nss_ipsecmgr_ctx_route_ipv6; |
| break; |
| } |
| |
| nss_ipsecmgr_ctx_notify_ipv6(skb, ctx); |
| return; |
| } |
| |
| default: |
| nss_ipsecmgr_warn("%px: non IP packet received", dev); |
| ctx->hstats.redir_exp_drop++; |
| |
| dev_kfree_skb_any(skb); |
| return; |
| } |
| |
| /* |
| * Use the tunnel-ID to find the associated device |
| */ |
| dev = dev_get_by_index(&init_net, tunnel_id); |
| if (!dev) { |
| dev_kfree_skb_any(skb); |
| ctx->hstats.redir_fail_dev++; |
| return; |
| } |
| |
| /* |
| * Reset SKB fields with the actual values |
| */ |
| tun = netdev_priv(dev); |
| skb->dev = tun->cb.skb_dev; |
| skb->pkt_type = PACKET_HOST; |
| skb->skb_iif = skb->dev->ifindex; |
| dev_put(dev); |
| |
| /* |
| * If, data callback is available then send the packet to the |
| * callback function |
| */ |
| if (tun->cb.except_cb) { |
| tun->cb.except_cb(tun->cb.app_data, skb); |
| ctx->hstats.redir_cb++; |
| return; |
| } |
| |
| /* |
| * Indicate up the stack |
| */ |
| forward_fn(skb, ctx); |
| return; |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_rx_outer() |
| * Process outer exception from NSS |
| */ |
| void nss_ipsecmgr_ctx_rx_outer(struct net_device *dev, struct sk_buff *skb, |
| __attribute__((unused)) struct napi_struct *napi) |
| { |
| struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev); |
| struct nss_ipsecmgr_ctx *ctx; |
| skb_reset_mac_header(skb); |
| skb_reset_network_header(skb); |
| |
| skb->dev = tun->cb.skb_dev; |
| skb->skb_iif = skb->dev->ifindex; |
| |
| ctx = nss_ipsecmgr_ctx_find(netdev_priv(dev), NSS_IPSEC_CMN_CTX_TYPE_OUTER); |
| if (!ctx) { |
| nss_ipsecmgr_warn("%px: Could not find ctx", dev); |
| dev_kfree_skb_any(skb); |
| return; |
| } |
| |
| ctx->hstats.outer_exp++; |
| |
| /* |
| * Reaching this point means the outer ECM rule is non-existent |
| * whereas the IPsec rules are still present in FW. So, this is an ESP |
| * encapsulated packet that has been exception to host from NSS. |
| * We can send it to Linux IP stack for further routing. |
| */ |
| switch (ip_hdr(skb)->version) { |
| case IPVERSION: { |
| struct iphdr *iph = ip_hdr(skb); |
| skb->protocol = cpu_to_be16(ETH_P_IP); |
| |
| if ((iph->protocol != IPPROTO_UDP) && (iph->protocol != IPPROTO_ESP)) { |
| nss_ipsecmgr_warn("%px: Unsupported IPv4 protocol(%u)", dev, iph->protocol); |
| dev_kfree_skb_any(skb); |
| |
| ctx->hstats.outer_exp_drop++; |
| return; |
| } |
| |
| skb_set_transport_header(skb, sizeof(*iph)); |
| if (tun->cb.except_cb) { |
| tun->cb.except_cb(tun->cb.app_data, skb); |
| ctx->hstats.outer_cb++; |
| return; |
| } |
| nss_ipsecmgr_ctx_route_ipv4(skb, ctx); |
| return; |
| } |
| |
| case 6: { |
| struct ipv6hdr *ip6h = ipv6_hdr(skb); |
| skb->protocol = cpu_to_be16(ETH_P_IPV6); |
| |
| if (ip6h->nexthdr != IPPROTO_ESP) { |
| nss_ipsecmgr_warn("%px: unsupported ipv6 next_hdr(%u)", dev, ip6h->nexthdr); |
| dev_kfree_skb_any(skb); |
| |
| ctx->hstats.outer_exp_drop++; |
| return; |
| } |
| |
| skb_set_transport_header(skb, sizeof(*ip6h)); |
| if (tun->cb.except_cb) { |
| tun->cb.except_cb(tun->cb.app_data, skb); |
| ctx->hstats.outer_cb++; |
| return; |
| } |
| nss_ipsecmgr_ctx_route_ipv6(skb, ctx); |
| return; |
| } |
| |
| default: |
| nss_ipsecmgr_warn("%px: non ip packet received after decapsulation", dev); |
| ctx->hstats.outer_exp_drop++; |
| |
| dev_kfree_skb_any(skb); |
| return; |
| } |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_rx_inner() |
| * Process inner exception from NSS |
| */ |
| void nss_ipsecmgr_ctx_rx_inner(struct net_device *dev, struct sk_buff *skb, |
| __attribute__((unused)) struct napi_struct *napi) |
| { |
| struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev); |
| struct nss_ipsecmgr_ctx *ctx; |
| |
| ctx = nss_ipsecmgr_ctx_find(netdev_priv(dev), NSS_IPSEC_CMN_CTX_TYPE_INNER); |
| if (!ctx) { |
| nss_ipsecmgr_warn("%px: Could not find ctx", dev); |
| dev_kfree_skb_any(skb); |
| return; |
| } |
| |
| ctx->hstats.inner_exp++; |
| |
| skb_reset_mac_header(skb); |
| skb_reset_network_header(skb); |
| |
| switch (ip_hdr(skb)->version) { |
| case IPVERSION: |
| skb->protocol = cpu_to_be16(ETH_P_IP); |
| skb_set_transport_header(skb, sizeof(struct iphdr)); |
| break; |
| |
| case 6: |
| skb->protocol = cpu_to_be16(ETH_P_IPV6); |
| skb_set_transport_header(skb, sizeof(struct ipv6hdr)); |
| break; |
| |
| default: |
| nss_ipsecmgr_warn("%px: Invalid IP header received for rx_inner", tun); |
| dev_kfree_skb_any(skb); |
| |
| ctx->hstats.inner_exp_drop++; |
| |
| return; |
| } |
| |
| skb->dev = tun->cb.skb_dev; |
| skb->pkt_type = PACKET_HOST; |
| skb->skb_iif = skb->dev->ifindex; |
| |
| /* |
| * If, data callback is available then send the packet to the |
| * callback funtion |
| */ |
| if (tun->cb.data_cb) { |
| tun->cb.data_cb(tun->cb.app_data, skb); |
| ctx->hstats.inner_cb++; |
| return; |
| } |
| |
| netif_receive_skb(skb); |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_rx_stats() |
| * Asynchronous event for reception of stat |
| */ |
| void nss_ipsecmgr_ctx_rx_stats(void *app_data, struct nss_cmn_msg *ncm) |
| { |
| struct nss_ipsec_cmn_msg *nicm = (struct nss_ipsec_cmn_msg *)ncm; |
| struct nss_ipsecmgr_tunnel *tun; |
| struct nss_ipsecmgr_ctx *ctx; |
| |
| ctx = app_data; |
| tun = ctx->tun; |
| |
| switch (ncm->type) { |
| |
| case NSS_IPSEC_CMN_MSG_TYPE_SA_SYNC: { |
| struct nss_ipsec_cmn_sa_sync *sync = &nicm->msg.sa_sync; |
| struct list_head *sa_db = ipsecmgr_drv->sa_db; |
| struct nss_ipsecmgr_event event = {0}; |
| nss_ipsecmgr_event_callback_t ev_cb; |
| struct nss_ipsecmgr_sa *sa; |
| void *app_data; |
| |
| event.type = NSS_IPSECMGR_EVENT_SA_STATS; |
| write_lock(&ipsecmgr_drv->lock); |
| |
| sa = nss_ipsecmgr_sa_find(sa_db, &sync->sa_tuple); |
| if (!sa) { |
| write_unlock(&ipsecmgr_drv->lock); |
| break; |
| } |
| |
| nss_ipsecmgr_sa_sync_state(sa, sync); |
| |
| ev_cb = sa->cb.event_cb; |
| if (ev_cb) { |
| nss_ipsecmgr_sa_sync2stats(sa, sync, &event.data.stats); |
| app_data = sa->cb.app_data; |
| } |
| |
| write_unlock(&ipsecmgr_drv->lock); |
| |
| if (ev_cb) |
| ev_cb(app_data, &event); |
| break; |
| } |
| |
| case NSS_IPSEC_CMN_MSG_TYPE_CTX_SYNC: { |
| struct nss_ipsec_cmn_ctx_sync *ctx_sync = &nicm->msg.ctx_sync; |
| uint32_t *msg_stats = (uint32_t *)&ctx_sync->stats; |
| uint64_t *ctx_stats = (uint64_t *)&ctx->stats; |
| int num; |
| |
| write_lock(&ipsecmgr_drv->lock); |
| |
| for (num = 0; num < sizeof(ctx->stats)/sizeof(*ctx_stats); num++) { |
| ctx_stats[num] += msg_stats[num]; |
| } |
| |
| write_unlock(&ipsecmgr_drv->lock); |
| break; |
| } |
| |
| default: |
| nss_ipsecmgr_info("%px: unhandled ipsec message type(%u)", nicm, nicm->cm.type); |
| break; |
| } |
| |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_stats_read() |
| * Read context stats into Linux device stats |
| */ |
| void nss_ipsecmgr_ctx_stats_read(struct nss_ipsecmgr_ctx *ctx, struct rtnl_link_stats64 *dev_stats) |
| { |
| struct nss_ipsecmgr_ctx_stats_priv *ctx_stats = &ctx->stats; |
| uint64_t *packets, *bytes, *dropped; |
| int i; |
| |
| switch (ctx->state.type) { |
| case NSS_IPSEC_CMN_CTX_TYPE_INNER: |
| case NSS_IPSEC_CMN_CTX_TYPE_MDATA_INNER: |
| packets = &dev_stats->tx_packets; |
| bytes = &dev_stats->tx_bytes; |
| dropped = &dev_stats->tx_dropped; |
| break; |
| |
| case NSS_IPSEC_CMN_CTX_TYPE_OUTER: |
| case NSS_IPSEC_CMN_CTX_TYPE_MDATA_OUTER: |
| packets = &dev_stats->rx_packets; |
| bytes = &dev_stats->rx_bytes; |
| dropped = &dev_stats->rx_dropped; |
| break; |
| default: |
| return; |
| } |
| |
| *packets += ctx_stats->rx_packets; |
| *bytes += ctx_stats->rx_bytes; |
| *dropped += (ctx_stats->rx_packets - ctx_stats->tx_packets); |
| |
| for (i = 0; i < ARRAY_SIZE(ctx_stats->rx_dropped); i++) { |
| *dropped += ctx_stats->rx_dropped[i]; |
| } |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_find() |
| * Find the context for the given type |
| */ |
| struct nss_ipsecmgr_ctx *nss_ipsecmgr_ctx_find(struct nss_ipsecmgr_tunnel *tun, enum nss_ipsec_cmn_ctx_type type) |
| { |
| struct list_head *head = &tun->ctx_db; |
| struct nss_ipsecmgr_ctx *ctx; |
| |
| /* |
| * Linux does not provide any specific API(s) to test for RW locks. The caller |
| * being internal is assumed to hold write lock before initiating this. |
| */ |
| list_for_each_entry(ctx, head, list) { |
| if (ctx->state.type == type) |
| return ctx; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_find_by_sa() |
| * Find the context for the given type |
| */ |
| struct nss_ipsecmgr_ctx *nss_ipsecmgr_ctx_find_by_sa(struct nss_ipsecmgr_tunnel *tun, enum nss_ipsecmgr_sa_type sa_type) |
| { |
| struct list_head *head = &tun->ctx_db; |
| enum nss_ipsec_cmn_ctx_type ctx_type; |
| struct nss_ipsecmgr_ctx *ctx; |
| |
| switch (sa_type) { |
| case NSS_IPSECMGR_SA_TYPE_ENCAP: |
| ctx_type = NSS_IPSEC_CMN_CTX_TYPE_INNER; |
| break; |
| |
| case NSS_IPSECMGR_SA_TYPE_DECAP: |
| ctx_type = NSS_IPSEC_CMN_CTX_TYPE_OUTER; |
| break; |
| |
| default: |
| nss_ipsecmgr_warn("%px: Unsupported SA type(%u)", tun, sa_type); |
| return NULL; |
| } |
| |
| /* |
| * Linux does not provide any specific API(s) to test for RW locks. The caller |
| * being internal is assumed to hold write lock before initiating this. |
| */ |
| list_for_each_entry(ctx, head, list) { |
| if (ctx->state.type == ctx_type) { |
| return ctx; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_attach() |
| * Attach context to the database |
| */ |
| void nss_ipsecmgr_ctx_attach(struct list_head *db, struct nss_ipsecmgr_ctx *ctx) |
| { |
| struct nss_ipsecmgr_tunnel *tun = ctx->tun; |
| |
| list_add(&ctx->list, db); |
| |
| /* |
| * Add ctx->ref to tun->ref |
| */ |
| write_lock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_ref_add(&ctx->ref, &tun->ref); |
| write_unlock_bh(&ipsecmgr_drv->lock); |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_config() |
| * Configure context |
| */ |
| bool nss_ipsecmgr_ctx_config(struct nss_ipsecmgr_ctx *ctx) |
| { |
| enum nss_ipsec_cmn_msg_type msg_type = NSS_IPSEC_CMN_MSG_TYPE_CTX_CONFIG; |
| struct nss_ipsec_cmn_ctx *ctx_msg; |
| struct nss_ipsec_cmn_msg nicm; |
| nss_tx_status_t status; |
| |
| memset(&nicm, 0, sizeof(struct nss_ipsec_cmn_msg)); |
| |
| ctx_msg = &nicm.msg.ctx; |
| ctx_msg->type = ctx->state.type; |
| ctx_msg->except_ifnum = ctx->state.except_ifnum; |
| ctx_msg->sibling_ifnum = ctx->state.sibling_ifnum; |
| |
| status = nss_ipsec_cmn_tx_msg_sync(ctx->nss_ctx, ctx->ifnum, msg_type, sizeof(*ctx_msg), &nicm); |
| if (status != NSS_TX_SUCCESS) { |
| nss_ipsecmgr_warn("%px: Failed to configure the context (ctx_type:%u),(tx_status:%d),(error:%x)", |
| ctx, ctx->state.type, status, nicm.cm.error); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_free() |
| * Free context |
| */ |
| void nss_ipsecmgr_ctx_free(struct nss_ipsecmgr_ctx *ctx) |
| { |
| nss_dynamic_interface_dealloc_node(ctx->ifnum, ctx->state.di_type); |
| kfree(ctx); |
| } |
| |
| /* |
| * nss_ipsecmgr_ctx_alloc() |
| * Allocate context for type and dynamic interface(s) |
| */ |
| struct nss_ipsecmgr_ctx *nss_ipsecmgr_ctx_alloc(struct nss_ipsecmgr_tunnel *tun, |
| enum nss_ipsec_cmn_ctx_type ctx_type, |
| enum nss_dynamic_interface_type di_type, |
| nss_ipsec_cmn_data_callback_t rx_data, |
| nss_ipsec_cmn_msg_callback_t rx_stats, |
| uint32_t features) |
| { |
| struct nss_ipsecmgr_ctx *ctx; |
| int32_t ifnum; |
| |
| ctx = kzalloc(sizeof(*ctx), in_atomic() ? GFP_ATOMIC : GFP_KERNEL); |
| if (!ctx) { |
| nss_ipsecmgr_warn("%px: failed to allocate context memory", tun); |
| return NULL; |
| } |
| |
| nss_ipsecmgr_trace("%px: Allocating dynamic interface type(%d)", ctx, di_type); |
| |
| ctx->tun = tun; |
| ctx->state.type = ctx_type; |
| ctx->state.di_type = di_type; |
| |
| ifnum = nss_dynamic_interface_alloc_node(di_type); |
| if (ifnum < 0) { |
| nss_ipsecmgr_warn("%px: failed to allocate dynamic interface(%d)", tun, di_type); |
| kfree(ctx); |
| return NULL; |
| } |
| |
| ctx->ifnum = ifnum; |
| ctx->state.stats_len = ctx->state.print_len = nss_ipsecmgr_ctx_stats_size(); |
| nss_ipsecmgr_ref_init(&ctx->ref, nss_ipsecmgr_ctx_del_ref, nss_ipsecmgr_ctx_free_ref); |
| nss_ipsecmgr_ref_init_print(&ctx->ref, nss_ipsecmgr_ctx_print_len, nss_ipsecmgr_ctx_print); |
| |
| INIT_LIST_HEAD(&ctx->list); |
| |
| ctx->nss_ctx = nss_ipsec_cmn_register_if(ctx->ifnum, tun->dev, rx_data, rx_stats, features, di_type, ctx); |
| if (!ctx->nss_ctx) { |
| nss_ipsecmgr_warn("%px: failed to register dynamic interface(%d, %d)", ctx, di_type, ctx->ifnum); |
| nss_dynamic_interface_dealloc_node(ctx->ifnum, di_type); |
| kfree(ctx); |
| return NULL; |
| } |
| |
| return ctx; |
| } |