blob: 329c7cf8aef09404bc0db7a3b0e98b6c5c9311de [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2014-2018, 2020-2021, The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
#include <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_mapping_instance *ecm_db_mappings = NULL;
/*
* Mapping hash table
*/
#define ECM_DB_MAPPING_HASH_SLOTS 32768
static struct ecm_db_mapping_instance **ecm_db_mapping_table;
/* Slots of the mapping hash table */
static int *ecm_db_mapping_table_lengths;
/* Tracks how long each chain is */
static int ecm_db_mapping_count = 0; /* Number of mappings allocated */
/*
* Mapping flags
*/
#define ECM_DB_MAPPING_FLAGS_INSERTED 1 /* Mapping is inserted into connection database tables */
/*
* ecm_db_mapping_generate_hash_index()
* Calculate the hash index.
*/
static inline ecm_db_mapping_hash_t ecm_db_mapping_generate_hash_index(ip_addr_t address, uint32_t port)
{
uint32_t tuple;
uint32_t hash_val;
ECM_IP_ADDR_HASH(tuple, address);
hash_val = (uint32_t)jhash_2words(tuple, port, ecm_db_jhash_rnd);
return (ecm_db_mapping_hash_t)(hash_val & (ECM_DB_MAPPING_HASH_SLOTS - 1));
}
/*
* _ecm_db_mapping_count_get()
* Return the mapping count (lockless).
*/
int _ecm_db_mapping_count_get(void)
{
return ecm_db_mapping_count;
}
/*
* _ecm_db_mapping_ref()
*/
void _ecm_db_mapping_ref(struct ecm_db_mapping_instance *mi)
{
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed\n", mi);
mi->refs++;
DEBUG_TRACE("%px: mapping ref %d\n", mi, mi->refs);
DEBUG_ASSERT(mi->refs > 0, "%px: ref wrap\n", mi);
}
/*
* ecm_db_mapping_ref()
*/
void ecm_db_mapping_ref(struct ecm_db_mapping_instance *mi)
{
spin_lock_bh(&ecm_db_lock);
_ecm_db_mapping_ref(mi);
spin_unlock_bh(&ecm_db_lock);
}
EXPORT_SYMBOL(ecm_db_mapping_ref);
#ifdef ECM_DB_ADVANCED_STATS_ENABLE
/*
* ecm_db_mapping_data_stats_get()
* Return data stats for the instance
*/
void ecm_db_mapping_data_stats_get(struct ecm_db_mapping_instance *mi, 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(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed", mi);
spin_lock_bh(&ecm_db_lock);
if (from_data_total) {
*from_data_total = mi->from_data_total;
}
if (to_data_total) {
*to_data_total = mi->to_data_total;
}
if (from_packet_total) {
*from_packet_total = mi->from_packet_total;
}
if (to_packet_total) {
*to_packet_total = mi->to_packet_total;
}
if (from_data_total_dropped) {
*from_data_total_dropped = mi->from_data_total_dropped;
}
if (to_data_total_dropped) {
*to_data_total_dropped = mi->to_data_total_dropped;
}
if (from_packet_total_dropped) {
*from_packet_total_dropped = mi->from_packet_total_dropped;
}
if (to_packet_total_dropped) {
*to_packet_total_dropped = mi->to_packet_total_dropped;
}
spin_unlock_bh(&ecm_db_lock);
}
EXPORT_SYMBOL(ecm_db_mapping_data_stats_get);
#endif
/*
* ecm_db_mapping_state_get()
* Prepare a mapping message
*/
int ecm_db_mapping_state_get(struct ecm_state_file_instance *sfi, struct ecm_db_mapping_instance *mi)
{
int result;
int port;
char address[ECM_IP_ADDR_STR_BUFF_SIZE];
int tcp_count[ECM_DB_OBJ_DIR_MAX];
int udp_count[ECM_DB_OBJ_DIR_MAX];
int conn_count[ECM_DB_OBJ_DIR_MAX];
uint32_t time_added;
struct ecm_db_host_instance *hi;
#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_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed", mi);
DEBUG_TRACE("Prep mapping msg for %px\n", mi);
/*
* Create a small xml stats element for our mapping.
* Extract information from the mapping for inclusion into the message
*/
spin_lock_bh(&ecm_db_lock);
memcpy(tcp_count, mi->tcp_count, sizeof(tcp_count));
memcpy(udp_count, mi->udp_count, sizeof(udp_count));
memcpy(conn_count, mi->conn_count, sizeof(conn_count));
spin_unlock_bh(&ecm_db_lock);
port = mi->port;
time_added = mi->time_added;
hi = mi->host;
ecm_ip_addr_to_string(address, hi->address);
#ifdef ECM_DB_ADVANCED_STATS_ENABLE
ecm_db_mapping_data_stats_get(mi, &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, "mapping"))) {
return result;
}
if ((result = ecm_state_write(sfi, "port", "%d", port))) {
return result;
}
if ((result = ecm_state_write(sfi, "from", "%d", conn_count[ECM_DB_OBJ_DIR_FROM]))) {
return result;
}
if ((result = ecm_state_write(sfi, "to", "%d", conn_count[ECM_DB_OBJ_DIR_TO]))) {
return result;
}
if ((result = ecm_state_write(sfi, "tcp_from", "%d", tcp_count[ECM_DB_OBJ_DIR_FROM]))) {
return result;
}
if ((result = ecm_state_write(sfi, "tcp_to", "%d", tcp_count[ECM_DB_OBJ_DIR_TO]))) {
return result;
}
if ((result = ecm_state_write(sfi, "udp_from", "%d", udp_count[ECM_DB_OBJ_DIR_FROM]))) {
return result;
}
if ((result = ecm_state_write(sfi, "udp_to", "%d", udp_count[ECM_DB_OBJ_DIR_TO]))) {
return result;
}
if ((result = ecm_state_write(sfi, "nat_from", "%d", conn_count[ECM_DB_OBJ_DIR_FROM_NAT]))) {
return result;
}
if ((result = ecm_state_write(sfi, "nat_to", "%d", conn_count[ECM_DB_OBJ_DIR_TO_NAT]))) {
return result;
}
if ((result = ecm_state_write(sfi, "tcp_nat_from", "%d", tcp_count[ECM_DB_OBJ_DIR_FROM_NAT]))) {
return result;
}
if ((result = ecm_state_write(sfi, "tcp_nat_to", "%d", tcp_count[ECM_DB_OBJ_DIR_TO_NAT]))) {
return result;
}
if ((result = ecm_state_write(sfi, "udp_nat_from", "%d", udp_count[ECM_DB_OBJ_DIR_FROM_NAT]))) {
return result;
}
if ((result = ecm_state_write(sfi, "udp_nat_to", "%d", udp_count[ECM_DB_OBJ_DIR_TO_NAT]))) {
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_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_mapping_state_get);
/*
* ecm_db_mapping_adress_get()
* Return address
*/
void ecm_db_mapping_adress_get(struct ecm_db_mapping_instance *mi, ip_addr_t addr)
{
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed", mi);
ECM_IP_ADDR_COPY(addr, mi->host->address);
}
EXPORT_SYMBOL(ecm_db_mapping_adress_get);
/*
* ecm_db_mapping_port_get()
* Return port
*/
int ecm_db_mapping_port_get(struct ecm_db_mapping_instance *mi)
{
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed", mi);
return mi->port;
}
EXPORT_SYMBOL(ecm_db_mapping_port_get);
/*
* ecm_db_mappings_get_and_ref_first()
* Obtain a ref to the first mapping instance, if any
*/
struct ecm_db_mapping_instance *ecm_db_mappings_get_and_ref_first(void)
{
struct ecm_db_mapping_instance *mi;
spin_lock_bh(&ecm_db_lock);
mi = ecm_db_mappings;
if (mi) {
_ecm_db_mapping_ref(mi);
}
spin_unlock_bh(&ecm_db_lock);
return mi;
}
EXPORT_SYMBOL(ecm_db_mappings_get_and_ref_first);
/*
* ecm_db_mapping_get_and_ref_next()
* Return the next mapping in the list given a mapping
*/
struct ecm_db_mapping_instance *ecm_db_mapping_get_and_ref_next(struct ecm_db_mapping_instance *mi)
{
struct ecm_db_mapping_instance *min;
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed", mi);
spin_lock_bh(&ecm_db_lock);
min = mi->next;
if (min) {
_ecm_db_mapping_ref(min);
}
spin_unlock_bh(&ecm_db_lock);
return min;
}
EXPORT_SYMBOL(ecm_db_mapping_get_and_ref_next);
/*
* ecm_db_mapping_deref()
* Release ref to mapping, possibly removing it from the database and destroying it.
*/
int ecm_db_mapping_deref(struct ecm_db_mapping_instance *mi)
{
#if (DEBUG_LEVEL >= 1)
int dir;
#endif
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed\n", mi);
spin_lock_bh(&ecm_db_lock);
mi->refs--;
DEBUG_TRACE("%px: mapping deref %d\n", mi, mi->refs);
DEBUG_ASSERT(mi->refs >= 0, "%px: ref wrap\n", mi);
if (mi->refs > 0) {
int refs = mi->refs;
spin_unlock_bh(&ecm_db_lock);
return refs;
}
#if (DEBUG_LEVEL >= 1)
for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) {
DEBUG_ASSERT(!mi->tcp_count[dir] && !mi->udp_count[dir] && !mi->conn_count[dir], "%px: %s not zero: %d, %d, %d\n",
mi, ecm_db_obj_dir_strings[dir], mi->tcp_count[dir], mi->udp_count[dir], mi->conn_count[dir]);
#ifdef ECM_DB_XREF_ENABLE
DEBUG_ASSERT(!mi->connections[dir], "%px: %s not null: %px\n", mi, ecm_db_obj_dir_strings[dir], mi->connections[dir]);
#endif
}
#endif
/*
* Remove from database if inserted
*/
if (!(mi->flags & ECM_DB_MAPPING_FLAGS_INSERTED)) {
spin_unlock_bh(&ecm_db_lock);
} else {
struct ecm_db_listener_instance *li;
/*
* Remove from the global list
*/
if (!mi->prev) {
DEBUG_ASSERT(ecm_db_mappings == mi, "%px: mapping table bad\n", mi);
ecm_db_mappings = mi->next;
} else {
mi->prev->next = mi->next;
}
if (mi->next) {
mi->next->prev = mi->prev;
}
mi->prev = NULL;
mi->next = NULL;
/*
* Unlink it from the mapping hash table
*/
if (!mi->hash_prev) {
DEBUG_ASSERT(ecm_db_mapping_table[mi->hash_index] == mi, "%px: hash table bad\n", mi);
ecm_db_mapping_table[mi->hash_index] = mi->hash_next;
} else {
mi->hash_prev->hash_next = mi->hash_next;
}
if (mi->hash_next) {
mi->hash_next->hash_prev = mi->hash_prev;
}
mi->hash_next = NULL;
mi->hash_prev = NULL;
ecm_db_mapping_table_lengths[mi->hash_index]--;
DEBUG_ASSERT(ecm_db_mapping_table_lengths[mi->hash_index] >= 0, "%px: invalid table len %d\n", mi, ecm_db_mapping_table_lengths[mi->hash_index]);
#ifdef ECM_DB_XREF_ENABLE
/*
* Unlink it from the host mapping list
*/
if (!mi->mapping_prev) {
DEBUG_ASSERT(mi->host->mappings == mi, "%px: mapping table bad\n", mi);
mi->host->mappings = mi->mapping_next;
} else {
mi->mapping_prev->mapping_next = mi->mapping_next;
}
if (mi->mapping_next) {
mi->mapping_next->mapping_prev = mi->mapping_prev;
}
mi->mapping_next = NULL;
mi->mapping_prev = NULL;
mi->host->mapping_count--;
#endif
spin_unlock_bh(&ecm_db_lock);
/*
* Throw removed event to listeners
*/
DEBUG_TRACE("%px: Throw mapping removed event\n", mi);
li = ecm_db_listeners_get_and_ref_first();
while (li) {
struct ecm_db_listener_instance *lin;
if (li->mapping_removed) {
li->mapping_removed(li->arg, mi);
}
/*
* Get next listener
*/
lin = ecm_db_listener_get_and_ref_next(li);
ecm_db_listener_deref(li);
li = lin;
}
}
/*
* Throw final event
*/
if (mi->final) {
mi->final(mi->arg);
}
/*
* Now release the host instance if the mapping had one
*/
if (mi->host) {
ecm_db_host_deref(mi->host);
}
/*
* We can now destroy the instance
*/
DEBUG_CLEAR_MAGIC(mi);
kfree(mi);
/*
* Decrease global mapping count
*/
spin_lock_bh(&ecm_db_lock);
ecm_db_mapping_count--;
DEBUG_ASSERT(ecm_db_mapping_count >= 0, "%px: mapping count wrap\n", mi);
spin_unlock_bh(&ecm_db_lock);
return 0;
}
EXPORT_SYMBOL(ecm_db_mapping_deref);
/*
* ecm_db_mapping_find_and_ref()
* Lookup and return a mapping reference if any.
*
* NOTE: For non-port based protocols the ports are expected to be -(protocol)
*/
struct ecm_db_mapping_instance *ecm_db_mapping_find_and_ref(ip_addr_t address, int port)
{
ecm_db_mapping_hash_t hash_index;
struct ecm_db_mapping_instance *mi;
DEBUG_TRACE("Lookup mapping with addr " ECM_IP_ADDR_OCTAL_FMT " and port %d\n", ECM_IP_ADDR_TO_OCTAL(address), port);
/*
* Compute the hash chain index and prepare to walk the chain
*/
hash_index = ecm_db_mapping_generate_hash_index(address, port);
/*
* Iterate the chain looking for a mapping with matching details
*/
spin_lock_bh(&ecm_db_lock);
mi = ecm_db_mapping_table[hash_index];
while (mi) {
if (mi->port != port) {
mi = mi->hash_next;
continue;
}
if (!ECM_IP_ADDR_MATCH(mi->host->address, address)) {
mi = mi->hash_next;
continue;
}
_ecm_db_mapping_ref(mi);
spin_unlock_bh(&ecm_db_lock);
DEBUG_TRACE("Mapping found %px\n", mi);
return mi;
}
spin_unlock_bh(&ecm_db_lock);
DEBUG_TRACE("Mapping not found\n");
return NULL;
}
EXPORT_SYMBOL(ecm_db_mapping_find_and_ref);
#ifdef ECM_DB_XREF_ENABLE
/*
* ecm_db_mapping_connections_get_and_ref_first()
* Return a reference to the first connection made on this mapping in the specified direction.
*/
struct ecm_db_connection_instance *ecm_db_mapping_connections_get_and_ref_first(struct ecm_db_mapping_instance *mi, ecm_db_obj_dir_t dir)
{
struct ecm_db_connection_instance *ci;
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed", mi);
spin_lock_bh(&ecm_db_lock);
ci = mi->connections[dir];
if (ci) {
_ecm_db_connection_ref(ci);
}
spin_unlock_bh(&ecm_db_lock);
return ci;
}
EXPORT_SYMBOL(ecm_db_mapping_connections_get_and_ref_first);
#endif
/*
* ecm_db_mapping_host_get_and_ref()
*/
struct ecm_db_host_instance *ecm_db_mapping_host_get_and_ref(struct ecm_db_mapping_instance *mi)
{
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed\n", mi);
spin_lock_bh(&ecm_db_lock);
_ecm_db_host_ref(mi->host);
spin_unlock_bh(&ecm_db_lock);
return mi->host;
}
EXPORT_SYMBOL(ecm_db_mapping_host_get_and_ref);
/*
* ecm_db_mapping_connections_total_count_get()
* Return the total number of connections (NAT and non-NAT) this mapping has
*/
int ecm_db_mapping_connections_total_count_get(struct ecm_db_mapping_instance *mi)
{
int count;
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed\n", mi);
spin_lock_bh(&ecm_db_lock);
count = mi->conn_count[ECM_DB_OBJ_DIR_FROM] +
mi->conn_count[ECM_DB_OBJ_DIR_TO] +
mi->conn_count[ECM_DB_OBJ_DIR_FROM_NAT] +
mi->conn_count[ECM_DB_OBJ_DIR_TO_NAT];
DEBUG_ASSERT(count >= 0, "%px: Count overflow from: %d, to: %d, nat_from: %d, nat_to: %d\n",
mi, mi->conn_count[ECM_DB_OBJ_DIR_FROM], mi->conn_count[ECM_DB_OBJ_DIR_TO],
mi->conn_count[ECM_DB_OBJ_DIR_FROM_NAT], mi->conn_count[ECM_DB_OBJ_DIR_TO_NAT]);
spin_unlock_bh(&ecm_db_lock);
return count;
}
EXPORT_SYMBOL(ecm_db_mapping_connections_total_count_get);
/*
* ecm_db_mapping_add()
* Add a mapping instance into the database
*
* NOTE: The mapping will take a reference to the host instance.
*/
void ecm_db_mapping_add(struct ecm_db_mapping_instance *mi, struct ecm_db_host_instance *hi, int port,
ecm_db_mapping_final_callback_t final, void *arg)
{
ecm_db_mapping_hash_t hash_index;
struct ecm_db_listener_instance *li;
spin_lock_bh(&ecm_db_lock);
DEBUG_CHECK_MAGIC(mi, ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed\n", mi);
DEBUG_CHECK_MAGIC(hi, ECM_DB_HOST_INSTANCE_MAGIC, "%px: magic failed\n", hi);
DEBUG_ASSERT(!(mi->flags & ECM_DB_MAPPING_FLAGS_INSERTED), "%px: inserted\n", mi);
DEBUG_ASSERT((hi->flags & ECM_DB_HOST_FLAGS_INSERTED), "%px: not inserted\n", hi);
DEBUG_ASSERT(!mi->tcp_count[ECM_DB_OBJ_DIR_FROM] && !mi->tcp_count[ECM_DB_OBJ_DIR_TO] &&
!mi->udp_count[ECM_DB_OBJ_DIR_FROM] && !mi->udp_count[ECM_DB_OBJ_DIR_TO],
"%px: protocol count errors\n", mi);
#ifdef ECM_DB_XREF_ENABLE
DEBUG_ASSERT(mi->connections[ECM_DB_OBJ_DIR_FROM] == NULL, "%px: connections not null\n", mi);
DEBUG_ASSERT(mi->connections[ECM_DB_OBJ_DIR_TO] == NULL, "%px: connections not null\n", mi);
DEBUG_ASSERT(!mi->conn_count[ECM_DB_OBJ_DIR_FROM] && !mi->conn_count[ECM_DB_OBJ_DIR_TO] &&
!mi->conn_count[ECM_DB_OBJ_DIR_FROM_NAT] && !mi->conn_count[ECM_DB_OBJ_DIR_TO_NAT],
"%px: connection count errors\n", mi);
#endif
spin_unlock_bh(&ecm_db_lock);
mi->arg = arg;
mi->final = final;
/*
* Compute hash table position for insertion
*/
hash_index = ecm_db_mapping_generate_hash_index(hi->address, port);
mi->hash_index = hash_index;
/*
* Record port
*/
mi->port = port;
/*
* Mapping takes a ref to the host
*/
ecm_db_host_ref(hi);
mi->host = hi;
/*
* Set time
*/
spin_lock_bh(&ecm_db_lock);
mi->time_added = ecm_db_time;
/*
* Record the mapping is inserted
*/
mi->flags |= ECM_DB_MAPPING_FLAGS_INSERTED;
/*
* Add into the global list
*/
mi->prev = NULL;
mi->next = ecm_db_mappings;
if (ecm_db_mappings) {
ecm_db_mappings->prev = mi;
}
ecm_db_mappings = mi;
/*
* Insert mapping into the mappings hash table
*/
mi->hash_prev = NULL;
mi->hash_next = ecm_db_mapping_table[hash_index];
if (ecm_db_mapping_table[hash_index]) {
ecm_db_mapping_table[hash_index]->hash_prev = mi;
}
ecm_db_mapping_table[hash_index] = mi;
ecm_db_mapping_table_lengths[hash_index]++;
DEBUG_ASSERT(ecm_db_mapping_table_lengths[hash_index] > 0, "%px: invalid table len %d\n", hi, ecm_db_mapping_table_lengths[hash_index]);
#ifdef ECM_DB_XREF_ENABLE
/*
* Insert mapping into the host mapping list
*/
mi->mapping_prev = NULL;
mi->mapping_next = hi->mappings;
if (hi->mappings) {
hi->mappings->mapping_prev = mi;
}
hi->mappings = mi;
hi->mapping_count++;
#endif
spin_unlock_bh(&ecm_db_lock);
/*
* Throw add event to the listeners
*/
DEBUG_TRACE("%px: Throw mapping added event\n", mi);
li = ecm_db_listeners_get_and_ref_first();
while (li) {
struct ecm_db_listener_instance *lin;
if (li->mapping_added) {
li->mapping_added(li->arg, mi);
}
/*
* Get next listener
*/
lin = ecm_db_listener_get_and_ref_next(li);
ecm_db_listener_deref(li);
li = lin;
}
}
EXPORT_SYMBOL(ecm_db_mapping_add);
/*
* ecm_db_mapping_hash_table_lengths_get()
* Return hash table length
*/
int ecm_db_mapping_hash_table_lengths_get(int index)
{
int length;
DEBUG_ASSERT((index >= 0) && (index < ECM_DB_MAPPING_HASH_SLOTS), "Bad protocol: %d\n", index);
spin_lock_bh(&ecm_db_lock);
length = ecm_db_mapping_table_lengths[index];
spin_unlock_bh(&ecm_db_lock);
return length;
}
EXPORT_SYMBOL(ecm_db_mapping_hash_table_lengths_get);
/*
* ecm_db_mapping_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_mapping_hash_index_get_next(int index)
{
index++;
if (index >= ECM_DB_MAPPING_HASH_SLOTS) {
return -1;
}
return index;
}
EXPORT_SYMBOL(ecm_db_mapping_hash_index_get_next);
/*
* ecm_db_mapping_hash_index_get_first()
* Return first hash index
*/
int ecm_db_mapping_hash_index_get_first(void)
{
return 0;
}
EXPORT_SYMBOL(ecm_db_mapping_hash_index_get_first);
/*
* ecm_db_mapping_alloc()
* Allocate a mapping instance
*/
struct ecm_db_mapping_instance *ecm_db_mapping_alloc(void)
{
struct ecm_db_mapping_instance *mi;
mi = (struct ecm_db_mapping_instance *)kzalloc(sizeof(struct ecm_db_mapping_instance), GFP_ATOMIC | __GFP_NOWARN);
if (!mi) {
DEBUG_WARN("Alloc failed\n");
return NULL;
}
mi->refs = 1;
DEBUG_SET_MAGIC(mi, ECM_DB_MAPPING_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(mi);
return NULL;
}
ecm_db_mapping_count++;
spin_unlock_bh(&ecm_db_lock);
DEBUG_TRACE("Mapping created %px\n", mi);
return mi;
}
EXPORT_SYMBOL(ecm_db_mapping_alloc);
/*
* ecm_db_mapping_init()
*/
bool ecm_db_mapping_init(struct dentry *dentry)
{
if (!debugfs_create_u32("mapping_count", S_IRUGO, dentry,
(u32 *)&ecm_db_mapping_count)) {
DEBUG_ERROR("Failed to create ecm db mapping count file in debugfs\n");
return false;
}
ecm_db_mapping_table = vzalloc(sizeof(struct ecm_db_mapping_instance *) * ECM_DB_MAPPING_HASH_SLOTS);
if (!ecm_db_mapping_table) {
DEBUG_ERROR("Failed to allocate virtual memory for ecm_db_mapping_table\n");
return false;
}
ecm_db_mapping_table_lengths = vzalloc(sizeof(int) * ECM_DB_MAPPING_HASH_SLOTS);
if (!ecm_db_mapping_table_lengths) {
DEBUG_ERROR("Failed to allocate virtual memory for ecm_db_mapping_table_lengths\n");
vfree(ecm_db_mapping_table);
return false;
}
return true;
}
/*
* ecm_db_mapping_exit()
*/
void ecm_db_mapping_exit(void)
{
vfree(ecm_db_mapping_table_lengths);
vfree(ecm_db_mapping_table);
}