| /* |
| ************************************************************************** |
| * Copyright (c) 2016-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 TORTUOUS ACTION, ARISING OUT |
| * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| ************************************************************************** |
| */ |
| #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/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 <crypto/algapi.h> |
| #include <crypto/aead.h> |
| #include <crypto/internal/hash.h> |
| #include <crypto/authenc.h> |
| #include <crypto/skcipher.h> |
| |
| #include <nss_api_if.h> |
| #include <nss_ipsec_cmn.h> |
| #include <nss_ipsecmgr.h> |
| #include <nss_cryptoapi.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" |
| |
| #define NSS_IPSECMGR_DEBUGFS_NAME_SZ 128 /* bytes */ |
| |
| extern struct nss_ipsecmgr_drv *ipsecmgr_drv; |
| |
| /* |
| * Linux crypto algorithm names. |
| */ |
| static const char *ipsecmgr_algo_name[NSS_IPSECMGR_ALGO_MAX] = { |
| "echainiv(authenc(hmac(sha1),cbc(aes)))", |
| "echainiv(authenc(hmac(sha256),cbc(aes)))", |
| "echainiv(authenc(hmac(sha1),cbc(des3_ede)))", |
| "echainiv(authenc(hmac(sha256),cbc(des3_ede)))", |
| "hmac(sha1)", |
| "hmac(sha256)", |
| "seqiv(rfc4106(gcm(aes)))", |
| "echainiv(authenc(hmac(md5),cbc(aes)))", |
| "echainiv(authenc(hmac(md5),cbc(des3_ede)))", |
| "echainiv(authenc(hmac(sha384),cbc(aes)))", |
| "echainiv(authenc(hmac(sha512),cbc(aes)))", |
| }; |
| |
| /* |
| * SA tuple print info |
| */ |
| static const struct nss_ipsecmgr_print ipsecmgr_print_sa_tuple[] = { |
| {"dest_ip", NSS_IPSECMGR_PRINT_IPADDR}, |
| {"dest_port", NSS_IPSECMGR_PRINT_WORD}, |
| {"src_ip", NSS_IPSECMGR_PRINT_IPADDR}, |
| {"src_port", NSS_IPSECMGR_PRINT_WORD}, |
| {"spi", NSS_IPSECMGR_PRINT_WORD}, |
| {"protocol", NSS_IPSECMGR_PRINT_WORD}, |
| {"ip_version", NSS_IPSECMGR_PRINT_WORD}, |
| }; |
| |
| /* |
| * SA replay print info |
| */ |
| static const struct nss_ipsecmgr_print ipsecmgr_print_sa_replay[] = { |
| {"replay_win_start", NSS_IPSECMGR_PRINT_DWORD}, |
| {"replay_win_current", NSS_IPSECMGR_PRINT_DWORD}, |
| {"replay_win_size", NSS_IPSECMGR_PRINT_WORD}, |
| }; |
| |
| /* |
| * SA tx default print info |
| */ |
| static const struct nss_ipsecmgr_print ipsecmgr_print_sa_feature[] = { |
| {"tx_default", NSS_IPSECMGR_PRINT_BYTE}, |
| {"flags", NSS_IPSECMGR_PRINT_WORD}, |
| }; |
| |
| /* |
| * SA statistics print info |
| */ |
| static const struct nss_ipsecmgr_print ipsecmgr_print_sa_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}, |
| {"\tfail_headroom", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_tailroom", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_replay", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_replay_dup", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_replay_win", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_pbuf_crypto", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_queue", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_queue_crypto", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_queue_nexthop", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_pbuf_alloc", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_pbuf_linear", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_pbuf_stats", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_pbuf_align", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_cipher", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_auth", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_seq_ovf", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_blk_len", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_hash_len", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_transform", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_crypto", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tfail_classification", NSS_IPSECMGR_PRINT_DWORD}, |
| {"\tis_stopped", NSS_IPSECMGR_PRINT_DWORD}, |
| }; |
| |
| /* |
| * nss_ipsecmgr_sa_tuple_print() |
| * Print SA tuple |
| */ |
| static ssize_t nss_ipsecmgr_sa_tuple_print(struct nss_ipsec_cmn_sa_tuple *tuple, char *buf, ssize_t max_len) |
| { |
| const struct nss_ipsecmgr_print *prn = ipsecmgr_print_sa_tuple; |
| uint32_t dest_ip[4], src_ip[4]; |
| ssize_t len = 0; |
| |
| len += snprintf(buf + len, max_len - len, "SA tuple: {"); |
| switch (tuple->ip_ver) { |
| case IPVERSION: |
| len += snprintf(buf + len, max_len - len, "%s: %pI4h,", prn->str, tuple->dest_ip); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->dest_port); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %pI4h,", prn->str, tuple->src_ip); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->src_port); |
| prn++; |
| |
| break; |
| |
| case 6: |
| nss_ipsecmgr_hton_v6addr(src_ip, tuple->src_ip); |
| nss_ipsecmgr_hton_v6addr(dest_ip, tuple->dest_ip); |
| |
| len += snprintf(buf + len, max_len - len, "%s: %pI6c,", prn->str, dest_ip); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->dest_port); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %pI6c,", prn->str, src_ip); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->src_port); |
| prn++; |
| |
| break; |
| } |
| |
| len += snprintf(buf + len, max_len - len, "%s: 0x%x,", prn->str, tuple->spi_index); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->protocol); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %u", prn->str, tuple->ip_ver); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "}\n"); |
| return len; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_feature_print() |
| * Print SA tx default value |
| */ |
| static ssize_t nss_ipsecmgr_sa_feature_print(struct nss_ipsecmgr_sa *sa, char *buf, ssize_t max_len) |
| { |
| const struct nss_ipsecmgr_print *prn = ipsecmgr_print_sa_feature; |
| ssize_t len = 0; |
| |
| len += snprintf(buf + len, max_len - len, "SA feature: {"); |
| len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, sa->state.tx_default); |
| prn++; |
| len += snprintf(buf + len, max_len - len, "%s: 0x%x", prn->str, sa->state.data.flags); |
| len += snprintf(buf + len, max_len - len, "}\n"); |
| |
| return len; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_replay_print() |
| * Print SA replay state |
| */ |
| static ssize_t nss_ipsecmgr_sa_replay_print(struct nss_ipsec_cmn_sa_replay *replay, char *buf, ssize_t max_len) |
| { |
| const struct nss_ipsecmgr_print *prn = ipsecmgr_print_sa_replay; |
| ssize_t len = 0; |
| |
| len += snprintf(buf + len, max_len - len, "SA replay: {"); |
| |
| len += snprintf(buf + len, max_len - len, "%s: %llu,", prn->str, replay->seq_start); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %llu,", prn->str, replay->seq_cur); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "%s: %u", prn->str, replay->window_size); |
| prn++; |
| |
| len += snprintf(buf + len, max_len - len, "}\n"); |
| return len; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_stats_print() |
| * Print SA statistics |
| */ |
| static ssize_t nss_ipsecmgr_sa_stats_print(struct nss_ipsecmgr_sa_stats_priv *stats, char *buf, ssize_t max_len) |
| { |
| const struct nss_ipsecmgr_print *prn = ipsecmgr_print_sa_stats; |
| uint64_t *stats_word = (uint64_t *)stats; |
| ssize_t len = 0; |
| int i; |
| |
| /* |
| * This expects a strict order as per the stats structure |
| */ |
| len += snprintf(buf + len, max_len - len, "SA stats: {\n"); |
| |
| for (i = 0; i < ARRAY_SIZE(ipsecmgr_print_sa_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_sa_print_len() |
| * Return the total length for printing |
| */ |
| static ssize_t nss_ipsecmgr_sa_print_len(struct nss_ipsecmgr_ref *ref) |
| { |
| ssize_t len = NSS_IPSECMGR_SA_PRINT_EXTRA; |
| const struct nss_ipsecmgr_print *prn; |
| int i; |
| |
| for (i = 0, prn = ipsecmgr_print_sa_tuple; i < ARRAY_SIZE(ipsecmgr_print_sa_tuple); i++, prn++) |
| len += strlen(prn->str) + prn->var_size; |
| |
| for (i = 0, prn = ipsecmgr_print_sa_replay; i < ARRAY_SIZE(ipsecmgr_print_sa_replay); i++, prn++) |
| len += strlen(prn->str) + prn->var_size; |
| |
| for (i = 0, prn = ipsecmgr_print_sa_stats; i < ARRAY_SIZE(ipsecmgr_print_sa_stats); i++, prn++) |
| len += strlen(prn->str) + prn->var_size; |
| |
| for (i = 0, prn = ipsecmgr_print_sa_feature; i < ARRAY_SIZE(ipsecmgr_print_sa_feature); i++, prn++) |
| len += strlen(prn->str) + prn->var_size; |
| |
| return len; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_print() |
| * Print SA info |
| */ |
| static ssize_t nss_ipsecmgr_sa_print(struct nss_ipsecmgr_ref *ref, char *buf) |
| { |
| struct nss_ipsecmgr_sa *sa = container_of(ref, struct nss_ipsecmgr_sa, ref); |
| ssize_t max_len = nss_ipsecmgr_sa_print_len(ref); |
| ssize_t len; |
| |
| len = snprintf(buf, max_len, "---- SA(0x%x) -----\n", sa->state.tuple.spi_index); |
| |
| len += nss_ipsecmgr_sa_tuple_print(&sa->state.tuple, buf + len, max_len - len); |
| len += nss_ipsecmgr_sa_replay_print(&sa->state.replay, buf + len, max_len - len); |
| len += nss_ipsecmgr_sa_feature_print(sa, buf + len, max_len - len); |
| len += nss_ipsecmgr_sa_stats_print(&sa->stats, buf + len, max_len - len); |
| |
| return len; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_crypto_alloc() |
| * Allocate Crypto resources |
| */ |
| static nss_ipsecmgr_status_t nss_ipsecmgr_sa_crypto_alloc(struct nss_ipsecmgr_sa *sa, |
| struct nss_ipsecmgr_sa_cmn *cmn, |
| struct nss_ipsec_cmn_sa_tuple *tuple, |
| struct nss_ipsec_cmn_sa_data *data) |
| { |
| struct nss_ipsecmgr_crypto_keys *keys = &cmn->keys; |
| struct crypto_authenc_key_param *key_param; |
| struct rtattr *rta; |
| char *rt_keys, *p; |
| uint32_t index; |
| uint16_t keylen; |
| |
| /* |
| * If, crypto is programmed using index(s) skip key-based programming |
| */ |
| if (!cmn->crypto_has_keys) { |
| sa->aead = NULL; |
| data->blk_len = cmn->index.blk_len; |
| data->iv_len = cmn->index.iv_len; |
| data->icv_len = cmn->icv_len; |
| tuple->crypto_index = cmn->index.session; |
| return NSS_IPSECMGR_OK; |
| } |
| |
| switch (cmn->algo) { |
| /* |
| * AEAD Algorithms |
| */ |
| case NSS_IPSECMGR_ALGO_AES_CBC_MD5_HMAC: |
| case NSS_IPSECMGR_ALGO_AES_CBC_SHA1_HMAC: |
| case NSS_IPSECMGR_ALGO_AES_CBC_SHA256_HMAC: |
| case NSS_IPSECMGR_ALGO_3DES_CBC_MD5_HMAC: |
| case NSS_IPSECMGR_ALGO_3DES_CBC_SHA1_HMAC: |
| case NSS_IPSECMGR_ALGO_3DES_CBC_SHA256_HMAC: |
| case NSS_IPSECMGR_ALGO_AES_CBC_SHA384_HMAC: |
| case NSS_IPSECMGR_ALGO_AES_CBC_SHA512_HMAC: |
| |
| sa->aead = crypto_alloc_aead(ipsecmgr_algo_name[cmn->algo], 0, 0); |
| if (IS_ERR(sa->aead)) { |
| nss_ipsecmgr_warn("%px: failed to allocate crypto aead context for algo=%s\n", sa, |
| ipsecmgr_algo_name[cmn->algo]); |
| return NSS_IPSECMGR_FAIL_NOCRYPTO; |
| } |
| |
| nss_ipsecmgr_trace("cipher_keylen:%d auth_keylen:%d\n", keys->cipher_keylen, keys->auth_keylen); |
| |
| /* |
| * Construct keys |
| */ |
| keylen = RTA_SPACE(sizeof(*key_param)); |
| keylen += keys->cipher_keylen; |
| keylen += keys->auth_keylen; |
| keylen += keys->nonce_size; |
| |
| rt_keys = vzalloc(keylen); |
| if (!rt_keys) { |
| nss_ipsecmgr_warn("%px: failed to allocate key memory\n", sa); |
| crypto_free_aead(sa->aead); |
| sa->aead = NULL; |
| return NSS_IPSECMGR_FAIL_NOMEM; |
| } |
| |
| p = rt_keys; |
| rta = (void *)p; |
| rta->rta_type = CRYPTO_AUTHENC_KEYA_PARAM; |
| rta->rta_len = RTA_LENGTH(sizeof(*key_param)); |
| key_param = RTA_DATA(rta); |
| p += RTA_SPACE(sizeof(*key_param)); |
| |
| /* |
| * Copy authentication key |
| */ |
| memcpy(p, keys->auth_key, keys->auth_keylen); |
| p += keys->auth_keylen; |
| |
| /* |
| * Copy cipher Key |
| */ |
| key_param->enckeylen = cpu_to_be32(keys->cipher_keylen); |
| memcpy(p, keys->cipher_key, keys->cipher_keylen); |
| |
| if (crypto_aead_setkey(sa->aead, rt_keys, keylen)) { |
| nss_ipsecmgr_warn("%px: failed to configure keys\n", sa); |
| crypto_free_aead(sa->aead); |
| sa->aead = NULL; |
| vfree(rt_keys); |
| return NSS_IPSECMGR_INVALID_KEYLEN; |
| } |
| |
| nss_cryptoapi_aead_ctx2session(sa->aead, &index); |
| data->blk_len = (uint8_t)crypto_aead_blocksize(sa->aead); |
| data->iv_len = (uint8_t)crypto_aead_ivsize(sa->aead); |
| data->icv_len = cmn->icv_len; |
| tuple->crypto_index = (uint16_t)index; |
| vfree(rt_keys); |
| break; |
| |
| /* |
| * AHASH Algorithms |
| */ |
| case NSS_IPSECMGR_ALGO_NULL_CIPHER_SHA1_HMAC: |
| case NSS_IPSECMGR_ALGO_NULL_CIPHER_SHA256_HMAC: |
| sa->ahash = crypto_alloc_ahash(ipsecmgr_algo_name[cmn->algo], 0, 0); |
| if (IS_ERR(sa->ahash)) { |
| nss_ipsecmgr_warn("%px: failed to allocate crypto ahash context\n", sa); |
| return NSS_IPSECMGR_FAIL_NOCRYPTO; |
| } |
| |
| if (crypto_ahash_setkey(sa->ahash, keys->auth_key, keys->auth_keylen)) { |
| nss_ipsecmgr_warn("%px: failed to configure keys\n", sa); |
| crypto_free_ahash(sa->ahash); |
| sa->ahash = NULL; |
| return NSS_IPSECMGR_INVALID_KEYLEN; |
| } |
| |
| nss_cryptoapi_ahash_ctx2session(sa->ahash, &index); |
| data->flags |= NSS_IPSEC_CMN_FLAG_CIPHER_NULL; |
| data->icv_len = cmn->icv_len; |
| data->blk_len = 0; |
| data->iv_len = 0; |
| tuple->crypto_index = (uint16_t)index; |
| break; |
| |
| /* |
| * GCM Mode |
| */ |
| case NSS_IPSECMGR_ALGO_AES_GCM_GMAC_RFC4106: |
| sa->aead = crypto_alloc_aead(ipsecmgr_algo_name[cmn->algo], 0, 0); |
| if (IS_ERR(sa->aead)) { |
| nss_ipsecmgr_warn("%px: failed to allocate crypto aead context\n", sa); |
| return NSS_IPSECMGR_FAIL_NOCRYPTO; |
| } |
| |
| keylen = keys->cipher_keylen + keys->nonce_size; |
| |
| /* |
| * Construct key with nonce |
| */ |
| rt_keys = vzalloc(keylen); |
| if (!rt_keys) { |
| nss_ipsecmgr_warn("%px: failed to allocate key memory\n", sa); |
| crypto_free_aead(sa->aead); |
| sa->aead = NULL; |
| return NSS_IPSECMGR_FAIL_NOMEM; |
| } |
| |
| memcpy(rt_keys, keys->cipher_key, keys->cipher_keylen); |
| memcpy(rt_keys + keys->cipher_keylen, (uint8_t *)keys->nonce, keys->nonce_size); |
| |
| if (crypto_aead_setkey(sa->aead, rt_keys, keylen)) { |
| nss_ipsecmgr_warn("%px: failed to configure keys\n", sa); |
| crypto_free_aead(sa->aead); |
| sa->aead = NULL; |
| vfree(rt_keys); |
| return NSS_IPSECMGR_INVALID_KEYLEN; |
| } |
| |
| nss_cryptoapi_aead_ctx2session(sa->aead, &index); |
| data->blk_len = (uint8_t)crypto_aead_blocksize(sa->aead); |
| data->iv_len = (uint8_t)crypto_aead_ivsize(sa->aead); |
| data->icv_len = cmn->icv_len; |
| data->flags |= NSS_IPSEC_CMN_FLAG_CIPHER_GCM; |
| tuple->crypto_index = (uint16_t)index; |
| vfree(rt_keys); |
| break; |
| |
| default: |
| nss_ipsecmgr_warn("%px: invalid crypto algorithm\n", sa); |
| return NSS_IPSECMGR_INVALID_ALGO; |
| } |
| |
| return NSS_IPSECMGR_OK; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_free() |
| * Free the SA entry and any associated crypto context |
| */ |
| static void nss_ipsecmgr_sa_free(struct nss_ipsecmgr_sa *sa) |
| { |
| if (sa->aead) { |
| crypto_free_aead(sa->aead); |
| sa->aead = NULL; |
| } |
| |
| if (sa->ahash) { |
| crypto_free_ahash(sa->ahash); |
| sa->ahash = NULL; |
| } |
| |
| kfree(sa); |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_del_ref() |
| * Detach the SA entry from the list |
| */ |
| static void nss_ipsecmgr_sa_del_ref(struct nss_ipsecmgr_ref *ref) |
| { |
| struct nss_ipsecmgr_sa *sa = container_of(ref, struct nss_ipsecmgr_sa, ref); |
| struct nss_ipsecmgr_tunnel *tun = NULL; |
| struct net_device *dev; |
| |
| /* |
| * 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. |
| */ |
| nss_ipsecmgr_write_lock_is_held(&ipsecmgr_drv->lock); |
| |
| list_del_init(&sa->list); |
| |
| dev = dev_get_by_index(&init_net, sa->tunnel_id); |
| if (!dev) { |
| nss_ipsecmgr_trace("%px: Failed to find dev for tunnel-ID(%u)", sa, sa->tunnel_id); |
| return; |
| } |
| |
| /* |
| * Check the default transmit SA; if it matches then we clear it |
| */ |
| tun = netdev_priv(dev); |
| if (tun->tx_sa == sa) { |
| tun->tx_sa = NULL; |
| } |
| |
| dev_put(dev); |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_free_ref() |
| * Detach the SA entry from the list |
| */ |
| static void nss_ipsecmgr_sa_free_ref(struct nss_ipsecmgr_ref *ref) |
| { |
| struct nss_ipsecmgr_sa *sa = container_of(ref, struct nss_ipsecmgr_sa, ref); |
| enum nss_ipsec_cmn_msg_type type = NSS_IPSEC_CMN_MSG_TYPE_SA_DESTROY; |
| struct nss_ipsec_cmn_msg nicm; |
| nss_tx_status_t status; |
| |
| /* |
| * The free path can potentially sleep hence we detach the SA here but |
| * free it later |
| */ |
| memset(&nicm, 0, sizeof(nicm)); |
| memcpy(&nicm.msg.sa.sa_tuple, &sa->state.tuple, sizeof(nicm.msg.sa.sa_tuple)); |
| |
| status = nss_ipsec_cmn_tx_msg_sync(sa->nss_ctx, sa->ifnum, type, sizeof(nicm.msg.sa), &nicm); |
| if (status != NSS_TX_SUCCESS) { |
| nss_ipsecmgr_warn("%px: Failed to send message(%u) to NSS(%u)", sa->nss_ctx, type, status); |
| } |
| |
| nss_ipsecmgr_sa_free(sa); |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_alloc() |
| * Allocate SA and initialize it |
| */ |
| static struct nss_ipsecmgr_sa *nss_ipsecmgr_sa_alloc(struct nss_ipsecmgr_ctx *ctx) |
| { |
| struct nss_ipsecmgr_tunnel *tun = ctx->tun; |
| struct nss_ipsecmgr_sa *sa; |
| |
| /* |
| * Allocate the SA entry |
| */ |
| sa = kzalloc(sizeof(*sa), GFP_ATOMIC); |
| if (!sa) { |
| nss_ipsecmgr_warn("%px: Failed to allocate SA", ctx); |
| return NULL; |
| } |
| |
| sa->tunnel_id = tun->dev->ifindex; |
| sa->type = ctx->state.type; |
| sa->nss_ctx = ctx->nss_ctx; |
| sa->ifnum = ctx->ifnum; |
| sa->cb = tun->cb; |
| |
| INIT_LIST_HEAD(&sa->list); |
| nss_ipsecmgr_ref_init(&sa->ref, nss_ipsecmgr_sa_del_ref, nss_ipsecmgr_sa_free_ref); |
| nss_ipsecmgr_ref_init_print(&sa->ref, nss_ipsecmgr_sa_print_len, nss_ipsecmgr_sa_print); |
| |
| return sa; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_update_db() |
| * Update SA database |
| */ |
| static bool nss_ipsecmgr_sa_update_db(struct nss_ipsecmgr_sa *sa) |
| { |
| struct list_head *db = ipsecmgr_drv->sa_db; |
| struct nss_ipsecmgr_tunnel *tun; |
| struct nss_ipsecmgr_ctx *ctx; |
| struct net_device *dev; |
| uint32_t hash_idx; |
| |
| dev = dev_get_by_index(&init_net, sa->tunnel_id); |
| if (!dev) { |
| nss_ipsecmgr_warn("%px: Failed to find tunnel(%d) between SA creation\n", sa, sa->tunnel_id); |
| return false; |
| } |
| |
| tun = netdev_priv(dev); |
| |
| write_lock_bh(&ipsecmgr_drv->lock); |
| |
| ctx = nss_ipsecmgr_ctx_find(tun, sa->type); |
| if (!ctx) { |
| nss_ipsecmgr_warn("%px: Failed to find context (%u) between SA creation\n", sa, sa->type); |
| write_unlock_bh(&ipsecmgr_drv->lock); |
| dev_put(dev); |
| return false; |
| } |
| |
| /* |
| * Save the SA for default TX on tunnel if enabled |
| */ |
| if (sa->state.tx_default) { |
| tun->tx_sa = sa; |
| } |
| |
| /* |
| * Compute the hash index and add SA reference to the context. |
| */ |
| hash_idx = nss_ipsecmgr_sa_tuple2hash(&sa->state.tuple, NSS_IPSECMGR_SA_MAX); |
| |
| nss_ipsecmgr_ref_add(&sa->ref, &ctx->ref); |
| list_add(&sa->list, &db[hash_idx]); |
| write_unlock_bh(&ipsecmgr_drv->lock); |
| |
| dev_put(dev); |
| return true; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_create_resp() |
| * SA create response callback |
| */ |
| static void nss_ipsecmgr_sa_create_resp(void *app_data, struct nss_cmn_msg *ncm) |
| { |
| struct nss_ipsecmgr_sa *sa = app_data; |
| |
| if (ncm->response != NSS_CMN_RESPONSE_ACK) { |
| #ifdef NSS_IPSECMGR_DEBUG |
| if (ncm->error == NSS_IPSEC_CMN_MSG_ERROR_SA_DUP) { |
| write_lock_bh(&ipsecmgr_drv->lock); |
| if (!nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa->state.tuple)) { |
| nss_ipsecmgr_trace("%px: Duplicate SA in FW not in host (%u)\n", sa, ncm->error); |
| } |
| write_unlock_bh(&ipsecmgr_drv->lock); |
| } |
| #endif |
| nss_ipsecmgr_trace("%px: NSS response error (%u)\n", sa, ncm->error); |
| nss_ipsecmgr_sa_free(sa); |
| return; |
| } |
| |
| if (!nss_ipsecmgr_sa_update_db(sa)) { |
| nss_ipsecmgr_warn("%px: Failed to update SA database", sa); |
| nss_ipsecmgr_sa_free(sa); |
| return; |
| } |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_init_encap() |
| * Initialize encapsulation SA |
| */ |
| static void nss_ipsecmgr_sa_init_encap(struct nss_ipsecmgr_sa *sa, struct nss_ipsecmgr_sa_data *data, |
| struct nss_ipsec_cmn_msg *nicm) |
| { |
| struct nss_ipsec_cmn_sa_tuple *sa_tuple = &nicm->msg.sa.sa_tuple; |
| struct nss_ipsec_cmn_sa_data *sa_data = &nicm->msg.sa.sa_data; |
| |
| sa_data->df = data->encap.df; |
| sa_data->dscp = data->encap.dscp; |
| sa_data->flags |= data->cmn.enable_natt ? NSS_IPSEC_CMN_FLAG_IPV4_NATT : 0; |
| sa_data->flags |= data->cmn.enable_esn ? NSS_IPSEC_CMN_FLAG_ESP_ESN : 0; |
| sa_data->flags |= data->cmn.skip_trailer ? NSS_IPSEC_CMN_FLAG_ESP_SKIP : 0; |
| sa_data->flags |= data->encap.copy_dscp ? NSS_IPSEC_CMN_FLAG_COPY_DSCP : 0; |
| sa_data->flags |= data->encap.copy_df ? NSS_IPSEC_CMN_FLAG_COPY_DF : 0; |
| sa_data->flags |= data->cmn.transport_mode ? NSS_IPSEC_CMN_FLAG_MODE_TRANS : 0; |
| |
| if (sa_tuple->ip_ver == 6) { |
| sa_data->flags &= ~NSS_IPSEC_CMN_FLAG_HDR_MASK; |
| sa_data->flags |= NSS_IPSEC_CMN_FLAG_IPV6; |
| } |
| |
| sa_tuple->hop_limit = data->encap.ttl_hop_limit; |
| |
| /* |
| * Copy tuple information for deletion |
| */ |
| memcpy(&sa->state.tuple, sa_tuple, sizeof(sa->state.tuple)); |
| memcpy(&sa->state.data, sa_data, sizeof(sa->state.data)); |
| sa->state.tx_default = !!data->encap.tx_default; |
| |
| nss_ipsecmgr_trace("%px:Encapsulation SA initialized ", sa); |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_init_decap() |
| * Initialize de-capsulation SA |
| */ |
| static void nss_ipsecmgr_sa_init_decap(struct nss_ipsecmgr_sa *sa, struct nss_ipsecmgr_sa_data *data, |
| struct nss_ipsec_cmn_msg *nicm) |
| { |
| struct nss_ipsec_cmn_sa_tuple *sa_tuple = &nicm->msg.sa.sa_tuple; |
| struct nss_ipsec_cmn_sa_data *sa_data = &nicm->msg.sa.sa_data; |
| |
| sa_data->window_size = data->decap.replay_win; |
| |
| sa_data->flags |= data->cmn.enable_natt ? NSS_IPSEC_CMN_FLAG_IPV4_NATT : 0; |
| sa_data->flags |= data->cmn.enable_esn ? NSS_IPSEC_CMN_FLAG_ESP_ESN : 0; |
| sa_data->flags |= data->cmn.skip_trailer ? NSS_IPSEC_CMN_FLAG_ESP_SKIP : 0; |
| sa_data->flags |= data->cmn.transport_mode ? NSS_IPSEC_CMN_FLAG_MODE_TRANS : 0; |
| sa_data->flags |= data->decap.replay_win ? NSS_IPSEC_CMN_FLAG_ESP_REPLAY : 0; |
| |
| if (sa_tuple->ip_ver == 6) { |
| sa_data->flags &= ~NSS_IPSEC_CMN_FLAG_HDR_MASK; |
| sa_data->flags |= NSS_IPSEC_CMN_FLAG_IPV6; |
| } |
| |
| /* |
| * Copy tuple information for deletion |
| */ |
| memcpy(&sa->state.tuple, sa_tuple, sizeof(sa->state.tuple)); |
| memcpy(&sa->state.data, sa_data, sizeof(sa->state.data)); |
| |
| nss_ipsecmgr_trace("%px:Decapsulation SA initialized ", sa); |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_sync_state() |
| * Update SA sync state |
| */ |
| void nss_ipsecmgr_sa_sync2stats(struct nss_ipsecmgr_sa *sa, struct nss_ipsec_cmn_sa_sync *sync, |
| struct nss_ipsecmgr_sa_stats *stats) |
| { |
| struct nss_ipsec_cmn_sa_stats *sa_stats = &sync->stats; |
| struct nss_ipsec_cmn_sa_data *sa_data = &sa->state.data; |
| uint32_t *drop_counters; |
| size_t num_counters; |
| int i; |
| |
| nss_ipsecmgr_sa_tuple2sa(&sync->sa_tuple, &stats->sa); |
| |
| switch (sa->type) { |
| case NSS_IPSEC_CMN_CTX_TYPE_INNER: |
| case NSS_IPSEC_CMN_CTX_TYPE_MDATA_INNER: |
| stats->pkt_count = sa_stats->cmn_stats.tx_packets; |
| stats->pkt_bytes = sa_stats->cmn_stats.tx_bytes; |
| break; |
| |
| case NSS_IPSEC_CMN_CTX_TYPE_OUTER: |
| case NSS_IPSEC_CMN_CTX_TYPE_MDATA_OUTER: |
| stats->pkt_count = sa_stats->cmn_stats.rx_packets; |
| stats->pkt_bytes = sa_stats->cmn_stats.rx_bytes; |
| break; |
| default: |
| return; |
| |
| } |
| |
| stats->pkt_failed = 0; |
| for (i = 0; i < ARRAY_SIZE(sa_stats->cmn_stats.rx_dropped); i++) |
| stats->pkt_failed += sa_stats->cmn_stats.rx_dropped[i]; |
| |
| /* |
| * Drop counters starts after common stats counters |
| */ |
| drop_counters = (uint32_t *)((uint8_t *)sa_stats + sizeof(sa_stats->cmn_stats)); |
| num_counters = (sizeof(*sa_stats) - sizeof(sa_stats->cmn_stats)) / sizeof(uint32_t); |
| |
| for (i = 0; i < num_counters; i++) |
| stats->pkt_failed += drop_counters[i]; |
| |
| if (sa_data->window_size) { |
| stats->window_size = sa_data->window_size; |
| stats->seq_start = sync->replay.seq_start; |
| stats->seq_cur = sync->replay.seq_cur; |
| } |
| |
| stats->fail_replay_win = sa_stats->fail_replay_win; |
| stats->fail_replay_dup = sa_stats->fail_replay_dup; |
| stats->fail_auth = sa_stats->fail_auth; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_sync_state() |
| * Update SA sync state |
| */ |
| void nss_ipsecmgr_sa_sync_state(struct nss_ipsecmgr_sa *sa, struct nss_ipsec_cmn_sa_sync *sa_sync) |
| { |
| uint32_t *msg_stats = (uint32_t *)&sa_sync->stats; |
| uint64_t *sa_stats = (uint64_t *)&sa->stats; |
| int num; |
| |
| /* |
| * DEBUG check to see if the lock is taken before accessing |
| * SA entry in the database |
| */ |
| nss_ipsecmgr_write_lock_is_held(&ipsecmgr_drv->lock); |
| |
| for (num = 0; num < sizeof(sa->stats)/sizeof(*sa_stats); num++) { |
| sa_stats[num] += msg_stats[num]; |
| } |
| |
| memcpy(&sa->state.replay, &sa_sync->replay, sizeof(sa->state.replay)); |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_find() |
| * Find an SA using a SA tuple |
| */ |
| struct nss_ipsecmgr_sa *nss_ipsecmgr_sa_find(struct list_head *db, struct nss_ipsec_cmn_sa_tuple *tuple) |
| { |
| struct nss_ipsecmgr_sa *sa; |
| uint32_t hash_idx; |
| |
| hash_idx = nss_ipsecmgr_sa_tuple2hash(tuple, NSS_IPSECMGR_SA_MAX); |
| |
| list_for_each_entry(sa, &db[hash_idx], list) { |
| if (nss_ipsecmgr_sa_tuple_match(&sa->state.tuple, tuple)) { |
| return sa; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * nss_ipsecmgr_sa_del() |
| * Delete an existing SA |
| */ |
| void nss_ipsecmgr_sa_del(struct net_device *dev, struct nss_ipsecmgr_sa_tuple *tuple) |
| { |
| struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev); |
| struct nss_ipsec_cmn_sa_tuple sa_tuple = {0}; |
| struct nss_ipsecmgr_sa *sa; |
| |
| nss_ipsecmgr_sa2tuple(tuple, &sa_tuple); |
| |
| /* |
| * Write lock needed here since SA and all related Flows |
| * are removed from DB. This requires write lock since SA Delete |
| * might be called in parallel from other cores as well |
| */ |
| write_lock_bh(&ipsecmgr_drv->lock); |
| |
| sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa_tuple); |
| if (!sa) { |
| write_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_warn("%px: failed to find SA for deletion\n", tun); |
| return; |
| } |
| |
| /* |
| * Free the entire reference hierarchy |
| */ |
| nss_ipsecmgr_ref_del(&sa->ref, &tun->free_refs); |
| write_unlock_bh(&ipsecmgr_drv->lock); |
| |
| schedule_work(&tun->free_work); |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_sa_del); |
| |
| /* |
| * nss_ipsecmgr_sa_add() |
| * Add a new SA for encapsulation or de-capsulation |
| */ |
| nss_ipsecmgr_status_t nss_ipsecmgr_sa_add(struct net_device *dev, struct nss_ipsecmgr_sa_tuple *tuple, |
| struct nss_ipsecmgr_sa_data *data, uint32_t *ifnum) |
| { |
| const enum nss_ipsec_cmn_msg_type type = NSS_IPSEC_CMN_MSG_TYPE_SA_CREATE; |
| struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev); |
| struct nss_ipsec_cmn_sa_tuple *msg_tuple; |
| struct nss_ipsec_cmn_sa_data *msg_data; |
| struct nss_ipsec_cmn_msg nicm = {{0}}; |
| struct nss_ipsecmgr_sa *sa = NULL; |
| struct nss_ipsecmgr_ctx *ctx; |
| nss_tx_status_t status; |
| |
| dev_hold(dev); |
| |
| msg_tuple = &nicm.msg.sa.sa_tuple; |
| msg_data = &nicm.msg.sa.sa_data; |
| |
| nss_ipsecmgr_sa2tuple(tuple, msg_tuple); |
| |
| /* |
| * Check if the SA already exists or not |
| */ |
| read_lock_bh(&ipsecmgr_drv->lock); |
| if (nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, msg_tuple)) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_trace("%px: Duplicate SA found", dev); |
| dev_put(dev); |
| return NSS_IPSECMGR_DUPLICATE_SA; |
| } |
| |
| ctx = nss_ipsecmgr_ctx_find_by_sa(tun, data->type); |
| if (!ctx) { |
| nss_ipsecmgr_warn("%px: failed to find inner context associated with tunnel", tun); |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| dev_put(dev); |
| return NSS_IPSECMGR_FAIL; |
| } |
| |
| /* |
| * Allocate the SA entry |
| */ |
| sa = nss_ipsecmgr_sa_alloc(ctx); |
| if (!sa) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_warn("%px: Failed to allocate SA for add", ctx); |
| dev_put(dev); |
| return NSS_IPSECMGR_FAIL_NOMEM; |
| } |
| |
| /* |
| * We are done with the net_device release the reference |
| */ |
| dev_put(dev); |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| |
| /* |
| * Allocate crypto resources |
| */ |
| if (nss_ipsecmgr_sa_crypto_alloc(sa, &data->cmn, msg_tuple, msg_data)) { |
| nss_ipsecmgr_warn("%px: Failed to allocate crypto resource for SA add", ctx); |
| nss_ipsecmgr_sa_free(sa); |
| return NSS_IPSECMGR_FAIL_NOCRYPTO; |
| } |
| |
| if (data->type == NSS_IPSECMGR_SA_TYPE_ENCAP) { |
| nss_ipsecmgr_sa_init_encap(sa, data, &nicm); |
| } else { |
| nss_ipsecmgr_sa_init_decap(sa, data, &nicm); |
| } |
| |
| nss_ipsec_cmn_msg_init(&nicm, sa->ifnum, type, sizeof(nicm.msg.sa), nss_ipsecmgr_sa_create_resp, sa); |
| |
| status = nss_ipsec_cmn_tx_msg(sa->nss_ctx, &nicm); |
| if (status != NSS_TX_SUCCESS) { |
| nss_ipsecmgr_warn("%px: Failed to send message(%u) to NSS(%u)\n", ctx, type, status); |
| nss_ipsecmgr_sa_free(sa); |
| return NSS_IPSECMGR_FAIL_MESSAGE; |
| } |
| |
| *ifnum = nss_ipsec_cmn_get_ifnum_with_coreid(sa->ifnum); |
| return NSS_IPSECMGR_OK; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_sa_add); |
| |
| /* |
| * nss_ipsecmgr_sa_add_sync() |
| * Add a new SA for encapsulation or de-capsulation synchronously |
| */ |
| nss_ipsecmgr_status_t nss_ipsecmgr_sa_add_sync(struct net_device *dev, struct nss_ipsecmgr_sa_tuple *tuple, |
| struct nss_ipsecmgr_sa_data *data, uint32_t *ifnum) |
| { |
| const enum nss_ipsec_cmn_msg_type type = NSS_IPSEC_CMN_MSG_TYPE_SA_CREATE; |
| struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev); |
| struct nss_ipsec_cmn_sa_tuple *msg_tuple; |
| struct nss_ipsec_cmn_sa_data *msg_data; |
| struct nss_ipsec_cmn_msg nicm = {{0}}; |
| struct nss_ipsecmgr_sa *sa = NULL; |
| struct nss_ipsecmgr_ctx *ctx; |
| nss_tx_status_t status; |
| |
| dev_hold(dev); |
| |
| msg_tuple = &nicm.msg.sa.sa_tuple; |
| msg_data = &nicm.msg.sa.sa_data; |
| |
| nss_ipsecmgr_sa2tuple(tuple, msg_tuple); |
| |
| /* |
| * Check if the SA already exists or not |
| */ |
| read_lock_bh(&ipsecmgr_drv->lock); |
| if (nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, msg_tuple)) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_trace("%px: Duplicate SA found", dev); |
| dev_put(dev); |
| return NSS_IPSECMGR_DUPLICATE_SA; |
| } |
| |
| ctx = nss_ipsecmgr_ctx_find_by_sa(tun, data->type); |
| if (!ctx) { |
| nss_ipsecmgr_warn("%px: failed to find inner context associated with tunnel", tun); |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| dev_put(dev); |
| return NSS_IPSECMGR_FAIL; |
| } |
| |
| /* |
| * Allocate the SA entry |
| */ |
| sa = nss_ipsecmgr_sa_alloc(ctx); |
| if (!sa) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_warn("%px: Failed to allocate SA for add", ctx); |
| dev_put(dev); |
| return NSS_IPSECMGR_FAIL_NOMEM; |
| } |
| |
| /* |
| * We are done with the net_device release the reference |
| */ |
| dev_put(dev); |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| |
| /* |
| * Allocate crypto resources |
| */ |
| if (nss_ipsecmgr_sa_crypto_alloc(sa, &data->cmn, msg_tuple, msg_data)) { |
| nss_ipsecmgr_warn("%px: Failed to allocate crypto resource for SA add", ctx); |
| nss_ipsecmgr_sa_free(sa); |
| return NSS_IPSECMGR_FAIL_NOCRYPTO; |
| } |
| |
| if (data->type == NSS_IPSECMGR_SA_TYPE_ENCAP) { |
| nss_ipsecmgr_sa_init_encap(sa, data, &nicm); |
| } else { |
| nss_ipsecmgr_sa_init_decap(sa, data, &nicm); |
| } |
| |
| nss_ipsec_cmn_msg_init(&nicm, sa->ifnum, type, sizeof(nicm.msg.sa), NULL, NULL); |
| |
| status = nss_ipsec_cmn_tx_msg_sync(sa->nss_ctx, sa->ifnum, type, sizeof(nicm.msg.sa), &nicm); |
| if (status != NSS_TX_SUCCESS) { |
| nss_ipsecmgr_warn("%px: Failed to send message(%u) to NSS(%u)\n", ctx, type, status); |
| nss_ipsecmgr_sa_free(sa); |
| return NSS_IPSECMGR_FAIL_MESSAGE; |
| } |
| |
| /* |
| * Since, this is a synchronous call add it to the database directly |
| */ |
| if (!nss_ipsecmgr_sa_update_db(sa)) { |
| nss_ipsecmgr_warn("%px: Failed to update SA database", sa); |
| nss_ipsecmgr_sa_free(sa); |
| return NSS_IPSECMGR_FAIL_ADD_DB; |
| } |
| |
| *ifnum = nss_ipsec_cmn_get_ifnum_with_coreid(sa->ifnum); |
| return NSS_IPSECMGR_OK; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_sa_add_sync); |
| |
| /* |
| * nss_ipsecmgr_sa_verify() |
| * Confirm SA is present or not for sa tuple. |
| */ |
| bool nss_ipsecmgr_sa_verify(struct net_device *dev, struct nss_ipsecmgr_sa_tuple *tuple) |
| { |
| struct nss_ipsec_cmn_sa_tuple sa_tuple = {0}; |
| struct nss_ipsecmgr_sa *sa; |
| |
| /* |
| * Look for an existing SA. |
| */ |
| nss_ipsecmgr_sa2tuple(tuple, &sa_tuple); |
| |
| read_lock_bh(&ipsecmgr_drv->lock); |
| sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa_tuple); |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| |
| return !!sa; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_sa_verify); |
| |
| /* |
| * nss_ipsecmgr_cra_name2algo() |
| * Returns nss_ipsecmgr_algo |
| */ |
| enum nss_ipsecmgr_algo nss_ipsecmgr_cra_name2algo(const char *cra_name) |
| { |
| enum nss_ipsecmgr_algo algo = NSS_IPSECMGR_ALGO_AES_CBC_SHA1_HMAC; |
| const char **algo_name = ipsecmgr_algo_name; |
| |
| for (; algo < NSS_IPSECMGR_ALGO_MAX; algo++, algo_name++) { |
| if (!strncmp(cra_name, *algo_name, strlen(*algo_name))) { |
| return algo; |
| } |
| } |
| |
| return NSS_IPSECMGR_ALGO_MAX; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_cra_name2algo); |
| |
| /* |
| * nss_ipsecmgr_sa_get_info() |
| * Get Crypto information for an already created SA. |
| */ |
| bool nss_ipsecmgr_sa_get_info(struct net_device *dev, struct nss_ipsecmgr_sa_tuple *tuple, |
| struct nss_ipsecmgr_sa_info *sa_info) |
| { |
| struct nss_ipsec_cmn_sa_tuple sa_tuple = {0}; |
| struct nss_ipsecmgr_sa *sa; |
| uint32_t mask; |
| |
| /* |
| * Look for an existing SA. |
| */ |
| nss_ipsecmgr_sa2tuple(tuple, &sa_tuple); |
| |
| read_lock_bh(&ipsecmgr_drv->lock); |
| sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa_tuple); |
| if (!sa) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| return false; |
| } |
| |
| sa_info->blk_len = sa->state.data.blk_len; |
| sa_info->iv_len = sa->state.data.iv_len; |
| sa_info->hash_len = sa->state.data.icv_len; |
| sa_info->session_idx = sa->state.tuple.crypto_index; |
| |
| sa_info->hdr_len = sizeof(struct ip_esp_hdr) + sa_info->iv_len; |
| mask = NSS_IPSEC_CMN_FLAG_HDR_MASK | NSS_IPSEC_CMN_FLAG_MODE_TRANS; |
| |
| switch (sa->state.data.flags & mask) { |
| case NSS_IPSEC_CMN_FLAG_IPV4_NATT | NSS_IPSEC_CMN_FLAG_MODE_TRANS: |
| sa_info->hdr_len += sizeof(struct udphdr); |
| break; |
| case NSS_IPSEC_CMN_FLAG_IPV6 | NSS_IPSEC_CMN_FLAG_MODE_TRANS: |
| case NSS_IPSEC_CMN_FLAG_MODE_TRANS: |
| break; |
| case NSS_IPSEC_CMN_FLAG_IPV4_NATT: |
| sa_info->hdr_len += sizeof(struct iphdr) + sizeof(struct udphdr); |
| break; |
| case NSS_IPSEC_CMN_FLAG_IPV6: |
| sa_info->hdr_len += sizeof(struct ipv6hdr); |
| break; |
| default: |
| sa_info->hdr_len += sizeof(struct iphdr); |
| break; |
| } |
| |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| |
| /* |
| * The user of trailer_len should take care of the odd length. |
| */ |
| sa_info->trailer_len = sa_info->blk_len + 1 + sa_info->hash_len; |
| |
| return true; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_sa_get_info); |
| |
| /* |
| * nss_ipsecmgr_sa_tx_inner() |
| * Offload given SKB to NSS for inner processing. |
| */ |
| nss_ipsecmgr_status_t nss_ipsecmgr_sa_tx_inner(struct net_device *dev, struct nss_ipsecmgr_sa_tuple *tuple, |
| struct sk_buff *skb) |
| { |
| struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev); |
| nss_ipsecmgr_status_t status = NSS_IPSECMGR_OK; |
| struct nss_ipsec_cmn_sa_tuple sa_tuple = {0}; |
| struct nss_ipsec_cmn_mdata_encap *enc_mdata; |
| struct nss_ctx_instance *nss_ctx; |
| struct nss_ipsecmgr_ctx *ctx; |
| struct nss_ipsecmgr_sa *sa; |
| nss_tx_status_t tx_status; |
| uint16_t data_len; |
| uint32_t ifnum; |
| |
| BUG_ON(skb_shared(skb)); |
| WARN_ON(skb_is_nonlinear(skb) || skb_has_frag_list(skb)); |
| |
| dev_hold(dev); |
| |
| nss_ipsecmgr_sa2tuple(tuple, &sa_tuple); |
| read_lock_bh(&ipsecmgr_drv->lock); |
| |
| /* |
| * Look for an existing SA. |
| */ |
| sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa_tuple); |
| if (unlikely(!sa)) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_warn("%px: Failed to find SA", tun); |
| status = NSS_IPSECMGR_INVALID_SA; |
| goto done; |
| } |
| |
| ctx = nss_ipsecmgr_ctx_find(tun, NSS_IPSEC_CMN_CTX_TYPE_MDATA_INNER); |
| if (unlikely(!ctx)) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_warn("%px: Failed to find context(%u)", tun, NSS_IPSEC_CMN_CTX_TYPE_MDATA_INNER); |
| status = NSS_IPSECMGR_INVALID_CTX; |
| goto done; |
| } |
| |
| ifnum = ctx->ifnum; |
| nss_ctx = ctx->nss_ctx; |
| data_len = skb->len; |
| |
| /* |
| * Expand data area to cover tailroom used by NSS. |
| */ |
| skb_put(skb, dev->needed_tailroom); |
| |
| /* |
| * Add metadata and send SKB to IPsec meta data inner node for encapsulation. |
| */ |
| enc_mdata = nss_ipsecmgr_tunnel_push_mdata(skb); |
| enc_mdata->sa = sa->state.tuple; |
| enc_mdata->data_len = data_len; |
| |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| |
| /* |
| * Send the packet to NSS |
| */ |
| tx_status = nss_ipsec_cmn_tx_buf(nss_ctx, skb, ifnum); |
| if (unlikely(tx_status != NSS_TX_SUCCESS)) { |
| nss_ipsecmgr_warn("%px: Failed to send buffer to NSS; error(%u)", tun, tx_status); |
| nss_ipsecmgr_tunnel_pull_mdata(skb); |
| skb_trim(skb, data_len); |
| status = NSS_IPSECMGR_FAIL; |
| goto done; |
| } |
| |
| done: |
| dev_put(dev); |
| return status; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_sa_tx_inner); |
| |
| /* |
| * nss_ipsecmgr_sa_tx_outer() |
| * Offload given SKB to NSS for outer processing. |
| */ |
| nss_ipsecmgr_status_t nss_ipsecmgr_sa_tx_outer(struct net_device *dev, struct nss_ipsecmgr_sa_tuple *tuple, |
| struct sk_buff *skb) |
| { |
| struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev); |
| nss_ipsecmgr_status_t status = NSS_IPSECMGR_OK; |
| struct nss_ipsec_cmn_sa_tuple sa_tuple = {0}; |
| struct nss_ipsec_cmn_mdata_decap *dec_mdata; |
| struct nss_ctx_instance *nss_ctx; |
| struct nss_ipsecmgr_ctx *ctx; |
| struct nss_ipsecmgr_sa *sa; |
| nss_tx_status_t tx_status; |
| uint32_t ifnum; |
| |
| BUG_ON(skb_shared(skb)); |
| |
| dev_hold(dev); |
| |
| nss_ipsecmgr_sa2tuple(tuple, &sa_tuple); |
| read_lock_bh(&ipsecmgr_drv->lock); |
| |
| /* |
| * Look for an existing SA. |
| */ |
| sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, &sa_tuple); |
| if (unlikely(!sa)) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_warn("%px: Failed to find SA", tun); |
| status = NSS_IPSECMGR_INVALID_SA; |
| goto done; |
| |
| } |
| |
| ctx = nss_ipsecmgr_ctx_find(tun, NSS_IPSEC_CMN_CTX_TYPE_MDATA_OUTER); |
| if (unlikely(!ctx)) { |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| nss_ipsecmgr_warn("%px: Failed to find context(%u)", tun, NSS_IPSEC_CMN_CTX_TYPE_MDATA_OUTER); |
| status = NSS_IPSECMGR_INVALID_CTX; |
| goto done; |
| |
| } |
| |
| ifnum = ctx->ifnum; |
| nss_ctx = ctx->nss_ctx; |
| |
| /* |
| * Add metadata and send SKB to IPsec meta data outer node for decapsulation. |
| */ |
| dec_mdata = nss_ipsecmgr_tunnel_push_mdata(skb); |
| dec_mdata->sa = sa->state.tuple; |
| |
| read_unlock_bh(&ipsecmgr_drv->lock); |
| |
| /* |
| * Send the packet to NSS |
| */ |
| tx_status = nss_ipsec_cmn_tx_buf(nss_ctx, skb, ifnum); |
| if (unlikely(tx_status != NSS_TX_SUCCESS)) { |
| nss_ipsecmgr_warn("%px: Failed to send buffer to NSS; error(%u)", tun, tx_status); |
| nss_ipsecmgr_tunnel_pull_mdata(skb); |
| status = NSS_IPSECMGR_FAIL; |
| goto done; |
| } |
| |
| done: |
| dev_put(dev); |
| return status; |
| } |
| EXPORT_SYMBOL(nss_ipsecmgr_sa_tx_outer); |