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