blob: 8221e77cf1e06a4b045498f3e5dc212c47d1e5c1 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2014-2018, 2020-2021 The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. 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 <linux/version.h>
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/icmp.h>
#include <linux/kthread.h>
#include <linux/debugfs.h>
#include <linux/pkt_sched.h>
#include <linux/string.h>
#include <linux/random.h>
#include <net/route.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <asm/unaligned.h>
#include <asm/uaccess.h> /* for put_user */
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_bridge.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_l4proto.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/ipv4/nf_conntrack_ipv4.h>
#include <net/netfilter/ipv4/nf_defrag_ipv4.h>
/*
* Debug output levels
* 0 = OFF
* 1 = ASSERTS / ERRORS
* 2 = 1 + WARN
* 3 = 2 + INFO
* 4 = 3 + TRACE
*/
#define DEBUG_LEVEL ECM_DB_DEBUG_LEVEL
#include "ecm_types.h"
#include "ecm_db_types.h"
#include "ecm_state.h"
#include "ecm_tracker.h"
#include "ecm_classifier.h"
#include "ecm_front_end_types.h"
#include "ecm_classifier_default.h"
#include "ecm_db.h"
/*
* Global list.
* All instances are inserted into global list - this allows easy iteration of all instances of a particular type.
* The list is doubly linked for fast removal. The list is in no particular order.
*/
struct ecm_db_node_instance *ecm_db_nodes = NULL;
/*
* Node hash table
*/
#define ECM_DB_NODE_HASH_SLOTS 32768
static struct ecm_db_node_instance **ecm_db_node_table;
/* Slots of the node hash table */
static int *ecm_db_node_table_lengths;
/* Tracks how long each chain is */
static int ecm_db_node_count = 0; /* Number of nodes allocated */
/*
* Node flags
*/
#define ECM_DB_NODE_FLAGS_INSERTED 1 /* Node is inserted into connection database tables */
/*
* ecm_db_node_generate_hash_index()
* Calculate the hash index.
*/
static inline ecm_db_node_hash_t ecm_db_node_generate_hash_index(uint8_t *address)
{
uint32_t hash_val;
hash_val = (uint32_t)jhash(address, 6, ecm_db_jhash_rnd);
hash_val &= (ECM_DB_NODE_HASH_SLOTS - 1);
return (ecm_db_node_hash_t)hash_val;
}
/*
* _ecm_db_node_count_get()
* Return the node count (lockless).
*/
int _ecm_db_node_count_get(void)
{
return ecm_db_node_count;
}
/*
* _ecm_db_node_ref()
*/
void _ecm_db_node_ref(struct ecm_db_node_instance *ni)
{
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed\n", ni);
ni->refs++;
DEBUG_TRACE("%px: node ref %d\n", ni, ni->refs);
DEBUG_ASSERT(ni->refs > 0, "%px: ref wrap\n", ni);
}
/*
* ecm_db_node_ref()
*/
void ecm_db_node_ref(struct ecm_db_node_instance *ni)
{
spin_lock_bh(&ecm_db_lock);
_ecm_db_node_ref(ni);
spin_unlock_bh(&ecm_db_lock);
}
EXPORT_SYMBOL(ecm_db_node_ref);
#ifdef ECM_DB_ADVANCED_STATS_ENABLE
/*
* ecm_db_node_data_stats_get()
* Return data stats for the instance
*/
void ecm_db_node_data_stats_get(struct ecm_db_node_instance *ni, uint64_t *from_data_total, uint64_t *to_data_total,
uint64_t *from_packet_total, uint64_t *to_packet_total,
uint64_t *from_data_total_dropped, uint64_t *to_data_total_dropped,
uint64_t *from_packet_total_dropped, uint64_t *to_packet_total_dropped)
{
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed", ni);
spin_lock_bh(&ecm_db_lock);
if (from_data_total) {
*from_data_total = ni->from_data_total;
}
if (to_data_total) {
*to_data_total = ni->to_data_total;
}
if (from_packet_total) {
*from_packet_total = ni->from_packet_total;
}
if (to_packet_total) {
*to_packet_total = ni->to_packet_total;
}
if (from_data_total_dropped) {
*from_data_total_dropped = ni->from_data_total_dropped;
}
if (to_data_total_dropped) {
*to_data_total_dropped = ni->to_data_total_dropped;
}
if (from_packet_total_dropped) {
*from_packet_total_dropped = ni->from_packet_total_dropped;
}
if (to_packet_total_dropped) {
*to_packet_total_dropped = ni->to_packet_total_dropped;
}
spin_unlock_bh(&ecm_db_lock);
}
EXPORT_SYMBOL(ecm_db_node_data_stats_get);
#endif
/*
* ecm_db_node_adress_get()
* Return address
*/
void ecm_db_node_adress_get(struct ecm_db_node_instance *ni, uint8_t *address_buffer)
{
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed", ni);
memcpy(address_buffer, ni->address, ETH_ALEN);
}
EXPORT_SYMBOL(ecm_db_node_adress_get);
/*
* ecm_db_nodes_get_and_ref_first()
* Obtain a ref to the first node instance, if any
*/
struct ecm_db_node_instance *ecm_db_nodes_get_and_ref_first(void)
{
struct ecm_db_node_instance *ni;
spin_lock_bh(&ecm_db_lock);
ni = ecm_db_nodes;
if (ni) {
_ecm_db_node_ref(ni);
}
spin_unlock_bh(&ecm_db_lock);
return ni;
}
EXPORT_SYMBOL(ecm_db_nodes_get_and_ref_first);
/*
* ecm_db_node_get_and_ref_next()
* Return the next node in the list given a node
*/
struct ecm_db_node_instance *ecm_db_node_get_and_ref_next(struct ecm_db_node_instance *ni)
{
struct ecm_db_node_instance *nin;
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed", ni);
spin_lock_bh(&ecm_db_lock);
nin = ni->next;
if (nin) {
_ecm_db_node_ref(nin);
}
spin_unlock_bh(&ecm_db_lock);
return nin;
}
EXPORT_SYMBOL(ecm_db_node_get_and_ref_next);
/*
* ecm_db_node_deref()
* Deref a node. Removing it on the last ref and destroying it.
*/
int ecm_db_node_deref(struct ecm_db_node_instance *ni)
{
#if (DEBUG_LEVEL >= 1)
int dir;
#endif
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed\n", ni);
spin_lock_bh(&ecm_db_lock);
ni->refs--;
DEBUG_TRACE("%px: node deref %d\n", ni, ni->refs);
DEBUG_ASSERT(ni->refs >= 0, "%px: ref wrap\n", ni);
if (ni->refs > 0) {
int refs = ni->refs;
spin_unlock_bh(&ecm_db_lock);
return refs;
}
#ifdef ECM_DB_XREF_ENABLE
#if (DEBUG_LEVEL >= 1)
for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) {
DEBUG_ASSERT((ni->connections[dir] == NULL) && (ni->connections_count[dir] == 0), "%px: %s connections not null\n", ni, ecm_db_obj_dir_strings[dir]);
}
#endif
#endif
/*
* Remove from database if inserted
*/
if (!(ni->flags & ECM_DB_NODE_FLAGS_INSERTED)) {
spin_unlock_bh(&ecm_db_lock);
} else {
struct ecm_db_listener_instance *li;
/*
* Remove from the global list
*/
if (!ni->prev) {
DEBUG_ASSERT(ecm_db_nodes == ni, "%px: node table bad\n", ni);
ecm_db_nodes = ni->next;
} else {
ni->prev->next = ni->next;
}
if (ni->next) {
ni->next->prev = ni->prev;
}
ni->prev = NULL;
ni->next = NULL;
/*
* Link out of hash table
*/
if (!ni->hash_prev) {
DEBUG_ASSERT(ecm_db_node_table[ni->hash_index] == ni, "%px: hash table bad\n", ni);
ecm_db_node_table[ni->hash_index] = ni->hash_next;
} else {
ni->hash_prev->hash_next = ni->hash_next;
}
if (ni->hash_next) {
ni->hash_next->hash_prev = ni->hash_prev;
}
ni->hash_next = NULL;
ni->hash_prev = NULL;
ecm_db_node_table_lengths[ni->hash_index]--;
DEBUG_ASSERT(ecm_db_node_table_lengths[ni->hash_index] >= 0, "%px: invalid table len %d\n", ni, ecm_db_node_table_lengths[ni->hash_index]);
#ifdef ECM_DB_XREF_ENABLE
/*
* Unlink it from the iface node list
*/
if (!ni->node_prev) {
DEBUG_ASSERT(ni->iface->nodes == ni, "%px: nodes table bad\n", ni);
ni->iface->nodes = ni->node_next;
} else {
ni->node_prev->node_next = ni->node_next;
}
if (ni->node_next) {
ni->node_next->node_prev = ni->node_prev;
}
ni->node_next = NULL;
ni->node_prev = NULL;
ni->iface->node_count--;
#endif
spin_unlock_bh(&ecm_db_lock);
/*
* Throw removed event to listeners
*/
DEBUG_TRACE("%px: Throw node removed event\n", ni);
li = ecm_db_listeners_get_and_ref_first();
while (li) {
struct ecm_db_listener_instance *lin;
if (li->node_removed) {
li->node_removed(li->arg, ni);
}
/*
* Get next listener
*/
lin = ecm_db_listener_get_and_ref_next(li);
ecm_db_listener_deref(li);
li = lin;
}
}
/*
* Throw final event
*/
if (ni->final) {
ni->final(ni->arg);
}
/*
* Now release the iface instance if the node had one
*/
if (ni->iface) {
ecm_db_iface_deref(ni->iface);
}
/*
* We can now destroy the instance
*/
DEBUG_CLEAR_MAGIC(ni);
kfree(ni);
/*
* Decrease global node count
*/
spin_lock_bh(&ecm_db_lock);
ecm_db_node_count--;
DEBUG_ASSERT(ecm_db_node_count >= 0, "%px: node count wrap\n", ni);
spin_unlock_bh(&ecm_db_lock);
return 0;
}
EXPORT_SYMBOL(ecm_db_node_deref);
/*
* ecm_db_node_is_mac_addr_equal()
* Compares the node's mac address with the given mac address.
*/
bool ecm_db_node_is_mac_addr_equal(struct ecm_db_node_instance *ni, uint8_t *address)
{
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed", ni);
if (ecm_mac_addr_equal(ni->address, address)) {
return false;
}
return true;
}
EXPORT_SYMBOL(ecm_db_node_is_mac_addr_equal);
/*
* ecm_db_node_find_and_ref()
* Lookup and return a node reference if any
*/
struct ecm_db_node_instance *ecm_db_node_find_and_ref(uint8_t *address, struct ecm_db_iface_instance *ii)
{
ecm_db_node_hash_t hash_index;
struct ecm_db_node_instance *ni;
DEBUG_TRACE("Lookup node with addr %pMi and iface %px\n", address, ii);
/*
* Compute the hash chain index and prepare to walk the chain
*/
hash_index = ecm_db_node_generate_hash_index(address);
/*
* Iterate the chain looking for a host with matching details
*/
spin_lock_bh(&ecm_db_lock);
ni = ecm_db_node_table[hash_index];
while (ni) {
if (memcmp(ni->address, address, ETH_ALEN)) {
ni = ni->hash_next;
continue;
}
if (ni->iface != ii) {
ni = ni->hash_next;
continue;
}
_ecm_db_node_ref(ni);
spin_unlock_bh(&ecm_db_lock);
DEBUG_TRACE("node found %px\n", ni);
return ni;
}
spin_unlock_bh(&ecm_db_lock);
DEBUG_TRACE("Node not found\n");
return NULL;
}
EXPORT_SYMBOL(ecm_db_node_find_and_ref);
/*
* ecm_db_node_chain_get_and_ref_first()
* Gets and refs the first node in the chain of that mac address.
*/
struct ecm_db_node_instance *ecm_db_node_chain_get_and_ref_first(uint8_t *address)
{
ecm_db_node_hash_t hash_index;
struct ecm_db_node_instance *ni;
DEBUG_TRACE("Get the first node with addr %pMi in the chain\n", address);
/*
* Compute the hash chain index.
*/
hash_index = ecm_db_node_generate_hash_index(address);
spin_lock_bh(&ecm_db_lock);
ni = ecm_db_node_table[hash_index];
if (ni) {
_ecm_db_node_ref(ni);
}
spin_unlock_bh(&ecm_db_lock);
return ni;
}
EXPORT_SYMBOL(ecm_db_node_chain_get_and_ref_first);
/*
* ecm_db_node_chain_get_and_ref_next()
* Gets and refs the next node in the chain..
*/
struct ecm_db_node_instance *ecm_db_node_chain_get_and_ref_next(struct ecm_db_node_instance *ni)
{
struct ecm_db_node_instance *nin;
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed", ni);
spin_lock_bh(&ecm_db_lock);
nin = ni->hash_next;
if (nin) {
_ecm_db_node_ref(nin);
}
spin_unlock_bh(&ecm_db_lock);
return nin;
}
EXPORT_SYMBOL(ecm_db_node_chain_get_and_ref_next);
/*
* ecm_db_node_iface_get_and_ref()
*/
struct ecm_db_iface_instance *ecm_db_node_iface_get_and_ref(struct ecm_db_node_instance *ni)
{
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed\n", ni);
spin_lock_bh(&ecm_db_lock);
_ecm_db_iface_ref(ni->iface);
spin_unlock_bh(&ecm_db_lock);
return ni->iface;
}
EXPORT_SYMBOL(ecm_db_node_iface_get_and_ref);
/*
* ecm_db_node_add()
* Add a node instance into the database
*/
void ecm_db_node_add(struct ecm_db_node_instance *ni, struct ecm_db_iface_instance *ii, uint8_t *address,
ecm_db_node_final_callback_t final, void *arg)
{
#if (DEBUG_LEVEL >= 1)
int dir;
#endif
ecm_db_node_hash_t hash_index;
struct ecm_db_listener_instance *li;
spin_lock_bh(&ecm_db_lock);
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed\n", ni);
DEBUG_CHECK_MAGIC(ii, ECM_DB_IFACE_INSTANCE_MAGIC, "%px: magic failed\n", ii);
DEBUG_ASSERT(address, "%px: address null\n", ni);
DEBUG_ASSERT((ni->iface == NULL), "%px: iface not null\n", ni);
DEBUG_ASSERT(!(ni->flags & ECM_DB_NODE_FLAGS_INSERTED), "%px: inserted\n", ni);
#ifdef ECM_DB_XREF_ENABLE
#if (DEBUG_LEVEL >= 1)
for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) {
DEBUG_ASSERT((ni->connections[dir] == NULL) && (ni->connections_count[dir] == 0), "%px: %s connections not null\n", ni, ecm_db_obj_dir_strings[dir]);
}
#endif
#endif
spin_unlock_bh(&ecm_db_lock);
memcpy(ni->address, address, ETH_ALEN);
ni->arg = arg;
ni->final = final;
/*
* Compute hash chain for insertion
*/
hash_index = ecm_db_node_generate_hash_index(address);
ni->hash_index = hash_index;
/*
* Node takes a ref to the iface
*/
ecm_db_iface_ref(ii);
ni->iface = ii;
/*
* Add into the global list
*/
spin_lock_bh(&ecm_db_lock);
ni->flags |= ECM_DB_NODE_FLAGS_INSERTED;
ni->prev = NULL;
ni->next = ecm_db_nodes;
if (ecm_db_nodes) {
ecm_db_nodes->prev = ni;
}
ecm_db_nodes = ni;
/*
* Insert into the hash chain
*/
ni->hash_prev = NULL;
ni->hash_next = ecm_db_node_table[hash_index];
if (ecm_db_node_table[hash_index]) {
ecm_db_node_table[hash_index]->hash_prev = ni;
}
ecm_db_node_table[hash_index] = ni;
ecm_db_node_table_lengths[hash_index]++;
DEBUG_ASSERT(ecm_db_node_table_lengths[hash_index] > 0, "%px: invalid table len %d\n", ni, ecm_db_node_table_lengths[hash_index]);
/*
* Set time of add
*/
ni->time_added = ecm_db_time;
#ifdef ECM_DB_XREF_ENABLE
/*
* Insert node into the iface nodes list
*/
ni->node_prev = NULL;
ni->node_next = ii->nodes;
if (ii->nodes) {
ii->nodes->node_prev = ni;
}
ii->nodes = ni;
ii->node_count++;
#endif
spin_unlock_bh(&ecm_db_lock);
/*
* Throw add event to the listeners
*/
DEBUG_TRACE("%px: Throw node added event\n", ni);
li = ecm_db_listeners_get_and_ref_first();
while (li) {
struct ecm_db_listener_instance *lin;
if (li->node_added) {
li->node_added(li->arg, ni);
}
/*
* Get next listener
*/
lin = ecm_db_listener_get_and_ref_next(li);
ecm_db_listener_deref(li);
li = lin;
}
}
EXPORT_SYMBOL(ecm_db_node_add);
/*
* ecm_db_node_state_get()
* Prepare a node message
*/
int ecm_db_node_state_get(struct ecm_state_file_instance *sfi, struct ecm_db_node_instance *ni)
{
int result;
char address[ECM_MAC_ADDR_STR_BUFF_SIZE];
#ifdef ECM_DB_XREF_ENABLE
int dir;
int connections_count[ECM_DB_OBJ_DIR_MAX];
#endif
uint32_t time_added;
#ifdef ECM_DB_ADVANCED_STATS_ENABLE
uint64_t from_data_total;
uint64_t to_data_total;
uint64_t from_packet_total;
uint64_t to_packet_total;
uint64_t from_data_total_dropped;
uint64_t to_data_total_dropped;
uint64_t from_packet_total_dropped;
uint64_t to_packet_total_dropped;
#endif
DEBUG_TRACE("Prep node msg for %px\n", ni);
/*
* Create a small xml stats block for our managed node, like:
* <node address="" hosts="" time_added="" from_data_total="" to_data_total="" />
*
* Extract information from the node for inclusion into the message
*/
#ifdef ECM_DB_XREF_ENABLE
spin_lock_bh(&ecm_db_lock);
for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) {
connections_count[dir] = ni->connections_count[dir];
}
spin_unlock_bh(&ecm_db_lock);
#endif
time_added = ni->time_added;
snprintf(address, sizeof(address), "%pM", ni->address);
#ifdef ECM_DB_ADVANCED_STATS_ENABLE
ecm_db_node_data_stats_get(ni, &from_data_total, &to_data_total,
&from_packet_total, &to_packet_total,
&from_data_total_dropped, &to_data_total_dropped,
&from_packet_total_dropped, &to_packet_total_dropped);
#endif
if ((result = ecm_state_prefix_add(sfi, "node"))) {
return result;
}
if ((result = ecm_state_write(sfi, "address", "%s", address))) {
return result;
}
if ((result = ecm_state_write(sfi, "time_added", "%u", time_added))) {
return result;
}
#ifdef ECM_DB_XREF_ENABLE
for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) {
/*
* ECM_DB_NODE_CONN_COUNT_STR_SIZE is the size of "FROM_NAT_connections_count"
* string which can be the max length of these strings
*/
char name[ECM_DB_NODE_CONN_COUNT_STR_SIZE];
snprintf(name, ECM_DB_NODE_CONN_COUNT_STR_SIZE, "%s_connections_count", ecm_db_obj_dir_strings[dir]);
if ((result = ecm_state_write(sfi, name, "%d", connections_count[dir]))) {
return result;
}
}
#endif
#ifdef ECM_DB_ADVANCED_STATS_ENABLE
if ((result = ecm_db_adv_stats_state_write(sfi, from_data_total, to_data_total,
from_packet_total, to_packet_total, from_data_total_dropped,
to_data_total_dropped, from_packet_total_dropped,
to_packet_total_dropped))) {
return result;
}
#endif
return ecm_state_prefix_remove(sfi);
}
EXPORT_SYMBOL(ecm_db_node_state_get);
/*
* ecm_db_node_hash_table_lengths_get()
* Return hash table length
*/
int ecm_db_node_hash_table_lengths_get(int index)
{
int length;
DEBUG_ASSERT((index >= 0) && (index < ECM_DB_NODE_HASH_SLOTS), "Bad protocol: %d\n", index);
spin_lock_bh(&ecm_db_lock);
length = ecm_db_node_table_lengths[index];
spin_unlock_bh(&ecm_db_lock);
return length;
}
EXPORT_SYMBOL(ecm_db_node_hash_table_lengths_get);
/*
* ecm_db_node_hash_index_get_next()
* Given a hash index, return the next one OR return -1 for no more hash indicies to return.
*/
int ecm_db_node_hash_index_get_next(int index)
{
index++;
if (index >= ECM_DB_NODE_HASH_SLOTS) {
return -1;
}
return index;
}
EXPORT_SYMBOL(ecm_db_node_hash_index_get_next);
/*
* ecm_db_node_hash_index_get_first()
* Return first hash index
*/
int ecm_db_node_hash_index_get_first(void)
{
return 0;
}
EXPORT_SYMBOL(ecm_db_node_hash_index_get_first);
/*
* ecm_db_node_get_connections_count()
* Returns the connections count on the node in the given direction.
*/
int ecm_db_node_get_connections_count(struct ecm_db_node_instance *ni, ecm_db_obj_dir_t dir)
{
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed\n", ni);
return ni->connections_count[dir];
}
/*
* ecm_db_node_alloc()
* Allocate a node instance
*/
struct ecm_db_node_instance *ecm_db_node_alloc(void)
{
struct ecm_db_node_instance *ni;
ni = (struct ecm_db_node_instance *)kzalloc(sizeof(struct ecm_db_node_instance), GFP_ATOMIC | __GFP_NOWARN);
if (!ni) {
DEBUG_WARN("Alloc failed\n");
return NULL;
}
ni->refs = 1;
DEBUG_SET_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC);
/*
* Alloc operation must be atomic to ensure thread and module can be held
*/
spin_lock_bh(&ecm_db_lock);
/*
* If the event processing thread is terminating then we cannot create new instances
*/
if (ecm_db_terminate_pending) {
spin_unlock_bh(&ecm_db_lock);
DEBUG_WARN("Thread terminating\n");
kfree(ni);
return NULL;
}
ecm_db_node_count++;
spin_unlock_bh(&ecm_db_lock);
DEBUG_TRACE("Node created %px\n", ni);
return ni;
}
EXPORT_SYMBOL(ecm_db_node_alloc);
#ifdef ECM_DB_XREF_ENABLE
/*
* ecm_db_node_connections_get_and_ref_first()
* Obtain a ref to the first connection instance of node on this direction, if any
*/
static inline struct ecm_db_connection_instance *
ecm_db_node_connections_get_and_ref_first(struct ecm_db_node_instance *node,
ecm_db_obj_dir_t dir)
{
struct ecm_db_connection_instance *ci;
DEBUG_CHECK_MAGIC(node, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed", node);
spin_lock_bh(&ecm_db_lock);
ci = node->connections[dir];
if (ci) {
_ecm_db_connection_ref(ci);
}
spin_unlock_bh(&ecm_db_lock);
return ci;
}
/*
* ecm_db_node_connection_get_and_ref_next()
* Return the next connection in the specified direction of given a connection
*/
static inline struct ecm_db_connection_instance *
ecm_db_node_connection_get_and_ref_next(struct ecm_db_connection_instance *ci,
ecm_db_obj_dir_t dir)
{
struct ecm_db_connection_instance *cin;
DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci);
spin_lock_bh(&ecm_db_lock);
cin = ci->node_next[dir];
if (cin) {
_ecm_db_connection_ref(cin);
}
spin_unlock_bh(&ecm_db_lock);
return cin;
}
/*
* ecm_db_should_keep_connection()
* check if any classifier believes this connection should
* be kept
*/
static bool ecm_db_should_keep_connection(
struct ecm_db_connection_instance *ci, uint8_t *mac)
{
bool should_keep_connection = false;
int assignment_count;
int aci_index;
struct ecm_classifier_instance *assignments[ECM_CLASSIFIER_TYPES];
assignment_count =
ecm_db_connection_classifier_assignments_get_and_ref(ci, assignments);
for (aci_index = 0; aci_index < assignment_count; ++aci_index) {
struct ecm_classifier_instance *aci;
aci = assignments[aci_index];
if (aci->should_keep_connection &&
aci->should_keep_connection(aci, mac)) {
should_keep_connection = true;
break;
}
}
ecm_db_connection_assignments_release(assignment_count, assignments);
return should_keep_connection;
}
/*
* ecm_db_traverse_node_connection_list_and_defunct()
* traverse a node in the specified direction and calls ecm_db_connection_make_defunct()
* for each entry. If ip_version is valid (non-zero), then defunct the
* connections matching version.
*/
void ecm_db_traverse_node_connection_list_and_defunct(
struct ecm_db_node_instance *node, ecm_db_obj_dir_t dir, int ip_version)
{
struct ecm_db_connection_instance *ci = NULL;
/*
* Iterate all from connections
*/
ci = ecm_db_node_connections_get_and_ref_first(node, dir);
while (ci) {
struct ecm_db_connection_instance *cin;
if (!ecm_db_should_keep_connection(ci, node->address)) {
if (ip_version != ECM_DB_IP_VERSION_IGNORE && (ecm_db_connection_ip_version_get(ci) != ip_version)) {
DEBUG_TRACE("%px: keeping connection, ip_version mismatch %d\n", ci, ci->serial);
goto keep_node_conn;
}
DEBUG_TRACE("%px: defunct %d\n", ci, ci->serial);
ecm_db_connection_make_defunct(ci);
} else {
DEBUG_TRACE("%px: keeping connection %d\n", ci, ci->serial);
}
keep_node_conn:
cin = ecm_db_node_connection_get_and_ref_next(ci, dir);
ecm_db_connection_deref(ci);
ci = cin;
}
DEBUG_INFO("%px: Defuncting from node connection list complete\n", node);
}
#ifdef ECM_INTERFACE_OVS_BRIDGE_ENABLE
/*
* ecm_db_node_ovs_connections_masked_defunct()
* Destroy connections created on the node
*/
void ecm_db_node_ovs_connections_masked_defunct(int ip_ver, uint8_t *src_mac, bool src_mac_check, ip_addr_t src_addr_mask,
uint16_t src_port_mask, uint8_t *dest_mac, bool dest_mac_check,
ip_addr_t dest_addr_mask, uint16_t dest_port_mask,
int proto_mask, ecm_db_obj_dir_t dir, bool is_routed)
{
struct ecm_db_node_instance *ni;
uint8_t smac[ETH_ALEN], dmac[ETH_ALEN];
uint8_t *mac;
ip_addr_t sip, dip;
uint16_t sport, dport;
int proto;
int cnt = 0;
char *direction = NULL;
mac = (dir == ECM_DB_OBJ_DIR_FROM) ? src_mac : dest_mac;
ni = ecm_db_node_chain_get_and_ref_first(mac);
if (!ni) {
DEBUG_WARN("Unable to find first instance node\n");
return;
}
/*
* Iterate through all node instances
*/
while (ni) {
struct ecm_db_connection_instance *ci;
struct ecm_db_node_instance *nni;
DEBUG_CHECK_MAGIC(ni, ECM_DB_NODE_INSTANCE_MAGIC, "%px: magic failed", ni);
if (!ecm_db_node_is_mac_addr_equal(ni, mac)) {
nni = ecm_db_node_chain_get_and_ref_next(ni);
ecm_db_node_deref(ni);
ni = nni;
continue;
}
ci = ecm_db_node_connections_get_and_ref_first(ni, dir);
while (ci) {
struct ecm_db_connection_instance *cin;
DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci);
/*
* Skip routed CI for brided flows
* Skip bridged CI for routed flows
*/
if (is_routed != ecm_db_connection_is_routed_get(ci)) {
goto next_ci;
}
/*
* Check IP version
*/
if (ip_ver != ECM_DB_IP_VERSION_IGNORE && (ecm_db_connection_ip_version_get(ci) != ip_ver)) {
goto next_ci;
}
/*
* Check protocol if specified
*/
proto = ecm_db_connection_protocol_get(ci);
if (!ECM_PROTO_MASK_MATCH(proto, proto_mask)) {
goto next_ci;
}
/*
* A : PCI < ------- br-home ----------bridging----------------br-wan ---->PC2
*
* B : PCI < ------- br-home ----------Routing----------------br-wan ---->PC2
*
* DNAT
* C : PCI < ------- br-home ----------Routing----------------br-wan ----> PC2
* SNAT
* D : PCI < ------- br-home ----------Routing----------------br-wan ----> PC2
*
*/
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, dmac);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, sip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dip);
sport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM);
dport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO);
/*
* 1. For topology A, B, C, D if drop rule is added in br-home or br-wan
* 2. For topoloy A, B if drop rule is added in br-wan
* Match in flow direction
*/
if ((!src_mac_check || ECM_MAC_ADDR_MATCH(smac, src_mac)) &&
(!dest_mac_check || ECM_MAC_ADDR_MATCH(dmac, dest_mac)) &&
ECM_IP_ADDR_MASK_MATCH(sip, src_addr_mask) &&
ECM_IP_ADDR_MASK_MATCH(dip, dest_addr_mask) &&
ECM_PORT_MASK_MATCH(sport, src_port_mask) &&
ECM_PORT_MASK_MATCH(dport, dest_port_mask)) {
direction = "flow";
goto defunct_conn;
}
/*
* 1. For topology A, B, C, D if drop rule is added in br-home or br-wan
* 2. For topoloy A, B if drop rule is added in br-wan
* Match in reverse direction
*/
if ((!src_mac_check || ECM_MAC_ADDR_MATCH(dmac, src_mac)) &&
(!dest_mac_check || ECM_MAC_ADDR_MATCH(smac, dest_mac)) &&
ECM_IP_ADDR_MASK_MATCH(dip, src_addr_mask) &&
ECM_IP_ADDR_MASK_MATCH(sip, dest_addr_mask) &&
ECM_PORT_MASK_MATCH(dport, src_port_mask) &&
ECM_PORT_MASK_MATCH(sport, dest_port_mask)) {
direction = "reverse";
goto defunct_conn;
}
/*
* There is no NATing in case of bridging
*/
if (!is_routed) {
goto next_ci;
}
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM_NAT, smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO_NAT, dmac);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM_NAT, sip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO_NAT, dip);
sport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM_NAT);
dport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO_NAT);
/*
* 1. For topoloy C, D if drop rule is added in br-wan
* Match in flow direction
*/
if ((!src_mac_check || ECM_MAC_ADDR_MATCH(smac, src_mac)) &&
(!dest_mac_check || ECM_MAC_ADDR_MATCH(dmac, dest_mac)) &&
ECM_IP_ADDR_MASK_MATCH(sip, src_addr_mask) &&
ECM_IP_ADDR_MASK_MATCH(dip, dest_addr_mask) &&
ECM_PORT_MASK_MATCH(sport, src_port_mask) &&
ECM_PORT_MASK_MATCH(dport, dest_port_mask)) {
direction = "flow (nat)";
goto defunct_conn;
}
/*
* 1. For topoloy C, D if drop rule is added in br-wan
* Match in reverse direction
*/
if ((!src_mac_check || ECM_MAC_ADDR_MATCH(dmac, src_mac)) &&
(!dest_mac_check || ECM_MAC_ADDR_MATCH(smac, dest_mac)) &&
ECM_IP_ADDR_MASK_MATCH(dip, src_addr_mask) &&
ECM_IP_ADDR_MASK_MATCH(sip, dest_addr_mask) &&
ECM_PORT_MASK_MATCH(dport, src_port_mask) &&
ECM_PORT_MASK_MATCH(sport, dest_port_mask)) {
direction = "reverse (nat)";
goto defunct_conn;
}
goto next_ci;
defunct_conn:
cnt++;
DEBUG_TRACE("%px: Defuncting 7 tuple %s connection\n", ci, is_routed ? "routed" : "bridged");
if (ECM_IP_ADDR_IS_V4(src_addr_mask)) {
DEBUG_TRACE("%px: Defunct CI masked 7 tuple match(%s) smac=%pM(%d) src=" ECM_IP_ADDR_DOT_FMT " sport=%d "
"dmac=%pM(%d) dest=" ECM_IP_ADDR_DOT_FMT ", dport=%d, proto=%d cnt=%d\n", ci, direction, smac,
src_mac_check, ECM_IP_ADDR_TO_DOT(sip), sport, dmac, dest_mac_check,
ECM_IP_ADDR_TO_DOT(dip), dport, proto, cnt);
} else {
DEBUG_TRACE("%px: Defunct CI masked 7 tuple match(%s) src=%pM(%d)" ECM_IP_ADDR_OCTAL_FMT " sport=%d "
"dmac=%pM(%d) dest=" ECM_IP_ADDR_OCTAL_FMT ", dport=%d, proto=%d, cnt=%d\n", ci, direction,
smac, src_mac_check, ECM_IP_ADDR_TO_OCTAL(sip), sport, dmac, dest_mac_check,
ECM_IP_ADDR_TO_OCTAL(dip), dport, proto, cnt);
}
ecm_db_connection_make_defunct(ci);
next_ci:
cin = ecm_db_node_connection_get_and_ref_next(ci, dir);
ecm_db_connection_deref(ci);
ci = cin;
}
ecm_db_node_deref(ni);
break;
}
DEBUG_TRACE("Completed OVS 7 tuple %s connections (cnt=%d) masked defunct\n", is_routed ? "routed" : "bridged", cnt);
if (ECM_IP_ADDR_IS_V4(src_addr_mask)) {
DEBUG_TRACE("Defunct request by masked 7 tuple smac_mask=%pM(%d) src_mask=" ECM_IP_ADDR_DOT_FMT " sport_mask=%d, "
"dmac_mask=%pM(%d) dest_mask=" ECM_IP_ADDR_DOT_FMT " dport_mask=%d, proto_mask=%d\n", src_mac,
src_mac_check, ECM_IP_ADDR_TO_DOT(src_addr_mask), src_port_mask, dest_mac, dest_mac_check,
ECM_IP_ADDR_TO_DOT(dest_addr_mask), dest_port_mask, proto_mask);
} else {
DEBUG_TRACE("Defunct request by masked 7 tuple smac_mask=%pM(%d) src_mask=" ECM_IP_ADDR_OCTAL_FMT " sport_mask=%d "
"dmac_mask=%pM(%d) dest_mask=" ECM_IP_ADDR_OCTAL_FMT " dport_mask=%d, proto_mask=%d cnt=%d\n",
src_mac, src_mac_check, ECM_IP_ADDR_TO_OCTAL(src_addr_mask), src_port_mask, dest_mac, dest_mac_check,
ECM_IP_ADDR_TO_OCTAL(dest_addr_mask), dest_port_mask, proto_mask, cnt);
}
}
/*
* ecm_db_node_ovs_routed_connections_defunct()
* Destroy the routed connections created on the node in the given
* direction which is related to the ovs_br interface.
*/
void ecm_db_node_ovs_routed_connections_defunct(uint8_t *node_mac, struct net_device *ovs_br, int ip_version, ecm_db_obj_dir_t dir)
{
struct ecm_db_iface_instance *ii;
struct ecm_db_node_instance *ni;
struct ecm_db_connection_instance *ci;
ii = ecm_db_iface_find_and_ref_by_interface_identifier(ovs_br->ifindex);
if (!ii) {
DEBUG_WARN("%px: Unable to find OVS bridge iface instance\n", ovs_br);
return;
}
/*
* Find the node instance which has the node_mac and related to the ovs_br.
* Nodes are stored in the database with their related interface instances.
*/
ni = ecm_db_node_find_and_ref(node_mac, ii);
if(!ni) {
DEBUG_WARN("%px: Unable to find node instance related to %pM and %s\n", ovs_br, node_mac, ovs_br->name);
ecm_db_iface_deref(ii);
return;
}
/*
* Iterate all routed connections on this node in the dir direction.
*/
ci = ecm_db_node_connections_get_and_ref_first(ni, dir);
while (ci) {
struct ecm_db_connection_instance *cin;
if (ecm_db_connection_is_routed_get(ci) && (ecm_db_connection_ip_version_get(ci) == ip_version)) {
DEBUG_TRACE("%px: Defuncting connection %p\n", ovs_br, ci);
ecm_db_connection_make_defunct(ci);
}
cin = ecm_db_node_connection_get_and_ref_next(ci, dir);
ecm_db_connection_deref(ci);
ci = cin;
}
ecm_db_node_deref(ni);
ecm_db_iface_deref(ii);
DEBUG_TRACE("%px: Completed OVS routed connection defunct\n", ovs_br);
}
/*
* ecm_db_traverse_snode_dnode_connection_list_and_defunct()
* Defunct connections between node1 (sni) and node2 (which has dmac address)
*/
void ecm_db_traverse_snode_dnode_connection_list_and_defunct(
struct ecm_db_node_instance *sni, uint8_t *dmac, int ip_version, ecm_db_obj_dir_t dir)
{
struct ecm_db_connection_instance *ci = NULL;
if (dir != ECM_DB_OBJ_DIR_FROM && dir != ECM_DB_OBJ_DIR_TO) {
DEBUG_WARN("Direction is incorrect: %d\n", dir);
return;
}
/*
* Iterate all connection instances which are sent or received from
* given node instannce (sni).
*/
ci = ecm_db_node_connections_get_and_ref_first(sni, dir);
while (ci) {
struct ecm_db_connection_instance *cin;
struct ecm_db_node_instance *dni;
/*
* Find the connection instance which match given MAC address (dmac)
*/
if (dir == ECM_DB_OBJ_DIR_FROM) {
/*
* Direction is FROM, then sni is source node.
* find the node in TO direction.
*/
dni = ci->node[ECM_DB_OBJ_DIR_TO];
} else {
/*
* Direction is TO, then sni is destination node.
* find the node in FROM direction.
*/
dni = ci->node[ECM_DB_OBJ_DIR_FROM];
}
/*
* if the MAC address of node (dni) match MAC address (dmac)
* then delete the connection instance.
*/
if (ecm_db_node_is_mac_addr_equal(dni, dmac)) {
if (ip_version != ECM_DB_IP_VERSION_IGNORE && (ecm_db_connection_ip_version_get(ci) != ip_version)) {
DEBUG_TRACE("%px: keeping connection, ip_version mismatch %d\n", ci, ci->serial);
goto keep_sni_conn;
}
DEBUG_TRACE("%px: defunct %d\n", ci, ci->serial);
ecm_db_connection_make_defunct(ci);
}
keep_sni_conn:
cin = ecm_db_node_connection_get_and_ref_next(ci, dir);
ecm_db_connection_deref(ci);
ci = cin;
}
DEBUG_INFO("%px: Defuncting from node connection list complete\n", sni);
}
#endif
#endif
/*
* ecm_db_node_init()
*/
bool ecm_db_node_init(struct dentry *dentry)
{
if (!debugfs_create_u32("node_count", S_IRUGO, dentry,
(u32 *)&ecm_db_node_count)) {
DEBUG_ERROR("Failed to create ecm db node count file in debugfs\n");
return false;
}
ecm_db_node_table = vzalloc(sizeof(struct ecm_db_node_instance *) * ECM_DB_NODE_HASH_SLOTS);
if (!ecm_db_node_table) {
DEBUG_ERROR("Failed to allocate virtual memory for ecm_db_node_table\n");
return false;
}
ecm_db_node_table_lengths = vzalloc(sizeof(int) * ECM_DB_NODE_HASH_SLOTS);
if (!ecm_db_node_table_lengths) {
DEBUG_ERROR("Failed to allocate virtual memory for ecm_db_node_table_lengths\n");
vfree(ecm_db_node_table);
return false;
}
return true;
}
/*
* ecm_db_node_exit()
*/
void ecm_db_node_exit(void)
{
vfree(ecm_db_node_table_lengths);
vfree(ecm_db_node_table);
}