blob: 4d3619fa1a005477d2014eaec034234981f83290 [file] [log] [blame]
/*
**************************************************************************
* 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 TORTUOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
#include <linux/version.h>
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/of.h>
#include <linux/ipv6.h>
#include <linux/skbuff.h>
#include <linux/module.h>
#include <linux/bitops.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/etherdevice.h>
#include <linux/vmalloc.h>
#include <linux/debugfs.h>
#include <linux/atomic.h>
#include <net/protocol.h>
#include <net/route.h>
#include <net/ip6_route.h>
#include <crypto/aead.h>
#include <crypto/internal/hash.h>
#include <nss_api_if.h>
#include <nss_ipsec_cmn.h>
#include <nss_ipsecmgr.h>
#include "nss_ipsecmgr_ref.h"
#include "nss_ipsecmgr_flow.h"
#include "nss_ipsecmgr_sa.h"
#include "nss_ipsecmgr_ctx.h"
#include "nss_ipsecmgr_tunnel.h"
#include "nss_ipsecmgr_priv.h"
extern struct nss_ipsecmgr_drv *ipsecmgr_drv;
/*
* Flow tuple print info
*/
static const struct nss_ipsecmgr_print ipsecmgr_print_flow_tuple[] = {
{"dest_ip", NSS_IPSECMGR_PRINT_IPADDR},
{"src_ip", NSS_IPSECMGR_PRINT_IPADDR},
{"spi", NSS_IPSECMGR_PRINT_WORD},
{"dst_port", NSS_IPSECMGR_PRINT_WORD},
{"src_port", NSS_IPSECMGR_PRINT_WORD},
{"user_pattern", NSS_IPSECMGR_PRINT_WORD},
{"protocol", NSS_IPSECMGR_PRINT_WORD},
{"ip_version", NSS_IPSECMGR_PRINT_WORD},
};
/*
* nss_ipsecmgr_flow_print_len()
* Print flow length
*/
static ssize_t nss_ipsecmgr_flow_print_len(struct nss_ipsecmgr_ref *ref)
{
const struct nss_ipsecmgr_print *prn = ipsecmgr_print_flow_tuple;
ssize_t len = NSS_IPSECMGR_FLOW_PRINT_EXTRA;
int i;
for (i = 0; i < ARRAY_SIZE(ipsecmgr_print_flow_tuple); i++, prn++)
len += strlen(prn->str) + prn->var_size;
return len;
}
/*
* nss_ipsecmgr_flow_print()
* Print flow
*/
static ssize_t nss_ipsecmgr_flow_print(struct nss_ipsecmgr_ref *ref, char *buf)
{
struct nss_ipsecmgr_flow *flow = container_of(ref, struct nss_ipsecmgr_flow, ref);
const struct nss_ipsecmgr_print *prn = ipsecmgr_print_flow_tuple;
struct nss_ipsec_cmn_flow_tuple *tuple = &flow->state.tuple;
uint32_t dest_ip[4], src_ip[4];
ssize_t max_len, len = 0;
max_len = nss_ipsecmgr_flow_print_len(&flow->ref);
len += snprintf(buf + len, max_len - len, "Flow tuple: {");
switch (tuple->ip_ver) {
case IPVERSION:
len += snprintf(buf + len, max_len - len, "%s: %pI4h,", prn->str, tuple->dest_ip);
prn++;
len += snprintf(buf + len, max_len - len, "%s: %pI4h,", prn->str, tuple->src_ip);
prn++;
break;
case 6:
nss_ipsecmgr_hton_v6addr(src_ip, tuple->src_ip);
nss_ipsecmgr_hton_v6addr(dest_ip, tuple->dest_ip);
len += snprintf(buf + len, max_len - len, "%s: %pI6c,", prn->str, dest_ip);
prn++;
len += snprintf(buf + len, max_len - len, "%s: %pI6c,", prn->str, src_ip);
prn++;
break;
}
len += snprintf(buf + len, max_len - len, "%s: 0x%x,", prn->str, tuple->spi_index);
prn++;
len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->dst_port);
prn++;
len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->src_port);
prn++;
len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->user_pattern);
prn++;
len += snprintf(buf + len, max_len - len, "%s: %u,", prn->str, tuple->protocol);
prn++;
len += snprintf(buf + len, max_len - len, "%s: %u", prn->str, tuple->ip_ver);
prn++;
len += snprintf(buf + len, max_len - len, "}\n");
return len;
}
/*
* nss_ipsecmgr_flow_update_db()
* Add flow to DB.
*/
static bool nss_ipsecmgr_flow_update_db(struct nss_ipsecmgr_flow *flow)
{
struct nss_ipsec_cmn_sa_tuple *sa_tuple = &flow->state.sa;
struct nss_ipsecmgr_sa *sa;
uint32_t hash_idx;
hash_idx = nss_ipsecmgr_flow_tuple2hash(&flow->state.tuple, NSS_IPSECMGR_FLOW_MAX);
write_lock(&ipsecmgr_drv->lock);
sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, sa_tuple);
if (!sa) {
write_unlock(&ipsecmgr_drv->lock);
nss_ipsecmgr_trace("%px: failed to find SA during flow update", flow);
return false;
}
flow->sa = sa;
/*
* Add reference of the flow to the SA associated with the outer.
*/
nss_ipsecmgr_ref_add(&flow->ref, &sa->ref);
list_add(&flow->list, &ipsecmgr_drv->flow_db[hash_idx]);
write_unlock(&ipsecmgr_drv->lock);
return true;
}
/*
* nss_ipsecmgr_flow_create_resp()
* response for the flow message
*/
static void nss_ipsecmgr_flow_create_resp(void *app_data, struct nss_cmn_msg *ncm)
{
struct nss_ipsecmgr_flow *flow = app_data;
/*
* If, NSS rejected the flow add then we will not
* add it to our list
*/
if (ncm->response != NSS_CMN_RESPONSE_ACK) {
nss_ipsecmgr_trace("%px: NSS response error (%u)", flow, ncm->error);
kfree(flow);
return;
}
if (!nss_ipsecmgr_flow_update_db(flow)) {
nss_ipsecmgr_trace("%px: failed to update flow in DB (%u)", flow, ncm->error);
kfree(flow);
return;
}
nss_ipsecmgr_trace("%px: Sucessfully updated flow in DB", flow);
}
/*
* nss_ipsecmgr_flow_free()
* Free flow object
*/
static void nss_ipsecmgr_flow_free(struct nss_ipsecmgr_flow *flow)
{
kfree(flow);
}
/*
* nss_ipsecmgr_flow_del_ref()
* Unlink the flow entry
*/
static void nss_ipsecmgr_flow_del_ref(struct nss_ipsecmgr_ref *ref)
{
struct nss_ipsecmgr_flow *flow = container_of(ref, struct nss_ipsecmgr_flow, ref);
/*
* Write lock needs to be held by the caller since flow db is
* getting modified.
*/
nss_ipsecmgr_write_lock_is_held(&ipsecmgr_drv->lock);
list_del_init(&flow->list);
}
/*
* nss_ipsecmgr_flow_free_ref()
* Free the flow entry
*/
static void nss_ipsecmgr_flow_free_ref(struct nss_ipsecmgr_ref *ref)
{
struct nss_ipsecmgr_flow *flow = container_of(ref, struct nss_ipsecmgr_flow, ref);
enum nss_ipsec_cmn_msg_type type = NSS_IPSEC_CMN_MSG_TYPE_FLOW_DESTROY;
struct nss_ipsec_cmn_msg nicm;
nss_tx_status_t status;
memset(&nicm, 0, sizeof(nicm));
memcpy(&nicm.msg.flow.flow_tuple, &flow->state.tuple, sizeof(nicm.msg.flow.flow_tuple));
memcpy(&nicm.msg.flow.sa_tuple, &flow->state.sa, sizeof(nicm.msg.flow.sa_tuple));
status = nss_ipsec_cmn_tx_msg_sync(flow->nss_ctx, flow->ifnum, type, sizeof(nicm.msg.flow), &nicm);
if (status != NSS_TX_SUCCESS) {
nss_ipsecmgr_info("%px: failed to send the flow message(%u)", flow, type);
}
if (nicm.cm.error != NSS_IPSEC_CMN_MSG_ERROR_NONE) {
nss_ipsecmgr_warn("%px: failed to free flow from NSS (%u)", flow, nicm.cm.error);
}
nss_ipsecmgr_flow_free(flow);
}
/*
* nss_ipsecmgr_flow_alloc()
* Add a new flow to database
*/
static struct nss_ipsecmgr_flow *nss_ipsecmgr_flow_alloc(struct nss_ipsecmgr_sa *sa,
struct nss_ipsec_cmn_flow_tuple *flow_tuple,
struct nss_ipsec_cmn_sa_tuple *sa_tuple)
{
struct nss_ipsecmgr_flow *flow = NULL;
flow = kzalloc(sizeof(*flow), GFP_ATOMIC);
if (!flow) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_info("%px: failed to allocate flow", sa);
return NULL;
}
/*
* Initialize the flow entry
*/
flow->ifnum = sa->ifnum;
flow->nss_ctx = sa->nss_ctx;
flow->tunnel_id = sa->tunnel_id;
INIT_LIST_HEAD(&flow->list);
nss_ipsecmgr_ref_init(&flow->ref, nss_ipsecmgr_flow_del_ref, nss_ipsecmgr_flow_free_ref);
nss_ipsecmgr_ref_init_print(&flow->ref, nss_ipsecmgr_flow_print_len, nss_ipsecmgr_flow_print);
/*
* Copy information received for flow creation
*/
memcpy(&flow->state.tuple, flow_tuple, sizeof(flow->state.tuple));
memcpy(&flow->state.sa, sa_tuple, sizeof(flow->state.sa));
return flow;
}
/*
* nss_ipsecmgr_flow_find()
* Lookup flow_tuple in the flow data base
*
* Note: No locks are taken here; so it needs to be called with a read/write lock held.
*/
struct nss_ipsecmgr_flow *nss_ipsecmgr_flow_find(struct list_head *db, struct nss_ipsec_cmn_flow_tuple *tuple)
{
uint32_t hash_idx = nss_ipsecmgr_flow_tuple2hash(tuple, NSS_IPSECMGR_FLOW_MAX);
struct list_head *head = &db[hash_idx];
struct nss_ipsecmgr_flow *flow;
/*
* Linux does not provide any specific API(s) to test for RW locks. The caller
* being internal is assumed to hold write lock before initiating this.
*/
list_for_each_entry(flow, head, list) {
if (nss_ipsecmgr_flow_tuple_match(&flow->state.tuple, tuple))
return flow;
}
return NULL;
}
/*
* nss_ipsecmgr_flow_get_sa()
* Lookup flow_tuple in flow database and return sa_tuple.
*/
nss_ipsecmgr_status_t nss_ipsecmgr_flow_get_sa(struct net_device *tun, struct nss_ipsecmgr_flow_tuple *flow_tuple,
struct nss_ipsecmgr_sa_tuple *sa_tuple)
{
struct nss_ipsec_cmn_flow_tuple tuple;
struct nss_ipsecmgr_flow *flow;
struct nss_ipsecmgr_sa *sa;
nss_ipsecmgr_flow2tuple(flow_tuple, &tuple);
write_lock_bh(&ipsecmgr_drv->lock);
flow = nss_ipsecmgr_flow_find(ipsecmgr_drv->flow_db, &tuple);
if (!flow) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_trace("%px: Failed to find flow", tun);
return NSS_IPSECMGR_FAIL_FLOW;
}
sa = flow->sa;
if (!sa) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_error("%px: SA is not associated with flow", tun);
return NSS_IPSECMGR_INVALID_SA;
}
nss_ipsecmgr_sa_tuple2sa(&sa->state.tuple, sa_tuple);
write_unlock_bh(&ipsecmgr_drv->lock);
return NSS_IPSECMGR_OK;
}
EXPORT_SYMBOL(nss_ipsecmgr_flow_get_sa);
/*
* nss_ipsecmgr_flow_del()
* Delete a existing flow from the database
*
* Note: We will only delete the flow associated with the requested SA.
* If we donot find the association then the flow delete will not perform
* the delete
*/
void nss_ipsecmgr_flow_del(struct net_device *dev, struct nss_ipsecmgr_flow_tuple *f_tuple,
struct nss_ipsecmgr_sa_tuple *s_tuple)
{
struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev);
struct list_head *flow_db = ipsecmgr_drv->flow_db;
struct list_head *sa_db = ipsecmgr_drv->sa_db;
struct nss_ipsec_cmn_flow_tuple flow_tuple = {0};
struct nss_ipsec_cmn_sa_tuple sa_tuple = {0};
struct nss_ipsecmgr_flow *flow;
struct nss_ipsecmgr_sa *sa;
nss_ipsecmgr_flow2tuple(f_tuple, &flow_tuple);
nss_ipsecmgr_sa2tuple(s_tuple, &sa_tuple);
/*
* Write lock needed here since Flow DB is looked up and removed
* No one should be accessing flow at this point
*/
write_lock_bh(&ipsecmgr_drv->lock);
sa = nss_ipsecmgr_sa_find(sa_db, &sa_tuple);
if (!sa) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_warn("%px: failed to find SA during flow_del", tun);
return;
}
flow = nss_ipsecmgr_flow_find(flow_db, &flow_tuple);
if (!flow) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_warn("%px: failed to find flow for flow_del", tun);
return;
}
/*
* Match if the SA provided in indeed associated with the flow. In case this is
* not associated due to re-key then we should not remove the flow
*/
if (flow->sa != sa) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_warn("%px: failed to match the SA in flow(%px) delete", tun, flow);
return;
}
/*
* Free the flow entry and reference.
*/
nss_ipsecmgr_ref_del(&flow->ref, &tun->free_refs);
write_unlock_bh(&ipsecmgr_drv->lock);
schedule_work(&tun->free_work);
}
EXPORT_SYMBOL(nss_ipsecmgr_flow_del);
/*
* nss_ipsecmgr_flow_add()
* Add a new flow to database asynchronously.
*/
nss_ipsecmgr_status_t nss_ipsecmgr_flow_add(struct net_device *dev, struct nss_ipsecmgr_flow_tuple *f_tuple,
struct nss_ipsecmgr_sa_tuple *s_tuple)
{
enum nss_ipsec_cmn_msg_type type = NSS_IPSEC_CMN_MSG_TYPE_FLOW_CREATE;
struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev);
struct nss_ipsec_cmn_flow_tuple *flow_tuple;
struct nss_ipsec_cmn_sa_tuple *sa_tuple;
struct nss_ipsec_cmn_msg nicm = {{0}};
struct nss_ipsecmgr_flow *flow;
struct nss_ipsecmgr_ctx *ctx;
struct nss_ipsecmgr_sa *sa;
nss_tx_status_t status;
dev_hold(dev);
flow_tuple = &nicm.msg.flow.flow_tuple;
sa_tuple = &nicm.msg.flow.sa_tuple;
nss_ipsecmgr_sa2tuple(s_tuple, sa_tuple);
nss_ipsecmgr_flow2tuple(f_tuple, flow_tuple);
/*
* Write lock needed here since Flow DB is looked up and added
* No one should be accessing/removing flow at this point
*/
write_lock_bh(&ipsecmgr_drv->lock);
/*
* Check if the SA exists
*/
sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, sa_tuple);
if (!sa) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_warn("%px: failed to find SA during flow_add", tun);
dev_put(dev);
return NSS_IPSECMGR_INVALID_SA;
}
/*
* Check if the context exists
*/
ctx = nss_ipsecmgr_ctx_find(tun, sa->type);
if (!ctx) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_warn("%px: Failed to find context (%u)", tun, sa->type);
dev_put(dev);
return NSS_IPSECMGR_INVALID_CTX;
}
/*
* TODO: Why do we need this
*/
memcpy(sa_tuple, &sa->state.tuple, sizeof(*sa_tuple));
/*
* At this point we do not know whether the flow is duplicate or not
* we try to perform a flow lookup to determine whether the flow already
* exists in the data base. Once, we have determined the flow then we check
* if the caller is performing a SA switch.
*/
flow = nss_ipsecmgr_flow_find(ipsecmgr_drv->flow_db, flow_tuple);
if (flow) {
/*
* Flow already exists; check if the SA entry is the same. In case
* the SA is same then it is surely a duplicate entry. We need to
* return from here without doing anything.
*/
if (flow->sa == sa) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_trace("%px: Duplicate flow in flow_add", ipsecmgr_drv);
dev_put(dev);
return NSS_IPSECMGR_DUPLICATE_FLOW;
}
/*
* Detach the flow from the reference tree and flow database, then free the object.
* Here we don't want to inform the NSS as it is not a real flow free.
* We are just moving an existing flow to a new SA
*/
nss_ipsecmgr_ref_del(&flow->ref, NULL);
nss_ipsecmgr_flow_free(flow);
}
flow = nss_ipsecmgr_flow_alloc(sa, flow_tuple, sa_tuple);
if (!flow) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_info("%px: failed to allocate flow", tun);
dev_put(dev);
return NSS_IPSECMGR_FAIL_FLOW_ALLOC;
}
write_unlock_bh(&ipsecmgr_drv->lock);
/*
* Initialize the IPsec message to be sent.
*/
nss_ipsec_cmn_msg_init(&nicm, flow->ifnum, type, sizeof(nicm.msg.flow), nss_ipsecmgr_flow_create_resp, flow);
/*
* Note: We don't need to hold the lock for the accessing the flow since
* it is not part of the list yet. Now, send a message to firmware for
* updating the flow in its table. Once, NSS confirms it then we will
* add the flow back in the database
*/
status = nss_ipsec_cmn_tx_msg(flow->nss_ctx, &nicm);
if (status != NSS_TX_SUCCESS) {
nss_ipsecmgr_info("%px: failed to send the flow create message", dev);
nss_ipsecmgr_flow_free(flow);
dev_put(dev);
return NSS_IPSECMGR_FAIL;
}
dev_put(dev);
return NSS_IPSECMGR_OK;
}
EXPORT_SYMBOL(nss_ipsecmgr_flow_add);
/*
* nss_ipsecmgr_flow_add()
* Add a new flow to database synchronously.
*/
nss_ipsecmgr_status_t nss_ipsecmgr_flow_add_sync(struct net_device *dev, struct nss_ipsecmgr_flow_tuple *f_tuple,
struct nss_ipsecmgr_sa_tuple *s_tuple)
{
enum nss_ipsec_cmn_msg_type type = NSS_IPSEC_CMN_MSG_TYPE_FLOW_CREATE;
struct nss_ipsecmgr_tunnel *tun = netdev_priv(dev);
struct nss_ipsec_cmn_flow_tuple *flow_tuple;
struct nss_ipsec_cmn_sa_tuple *sa_tuple;
struct nss_ipsec_cmn_msg nicm = {{0}};
struct nss_ipsecmgr_flow *flow;
struct nss_ipsecmgr_ctx *ctx;
struct nss_ipsecmgr_sa *sa;
nss_tx_status_t status;
BUG_ON(in_atomic());
dev_hold(dev);
memset(&nicm, 0, sizeof(nicm));
flow_tuple = &nicm.msg.flow.flow_tuple;
sa_tuple = &nicm.msg.flow.sa_tuple;
nss_ipsecmgr_sa2tuple(s_tuple, sa_tuple);
nss_ipsecmgr_flow2tuple(f_tuple, flow_tuple);
/*
* Write lock needed here since Flow DB is looked up and added
* No one should be accessing/removing flow at this point
*/
write_lock_bh(&ipsecmgr_drv->lock);
/*
* Check if the SA exists
*/
sa = nss_ipsecmgr_sa_find(ipsecmgr_drv->sa_db, sa_tuple);
if (!sa) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_warn("%px: failed to find SA during flow_add", tun);
dev_put(dev);
return NSS_IPSECMGR_INVALID_SA;
}
/*
* Check if the context exists
*/
ctx = nss_ipsecmgr_ctx_find(tun, sa->type);
if (!ctx) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_warn("%px: Failed to find context (%u)", tun, sa->type);
dev_put(dev);
return NSS_IPSECMGR_INVALID_CTX;
}
/*
* TODO: Why do we need this
*/
memcpy(sa_tuple, &sa->state.tuple, sizeof(*sa_tuple));
/*
* At this point we do not know whether the flow is duplicate or not
* we try to perform a flow lookup to determine whether the flow already
* exists in the data base. Once, we have determined the flow then we check
* if the caller is performing a SA switch.
*/
flow = nss_ipsecmgr_flow_find(ipsecmgr_drv->flow_db, flow_tuple);
if (flow) {
/*
* Flow already exists; check if the SA entry is the same. In case
* the SA is same then it is surely a duplicate entry. We need to
* return from here without doing anything.
*/
if (flow->sa == sa) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_trace("%px: Duplicate flow in flow_add", ipsecmgr_drv);
dev_put(dev);
return NSS_IPSECMGR_DUPLICATE_FLOW;
}
/*
* Detach the flow from the reference tree and flow database, then free the object.
* Here we don't want to inform the NSS as it is not a real flow free.
* We are just moving an existing flow to a new SA
*/
nss_ipsecmgr_ref_del(&flow->ref, NULL);
nss_ipsecmgr_flow_free(flow);
}
flow = nss_ipsecmgr_flow_alloc(sa, flow_tuple, sa_tuple);
if (!flow) {
write_unlock_bh(&ipsecmgr_drv->lock);
nss_ipsecmgr_info("%px: failed to allocate flow", tun);
dev_put(dev);
return NSS_IPSECMGR_FAIL_FLOW_ALLOC;
}
write_unlock_bh(&ipsecmgr_drv->lock);
/*
* Initialize the IPsec message to be sent.
*/
nss_ipsec_cmn_msg_init(&nicm, flow->ifnum, type, sizeof(nicm.msg.flow), NULL, NULL);
/*
* Note: We don't need to hold the lock for the accessing the flow since
* it is not part of the list yet. Now, send a message to firmware for
* updating the flow in its table. Once, NSS confirms it then we will
* add the flow back in the database
*/
status = nss_ipsec_cmn_tx_msg_sync(flow->nss_ctx, flow->ifnum, type, sizeof(nicm.msg.flow), &nicm);
if (status != NSS_TX_SUCCESS) {
nss_ipsecmgr_info("%px: failed to send the flow create message", dev);
nss_ipsecmgr_flow_free(flow);
dev_put(dev);
return NSS_IPSECMGR_FAIL;
}
/*
* Since, this is a synchronous call add it to the database directly
*/
if (!nss_ipsecmgr_flow_update_db(flow)) {
nss_ipsecmgr_info("%px: failed to add flow to DB", dev);
nss_ipsecmgr_flow_free(flow);
dev_put(dev);
return NSS_IPSECMGR_FAIL_ADD_DB;
}
dev_put(dev);
return NSS_IPSECMGR_OK;
}
EXPORT_SYMBOL(nss_ipsecmgr_flow_add_sync);