blob: afecd2f775ecb668593364b41d599fa6dbc9a0db [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2016-2018, 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 TORTUOUS 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/of.h>
#include <linux/ipv6.h>
#include <linux/skbuff.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <net/route.h>
#include <net/ip6_route.h>
#include <asm/atomic.h>
#include <linux/debugfs.h>
#include <linux/vmalloc.h>
#include <nss_api_if.h>
#include <nss_ipsec.h>
#include <nss_ipsecmgr.h>
#if defined(NSS_IPSECMGR_IPQ807X_SUPPORT)
#include <nss_crypto_api.h>
#else
#include <nss_crypto_if.h>
#endif
#include "nss_ipsecmgr_priv.h"
extern struct nss_ipsecmgr_drv *ipsecmgr_ctx;
/*
* SA operation info
*/
struct nss_ipsecmgr_sa_info {
struct nss_ipsec_msg nim;
struct nss_ipsecmgr_key sa_key;
struct nss_ipsecmgr_key child_key;
struct nss_ipsecmgr_sa *sa;
uint32_t fail_hash_thresh;
uint32_t sa_overhead;
uint32_t dst_mtu;
struct nss_ipsecmgr_ref * (*child_alloc)(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_key *key);
struct nss_ipsecmgr_ref * (*child_lookup)(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_key *key);
};
/*
* nss_ipsecmgr_sa_dump()
* dump sa statistics
*/
static ssize_t nss_ipsecmgr_sa_dump(struct nss_ipsecmgr_sa_entry *sa, char *buf, ssize_t max_len)
{
struct nss_ipsec_rule_data *data;
struct nss_ipsec_rule_oip *oip;
uint32_t addr[4];
ssize_t len;
char *type;
oip = &sa->nim.msg.rule.oip;
data = &sa->nim.msg.rule.data;
switch (sa->nim.type) {
case NSS_IPSEC_TYPE_ENCAP:
type = "encap";
break;
case NSS_IPSEC_TYPE_DECAP:
type = "decap";
break;
default:
return 0;
}
len = snprintf(buf, max_len, "Type:%s\n", type);
switch (oip->ip_ver) {
case IPVERSION:
len += snprintf(buf + len, max_len - len, "dst_ip: %pI4h\n", &oip->dst_addr[0]);
len += snprintf(buf + len, max_len - len, "src_ip: %pI4h\n", &oip->src_addr[0]);
break;
case 6:
len += snprintf(buf + len, max_len - len, "dst_ip: %pI6c\n", nss_ipsecmgr_v6addr_ntoh(oip->dst_addr, addr));
len += snprintf(buf + len, max_len - len, "src_ip: %pI6c\n", nss_ipsecmgr_v6addr_ntoh(oip->src_addr, addr));
break;
}
len += snprintf(buf + len, max_len - len, "spi_idx: 0x%x\n", oip->esp_spi);
len += snprintf(buf + len, max_len - len, "ttl: %d\n", oip->ttl_hop_limit);
len += snprintf(buf + len, max_len - len, "crypto session: %d\n", data->crypto_index);
len += snprintf(buf + len, max_len - len, "ESN: %d\n", data->enable_esn);
len += snprintf(buf + len, max_len - len, "seq_num: %llx\n\n", sa->pkts.seq_num);
/*
* display window information only for decap SA
*/
len += snprintf(buf + len, max_len - len, "win_size: %d\n", sa->pkts.window_size);
len += snprintf(buf + len, max_len - len, "wmax: 0x%llx\n\n", sa->pkts.window_max);
/*
* packet stats
*/
len += snprintf(buf + len, max_len - len, "processed: %llu\n", sa->pkts.count);
len += snprintf(buf + len, max_len - len, "no_headroom: %d\n", sa->pkts.no_headroom);
len += snprintf(buf + len, max_len - len, "no_tailroom: %d\n", sa->pkts.no_tailroom);
len += snprintf(buf + len, max_len - len, "no_buf: %d\n", sa->pkts.no_buf);
len += snprintf(buf + len, max_len - len, "fail_queue: %d\n", sa->pkts.fail_queue);
len += snprintf(buf + len, max_len - len, "fail_hash: %d\n", sa->pkts.fail_hash);
len += snprintf(buf + len, max_len - len, "fail_replay: %d\n\n\n", sa->pkts.fail_replay);
return len;
}
/*
* nss_ipsecmgr_sa_stats_read()
* read sa statistics
*/
ssize_t nss_ipsecmgr_sa_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos)
{
struct nss_ipsecmgr_sa_db *sa_db;
struct nss_ipsecmgr_sa_entry *sa;
ssize_t len, max_len, ret;
struct list_head *head;
uint32_t num_entries;
char *buf;
int i;
sa_db = &ipsecmgr_ctx->sa_db;
num_entries = atomic_read(&sa_db->num_entries);
if (!num_entries) {
return 0;
}
len = 0;
max_len = num_entries * NSS_IPSECMGR_SA_STATS_SIZE;
buf = vzalloc(max_len);
if (!buf) {
nss_ipsecmgr_error("unable to allocate local buffer for SA stats\n");
return 0;
}
/*
* walk the SA database for each entry and retrieve the stats
*/
read_lock_bh(&ipsecmgr_ctx->lock);
head = sa_db->entries;
for (i = NSS_IPSECMGR_MAX_SA; ((max_len - len) > 0) && i--; head++) {
list_for_each_entry(sa, head, node) {
if (unlikely((max_len - len) <= 0)) {
break;
}
len += nss_ipsecmgr_sa_dump(sa, buf + len, max_len - len);
}
}
read_unlock_bh(&ipsecmgr_ctx->lock);
ret = simple_read_from_buffer(ubuf, sz, ppos, buf, len);
vfree(buf);
return ret;
}
/*
* nss_ipsecmgr_sa_free()
* deallocate the SA if there are no references
*/
static void nss_ipsecmgr_sa_free(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_ref *ref)
{
struct nss_ipsecmgr_sa_entry *entry = container_of(ref, struct nss_ipsecmgr_sa_entry, ref);
struct nss_ipsecmgr_sa_db *db = &ipsecmgr_ctx->sa_db;
if (!nss_ipsecmgr_ref_is_empty(ref)) {
return;
}
/*
* there should be no references remove it from
* the sa_db and free the entry
*/
list_del_init(&entry->node);
atomic_dec(&db->num_entries);
kfree(entry);
}
/*
* nss_ipsecmgr_sa_get_overhead()
* get overhead from SA
*/
static uint32_t nss_ipsecmgr_sa_overhead(struct nss_ipsecmgr_ref *ref)
{
struct nss_ipsecmgr_sa_entry *entry;
entry = container_of(ref, struct nss_ipsecmgr_sa_entry, ref);
return entry->sa_overhead;
}
/*
* nss_ipsecmgr_sa_del()
* delete sa/child from the reference chain
*/
static bool nss_ipsecmgr_sa_del(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_sa_info *info)
{
struct nss_ipsecmgr_ref *sa_ref, *child_ref;
/*
* lock database
*/
write_lock_bh(&ipsecmgr_ctx->lock);
/*
* search the flow for deletion
*/
child_ref = info->child_lookup(priv, &info->child_key);
if (!child_ref) {
/*
* unlock device
*/
write_unlock_bh(&ipsecmgr_ctx->lock);
nss_ipsecmgr_warn("%px:failed to lookup child_entry\n", priv->dev);
nss_ipsecmgr_trace("%px:child_lookup(%px)\n", priv, info->child_lookup);
return false;
}
/*
* search the SA in sa_db
*/
sa_ref = nss_ipsecmgr_sa_lookup(&info->sa_key);
if (!sa_ref) {
write_unlock_bh(&ipsecmgr_ctx->lock);
nss_ipsecmgr_warn("%px:failed to lookup sa_entry\n", priv->dev);
return false;
}
/*
* Remove the reference if it is associated the SA
*/
if (nss_ipsecmgr_ref_is_child(child_ref, sa_ref)) {
nss_ipsecmgr_ref_free(priv, child_ref);
}
/*
* This deallocates the SA if there are no further references
*/
nss_ipsecmgr_sa_free(priv, sa_ref);
write_unlock_bh(&ipsecmgr_ctx->lock);
return true;
}
/*
* nss_ipsecmgr_sa_add()
* add sa/child from the reference chain
*/
static bool nss_ipsecmgr_sa_add(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_sa_info *info)
{
struct nss_ipsecmgr_ref *sa_ref, *child_ref;
struct nss_ipsecmgr_sa_entry *sa;
BUG_ON(!info->child_alloc);
BUG_ON(!info->child_lookup);
/*
* lock database
*/
write_lock_bh(&ipsecmgr_ctx->lock);
/*
* allocate a flow, this returns either a new flow or an existing
* one incase it is found
*/
child_ref = info->child_alloc(priv, &info->child_key);
if (!child_ref) {
/*
* unlock device
*/
write_unlock_bh(&ipsecmgr_ctx->lock);
nss_ipsecmgr_warn("%px:failed to alloc child_entry\n", priv->dev);
nss_ipsecmgr_trace("%px:child_alloc(%px)\n", priv, info->child_alloc);
return false;
}
/*
* allocate a SA, when flow alloc is successful. This returns either
* new SA or an existing one incase it is found
*/
sa_ref = nss_ipsecmgr_sa_alloc(priv, &info->sa_key);
if (!sa_ref) {
/*
* release the flow and unlock device
*/
nss_ipsecmgr_ref_free(priv, child_ref);
write_unlock_bh(&ipsecmgr_ctx->lock);
nss_ipsecmgr_warn("%px:failed to alloc sa_entry\n", priv->dev);
return false;
}
/*
* we are only interested the storing the SA portion of the message
*/
sa = container_of(sa_ref, struct nss_ipsecmgr_sa_entry, ref);
sa->ifnum = info->nim.cm.interface;
sa->fail_hash_thresh = info->fail_hash_thresh;
sa->sa_overhead = info->sa_overhead;
/*
* Set outer dst entry when overhead is non zero
*/
if (info->sa_overhead)
atomic_set(&priv->outer_dst_mtu, info->dst_mtu);
memcpy(&sa->nim, &info->nim, sizeof(struct nss_ipsec_msg));
memset(&sa->nim.tuple, 0, sizeof(struct nss_ipsec_tuple));
/*
* Store the SA information to update user for stats reporting
*/
memcpy(&sa->sa_info, info->sa, sizeof(struct nss_ipsecmgr_sa));
/*
* clear the stats information
*/
memset(&sa->pkts, 0, sizeof(struct nss_ipsecmgr_sa_pkt_stats));
/*
* add child to parent
*/
nss_ipsecmgr_ref_add(child_ref, sa_ref);
/*
* Trigger the notification chain for the child
* Note: if there is change in any data then the trigger
* will update the NSS for the change
*/
nss_ipsecmgr_ref_update(priv, child_ref, &info->nim);
write_unlock_bh(&ipsecmgr_ctx->lock);
return true;
}
/*
* nss_ipsecmgr_sa_alloc()
* allocate the SA if there is none in the DB
*/
struct nss_ipsecmgr_ref *nss_ipsecmgr_sa_alloc(struct nss_ipsecmgr_priv *priv, struct nss_ipsecmgr_key *key)
{
struct nss_ipsecmgr_sa_entry *sa;
struct nss_ipsecmgr_sa_db *db;
struct nss_ipsecmgr_ref *ref;
int idx;
/*
* Search the object in the database first
*/
ref = nss_ipsecmgr_sa_lookup(key);
if (ref) {
return ref;
}
/*
* Object doesn't exist, allocate it
*/
sa = kzalloc(sizeof(struct nss_ipsecmgr_sa_entry), GFP_ATOMIC);
if (!sa) {
nss_ipsecmgr_info("failed to alloc sa_entry\n");
return NULL;
}
/*
* store tunnel private reference
*/
sa->priv = priv;
/*
* initialize sa list node
*/
ref = &sa->ref;
db = &ipsecmgr_ctx->sa_db;
INIT_LIST_HEAD(&sa->node);
/*
* update key
*/
idx = nss_ipsecmgr_key_data2idx(key, NSS_CRYPTO_MAX_IDXS);
memcpy(&sa->key, key, sizeof(struct nss_ipsecmgr_key));
list_add(&sa->node, &db->entries[idx]);
atomic_inc(&db->num_entries);
/*
* initiallize the reference object
*/
nss_ipsecmgr_ref_init(&sa->ref, NULL, nss_ipsecmgr_sa_free);
nss_ipsecmgr_ref_set_overhead(&sa->ref, nss_ipsecmgr_sa_overhead);
return ref;
}
/*
* nss_ipsecmgr_sa_copy()
* update the SA entry with the SA data
*/
void nss_ipsecmgr_copy_v4_sa(struct nss_ipsec_msg *nim, struct nss_ipsecmgr_sa_v4 *sa)
{
struct nss_ipsec_rule_oip *oip = &nim->msg.rule.oip;
oip->dst_addr[0] = sa->dst_ip;
oip->src_addr[0] = sa->src_ip;
oip->ttl_hop_limit = sa->ttl;
oip->esp_spi = sa->spi_index;
oip->ip_ver = IPVERSION;
oip->dst_port = 0;
oip->src_port = 0;
oip->proto_next_hdr = 0;
}
/*
* nss_ipsecmgr_copy_v6_sa()
* update the SA entry with the SA data
*/
void nss_ipsecmgr_copy_v6_sa(struct nss_ipsec_msg *nim, struct nss_ipsecmgr_sa_v6 *sa)
{
struct nss_ipsec_rule_oip *oip = &nim->msg.rule.oip;
/*
* copy outer header
*/
memcpy(oip->dst_addr, sa->dst_ip, sizeof(uint32_t) * 4);
memcpy(oip->src_addr, sa->src_ip, sizeof(uint32_t) * 4);
oip->esp_spi = sa->spi_index;
oip->ttl_hop_limit = sa->hop_limit;
oip->ip_ver = 6;
oip->dst_port = 0;
oip->src_port = 0;
oip->proto_next_hdr = 0;
}
/*
* nss_ipsecmgr_sa_copy()
* update the SA entry with the SA data
*/
void nss_ipsecmgr_copy_sa_data(struct nss_ipsec_msg *nim, struct nss_ipsecmgr_sa_data *sa_data)
{
struct nss_ipsec_rule_data *data = &nim->msg.rule.data;
data->crypto_index = (uint16_t)sa_data->crypto_index;
data->window_size = sa_data->esp.replay_win;
data->enable_esn = sa_data->enable_esn;
data->nat_t_req = sa_data->esp.nat_t_req;
data->cipher_blk_len = nss_crypto_get_cipher_block_len(data->crypto_index);
data->iv_len = nss_crypto_get_iv_len(data->crypto_index);
data->esp_icv_len = sa_data->esp.icv_len;
data->esp_seq_skip = sa_data->esp.seq_skip;
data->esp_tail_skip = sa_data->esp.trailer_skip;
data->use_pattern = sa_data->use_pattern;
data->dscp = sa_data->esp.dscp;
data->df = !!sa_data->esp.df;
data->copy_dscp = !!sa_data->esp.dscp_copy;
data->copy_df = !!sa_data->esp.df_copy;
}
/*
* nss_ipsecmgr_v4_sa2key()
* convert a SA into a key
*/
void nss_ipsecmgr_v4_sa2key(struct nss_ipsecmgr_sa_v4 *sa, struct nss_ipsecmgr_key *key)
{
nss_ipsecmgr_key_reset(key);
nss_ipsecmgr_key_write_8(key, 4 /* v4 */, NSS_IPSECMGR_KEY_POS_IP_VER);
nss_ipsecmgr_key_write_8(key, IPPROTO_ESP, NSS_IPSECMGR_KEY_POS_IP_PROTO);
nss_ipsecmgr_key_write_32(key, sa->dst_ip, NSS_IPSECMGR_KEY_POS_IPV4_DST);
nss_ipsecmgr_key_write_32(key, sa->src_ip, NSS_IPSECMGR_KEY_POS_IPV4_SRC);
nss_ipsecmgr_key_write_32(key, sa->spi_index, NSS_IPSECMGR_KEY_POS_IPV4_ESP_SPI);
key->len = NSS_IPSECMGR_KEY_LEN_IPV4_SA;
}
/*
* nss_ipsecmgr_sa_tuple2key()
* convert a SA into a key
*/
void nss_ipsecmgr_sa_tuple2key(struct nss_ipsec_tuple *tuple, struct nss_ipsecmgr_key *key)
{
uint32_t i;
nss_ipsecmgr_key_reset(key);
switch (tuple->ip_ver) {
case IPVERSION:
nss_ipsecmgr_key_write_8(key, 4 /* v4 */, NSS_IPSECMGR_KEY_POS_IP_VER);
nss_ipsecmgr_key_write_8(key, IPPROTO_ESP, NSS_IPSECMGR_KEY_POS_IP_PROTO);
nss_ipsecmgr_key_write_32(key, nss_ipsecmgr_get_v4addr(tuple->dst_addr), NSS_IPSECMGR_KEY_POS_IPV4_DST);
nss_ipsecmgr_key_write_32(key, nss_ipsecmgr_get_v4addr(tuple->src_addr), NSS_IPSECMGR_KEY_POS_IPV4_SRC);
nss_ipsecmgr_key_write_32(key, tuple->esp_spi, NSS_IPSECMGR_KEY_POS_IPV4_ESP_SPI);
key->len = NSS_IPSECMGR_KEY_LEN_IPV4_SA;
break;
case 6:
nss_ipsecmgr_key_write_8(key, 6 /* v6 */, NSS_IPSECMGR_KEY_POS_IP_VER);
nss_ipsecmgr_key_write_8(key, IPPROTO_ESP, NSS_IPSECMGR_KEY_POS_IP_PROTO);
nss_ipsecmgr_v6addr_swap(tuple->dst_addr, tuple->dst_addr);
nss_ipsecmgr_v6addr_swap(tuple->src_addr, tuple->src_addr);
for (i = 0; i < 4; i++) {
nss_ipsecmgr_key_write_32(key, tuple->dst_addr[i], NSS_IPSECMGR_KEY_POS_IPV6_DST + (i * 32));
nss_ipsecmgr_key_write_32(key, tuple->src_addr[i], NSS_IPSECMGR_KEY_POS_IPV6_SRC + (i * 32));
}
nss_ipsecmgr_key_write_32(key, tuple->esp_spi, NSS_IPSECMGR_KEY_POS_IPV6_ESP_SPI);
key->len = NSS_IPSECMGR_KEY_LEN_IPV6_SA;
break;
}
}
/*
* nss_ipsecmgr_v6_sa2key()
* convert a SA into a key
*/
void nss_ipsecmgr_v6_sa2key(struct nss_ipsecmgr_sa_v6 *sa, struct nss_ipsecmgr_key *key)
{
uint32_t i;
nss_ipsecmgr_key_reset(key);
nss_ipsecmgr_key_write_8(key, 6 /* v6 */, NSS_IPSECMGR_KEY_POS_IP_VER);
nss_ipsecmgr_key_write_8(key, IPPROTO_ESP, NSS_IPSECMGR_KEY_POS_IP_PROTO);
for (i = 0; i < 4; i++) {
nss_ipsecmgr_key_write_32(key, sa->dst_ip[i], NSS_IPSECMGR_KEY_POS_IPV6_DST + (i * 32));
nss_ipsecmgr_key_write_32(key, sa->src_ip[i], NSS_IPSECMGR_KEY_POS_IPV6_SRC + (i * 32));
}
nss_ipsecmgr_key_write_32(key, sa->spi_index, NSS_IPSECMGR_KEY_POS_IPV6_ESP_SPI);
key->len = NSS_IPSECMGR_KEY_LEN_IPV6_SA;
}
/*
* nss_ipsecmgr_sa_stats_update()
* Update sa stats locally
*/
void nss_ipsecmgr_sa_stats_update(struct nss_ipsec_msg *nim, struct nss_ipsecmgr_sa_entry *sa)
{
struct nss_ipsecmgr_sa_pkt_stats *stats;
struct nss_ipsec_sa_stats *sa_stats;
struct nss_ipsec_sa_stats *pkts;
pkts = &nim->msg.stats.sa;
sa_stats = &nim->msg.stats.sa;
stats = &sa->pkts;
/*
* update SA specific stats
*/
stats->count += pkts->count;
stats->bytes += pkts->bytes;
stats->no_headroom += pkts->no_headroom;
stats->no_tailroom += pkts->no_tailroom;
stats->no_buf += pkts->no_resource;
stats->fail_queue += pkts->fail_queue;
stats->fail_hash += pkts->fail_hash;
stats->fail_replay += pkts->fail_replay;
sa->esn_enabled = sa_stats->esn_enabled;
sa->pkts.seq_num = sa_stats->seq_num;
sa->pkts.window_max = sa_stats->window_max;
sa->pkts.window_size = sa_stats->window_size;
}
/*
* nss_ipsecmgr_sa_lookup()
* lookup the SA in the sa_db
*/
struct nss_ipsecmgr_ref *nss_ipsecmgr_sa_lookup(struct nss_ipsecmgr_key *key)
{
struct nss_ipsecmgr_sa_db *db = &ipsecmgr_ctx->sa_db;
struct nss_ipsecmgr_sa_entry *entry;
struct list_head *head;
int idx;
idx = nss_ipsecmgr_key_data2idx(key, NSS_CRYPTO_MAX_IDXS);
head = &db->entries[idx];
list_for_each_entry(entry, head, node) {
if (nss_ipsecmgr_key_cmp(&entry->key, key)) {
return &entry->ref;
}
}
return NULL;
}
/*
* nss_ipsecmgr_sa_flush_all()
* remove all SA and its corresponding references
*/
void nss_ipsecmgr_sa_flush_all(struct nss_ipsecmgr_priv *priv)
{
struct nss_ipsecmgr_sa_db *sa_db = &ipsecmgr_ctx->sa_db;
struct nss_ipsecmgr_sa_entry *entry;
struct nss_ipsecmgr_sa_entry *tmp;
int ifindex = priv->dev->ifindex;
struct list_head *head;
int i;
/*
* lock database
*/
write_lock_bh(&ipsecmgr_ctx->lock);
/*
* walk the SA database for each entry and delete the attached SA.
* Assumption is that single SA cannot be associated to multiple ipsectunX interfaces.
*/
for (i = 0, head = sa_db->entries; i < NSS_IPSECMGR_MAX_SA; i++, head++) {
list_for_each_entry_safe(entry, tmp, head, node) {
if (entry->nim.tunnel_id == ifindex) {
nss_ipsecmgr_ref_free(priv, &entry->ref);
}
}
}
/*
* unlock database
*/
write_unlock_bh(&ipsecmgr_ctx->lock);
}
/*
* nss_ipsecmgr_encap_add()
* add encap flow/subnet to an existing or new SA
*/
bool nss_ipsecmgr_encap_add(struct net_device *tun, struct nss_ipsecmgr_encap_flow *flow,
struct nss_ipsecmgr_sa *sa, struct nss_ipsecmgr_sa_data *data)
{
struct nss_ipsecmgr_priv *priv = netdev_priv(tun);
struct nss_ipsecmgr_sa_info info = { {{0} } };
struct dst_entry *dst;
struct flowi6 fl6;
nss_ipsecmgr_info("%px:encap_add initiated\n", tun);
info.sa_overhead = sizeof(struct ip_esp_hdr);
info.sa_overhead += NSS_IPSECMGR_ESP_PAD_SZ;
info.sa_overhead += NSS_IPSECMGR_ESP_TRAIL_SZ;
info.sa_overhead += ETH_HLEN;
nss_ipsecmgr_encap_flow_init(&info.nim, NSS_IPSEC_MSG_TYPE_ADD_RULE, priv);
switch (flow->type) {
case NSS_IPSECMGR_FLOW_TYPE_V4_TUPLE:
nss_ipsecmgr_copy_encap_v4_flow(&info.nim, &flow->data.v4_tuple);
nss_ipsecmgr_encap_v4_flow2key(&flow->data.v4_tuple, &info.child_key);
info.child_alloc = nss_ipsecmgr_flow_alloc;
info.child_lookup = nss_ipsecmgr_flow_lookup;
break;
case NSS_IPSECMGR_FLOW_TYPE_V4_SUBNET:
if (nss_ipsecmgr_verify_v4_subnet(&flow->data.v4_subnet)) {
nss_ipsecmgr_warn("%px:invalid subnet and mask\n", tun);
return false;
}
nss_ipsecmgr_v4_subnet2key(&flow->data.v4_subnet, &info.child_key);
info.child_alloc = nss_ipsecmgr_subnet_alloc;
info.child_lookup = nss_ipsecmgr_subnet_lookup;
break;
case NSS_IPSECMGR_FLOW_TYPE_V6_TUPLE:
nss_ipsecmgr_copy_encap_v6_flow(&info.nim, &flow->data.v6_tuple);
nss_ipsecmgr_encap_v6_flow2key(&flow->data.v6_tuple, &info.child_key);
info.child_alloc = nss_ipsecmgr_flow_alloc;
info.child_lookup = nss_ipsecmgr_flow_lookup;
break;
case NSS_IPSECMGR_FLOW_TYPE_V6_SUBNET:
if (nss_ipsecmgr_verify_v6_subnet(&flow->data.v6_subnet)) {
nss_ipsecmgr_warn("%px:invalid subnet and mask\n", tun);
return false;
}
nss_ipsecmgr_v6_subnet2key(&flow->data.v6_subnet, &info.child_key);
info.child_alloc = nss_ipsecmgr_subnet_alloc;
info.child_lookup = nss_ipsecmgr_subnet_lookup;
break;
default:
nss_ipsecmgr_warn("%px:unknown flow type(%d)\n", tun, flow->type);
return false;
}
switch (sa->type) {
case NSS_IPSECMGR_SA_TYPE_V4:
nss_ipsecmgr_copy_v4_sa(&info.nim, &sa->data.v4);
nss_ipsecmgr_copy_sa_data(&info.nim, data);
nss_ipsecmgr_v4_sa2key(&sa->data.v4, &info.sa_key);
info.sa_overhead += sizeof(struct iphdr);
info.sa_overhead += data->esp.nat_t_req ?
sizeof(struct udphdr) : 0;
/*
* Update initial value for outer_dst_mtu
* Further this value will be updated as per the PMTU error
* generated for the Tunnel.
*/
dst = (struct dst_entry *)ip_route_output(&init_net,
htonl(sa->data.v4.dst_ip), 0, 0, 0);
if (IS_ERR(dst)) {
return false;
}
info.dst_mtu = dst_mtu(dst);
dst_release(dst);
break;
case NSS_IPSECMGR_SA_TYPE_V6:
nss_ipsecmgr_copy_v6_sa(&info.nim, &sa->data.v6);
nss_ipsecmgr_copy_sa_data(&info.nim, data);
nss_ipsecmgr_v6_sa2key(&sa->data.v6, &info.sa_key);
info.sa_overhead += sizeof(struct ipv6hdr);
nss_ipsecmgr_v6addr_ntoh((uint32_t *)&sa->data.v6.dst_ip, (uint32_t *)&fl6.daddr);
/*
* Update initial value for outer_dst_mtu
* Further this value will be updated as per the PMTU error
* generated for the Tunnel.
*/
dst = ip6_route_output(&init_net, NULL, &fl6);
if (IS_ERR(dst)) {
return false;
}
info.dst_mtu = dst_mtu(dst);
dst_release(dst);
break;
default:
nss_ipsecmgr_warn("%px:unknown sa type(%d)\n", tun, sa->type);
return false;
}
/*
* Compute additional overhead for this SA
*/
info.sa = sa;
info.sa_overhead += nss_crypto_get_iv_len(data->crypto_index);
info.sa_overhead += data->esp.icv_len;
return nss_ipsecmgr_sa_add(priv, &info);
}
EXPORT_SYMBOL(nss_ipsecmgr_encap_add);
/*
* nss_ipsecmgr_encap_del()
* del encap flow/subnet to an existing SA
*
* Note: if this is the only/last flow or subnet in the SA then
* the SA will be also be deallocated
*/
bool nss_ipsecmgr_encap_del(struct net_device *tun, struct nss_ipsecmgr_encap_flow *flow, struct nss_ipsecmgr_sa *sa)
{
struct nss_ipsecmgr_priv *priv = netdev_priv(tun);
struct nss_ipsecmgr_sa_info info;
nss_ipsecmgr_info("%px:encap_del initiated\n", tun);
memset(&info, 0, sizeof(struct nss_ipsecmgr_sa_info));
nss_ipsecmgr_encap_flow_init(&info.nim, NSS_IPSEC_MSG_TYPE_DEL_RULE, priv);
switch (flow->type) {
case NSS_IPSECMGR_FLOW_TYPE_V4_TUPLE:
nss_ipsecmgr_copy_encap_v4_flow(&info.nim, &flow->data.v4_tuple);
nss_ipsecmgr_copy_v4_sa(&info.nim, &sa->data.v4);
nss_ipsecmgr_encap_v4_flow2key(&flow->data.v4_tuple, &info.child_key);
nss_ipsecmgr_v4_sa2key(&sa->data.v4, &info.sa_key);
info.child_alloc = nss_ipsecmgr_flow_alloc;
info.child_lookup = nss_ipsecmgr_flow_lookup;
break;
case NSS_IPSECMGR_FLOW_TYPE_V4_SUBNET:
if (nss_ipsecmgr_verify_v4_subnet(&flow->data.v4_subnet)) {
nss_ipsecmgr_warn("%px:invalid subnet and mask\n", tun);
return false;
}
nss_ipsecmgr_copy_v4_sa(&info.nim, &sa->data.v4);
nss_ipsecmgr_v4_subnet2key(&flow->data.v4_subnet, &info.child_key);
nss_ipsecmgr_v4_sa2key(&sa->data.v4, &info.sa_key);
info.child_alloc = nss_ipsecmgr_subnet_alloc;
info.child_lookup = nss_ipsecmgr_subnet_lookup;
break;
case NSS_IPSECMGR_FLOW_TYPE_V6_TUPLE:
nss_ipsecmgr_copy_encap_v6_flow(&info.nim, &flow->data.v6_tuple);
nss_ipsecmgr_copy_v6_sa(&info.nim, &sa->data.v6);
nss_ipsecmgr_encap_v6_flow2key(&flow->data.v6_tuple, &info.child_key);
nss_ipsecmgr_v6_sa2key(&sa->data.v6, &info.sa_key);
info.child_alloc = nss_ipsecmgr_flow_alloc;
info.child_lookup = nss_ipsecmgr_flow_lookup;
break;
case NSS_IPSECMGR_FLOW_TYPE_V6_SUBNET:
if (nss_ipsecmgr_verify_v6_subnet(&flow->data.v6_subnet)) {
nss_ipsecmgr_warn("%px:invalid subnet and mask\n", tun);
return false;
}
nss_ipsecmgr_copy_v6_sa(&info.nim, &sa->data.v6);
nss_ipsecmgr_v6_subnet2key(&flow->data.v6_subnet, &info.child_key);
nss_ipsecmgr_v6_sa2key(&sa->data.v6, &info.sa_key);
info.child_alloc = nss_ipsecmgr_subnet_alloc;
info.child_lookup = nss_ipsecmgr_subnet_lookup;
break;
default:
nss_ipsecmgr_warn("%px:unknown flow type(%d)\n", tun, flow->type);
return false;
}
return nss_ipsecmgr_sa_del(priv, &info);
}
EXPORT_SYMBOL(nss_ipsecmgr_encap_del);
/*
* nss_ipsecmgr_decap_add()
* add decap flow/subnet to an existing or new SA
*
* Note: In case of decap rule, sa become flow for lookup into flow table
*/
bool nss_ipsecmgr_decap_add(struct net_device *tun, struct nss_ipsecmgr_sa *sa, struct nss_ipsecmgr_sa_data *data)
{
struct nss_ipsecmgr_priv *priv = netdev_priv(tun);
struct nss_ipsec_rule *ipsec_rule;
struct nss_ipsecmgr_sa_info info;
nss_ipsecmgr_info("%px:decap_add initiated\n", tun);
memset(&info, 0, sizeof(struct nss_ipsecmgr_sa_info));
nss_ipsecmgr_decap_flow_init(&info.nim, NSS_IPSEC_MSG_TYPE_ADD_RULE, priv);
switch (sa->type) {
case NSS_IPSECMGR_SA_TYPE_V4:
nss_ipsecmgr_copy_decap_v4_flow(&info.nim, &sa->data.v4);
nss_ipsecmgr_copy_v4_sa(&info.nim, &sa->data.v4);
nss_ipsecmgr_copy_sa_data(&info.nim, data);
/*
* if NATT is set override the protocol and port numbers
*/
ipsec_rule = &info.nim.msg.rule;
if (ipsec_rule->data.nat_t_req) {
info.nim.tuple.proto_next_hdr = IPPROTO_UDP;
info.nim.tuple.dst_port = NSS_IPSECMGR_NATT_PORT_DATA;
info.nim.tuple.src_port = NSS_IPSECMGR_NATT_PORT_DATA;
}
nss_ipsecmgr_decap_v4_flow2key(&sa->data.v4, &info.child_key);
nss_ipsecmgr_v4_sa2key(&sa->data.v4, &info.sa_key);
break;
case NSS_IPSECMGR_SA_TYPE_V6:
nss_ipsecmgr_copy_decap_v6_flow(&info.nim, &sa->data.v6);
nss_ipsecmgr_copy_v6_sa(&info.nim, &sa->data.v6);
nss_ipsecmgr_copy_sa_data(&info.nim, data);
nss_ipsecmgr_decap_v6_flow2key(&sa->data.v6, &info.child_key);
nss_ipsecmgr_v6_sa2key(&sa->data.v6, &info.sa_key);
break;
default:
nss_ipsecmgr_warn("%px:unknown flow type(%d)\n", tun, sa->type);
return false;
}
/*
* Store the fail_hash_threshold in the info
*/
info.sa = sa;
info.sa_overhead = 0;
info.fail_hash_thresh = data->fail_hash_thresh;
info.child_alloc = nss_ipsecmgr_flow_alloc;
info.child_lookup = nss_ipsecmgr_flow_lookup;
return nss_ipsecmgr_sa_add(priv, &info);
}
EXPORT_SYMBOL(nss_ipsecmgr_decap_add);
/*
* nss_ipsecmgr_sa_flush()
* flush sa and all associated references.
*/
bool nss_ipsecmgr_sa_flush(struct net_device *tun, struct nss_ipsecmgr_sa *sa)
{
struct nss_ipsecmgr_priv *priv = netdev_priv(tun);
struct nss_ipsecmgr_key sa_key;
struct nss_ipsecmgr_ref *sa_ref;
switch (sa->type) {
case NSS_IPSECMGR_SA_TYPE_V4:
nss_ipsecmgr_v4_sa2key(&sa->data.v4, &sa_key);
break;
case NSS_IPSECMGR_SA_TYPE_V6:
nss_ipsecmgr_v6_sa2key(&sa->data.v6, &sa_key);
break;
default:
nss_ipsecmgr_warn("%px:Unsupported sa type (type - %d)\n", tun, sa->type);
return false;
}
/*
* lock database
*/
write_lock_bh(&ipsecmgr_ctx->lock);
/*
* search the SA in sa_db
*/
sa_ref = nss_ipsecmgr_sa_lookup(&sa_key);
if (!sa_ref) {
write_unlock_bh(&ipsecmgr_ctx->lock);
nss_ipsecmgr_warn("%px:failed to lookup SA\n", priv);
return false;
}
/*
* remove the reference from its associated SA
*/
nss_ipsecmgr_ref_free(priv, sa_ref);
/*
* unlock database
*/
write_unlock_bh(&ipsecmgr_ctx->lock);
return true;
}
EXPORT_SYMBOL(nss_ipsecmgr_sa_flush);