| /* |
| ************************************************************************** |
| * 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); |
| } |