blob: 3db69b8e0b6a14da62ec6a04a0546f6254d2ae92 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2015-2016,2018-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 TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
#include <crypto/internal/skcipher.h>
#include <linux/debugfs.h>
#include <linux/etherdevice.h>
#include <linux/icmp.h>
#include <linux/inet.h>
#include <linux/udp.h>
#include <linux/version.h>
#include <net/genetlink.h>
#include <net/ip6_checksum.h>
#include <net/udp.h>
#include <nss_api_if.h>
#include <nss_dtls.h>
#include <nss_dtlsmgr.h>
#include <nss_dtls_cmn.h>
#include <nss_nlcmn_if.h>
#include <nss_nl_if.h>
#include "nss_nl.h"
#include "nss_nldtls.h"
#include "nss_nldtls_if.h"
/*
* prototypes
*/
static int nss_nldtls_ops_create_tun(struct sk_buff *skb, struct genl_info *info);
static int nss_nldtls_ops_destroy_tun(struct sk_buff *skb, struct genl_info *info);
static int nss_nldtls_ops_update_config(struct sk_buff *skb, struct genl_info *info);
static int nss_nldtls_ops_tx_pkts(struct sk_buff *skb, struct genl_info *info);
/*
* Initializing the global variables
*/
static struct nss_nldtls_gbl_ctx gbl_ctx = {
.lock = __SPIN_LOCK_UNLOCKED(lock),
.num_tun = ATOMIC_INIT(0),
.dtls_list_head = LIST_HEAD_INIT(gbl_ctx.dtls_list_head),
.log_en = false
};
/*
* nss_nldtls_family_mcgrp
* Multicast group for sending message status & events
*/
static const struct genl_multicast_group nss_nldtls_family_mcgrp[] = {
{.name = NSS_NLDTLS_MCAST_GRP},
};
/*
* nss_nldtls_ops
* Operation table called by the generic netlink layer based on the command
*/
static struct genl_ops nss_nldtls_ops[] = {
{.cmd = NSS_NLDTLS_CMD_TYPE_CREATE_TUN, .doit = nss_nldtls_ops_create_tun,},
{.cmd = NSS_NLDTLS_CMD_TYPE_DESTROY_TUN, .doit = nss_nldtls_ops_destroy_tun,},
{.cmd = NSS_NLDTLS_CMD_TYPE_UPDATE_CONFIG, .doit = nss_nldtls_ops_update_config,},
{.cmd = NSS_NLDTLS_CMD_TYPE_TX_PKTS, .doit = nss_nldtls_ops_tx_pkts,},
};
/*
* nss_nldtls_family
* Dtls family definition
*/
struct genl_family nss_nldtls_family = {
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 0))
.id = GENL_ID_GENERATE, /* Auto generate ID */
#endif
.name = NSS_NLDTLS_FAMILY, /* family name string */
.hdrsize = sizeof(struct nss_nldtls_rule), /* NSS NETLINK dtls rule */
.version = NSS_NL_VER, /* Set it to NSS_NLDTLS version */
.maxattr = NSS_NLDTLS_CMD_TYPE_MAX, /* maximum commands supported */
.netnsok = true,
.pre_doit = NULL,
.post_doit = NULL,
.ops = nss_nldtls_ops,
.n_ops = ARRAY_SIZE(nss_nldtls_ops),
.mcgrps = nss_nldtls_family_mcgrp,
.n_mcgrps = ARRAY_SIZE(nss_nldtls_family_mcgrp)
};
/*
* nss_nldtls_find_tun_ctx()
* Returns the global context object of a tunnel
*/
static struct nss_nldtls_tun_ctx *nss_nldtls_find_tun_ctx(struct net_device *dev)
{
struct nss_nldtls_tun_ctx *entry;
spin_lock(&gbl_ctx.lock);
list_for_each_entry(entry, &gbl_ctx.dtls_list_head, list) {
if (!strncmp(entry->dev_name, dev->name, IFNAMSIZ)) {
spin_unlock(&gbl_ctx.lock);
return entry;
}
}
spin_unlock(&gbl_ctx.lock);
return NULL;
}
/*
* nss_nldtls_dev_rx_handler()
* Common rx handler for all dtls dev
*/
static rx_handler_result_t nss_nldtls_dev_rx_handler(struct sk_buff **pskb)
{
static bool first_pkt;
unsigned long long duration;
struct sk_buff *skb = *pskb;
ktime_t delta;
if (unlikely(!first_pkt)) {
gbl_ctx.first_rx_pkt_time = ktime_get();
first_pkt = true;
}
gbl_ctx.last_rx_pkt_time = ktime_get();
if (unlikely(gbl_ctx.log_en)) {
struct net_device *dev;
delta = ktime_sub(gbl_ctx.last_rx_pkt_time, gbl_ctx.first_rx_pkt_time);
duration = (unsigned long long) ktime_to_ns(delta) >> 10;
print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, skb->data, 64);
dev = dev_get_by_index(&init_net, skb->skb_iif);
if (dev) {
nss_nl_info("%px: in_dev = %s, out_dev = %s\n", skb, dev->name, skb->dev->name);
dev_put(dev);
}
nss_nl_info("%px: DTLS RX (%s) pkt len = %d udp_csum = %s rx_time: %llu\n", skb,
skb->dev->name, skb->len, udp_lib_checksum_complete(skb) ?
"invalid" : "valid", duration);
}
dev_kfree_skb_any(skb);
return RX_HANDLER_CONSUMED;
}
/*
* nss_nldtls_create_ipv4_rule()
* Create a nss entry to accelerate the given IPv4 connection
*/
static int nss_nldtls_create_ipv4_rule(struct nss_ipv4_create *unic, uint16_t rule_flags)
{
struct nss_ipv4_rule_create_msg *nircm;
struct nss_ctx_instance *nss_ctx;
struct nss_ipv4_msg nim;
nss_tx_status_t status;
nss_ctx = nss_ipv4_get_mgr();
if (!nss_ctx) {
nss_nl_info("%px: Couldn't get IPv4 ctx\n", unic);
return -1;
}
nss_nl_info("%px: IPv4 rule: src:%pI4h:%d dst:%pI4h:%d p:%d\n",
unic, &unic->src_ip, unic->src_port,
&unic->dest_ip, unic->dest_port, unic->protocol);
memset(&nim, 0, sizeof (struct nss_ipv4_msg));
nss_ipv4_msg_init(&nim, NSS_IPV4_RX_INTERFACE,
NSS_IPV4_TX_CREATE_RULE_MSG,
sizeof(struct nss_ipv4_rule_create_msg), NULL, NULL);
nircm = &nim.msg.rule_create;
nircm->valid_flags = 0;
nircm->rule_flags = 0;
/*
* Copy over the 5 tuple details.
*/
nircm->tuple.protocol = (uint8_t)unic->protocol;
nircm->tuple.flow_ip = unic->src_ip;
nircm->tuple.flow_ident = (uint32_t)unic->src_port;
nircm->tuple.return_ip = unic->dest_ip;
nircm->tuple.return_ident = (uint32_t)unic->dest_port;
/*
* Copy over the connection rules and set the CONN_VALID flag
*/
nircm->conn_rule.flow_interface_num = unic->src_interface_num;
nircm->conn_rule.flow_mtu = unic->from_mtu;
nircm->conn_rule.flow_ip_xlate = unic->src_ip_xlate;
nircm->conn_rule.flow_ident_xlate = (uint32_t)unic->src_port_xlate;
memcpy(nircm->conn_rule.flow_mac, unic->src_mac, 6);
nircm->conn_rule.return_interface_num = unic->dest_interface_num;
nircm->conn_rule.return_mtu = unic->to_mtu;
nircm->conn_rule.return_ip_xlate = unic->dest_ip_xlate;
nircm->conn_rule.return_ident_xlate = (uint32_t)unic->dest_port_xlate;
if ((nircm->tuple.return_ip != nircm->conn_rule.return_ip_xlate) ||
(nircm->tuple.return_ident != nircm->conn_rule.return_ident_xlate))
memcpy(nircm->conn_rule.return_mac, unic->dest_mac_xlate, 6);
else
memcpy(nircm->conn_rule.return_mac, unic->dest_mac, 6);
/*
* Copy over the DSCP rule parameters
*/
if (unic->flags & NSS_IPV4_CREATE_FLAG_DSCP_MARKING) {
nircm->dscp_rule.flow_dscp = unic->flow_dscp;
nircm->dscp_rule.return_dscp = unic->return_dscp;
nircm->rule_flags |= NSS_IPV4_RULE_CREATE_FLAG_DSCP_MARKING;
nircm->valid_flags |= NSS_IPV4_RULE_CREATE_DSCP_MARKING_VALID;
}
nircm->valid_flags |= NSS_IPV4_RULE_CREATE_CONN_VALID;
/*
* Copy over the pppoe rules and set the PPPOE_VALID flag.
*/
nircm->pppoe_rule.flow_if_exist = unic->flow_pppoe_if_exist;
nircm->pppoe_rule.flow_if_num = unic->flow_pppoe_if_num;
nircm->pppoe_rule.return_if_exist = unic->return_pppoe_if_exist;
nircm->pppoe_rule.return_if_num = unic->return_pppoe_if_num;
nircm->valid_flags |= NSS_IPV4_RULE_CREATE_PPPOE_VALID;
/*
* Copy over the vlan rules and set the VLAN_VALID flag
*/
nircm->vlan_primary_rule.ingress_vlan_tag = unic->in_vlan_tag[0];
nircm->vlan_primary_rule.egress_vlan_tag = unic->out_vlan_tag[0];
nircm->vlan_secondary_rule.ingress_vlan_tag = unic->in_vlan_tag[1];
nircm->vlan_secondary_rule.egress_vlan_tag = unic->out_vlan_tag[1];
nircm->valid_flags |= NSS_IPV4_RULE_CREATE_VLAN_VALID;
/*
* Copy over the qos rules and set the QOS_VALID flag
*/
nircm->qos_rule.flow_qos_tag = unic->flow_qos_tag;
nircm->qos_rule.return_qos_tag = unic->return_qos_tag;
nircm->valid_flags |= NSS_IPV4_RULE_CREATE_QOS_VALID;
if (unic->flags & NSS_IPV4_CREATE_FLAG_NO_SEQ_CHECK)
nircm->rule_flags |= NSS_IPV4_RULE_CREATE_FLAG_NO_SEQ_CHECK;
if (unic->flags & NSS_IPV4_CREATE_FLAG_BRIDGE_FLOW)
nircm->rule_flags |= NSS_IPV4_RULE_CREATE_FLAG_BRIDGE_FLOW;
if (unic->flags & NSS_IPV4_CREATE_FLAG_ROUTED)
nircm->rule_flags |= NSS_IPV4_RULE_CREATE_FLAG_ROUTED;
/*
* Set the flag NSS_IPV4_RULE_CREATE_FLAG_ICMP_NO_CME_FLUSH so that
* rule is not flushed when NSS FW receives ICMP errors/packets.
*/
nircm->rule_flags |= NSS_IPV4_RULE_CREATE_FLAG_ICMP_NO_CME_FLUSH;
/*
* Add any other additional flags which caller has requested.
* For example: update MTU
*/
nircm->rule_flags |= rule_flags;
status = nss_ipv4_tx(nss_ctx, &nim);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create IPv4 message failed %d\n", nss_ctx, status);
}
return 0;
}
/*
* nss_nldtls_create_ipv6_rule()
* Create a nss entry to accelerate the given IPV6 connection
*/
static int nss_nldtls_create_ipv6_rule(struct nss_ipv6_create *unic, uint16_t rule_flags)
{
struct nss_ipv6_rule_create_msg *nircm;
struct nss_ctx_instance *nss_ctx;
struct nss_ipv6_msg nim;
nss_tx_status_t status;
nss_ctx = nss_ipv6_get_mgr();
if (!nss_ctx) {
nss_nl_info("%px: Couldn't get IPv6 ctx\n", unic);
return -1;
}
nss_nl_info("%px: Create IPv6 rule: %pI6:%d %pI6:%d p:%d\n",
unic, unic->src_ip, unic->src_port, unic->dest_ip,
unic->dest_port, unic->protocol);
memset(&nim, 0, sizeof (struct nss_ipv6_msg));
nss_ipv6_msg_init(&nim, NSS_IPV6_RX_INTERFACE,
NSS_IPV6_TX_CREATE_RULE_MSG,
sizeof(struct nss_ipv6_rule_create_msg), NULL, NULL);
nircm = &nim.msg.rule_create;
nircm->rule_flags = 0;
nircm->valid_flags = 0;
/*
* Copy over the 5 tuple information.
*/
nircm->tuple.protocol = (uint8_t)unic->protocol;
memcpy(nircm->tuple.flow_ip, unic->src_ip, sizeof(nircm->tuple.flow_ip));
memcpy(nircm->tuple.return_ip, unic->dest_ip, sizeof(nircm->tuple.return_ip));
nircm->tuple.flow_ident = (uint32_t)unic->src_port;
nircm->tuple.return_ident = (uint32_t)unic->dest_port;
/*
* Copy over the connection rules and set CONN_VALID flag
*/
nircm->conn_rule.flow_interface_num = unic->src_interface_num;
nircm->conn_rule.flow_mtu = unic->from_mtu;
nircm->conn_rule.return_interface_num = unic->dest_interface_num;
nircm->conn_rule.return_mtu = unic->to_mtu;
memcpy(nircm->conn_rule.flow_mac, unic->src_mac, 6);
memcpy(nircm->conn_rule.return_mac, unic->dest_mac, 6);
nircm->valid_flags |= NSS_IPV6_RULE_CREATE_CONN_VALID;
/*
* Copy over the DSCP rule parameters
*/
if (unic->flags & NSS_IPV6_CREATE_FLAG_DSCP_MARKING) {
nircm->dscp_rule.flow_dscp = unic->flow_dscp;
nircm->dscp_rule.return_dscp = unic->return_dscp;
nircm->rule_flags |= NSS_IPV6_RULE_CREATE_FLAG_DSCP_MARKING;
nircm->valid_flags |= NSS_IPV6_RULE_CREATE_DSCP_MARKING_VALID;
}
/*
* Copy over the pppoe rules and set PPPOE_VALID flag
*/
nircm->pppoe_rule.flow_if_exist = unic->flow_pppoe_if_exist;
nircm->pppoe_rule.flow_if_num = unic->flow_pppoe_if_num;
nircm->pppoe_rule.return_if_exist = unic->return_pppoe_if_exist;
nircm->pppoe_rule.return_if_num = unic->return_pppoe_if_num;
nircm->valid_flags |= NSS_IPV6_RULE_CREATE_PPPOE_VALID;
/*
* Copy over the tcp rules and set TCP_VALID flag
*/
nircm->tcp_rule.flow_window_scale = unic->flow_window_scale;
nircm->tcp_rule.flow_max_window = unic->flow_max_window;
nircm->tcp_rule.flow_end = unic->flow_end;
nircm->tcp_rule.flow_max_end = unic->flow_max_end;
nircm->tcp_rule.return_window_scale = unic->return_window_scale;
nircm->tcp_rule.return_max_window = unic->return_max_window;
nircm->tcp_rule.return_end = unic->return_end;
nircm->tcp_rule.return_max_end = unic->return_max_end;
nircm->valid_flags |= NSS_IPV6_RULE_CREATE_TCP_VALID;
/*
* Copy over the vlan rules and set the VLAN_VALID flag
*/
nircm->vlan_primary_rule.egress_vlan_tag = unic->out_vlan_tag[0];
nircm->vlan_primary_rule.ingress_vlan_tag = unic->in_vlan_tag[0];
nircm->vlan_secondary_rule.egress_vlan_tag = unic->out_vlan_tag[1];
nircm->vlan_secondary_rule.ingress_vlan_tag = unic->in_vlan_tag[1];
nircm->valid_flags |= NSS_IPV6_RULE_CREATE_VLAN_VALID;
/*
* Copy over the qos rules and set the QOS_VALID flag
*/
nircm->qos_rule.flow_qos_tag = unic->flow_qos_tag;
nircm->qos_rule.return_qos_tag = unic->return_qos_tag;
nircm->valid_flags |= NSS_IPV6_RULE_CREATE_QOS_VALID;
if (unic->flags & NSS_IPV6_CREATE_FLAG_NO_SEQ_CHECK)
nircm->rule_flags |= NSS_IPV6_RULE_CREATE_FLAG_NO_SEQ_CHECK;
if (unic->flags & NSS_IPV6_CREATE_FLAG_BRIDGE_FLOW)
nircm->rule_flags |= NSS_IPV6_RULE_CREATE_FLAG_BRIDGE_FLOW;
if (unic->flags & NSS_IPV6_CREATE_FLAG_ROUTED)
nircm->rule_flags |= NSS_IPV6_RULE_CREATE_FLAG_ROUTED;
/*
* Set the flag NSS_IPV4_RULE_CREATE_FLAG_ICMP_NO_CME_FLUSH so that
* rule is not flushed when NSS FW receives ICMP errors/packets.
*/
nircm->rule_flags |= NSS_IPV4_RULE_CREATE_FLAG_ICMP_NO_CME_FLUSH;
/*
* Add any other additional flags which caller has requested.
* For example: update MTU
*/
nircm->rule_flags |= rule_flags;
status = nss_ipv6_tx(nss_ctx, &nim);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create IPv4 message failed %d\n", nss_ctx, status);
}
return 0;
}
/*
* nss_nldtls_data_callback()
* Data callback function for dtls
*/
static void nss_nldtls_data_callback(void *app_data, struct sk_buff *skb)
{
struct nss_dtlsmgr_metadata *ndm;
struct nss_nldtls_tun_ctx *tun;
struct nss_nldtls_stats *stats;
struct net_device *dev;
dev = dev_get_by_index(&init_net, skb->skb_iif);
if (!dev) {
nss_nl_error("Unable to get net dev for skb_iif %d\n", skb->skb_iif);
dev_kfree_skb_any(skb);
return;
}
ndm = (struct nss_dtlsmgr_metadata *)skb->data;
tun = nss_nldtls_find_tun_ctx(dev);
if (!tun) {
nss_nl_error("Unable find tunnel ctx for %s\n", dev->name);
dev_put(dev);
dev_kfree_skb_any(skb);
return;
}
stats = &tun->stats[NSS_NLDTLS_CTYPE_TO_IDX(ndm->ctype)];
spin_lock(&gbl_ctx.lock);
stats->rx_pkts++;
stats->rx_bytes += skb->len - sizeof(*ndm);
spin_unlock(&gbl_ctx.lock);
if (unlikely(gbl_ctx.log_en)) {
nss_nl_hex_dump_bytes("", DUMP_PREFIX_OFFSET, skb->data, (skb->len > 64) ? 64 : skb->len);
}
nss_nl_trace("%px Received DTLS packet\n", skb);
dev_put(dev);
dev_kfree_skb_any(skb);
}
/*
* nss_nldtls_create_session()
* Create a DTLS session through dtlsmgr driver API.
*/
static struct net_device *nss_nldtls_create_session(struct nss_nldtls_rule *nl_rule)
{
struct nss_nldtls_tun_ctx *dtls_tun_data;
struct nss_dtlsmgr_config dcfg;
struct nss_dtlsmgr_ctx *ctx;
struct net_device *ndev;
uint16_t key_len;
uint8_t algo;
int err;
if (atomic_read(&gbl_ctx.num_tun) >= NSS_NLDTLS_MAX_TUNNELS) {
nss_nl_error("Max number of tunnels exhausted: 32\n");
return NULL;
}
memset(&dcfg, 0, sizeof(struct nss_dtlsmgr_config));
algo = nl_rule->msg.create.encap.cfg.crypto.algo;
dcfg.flags = nl_rule->msg.create.flags | NSS_DTLSMGR_ENCAP_METADATA;
if (algo == NSS_DTLSMGR_ALGO_AES_GCM)
dcfg.flags |= NSS_DTLSMGR_CIPHER_MODE_GCM;
dcfg.app_data = NULL;
dcfg.notify = NULL;
dcfg.data = nss_nldtls_data_callback;
/*
* Encap configuration
*/
key_len = nl_rule->msg.create.encap.cfg.crypto.cipher_key.len;
if (key_len > NSS_NLDTLS_CIPHER_KEY_MAX) {
nss_nl_error("Invalid cipher length: %u\n", key_len);
return NULL;
}
key_len = nl_rule->msg.create.encap.cfg.crypto.auth_key.len;
if (key_len > NSS_NLDTLS_AUTH_KEY_MAX) {
nss_nl_error("Invalid authentication length: %u\n", key_len);
return NULL;
}
key_len = nl_rule->msg.create.encap.cfg.crypto.nonce.len;
if (key_len > NSS_NLDTLS_NONCE_SIZE_MAX) {
nss_nl_error("Invalid nonce length: %u\n", key_len);
return NULL;
}
nl_rule->msg.create.encap.cfg.crypto.cipher_key.data = nl_rule->msg.create.encap.keys.cipher;
nl_rule->msg.create.encap.cfg.crypto.auth_key.data = nl_rule->msg.create.encap.keys.auth;
nl_rule->msg.create.encap.cfg.crypto.nonce.data = nl_rule->msg.create.encap.keys.nonce;
memcpy((void *)&dcfg.encap, (void *)&nl_rule->msg.create.encap.cfg, sizeof(struct nss_dtlsmgr_encap_config));
/*
* Decap configuration
*/
key_len = nl_rule->msg.create.decap.cfg.crypto.cipher_key.len;
if (key_len > NSS_NLDTLS_CIPHER_KEY_MAX) {
nss_nl_error("Invalid cipher length: %u\n", key_len);
return NULL;
}
key_len = nl_rule->msg.create.decap.cfg.crypto.auth_key.len;
if (key_len > NSS_NLDTLS_AUTH_KEY_MAX) {
nss_nl_error("Invalid authentication length: %u\n", key_len);
return NULL;
}
key_len = nl_rule->msg.create.decap.cfg.crypto.nonce.len;
if (key_len > NSS_NLDTLS_NONCE_SIZE_MAX) {
nss_nl_error("Invalid nonce length: %u\n", key_len);
return NULL;
}
nl_rule->msg.create.decap.cfg.crypto.cipher_key.data = nl_rule->msg.create.decap.keys.cipher;
nl_rule->msg.create.decap.cfg.crypto.auth_key.data = nl_rule->msg.create.decap.keys.auth;
nl_rule->msg.create.decap.cfg.crypto.nonce.data = nl_rule->msg.create.decap.keys.nonce;
memcpy((void *)&dcfg.decap, (void *)&nl_rule->msg.create.decap.cfg, sizeof(struct nss_dtlsmgr_decap_config));
dcfg.decap.nexthop_ifnum = NSS_N2H_INTERFACE;
/*
* Create a dtls session
*/
ndev = nss_dtlsmgr_session_create(&dcfg);
if (!ndev) {
nss_nl_error("Failed to create DTLS session\n");
return NULL;
}
ctx = netdev_priv(ndev);
/*
* Register rx handler for dtls netdev
*/
rtnl_lock();
err = netdev_rx_handler_register(ndev, nss_nldtls_dev_rx_handler, 0);
if (err) {
rtnl_unlock();
nss_dtlsmgr_session_destroy(ndev);
nss_nl_error("%px: Failed to register rx handler\n", ctx);
return NULL;
}
rtnl_unlock();
/*
* Prepare data for current tunnel
*/
dtls_tun_data = (struct nss_nldtls_tun_ctx *)kmalloc(sizeof(*dtls_tun_data), GFP_KERNEL);
dtls_tun_data->nl_rule = nl_rule;
memcpy(dtls_tun_data->dev_name, ndev->name, IFNAMSIZ);
memset(&dtls_tun_data->stats, 0, sizeof(dtls_tun_data->stats));
/*
* Adding tunnel to global list of tunnels
*/
spin_lock(&gbl_ctx.lock);
list_add_tail(&dtls_tun_data->list, &gbl_ctx.dtls_list_head);
spin_unlock(&gbl_ctx.lock);
nss_nl_info("%px: Succesfully created dtls session.\n", ctx);
return ndev;
}
/*
* nss_nldtls_create_ipv4_rule_entry()
* Handler for adding ipv4 rule entry for dtls session
*/
static int nss_nldtls_create_ipv4_rule_entry(struct net_device *dtls_dev, struct nss_nldtls_rule *nl_rule)
{
struct nss_ipv4_create ipv4;
struct net_device *ndev;
uint32_t if_num;
/*
* Configure IPv4 rule
*/
memset(&ipv4, 0, sizeof(struct nss_ipv4_create));
ipv4.from_mtu = nl_rule->msg.create.from_mtu;
ipv4.to_mtu = nl_rule->msg.create.to_mtu;
ndev = dev_get_by_name(&init_net, &nl_rule->msg.create.gmac_ifname[0]);
if (ndev == NULL) {
nss_nl_error("Can't find %s netdev\n", nl_rule->msg.create.gmac_ifname);
return -EINVAL;
}
if_num = nss_cmn_get_interface_number_by_dev(ndev);
ipv4.src_interface_num = if_num;
ipv4.dest_interface_num = nss_dtlsmgr_get_interface(dtls_dev, NSS_DTLSMGR_INTERFACE_TYPE_OUTER);
ipv4.src_port = nl_rule->msg.create.encap.cfg.dport;
ipv4.src_port_xlate = nl_rule->msg.create.encap.cfg.dport;
ipv4.src_ip = nl_rule->msg.create.encap.cfg.dip[0];
ipv4.src_ip_xlate = nl_rule->msg.create.encap.cfg.dip[0];
ipv4.dest_ip = nl_rule->msg.create.encap.cfg.sip[0];
ipv4.dest_ip_xlate = nl_rule->msg.create.encap.cfg.sip[0];
ipv4.dest_port = nl_rule->msg.create.encap.cfg.sport;
ipv4.dest_port_xlate = nl_rule->msg.create.encap.cfg.sport;
if (nl_rule->msg.create.flags & NSS_DTLSMGR_HDR_UDPLITE)
ipv4.protocol = IPPROTO_UDPLITE;
else
ipv4.protocol = IPPROTO_UDP;
ipv4.in_vlan_tag[0] = NSS_NLDTLS_VLAN_INVALID;
ipv4.out_vlan_tag[0] = NSS_NLDTLS_VLAN_INVALID;
ipv4.in_vlan_tag[1] = NSS_NLDTLS_VLAN_INVALID;
ipv4.out_vlan_tag[1] = NSS_NLDTLS_VLAN_INVALID;
memcpy(&ipv4.src_mac[0], &nl_rule->msg.create.gmac_ifmac[0], sizeof(ipv4.src_mac));
/*
* Create an ipv4 rule entry
*/
return nss_nldtls_create_ipv4_rule(&ipv4, 0);
}
/*
* nss_nldtls_create_ipv6_rule_entry()
* Handler to add an ipv6 rule entry for dtls session
*/
static int nss_nldtls_create_ipv6_rule_entry(struct net_device *dtls_dev, struct nss_nldtls_rule *nl_rule)
{
struct nss_ipv6_create ipv6;
struct net_device *ndev;
uint32_t if_num;
/*
* Configure IPv6 rule
*/
memset(&ipv6, 0, sizeof(struct nss_ipv6_create));
ipv6.from_mtu = nl_rule->msg.create.from_mtu;
ipv6.to_mtu = nl_rule->msg.create.to_mtu;
ndev = dev_get_by_name(&init_net, &nl_rule->msg.create.gmac_ifname[0]);
if (ndev == NULL) {
nss_nl_error("Can't find %s netdev\n", nl_rule->msg.create.gmac_ifname);
return -EINVAL;
}
if_num = nss_cmn_get_interface_number_by_dev(ndev);
ipv6.src_interface_num = if_num;
ipv6.dest_interface_num = nss_dtlsmgr_get_interface(dtls_dev, NSS_DTLSMGR_INTERFACE_TYPE_OUTER);
ipv6.src_port = nl_rule->msg.create.encap.cfg.dport;
ipv6.dest_port = nl_rule->msg.create.encap.cfg.sport;
/*
* Configure IPv6 rule
*/
memcpy(ipv6.src_ip, nl_rule->msg.create.encap.cfg.dip, sizeof(ipv6.src_ip));
memcpy(ipv6.dest_ip, nl_rule->msg.create.encap.cfg.sip, sizeof(ipv6.dest_ip));
if (nl_rule->msg.create.flags & NSS_DTLSMGR_HDR_UDPLITE)
ipv6.protocol = IPPROTO_UDPLITE;
else
ipv6.protocol = IPPROTO_UDP;
ipv6.in_vlan_tag[0] = NSS_NLDTLS_VLAN_INVALID;
ipv6.in_vlan_tag[1] = NSS_NLDTLS_VLAN_INVALID;
ipv6.out_vlan_tag[1] = NSS_NLDTLS_VLAN_INVALID;
ipv6.out_vlan_tag[0] = NSS_NLDTLS_VLAN_INVALID;
memcpy(&ipv6.src_mac[0], &nl_rule->msg.create.gmac_ifmac[0], sizeof(ipv6.src_mac));
/*
* Create an ipv6 rule entry
*/
return nss_nldtls_create_ipv6_rule(&ipv6, 0);
}
/*
* nss_nldtls_destroy_tun()
* Common handler for tunnel destroy
*/
static int nss_nldtls_destroy_tun(struct net_device *dev)
{
struct nss_nldtls_tun_ctx *tun;
tun = nss_nldtls_find_tun_ctx(dev);
if (!tun) {
nss_nl_error("Unable to find context of the tunnel: %s\n", dev->name);
return -EAGAIN;
}
/*
* Delete tunnel node from the list
*/
list_del_init(&tun->list);
kfree(tun);
/*
* Destroy the dtls session
*/
if (nss_dtlsmgr_session_destroy(dev)) {
nss_nl_error("Unable to destroy the tunnel: %s\n", dev->name);
return -EAGAIN;
}
return 0;
}
/*
* nss_nldtls_ops_create_tun()
* Handler for creating tunnel
*/
static int nss_nldtls_ops_create_tun(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nldtls_rule *nl_rule;
struct net_device *dtls_dev;
struct nss_nlcmn *nl_cm;
int ret = 0;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nldtls_family, info, NSS_NLDTLS_CMD_TYPE_CREATE_TUN);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract create tunnel data\n", skb);
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nldtls_rule, cm);
/*
* Create tunnel based on ip version
*/
if (nl_rule->msg.create.ip_version == NSS_NLDTLS_IP_VERS_4) {
dtls_dev = nss_nldtls_create_session(nl_rule);
if (!dtls_dev) {
nss_nl_error("%px: Unable to create dtls session for v4\n", skb);
return -EINVAL;
}
/*
* Create IPv4 rule entry
*/
ret = nss_nldtls_create_ipv4_rule_entry(dtls_dev, nl_rule);
if (ret < 0) {
nss_nldtls_destroy_tun(dtls_dev);
nss_nl_error("%px Unable to add a rule entry for ipv4.\n", skb);
return -EAGAIN;
}
atomic_inc(&gbl_ctx.num_tun);
nss_nl_info("%px: Successfully created ipv4 dtls tunnel\n", skb);
} else {
dtls_dev = nss_nldtls_create_session(nl_rule);
if (!dtls_dev) {
nss_nl_error("%px: Unable to create dtls session for v6\n", skb);
return -EINVAL;
}
/*
* Create IPv6 rule entry
*/
ret = nss_nldtls_create_ipv6_rule_entry(dtls_dev, nl_rule);
if (ret < 0) {
nss_nldtls_destroy_tun(dtls_dev);
nss_nl_error("%px: Unable to add a rule entry for ipv6.\n", skb);
return -EAGAIN;
}
atomic_inc(&gbl_ctx.num_tun);
nss_nl_info("%px: Successfully created ipv6 dtls tunnel\n", skb);
}
return 0;
}
/*
* nss_nldtls_ops_destroy_tun()
* Handler to destroy tunnel
*/
static int nss_nldtls_ops_destroy_tun(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nldtls_rule *nl_rule;
struct nss_nlcmn *nl_cm;
struct net_device *dev;
int ret;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nldtls_family, info, NSS_NLDTLS_CMD_TYPE_DESTROY_TUN);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract destroy tunnel data\n", skb);
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nldtls_rule, cm);
dev = dev_get_by_name(&init_net, nl_rule->msg.destroy.dev_name);
if (!dev) {
nss_nl_error("%px Unable to find dev: %s\n", skb, nl_rule->msg.destroy.dev_name);
return -EINVAL;
}
dev_put(dev);
/*
* Common dtls handler for tunnel destroy
*/
ret = nss_nldtls_destroy_tun(dev);
if (ret < 0) {
nss_nl_error("%px Unable to destroy tunnel: %s\n", skb, dev->name);
return -EAGAIN;
}
atomic_dec(&gbl_ctx.num_tun);
nss_nl_info("%px: Successfully destroyed dtls session: %s\n", skb, nl_rule->msg.destroy.dev_name);
return 0;
}
/*
* nss_nldtls_ops_update_config()
* Handler for updating configuration command
*/
static int nss_nldtls_ops_update_config(struct sk_buff *skb, struct genl_info *info)
{
struct nss_dtlsmgr_config_update dcfg;
struct nss_nldtls_rule *nl_rule;
struct nss_nldtls_tun_ctx *tun;
struct nss_dtlsmgr_ctx *ctx;
nss_dtlsmgr_status_t status;
struct nss_nlcmn *nl_cm;
struct net_device *dev;
uint16_t key_len;
/*
* extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nldtls_family, info, NSS_NLDTLS_CMD_TYPE_UPDATE_CONFIG);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract update_config data.\n", skb);
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nldtls_rule, cm);
dev = dev_get_by_name(&init_net, nl_rule->msg.update_config.dev_name);
if (!dev) {
nss_nl_error("%px Unable to find dev: %s\n", skb, nl_rule->msg.update_config.dev_name);
return -EINVAL;
}
ctx = netdev_priv(dev);
tun = nss_nldtls_find_tun_ctx(dev);
if (!tun) {
nss_nl_error("%px Unable to find context of the tunnel: %s\n", ctx, dev->name);
dev_put(dev);
return -EAGAIN;
}
/*
* Configure the dtls configuration
*/
dcfg.crypto.algo = nl_rule->msg.update_config.config_update.crypto.algo;
dcfg.crypto.cipher_key.data = nl_rule->msg.update_config.keys.cipher;
dcfg.crypto.auth_key.data = nl_rule->msg.update_config.keys.auth;
dcfg.crypto.nonce.data = nl_rule->msg.update_config.keys.nonce;
key_len = nl_rule->msg.update_config.config_update.crypto.cipher_key.len;
if (key_len > NSS_NLDTLS_CIPHER_KEY_MAX) {
nss_nl_error("Invalid cipher length: %u\n", key_len);
return -EINVAL;
}
dcfg.crypto.cipher_key.len = key_len;
key_len = nl_rule->msg.update_config.config_update.crypto.auth_key.len;
if (key_len > NSS_NLDTLS_AUTH_KEY_MAX) {
nss_nl_error("Invalid authentication length: %u\n", key_len);
return -EINVAL;
}
dcfg.crypto.auth_key.len = key_len;
key_len = nl_rule->msg.update_config.config_update.crypto.nonce.len;
if (key_len > NSS_NLDTLS_NONCE_SIZE_MAX) {
nss_nl_error("Invalid nonce length: %u\n", key_len);
return -EINVAL;
}
dcfg.crypto.nonce.len = key_len;
dcfg.epoch = nl_rule->msg.update_config.config_update.epoch;
dcfg.window_size = nl_rule->msg.update_config.config_update.window_size;
if (!nl_rule->msg.update_config.dir) {
status = nss_dtlsmgr_session_update_encap(dev, &dcfg);
if (status != NSS_DTLSMGR_OK) {
nss_nl_error("%px Unable to update encap configuration\n", ctx);
dev_put(dev);
return -EINVAL;
}
nss_nl_info("%px: Successfully update the encap configuration\n", ctx);
} else {
status = nss_dtlsmgr_session_update_decap(dev, &dcfg);
if (status != NSS_DTLSMGR_OK) {
nss_nl_error("%px Unable to update decap configuration\n", ctx);
dev_put(dev);
return -EINVAL;
}
nss_nl_info("%px: Successfully update the decap configuration\n", ctx);
}
/*
* Update the tun data configuration
*/
tun->nl_rule = nl_rule;
dev_put(dev);
return 0;
}
/*
* nss_nldtls_alloc_pkt()
* Handler for forming ctype packet
*/
static struct sk_buff *nss_nldtls_alloc_pkt(struct nss_nldtls_rule *nl_rule,
struct net_device *dev, uint32_t pkt_sz, uint8_t ctype)
{
struct nss_dtlsmgr_metadata *ndm;
uint16_t hdr_len, payload_len;
struct sk_buff *skb;
hdr_len = dev->needed_headroom + sizeof(*ndm);
payload_len = hdr_len + dev->needed_tailroom + pkt_sz;
skb = netdev_alloc_skb(dev, payload_len);
if (!skb) {
return NULL;
}
skb_reserve(skb, hdr_len);
/*
* Fill the packet with dummy data
*/
memset(skb_put(skb, pkt_sz), NSS_NLDTLS_DUMMY_DATA, skb->len);
ndm = nss_dtlsmgr_metadata_init(skb);
nss_dtlsmgr_metadata_set_seq(ndm, nl_rule->msg.tx_pkts.seq_num);
nss_dtlsmgr_metadata_set_ctype(ndm, ctype);
if (unlikely(nl_rule->msg.tx_pkts.log_en)) {
nss_nl_info("%px DTLS TX pkt len:%u\n", skb, skb->len);
}
return skb;
}
/*
* nss_nldtls_ops_tx_pkts()
* Handler for sending traffic
*/
static int nss_nldtls_ops_tx_pkts(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nldtls_rule *nl_rule;
struct nss_nldtls_tun_ctx *tun;
struct nss_nldtls_stats *stats;
unsigned long long duration;
struct nss_nlcmn *nl_cm;
struct net_device *dev;
struct sk_buff *tx_skb;
uint32_t num_pkts;
uint32_t pkt_sz;
uint32_t count;
ktime_t delta;
uint8_t ctype;
/*
* extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nldtls_family, info, NSS_NLDTLS_CMD_TYPE_TX_PKTS);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract tx_pkts data\n", skb);
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nldtls_rule, cm);
ctype = nl_rule->msg.tx_pkts.ctype;
num_pkts = nl_rule->msg.tx_pkts.num_pkts;
switch (ctype) {
case NSS_DTLSMGR_METADATA_CTYPE_CCS:
pkt_sz = NSS_NLDTLS_CCS_PKT_SZ;
break;
case NSS_DTLSMGR_METADATA_CTYPE_ALERT:
pkt_sz = NSS_NLDTLS_ALERT_PKT_SZ;
break;
case NSS_DTLSMGR_METADATA_CTYPE_HANDSHAKE:
pkt_sz = NSS_NLDTLS_HANDSHAKE_PKT_SZ;
break;
case NSS_DTLSMGR_METADATA_CTYPE_APP:
pkt_sz = nl_rule->msg.tx_pkts.pkt_sz;
break;
default:
return -EINVAL;
}
dev = dev_get_by_name(&init_net, nl_rule->msg.tx_pkts.dev_name);
if (!dev) {
nss_nl_error("%px Unable to find dev: %s\n", skb, nl_rule->msg.tx_pkts.dev_name);
return -EINVAL;
}
tun = nss_nldtls_find_tun_ctx(dev);
if (!tun) {
nss_nl_error("%px Unable to find context of the tunnel: %s\n", skb, dev->name);
dev_put(dev);
return -EINVAL;
}
spin_lock(&gbl_ctx.lock);
gbl_ctx.log_en = nl_rule->msg.tx_pkts.log_en;
spin_unlock(&gbl_ctx.lock);
gbl_ctx.first_tx_pkt_time = ktime_get();
for (count = 0; count < num_pkts; count++) {
tx_skb = nss_nldtls_alloc_pkt(nl_rule, dev, pkt_sz, ctype);
if (!tx_skb) {
nss_nl_error("%px Failed to allocate skb\n", dev);
break;
}
dev->netdev_ops->ndo_start_xmit(tx_skb, dev);
}
stats = &tun->stats[NSS_NLDTLS_CTYPE_TO_IDX(ctype)];
spin_lock(&gbl_ctx.lock);
stats->tx_pkts += count;
stats->tx_bytes += (count * pkt_sz);
spin_unlock(&gbl_ctx.lock);
dev_put(dev);
if (count != num_pkts) {
nss_nl_error("%px Error in transmission\n", skb);
return -EAGAIN;
}
gbl_ctx.last_tx_pkt_time = ktime_get();
delta = ktime_sub(gbl_ctx.last_tx_pkt_time, gbl_ctx.first_tx_pkt_time);
duration = (unsigned long long) ktime_to_ns(delta) >> 10;
nss_nl_info("%px Packets sent in %llu usecs", dev, duration);
nss_nl_info("%px Traffic transmission successful\n", skb);
return 0;
}
/*
* nss_nldtls_tunnel_stats_read()
* reads the per tunnel tx and rx packets stats for every ctypes
*/
static ssize_t nss_nldtls_tunnel_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *f_ppos)
{
struct nss_nldtls_stats stats[NSS_NLDTLS_CTYPE_MAX];
struct nss_nldtls_tun_ctx *entry;
uint32_t max_output_lines;
char dev_name[IFNAMSIZ];
ssize_t bytes_read = 0;
ssize_t size_wr = 0;
ssize_t size_al;
char *lbuf;
max_output_lines = 2 + (NSS_NLDTLS_MAX_TUNNELS * NSS_NLDTLS_STATS_MAX_ROW);
size_al = NSS_NLDTLS_STATS_MAX_STR_LEN * max_output_lines;
lbuf = vzalloc(size_al);
if (!lbuf) {
nss_nl_error("%px Could not allocate buffer for debug entry\n", f_ppos);
return 0;
}
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\nDTLS netlink ctype stats:\n");
list_for_each_entry(entry, &gbl_ctx.dtls_list_head, list) {
spin_lock_bh(&gbl_ctx.lock);
memcpy(&stats, &entry->stats, sizeof(stats));
strlcpy(dev_name, entry->dev_name, IFNAMSIZ);
spin_unlock_bh(&gbl_ctx.lock);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n--------------------------------");
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n%s:\t %s", "dev", dev_name);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n change_cipher_spec");
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "tx_pkts", stats[0].tx_pkts);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "tx_bytes", stats[0].tx_bytes);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "rx_pkts", stats[0].rx_pkts);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "rx_bytes", stats[0].rx_bytes);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n alert");
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "tx_pkts", stats[1].tx_pkts);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "tx_bytes", stats[1].tx_bytes);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "rx_pkts", stats[1].rx_pkts);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "rx_bytes", stats[1].rx_bytes);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n handshake");
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "tx_pkts", stats[2].tx_pkts);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "tx_bytes", stats[2].tx_bytes);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "rx_pkts", stats[2].rx_pkts);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "rx_bytes", stats[2].rx_bytes);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n app_data");
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "tx_pkts", stats[3].tx_pkts);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "tx_bytes", stats[3].tx_bytes);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "rx_pkts", stats[3].rx_pkts);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n\t%s:\t %llu", "rx_bytes", stats[3].rx_bytes);
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n--------------------------------\n");
}
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\nDTLS netlink ctype stats end\n\n");
bytes_read = simple_read_from_buffer(ubuf, sz, f_ppos, lbuf, size_wr);
vfree(lbuf);
return bytes_read;
}
/*
* nss_nldtls_stats_ops()
* file operation handler for dentry
*/
static const struct file_operations nss_nldtls_stats_ops = {
.read = nss_nldtls_tunnel_stats_read,
};
/*
* nss_nldtls_init()
* Init handler for dtls
*/
bool nss_nldtls_init(void)
{
int err;
nss_nl_info_always("Init NSS netlink dtls handler\n");
/*
* register NETLINK ops with the family
*/
err = genl_register_family(&nss_nldtls_family);
if (err) {
nss_nl_info_always("Error: %d unable to register gre_redir family\n", err);
genl_unregister_family(&nss_nldtls_family);
return false;
}
/*
* Create a debugfs entry for netlink dtls
*/
gbl_ctx.dentry = debugfs_create_dir("nldtls", NULL);
if (!gbl_ctx.dentry) {
nss_nl_info_always("Cannot create nldtls directory\n");
goto free_family;
}
if (!debugfs_create_file("stats", 0400, gbl_ctx.dentry, NULL, &nss_nldtls_stats_ops)) {
nss_nl_info_always("Cannot create nldtls dentry file\n");
goto free_debugfs;
}
return true;
free_debugfs:
debugfs_remove_recursive(gbl_ctx.dentry);
free_family:
genl_unregister_family(&nss_nldtls_family);
return false;
}
/*
* nss_nldtls_exit()
* Exit handler for dtls
*/
bool nss_nldtls_exit(void)
{
struct nss_nldtls_tun_ctx *entry, *tmp;
struct net_device *dev;
int err;
nss_nl_info_always("Exit NSS netlink dtls handler\n");
/*
* Destroy all active tunnel before exiting
*/
list_for_each_entry_safe(entry, tmp, &gbl_ctx.dtls_list_head, list) {
dev = dev_get_by_name(&init_net, entry->dev_name);
if (dev) {
dev_put(dev);
nss_nldtls_destroy_tun(dev);
}
}
nss_nl_info_always("All active tunnels destroyed\n");
/*
* unregister the ops family
*/
err = genl_unregister_family(&nss_nldtls_family);
if (err) {
nss_nl_info_always("Error: %d unable to unregister dtls NETLINK family\n", err);
return false;
}
return true;
}