| /* |
| ************************************************************************** |
| * Copyright (c) 2016-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_connmgr_dtls.c |
| * NSS DTLS Manager |
| */ |
| |
| #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 <net/ipv6.h> |
| #include <linux/if_arp.h> |
| #include <linux/etherdevice.h> |
| #include <linux/atomic.h> |
| |
| #include <nss_api_if.h> |
| #include <nss_dynamic_interface.h> |
| |
| #include "nss_connmgr_dtls.h" |
| |
| /* |
| * Global DTLS context |
| */ |
| static struct nss_dtlsmgr_ctx g_ctx; |
| |
| static struct nss_dtlsmgr_crypto_algo dtlsmgr_algo[NSS_DTLSMGR_ALGO_MAX] = { |
| [NSS_DTLSMGR_ALGO_AES_CBC_SHA1_HMAC] = { |
| .cipher_algo = NSS_CRYPTO_CIPHER_AES_CBC, |
| .auth_algo = NSS_CRYPTO_AUTH_SHA1_HMAC, |
| .hash_len = NSS_CRYPTO_MAX_HASHLEN_SHA1 , |
| .iv_len = NSS_CRYPTO_MAX_IVLEN_AES, |
| }, |
| [NSS_DTLSMGR_ALGO_AES_CBC_SHA256_HMAC] = { |
| .cipher_algo = NSS_CRYPTO_CIPHER_AES_CBC, |
| .auth_algo = NSS_CRYPTO_AUTH_SHA256_HMAC, |
| .hash_len = NSS_CRYPTO_MAX_HASHLEN_SHA256 , |
| .iv_len = NSS_CRYPTO_MAX_IVLEN_AES, |
| }, |
| [NSS_DTLSMGR_ALGO_3DES_CBC_SHA1_HMAC] = { |
| .cipher_algo = NSS_CRYPTO_CIPHER_DES, |
| .auth_algo = NSS_CRYPTO_AUTH_SHA1_HMAC, |
| .hash_len = NSS_CRYPTO_MAX_HASHLEN_SHA1 , |
| .iv_len = NSS_CRYPTO_MAX_IVLEN_DES, |
| }, |
| [NSS_DTLSMGR_ALGO_3DES_CBC_SHA256_HMAC] = { |
| .cipher_algo = NSS_CRYPTO_CIPHER_DES, |
| .auth_algo = NSS_CRYPTO_AUTH_SHA256_HMAC, |
| .hash_len = NSS_CRYPTO_MAX_HASHLEN_SHA256, |
| .iv_len = NSS_CRYPTO_MAX_IVLEN_DES, |
| } |
| }; |
| |
| /* |
| * nss_dtlsmgr_session_insert() |
| * Insert a DTLS session into global list of sessions. |
| * Must be called with global context lock held. |
| */ |
| static bool nss_dtlsmgr_session_insert(struct nss_dtlsmgr_session *s) |
| { |
| int32_t i; |
| |
| assert_spin_locked(&g_ctx.lock); |
| |
| for (i = 0; i < NSS_MAX_DTLS_SESSIONS; i++) { |
| if (g_ctx.session[i] == NULL) { |
| g_ctx.session[i] = s; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* |
| * nss_dtlsmgr_session_remove() |
| * Remove a DTLS session from list of sessions. |
| * Must be called with global context lock held. |
| */ |
| static struct nss_dtlsmgr_session *nss_dtlsmgr_session_remove(uint32_t sif) |
| { |
| int32_t i; |
| struct nss_dtlsmgr_session *s; |
| |
| assert_spin_locked(&g_ctx.lock); |
| |
| for (i = 0; i < NSS_MAX_DTLS_SESSIONS; i++) { |
| s = g_ctx.session[i]; |
| if (!s) |
| continue; |
| |
| nss_dtlsmgr_assert(s->magic == NSS_DTLSMGR_SESSION_MAGIC); |
| if (s->nss_dtls_if == sif) { |
| g_ctx.session[i] = NULL; |
| return s; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * nss_dtlsmgr_session_find() |
| * Find a DTLS session from list of sessions. |
| * Must be called with global context lock held. |
| */ |
| static struct nss_dtlsmgr_session *nss_dtlsmgr_session_find(uint32_t sif) |
| { |
| int32_t i; |
| struct nss_dtlsmgr_session *s; |
| |
| assert_spin_locked(&g_ctx.lock); |
| |
| for (i = 0; i < NSS_MAX_DTLS_SESSIONS; i++) { |
| s = g_ctx.session[i]; |
| if (!s) |
| continue; |
| |
| nss_dtlsmgr_assert(s->magic == NSS_DTLSMGR_SESSION_MAGIC); |
| if (s->nss_dtls_if == sif) |
| return s; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * nss_dtlsmgr_session_cleanup() |
| * Cleanup DTLS session |
| */ |
| static void nss_dtlsmgr_session_cleanup(struct nss_dtlsmgr_session *ds) |
| { |
| nss_crypto_status_t crypto_status; |
| |
| nss_dtlsmgr_info("%px: DTLS session I/F %u cleanup\n", |
| &g_ctx, ds->nss_dtls_if); |
| |
| nss_dtls_unregister_if(ds->nss_dtls_if); |
| |
| nss_dtlsmgr_netdev_destroy(ds); |
| |
| nss_dynamic_interface_dealloc_node(ds->nss_dtls_if, |
| NSS_DYNAMIC_INTERFACE_TYPE_DTLS); |
| |
| crypto_status = nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->crypto_idx_encap); |
| if (crypto_status != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: dtls I/F:%u, unable to free crypto session id:%d\n", |
| &g_ctx, ds->nss_dtls_if, ds->crypto_idx_encap); |
| } |
| |
| crypto_status = nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->crypto_idx_decap); |
| if (crypto_status != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: dtls I/F:%u, unable to free crypto session id:%d\n", |
| &g_ctx, ds->nss_dtls_if, ds->crypto_idx_decap); |
| } |
| |
| if (ds->cidx_decap_pending != NSS_CRYPTO_MAX_IDXS) { |
| crypto_status = nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->cidx_decap_pending); |
| if (crypto_status != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: dtls I/F:%u, unable to free crypto session id:%d\n", &g_ctx, ds->nss_dtls_if, ds->cidx_decap_pending); |
| } |
| } |
| |
| if (ds->cidx_encap_pending != NSS_CRYPTO_MAX_IDXS) { |
| crypto_status = nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->cidx_encap_pending); |
| if (crypto_status != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: dtls I/F:%u, unable to free crypto session id:%d\n", &g_ctx, ds->nss_dtls_if, ds->cidx_encap_pending); |
| } |
| } |
| |
| ds->magic = 0; |
| kfree(ds); |
| } |
| |
| /* |
| * nss_dtlsmgr_session_find_and_ref() |
| * Find a DTLS session from list of sessions and |
| * increments reference count. Must be called with |
| * global context lock held. |
| */ |
| static struct nss_dtlsmgr_session *nss_dtlsmgr_session_find_and_ref(uint32_t sif) |
| { |
| struct nss_dtlsmgr_session *s; |
| |
| s = nss_dtlsmgr_session_find(sif); |
| if (atomic_inc_and_test(&s->ref)) { |
| nss_dtlsmgr_assert(false); |
| } |
| |
| return s; |
| } |
| |
| /* |
| * nss_dtlsmgr_session_ref_dec() |
| * Decrement reference count of a DTLS session. |
| * Perform session cleanup if reference count falls to zero. |
| */ |
| static void nss_dtlsmgr_session_ref_dec(struct nss_dtlsmgr_session *s) |
| { |
| if (atomic_dec_and_test(&s->ref)) { |
| nss_dtlsmgr_session_cleanup(s); |
| } |
| } |
| |
| /* |
| * nss_connmgr_dtls_data_receive() |
| * Handler to receive packets from NSS. |
| */ |
| static void nss_connmgr_dtls_data_receive(struct net_device *dev, |
| struct sk_buff *skb, |
| struct napi_struct *napi) |
| { |
| struct nss_dtlsmgr_netdev_priv *priv; |
| struct nss_dtlsmgr_session *s; |
| __be32 meta; |
| |
| BUG_ON(dev == NULL); |
| BUG_ON(skb == NULL); |
| |
| dev_hold(dev); |
| priv = netdev_priv(dev); |
| s = priv->s; |
| |
| /* |
| * Get DTLS metadata |
| */ |
| meta = *(__be32 *)skb->data; |
| meta = ntohl(meta); |
| if (NSS_DTLSMGR_METADATA_CTYPE(meta) != NSS_DTLSMGR_CTYPE_APP) { |
| nss_dtlsmgr_info("%px: Dropping non app dtls pkt\n", skb); |
| dev_kfree_skb_any(skb); |
| dev_put(dev); |
| return; |
| } |
| |
| if (NSS_DTLSMGR_METADATA_ERROR(meta) != NSS_DTLSMGR_METADATA_ERROR_OK) { |
| nss_dtlsmgr_info("%px: Dropping error pkt\n", skb); |
| dev_kfree_skb_any(skb); |
| dev_put(dev); |
| return; |
| } |
| |
| /* |
| * Remove four bytes at start of |
| * buffer containing the DTLS metadata. |
| */ |
| skb_pull(skb, NSS_DTLSMGR_METADATA_LEN); |
| |
| skb_reset_network_header(skb); |
| skb->pkt_type = PACKET_HOST; |
| skb->skb_iif = dev->ifindex; |
| skb->dev = dev; |
| if (s->flags & NSS_DTLSMGR_HDR_IPV6) |
| skb->protocol = htons(ETH_P_IPV6); |
| else |
| skb->protocol = htons(ETH_P_IP); |
| |
| netif_receive_skb(skb); |
| dev_put(dev); |
| } |
| |
| /* |
| * nss_connmgr_dtls_event_receive() |
| * Event Callback to receive events from NSS |
| */ |
| static void nss_connmgr_dtls_event_receive(void *if_ctx, |
| struct nss_dtls_msg *tnlmsg) |
| { |
| struct nss_dtlsmgr_session *ds = (struct nss_dtlsmgr_session *)if_ctx; |
| struct nss_dtlsmgr_session_stats_update stats; |
| struct nss_dtls_session_stats *msg_stats; |
| |
| spin_lock(&g_ctx.lock); |
| ds = nss_dtlsmgr_session_find_and_ref(tnlmsg->cm.interface); |
| spin_unlock(&g_ctx.lock); |
| |
| if (!ds) { |
| return; |
| } |
| |
| switch (tnlmsg->cm.type) { |
| case NSS_DTLS_MSG_SESSION_STATS: |
| if (ds->stats_update_cb == NULL) |
| break; |
| |
| memset(&stats, 0, sizeof(struct nss_dtlsmgr_session_stats_update)); |
| msg_stats = &tnlmsg->msg.stats; |
| |
| stats.tx_pkts = msg_stats->node_stats.tx_packets; |
| stats.rx_pkts = msg_stats->node_stats.rx_packets; |
| stats.rx_dropped = nss_cmn_rx_dropped_sum(&msg_stats->node_stats); |
| stats.tx_auth_done = msg_stats->tx_auth_done; |
| stats.rx_auth_done = msg_stats->rx_auth_done; |
| stats.tx_cipher_done = msg_stats->tx_cipher_done; |
| stats.rx_cipher_done = msg_stats->rx_cipher_done; |
| stats.tx_cbuf_alloc_fail = msg_stats->tx_cbuf_alloc_fail; |
| stats.rx_cbuf_alloc_fail = msg_stats->rx_cbuf_alloc_fail; |
| stats.tx_cenqueue_fail = msg_stats->tx_cenqueue_fail; |
| stats.rx_cenqueue_fail = msg_stats->rx_cenqueue_fail; |
| stats.tx_dropped_hroom = msg_stats->tx_dropped_hroom; |
| stats.tx_dropped_troom = msg_stats->tx_dropped_troom; |
| stats.tx_forward_enqueue_fail = msg_stats->tx_forward_enqueue_fail; |
| stats.rx_forward_enqueue_fail = msg_stats->rx_forward_enqueue_fail; |
| stats.rx_invalid_version = msg_stats->rx_invalid_version; |
| stats.rx_invalid_epoch = msg_stats->rx_invalid_epoch; |
| stats.rx_malformed = msg_stats->rx_malformed; |
| stats.rx_cipher_fail = msg_stats->rx_cipher_fail; |
| stats.rx_auth_fail = msg_stats->rx_auth_fail; |
| stats.rx_capwap_classify_fail = msg_stats->rx_capwap_classify_fail; |
| stats.rx_replay_fail = msg_stats->rx_replay_fail; |
| stats.rx_replay_duplicate = msg_stats->rx_replay_duplicate; |
| stats.rx_replay_out_of_window = msg_stats->rx_replay_out_of_window; |
| stats.outflow_queue_full = msg_stats->outflow_queue_full; |
| stats.decap_queue_full = msg_stats->decap_queue_full; |
| stats.pbuf_alloc_fail = msg_stats->pbuf_alloc_fail; |
| stats.pbuf_copy_fail = msg_stats->pbuf_copy_fail; |
| stats.epoch = msg_stats->epoch; |
| stats.tx_seq_high = msg_stats->tx_seq_high; |
| stats.tx_seq_low = msg_stats->tx_seq_low; |
| |
| ds->stats_update_cb(ds->nss_dtls_if, &stats); |
| break; |
| |
| default: |
| nss_dtlsmgr_info("%px: Unknown Event from NSS\n", &g_ctx); |
| break; |
| } |
| |
| nss_dtlsmgr_session_ref_dec(ds); |
| } |
| |
| /* |
| * nss_dtlsmgr_alloc_crypto() |
| * Allocate a crypto session and update encrypt/decrypt session parameters. |
| */ |
| nss_dtlsmgr_status_t nss_dtlsmgr_alloc_crypto(struct nss_dtlsmgr_crypto *crypto, uint32_t *crypto_idx, bool encrypt) |
| { |
| struct nss_dtlsmgr_crypto_algo *algo; |
| struct nss_crypto_params params; |
| struct nss_crypto_key cipher; |
| struct nss_crypto_key auth; |
| |
| memset(&cipher, 0, sizeof(struct nss_crypto_key)); |
| memset(&auth, 0, sizeof(struct nss_crypto_key)); |
| |
| if (crypto->algo >= NSS_DTLSMGR_ALGO_MAX) { |
| nss_dtlsmgr_info("%px: invalid algorithm type %d", &g_ctx, crypto->algo); |
| return NSS_DTLSMGR_FAIL_NOCRYPTO; |
| } |
| |
| algo = &dtlsmgr_algo[crypto->algo]; |
| |
| cipher.algo = algo->cipher_algo; |
| cipher.key = crypto->cipher_key.data; |
| cipher.key_len = crypto->cipher_key.len; |
| |
| auth.algo = algo->auth_algo; |
| auth.key = crypto->auth_key.data; |
| auth.key_len = crypto->auth_key.len; |
| |
| if (nss_crypto_session_alloc(g_ctx.crypto_hdl, &cipher, &auth, crypto_idx) != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: DTLS crypto alloc failed\n", &g_ctx); |
| return NSS_DTLSMGR_FAIL_NOCRYPTO; |
| } |
| |
| /* |
| * Update crypto session |
| */ |
| memset(¶ms, 0, sizeof(struct nss_crypto_params)); |
| params.cipher_skip = NSS_DTLSMGR_HDR_LEN + algo->iv_len; |
| params.auth_skip = 0; |
| params.req_type = (encrypt ? NSS_CRYPTO_REQ_TYPE_ENCRYPT : NSS_CRYPTO_REQ_TYPE_DECRYPT); |
| params.req_type |= NSS_CRYPTO_REQ_TYPE_AUTH; |
| |
| if (nss_crypto_session_update(g_ctx.crypto_hdl, *crypto_idx, ¶ms) != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: failed to update crypto session %d", &g_ctx, *crypto_idx); |
| nss_crypto_session_free(g_ctx.crypto_hdl, *crypto_idx); |
| return NSS_DTLSMGR_FAIL_NOCRYPTO; |
| } |
| |
| nss_dtlsmgr_info("%px: auth_skip:%d cipher_skip:%d\n", &g_ctx, params.auth_skip, params.cipher_skip); |
| return NSS_DTLSMGR_OK; |
| } |
| |
| /* |
| * 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_session *ds; |
| struct nss_dtls_msg dtlsmsg; |
| int32_t i = 0; |
| struct nss_dtlsmgr_crypto_algo *encap_algo, *decap_algo; |
| struct nss_dtls_session_configure *scfg; |
| nss_tx_status_t status; |
| nss_crypto_status_t crypto_status; |
| enum nss_dtlsmgr_status ret = NSS_DTLSMGR_FAIL; |
| uint32_t features = 0; |
| uint32_t mtu_adjust; |
| |
| if ((cfg->encap.ver != NSS_DTLSMGR_VERSION_1_0) && (cfg->encap.ver != NSS_DTLSMGR_VERSION_1_2)) { |
| nss_dtlsmgr_warn("%px: Invalid DTLS version\n", &g_ctx); |
| return NULL; |
| } |
| |
| /* |
| * Allocate memory for new dtls session |
| */ |
| ds = kzalloc(sizeof(struct nss_dtlsmgr_session), GFP_KERNEL); |
| if (!ds) { |
| nss_dtlsmgr_info("%px: DTLS client allocation failed\n", &g_ctx); |
| return NULL; |
| } |
| |
| /* |
| * Create crypto session for encap |
| */ |
| ret = nss_dtlsmgr_alloc_crypto(&cfg->encap.crypto, &ds->crypto_idx_encap, true); |
| if (ret != NSS_DTLSMGR_OK) { |
| nss_dtlsmgr_info("failed to create encap session %d", ret); |
| goto dtls_crypto_encap_alloc_fail; |
| } |
| |
| /* |
| * Create crypto session for decap |
| */ |
| ret = nss_dtlsmgr_alloc_crypto(&cfg->decap.crypto, &ds->crypto_idx_decap, false); |
| if (ret != NSS_DTLSMGR_OK) { |
| nss_dtlsmgr_info("failed to create decap session %d", ret); |
| goto dtls_crypto_decap_alloc_fail; |
| } |
| |
| /* |
| * Allocate NSS dynamic interface |
| */ |
| ds->nss_dtls_if = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_DTLS); |
| if (ds->nss_dtls_if == -1) { |
| nss_dtlsmgr_info("%px: DTLS dynamic I/F alloc failed\n", &g_ctx); |
| goto dtls_dynamic_if_alloc_fail; |
| } |
| |
| /* |
| * Create netdevice |
| */ |
| if (nss_dtlsmgr_netdev_create(ds) != NSS_DTLSMGR_OK) { |
| nss_dtlsmgr_info("%px: DTLS netdev creation failed\n", &g_ctx); |
| goto dtls_netdev_create_fail; |
| } |
| |
| /* |
| * Register NSS DTLS I/F |
| */ |
| ds->nss_ctx = nss_dtls_register_if(ds->nss_dtls_if, |
| nss_connmgr_dtls_data_receive, |
| nss_connmgr_dtls_event_receive, |
| ds->netdev, features, |
| (void *)ds); |
| if (ds->nss_ctx == NULL) { |
| nss_dtlsmgr_info("%px: DTLS dynamic I/F register failed\n", &g_ctx); |
| goto dtls_dynamic_if_register_fail; |
| } |
| |
| /* |
| * Initialize DTLS manager session |
| */ |
| ds->magic = NSS_DTLSMGR_SESSION_MAGIC; |
| ds->flags = cfg->flags; |
| ds->ver = cfg->encap.ver; |
| ds->sport = cfg->encap.sport; |
| ds->dport = cfg->encap.dport; |
| ds->epoch = cfg->encap.epoch; |
| ds->ip_ttl = cfg->encap.ip_ttl; |
| ds->nss_app_if = cfg->decap.nexthop_ifnum; |
| ds->window_size = cfg->decap.window_size; |
| ds->stats_update_cb = NULL; /* TODO */ |
| ds->cidx_encap_pending = NSS_CRYPTO_MAX_IDXS; |
| ds->cidx_decap_pending = NSS_CRYPTO_MAX_IDXS; |
| atomic_set(&ds->ref, 1); |
| |
| if (ds->flags & NSS_DTLSMGR_HDR_IPV6) { |
| for (i = 0; i < 4 ; i++) { |
| ds->sip.ipv6[i] = cfg->encap.sip[i]; |
| ds->dip.ipv6[i] = cfg->encap.dip[i]; |
| } |
| } else { |
| ds->sip.ipv4 = cfg->encap.sip[0]; |
| ds->dip.ipv4 = cfg->encap.dip[0]; |
| } |
| |
| /* |
| * Insert session into DTLS manager list |
| */ |
| spin_lock_bh(&g_ctx.lock); |
| if (!nss_dtlsmgr_session_insert(ds)) { |
| spin_unlock_bh(&g_ctx.lock); |
| goto dtls_session_insert_fail; |
| } |
| spin_unlock_bh(&g_ctx.lock); |
| |
| /* |
| * Send DTLS configure message to NSS |
| */ |
| memset(&dtlsmsg, 0, sizeof(struct nss_dtls_msg)); |
| nss_dtls_msg_init(&dtlsmsg, ds->nss_dtls_if, |
| NSS_DTLS_MSG_SESSION_CONFIGURE, |
| sizeof(struct nss_dtls_session_configure), |
| NULL, NULL); |
| |
| encap_algo = &dtlsmgr_algo[cfg->encap.crypto.algo]; |
| decap_algo = &dtlsmgr_algo[cfg->decap.crypto.algo]; |
| |
| scfg = &dtlsmsg.msg.cfg; |
| scfg->ver = ds->ver; |
| scfg->flags = ds->flags; |
| scfg->crypto_idx_encap = ds->crypto_idx_encap; |
| scfg->crypto_idx_decap = ds->crypto_idx_decap; |
| scfg->iv_len_encap = encap_algo->iv_len; |
| scfg->iv_len_decap = decap_algo->iv_len; |
| scfg->hash_len_encap = encap_algo->hash_len; |
| scfg->hash_len_decap = decap_algo->hash_len; |
| scfg->cipher_algo_encap = encap_algo->cipher_algo; |
| scfg->cipher_algo_decap = decap_algo->cipher_algo; |
| scfg->auth_algo_encap = encap_algo->auth_algo; |
| scfg->auth_algo_decap = decap_algo->auth_algo; |
| scfg->nss_app_if = ds->nss_app_if; |
| scfg->sport = ds->sport; |
| scfg->dport = ds->dport; |
| scfg->epoch = ds->epoch; |
| scfg->window_size = ds->window_size; |
| scfg->oip_ttl = ds->ip_ttl; |
| |
| if (ds->flags & NSS_DTLSMGR_HDR_IPV6) { |
| for (i = 0; i < 4; i++) { |
| scfg->sip[i] = ds->sip.ipv6[i]; |
| scfg->dip[i] = ds->dip.ipv6[i]; |
| } |
| } else { |
| scfg->sip[0] = ds->sip.ipv4; |
| scfg->dip[0] = ds->dip.ipv4; |
| } |
| |
| status = nss_dtls_tx_msg_sync(ds->nss_ctx, &dtlsmsg); |
| if (status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_info("%px: DTLS cfg msg tx failed\n", &g_ctx); |
| goto dtls_msg_tx_fail; |
| } |
| |
| /* |
| * Adjust MTU of netdev |
| */ |
| mtu_adjust = NSS_DTLSMGR_HDR_LEN; |
| mtu_adjust += sizeof(struct udphdr); |
| |
| if (ds->flags & NSS_DTLSMGR_HDR_IPV6) |
| mtu_adjust += sizeof(struct ipv6hdr); |
| else |
| mtu_adjust += sizeof(struct iphdr); |
| |
| if (ds->flags & NSS_DTLSMGR_HDR_CAPWAP) |
| mtu_adjust += NSS_DTLSMGR_CAPWAPHDR_LEN; |
| |
| mtu_adjust += ((scfg->iv_len_encap * 2) + scfg->hash_len_encap); |
| |
| ds->netdev->mtu -= mtu_adjust; |
| |
| nss_dtlsmgr_info("%px: NSS DTLS session I/F:%d(%s) created\n", |
| &g_ctx, ds->nss_dtls_if, ds->netdev->name); |
| |
| return ds->netdev; |
| |
| dtls_msg_tx_fail: |
| spin_lock_bh(&g_ctx.lock); |
| nss_dtlsmgr_session_remove(ds->nss_dtls_if); |
| spin_unlock_bh(&g_ctx.lock); |
| |
| dtls_session_insert_fail: |
| nss_dtls_unregister_if(ds->nss_dtls_if); |
| |
| dtls_dynamic_if_register_fail: |
| nss_dtlsmgr_netdev_destroy(ds); |
| |
| dtls_netdev_create_fail: |
| nss_dynamic_interface_dealloc_node(ds->nss_dtls_if, |
| NSS_DYNAMIC_INTERFACE_TYPE_DTLS); |
| |
| dtls_dynamic_if_alloc_fail: |
| crypto_status = nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->crypto_idx_decap); |
| if (crypto_status != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: dtls I/F:%u, unable to free crypto session id:%d\n", &g_ctx, ds->nss_dtls_if, ds->crypto_idx_decap); |
| } |
| |
| dtls_crypto_decap_alloc_fail: |
| crypto_status = nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->crypto_idx_encap); |
| if (crypto_status != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: dtls I/F:%u, unable to free crypto session id:%d\n", &g_ctx, ds->nss_dtls_if, ds->crypto_idx_encap); |
| } |
| |
| dtls_crypto_encap_alloc_fail: |
| ds->magic = 0; |
| kfree(ds); |
| 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_netdev_priv *priv; |
| struct nss_dtlsmgr_session *ds; |
| nss_tx_status_t nss_status; |
| struct nss_dtls_msg dtlsmsg; |
| |
| priv = netdev_priv(dev); |
| ds = priv->s; |
| |
| if (!ds) { |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| /* |
| * Send DTLS session destroy command to FW |
| */ |
| memset(&dtlsmsg, 0, sizeof(struct nss_dtls_msg)); |
| nss_dtls_msg_init(&dtlsmsg, (uint16_t)ds->nss_dtls_if, |
| NSS_DTLS_MSG_SESSION_DESTROY, 0, NULL, NULL); |
| |
| nss_status = nss_dtls_tx_msg_sync(ds->nss_ctx, &dtlsmsg); |
| if (nss_status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_warn("%px: Failed to send DTLS session destroy for I/F %u", &g_ctx, ds->nss_dtls_if); |
| |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| spin_lock_bh(&g_ctx.lock); |
| ds = nss_dtlsmgr_session_remove(ds->nss_dtls_if); |
| spin_unlock_bh(&g_ctx.lock); |
| |
| if (!ds) { |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| /* |
| * Decrement reference count so as to drop it to zero |
| */ |
| nss_dtlsmgr_session_ref_dec(ds); |
| |
| nss_dtlsmgr_info("%px: DTLS session I/F %u disabled\n", &g_ctx, ds->nss_dtls_if); |
| |
| return NSS_DTLSMGR_OK; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_destroy); |
| |
| /* |
| * nss_dtlsmgr_session_update_decap() |
| * Update pending decap cipher of a DTLS session. |
| */ |
| nss_dtlsmgr_status_t nss_dtlsmgr_session_update_decap(struct net_device *dev, struct nss_dtlsmgr_config_update *udata) |
| { |
| struct nss_dtlsmgr_netdev_priv *priv; |
| struct nss_dtlsmgr_session *ds; |
| nss_tx_status_t nss_status; |
| nss_crypto_status_t crypto_status; |
| enum nss_dtlsmgr_status ret; |
| struct nss_dtls_msg dtlsmsg; |
| struct nss_dtls_session_cipher_update *update; |
| struct nss_dtlsmgr_crypto_algo *decap_algo; |
| |
| priv = netdev_priv(dev); |
| ds = priv->s; |
| if (!ds) { |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| /* |
| * Free any crypto session, for the decap pending cipher |
| * state, allocated by a previous call to this API but |
| * were subsequently not used for packet processing. |
| */ |
| if (ds->cidx_decap_pending != NSS_CRYPTO_MAX_IDXS) { |
| crypto_status = nss_crypto_session_free(g_ctx.crypto_hdl, ds->cidx_decap_pending); |
| if (crypto_status != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: dtls I/F:%u, unable to free crypto session id:%d\n", |
| &g_ctx, ds->nss_dtls_if, ds->cidx_decap_pending); |
| } |
| |
| ds->cidx_decap_pending = NSS_CRYPTO_MAX_IDXS; |
| } |
| |
| /* |
| * Alloc crypto session for decap |
| */ |
| ret = nss_dtlsmgr_alloc_crypto(&udata->crypto, &ds->cidx_decap_pending, false); |
| if (ret != NSS_DTLSMGR_OK) { |
| nss_dtlsmgr_info("failed to rekey decap session %d", ret); |
| ds->cidx_decap_pending = NSS_CRYPTO_MAX_IDXS; |
| nss_dtlsmgr_session_ref_dec(ds); |
| return NSS_DTLSMGR_FAIL_NOCRYPTO; |
| } |
| |
| decap_algo = &dtlsmgr_algo[udata->crypto.algo]; |
| |
| /* |
| * Initialize DTLS session Rx cipher update message |
| */ |
| memset(&dtlsmsg, 0, sizeof(struct nss_dtls_msg)); |
| update = &dtlsmsg.msg.cipher_update; |
| update->crypto_idx = ds->cidx_decap_pending; |
| update->epoch = udata->epoch; |
| update->iv_len = decap_algo->iv_len; |
| update->hash_len = decap_algo->hash_len; |
| update->auth_algo = decap_algo->auth_algo; |
| update->cipher_algo = decap_algo->cipher_algo; |
| |
| nss_dtls_msg_init(&dtlsmsg, (uint16_t)ds->nss_dtls_if, |
| NSS_DTLS_MSG_REKEY_DECAP_CIPHER_UPDATE, |
| sizeof(struct nss_dtls_session_cipher_update), NULL, NULL); |
| |
| /* |
| * Send DTLS session Rx cipher update command to FW |
| */ |
| nss_status = nss_dtls_tx_msg_sync(ds->nss_ctx, &dtlsmsg); |
| if (nss_status != NSS_TX_SUCCESS) { |
| nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->cidx_decap_pending); |
| ds->cidx_decap_pending = NSS_CRYPTO_MAX_IDXS; |
| nss_dtlsmgr_session_ref_dec(ds); |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| nss_dtlsmgr_session_ref_dec(ds); |
| return NSS_DTLSMGR_OK; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_update_decap); |
| |
| /* |
| * nss_dtlsmgr_session_update_encap() |
| * Update pending encap cipher of a DTLS session. |
| */ |
| nss_dtlsmgr_status_t nss_dtlsmgr_session_update_encap(struct net_device *dev, struct nss_dtlsmgr_config_update *udata) |
| { |
| struct nss_dtlsmgr_netdev_priv *priv; |
| struct nss_dtlsmgr_session *ds; |
| nss_tx_status_t nss_status; |
| nss_crypto_status_t crypto_status; |
| enum nss_dtlsmgr_status ret; |
| struct nss_dtls_msg dtlsmsg; |
| struct nss_dtls_session_cipher_update *update; |
| struct nss_dtlsmgr_crypto_algo *encap_algo; |
| |
| priv = netdev_priv(dev); |
| ds = priv->s; |
| if (!ds) { |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| /* |
| * Free any crypto session, for the encap pending cipher |
| * state, allocated by a previous call to this API but |
| * were subsequently not used for packet processing. |
| */ |
| if (ds->cidx_encap_pending != NSS_CRYPTO_MAX_IDXS) { |
| crypto_status = nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->cidx_encap_pending); |
| if (crypto_status != NSS_CRYPTO_STATUS_OK) { |
| nss_dtlsmgr_info("%px: dtls I/F:%u, unable to free crypto session id:%d\n", &g_ctx, ds->nss_dtls_if, ds->cidx_encap_pending); |
| } |
| |
| ds->cidx_encap_pending = NSS_CRYPTO_MAX_IDXS; |
| } |
| |
| /* |
| * Alloc crypto session for decap |
| */ |
| ret = nss_dtlsmgr_alloc_crypto(&udata->crypto, &ds->cidx_encap_pending, true); |
| if (ret != NSS_DTLSMGR_OK) { |
| nss_dtlsmgr_info("failed to rekey encap session %d", ret); |
| ds->cidx_encap_pending = NSS_CRYPTO_MAX_IDXS; |
| nss_dtlsmgr_session_ref_dec(ds); |
| return NSS_DTLSMGR_FAIL_NOCRYPTO; |
| } |
| |
| encap_algo = &dtlsmgr_algo[udata->crypto.algo]; |
| |
| /* |
| * Initialize DTLS session Tx cipher update message |
| */ |
| memset(&dtlsmsg, 0, sizeof(struct nss_dtls_msg)); |
| update = &dtlsmsg.msg.cipher_update; |
| update->crypto_idx = ds->cidx_encap_pending; |
| update->epoch = udata->epoch; |
| update->iv_len = encap_algo->iv_len; |
| update->hash_len = encap_algo->hash_len; |
| update->auth_algo = encap_algo->auth_algo; |
| update->cipher_algo = encap_algo->cipher_algo; |
| |
| nss_dtls_msg_init(&dtlsmsg, (uint16_t)ds->nss_dtls_if, |
| NSS_DTLS_MSG_REKEY_ENCAP_CIPHER_UPDATE, |
| sizeof(struct nss_dtls_session_cipher_update), NULL, NULL); |
| |
| /* |
| * Send DTLS session Rx cipher update command to FW |
| */ |
| nss_status = nss_dtls_tx_msg_sync(ds->nss_ctx, &dtlsmsg); |
| if (nss_status != NSS_TX_SUCCESS) { |
| nss_crypto_session_free(g_ctx.crypto_hdl, |
| ds->cidx_encap_pending); |
| ds->cidx_encap_pending = NSS_CRYPTO_MAX_IDXS; |
| nss_dtlsmgr_session_ref_dec(ds); |
| return NSS_DTLSMGR_FAIL; |
| } |
| |
| nss_dtlsmgr_session_ref_dec(ds); |
| return NSS_DTLSMGR_OK; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_update_encap); |
| |
| /* |
| * nss_dtlsmgr_session_switch_decap() |
| * Set pending decap cipher state of a DTLS session to current. |
| */ |
| bool nss_dtlsmgr_session_switch_decap(struct net_device *dev) |
| { |
| struct nss_dtlsmgr_netdev_priv *priv; |
| struct nss_dtlsmgr_session *ds; |
| nss_tx_status_t nss_status; |
| struct nss_dtls_msg dtlsmsg; |
| |
| priv = netdev_priv(dev); |
| ds = priv->s; |
| if (!ds) { |
| return false; |
| } |
| |
| memset(&dtlsmsg, 0, sizeof(struct nss_dtls_msg)); |
| nss_dtls_msg_init(&dtlsmsg, (uint16_t)ds->nss_dtls_if, |
| NSS_DTLS_MSG_REKEY_DECAP_CIPHER_SWITCH, |
| 0, NULL, NULL); |
| |
| /* |
| * Send DTLS session Rx cipher switch command to FW |
| */ |
| nss_status = nss_dtls_tx_msg_sync(ds->nss_ctx, &dtlsmsg); |
| if (nss_status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_session_ref_dec(ds); |
| return false; |
| } |
| |
| ds->crypto_idx_decap = ds->cidx_decap_pending; |
| ds->cidx_decap_pending = NSS_CRYPTO_MAX_IDXS; |
| |
| nss_dtlsmgr_session_ref_dec(ds); |
| return true; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_switch_decap); |
| |
| /* |
| * nss_dtlsmgr_session_switch_encap() |
| * Set pending encap cipher state of a DTLS session to current. |
| */ |
| bool nss_dtlsmgr_session_switch_encap(struct net_device *dev) |
| { |
| struct nss_dtlsmgr_netdev_priv *priv; |
| struct nss_dtlsmgr_session *ds; |
| nss_tx_status_t nss_status; |
| struct nss_dtls_msg dtlsmsg; |
| |
| priv = netdev_priv(dev); |
| ds = priv->s; |
| if (!ds) { |
| return false; |
| } |
| |
| memset(&dtlsmsg, 0, sizeof(struct nss_dtls_msg)); |
| nss_dtls_msg_init(&dtlsmsg, (uint16_t)ds->nss_dtls_if, |
| NSS_DTLS_MSG_REKEY_ENCAP_CIPHER_SWITCH, |
| 0, NULL, NULL); |
| |
| /* |
| * Send DTLS session Tx cipher switch command to FW |
| */ |
| nss_status = nss_dtls_tx_msg_sync(ds->nss_ctx, &dtlsmsg); |
| if (nss_status != NSS_TX_SUCCESS) { |
| nss_dtlsmgr_session_ref_dec(ds); |
| return false; |
| } |
| |
| ds->crypto_idx_encap = ds->cidx_encap_pending; |
| ds->cidx_encap_pending = NSS_CRYPTO_MAX_IDXS; |
| |
| nss_dtlsmgr_session_ref_dec(ds); |
| return true; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_session_switch_encap); |
| |
| /* |
| * 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; |
| |
| if (type > NSS_DTLSMGR_INTERFACE_TYPE_MAX) { |
| nss_dtlsmgr_warn("%px: invalid interface type %d", dev, type); |
| return -EINVAL; |
| } |
| |
| ifnum = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_DTLS); |
| if (ifnum < 0) { |
| nss_dtlsmgr_warn("%px: couldn't find DTLS interface number (%d)", dev, ifnum); |
| return ifnum; |
| } |
| |
| ifnum = nss_dtls_get_ifnum_with_coreid(ifnum); |
| |
| return ifnum; |
| } |
| EXPORT_SYMBOL(nss_dtlsmgr_get_interface); |
| |
| /* |
| * nss_dtls_crypto_attach() |
| */ |
| static nss_crypto_user_ctx_t nss_dtls_crypto_attach(nss_crypto_handle_t crypto) |
| { |
| struct nss_dtlsmgr_ctx *sc = &g_ctx; |
| |
| sc->crypto_hdl = crypto; |
| nss_dtlsmgr_info("%px: DTLS client crypto attach\n", &g_ctx); |
| return (nss_crypto_user_ctx_t)sc; |
| } |
| |
| /* |
| * nss_dtls_crypto_detach() |
| */ |
| static void nss_dtls_crypto_detach(nss_crypto_user_ctx_t uctx) |
| { |
| struct nss_dtlsmgr_ctx *sc = NULL; |
| |
| sc = (struct nss_dtlsmgr_ctx *)uctx; |
| nss_dtlsmgr_assert(sc == &g_ctx); |
| |
| sc->crypto_hdl = NULL; |
| nss_dtlsmgr_info("%px: DTLS client crypto detach\n", &g_ctx); |
| } |
| |
| /* |
| * nss_dtls_init_module() |
| */ |
| int __init nss_dtls_init_module(void) |
| { |
| int32_t i; |
| |
| nss_dtlsmgr_info("%px: NSS DTLS Manager\n", &g_ctx); |
| |
| for (i = 0; i < NSS_MAX_DTLS_SESSIONS; i++) { |
| g_ctx.session[i] = NULL; |
| } |
| |
| spin_lock_init(&(g_ctx.lock)); |
| nss_crypto_register_user(nss_dtls_crypto_attach, |
| nss_dtls_crypto_detach, |
| "nss-dtls"); |
| return 0; |
| } |
| |
| /* |
| * nss_dtls_destroy_all_sessions() |
| */ |
| static void nss_dtls_destroy_all_sessions(void) |
| { |
| nss_tx_status_t nss_status; |
| struct nss_dtls_msg dtlsmsg; |
| struct nss_dtlsmgr_session *ds; |
| int32_t i; |
| |
| for (i = 0; i < NSS_MAX_DTLS_SESSIONS; i++) { |
| spin_lock_bh(&g_ctx.lock); |
| if (g_ctx.session[i] == NULL) { |
| spin_unlock_bh(&g_ctx.lock); |
| continue; |
| } |
| |
| /* |
| * Remove session from list of sessions |
| */ |
| ds = g_ctx.session[i]; |
| g_ctx.session[i] = NULL; |
| spin_unlock_bh(&g_ctx.lock); |
| |
| nss_dtlsmgr_assert(ds->magic == NSS_DTLSMGR_SESSION_MAGIC); |
| |
| /* |
| * Send DTLS session destroy command to FW |
| */ |
| memset(&dtlsmsg, 0, sizeof(struct nss_dtls_msg)); |
| nss_dtls_msg_init(&dtlsmsg, (uint16_t)ds->nss_dtls_if, |
| NSS_DTLS_MSG_SESSION_DESTROY, 0, |
| NULL, NULL); |
| |
| nss_status = nss_dtls_tx_msg_sync(ds->nss_ctx, &dtlsmsg); |
| if (nss_status != NSS_TX_SUCCESS) |
| nss_dtlsmgr_warn("%px: Failed to send DTLS session destroy for I/F %u\n", &g_ctx, ds->nss_dtls_if); |
| |
| nss_dtlsmgr_session_ref_dec(ds); |
| } |
| } |
| |
| /* |
| * nss_dtls_exit_module() |
| */ |
| void __exit nss_dtls_exit_module(void) |
| { |
| nss_dtls_destroy_all_sessions(); |
| nss_crypto_unregister_user(g_ctx.crypto_hdl); |
| } |
| |
| module_init(nss_dtls_init_module); |
| module_exit(nss_dtls_exit_module); |
| |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_DESCRIPTION("NSS DTLS manager"); |