| /* |
| ************************************************************************** |
| * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all copies. |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| ************************************************************************** |
| */ |
| |
| /* |
| * nss_dtlsmgr_ctx.c |
| * NSS DTLS Manager Context |
| */ |
| |
| #include <linux/version.h> |
| #include <linux/types.h> |
| #include <linux/ip.h> |
| #include <linux/tcp.h> |
| #include <linux/module.h> |
| #include <linux/skbuff.h> |
| #include <linux/crypto.h> |
| #include <linux/debugfs.h> |
| #include <linux/rtnetlink.h> |
| #include <net/ipv6.h> |
| #include <net/vxlan.h> |
| #include <linux/if_arp.h> |
| #include <linux/etherdevice.h> |
| #include <linux/atomic.h> |
| #include <asm/cmpxchg.h> |
| |
| #include <crypto/algapi.h> |
| #include <crypto/aead.h> |
| #include <crypto/aes.h> |
| #include <crypto/authenc.h> |
| #include <crypto/des.h> |
| #include <crypto/sha.h> |
| #include <crypto/skcipher.h> |
| #include <crypto/hash.h> |
| |
| #include <nss_api_if.h> |
| #include <nss_dynamic_interface.h> |
| |
| #include <nss_cryptoapi.h> |
| #include <nss_dtls_cmn.h> |
| #include <nss_dtlsmgr.h> |
| |
| #include "nss_dtlsmgr_private.h" |
| |
| #define NSS_DTLSMGR_KEY_PARAM_SIZE RTA_SPACE(sizeof(struct crypto_authenc_key_param)) |
| |
| extern struct nss_dtlsmgr g_dtls; |
| |
| static struct nss_dtlsmgr_algo_info dtlsmgr_algo_info[NSS_DTLSMGR_ALGO_MAX] = { |
| {"echainiv(authenc(hmac(sha1),cbc(aes)))", NSS_DTLSMGR_KEY_PARAM_SIZE}, |
| {"echainiv(authenc(hmac(sha256),cbc(aes)))", NSS_DTLSMGR_KEY_PARAM_SIZE}, |
| {"echainiv(authenc(hmac(sha1),cbc(des3_ede)))", NSS_DTLSMGR_KEY_PARAM_SIZE}, |
| {"echainiv(authenc(hmac(sha256),cbc(des3_ede)))", NSS_DTLSMGR_KEY_PARAM_SIZE}, |
| {"rfc4106(gcm(aes))", 0} |
| }; |
| |
| /* |
| * nss_dtlsmgr_ctx_alloc_crypto() |
| * Allocate a crypto session through Linux CryptoAPI framework. |
| */ |
| static int nss_dtlsmgr_ctx_alloc_crypto(struct nss_dtlsmgr_ctx *ctx, struct nss_dtlsmgr_dtls_data *dtls, |
| struct nss_dtlsmgr_crypto *crypto) |
| { |
| struct crypto_authenc_key_param *key_param; |
| struct nss_dtlsmgr_algo_info *info; |
| struct rtattr *rta; |
| char *keys, *p; |
| uint16_t keylen; |
| |
| if (crypto->algo >= ARRAY_SIZE(dtlsmgr_algo_info)) { |
| nss_dtlsmgr_warn("%px: invalid crypto algorithm", ctx); |
| return -EINVAL; |
| } |
| |
| info = &dtlsmgr_algo_info[crypto->algo]; |
| dtls->aead = crypto_alloc_aead(info->name, 0, 0); |
| if (IS_ERR(dtls->aead)) { |
| nss_dtlsmgr_warn("%px: failed to allocate crypto aead context", ctx); |
| return -ENOMEM; |
| } |
| |
| nss_dtlsmgr_trace("cipher_keylen:%d auth_keylen:%d nonce_len:%d\n", |
| crypto->cipher_key.len, crypto->auth_key.len, crypto->nonce.len); |
| |
| /* |
| * Construct keys |
| */ |
| keylen = info->rta_key_size; |
| keylen += crypto->cipher_key.len; |
| keylen += crypto->auth_key.len; |
| keylen += crypto->nonce.len; |
| |
| keys = vzalloc(keylen); |
| if (!keys) { |
| nss_dtlsmgr_warn("%px: failed to allocate key memory", ctx); |
| crypto_free_aead(dtls->aead); |
| return -ENOMEM; |
| } |
| |
| if (crypto->algo == NSS_DTLSMGR_ALGO_AES_GCM) { |
| memcpy(keys, crypto->cipher_key.data, crypto->cipher_key.len); |
| /* Copy nonce after the key */ |
| memcpy(keys + crypto->cipher_key.len, crypto->nonce.data, crypto->nonce.len); |
| goto setkey; |
| } |
| |
| p = 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, crypto->auth_key.data, crypto->auth_key.len); |
| p += crypto->auth_key.len; |
| |
| /* |
| * Copy cipher Key |
| */ |
| key_param->enckeylen = cpu_to_be32(crypto->cipher_key.len); |
| memcpy(p, crypto->cipher_key.data, crypto->cipher_key.len); |
| |
| setkey: |
| |
| if (crypto_aead_setkey(dtls->aead, keys, keylen)) { |
| nss_dtlsmgr_warn("%px: failed to configure keys", ctx); |
| vfree(keys); |
| crypto_free_aead(dtls->aead); |
| return -ENOSPC; |
| } |
| |
| nss_cryptoapi_aead_ctx2session(dtls->aead, &dtls->crypto_idx); |
| dtls->blk_len = (uint8_t)crypto_aead_blocksize(dtls->aead); |
| dtls->hash_len = (uint8_t)crypto_aead_authsize(dtls->aead); |
| dtls->iv_len = (uint8_t)crypto_aead_ivsize(dtls->aead); |
| |
| vfree(keys); |
| return 0; |
| } |
| |
| /* |
| * nss_dtlsmgr_ctx_alloc_dtls() |
| * Allocate a DTLS session. |
| */ |
| static struct nss_dtlsmgr_dtls_data *nss_dtlsmgr_ctx_alloc_dtls(struct nss_dtlsmgr_ctx *ctx, |
| struct nss_dtlsmgr_ctx_data *data, |
| struct nss_dtlsmgr_crypto *crypto) |
| { |
| struct nss_dtlsmgr_dtls_data *dtls; |
| int error; |
| |
| nss_dtlsmgr_trace("%px: allocating context data(%u)", ctx, data->di_type); |
| |
| dtls = vzalloc(sizeof(*dtls)); |
| if (!dtls) { |
| nss_dtlsmgr_warn("%px: failed to allocate dtls data(%u) ", ctx, data->di_type); |
| return NULL; |
| } |
| |
| INIT_LIST_HEAD(&dtls->list); |
| |
| error = nss_dtlsmgr_ctx_alloc_crypto(ctx, dtls, crypto); |
| if (error < 0) { |
| nss_dtlsmgr_warn("%px: unable to allocate crypto(%u) - error(%d)", ctx, data->di_type, error); |
| vfree(dtls); |
| return NULL; |
| } |
| |
| nss_dtlsmgr_trace("%px: crypto_aead allocated", ctx); |
| return dtls; |
| } |
| |
| /* |
| * nss_dtlsmgr_ctx_free_dtls() |
| * Free the DTLS context. |
| */ |
| static void nss_dtlsmgr_ctx_free_dtls(struct nss_dtlsmgr_dtls_data *dtls) |
| { |
| crypto_free_aead(dtls->aead); |
| vfree(dtls); |
| } |
| |
| /* |
| * nss_dtlsmgr_ctx_configure_hdr() |
| * Configure the DTLS header related information. |
| */ |
| static bool nss_dtlsmgr_ctx_configure_hdr(struct nss_dtlsmgr_ctx_data *data) |
| { |
| const uint32_t type = NSS_DTLS_CMN_MSG_TYPE_CONFIGURE_HDR; |
| enum nss_dtls_cmn_error resp = NSS_DTLS_CMN_ERROR_NONE; |
| struct nss_dtls_cmn_ctx_config_hdr *cfg; |
| struct nss_dtls_cmn_msg ndcm = { {0} }; |
| nss_tx_status_t status; |
| uint32_t mask = 0; |
| |
| BUG_ON(in_atomic()); |
| |
| mask |= NSS_DTLS_CMN_CTX_HDR_IPV6; |
| mask |= NSS_DTLS_CMN_CTX_HDR_UDPLITE; |
| mask |= NSS_DTLS_CMN_CTX_HDR_CAPWAP; |
| mask |= NSS_DTLS_CMN_CTX_CIPHER_MODE_GCM; |
| mask |= NSS_DTLS_CMN_CTX_ENCAP_UDPLITE_CSUM; |
| mask |= NSS_DTLS_CMN_CTX_ENCAP_METADATA; |
| mask |= NSS_DTLS_CMN_CTX_DECAP_ACCEPT_ALL; |
| |
| cfg = &ndcm.msg.hdr_cfg; |
| cfg->flags = data->flags & mask; |
| cfg->dest_ifnum = data->dest_ifnum; |
| cfg->src_ifnum = data->src_ifnum; |
| |
| memcpy(cfg->sip, data->flow.sip, sizeof(cfg->sip)); |
| memcpy(cfg->dip, data->flow.dip, sizeof(cfg->dip)); |
| |
| cfg->sport = data->flow.sport; |
| cfg->dport = data->flow.dport; |
| cfg->hop_limit_ttl = data->flow.hop_limit_ttl; |
| cfg->dscp = data->flow.dscp; |
| cfg->dscp_copy = data->flow.dscp_copy; |
| cfg->df = data->flow.df; |
| |
| nss_dtlsmgr_trace("flags:0x%x dest_ifnum:0x%x src_ifnum:0x%x sport:0x%x dport:0x%x sip:0x%x dip:0x%x", |
| cfg->flags, cfg->dest_ifnum, cfg->src_ifnum, cfg->sport, cfg->dport, |
| cfg->sip[0], cfg->dip[0]); |
| |
| status = nss_dtls_cmn_tx_msg_sync(data->nss_ctx, data->ifnum, type, sizeof(*cfg), &ndcm, &resp); |
| if (status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_warn("%px: msg_sync failed, if_num(%u), status(%d), type(%d), resp(%d)", |
| data, data->ifnum, type, status, resp); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nss_dtlsmgr_ctx_configure_dtls() |
| * Configure the DTLS version, crypto related data, window size and epoch. |
| */ |
| static bool nss_dtlsmgr_ctx_configure_dtls(struct nss_dtlsmgr_ctx_data *data, struct nss_dtlsmgr_dtls_data *dtls) |
| { |
| const uint32_t type = NSS_DTLS_CMN_MSG_TYPE_CONFIGURE_DTLS; |
| enum nss_dtls_cmn_error resp = NSS_DTLS_CMN_ERROR_NONE; |
| struct nss_dtls_cmn_ctx_config_dtls *cfg; |
| struct nss_dtls_cmn_msg ndcm = {0}; |
| nss_tx_status_t status; |
| |
| BUG_ON(in_atomic()); |
| |
| cfg = &ndcm.msg.dtls_cfg; |
| cfg->ver = dtls->ver; |
| cfg->crypto_idx = dtls->crypto_idx; |
| cfg->epoch = dtls->epoch; |
| cfg->window_size = dtls->window_size; |
| cfg->iv_len = dtls->iv_len; |
| cfg->hash_len = dtls->hash_len; |
| cfg->blk_len = dtls->blk_len; |
| |
| status = nss_dtls_cmn_tx_msg_sync(data->nss_ctx, data->ifnum, type, sizeof(*cfg), &ndcm, &resp); |
| if (status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_warn("%px: msg_sync failed, if_num(%u), status(%d), type(%d), resp(%d)", |
| data, data->ifnum, type, status, resp); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nss_dtlsmgr_ctx_deconfigure() |
| * Deconfigure the DTLS context and free all the related data. |
| */ |
| static bool nss_dtlsmgr_ctx_deconfigure(struct nss_dtlsmgr_ctx *ctx, struct nss_dtlsmgr_ctx_data *data) |
| { |
| const uint32_t type = NSS_DTLS_CMN_MSG_TYPE_DECONFIGURE; |
| enum nss_dtls_cmn_error resp = NSS_DTLS_CMN_ERROR_NONE; |
| struct nss_dtls_cmn_msg ndcm = {0}; |
| struct nss_dtlsmgr_dtls_data *cur; |
| nss_tx_status_t status; |
| |
| status = nss_dtls_cmn_tx_msg_sync(data->nss_ctx, data->ifnum, type, 0, &ndcm, &resp); |
| if (status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_warn("%px: msg_sync failed, if_num(%u), status(%d), type(%d), resp(%d)", |
| ctx, data->ifnum, type, status, resp); |
| return false; |
| } |
| |
| nss_dtls_cmn_unregister_if(data->ifnum); |
| |
| for (;;) { |
| write_lock(&ctx->lock); |
| cur = list_first_entry_or_null(&data->dtls_active, struct nss_dtlsmgr_dtls_data, list); |
| if (!cur) { |
| write_unlock(&ctx->lock); |
| break; |
| } |
| |
| list_del(&cur->list); |
| write_unlock(&ctx->lock); |
| nss_dtlsmgr_ctx_free_dtls(cur); |
| } |
| |
| status = nss_dynamic_interface_dealloc_node(data->ifnum, data->di_type); |
| if (status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_warn("%px: fail to deallocate dynamic(%d) interface(%u)", ctx, data->di_type, data->ifnum); |
| return false; |
| } |
| |
| data->ifnum = -1; |
| return true; |
| } |
| |
| /* |
| * nss_dtlsmgr_ctx_create_encap() |
| * Create DTLS encapsulation dynamic interface and configure the DTLS context. |
| */ |
| static int nss_dtlsmgr_ctx_create_encap(struct nss_dtlsmgr_ctx *ctx, uint32_t ifnum, |
| uint32_t src_ifnum, struct nss_dtlsmgr_config *ndc) |
| { |
| struct nss_dtlsmgr_encap_config *cfg = &ndc->encap; |
| struct nss_dtlsmgr_ctx_data *data = &ctx->encap; |
| struct nss_dtlsmgr_flow_data *flow = &data->flow; |
| struct nss_dtlsmgr_dtls_data *dtls; |
| |
| dtls = nss_dtlsmgr_ctx_alloc_dtls(ctx, &ctx->encap, &cfg->crypto); |
| if (!dtls) { |
| nss_dtlsmgr_warn("%px: unable to allocate encap context data", ctx); |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&data->dtls_active); |
| |
| data->di_type = NSS_DYNAMIC_INTERFACE_TYPE_DTLS_CMN_INNER; |
| data->ifnum = ifnum; |
| data->src_ifnum = src_ifnum; |
| data->flags = ndc->flags & (NSS_DTLSMGR_HDR_MASK | NSS_DTLSMGR_CRYPTO_MASK | NSS_DTLSMGR_ENCAP_MASK); |
| data->tailroom = dtls->blk_len + dtls->hash_len; |
| data->headroom = dtls->iv_len; |
| |
| memcpy(&flow->sip, cfg->sip, sizeof(flow->sip)); |
| memcpy(&flow->dip, cfg->dip, sizeof(flow->dip)); |
| |
| flow->sport = cfg->sport; |
| flow->dport = cfg->dport; |
| flow->dscp = cfg->dscp; |
| flow->dscp_copy = cfg->dscp_copy; |
| flow->df = cfg->df; |
| flow->hop_limit_ttl = cfg->ip_ttl; |
| |
| dtls->epoch = cfg->epoch; |
| dtls->ver = cfg->ver; |
| |
| data->headroom += NSS_DTLSMGR_DTLS_HDR_SZ; |
| |
| /* |
| * We need to provide the firmware the source and |
| * destination interface number. This allows it |
| * to work with dynamically created interfaces |
| * |
| */ |
| switch (data->flags & (NSS_DTLSMGR_HDR_IPV6 | NSS_DTLSMGR_HDR_CAPWAP)) { |
| case NSS_DTLSMGR_HDR_IPV6 | NSS_DTLSMGR_HDR_CAPWAP: |
| data->dest_ifnum = NSS_IPV6_RX_INTERFACE; |
| data->headroom += sizeof(struct ipv6hdr); |
| data->headroom += NSS_DTLSMGR_CAPWAP_DTLS_HDR_SZ; |
| data->headroom += NSS_DTLSMGR_SGT_HDR_SZ; |
| break; |
| case NSS_DTLSMGR_HDR_IPV6: |
| data->dest_ifnum = NSS_IPV6_RX_INTERFACE; |
| data->headroom += sizeof(struct ipv6hdr); |
| break; |
| case NSS_DTLSMGR_HDR_CAPWAP: |
| data->dest_ifnum = NSS_IPV4_RX_INTERFACE; |
| data->headroom += sizeof(struct iphdr); |
| data->headroom += NSS_DTLSMGR_CAPWAP_DTLS_HDR_SZ; |
| data->headroom += NSS_DTLSMGR_SGT_HDR_SZ; |
| break; |
| default: |
| data->dest_ifnum = NSS_IPV4_RX_INTERFACE; |
| data->headroom += sizeof(struct iphdr); |
| break; |
| } |
| |
| /* |
| * Header size is same for UDP and UDPLite |
| */ |
| data->headroom += sizeof(struct ethhdr) + sizeof(struct vlan_hdr) + sizeof(struct udphdr); |
| |
| nss_dtlsmgr_trace("%px: encap ifnum(%u), src(%u), dest(0x%x)", ctx, data->ifnum, |
| data->src_ifnum, data->dest_ifnum); |
| |
| /* |
| * Register NSS DTLS Encap I/F |
| */ |
| data->nss_ctx = nss_dtls_cmn_register_if(data->ifnum, |
| nss_dtlsmgr_ctx_dev_rx_inner, |
| nss_dtlsmgr_ctx_dev_event_inner, |
| ctx->dev, |
| 0, |
| data->di_type, |
| (void *)data); |
| if (!data->nss_ctx) { |
| nss_dtlsmgr_warn("%px: NSS register interface(%u) failed", ctx, data->ifnum); |
| nss_dtlsmgr_ctx_free_dtls(dtls); |
| return -ENODEV; |
| } |
| |
| if (!nss_dtlsmgr_ctx_configure_hdr(data)) { |
| nss_dtlsmgr_warn("%px: unable to configure(%d) hdr", ctx, data->di_type); |
| goto fail; |
| } |
| |
| if (!nss_dtlsmgr_ctx_configure_dtls(data, dtls)) { |
| nss_dtlsmgr_warn("%px: unable to configure(%d) dtls", ctx, data->di_type); |
| goto fail; |
| } |
| |
| write_lock(&ctx->lock); |
| list_add(&dtls->list, &data->dtls_active); |
| write_unlock(&ctx->lock); |
| |
| return 0; |
| fail: |
| nss_dtls_cmn_unregister_if(data->ifnum); |
| nss_dtlsmgr_ctx_free_dtls(dtls); |
| return -EBUSY; |
| } |
| |
| /* |
| * nss_dtlsmgr_ctx_create_decap() |
| * Create DTLS decapsulation dynamic interface and configure the DTLS context. |
| */ |
| static int nss_dtlsmgr_ctx_create_decap(struct nss_dtlsmgr_ctx *ctx, uint32_t ifnum, uint32_t src_ifnum, |
| struct nss_dtlsmgr_config *cfg) |
| { |
| struct nss_dtlsmgr_ctx_data *data = &ctx->decap; |
| struct nss_dtlsmgr_dtls_data *dtls; |
| |
| dtls = nss_dtlsmgr_ctx_alloc_dtls(ctx, &ctx->decap, &cfg->decap.crypto); |
| if (!dtls) { |
| nss_dtlsmgr_warn("%px: unable to allocate decap context data", ctx); |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&data->dtls_active); |
| |
| data->di_type = NSS_DYNAMIC_INTERFACE_TYPE_DTLS_CMN_OUTER; |
| data->ifnum = ifnum; |
| |
| /* |
| * We need to provide the firmware the source and |
| * destination interface number. This allows it |
| * to work with dynamically created interfaces |
| * |
| */ |
| data->src_ifnum = src_ifnum; |
| data->dest_ifnum = cfg->decap.nexthop_ifnum; |
| data->tailroom = data->headroom = 0; |
| data->flags = cfg->flags & (NSS_DTLSMGR_HDR_MASK | NSS_DTLSMGR_CRYPTO_MASK | NSS_DTLSMGR_DECAP_MASK); |
| |
| nss_dtlsmgr_trace("%px: decap ifnum(%u), src(%u), dest(%u)", ctx, data->ifnum, |
| data->src_ifnum, data->dest_ifnum); |
| |
| dtls->window_size = cfg->decap.window_size; |
| dtls->ver = cfg->encap.ver; |
| |
| /* |
| * Register NSS DTLS Decap I/F |
| */ |
| data->nss_ctx = nss_dtls_cmn_register_if(data->ifnum, |
| nss_dtlsmgr_ctx_dev_rx_outer, |
| nss_dtlsmgr_ctx_dev_event_outer, |
| ctx->dev, |
| 0, |
| data->di_type, |
| (void *)data); |
| if (!data->nss_ctx) { |
| nss_dtlsmgr_warn("%px: NSS register interface(%u) failed", ctx, data->ifnum); |
| nss_dtlsmgr_ctx_free_dtls(dtls); |
| return -ENODEV; |
| } |
| |
| if (!nss_dtlsmgr_ctx_configure_hdr(data)) { |
| nss_dtlsmgr_warn("%px: unable to configure(%d) hdr", ctx, data->di_type); |
| goto fail; |
| } |
| |
| if (!nss_dtlsmgr_ctx_configure_dtls(data, dtls)) { |
| nss_dtlsmgr_warn("%px: unable to configure(%d) hdr", ctx, data->di_type); |
| goto fail; |
| } |
| |
| write_lock(&ctx->lock); |
| list_add(&dtls->list, &data->dtls_active); |
| write_unlock(&ctx->lock); |
| |
| return 0; |
| fail: |
| nss_dtls_cmn_unregister_if(data->ifnum); |
| nss_dtlsmgr_ctx_free_dtls(dtls); |
| return -EBUSY; |
| } |
| |
| /* |
| * nss_dtlsmgr_session_switch() |
| * Send a switch message to firmware to use new cipher spec |
| * |
| * Note: This deletes the older cipher spec and pops the next cipher spec |
| * for use. |
| */ |
| static bool nss_dtlsmgr_session_switch(struct nss_dtlsmgr_ctx *ctx, struct nss_dtlsmgr_ctx_data *data) |
| { |
| const uint32_t type = NSS_DTLS_CMN_MSG_TYPE_SWITCH_DTLS; |
| enum nss_dtls_cmn_error resp = NSS_DTLS_CMN_ERROR_NONE; |
| struct nss_dtls_cmn_msg ndcm = {0}; |
| struct nss_dtlsmgr_dtls_data *dtls; |
| nss_tx_status_t status; |
| |
| BUG_ON(in_atomic()); |
| |
| /* |
| * TODO: Add retry messaging to ensure that in case of failures, due to queue |
| * full conditions we do attempt few retries before aborting. |
| */ |
| status = nss_dtls_cmn_tx_msg_sync(data->nss_ctx, data->ifnum, type, 0, &ndcm, &resp); |
| if (status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_warn("%px: msg_sync failed, if_num(%u), status(%d), type(%d), resp(%d)", |
| ctx, data->ifnum, type, status, resp); |
| return false; |
| } |
| |
| /* |
| * We essentially pop the head of the dtls list. |
| * It is expected that an update should have already |
| * added a new dtls entry at the tail of the list |
| */ |
| write_lock(&ctx->lock); |
| dtls = list_first_entry_or_null(&data->dtls_active, struct nss_dtlsmgr_dtls_data, list); |
| if (!dtls) { |
| write_unlock(&ctx->lock); |
| return false; |
| } |
| |
| list_del(&dtls->list); |
| write_unlock(&ctx->lock); |
| |
| nss_dtlsmgr_ctx_free_dtls(dtls); |
| return true; |
| } |
| |
| /* |
| * nss_dtlsmgr_session_create() |
| * Create DTLS session and associated crypto sessions. |
| */ |
| struct net_device *nss_dtlsmgr_session_create(struct nss_dtlsmgr_config *cfg) |
| { |
| struct nss_dtlsmgr *drv = &g_dtls; |
| struct nss_dtlsmgr_ctx *ctx = NULL; |
| struct net_device *dev; |
| int32_t encap_ifnum; |
| int32_t decap_ifnum; |
| int error; |
| |
| if (!atomic_read(&drv->is_configured)) { |
| nss_dtlsmgr_warn("%px: dtls firmware not ready", drv); |
| return NULL; |
| } |
| |
| if ((cfg->encap.ver != NSS_DTLSMGR_VERSION_1_0) && (cfg->encap.ver != NSS_DTLSMGR_VERSION_1_2)) { |
| nss_dtlsmgr_warn("%px: invalid encapsulation version(%d)", drv, cfg->encap.ver); |
| return NULL; |
| } |
| |
| encap_ifnum = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_DTLS_CMN_INNER); |
| if (encap_ifnum < 0) { |
| nss_dtlsmgr_warn("%px: failed to allocate encap dynamic interface(%u)", drv, encap_ifnum); |
| return NULL; |
| } |
| |
| decap_ifnum = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_DTLS_CMN_OUTER); |
| if (decap_ifnum < 0) { |
| nss_dtlsmgr_warn("%px: failed to allocate decap dynamic interface(%u)", drv, decap_ifnum); |
| goto dealloc_encap_node; |
| } |
| |
| nss_dtlsmgr_trace("dynamic interfaces, encap(%u), decap(%u)", encap_ifnum, decap_ifnum); |
| |
| dev = alloc_netdev(sizeof(*ctx), "dtls%d", NET_NAME_ENUM, nss_dtlsmgr_ctx_dev_setup); |
| if (!dev) { |
| nss_dtlsmgr_warn("%px: unable to allocate dtls device", ctx); |
| goto dealloc_decap_node; |
| } |
| |
| ctx = netdev_priv(dev); |
| ctx->dev = dev; |
| rwlock_init(&ctx->lock); |
| |
| NSS_DTLSMGR_SET_MAGIC(ctx, NSS_DTLSMGR_CTX_MAGIC); |
| |
| error = nss_dtlsmgr_ctx_create_encap(ctx, encap_ifnum, decap_ifnum, cfg); |
| if (error < 0) { |
| nss_dtlsmgr_warn("%px: unable to create encap context, error(%d)", ctx, error); |
| goto free_dev; |
| } |
| |
| error = nss_dtlsmgr_ctx_create_decap(ctx, decap_ifnum, encap_ifnum, cfg); |
| if (error < 0) { |
| nss_dtlsmgr_warn("%px: unable to create decap context, error(%d)", ctx, error); |
| goto destroy_encap; |
| } |
| |
| /* |
| * Set the needed headroom and tailroom as a multiple of 4 bytes |
| * so that the skb data pointer remains 4 byte aligned when the |
| * headroom/tailroom is adjusted. |
| */ |
| dev->needed_headroom = NSS_DTLSMGR_NEEDED_HEADROOM_SZ; |
| dev->needed_tailroom = NSS_DTLSMGR_NEEDED_TAILROOM_SZ; |
| |
| ctx->app_data = cfg->app_data; |
| ctx->notify_cb = cfg->notify; |
| ctx->data_cb = cfg->data; |
| |
| /* |
| * If, the user has not provided the callback function then |
| * we will register the default callback handler |
| */ |
| if (!ctx->data_cb) { |
| ctx->data_cb = nss_dtlsmgr_ctx_dev_data_callback; |
| ctx->app_data = ctx; |
| } |
| |
| error = register_netdev(dev); |
| if (error < 0) { |
| nss_dtlsmgr_warn("%px: unable register net_device(%s)", ctx, dev->name); |
| goto destroy_decap; |
| } |
| |
| dev->mtu = dev->mtu - (ctx->encap.headroom + ctx->encap.tailroom); |
| |
| nss_dtlsmgr_trace("%px: dtls session(%s) created, encap(%u), decap(%u)", |
| ctx, dev->name, ctx->encap.ifnum, ctx->decap.ifnum); |
| |
| if (nss_dtlsmgr_create_debugfs(ctx)) { |
| nss_dtlsmgr_warn("Failed to create debugfs for ctx(%px)", ctx); |
| } |
| |
| return dev; |
| |
| destroy_decap: |
| nss_dtlsmgr_ctx_deconfigure(ctx, &ctx->decap); |
| |
| destroy_encap: |
| nss_dtlsmgr_ctx_deconfigure(ctx, &ctx->encap); |
| |
| free_dev: |
| free_netdev(dev); |
| |
| dealloc_decap_node: |
| nss_dynamic_interface_dealloc_node(decap_ifnum, NSS_DYNAMIC_INTERFACE_TYPE_DTLS_CMN_OUTER); |
| |
| dealloc_encap_node: |
| nss_dynamic_interface_dealloc_node(encap_ifnum, NSS_DYNAMIC_INTERFACE_TYPE_DTLS_CMN_INNER); |
| return NULL; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_create); |
| |
| /* |
| * nss_dtlsmgr_session_destroy() |
| * Destroy DTLS session |
| */ |
| nss_dtlsmgr_status_t nss_dtlsmgr_session_destroy(struct net_device *dev) |
| { |
| struct nss_dtlsmgr_ctx *ctx = netdev_priv(dev); |
| NSS_DTLSMGR_VERIFY_MAGIC(ctx); |
| |
| /* |
| * Reset the callback handlers atomically |
| */ |
| xchg(&ctx->notify_cb, NULL); |
| xchg(&ctx->data_cb, NULL); |
| |
| nss_dtlsmgr_trace("%px: destroying encap(%u) and decap(%u) sessions", |
| ctx, ctx->encap.ifnum, ctx->decap.ifnum); |
| |
| if (!nss_dtlsmgr_ctx_deconfigure(ctx, &ctx->encap)) { |
| nss_dtlsmgr_warn("%px: unable to deconfigure encap", ctx); |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| if (!nss_dtlsmgr_ctx_deconfigure(ctx, &ctx->decap)) { |
| nss_dtlsmgr_warn("%px: unable to deconfigure decap", ctx); |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| NSS_DTLSMGR_SET_MAGIC(ctx, 0); |
| |
| unregister_netdev(dev); |
| |
| return NSS_DTLSMGR_OK; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_destroy); |
| |
| /* |
| * nss_dtlsmgr_session_update_encap() |
| * Update the encapsulation crypto keys. |
| */ |
| nss_dtlsmgr_status_t nss_dtlsmgr_session_update_encap(struct net_device *dev, struct nss_dtlsmgr_config_update *cfg) |
| { |
| struct nss_dtlsmgr_ctx *ctx = netdev_priv(dev); |
| struct nss_dtlsmgr_ctx_data *data = &ctx->encap; |
| struct nss_dtlsmgr_dtls_data *dtls, *prev_dtls; |
| |
| NSS_DTLSMGR_VERIFY_MAGIC(ctx); |
| |
| dtls = nss_dtlsmgr_ctx_alloc_dtls(ctx, &ctx->encap, &cfg->crypto); |
| if (!dtls) { |
| nss_dtlsmgr_warn("%px: unable to update encap context data", ctx); |
| return NSS_DTLSMGR_FAIL_NOMEM; |
| } |
| |
| /* |
| * Get the first entry in the list to compare the crypto key lengths |
| */ |
| prev_dtls = list_first_entry_or_null(&data->dtls_active, struct nss_dtlsmgr_dtls_data, list); |
| if (!prev_dtls) { |
| nss_dtlsmgr_warn("%px: dtls list is emtpy\n", ctx); |
| return NSS_DTLSMGR_FAIL_NOCRYPTO; |
| } |
| |
| /* |
| * If the new keys lengths are longer, then there isn't enough headroom and tailroom. |
| */ |
| BUG_ON(prev_dtls->iv_len < dtls->iv_len); |
| BUG_ON(prev_dtls->blk_len < dtls->blk_len); |
| BUG_ON(prev_dtls->hash_len < dtls->hash_len); |
| |
| nss_dtlsmgr_trace("%px: encap context update allocated (%u)", ctx, ctx->encap.ifnum); |
| |
| dtls->epoch = cfg->epoch; |
| dtls->window_size = cfg->window_size; |
| |
| if (!nss_dtlsmgr_ctx_configure_dtls(&ctx->encap, dtls)) { |
| nss_dtlsmgr_warn("%px: unable to configure encap dtls", ctx); |
| nss_dtlsmgr_ctx_free_dtls(dtls); |
| return NSS_DTLSMGR_FAIL_MESSAGE; |
| } |
| |
| write_lock(&ctx->lock); |
| list_add_tail(&dtls->list, &ctx->encap.dtls_active); |
| write_unlock(&ctx->lock); |
| |
| nss_dtlsmgr_trace("%px: encap context update done", ctx); |
| return NSS_DTLSMGR_OK; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_update_encap); |
| |
| /* |
| * nss_dtlsmgr_session_update_decap() |
| * Update the decapsulation crypto keys. |
| */ |
| nss_dtlsmgr_status_t nss_dtlsmgr_session_update_decap(struct net_device *dev, struct nss_dtlsmgr_config_update *cfg) |
| { |
| struct nss_dtlsmgr_ctx *ctx = netdev_priv(dev); |
| struct nss_dtlsmgr_dtls_data *dtls; |
| |
| NSS_DTLSMGR_VERIFY_MAGIC(ctx); |
| |
| dtls = nss_dtlsmgr_ctx_alloc_dtls(ctx, &ctx->decap, &cfg->crypto); |
| if (!dtls) { |
| nss_dtlsmgr_warn("%px: unable to update decap context data", ctx); |
| return NSS_DTLSMGR_FAIL_NOMEM; |
| } |
| |
| nss_dtlsmgr_trace("%px: decap context update allocated (%u)", ctx, ctx->decap.ifnum); |
| |
| dtls->epoch = cfg->epoch; |
| dtls->window_size = cfg->window_size; |
| |
| if (!nss_dtlsmgr_ctx_configure_dtls(&ctx->decap, dtls)) { |
| nss_dtlsmgr_warn("%px: unable to configure decap dtls", ctx); |
| nss_dtlsmgr_ctx_free_dtls(dtls); |
| return NSS_DTLSMGR_FAIL_MESSAGE; |
| } |
| |
| write_lock(&ctx->lock); |
| list_add_tail(&dtls->list, &ctx->decap.dtls_active); |
| write_unlock(&ctx->lock); |
| |
| nss_dtlsmgr_trace("%px: decap context update done", ctx); |
| return NSS_DTLSMGR_OK; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_update_decap); |
| |
| /* |
| * nss_dtlsmgr_session_switch_encap() |
| * Send a message to encapsulation DTLS interface to switch to the new crypto keys. |
| */ |
| bool nss_dtlsmgr_session_switch_encap(struct net_device *dev) |
| { |
| struct nss_dtlsmgr_ctx *ctx = netdev_priv(dev); |
| struct nss_dtlsmgr_ctx_data *data = &ctx->encap; |
| |
| NSS_DTLSMGR_VERIFY_MAGIC(ctx); |
| |
| if (!nss_dtlsmgr_session_switch(ctx, data)) { |
| nss_dtlsmgr_warn("%px: failed to send encap switch_dtls(%u)", ctx, data->ifnum); |
| return false; |
| } |
| |
| nss_dtlsmgr_trace("%px: encap(%u) cipher switch done", ctx, data->ifnum); |
| return true; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_switch_encap); |
| |
| /* |
| * nss_dtlsmgr_session_switch_decap() |
| * Send a message to decapsulation DTLS interface to switch to the new crypto keys. |
| */ |
| bool nss_dtlsmgr_session_switch_decap(struct net_device *dev) |
| { |
| struct nss_dtlsmgr_ctx *ctx = netdev_priv(dev); |
| struct nss_dtlsmgr_ctx_data *data = &ctx->decap; |
| |
| NSS_DTLSMGR_VERIFY_MAGIC(ctx); |
| |
| if (!nss_dtlsmgr_session_switch(ctx, data)) { |
| nss_dtlsmgr_warn("%px: failed to send decap switch_dtls(%u)", ctx, data->ifnum); |
| return false; |
| } |
| |
| nss_dtlsmgr_trace("%px: decap(%u) cipher switch done", ctx, data->ifnum); |
| return true; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_switch_decap); |
| |
| /* |
| * nss_dtlsmgr_get_interface() |
| * Returns NSS DTLS interface number for encap/decap on success. |
| */ |
| int32_t nss_dtlsmgr_get_interface(struct net_device *dev, enum nss_dtlsmgr_interface_type type) |
| { |
| int32_t ifnum; |
| |
| switch (type) { |
| case NSS_DTLSMGR_INTERFACE_TYPE_INNER: |
| ifnum = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_DTLS_CMN_INNER); |
| break; |
| |
| case NSS_DTLSMGR_INTERFACE_TYPE_OUTER: |
| ifnum = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_DTLS_CMN_OUTER); |
| break; |
| |
| default: |
| nss_dtlsmgr_warn("%px: invalid interface type %d", dev, type); |
| return -EINVAL; |
| } |
| |
| if (ifnum < 0) { |
| nss_dtlsmgr_warn("%px: couldn't find DTLS interface number (%d)", dev, ifnum); |
| return ifnum; |
| } |
| |
| return nss_dtls_cmn_get_ifnum(ifnum); |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_get_interface); |