blob: 2d6ecb4e4ce4d4012d1516abfac05649793db3ec [file] [log] [blame]
/*
**************************************************************************
* 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.
**************************************************************************
*/
/*
* nss_ovpnmgr_tun.c
*/
#include <linux/etherdevice.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/if_tun.h>
#include <linux/ip.h>
#include <net/ip.h>
#include <net/udp.h>
#include <net/ip6_checksum.h>
#include <linux/ipv6.h>
#include <linux/crypto.h>
#include <nss_api_if.h>
#include <nss_qvpn.h>
#include "nss_ovpnmgr.h"
#include "nss_ovpnmgr_crypto.h"
#include "nss_ovpnmgr_tun.h"
#include "nss_ovpnmgr_app.h"
#include "nss_ovpnmgr_debugfs.h"
#include "nss_ovpnmgr_priv.h"
#include "nss_ovpnmgr_route.h"
#if NSS_OVPNMGR_DEBUG_ENABLE_PKT_DUMP
/*
* nss_ovpnmgr_tun_pkt_dump()
* Dump packet.
*/
static void nss_ovpnmgr_tun_pkt_dump(struct sk_buff *skb, char *hdr, uint16_t offset, uint16_t len)
{
nss_ovpnmgr_info("%s:\n", hdr);
print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, skb->data + offset, len);
}
#define NSS_OVPNMGR_TUN_PKT_DUMP(skb, hdr, offset, len) nss_ovpnmgr_tun_pkt_dump(skb, hdr, offset, len)
#else
#define NSS_OVPNMGR_TUN_PKT_DUMP(skb, hdr, offset, len)
#endif
/*
* nss_ovpnmgr_tun_ipv4_forward()
* Transmit IPv4 encapsulated packet exceptioned from NSS.
*/
static void nss_ovpnmgr_tun_ipv4_forward(struct nss_ovpnmgr_tun *tun, struct sk_buff *skb)
{
struct nss_ovpnmgr_app *app = tun->app;
struct udphdr *udph;
struct rtable *rt;
struct iphdr *iph;
skb->protocol = htons(ETH_P_IP);
skb_reset_network_header(skb);
iph = ip_hdr(skb);
rt = ip_route_output(dev_net(app->dev), iph->daddr, iph->saddr, 0, 0);
if (unlikely(IS_ERR(rt))) {
nss_ovpnmgr_warn("%px: Failed to find IPv4 route.\n", skb);
tun->outer.stats.host_pkt_drop++;
dev_kfree_skb_any(skb);
return;
}
/*
* Reset skb->dst.
*/
skb_dst_drop(skb);
skb_dst_set(skb, &rt->dst);
/*
* Set packet type as host generated and set skb->dev to tun/tap device.
*/
skb->pkt_type = PACKET_HOST;
skb->dev = app->dev;
/*
* Check IPv4 header length.
*/
if (ip_hdrlen(skb) != sizeof(*iph)) {
nss_ovpnmgr_warn("%px: IPv4 header length is incorrect.\n", skb);
tun->outer.stats.host_pkt_drop++;
dev_kfree_skb_any(skb);
return;
}
/*
* Set transport header.
*/
skb_set_transport_header(skb, ip_hdrlen(skb));
skb->skb_iif = app->dev->ifindex;
if (iph->protocol != IPPROTO_UDP) {
nss_ovpnmgr_warn("%px: Encapuslation protocol is not UDP.\n", skb);
tun->outer.stats.host_pkt_drop++;
dev_kfree_skb_any(skb);
return;
}
udph = udp_hdr(skb);
udp_set_csum(0, skb, iph->saddr, iph->daddr, ntohs(udph->len));
ip_local_out(&init_net, NULL, skb);
}
/*
* nss_ovpnmgr_tun_ipv6_forward()
* Transmit IPv6 encapsulated packet exceptioned from NSS.
*/
static void nss_ovpnmgr_tun_ipv6_forward(struct nss_ovpnmgr_tun *tun, struct sk_buff *skb)
{
struct nss_ovpnmgr_app *app = tun->app;
struct rt6_info *rt6;
struct ipv6hdr *ip6h;
skb_reset_network_header(skb);
skb->protocol = htons(ETH_P_IPV6);
ip6h = ipv6_hdr(skb);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0))
rt6 = rt6_lookup(dev_net(app->dev), &ip6h->daddr, &ip6h->saddr, 0, 0);
#else
rt6 = rt6_lookup(dev_net(app->dev), &ip6h->daddr, &ip6h->saddr, 0, 0, 0);
#endif
if (!rt6) {
nss_ovpnmgr_warn("%px: Failed to find IPv6 route.\n", skb);
tun->outer.stats.host_pkt_drop++;
dev_kfree_skb_any(skb);
return;
}
/*
* Reset skb->dst.
*/
skb_dst_drop(skb);
skb_dst_set(skb, &rt6->dst);
/*
* Set packet type as host generated and set skb->dev to tun/tap device.
*/
skb->pkt_type = PACKET_HOST;
skb->dev = app->dev;
/*
* Set transport header.
*/
skb_set_transport_header(skb, sizeof(*ip6h));
skb->skb_iif = app->dev->ifindex;
if (ip6h->nexthdr != IPPROTO_UDP) {
nss_ovpnmgr_warn("%px: Encapuslation protocol is not UDP.\n", skb);
tun->outer.stats.host_pkt_drop++;
dev_kfree_skb_any(skb);
return;
}
skb->ip_summed = CHECKSUM_PARTIAL;
ip6_local_out(&init_net, NULL, skb);
}
/*
* nss_ovpnmgr_tun_rx_outer()
* Handles encapsulated packets received from NSS firmware
*/
static void nss_ovpnmgr_tun_rx_outer(struct net_device *dev, struct sk_buff *skb, struct napi_struct *napi)
{
struct nss_ovpnmgr_tun *tun = netdev_priv(dev);
skb_reset_network_header(skb);
switch (ip_hdr(skb)->version) {
case IPVERSION:
/*
* Dump ip_hdr(20) + udp_hdr(8) + ovpn_hdr(4)
*/
NSS_OVPNMGR_TUN_PKT_DUMP(skb, "IPv4 packet from NSS(Outer)", 0, 32);
nss_ovpnmgr_tun_ipv4_forward(tun, skb);
break;
case 6:
/*
* Dump ipv6_hdr(40) + udp_hdr(8) + ovpn_hdr(4)
*/
NSS_OVPNMGR_TUN_PKT_DUMP(skb, "IPv6 packet from NSS(Outer)", 0, 52);
nss_ovpnmgr_tun_ipv6_forward(tun, skb);
break;
default:
/*
* Dump initial 20 bytes to analyze junk packet.
*/
NSS_OVPNMGR_TUN_PKT_DUMP(skb, "Junk packet from NSS(Outer)", 0, 20);
tun->outer.stats.host_pkt_drop++;
dev_kfree_skb_any(skb);
}
}
/*
* nss_ovpnmgr_tun_rx_inner()
* Handles decapsulated packets received from NSS firmware
*/
static void nss_ovpnmgr_tun_rx_inner(struct net_device *dev, struct sk_buff *skb, struct napi_struct *napi)
{
struct nss_ovpnmgr_tun *tun = netdev_priv(dev);
struct nss_ovpnmgr_app *app = tun->app;
skb_reset_mac_header(skb);
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
switch (ip_hdr(skb)->version) {
case IPVERSION:
/*
* Dump ip_hdr(20) + transport header(8)
*/
NSS_OVPNMGR_TUN_PKT_DUMP(skb, "IPv4 packet from NSS(Inner)", 0, 28);
skb->protocol = htons(ETH_P_IP);
break;
case 6:
/*
* Dump ipv6_hdr(40) + transport header(8)
*/
NSS_OVPNMGR_TUN_PKT_DUMP(skb, "IPv6 packet from NSS(Inner)", 0, 48);
skb->protocol = htons(ETH_P_IPV6);
break;
default:
/*
* Dump maximum control packet size(28)
*/
NSS_OVPNMGR_TUN_PKT_DUMP(skb, "OCC/PING(OVPN) packet from NSS(Inner)", 0, 28);
nss_ovpnmgr_app_forward(app, tun, skb);
return;
}
skb->ip_summed = CHECKSUM_NONE;
skb->pkt_type = PACKET_HOST;
skb->dev = app->dev;
skb->skb_iif = app->dev->ifindex;
netif_receive_skb(skb);
}
/*
* nss_ovpnmgr_tun_stats_update()
* Update statistics.
*/
static void nss_ovpnmgr_tun_stats_update(struct nss_ovpnmgr_tun *tun, struct rtnl_link_stats64 *stats)
{
struct nss_ovpnmgr_tun_ctx_stats *inner_stats, *outer_stats;
int i;
/*
* This API is called under lock.
*/
inner_stats = &tun->inner.stats;
outer_stats = &tun->outer.stats;
stats->rx_packets += inner_stats->rx_packets;
stats->rx_bytes += inner_stats->rx_bytes;
stats->tx_packets += outer_stats->tx_packets;
stats->tx_bytes += outer_stats->tx_bytes;
for (i = 0; i < NSS_MAX_NUM_PRI; i++) {
stats->rx_dropped += inner_stats->rx_dropped[i];
stats->tx_dropped += outer_stats->rx_dropped[i];
}
for (i = 1; i < NSS_CRYPTO_CMN_RESP_ERROR_MAX; i++) {
stats->rx_dropped += inner_stats->fail_crypto[i];
stats->tx_dropped += outer_stats->fail_crypto[i];
}
for (i = 0; i < NSS_QVPN_PKT_DROP_EVENT_MAX; i++) {
stats->rx_dropped += inner_stats->fail_offload[i];
stats->tx_dropped += outer_stats->fail_offload[i];
}
}
/*
* nss_ovpnmgr_tun_update_node_stats()
* Update node statistics.
*/
static void nss_ovpnmgr_tun_update_node_stats(struct nss_ovpnmgr_tun *tun, struct nss_qvpn_stats_sync_msg *stats, bool inner)
{
struct nss_ovpnmgr_tun_stats *tun_stats = &tun->stats;
if (inner) {
tun_stats->tun_read_bytes += stats->node_stats.rx_bytes;
tun_stats->link_write_bytes += stats->node_stats.tx_bytes;
} else {
tun_stats->link_read_bytes += stats->node_stats.rx_bytes;
tun_stats->tun_write_bytes += stats->node_stats.tx_bytes;
tun_stats->link_read_bytes_auth += stats->node_stats.tx_bytes;
tun_stats->link_read_bytes_auth += stats->exception_event[NSS_QVPN_EXCEPTION_EVENT_RX_DATA_PKT];
}
}
/*
* nss_ovpnmgr_tun_event_inner()
* Handles events/messages from NSS firmware
*/
static void nss_ovpnmgr_tun_event_inner(void *app_data, struct nss_cmn_msg *ncm)
{
struct nss_ovpnmgr_tun_ctx_stats *ctx_stats;
struct nss_ovpnmgr_tun *tun = app_data;
struct nss_qvpn_msg *nom = (struct nss_qvpn_msg *)ncm;
struct nss_qvpn_stats_sync_msg *stats = &nom->msg.stats;
struct nss_ovpnmgr_app *app = tun->app;
struct net_device *dev;
int i;
if (nom->cm.type != NSS_QVPN_MSG_TYPE_SYNC_STATS) {
nss_ovpnmgr_warn("%px: Unsupported event received from NSS: %d\n", tun, nom->cm.type);
return;
}
write_lock(&ovpnmgr_ctx.lock);
ctx_stats = &tun->inner.stats;
ctx_stats->rx_packets += stats->node_stats.rx_packets;
ctx_stats->rx_bytes += stats->node_stats.rx_bytes;
ctx_stats->tx_packets += stats->node_stats.tx_packets;
ctx_stats->tx_bytes += stats->node_stats.tx_bytes;
for (i = 0; i < NSS_MAX_NUM_PRI; i++) {
ctx_stats->rx_dropped[i] += stats->node_stats.rx_dropped[i];
}
for (i = 0; i < NSS_CRYPTO_CMN_RESP_ERROR_MAX; i++) {
ctx_stats->fail_crypto[i] += stats->crypto_resp_error[i];
}
for (i = 0; i < NSS_QVPN_PKT_DROP_EVENT_MAX; i++) {
ctx_stats->fail_offload[i] += stats->pkt_drop_event[i];
}
for (i = 0 ; i < NSS_QVPN_EXCEPTION_EVENT_MAX; i++) {
ctx_stats->exception[i] += stats->exception_event[i];
}
dev = app->dev;
nss_ovpnmgr_tun_update_node_stats(tun, stats, true);
write_unlock(&ovpnmgr_ctx.lock);
}
/*
* nss_ovpnmgr_tun_event_outer()
* Handles events/messages from NSS firmware
*/
static void nss_ovpnmgr_tun_event_outer(void *app_data, struct nss_cmn_msg *ncm)
{
struct nss_ovpnmgr_tun_ctx_stats *ctx_stats;
struct nss_ovpnmgr_tun *tun = app_data;
struct nss_qvpn_msg *nom = (struct nss_qvpn_msg *)ncm;
struct nss_qvpn_stats_sync_msg *stats = &nom->msg.stats;
struct nss_ovpnmgr_app *app = tun->app;
struct net_device *dev;
int i;
if (nom->cm.type != NSS_QVPN_MSG_TYPE_SYNC_STATS) {
nss_ovpnmgr_warn("%px: Unsupported event received from NSS: %d\n", tun, nom->cm.type);
return;
}
write_lock(&ovpnmgr_ctx.lock);
ctx_stats = &tun->outer.stats;
ctx_stats->rx_packets += stats->node_stats.rx_packets;
ctx_stats->rx_bytes += stats->node_stats.rx_bytes;
ctx_stats->tx_packets += stats->node_stats.tx_packets;
ctx_stats->tx_bytes += stats->node_stats.tx_bytes;
for (i = 0; i < NSS_MAX_NUM_PRI; i++) {
ctx_stats->rx_dropped[i] += stats->node_stats.rx_dropped[i];
}
for (i = 0; i < NSS_CRYPTO_CMN_RESP_ERROR_MAX; i++) {
ctx_stats->fail_crypto[i] += stats->crypto_resp_error[i];
}
for (i = 0; i < NSS_QVPN_PKT_DROP_EVENT_MAX; i++) {
ctx_stats->fail_offload[i] += stats->pkt_drop_event[i];
}
for (i = 0 ; i < NSS_QVPN_EXCEPTION_EVENT_MAX; i++) {
ctx_stats->exception[i] += stats->exception_event[i];
}
dev = app->dev;
nss_ovpnmgr_tun_update_node_stats(tun, stats, false);
write_unlock(&ovpnmgr_ctx.lock);
}
/*
* nss_ovpnmgr_tun_ctx_deinit()
* De-initialize tunnel node.
*/
static int nss_ovpnmgr_tun_ctx_deinit(struct nss_ovpnmgr_tun_ctx *ctx)
{
nss_qvpn_unregister_if(ctx->ifnum);
if (nss_dynamic_interface_dealloc_node(ctx->ifnum, ctx->di_type) != NSS_TX_SUCCESS) {
nss_ovpnmgr_warn("%px: failed to dealloc OVPN inner DI\n", ctx);
}
/* Unregister Encryption */
nss_ovpnmgr_crypto_ctx_free(&ctx->active);
nss_ovpnmgr_crypto_ctx_free(&ctx->expiring);
return 0;
}
/*
* nss_ovpnmgr_tun_deconfig()
* Deconfigure inner tunnel node.
*/
static int nss_ovpnmgr_tun_deconfig(struct nss_ovpnmgr_tun_ctx *ctx, struct nss_ovpnmgr_tun *tun)
{
struct nss_qvpn_msg nom;
struct nss_qvpn_tunnel_config_msg *msg = &nom.msg.tunnel_config;
enum nss_qvpn_error_type resp;
nss_tx_status_t status;
/*
* TODO: Need to add support to retry if the failure is due to queueu full.
*/
status = nss_qvpn_tx_msg_sync(tun->nss_ctx, &nom, ctx->ifnum, NSS_QVPN_MSG_TYPE_TUNNEL_DECONFIGURE,
sizeof(*msg), &resp);
if (status != NSS_TX_SUCCESS) {
nss_ovpnmgr_warn("%px: failed to configure outer interface, resp=%d, status = %d\n", tun, resp, status);
return status == NSS_TX_FAILURE_QUEUE ? -EBUSY : -EINVAL;
}
return 0;
}
/*
* ovpnmgr_dev_ops
*/
static const struct net_device_ops ovpnmgr_dev_ops = {
/*
* ovpn netdev is created for transmitting control packets in data channel
* to application. No addtional functionality is implemented.
*/
};
/*
* nss_ovpnmgr_tun_free()
* Delete OVPN tunnel.
*/
static void nss_ovpnmgr_tun_free(struct net_device *dev)
{
struct nss_ovpnmgr_tun *tun = netdev_priv(dev);
nss_ovpnmgr_tun_deconfig(&tun->inner, tun);
nss_ovpnmgr_tun_deconfig(&tun->outer, tun);
nss_ovpnmgr_tun_ctx_deinit(&tun->inner);
nss_ovpnmgr_tun_ctx_deinit(&tun->outer);
free_netdev(dev);
}
/*
* nss_ovpnmgr_tun_dev_setup()
* Setup function for dummy netdevice.
*/
static void nss_ovpnmgr_tun_dev_setup(struct net_device *dev)
{
dev->needed_headroom = NSS_OVPNMGR_TUN_HEADROOM;
dev->needed_tailroom = NSS_OVPNMGR_TUN_TAILROOM;
dev->header_ops = NULL;
dev->netdev_ops = &ovpnmgr_dev_ops;
dev->ethtool_ops = NULL;
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 11, 8))
dev->destructor = nss_ovpnmgr_tun_free;
#else
dev->priv_destructor = nss_ovpnmgr_tun_free;
#endif
}
/*
* nss_ovpnmgr_tun_alloc()
* Allocte an OVPN tunnel instance.
*/
static struct nss_ovpnmgr_tun *nss_ovpnmgr_tun_alloc(void)
{
struct nss_ovpnmgr_tun *tun;
struct net_device *dev;
dev = alloc_netdev(sizeof(*tun), NSS_OVPNMGR_TUN_NAME, NET_NAME_ENUM, nss_ovpnmgr_tun_dev_setup);
if (!dev) {
nss_ovpnmgr_warn("unable to allocate ovpn netdev\n");
return NULL;
}
tun = netdev_priv(dev);
tun->dev = dev;
INIT_LIST_HEAD(&tun->list);
INIT_LIST_HEAD(&tun->route_list);
tun->nss_ctx = nss_qvpn_get_context();
if (unlikely(!tun->nss_ctx)) {
nss_ovpnmgr_warn("%px: Failed to get NSS OVPN context.\n", tun);
return NULL;
}
tun->inner.nss_ctx = tun->outer.nss_ctx = tun->nss_ctx;
return tun;
}
/*
* nss_ovpnmgr_tun_init_qvpn_config()
* Initialize QVPN configuration data structure.
*/
static void nss_ovpnmgr_tun_init_qvpn_config(struct nss_ovpnmgr_tun *tun, struct nss_qvpn_tunnel_config_msg *qvpn_cfg)
{
memset(qvpn_cfg, 0, sizeof(*qvpn_cfg));
if (tun->tun_cfg.flags & NSS_OVPNMGR_HDR_FLAG_IPV6) {
nss_ovpnmgr_ipv6_addr_ntohl(qvpn_cfg->hdr_cfg.dst_ip, tun->tun_hdr.dst_ip);
nss_ovpnmgr_ipv6_addr_ntohl(qvpn_cfg->hdr_cfg.src_ip, tun->tun_hdr.src_ip);
qvpn_cfg->hdr_cfg.hdr_flags = NSS_QVPN_HDR_FLAG_IPV6;
} else {
qvpn_cfg->hdr_cfg.src_ip[0] = ntohl(tun->tun_hdr.src_ip[0]);
qvpn_cfg->hdr_cfg.dst_ip[0] = ntohl(tun->tun_hdr.dst_ip[0]);
}
qvpn_cfg->hdr_cfg.src_port = ntohs(tun->tun_hdr.src_port);
qvpn_cfg->hdr_cfg.dst_port = ntohs(tun->tun_hdr.dst_port);
qvpn_cfg->hdr_cfg.hop_limit = tun->tun_hdr.hop_limit;
qvpn_cfg->hdr_cfg.hdr_flags |= NSS_QVPN_HDR_FLAG_L4_UDP;
qvpn_cfg->hdr_cfg.seqnum_size = 4;
qvpn_cfg->hdr_cfg.anti_replay_alg = NSS_QVPN_ANTI_REPLAY_ALG_REPLAY_WINDOW;
qvpn_cfg->hdr_cfg.session_id_offset = 0;
qvpn_cfg->hdr_cfg.session_id_size = 1;
qvpn_cfg->hdr_cfg.vpn_hdr_head_offset = 0;
qvpn_cfg->hdr_cfg.vpn_hdr_tail_size = 0;
}
/*
* nss_ovpnmgr_tun_inner_config()
* Configure inner tunnel node.
*/
static int nss_ovpnmgr_tun_inner_config(struct nss_ovpnmgr_tun *tun)
{
struct nss_qvpn_msg nom;
struct nss_qvpn_tunnel_config_msg *qvpn_cfg = &nom.msg.tunnel_config;
enum nss_qvpn_error_type resp;
nss_tx_status_t status;
int32_t total_cmds = 1;
nss_ovpnmgr_tun_init_qvpn_config(tun, qvpn_cfg);
qvpn_cfg->sibling_if = tun->outer.ifnum;
/*
* QVPN commands for OVPN encapsulation.
* NSS_QVPN_CMDS_TYPE_ADD_VPN_HDR, NSS_QVPN_CMDS_TYPE_ANTI_REPLAY,
* NSS_QVPN_CMDS_TYPE_ENCRYPT, NSS_QVPN_CMDS_TYPE_ADD_L3_L4_HDR
*/
qvpn_cfg->cmd[0] = NSS_QVPN_CMDS_TYPE_ADD_VPN_HDR;
if (!(tun->tun_cfg.flags & NSS_OVPNMGR_HDR_FLAG_NO_REPLAY)) {
qvpn_cfg->cmd[total_cmds++] = NSS_QVPN_CMDS_TYPE_ANTI_REPLAY;
}
if (tun->inner.active.crypto_idx != U16_MAX) {
qvpn_cfg->cmd[total_cmds++] = NSS_QVPN_CMDS_TYPE_ENCRYPT;
qvpn_cfg->cmd_profile = NSS_QVPN_PROFILE_CRYPTO_ENCAP;
} else {
qvpn_cfg->cmd_profile = NSS_QVPN_PROFILE_ENCAP;
}
qvpn_cfg->cmd[total_cmds++] = NSS_QVPN_CMDS_TYPE_ADD_L3_L4_HDR;
qvpn_cfg->total_cmds = total_cmds;
/*
* Crypto configuration.
*/
qvpn_cfg->crypto_key.crypto_idx = tun->inner.active.crypto_idx;
if (tun->tun_cfg.flags & NSS_OVPNMGR_HDR_FLAG_DATA_V2) {
uint32_t *session_id = (uint32_t *)qvpn_cfg->hdr_cfg.vpn_hdr_head;
*session_id = htonl((NSS_OVPNMGR_TUN_DATA_V2 << NSS_OVPNMGR_TUN_OPCODE_SHIFT) |
(tun->inner.active.key_id << NSS_OVPNMGR_TUN_KEY_ID_SHIFT) |
(tun->tun_cfg.peer_id & 0xFFFFFF));
/*
* [op+kid|peer-id|HMAC Len|IV|SNO|Inner Packet]
* [1|3|20-32|16-24-32]
*/
qvpn_cfg->crypto_cfg.hmac_offset = 4;
qvpn_cfg->hdr_cfg.vpn_hdr_head_size = 4;
} else {
uint32_t *session_id = (uint32_t *)qvpn_cfg->hdr_cfg.vpn_hdr_head;
*session_id = htonl((NSS_OVPNMGR_TUN_DATA_V1 << NSS_OVPNMGR_TUN_OPCODE_SHIFT) |
(tun->inner.active.key_id << NSS_OVPNMGR_TUN_KEY_ID_SHIFT));
/*
* [op+kid|HMAC Len|IV|SNO|Inner Packet]
* [1|20-32|16-24-32]
*/
qvpn_cfg->crypto_cfg.hmac_offset = 1;
qvpn_cfg->hdr_cfg.vpn_hdr_head_size = 1;
}
memcpy(qvpn_cfg->crypto_key.session_id, qvpn_cfg->hdr_cfg.vpn_hdr_head, 1);
qvpn_cfg->crypto_cfg.hmac_len = tun->inner.active.hash_len;
qvpn_cfg->crypto_cfg.iv_len = tun->inner.active.iv_len;
qvpn_cfg->hdr_cfg.vpn_hdr_head_size += qvpn_cfg->crypto_cfg.hmac_len + qvpn_cfg->crypto_cfg.iv_len +
qvpn_cfg->hdr_cfg.seqnum_size;
qvpn_cfg->crypto_cfg.iv_offset = qvpn_cfg->crypto_cfg.hmac_offset + qvpn_cfg->crypto_cfg.hmac_len;
qvpn_cfg->crypto_cfg.auth_offset = qvpn_cfg->crypto_cfg.iv_offset;
qvpn_cfg->crypto_cfg.cipher_op_offset = qvpn_cfg->crypto_cfg.iv_offset + qvpn_cfg->crypto_cfg.iv_len;
qvpn_cfg->crypto_cfg.cipher_blk_size = tun->outer.active.blk_len;
qvpn_cfg->hdr_cfg.seqnum_offset = qvpn_cfg->crypto_cfg.iv_offset + qvpn_cfg->crypto_cfg.iv_len;
switch (tun->inner.active.crypto_type) {
case NSS_OVPNMGR_CRYPTO_TYPE_AEAD:
qvpn_cfg->crypto_cfg.crypto_mode = NSS_QVPN_CRYPTO_MODE_ENC_AUTH;
qvpn_cfg->crypto_cfg.iv_type = NSS_QVPN_IV_TYPE_DYNAMIC_RAND;
qvpn_cfg->crypto_cfg.pad_type = NSS_QVPN_PAD_TYPE_PKCS7;
break;
case NSS_OVPNMGR_CRYPTO_TYPE_ABLK:
qvpn_cfg->crypto_cfg.crypto_mode = NSS_QVPN_CRYPTO_MODE_ENC;
qvpn_cfg->crypto_cfg.iv_type = NSS_QVPN_IV_TYPE_DYNAMIC_RAND;
qvpn_cfg->crypto_cfg.pad_type = NSS_QVPN_PAD_TYPE_PKCS7;
break;
case NSS_OVPNMGR_CRYPTO_TYPE_AHASH:
qvpn_cfg->crypto_cfg.crypto_mode = NSS_QVPN_CRYPTO_MODE_AUTH;
qvpn_cfg->crypto_cfg.iv_type = NSS_QVPN_IV_TYPE_NONE;
qvpn_cfg->crypto_cfg.pad_type = NSS_QVPN_PAD_TYPE_NONE;
break;
default:
qvpn_cfg->crypto_cfg.crypto_mode = NSS_QVPN_CRYPTO_MODE_NONE;
qvpn_cfg->crypto_cfg.iv_type = NSS_QVPN_IV_TYPE_NONE;
qvpn_cfg->crypto_cfg.pad_type = NSS_QVPN_PAD_TYPE_NONE;
}
status = nss_qvpn_tx_msg_sync(tun->nss_ctx, &nom, tun->inner.ifnum, NSS_QVPN_MSG_TYPE_TUNNEL_CONFIGURE,
sizeof(*qvpn_cfg), &resp);
if (status != NSS_TX_SUCCESS) {
nss_ovpnmgr_warn("%px: failed to configure inner interface, resp = %d, status = %d\n", tun, resp, status);
return status == NSS_TX_FAILURE_QUEUE ? -EBUSY : -EINVAL;
}
return 0;
}
/*
* nss_ovpnmgr_tun_outer_config()
* Configure outer tunnel node.
*/
static int nss_ovpnmgr_tun_outer_config(struct nss_ovpnmgr_tun *tun)
{
struct nss_qvpn_msg nom;
struct nss_qvpn_tunnel_config_msg *qvpn_cfg = &nom.msg.tunnel_config;
enum nss_qvpn_error_type resp;
nss_tx_status_t status;
int32_t total_cmds = 0;
nss_ovpnmgr_tun_init_qvpn_config(tun, qvpn_cfg);
qvpn_cfg->sibling_if = tun->inner.ifnum;
/*
* QVPN commands for OVPN encapsulation.
* NSS_QVPN_CMDS_TYPE_DECRYPT, NSS_QVPN_CMDS_TYPE_ANTI_REPLAY,
* NSS_QVPN_CMDS_TYPE_REMOVE_L3_L4_HDR, NSS_QVPN_CMDS_TYPE_REMOVE_VPN_HDR
*/
if (tun->outer.active.crypto_idx != U16_MAX) {
qvpn_cfg->cmd[total_cmds++] = NSS_QVPN_CMDS_TYPE_DECRYPT;
qvpn_cfg->cmd_profile = NSS_QVPN_PROFILE_CRYPTO_DECAP;
} else {
qvpn_cfg->cmd_profile = NSS_QVPN_PROFILE_DECAP;
}
if (!(tun->tun_cfg.flags & NSS_OVPNMGR_HDR_FLAG_NO_REPLAY)) {
qvpn_cfg->cmd[total_cmds++] = NSS_QVPN_CMDS_TYPE_ANTI_REPLAY;
}
qvpn_cfg->cmd[total_cmds++] = NSS_QVPN_CMDS_TYPE_REMOVE_L3_L4_HDR;
qvpn_cfg->cmd[total_cmds++] = NSS_QVPN_CMDS_TYPE_REMOVE_VPN_HDR;
qvpn_cfg->total_cmds = total_cmds;
/*
* Crypto configuration.
*/
qvpn_cfg->crypto_key.crypto_idx = tun->outer.active.crypto_idx;
if (tun->tun_cfg.flags & NSS_OVPNMGR_HDR_FLAG_PEER_DATA_V2) {
uint32_t *session_id = (uint32_t *)qvpn_cfg->hdr_cfg.vpn_hdr_head;
nss_ovpnmgr_info("Peer transmits V2 data packets\n");
*session_id = htonl((NSS_OVPNMGR_TUN_DATA_V2 << NSS_OVPNMGR_TUN_OPCODE_SHIFT) |
(tun->inner.active.key_id << NSS_OVPNMGR_TUN_KEY_ID_SHIFT) |
(tun->tun_cfg.peer_id & 0xFFFFFF));
/*
* [op+kid|peer-id|HMAC Len|IV|SNO|Inner Packet]
* [1|3|20-32|16-24-32]
*/
qvpn_cfg->crypto_cfg.hmac_offset = 4;
qvpn_cfg->hdr_cfg.vpn_hdr_head_size = 4;
} else {
uint32_t *session_id = (uint32_t *)qvpn_cfg->hdr_cfg.vpn_hdr_head;
*session_id = htonl((NSS_OVPNMGR_TUN_DATA_V1 << NSS_OVPNMGR_TUN_OPCODE_SHIFT) |
(tun->inner.active.key_id << NSS_OVPNMGR_TUN_KEY_ID_SHIFT));
/*
* [op+kid|HMAC Len|IV|SNO|Inner Packet]
* [1|20-32|16-24-32]
*/
qvpn_cfg->crypto_cfg.hmac_offset = 1;
qvpn_cfg->hdr_cfg.vpn_hdr_head_size = 1;
}
memcpy(qvpn_cfg->crypto_key.session_id, qvpn_cfg->hdr_cfg.vpn_hdr_head, 1);
qvpn_cfg->crypto_cfg.hmac_len = tun->outer.active.hash_len;
qvpn_cfg->crypto_cfg.iv_len = tun->outer.active.iv_len;
qvpn_cfg->hdr_cfg.vpn_hdr_head_size += qvpn_cfg->crypto_cfg.hmac_len + qvpn_cfg->crypto_cfg.iv_len +
qvpn_cfg->hdr_cfg.seqnum_size;
qvpn_cfg->crypto_cfg.iv_offset = qvpn_cfg->crypto_cfg.hmac_offset + qvpn_cfg->crypto_cfg.hmac_len;
qvpn_cfg->crypto_cfg.auth_offset = qvpn_cfg->crypto_cfg.iv_offset;
qvpn_cfg->crypto_cfg.cipher_op_offset = qvpn_cfg->crypto_cfg.iv_offset + qvpn_cfg->crypto_cfg.iv_len;
qvpn_cfg->crypto_cfg.cipher_blk_size = tun->outer.active.blk_len;
switch (tun->outer.active.crypto_type) {
case NSS_OVPNMGR_CRYPTO_TYPE_AEAD:
qvpn_cfg->crypto_cfg.crypto_mode = NSS_QVPN_CRYPTO_MODE_AUTH_DEC;
qvpn_cfg->crypto_cfg.iv_type = NSS_QVPN_IV_TYPE_DYNAMIC_RAND;
qvpn_cfg->crypto_cfg.pad_type = NSS_QVPN_PAD_TYPE_PKCS7;
break;
case NSS_OVPNMGR_CRYPTO_TYPE_ABLK:
qvpn_cfg->crypto_cfg.crypto_mode = NSS_QVPN_CRYPTO_MODE_DEC;
qvpn_cfg->crypto_cfg.iv_type = NSS_QVPN_IV_TYPE_DYNAMIC_RAND;
qvpn_cfg->crypto_cfg.pad_type = NSS_QVPN_PAD_TYPE_PKCS7;
break;
case NSS_OVPNMGR_CRYPTO_TYPE_AHASH:
qvpn_cfg->crypto_cfg.crypto_mode = NSS_QVPN_CRYPTO_MODE_AUTH;
qvpn_cfg->crypto_cfg.iv_type = NSS_QVPN_IV_TYPE_NONE;
qvpn_cfg->crypto_cfg.pad_type = NSS_QVPN_PAD_TYPE_NONE;
break;
default:
qvpn_cfg->crypto_cfg.crypto_mode = NSS_QVPN_CRYPTO_MODE_NONE;
qvpn_cfg->crypto_cfg.iv_type = NSS_QVPN_IV_TYPE_NONE;
qvpn_cfg->crypto_cfg.pad_type = NSS_QVPN_PAD_TYPE_NONE;
}
status = nss_qvpn_tx_msg_sync(tun->nss_ctx, &nom, tun->outer.ifnum, NSS_QVPN_MSG_TYPE_TUNNEL_CONFIGURE,
sizeof(*qvpn_cfg), &resp);
if (status != NSS_TX_SUCCESS) {
nss_ovpnmgr_warn("%px: failed to configure outer interface, resp = %d, status = %d\n", tun, resp, status);
return status == NSS_TX_FAILURE_QUEUE ? -EBUSY : -EINVAL;
}
return 0;
}
/*
* nss_ovpnmgr_tun_ctx_inner_init()
* Initialize inner tunnel context.
*/
static int nss_ovpnmgr_tun_ctx_inner_init(struct nss_ovpnmgr_tun *tun,
struct nss_ovpnmgr_crypto_config *crypto_cfg)
{
int ret;
/*
* Register Encryption.
*/
if (nss_ovpnmgr_crypto_ctx_alloc(&tun->inner.active, crypto_cfg, &crypto_cfg->encrypt)) {
nss_ovpnmgr_error("%px: Failed to register Encryption session\n", tun);
return -EINVAL;
}
/* Send message to NSS FW */
tun->inner.di_type = NSS_DYNAMIC_INTERFACE_TYPE_QVPN_INNER;
tun->inner.ifnum = nss_dynamic_interface_alloc_node(tun->inner.di_type);
if (tun->inner.ifnum < 0) {
nss_ovpnmgr_error("%px: Failed to alloc OVPN inner dynamic interface\n", tun);
ret = -ENXIO;
goto free_crypto;
}
if (!nss_qvpn_register_if(tun->inner.ifnum, nss_ovpnmgr_tun_rx_inner,
nss_ovpnmgr_tun_event_inner, tun->dev, 0, tun)) {
nss_ovpnmgr_warn("%px: Failed to register OVPN inner interface %d\n", tun, tun->inner.ifnum);
ret = -EIO;
goto free_ifnum;
}
return 0;
free_ifnum:
if (nss_dynamic_interface_dealloc_node(tun->inner.ifnum, tun->inner.di_type)) {
nss_ovpnmgr_warn("%px: Failed to dealloc inner DI.\n", tun);
}
free_crypto:
/* Unregister Encryption */
nss_ovpnmgr_crypto_ctx_free(&tun->inner.active);
return ret;
}
/*
* nss_ovpnmgr_tun_ctx_outer_init()
* Initialize outer tunnel context.
*/
static int nss_ovpnmgr_tun_ctx_outer_init(struct nss_ovpnmgr_tun *tun, struct nss_ovpnmgr_crypto_config *crypto_cfg)
{
int ret;
/*
* Register Encryption.
*/
if (nss_ovpnmgr_crypto_ctx_alloc(&tun->outer.active, crypto_cfg, &crypto_cfg->decrypt)) {
nss_ovpnmgr_error("%px: Failed to register Encryption session\n", tun);
return -EINVAL;
}
/* Send message to NSS FW */
tun->outer.di_type = NSS_DYNAMIC_INTERFACE_TYPE_QVPN_OUTER;
tun->outer.ifnum = nss_dynamic_interface_alloc_node(tun->outer.di_type);
if (tun->outer.ifnum < 0) {
nss_ovpnmgr_error("%px: Failed to alloc OVPN outer dynamic interface\n", tun);
ret = -ENXIO;
goto free_crypto;
}
if (!nss_qvpn_register_if(tun->outer.ifnum, nss_ovpnmgr_tun_rx_outer, nss_ovpnmgr_tun_event_outer,
tun->dev, 0, tun)) {
nss_ovpnmgr_warn("%px: Failed to register OVPN outer interface %d\n", tun, tun->outer.ifnum);
ret = -EIO;
goto free_ifnum;
}
return 0;
free_ifnum:
if (nss_dynamic_interface_dealloc_node(tun->outer.ifnum, tun->outer.di_type)) {
nss_ovpnmgr_warn("%px: Failed to dealloc outer dynamic node.\n", tun);
}
free_crypto:
/* Unregister Decryption */
nss_ovpnmgr_crypto_ctx_free(&tun->outer.active);
return ret;
}
/*
* nss_ovpnmgr_tun_config_verify()
* Verify tunnel configuration.
*/
static int nss_ovpnmgr_tun_config_verify(struct nss_ovpnmgr_tun_tuple *tun_hdr, struct nss_ovpnmgr_tun_config *tun_cfg)
{
if (tun_cfg->flags & NSS_OVPNMGR_HDR_FLAG_L4_PROTO_TCP) {
nss_ovpnmgr_warn("%px: OVPN data channel offload over TCP is not supported\n", tun_hdr);
return -EINVAL;
}
if (tun_cfg->flags & NSS_OVPNMGR_HDR_FLAG_FRAG) {
nss_ovpnmgr_warn("%px: Fragmentation specified by OVPN protocol is not supported\n", tun_hdr);
return -EINVAL;
}
if (tun_cfg->flags & NSS_OVPNMGR_HDR_FLAG_PID_LONG_FMT) {
nss_ovpnmgr_warn("%px: PID Long format is not supported\n", tun_hdr);
return -EINVAL;
}
if (tun_cfg->flags & NSS_OVPNMGR_HDR_FLAG_SHARED_KEY) {
nss_ovpnmgr_warn("%px: Crypto shared key is not supported\n", tun_hdr);
return -EINVAL;
}
if (tun_cfg->flags & NSS_OVPNMGR_HDR_FLAG_COPY_TOS) {
nss_ovpnmgr_warn("%px: Copy TOS is not supported\n", tun_hdr);
return -EINVAL;
}
if (!tun_hdr->dst_ip[0] || !tun_hdr->src_ip[0] || !tun_hdr->src_port || !tun_hdr->dst_port) {
nss_ovpnmgr_warn("%px: IP/UDP Tunnel parameters are invalid.\n", tun_hdr);
return -EINVAL;
}
if (!tun_hdr->hop_limit) {
nss_ovpnmgr_warn("%px: Invalid hop_limit.\n", tun_hdr);
return -EINVAL;
}
return 0;
}
/*
* nss_ovpnmgr_tun_config_dump()
* Dump configuration details.
*/
static void nss_ovpnmgr_tun_config_dump(struct net_device *app_dev, struct nss_ovpnmgr_tun_tuple *tun_hdr,
struct nss_ovpnmgr_tun_config *tun_cfg, struct nss_ovpnmgr_crypto_config *crypto_cfg)
{
nss_ovpnmgr_info("%px: app_dev = %s\n", app_dev, app_dev->name);
nss_ovpnmgr_info("flags = %x, peer_id = %u\n", tun_cfg->flags, tun_cfg->peer_id);
if (tun_cfg->flags & NSS_OVPNMGR_HDR_FLAG_IPV6) {
nss_ovpnmgr_info("Remote tunnel IP = %pI6c\n", tun_hdr->dst_ip);
nss_ovpnmgr_info("local tunnel IP = %pI6c\n", tun_hdr->src_ip);
nss_ovpnmgr_info("local tunnel Port = %d\n", tun_hdr->src_port);
nss_ovpnmgr_info("remote tunnel Port = %d\n", tun_hdr->dst_port);
} else {
nss_ovpnmgr_info("Remote tunnel IP = %pI4\n", &tun_hdr->dst_ip[0]);
nss_ovpnmgr_info("local tunnel IP = %pI4\n", &tun_hdr->src_ip[0]);
nss_ovpnmgr_info("local tunnel Port = %d\n", tun_hdr->src_port);
nss_ovpnmgr_info("remote tunnel Port = %d\n", tun_hdr->dst_port);
}
nss_ovpnmgr_info("hop_limit = %d\n", tun_hdr->hop_limit);
nss_ovpnmgr_info("Crypto: algo = %d, cipher_keylen = %d, hmac_keylen = %d\n",
crypto_cfg->algo, crypto_cfg->encrypt.cipher_keylen,
crypto_cfg->encrypt.hmac_keylen);
}
/*
* nss_ovpnmgr_tun_get_stats()
* Update offload tunnel statistics
*/
void nss_ovpnmgr_tun_get_stats(struct net_device *app_dev, struct rtnl_link_stats64 *stats)
{
struct nss_ovpnmgr_app *app;
struct nss_ovpnmgr_tun *tun;
write_lock_bh(&ovpnmgr_ctx.lock);
app = nss_ovpnmgr_app_find(app_dev);
if (!app) {
write_unlock_bh(&ovpnmgr_ctx.lock);
return;
}
/*
* Get per tunnel statistics.
*/
list_for_each_entry(tun, &app->tun_list, list) {
nss_ovpnmgr_tun_stats_update(tun, stats);
}
write_unlock_bh(&ovpnmgr_ctx.lock);
}
/*
* nss_ovpnmgr_tun_route_update()
* Update route state
*/
void nss_ovpnmgr_tun_route_update(uint32_t tunnel_id, uint32_t *from_addr, uint32_t *to_addr, int version)
{
struct nss_ovpnmgr_route_tuple rt;
struct nss_ovpnmgr_tun *tun;
struct net_device *tun_dev;
int addr_size;
tun_dev = dev_get_by_index(&init_net, tunnel_id);
if (!tun_dev) {
nss_ovpnmgr_warn("Couldn't find tunnel with tunnel_id = %d\n", tunnel_id);
return;
}
tun = netdev_priv(tun_dev);
addr_size = version == IPVERSION ? sizeof(rt.ip_addr[0]) : sizeof(rt.ip_addr);
memset(&rt, 0, sizeof(rt));
rt.ip_version = version;
memcpy(rt.ip_addr, from_addr, addr_size);
write_lock_bh(&ovpnmgr_ctx.lock);
/*
* Set route active for from_addr.
*/
if (!nss_ovpnmgr_route_set_active(&tun->route_list, &rt)) {
goto done;
}
/*
* Route is not found for from_addr, set route active for to_addr.
*/
memcpy(rt.ip_addr, to_addr, addr_size);
nss_ovpnmgr_route_set_active(&tun->route_list, &rt);
done:
write_unlock_bh(&ovpnmgr_ctx.lock);
dev_put(tun_dev);
}
EXPORT_SYMBOL(nss_ovpnmgr_tun_route_update);
/*
* nss_ovpnmgr_tun_stats_get()
* Get statistics of tunnel
*/
int nss_ovpnmgr_tun_stats_get(uint32_t tunnel_id, struct nss_ovpnmgr_tun_stats *stats)
{
struct nss_ovpnmgr_tun *tun;
struct net_device *tun_dev;
tun_dev = dev_get_by_index(&init_net, tunnel_id);
if (!tun_dev) {
nss_ovpnmgr_warn("Couldn't find tunnel with tunnel_id = %d\n", tunnel_id);
return -EINVAL;
}
tun = netdev_priv(tun_dev);
write_lock_bh(&ovpnmgr_ctx.lock);
stats->tun_read_bytes = tun->stats.tun_read_bytes;
stats->tun_write_bytes = tun->stats.tun_write_bytes;
stats->link_read_bytes = tun->stats.link_read_bytes;
stats->link_read_bytes_auth = tun->stats.link_read_bytes_auth;
stats->link_write_bytes = tun->stats.link_write_bytes;
memset(&tun->stats, 0, sizeof(tun->stats));
write_unlock_bh(&ovpnmgr_ctx.lock);
dev_put(tun_dev);
return 0;
}
EXPORT_SYMBOL(nss_ovpnmgr_tun_stats_get);
/*
* nss_ovpnmgr_tun_tx()
* Transmit packet to NSS firmware for encapsulation/decapsulation.
*/
int nss_ovpnmgr_tun_tx(uint32_t tunnel_id, struct nss_ovpnmgr_metadata *mdata, struct sk_buff *skb)
{
struct nss_ovpnmgr_tun_ctx_stats *stats;
struct nss_ovpnmgr_tun *tun;
struct net_device *tun_dev;
nss_tx_status_t status;
bool expand_skb = false;
uint32_t ifnum;
uint16_t nhead;
uint16_t ntail;
int err;
tun_dev = dev_get_by_index(&init_net, tunnel_id);
if (!tun_dev) {
nss_ovpnmgr_warn("%px: Couldn't find tunnel with tunnel_id = %d\n", skb, mdata->tunnel_id);
return -EINVAL;
}
tun = netdev_priv(tun_dev);
nss_ovpnmgr_info("%px: metadata flags (0x%x)\n", tun, mdata->flags);
nhead = tun_dev->needed_headroom;
ntail = tun_dev->needed_tailroom;
read_lock_bh(&ovpnmgr_ctx.lock);
if (mdata->flags & NSS_OVPNMGR_METADATA_FLAG_PKT_DECAP) {
ifnum = tun->outer.ifnum;
stats = &tun->outer.stats;
} else {
ifnum = tun->inner.ifnum;
stats = &tun->inner.stats;
}
read_unlock_bh(&ovpnmgr_ctx.lock);
/*
* Check if skb is shared
*/
if (unlikely(skb_shared(skb))) {
skb = skb_unshare(skb, in_atomic() ? GFP_ATOMIC : GFP_KERNEL);
if (!skb) {
nss_ovpnmgr_trace("%px: unable to expand skb, dev = %s\n", tun, tun_dev->name);
err = -ENOSPC;
goto free;
}
}
if (skb_cloned(skb) || (skb_headroom(skb) < nhead) || (skb_tailroom(skb) < ntail)) {
expand_skb = true;
}
if (expand_skb && pskb_expand_head(skb, nhead, ntail, in_atomic() ? GFP_ATOMIC : GFP_KERNEL)) {
nss_ovpnmgr_trace("%px: unable to expand skb, dev = %s\n", tun, tun_dev->name);
err = -ENOMEM;
goto free;
}
/*
* Dump IPv4/v6 header (40) + transport header (8) + ovpn header (4)
*/
NSS_OVPNMGR_TUN_PKT_DUMP(skb, "Pkt to NSS", 0, 52);
status = nss_qvpn_tx_buf(tun->nss_ctx, ifnum, skb);
if (status != NSS_TX_SUCCESS) {
nss_ovpnmgr_trace("%px: Packet offload failed, status = %d\n", tun, status);
err = (status == NSS_TX_FAILURE_QUEUE) ? -EBUSY : -EINVAL;
goto free;
}
dev_put(tun_dev);
return 0;
free:
write_lock_bh(&ovpnmgr_ctx.lock);
stats->host_pkt_drop++;
write_unlock_bh(&ovpnmgr_ctx.lock);
dev_put(tun_dev);
return err;
}
EXPORT_SYMBOL(nss_ovpnmgr_tun_tx);
/*
* nss_ovpnmgr_tun_del()
* Delete tunnel.
*/
int nss_ovpnmgr_tun_del(uint32_t tunnel_id)
{
struct nss_ovpnmgr_route *route, *n;
struct nss_ovpnmgr_tun *tun;
struct net_device *tun_dev;
tun_dev = dev_get_by_index(&init_net, tunnel_id);
if (!tun_dev) {
nss_ovpnmgr_warn("Couldn't find tunnel: tunnel_id = %u\n\n", tunnel_id);
return -EINVAL;
}
tun = netdev_priv(tun_dev);
nss_ovpnmgr_info("%px: Deleting Tunnel, tunnel_id = %d\n", tun, tun->tunnel_id);
write_lock_bh(&ovpnmgr_ctx.lock);
/*
* Delete Routes from the tunnel.
*/
list_for_each_entry_safe(route, n, &tun->route_list, list) {
list_del(&route->list);
kfree(route);
}
/*
* Remove this tunnel from tunnel list.
*/
list_del(&tun->list);
write_unlock_bh(&ovpnmgr_ctx.lock);
/*
* Release tun_dev before freeing tunnel.
*/
dev_put(tun_dev);
/*
* Unregister ovpn netdevice.
*/
rtnl_is_locked() ? unregister_netdevice(tun_dev) : unregister_netdev(tun_dev);
return 0;
}
EXPORT_SYMBOL(nss_ovpnmgr_tun_del);
/*
* nss_ovpnmgr_tun_add()
* Add new tunnel.
*/
uint32_t nss_ovpnmgr_tun_add(struct net_device *app_dev,
struct nss_ovpnmgr_tun_tuple *tun_hdr,
struct nss_ovpnmgr_tun_config *tun_cfg,
struct nss_ovpnmgr_crypto_config *crypto_cfg)
{
struct nss_ovpnmgr_app *app;
struct nss_ovpnmgr_tun *tun;
struct net_device *tun_dev;
int status;
nss_ovpnmgr_tun_config_dump(app_dev, tun_hdr, tun_cfg, crypto_cfg);
/*
* Veryify tunnel configuration parameters.
*/
status = nss_ovpnmgr_tun_config_verify(tun_hdr, tun_cfg);
if (status < 0) {
nss_ovpnmgr_warn("%px: Tunnel configuration parameters are invalid, status=%d.\n", app_dev, status);
return 0;
}
read_lock_bh(&ovpnmgr_ctx.lock);
app = nss_ovpnmgr_app_find(app_dev);
if (!app) {
read_unlock_bh(&ovpnmgr_ctx.lock);
nss_ovpnmgr_warn("%px: Couldn't find app for app_dev=%s\n", app_dev, app_dev->name);
return 0;
}
/*
* Hold the device until we add the tunnel.
*/
dev_hold(app_dev);
read_unlock_bh(&ovpnmgr_ctx.lock);
tun = nss_ovpnmgr_tun_alloc();
if (!tun) {
nss_ovpnmgr_warn("%px: Tunnel allocation failed.\n", app);
dev_put(app_dev);
return 0;
}
tun_dev = tun->dev;
/*
* Copy tunnel header parameters.
*/
memcpy(&tun->tun_hdr, tun_hdr, sizeof(*tun_hdr));
tun->tun_cfg.flags = tun_cfg->flags;
/*
* Initialize tunnel inner context.
*/
if (nss_ovpnmgr_tun_ctx_inner_init(tun, crypto_cfg)) {
nss_ovpnmgr_error("%px: Failed to initialize inner context.\n", app);
goto free_tun;
}
/*
* Initialize tunnel outer context.
*/
if (nss_ovpnmgr_tun_ctx_outer_init(tun, crypto_cfg)) {
nss_ovpnmgr_error("%px: Failed to initialize outer context.\n", app);
goto free_inner;
}
/*
* Configure inner node.
*/
if (nss_ovpnmgr_tun_inner_config(tun)) {
nss_ovpnmgr_warn("%px: inner node tunnel configuration failed\n", tun);
goto free_outer;
}
/*
* Configure outer node.
*/
if (nss_ovpnmgr_tun_outer_config(tun)) {
nss_ovpnmgr_warn("%px: Outer node tunnel configuration failed\n", tun);
goto free_inner_config;
}
/*
* Register netdev.
*/
status = rtnl_is_locked() ? register_netdevice(tun_dev) : register_netdev(tun_dev);
if (status < 0) {
nss_ovpnmgr_warn("register net dev failed :%s\n", tun_dev->name);
goto free_outer_config;
}
/*
* Assign ifindex as tunnel_id as it is unique.
*/
tun->tunnel_id = tun_dev->ifindex;
tun->app = app;
write_lock_bh(&ovpnmgr_ctx.lock);
list_add(&tun->list, &app->tun_list);
write_unlock_bh(&ovpnmgr_ctx.lock);
dev_put(app_dev);
return tun->tunnel_id;
free_outer_config:
nss_ovpnmgr_tun_deconfig(&tun->outer, tun);
free_inner_config:
nss_ovpnmgr_tun_deconfig(&tun->inner, tun);
free_outer:
nss_ovpnmgr_tun_ctx_deinit(&tun->outer);
free_inner:
nss_ovpnmgr_tun_ctx_deinit(&tun->inner);
free_tun:
free_netdev(tun_dev);
dev_put(app_dev);
return 0;
}
EXPORT_SYMBOL(nss_ovpnmgr_tun_add);