Merge "[qca-nss-ecm] Request connections stats from ECM"
diff --git a/frontends/nss/ecm_nss_ipv4.c b/frontends/nss/ecm_nss_ipv4.c
index 3a0a6fb..578a146 100644
--- a/frontends/nss/ecm_nss_ipv4.c
+++ b/frontends/nss/ecm_nss_ipv4.c
@@ -105,6 +105,9 @@
#include "ecm_front_end_common.h"
+#define ECM_NSS_IPV4_STATS_SYNC_PERIOD msecs_to_jiffies(1000)
+#define ECM_NSS_IPV4_STATS_SYNC_UDELAY 4000 /* Delay for 4 ms */
+
int ecm_nss_ipv4_no_action_limit_default = 250; /* Default no-action limit. */
int ecm_nss_ipv4_driver_fail_limit_default = 250; /* Default driver fail limit. */
int ecm_nss_ipv4_nack_limit_default = 250; /* Default nack limit. */
@@ -155,6 +158,17 @@
static int ecm_nss_ipv4_stopped = 0; /* When non-zero further traffic will not be processed */
/*
+ * Workqueue for the connection sync
+ */
+struct workqueue_struct *ecm_nss_ipv4_workqueue;
+struct delayed_work ecm_nss_ipv4_work;
+struct nss_ipv4_msg *ecm_nss_ipv4_sync_req_msg;
+static unsigned long int ecm_nss_ipv4_next_req_time;
+static unsigned long int ecm_nss_ipv4_stats_request_success = 0; /* Number of success stats request */
+static unsigned long int ecm_nss_ipv4_stats_request_fail = 0; /* Number of failed stats request */
+static unsigned long int ecm_nss_ipv4_stats_request_nack = 0; /* Number of NACK'd stats request */
+
+/*
* ecm_nss_ipv4_node_establish_and_ref()
* Returns a reference to a node, possibly creating one if necessary.
*
@@ -1550,12 +1564,11 @@
}
/*
- * ecm_nss_ipv4_net_dev_callback()
- * Callback handler from the NSS.
+ * ecm_nss_ipv4_process_one_conn_sync_msg()
+ * Process one connection sync message.
*/
-static void ecm_nss_ipv4_net_dev_callback(void *app_data, struct nss_ipv4_msg *nim)
+static inline void ecm_nss_ipv4_process_one_conn_sync_msg(struct nss_ipv4_conn_sync *sync)
{
- struct nss_ipv4_conn_sync *sync;
struct nf_conntrack_tuple_hash *h;
struct nf_conntrack_tuple tuple;
struct nf_conn *ct;
@@ -1572,15 +1585,6 @@
struct ecm_classifier_rule_sync class_sync;
/*
- * Only respond to sync messages
- */
- if (nim->cm.type != NSS_IPV4_RX_CONN_STATS_SYNC_MSG) {
- DEBUG_TRACE("Ignoring nim: %p - not sync: %d", nim, nim->cm.type);
- return;
- }
- sync = &nim->msg.conn_stats;
-
- /*
* Look up ecm connection with a view to synchronising the connection, classifier and data tracker.
* Note that we use _xlate versions for destination - for egressing connections this would be the wan IP address,
* but for ingressing this would be the LAN side (non-nat'ed) address and is what we need for lookup of our connection.
@@ -1902,6 +1906,110 @@
}
/*
+ * ecm_nss_ipv4_net_dev_callback()
+ * Callback handler from the NSS.
+ */
+static void ecm_nss_ipv4_net_dev_callback(void *app_data, struct nss_ipv4_msg *nim)
+{
+ struct nss_ipv4_conn_sync *sync;
+
+ /*
+ * Only respond to sync messages
+ */
+ if (nim->cm.type != NSS_IPV4_RX_CONN_STATS_SYNC_MSG) {
+ DEBUG_TRACE("Ignoring nim: %p - not sync: %d", nim, nim->cm.type);
+ return;
+ }
+ sync = &nim->msg.conn_stats;
+ ecm_nss_ipv4_process_one_conn_sync_msg(sync);
+}
+
+/*
+ * ecm_nss_ipv4_connection_sync_many_callback()
+ * Callback for conn_sync_many request message
+ */
+static void ecm_nss_ipv4_connection_sync_many_callback(void *app_data, struct nss_ipv4_msg *nim)
+{
+ struct nss_ipv4_conn_sync_many_msg *nicsm = &nim->msg.conn_stats_many;
+ int i;
+
+ /*
+ * If ECM is terminating, don't process this final stats
+ */
+ if (ecm_nss_ipv4_terminate_pending) {
+ return;
+ }
+
+ if (nim->cm.response == NSS_CMN_RESPONSE_ACK) {
+ for (i = 0; i < nicsm->count; i++) {
+ ecm_nss_ipv4_process_one_conn_sync_msg(&nicsm->conn_sync[i]);
+ }
+ ecm_nss_ipv4_sync_req_msg->msg.conn_stats_many.index = nicsm->next;
+ } else {
+ /*
+ * We get a NACK from FW, which should not happen, restart the request
+ */
+ DEBUG_WARN("IPv4 conn stats request failed, restarting\n");
+ ecm_nss_ipv4_stats_request_nack++;
+ ecm_nss_ipv4_sync_req_msg->msg.conn_stats_many.index = 0;
+ }
+ queue_delayed_work(ecm_nss_ipv4_workqueue, &ecm_nss_ipv4_work, 0);
+}
+
+/*
+ * ecm_nss_ipv4_stats_sync_req_work()
+ * Schedule delayed work to process connection stats and request next sync
+ */
+static void ecm_nss_ipv4_stats_sync_req_work(struct work_struct *work)
+{
+ /*
+ * Prepare a nss_ipv4_msg with CONN_STATS_SYNC_MANY request
+ */
+ struct nss_ipv4_conn_sync_many_msg *nicsm_req;
+ nss_tx_status_t nss_tx_status;
+ int retry = 3;
+ unsigned long int current_jiffies;
+
+ usleep_range(ECM_NSS_IPV4_STATS_SYNC_UDELAY - 100, ECM_NSS_IPV4_STATS_SYNC_UDELAY);
+
+ nicsm_req = &ecm_nss_ipv4_sync_req_msg->msg.conn_stats_many;
+
+ /*
+ * If index is 0, we are starting a new round, but if we still have time remain
+ * in this round, sleep until it ends
+ */
+ if (nicsm_req->index == 0) {
+ current_jiffies = jiffies;
+ if (ecm_nss_ipv4_next_req_time > current_jiffies) {
+ msleep(jiffies_to_msecs(ecm_nss_ipv4_next_req_time - current_jiffies));
+ }
+ ecm_nss_ipv4_next_req_time = jiffies + ECM_NSS_IPV4_STATS_SYNC_PERIOD;
+ }
+
+ while (retry) {
+ if (ecm_nss_ipv4_terminate_pending) {
+ return;
+ }
+ nss_tx_status = nss_ipv4_tx_with_size(ecm_nss_ipv4_nss_ipv4_mgr, ecm_nss_ipv4_sync_req_msg, PAGE_SIZE);
+ if (nss_tx_status == NSS_TX_SUCCESS) {
+ ecm_nss_ipv4_stats_request_success++;
+ return;
+ }
+ ecm_nss_ipv4_stats_request_fail++;
+ retry--;
+ DEBUG_TRACE("TX_NOT_OKAY, try again later\n");
+ usleep_range(100, 200);
+ }
+
+ /*
+ * TX failed after retries, reschedule ourselves with fresh start
+ */
+ nicsm_req->count = 0;
+ nicsm_req->index = 0;
+ queue_delayed_work(ecm_nss_ipv4_workqueue, &ecm_nss_ipv4_work, ECM_NSS_IPV4_STATS_SYNC_PERIOD);
+}
+
+/*
* struct nf_hook_ops ecm_nss_ipv4_netfilter_hooks[]
* Hooks into netfilter packet monitoring points.
*/
@@ -2249,6 +2357,82 @@
};
/*
+ * ecm_nss_ipv4_get_stats_request_counter()
+ */
+static ssize_t ecm_nss_ipv4_get_stats_request_counter(struct file *file,
+ char __user *user_buf,
+ size_t sz, loff_t *ppos)
+{
+ char *buf;
+ int ret;
+
+ buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf) {
+ return -ENOMEM;
+ }
+
+ ret = snprintf(buf, (ssize_t)PAGE_SIZE, "success=%lu\tfail=%lu\tnack=%lu\t\n",
+ ecm_nss_ipv4_stats_request_success, ecm_nss_ipv4_stats_request_fail,
+ ecm_nss_ipv4_stats_request_nack);
+ if (ret < 0) {
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ ret = simple_read_from_buffer(user_buf, sz, ppos, buf, ret);
+ kfree(buf);
+ return ret;
+}
+
+/*
+ * File operations for decel command average time.
+ */
+static struct file_operations ecm_nss_ipv4_stats_request_counter_fops = {
+ .read = ecm_nss_ipv4_get_stats_request_counter,
+};
+
+/*
+ * ecm_nss_ipv4_sync_queue_init
+ * Initialize the workqueue for ipv4 stats sync
+ */
+static bool ecm_nss_ipv4_sync_queue_init(void)
+{
+ struct nss_ipv4_conn_sync_many_msg *nicsm;
+
+ /*
+ * Setup the connection sync msg/work/workqueue
+ */
+ ecm_nss_ipv4_sync_req_msg = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!ecm_nss_ipv4_sync_req_msg) {
+ return false;
+ }
+
+ nss_ipv4_msg_init(ecm_nss_ipv4_sync_req_msg, NSS_IPV4_RX_INTERFACE,
+ NSS_IPV4_TX_CONN_STATS_SYNC_MANY_MSG,
+ sizeof(struct nss_ipv4_conn_sync_many_msg) ,
+ ecm_nss_ipv4_connection_sync_many_callback,
+ NULL);
+
+ nicsm = &ecm_nss_ipv4_sync_req_msg->msg.conn_stats_many;
+
+ /*
+ * Start with index 0 and calculate the size of the conn stats array
+ */
+ nicsm->index = 0;
+ nicsm->size = PAGE_SIZE;
+
+ ecm_nss_ipv4_workqueue = create_singlethread_workqueue("ecm_nss_ipv4_workqueue");
+ if (!ecm_nss_ipv4_workqueue) {
+ kfree(ecm_nss_ipv4_sync_req_msg);
+ return false;
+ }
+ INIT_DELAYED_WORK(&ecm_nss_ipv4_work, ecm_nss_ipv4_stats_sync_req_work);
+ queue_delayed_work(ecm_nss_ipv4_workqueue, &ecm_nss_ipv4_work, ECM_NSS_IPV4_STATS_SYNC_PERIOD);
+
+ return true;
+}
+
+/*
* ecm_nss_ipv4_init()
*/
int ecm_nss_ipv4_init(struct dentry *dentry)
@@ -2328,6 +2512,12 @@
goto task_cleanup;
}
+ if (!debugfs_create_file("stats_request_counter", S_IRUGO, ecm_nss_ipv4_dentry,
+ NULL, &ecm_nss_ipv4_stats_request_counter_fops)) {
+ DEBUG_ERROR("Failed to create ecm nss ipv4 stats request counter file in debugfs\n");
+ goto task_cleanup;
+ }
+
#ifdef ECM_NON_PORTED_SUPPORT_ENABLE
if (!ecm_nss_non_ported_ipv4_debugfs_init(ecm_nss_ipv4_dentry)) {
DEBUG_ERROR("Failed to create ecm non-ported files in debugfs\n");
@@ -2351,6 +2541,11 @@
goto task_cleanup;
}
+ if (!ecm_nss_ipv4_sync_queue_init()) {
+ DEBUG_ERROR("Failed to create ecm ipv4 connection sync workqueue\n");
+ goto task_cleanup;
+ }
+
#ifdef ECM_MULTICAST_ENABLE
ecm_nss_multicast_ipv4_init();
#endif
@@ -2401,5 +2596,12 @@
#ifdef ECM_MULTICAST_ENABLE
ecm_nss_multicast_ipv4_exit();
#endif
+
+ /*
+ * Cancel the conn sync req work and destroy workqueue
+ */
+ cancel_delayed_work_sync(&ecm_nss_ipv4_work);
+ destroy_workqueue(ecm_nss_ipv4_workqueue);
+ kfree(ecm_nss_ipv4_sync_req_msg);
}
EXPORT_SYMBOL(ecm_nss_ipv4_exit);
diff --git a/frontends/nss/ecm_nss_ipv6.c b/frontends/nss/ecm_nss_ipv6.c
index fee8c42..20024de 100644
--- a/frontends/nss/ecm_nss_ipv6.c
+++ b/frontends/nss/ecm_nss_ipv6.c
@@ -112,6 +112,9 @@
#include "ecm_front_end_common.h"
+#define ECM_NSS_IPV6_STATS_SYNC_PERIOD msecs_to_jiffies(1000)
+#define ECM_NSS_IPV6_STATS_SYNC_UDELAY 4000 /* Delay for 4ms */
+
int ecm_nss_ipv6_no_action_limit_default = 250; /* Default no-action limit. */
int ecm_nss_ipv6_driver_fail_limit_default = 250; /* Default driver fail limit. */
int ecm_nss_ipv6_nack_limit_default = 250; /* Default nack limit. */
@@ -142,6 +145,17 @@
bool ecm_nss_ipv6_terminate_pending = false; /* True when the user has signalled we should quit */
/*
+ * Workqueue for the connection sync
+ */
+struct workqueue_struct *ecm_nss_ipv6_workqueue;
+struct delayed_work ecm_nss_ipv6_work;
+struct nss_ipv6_msg *ecm_nss_ipv6_sync_req_msg;
+static unsigned long int ecm_nss_ipv6_next_req_time;
+static unsigned long int ecm_nss_ipv6_stats_request_success = 0; /* Number of success stats request */
+static unsigned long int ecm_nss_ipv6_stats_request_fail = 0; /* Number of failed stats request */
+static unsigned long int ecm_nss_ipv6_stats_request_nack = 0; /* Number of NACK'd stats request */
+
+/*
* NSS driver linkage
*/
struct nss_ctx_instance *ecm_nss_ipv6_nss_ipv6_mgr = NULL;
@@ -1281,12 +1295,11 @@
}
/*
- * ecm_nss_ipv6_net_dev_callback()
- * Callback handler from the NSS.
+ * ecm_nss_ipv6_process_one_conn_sync_msg()
+ * Process one connection sync message.
*/
-static void ecm_nss_ipv6_net_dev_callback(void *app_data, struct nss_ipv6_msg *nim)
+static inline void ecm_nss_ipv6_process_one_conn_sync_msg(struct nss_ipv6_conn_sync *sync)
{
- struct nss_ipv6_conn_sync *sync;
struct nf_conntrack_tuple_hash *h;
struct nf_conntrack_tuple tuple;
struct nf_conn *ct;
@@ -1303,15 +1316,6 @@
struct in6_addr origin6 __attribute__((unused));
struct ecm_classifier_rule_sync class_sync;
- /*
- * Only respond to sync messages
- */
- if (nim->cm.type != NSS_IPV6_RX_CONN_STATS_SYNC_MSG) {
- DEBUG_TRACE("Ignoring nim: %p - not sync: %d", nim, nim->cm.type);
- return;
- }
- sync = &nim->msg.conn_stats;
-
ECM_NSS_IPV6_ADDR_TO_IP_ADDR(flow_ip, sync->flow_ip);
ECM_NSS_IPV6_ADDR_TO_IP_ADDR(return_ip, sync->return_ip);
@@ -1620,6 +1624,110 @@
}
/*
+ * ecm_nss_ipv6_net_dev_callback()
+ * Callback handler from the NSS.
+ */
+static void ecm_nss_ipv6_net_dev_callback(void *app_data, struct nss_ipv6_msg *nim)
+{
+ struct nss_ipv6_conn_sync *sync;
+ /*
+ * Only respond to sync messages
+ */
+ if (nim->cm.type != NSS_IPV6_RX_CONN_STATS_SYNC_MSG) {
+ DEBUG_TRACE("Ignoring nim: %p - not sync: %d", nim, nim->cm.type);
+ return;
+ }
+ sync = &nim->msg.conn_stats;
+ ecm_nss_ipv6_process_one_conn_sync_msg(sync);
+}
+
+/*
+ * ecm_nss_ipv6_connection_sync_many_callback()
+ * Callback for conn_sync_many request message
+ */
+static void ecm_nss_ipv6_connection_sync_many_callback(void *app_data, struct nss_ipv6_msg *nim)
+{
+ struct nss_ipv6_conn_sync_many_msg *nicsm = &nim->msg.conn_stats_many;
+ int i;
+
+ /*
+ * If ECM is terminating, don't process this last stats
+ */
+ if (ecm_nss_ipv6_terminate_pending) {
+ return;
+ }
+
+ if (nim->cm.response == NSS_CMN_RESPONSE_ACK) {
+ for (i = 0; i < nicsm->count; i++) {
+ ecm_nss_ipv6_process_one_conn_sync_msg(&nicsm->conn_sync[i]);
+ }
+ ecm_nss_ipv6_sync_req_msg->msg.conn_stats_many.index = nicsm->next;
+ } else {
+ /*
+ * We get a NACK from FW, which should not happen, restart the request
+ */
+ DEBUG_WARN("IPv6 conn stats request failed, restarting\n");
+ ecm_nss_ipv6_stats_request_nack++;
+ ecm_nss_ipv6_sync_req_msg->msg.conn_stats_many.index = 0;
+ }
+ queue_delayed_work(ecm_nss_ipv6_workqueue, &ecm_nss_ipv6_work, 0);
+}
+
+/*
+ * ecm_nss_ipv6_stats_sync_req_work()
+ * Schedule delayed work to process connection stats and request next sync
+ */
+static void ecm_nss_ipv6_stats_sync_req_work(struct work_struct *work)
+{
+ /*
+ * Prepare a nss_ipv6_msg with CONN_STATS_SYNC_MANY request
+ */
+ struct nss_ipv6_conn_sync_many_msg *nicsm_req;
+ nss_tx_status_t nss_tx_status;
+ int retry = 3;
+ unsigned long int current_jiffies;
+
+ usleep_range(ECM_NSS_IPV6_STATS_SYNC_UDELAY - 100, ECM_NSS_IPV6_STATS_SYNC_UDELAY);
+
+ nicsm_req = &ecm_nss_ipv6_sync_req_msg->msg.conn_stats_many;
+
+ /*
+ * If index is 0, we are starting a new round, but if we still have time remain
+ * in this round, sleep until it ends
+ */
+ if (nicsm_req->index == 0) {
+ current_jiffies = jiffies;
+ if (ecm_nss_ipv6_next_req_time > current_jiffies) {
+ msleep(jiffies_to_msecs(ecm_nss_ipv6_next_req_time - current_jiffies));
+ }
+ ecm_nss_ipv6_next_req_time = jiffies + ECM_NSS_IPV6_STATS_SYNC_PERIOD;
+ }
+
+ while (retry) {
+ if (ecm_nss_ipv6_terminate_pending) {
+ return;
+ }
+ nss_tx_status = nss_ipv6_tx_with_size(ecm_nss_ipv6_nss_ipv6_mgr, ecm_nss_ipv6_sync_req_msg, PAGE_SIZE);
+ if (nss_tx_status == NSS_TX_SUCCESS) {
+ ecm_nss_ipv6_stats_request_success++;
+ return;
+ }
+ ecm_nss_ipv6_stats_request_fail++;
+ retry--;
+ DEBUG_TRACE("TX_NOT_OKAY, try again later\n");
+ usleep_range(100, 200);
+ }
+
+ /*
+ * TX failed after retries, reschedule ourselves with fresh start
+ */
+ nicsm_req->count = 0;
+ nicsm_req->index = 0;
+ queue_delayed_work(ecm_nss_ipv6_workqueue, &ecm_nss_ipv6_work, ECM_NSS_IPV6_STATS_SYNC_PERIOD);
+}
+
+
+/*
* struct nf_hook_ops ecm_nss_ipv6_netfilter_hooks[]
* Hooks into netfilter packet monitoring points.
*/
@@ -1967,6 +2075,82 @@
};
/*
+ * ecm_nss_ipv6_get_stats_request_counter()
+ */
+static ssize_t ecm_nss_ipv6_get_stats_request_counter(struct file *file,
+ char __user *user_buf,
+ size_t sz, loff_t *ppos)
+{
+ char *buf;
+ int ret;
+
+ buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf) {
+ return -ENOMEM;
+ }
+
+ ret = snprintf(buf, (ssize_t)PAGE_SIZE, "success=%lu\tfail=%lu\tnack=%lu\t\n",
+ ecm_nss_ipv6_stats_request_success, ecm_nss_ipv6_stats_request_fail,
+ ecm_nss_ipv6_stats_request_nack);
+ if (ret < 0) {
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ ret = simple_read_from_buffer(user_buf, sz, ppos, buf, ret);
+ kfree(buf);
+ return ret;
+}
+
+/*
+ * File operations for decel command average time.
+ */
+static struct file_operations ecm_nss_ipv6_stats_request_counter_fops = {
+ .read = ecm_nss_ipv6_get_stats_request_counter,
+};
+
+/*
+ * ecm_nss_ipv6_sync_queue_init
+ * Initialize the workqueue for ipv6 stats sync
+ */
+static bool ecm_nss_ipv6_sync_queue_init(void)
+{
+ struct nss_ipv6_conn_sync_many_msg *nicsm;
+
+ /*
+ * Setup the connection sync msg/work/workqueue
+ */
+ ecm_nss_ipv6_sync_req_msg = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!ecm_nss_ipv6_sync_req_msg) {
+ return false;
+ }
+
+ nss_ipv6_msg_init(ecm_nss_ipv6_sync_req_msg, NSS_IPV6_RX_INTERFACE,
+ NSS_IPV6_TX_CONN_STATS_SYNC_MANY_MSG,
+ sizeof(struct nss_ipv6_conn_sync_many_msg),
+ ecm_nss_ipv6_connection_sync_many_callback,
+ NULL);
+
+ nicsm = &ecm_nss_ipv6_sync_req_msg->msg.conn_stats_many;
+
+ /*
+ * Start with index 0 and calculate the size of the conn stats array
+ */
+ nicsm->index = 0;
+ nicsm->size = PAGE_SIZE;
+
+ ecm_nss_ipv6_workqueue = create_singlethread_workqueue("ecm_nss_ipv6_workqueue");
+ if (!ecm_nss_ipv6_workqueue) {
+ kfree(ecm_nss_ipv6_sync_req_msg);
+ return false;
+ }
+ INIT_DELAYED_WORK(&ecm_nss_ipv6_work, ecm_nss_ipv6_stats_sync_req_work);
+ queue_delayed_work(ecm_nss_ipv6_workqueue, &ecm_nss_ipv6_work, ECM_NSS_IPV6_STATS_SYNC_PERIOD);
+
+ return true;
+}
+
+/*
* ecm_nss_ipv6_init()
*/
int ecm_nss_ipv6_init(struct dentry *dentry)
@@ -2046,6 +2230,12 @@
goto task_cleanup;
}
+ if (!debugfs_create_file("stats_request_counter", S_IRUGO, ecm_nss_ipv6_dentry,
+ NULL, &ecm_nss_ipv6_stats_request_counter_fops)) {
+ DEBUG_ERROR("Failed to create ecm nss ipv6 stats_request_counter file in debugfs\n");
+ goto task_cleanup;
+ }
+
#ifdef ECM_NON_PORTED_SUPPORT_ENABLE
if (!ecm_nss_non_ported_ipv6_debugfs_init(ecm_nss_ipv6_dentry)) {
DEBUG_ERROR("Failed to create ecm non-ported files in debugfs\n");
@@ -2053,6 +2243,11 @@
}
#endif
+ if (!ecm_nss_ipv6_sync_queue_init()) {
+ DEBUG_ERROR("Failed to create ecm ipv6 connection sync workqueue\n");
+ goto task_cleanup;
+ }
+
#ifdef ECM_MULTICAST_ENABLE
if (!ecm_nss_multicast_ipv6_debugfs_init(ecm_nss_ipv6_dentry)) {
DEBUG_ERROR("Failed to create ecm multicast files in debugfs\n");
@@ -2118,5 +2313,12 @@
#ifdef ECM_MULTICAST_ENABLE
ecm_nss_multicast_ipv6_exit();
#endif
+
+ /*
+ * Cancel the conn sync req work and destroy workqueue
+ */
+ cancel_delayed_work_sync(&ecm_nss_ipv6_work);
+ destroy_workqueue(ecm_nss_ipv6_workqueue);
+ kfree(ecm_nss_ipv6_sync_req_msg);
}
EXPORT_SYMBOL(ecm_nss_ipv6_exit);