| /* |
| ************************************************************************** |
| * Copyright (c) 2014-2022, 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" |
| |
| /* |
| * Magic number |
| */ |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| #define ECM_DB_CLASSIFIER_TYPE_ASSIGNMENT_MAGIC 0xAEF4 |
| #endif |
| |
| /* |
| * 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_connection_instance *ecm_db_connections = NULL; |
| |
| /* |
| * Connection hash table |
| */ |
| #define ECM_DB_CONNECTION_HASH_SLOTS 32768 |
| static struct ecm_db_connection_instance **ecm_db_connection_table; |
| /* Slots of the connection hash table */ |
| static int *ecm_db_connection_table_lengths; |
| /* Tracks how long each chain is */ |
| static int ecm_db_connection_count = 0; /* Number of connections allocated */ |
| |
| /* |
| * Connection serial number hash table |
| */ |
| #define ECM_DB_CONNECTION_SERIAL_HASH_SLOTS 32768 |
| struct ecm_db_connection_instance **ecm_db_connection_serial_table; |
| /* Slots of the connection serial hash table */ |
| int *ecm_db_connection_serial_table_lengths; |
| /* Tracks how long each chain is */ |
| |
| static int ecm_db_connection_serial = 0; /* Serial number - ensures each connection has a unique serial number. |
| * Serial numbers are used mainly by classifiers that keep their own state |
| * and can 'link' their state to the right connection using a serial number. |
| * The serial number is also used as a soft linkage to other subsystems such as NA. |
| */ |
| |
| /* |
| * Simple stats |
| */ |
| #define ECM_DB_PROTOCOL_COUNT 256 |
| static int ecm_db_connection_count_by_protocol[ECM_DB_PROTOCOL_COUNT]; /* Each IP protocol has its own count */ |
| |
| /* |
| * Connection state validity |
| * This counter is incremented whenever a general change is detected which requires re-generation of state for ALL connections. |
| */ |
| uint16_t ecm_db_connection_generation = 0; /* Generation counter to detect when all connection state is considered stale and all must be re-generated */ |
| |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| /* |
| * Classifier TYPE assignment lists. |
| * |
| * For each type of classifier a list is kept of all connections assigned a classifier of that type. |
| * This permits a classifier type to rapidly retrieve all connections with classifiers assigned to it of that type. |
| * |
| * NOTE: This is in addition to the basic functionality whereby a connection keeps a list of classifier instances |
| * that are assigned to it in descending order of priority. |
| */ |
| |
| /* |
| * struct ecm_db_connection_classifier_type_assignment_list |
| * A list, one for each classifier type. |
| */ |
| struct ecm_db_connection_classifier_type_assignment_list { |
| struct ecm_db_connection_instance *type_assignments_list; |
| /* Lists of connections assigned to this type of classifier */ |
| int32_t type_assignment_count; /* Number of connections in the list */ |
| } ecm_db_connection_classifier_type_assignments[ECM_CLASSIFIER_TYPES]; |
| /* Each classifier type has a list of connections that are assigned to classifier instances of that type */ |
| #endif |
| |
| /* |
| * _ecm_db_connection_count_get() |
| * Return the connection count (lockless). |
| */ |
| int _ecm_db_connection_count_get(void) |
| { |
| return ecm_db_connection_count; |
| } |
| |
| /* |
| * ecm_db_connection_count_get() |
| * Return the connection count |
| */ |
| int ecm_db_connection_count_get(void) |
| { |
| int count; |
| |
| spin_lock_bh(&ecm_db_lock); |
| count = ecm_db_connection_count; |
| spin_unlock_bh(&ecm_db_lock); |
| return count; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_count_get); |
| |
| /* |
| * ecm_db_connection_count_by_protocol_get() |
| * Return # connections for the given protocol |
| */ |
| int ecm_db_connection_count_by_protocol_get(int protocol) |
| { |
| int count; |
| |
| DEBUG_ASSERT((protocol >= 0) && (protocol < ECM_DB_PROTOCOL_COUNT), "Bad protocol: %d\n", protocol); |
| spin_lock_bh(&ecm_db_lock); |
| count = ecm_db_connection_count_by_protocol[protocol]; |
| spin_unlock_bh(&ecm_db_lock); |
| return count; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_count_by_protocol_get); |
| |
| /* |
| * ecm_db_connection_l2_encap_proto_set() |
| * Sets the L2 encap protocol. |
| */ |
| void ecm_db_connection_l2_encap_proto_set(struct ecm_db_connection_instance *ci, uint16_t l2_encap_proto) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| ci->l2_encap_proto = l2_encap_proto; |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| |
| /* |
| * ecm_db_connection_l2_encap_proto_get() |
| * Gets the L2 encap protocol. |
| */ |
| uint16_t ecm_db_connection_l2_encap_proto_get(struct ecm_db_connection_instance *ci) |
| { |
| uint16_t proto; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| proto = ci->l2_encap_proto; |
| spin_unlock_bh(&ecm_db_lock); |
| |
| return proto; |
| } |
| |
| /* |
| * ecm_db_connection_mark_set() |
| * Sets the mark value of the connection. |
| */ |
| void ecm_db_connection_mark_set(struct ecm_db_connection_instance *ci, uint32_t mark) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| ci->mark = mark; |
| spin_unlock_bh(&ecm_db_lock); |
| |
| } |
| |
| /* |
| * ecm_db_connection_flag_set() |
| * Sets the flag in connection instance. |
| */ |
| void ecm_db_connection_flag_set(struct ecm_db_connection_instance *ci, uint32_t flag) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| ci->flags |= flag; |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| |
| /* |
| * ecm_db_connection_mark_get() |
| * Gets the mark value of the connection. |
| */ |
| uint32_t ecm_db_connection_mark_get(struct ecm_db_connection_instance *ci) |
| { |
| uint16_t mark; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| mark = ci->mark; |
| spin_unlock_bh(&ecm_db_lock); |
| |
| return mark; |
| } |
| |
| /* |
| * ecm_db_connection_front_end_get_and_ref() |
| * Return ref to the front end instance of the connection |
| */ |
| struct ecm_front_end_connection_instance *ecm_db_connection_front_end_get_and_ref(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| ci->feci->ref(ci->feci); |
| return ci->feci; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_front_end_get_and_ref); |
| |
| /* |
| * ecm_db_connection_defunct_callback() |
| * Invoked by the expiration of the defunct_timer contained in a connection instance |
| */ |
| static void ecm_db_connection_defunct_callback(void *arg) |
| { |
| int accel_mode; |
| bool ret; |
| |
| struct ecm_db_connection_instance *ci = (struct ecm_db_connection_instance *)arg; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| DEBUG_INFO("%px: defunct timer expired\n", ci); |
| |
| ret = ci->defunct(ci->feci, &accel_mode); |
| |
| /* |
| * If the returned 'ret' is success, this means this callback succeeded to |
| * defunct the connection and it can release the last reference. |
| * If it fails, this means that another defunct process defuncted the connection |
| * before this callback. In that case, we will check the accel_mode of the connection. |
| * If the other call defuncted the connection successfully, it will set the accel_mode to |
| * ECM_FRONT_END_ACCELERATION_MODE_FAIL_DEFUNCT_SHORT for s short amount of time to avoid |
| * further accel/decel attempts. So, in this accel_mode, this callback shouldn't release the |
| * last reference. It will be released by the ecm_db_connection_make_defunct() function. |
| */ |
| if (ret || (ECM_FRONT_END_ACCELERATION_FAILED(accel_mode) && (accel_mode != ECM_FRONT_END_ACCELERATION_MODE_FAIL_DEFUNCT_SHORT))) { |
| ecm_db_connection_deref(ci); |
| } |
| } |
| |
| /* |
| * ecm_db_connection_elapsed_defunct_timer() |
| * Returns the elapsed time of defunct timer. |
| * If the timer is already expired and not removed from the database, the |
| * function returns a negative value. The caller MUST handle this return value. |
| */ |
| int ecm_db_connection_elapsed_defunct_timer(struct ecm_db_connection_instance *ci) |
| { |
| long int expires_in; |
| int elapsed; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| /* |
| * Do some sanity checks. |
| * If it is not in a timer group, which means already expired, or the |
| * connection has not been fully created yet. Just return 0. |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| if (ci->defunct_timer.group == ECM_DB_TIMER_GROUPS_MAX) { |
| spin_unlock_bh(&ecm_db_lock); |
| return -1; |
| } |
| |
| /* |
| * Already expired, but not removed from the database completely. |
| */ |
| expires_in = (long int)(ci->defunct_timer.timeout - ecm_db_time); |
| if (expires_in < 0) { |
| spin_unlock_bh(&ecm_db_lock); |
| return -1; |
| } |
| |
| elapsed = ecm_db_timer_groups[ci->defunct_timer.group].time - expires_in; |
| spin_unlock_bh(&ecm_db_lock); |
| |
| return elapsed; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_elapsed_defunct_timer); |
| |
| /* |
| * ecm_db_connection_defunct_timer_reset() |
| * Set/change the timer group associated with a connection. Returns false if the connection has become defunct and the new group cannot be set for that reason. |
| */ |
| bool ecm_db_connection_defunct_timer_reset(struct ecm_db_connection_instance *ci, ecm_db_timer_group_t tg) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| return ecm_db_timer_group_entry_reset(&ci->defunct_timer, tg); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_defunct_timer_reset); |
| |
| /* |
| * ecm_db_connection_defunct_timer_touch() |
| * Update the connections defunct timer to stop it timing out. Returns false if the connection defunct timer has expired. |
| */ |
| bool ecm_db_connection_defunct_timer_touch(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| return ecm_db_timer_group_entry_touch(&ci->defunct_timer); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_defunct_timer_touch); |
| |
| /* |
| * ecm_db_connection_defunct_timer_no_touch_set() |
| * Set no touch flag in CI |
| */ |
| void ecm_db_connection_defunct_timer_no_touch_set(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%p: magic failed", ci); |
| ci->timer_no_touch = true; |
| } |
| |
| /* |
| * ecm_db_connection_defunct_timer_no_touch_get() |
| * Get no touch flag in CI |
| */ |
| bool ecm_db_connection_defunct_timer_no_touch_get(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%p: magic failed", ci); |
| return ci->timer_no_touch; |
| } |
| |
| /* |
| * ecm_db_connection_timer_group_get() |
| * Return the timer group id |
| */ |
| ecm_db_timer_group_t ecm_db_connection_timer_group_get(struct ecm_db_connection_instance *ci) |
| { |
| ecm_db_timer_group_t tg; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| tg = ci->defunct_timer.group; |
| spin_unlock_bh(&ecm_db_lock); |
| return tg; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_timer_group_get); |
| |
| /* |
| * ecm_db_connection_make_defunct() |
| * Make connection defunct. |
| */ |
| void ecm_db_connection_make_defunct(struct ecm_db_connection_instance *ci) |
| { |
| int accel_mode; |
| bool ret; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| /* |
| * If connection's defunct timer is already removed from the groups, |
| * this means that the connection is timed out and already in defunct process. |
| * So, let's not continue in this defunct process. |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| if (ci->defunct_timer.group == ECM_DB_TIMER_GROUPS_MAX) { |
| spin_unlock_bh(&ecm_db_lock); |
| return; |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| |
| /* |
| * Call the frontend's defunct callback function and handle the return values. |
| */ |
| ret = ci->defunct(ci->feci, &accel_mode); |
| |
| /* |
| * If the defunct is success, first we should remove the timer and then release |
| * the last reference. It is possible that while we are handling the defunct callback, |
| * the timer was expired and removed from the list. So, we don't need to check the |
| * return value of the timer removal function. Regardless of who removed the timer, we should |
| * release the last reference. |
| */ |
| if (ret) { |
| ecm_db_timer_group_entry_remove(&ci->defunct_timer); |
| ecm_db_connection_deref(ci); |
| return; |
| } |
| |
| /* |
| * If defunct fails and the connection state is in one of the fail states, we should remove the timer |
| * and release the last reference. In this case we should check the timer removal function's return value |
| * to make sure that it is removed by us. |
| */ |
| if (ECM_FRONT_END_ACCELERATION_FAILED(accel_mode)) { |
| if (ecm_db_timer_group_entry_remove(&ci->defunct_timer)) { |
| ecm_db_connection_deref(ci); |
| } |
| } |
| } |
| EXPORT_SYMBOL(ecm_db_connection_make_defunct); |
| |
| /* |
| * ecm_db_connection_data_totals_update() |
| * Update the total data (and packets) sent/received by the given host |
| */ |
| void ecm_db_connection_data_totals_update(struct ecm_db_connection_instance *ci, bool is_from, uint64_t size, uint64_t packets) |
| { |
| int32_t i; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| |
| if (is_from) { |
| /* |
| * Update totals sent by the FROM side of connection |
| */ |
| ci->from_data_total += size; |
| ci->from_packet_total += packets; |
| #ifdef ECM_DB_ADVANCED_STATS_ENABLE |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->from_data_total += size; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->host->from_data_total += size; |
| ci->node[ECM_DB_OBJ_DIR_FROM]->from_data_total += size; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->from_packet_total += packets; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->host->from_packet_total += packets; |
| ci->node[ECM_DB_OBJ_DIR_FROM]->from_packet_total += packets; |
| |
| /* |
| * Data from the host is essentially TO the interface on which the host is reachable |
| */ |
| for (i = ci->interface_first[ECM_DB_OBJ_DIR_FROM]; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| ci->interfaces[ECM_DB_OBJ_DIR_FROM][i]->to_data_total += size; |
| ci->interfaces[ECM_DB_OBJ_DIR_FROM][i]->to_packet_total += packets; |
| } |
| |
| /* |
| * Update totals sent TO the other side of the connection |
| */ |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->to_data_total += size; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->host->to_data_total += size; |
| ci->node[ECM_DB_OBJ_DIR_TO]->to_data_total += size; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->to_packet_total += packets; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->host->to_packet_total += packets; |
| ci->node[ECM_DB_OBJ_DIR_TO]->to_packet_total += packets; |
| |
| /* |
| * Sending to the other side means FROM the interface we reach that host |
| */ |
| for (i = ci->interface_first[ECM_DB_OBJ_DIR_TO]; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| ci->interfaces[ECM_DB_OBJ_DIR_TO][i]->from_data_total += size; |
| ci->interfaces[ECM_DB_OBJ_DIR_TO][i]->from_packet_total += packets; |
| } |
| #endif |
| spin_unlock_bh(&ecm_db_lock); |
| return; |
| } |
| |
| /* |
| * Update totals sent by the TO side of this connection |
| */ |
| ci->to_data_total += size; |
| ci->to_packet_total += packets; |
| #ifdef ECM_DB_ADVANCED_STATS_ENABLE |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->from_data_total += size; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->host->from_data_total += size; |
| ci->node[ECM_DB_OBJ_DIR_TO]->from_data_total += size; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->from_packet_total += packets; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->host->from_packet_total += packets; |
| ci->node[ECM_DB_OBJ_DIR_TO]->from_packet_total += packets; |
| |
| /* |
| * Data from the host is essentially TO the interface on which the host is reachable |
| */ |
| for (i = ci->interface_first[ECM_DB_OBJ_DIR_TO]; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| ci->interfaces[ECM_DB_OBJ_DIR_TO][i]->to_data_total += size; |
| ci->interfaces[ECM_DB_OBJ_DIR_TO][i]->to_packet_total += packets; |
| } |
| |
| /* |
| * Update totals sent TO the other side of the connection |
| */ |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->to_data_total += size; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->host->to_data_total += size; |
| ci->node[ECM_DB_OBJ_DIR_FROM]->to_data_total += size; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->to_packet_total += packets; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->host->to_packet_total += packets; |
| ci->node[ECM_DB_OBJ_DIR_FROM]->to_packet_total += packets; |
| |
| /* |
| * Sending to the other side means FROM the interface we reach that host |
| */ |
| for (i = ci->interface_first[ECM_DB_OBJ_DIR_FROM]; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| ci->interfaces[ECM_DB_OBJ_DIR_FROM][i]->from_data_total += size; |
| ci->interfaces[ECM_DB_OBJ_DIR_FROM][i]->from_packet_total += packets; |
| } |
| #endif |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_data_totals_update); |
| |
| /* |
| * ecm_db_connection_data_totals_update_dropped() |
| * Update the total data (and packets) sent by the given host but which we dropped |
| */ |
| void ecm_db_connection_data_totals_update_dropped(struct ecm_db_connection_instance *ci, bool is_from, uint64_t size, uint64_t packets) |
| { |
| int32_t i; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| if (is_from) { |
| /* |
| * Update dropped totals sent by the FROM side |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| ci->from_data_total_dropped += size; |
| ci->from_packet_total_dropped += packets; |
| #ifdef ECM_DB_ADVANCED_STATS_ENABLE |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->from_data_total_dropped += size; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->host->from_data_total_dropped += size; |
| ci->node[ECM_DB_OBJ_DIR_FROM]->from_data_total_dropped += size; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->from_packet_total_dropped += packets; |
| ci->mapping[ECM_DB_OBJ_DIR_FROM]->host->from_packet_total_dropped += packets; |
| ci->node[ECM_DB_OBJ_DIR_FROM]->from_packet_total_dropped += packets; |
| |
| /* |
| * Data from the host is essentially TO the interface on which the host is reachable |
| */ |
| for (i = ci->interface_first[ECM_DB_OBJ_DIR_FROM]; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| ci->interfaces[ECM_DB_OBJ_DIR_FROM][i]->to_data_total_dropped += size; |
| ci->interfaces[ECM_DB_OBJ_DIR_FROM][i]->to_packet_total_dropped += packets; |
| } |
| #endif |
| spin_unlock_bh(&ecm_db_lock); |
| return; |
| } |
| |
| /* |
| * Update dropped totals sent by the TO side of this connection |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| ci->to_data_total_dropped += size; |
| ci->to_packet_total_dropped += packets; |
| #ifdef ECM_DB_ADVANCED_STATS_ENABLE |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->from_data_total_dropped += size; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->host->from_data_total_dropped += size; |
| ci->node[ECM_DB_OBJ_DIR_TO]->from_data_total_dropped += size; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->from_packet_total_dropped += packets; |
| ci->mapping[ECM_DB_OBJ_DIR_TO]->host->from_packet_total_dropped += packets; |
| ci->node[ECM_DB_OBJ_DIR_TO]->from_packet_total_dropped += packets; |
| |
| /* |
| * Data from the host is essentially TO the interface on which the host is reachable |
| */ |
| for (i = ci->interface_first[ECM_DB_OBJ_DIR_TO]; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| ci->interfaces[ECM_DB_OBJ_DIR_TO][i]->to_data_total_dropped += size; |
| ci->interfaces[ECM_DB_OBJ_DIR_TO][i]->to_packet_total_dropped += packets; |
| } |
| #endif |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_data_totals_update_dropped); |
| |
| /* |
| * ecm_db_connection_data_stats_get() |
| * Return data stats for the instance |
| */ |
| void ecm_db_connection_data_stats_get(struct ecm_db_connection_instance *ci, 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(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| if (from_data_total) { |
| *from_data_total = ci->from_data_total; |
| } |
| if (to_data_total) { |
| *to_data_total = ci->to_data_total; |
| } |
| if (from_packet_total) { |
| *from_packet_total = ci->from_packet_total; |
| } |
| if (to_packet_total) { |
| *to_packet_total = ci->to_packet_total; |
| } |
| if (from_data_total_dropped) { |
| *from_data_total_dropped = ci->from_data_total_dropped; |
| } |
| if (to_data_total_dropped) { |
| *to_data_total_dropped = ci->to_data_total_dropped; |
| } |
| if (from_packet_total_dropped) { |
| *from_packet_total_dropped = ci->from_packet_total_dropped; |
| } |
| if (to_packet_total_dropped) { |
| *to_packet_total_dropped = ci->to_packet_total_dropped; |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_data_stats_get); |
| |
| /* |
| * ecm_db_connection_serial_get() |
| * Return serial |
| */ |
| uint32_t ecm_db_connection_serial_get(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| return ci->serial; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_serial_get); |
| |
| /* |
| * ecm_db_connection_address_get() |
| * Return ip address address |
| */ |
| void ecm_db_connection_address_get(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir, ip_addr_t addr) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| DEBUG_CHECK_MAGIC(ci->mapping[dir], ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed", ci->mapping[dir]); |
| DEBUG_CHECK_MAGIC(ci->mapping[dir]->host, ECM_DB_HOST_INSTANCE_MAGIC, "%px: magic failed", ci->mapping[dir]->host); |
| ECM_IP_ADDR_COPY(addr, ci->mapping[dir]->host->address); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_address_get); |
| |
| /* |
| * ecm_db_connection_port_get() |
| * Return port |
| */ |
| int ecm_db_connection_port_get(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| DEBUG_CHECK_MAGIC(ci->mapping[dir], ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: magic failed", ci->mapping[dir]); |
| return ci->mapping[dir]->port; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_port_get); |
| |
| /* |
| * ecm_db_connection_node_address_get() |
| * Return address of the node used when sending packets to the specified side. |
| */ |
| void ecm_db_connection_node_address_get(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir, uint8_t *address_buffer) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| memcpy(address_buffer, ci->node[dir]->address, ETH_ALEN); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_node_address_get); |
| |
| /* |
| * ecm_db_connection_iface_name_get() |
| * Return name of interface on which the specified side may be reached |
| */ |
| void ecm_db_connection_iface_name_get(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir, char *name_buffer) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| strlcpy(name_buffer, ci->node[dir]->iface->name, IFNAMSIZ); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_iface_name_get); |
| |
| /* |
| * ecm_db_connection_iface_mtu_get() |
| * Return MTU of interface on which the specified side may be reached |
| */ |
| int ecm_db_connection_iface_mtu_get(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| int mtu; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| spin_lock_bh(&ecm_db_lock); |
| mtu = ci->node[dir]->iface->mtu; |
| spin_unlock_bh(&ecm_db_lock); |
| return mtu; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_iface_mtu_get); |
| |
| /* |
| * ecm_db_connection_iface_type_get() |
| * Return type of interface on which the specified side may be reached |
| */ |
| ecm_db_iface_type_t ecm_db_connection_iface_type_get(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| ecm_db_iface_type_t type; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| spin_lock_bh(&ecm_db_lock); |
| type = ci->node[dir]->iface->type; |
| spin_unlock_bh(&ecm_db_lock); |
| return type; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_iface_type_get); |
| |
| /* |
| * ecm_db_connection_regeneration_occurrances_get() |
| * Get the number of regeneration occurrances that have occurred since the connection was created. |
| */ |
| uint16_t ecm_db_connection_regeneration_occurrances_get(struct ecm_db_connection_instance *ci) |
| { |
| uint16_t occurances; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| occurances = ci->regen_occurances; |
| spin_unlock_bh(&ecm_db_lock); |
| return occurances; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_regeneration_occurrances_get); |
| |
| /* |
| * ecm_db_connection_regeneration_completed() |
| * Re-generation was completed successfully |
| */ |
| void ecm_db_connection_regeneration_completed(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| |
| DEBUG_ASSERT(ci->regen_in_progress, "%px: Bad call", ci); |
| DEBUG_ASSERT(ci->regen_required > 0, "%px: Bad call", ci); |
| |
| /* |
| * Decrement the required counter by 1. |
| * This may mean that regeneration is still required due to another change occuring _during_ re-generation. |
| */ |
| ci->regen_required--; |
| ci->regen_in_progress = false; |
| ci->regen_success++; |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_regeneration_completed); |
| |
| /* |
| * ecm_db_connection_regeneration_failed() |
| * Re-generation failed |
| */ |
| void ecm_db_connection_regeneration_failed(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| |
| DEBUG_ASSERT(ci->regen_in_progress, "%px: Bad call", ci); |
| DEBUG_ASSERT(ci->regen_required > 0, "%px: Bad call", ci); |
| |
| /* |
| * Re-generation is no longer in progress BUT we leave the regen |
| * counter as it is so as to indicate re-generation is still needed |
| */ |
| ci->regen_in_progress = false; |
| ci->regen_fail++; |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_regeneration_failed); |
| |
| /* |
| * ecm_db_connection_regeneration_required_check() |
| * Returns true if the connection needs to be re-generated. |
| * |
| * If re-generation is needed this will mark the connection to indicate that re-generation is needed AND in progress. |
| * If the return code is TRUE the caller MUST handle the re-generation. |
| * Upon re-generation completion you must call ecm_db_connection_regeneration_completed() or ecm_db_connection_regeneration_failed(). |
| */ |
| bool ecm_db_connection_regeneration_required_check(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| /* |
| * Check the global generation counter for changes |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| if (ci->generation != ecm_db_connection_generation) { |
| /* |
| * Re-generation is needed |
| */ |
| ci->regen_occurances++; |
| ci->regen_required++; |
| |
| /* |
| * Record that we have seen this change |
| */ |
| ci->generation = ecm_db_connection_generation; |
| } |
| |
| /* |
| * If re-generation is in progress then something is handling re-generation already |
| * so we tell the caller that it cannot handle re-generation. |
| */ |
| if (ci->regen_in_progress) { |
| spin_unlock_bh(&ecm_db_lock); |
| return false; |
| } |
| |
| /* |
| * Is re-generation required? |
| */ |
| if (ci->regen_required == 0) { |
| spin_unlock_bh(&ecm_db_lock); |
| return false; |
| } |
| |
| /* |
| * Flag that re-generation is in progress and tell the caller to handle re-generation |
| */ |
| ci->regen_in_progress = true; |
| spin_unlock_bh(&ecm_db_lock); |
| return true; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_regeneration_required_check); |
| |
| /* |
| * ecm_db_connection_regeneration_required_peek() |
| * Returns true if the connection needs to be regenerated. |
| * |
| * NOTE: The caller MUST NOT handle re-generation, the caller may use this indication |
| * to determine the sanity of the connection state and whether acceleration is permitted. |
| */ |
| bool ecm_db_connection_regeneration_required_peek(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| |
| /* |
| * Check the global generation counter for changes (record any change now) |
| */ |
| if (ci->generation != ecm_db_connection_generation) { |
| /* |
| * Re-generation is needed, flag the connection as needing re-generation now. |
| */ |
| ci->regen_occurances++; |
| ci->regen_required++; |
| |
| /* |
| * Record that we have seen this change |
| */ |
| ci->generation = ecm_db_connection_generation; |
| } |
| if (ci->regen_required == 0) { |
| spin_unlock_bh(&ecm_db_lock); |
| return false; |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| return true; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_regeneration_required_peek); |
| |
| /* |
| * ecm_db_connection_regeneration_needed() |
| * Cause a specific connection to require re-generation |
| * |
| * NOTE: This only flags that re-generation is needed. |
| * The connection will typically be re-generated when ecm_db_connection_regeneration_required_check() is invoked. |
| */ |
| void ecm_db_connection_regeneration_needed(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| ci->regen_occurances++; |
| ci->regen_required++; |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_regeneration_needed); |
| |
| /* |
| * ecm_db_regeneration_needed() |
| * Bump the global generation index to cause a re-generation of all connections state. |
| */ |
| void ecm_db_regeneration_needed(void) |
| { |
| spin_lock_bh(&ecm_db_lock); |
| ecm_db_connection_generation++; |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_regeneration_needed); |
| |
| /* |
| * ecm_db_connection_regenerate() |
| * Re-generate a specific connection |
| */ |
| void ecm_db_connection_regenerate(struct ecm_db_connection_instance *ci) |
| { |
| struct ecm_front_end_connection_instance *feci; |
| |
| DEBUG_TRACE("Regenerate connection: %px\n", ci); |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| /* |
| * Notify front end to regenerate a connection. |
| */ |
| feci = ecm_db_connection_front_end_get_and_ref(ci); |
| feci->regenerate(feci, ci); |
| feci->deref(feci); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_regenerate); |
| |
| /* |
| * ecm_db_connection_direction_get() |
| * Return direction of the connection. |
| * |
| * NOTE: an EGRESS connection means that packets being sent to mapping_to should have qos applied. |
| * INGRESS means that packets being sent to mapping[ECM_DB_OBJ_DIR_FROM] should have qos applied. |
| */ |
| ecm_db_direction_t ecm_db_connection_direction_get(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| return ci->direction; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_direction_get); |
| |
| /* |
| * ecm_db_connection_is_routed_get() |
| * Return whether connection is a routed path or not |
| */ |
| bool ecm_db_connection_is_routed_get(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| return ci->is_routed; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_is_routed_get); |
| |
| /* |
| * ecm_db_connection_protocol_get() |
| * Return protocol of connection |
| */ |
| int ecm_db_connection_protocol_get(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| return ci->protocol; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_protocol_get); |
| |
| /* |
| * ecm_db_connection_ip_version_get() |
| * Return IP version of connection |
| */ |
| int ecm_db_connection_ip_version_get(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| return ci->ip_version; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_ip_version_get); |
| |
| /* |
| * ecm_db_connection_is_pppoe_bridged_get() |
| * Return whether connection is pppoe bridged or not |
| */ |
| bool ecm_db_connection_is_pppoe_bridged_get(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| return ci->flags & ECM_DB_CONNECTION_FLAGS_PPPOE_BRIDGE; |
| } |
| |
| /* |
| * ecm_db_connection_defunct_timer_remove_and_set() |
| * Move the connection to a new timer group. |
| * |
| * Before setting the new group, check if the timer group is set. If it is set, |
| * remove it first from the current group. |
| * |
| */ |
| void ecm_db_connection_defunct_timer_remove_and_set(struct ecm_db_connection_instance *ci, ecm_db_timer_group_t tg) |
| { |
| struct ecm_db_timer_group_entry *tge; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| DEBUG_TRACE("%px: ecm_db_connection_defunct_timer_remove_and_set\n", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| tge = &ci->defunct_timer; |
| if (tge->group == tg) { |
| spin_unlock_bh(&ecm_db_lock); |
| DEBUG_TRACE("%px: timer group is aslready equal to %d\n", ci, tg); |
| return; |
| } |
| |
| if (tge->group != ECM_DB_TIMER_GROUPS_MAX) { |
| _ecm_db_timer_group_entry_remove(tge); |
| } |
| |
| /* |
| * Set new group |
| */ |
| _ecm_db_timer_group_entry_set(tge, tg); |
| spin_unlock_bh(&ecm_db_lock); |
| DEBUG_TRACE("%px: New timer group is: %d\n", ci, tge->group); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_defunct_timer_remove_and_set); |
| |
| /* |
| * _ecm_db_connection_ref() |
| */ |
| void _ecm_db_connection_ref(struct ecm_db_connection_instance *ci) |
| { |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| ci->refs++; |
| DEBUG_TRACE("%px: connection ref %d\n", ci, ci->refs); |
| DEBUG_ASSERT(ci->refs > 0, "%px: ref wrap\n", ci); |
| } |
| |
| /* |
| * ecm_db_connection_ref() |
| */ |
| void ecm_db_connection_ref(struct ecm_db_connection_instance *ci) |
| { |
| spin_lock_bh(&ecm_db_lock); |
| _ecm_db_connection_ref(ci); |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_ref); |
| |
| /* |
| * ecm_db_connections_get_and_ref_first() |
| * Obtain a ref to the first connection instance, if any |
| */ |
| struct ecm_db_connection_instance *ecm_db_connections_get_and_ref_first(void) |
| { |
| struct ecm_db_connection_instance *ci; |
| spin_lock_bh(&ecm_db_lock); |
| ci = ecm_db_connections; |
| if (ci) { |
| _ecm_db_connection_ref(ci); |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| return ci; |
| } |
| EXPORT_SYMBOL(ecm_db_connections_get_and_ref_first); |
| |
| /* |
| * ecm_db_connection_get_and_ref_next() |
| * Return the next connection in the list given a connection |
| */ |
| struct ecm_db_connection_instance *ecm_db_connection_get_and_ref_next(struct ecm_db_connection_instance *ci) |
| { |
| 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->next; |
| if (cin) { |
| _ecm_db_connection_ref(cin); |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| return cin; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_get_and_ref_next); |
| |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| /* |
| * _ecm_db_classifier_type_assignment_remove() |
| * Remove the connection from the classifier type assignment list (of the given type) |
| */ |
| static void _ecm_db_classifier_type_assignment_remove(struct ecm_db_connection_instance *ci, ecm_classifier_type_t ca_type) |
| { |
| struct ecm_db_connection_classifier_type_assignment *ta; |
| struct ecm_db_connection_classifier_type_assignment_list *tal; |
| |
| DEBUG_ASSERT(spin_is_locked(&ecm_db_lock), "%px: lock is not held\n", ci); |
| |
| DEBUG_TRACE("%px: Classifier type assignment remove: %d\n", ci, ca_type); |
| ta = &ci->type_assignment[ca_type]; |
| DEBUG_CHECK_MAGIC(ta, ECM_DB_CLASSIFIER_TYPE_ASSIGNMENT_MAGIC, "%px: magic failed, ci: %px\n", ta, ci); |
| DEBUG_ASSERT(ta->iteration_count == 0, "%px: iteration count: %d, type: %d\n", ci, ta->iteration_count, ca_type); |
| |
| if (ta->next) { |
| struct ecm_db_connection_classifier_type_assignment *tan = &ta->next->type_assignment[ca_type]; |
| DEBUG_ASSERT(tan->prev == ci, "Bad list, expecting: %px, got: %px\n", ci, tan->prev); |
| tan->prev = ta->prev; |
| } |
| |
| tal = &ecm_db_connection_classifier_type_assignments[ca_type]; |
| if (ta->prev) { |
| struct ecm_db_connection_classifier_type_assignment *tap = &ta->prev->type_assignment[ca_type]; |
| DEBUG_ASSERT(tap->next == ci, "Bad list, expecting: %px, got: %px\n", ci, tap->next); |
| tap->next = ta->next; |
| } else { |
| /* |
| * Set new head of list |
| */ |
| DEBUG_ASSERT(tal->type_assignments_list == ci, "Bad head, expecting %px, got %px, type: %d\n", ci, tal->type_assignments_list, ca_type); |
| tal->type_assignments_list = ta->next; |
| } |
| ta->next = NULL; |
| ta->prev = NULL; |
| ta->pending_unassign = false; |
| |
| /* |
| * Decrement assignment count |
| */ |
| tal->type_assignment_count--; |
| DEBUG_ASSERT(tal->type_assignment_count >= 0, "Bad type assignment count: %d, type: %d\n", tal->type_assignment_count, ca_type); |
| |
| DEBUG_CLEAR_MAGIC(ta); |
| } |
| #endif |
| |
| /* |
| * _ecm_db_connection_classifier_unassign() |
| * Unassign a classifier and remove the classifier type |
| * |
| * The default classifier cannot be unassigned. |
| */ |
| static inline void _ecm_db_connection_classifier_unassign(struct ecm_db_connection_instance *ci, struct ecm_classifier_instance *cci, ecm_classifier_type_t ca_type) |
| { |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| struct ecm_db_connection_classifier_type_assignment *ta; |
| #endif |
| DEBUG_ASSERT(spin_is_locked(&ecm_db_lock), "%px: lock is not held\n", ci); |
| |
| /* |
| * Clear the assignment. |
| */ |
| ci->assignments_by_type[ca_type] = NULL; |
| |
| /* |
| * Link out of assignments list |
| */ |
| if (cci->ca_prev) { |
| cci->ca_prev->ca_next = cci->ca_next; |
| } else { |
| DEBUG_ASSERT(ci->assignments == cci, "%px: Bad assigmnment list, expecting: %px, got: %px", ci, cci, ci->assignments); |
| ci->assignments = cci->ca_next; |
| } |
| if (cci->ca_next) { |
| cci->ca_next->ca_prev = cci->ca_prev; |
| } |
| cci->ca_next = NULL; |
| cci->ca_prev = NULL; |
| |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| /* |
| * Remove from the classifier type assignment list |
| */ |
| ta = &ci->type_assignment[ca_type]; |
| DEBUG_CHECK_MAGIC(ta, ECM_DB_CLASSIFIER_TYPE_ASSIGNMENT_MAGIC, "%px: magic failed, ci: %px", ta, ci); |
| if (ta->iteration_count > 0) { |
| /* |
| * The list entry is being iterated outside of db lock being held. |
| * We cannot remove this entry since it would mess up iteration. |
| * Set the pending flag to be actioned another time |
| */ |
| ta->pending_unassign = true; |
| return; |
| } |
| |
| /* |
| * Remove the list entry |
| */ |
| DEBUG_INFO("%px: Remove type assignment: %d\n", ci, ca_type); |
| _ecm_db_classifier_type_assignment_remove(ci, ca_type); |
| #endif |
| cci->deref(cci); |
| } |
| |
| /* |
| * ecm_db_connection_deref() |
| * Release reference to connection. Connection is removed from database on final deref and destroyed. |
| */ |
| int ecm_db_connection_deref(struct ecm_db_connection_instance *ci) |
| { |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| ecm_classifier_type_t ca_type; |
| #endif |
| int32_t i; |
| int32_t dir; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| ci->refs--; |
| DEBUG_TRACE("%px: connection deref %d\n", ci, ci->refs); |
| DEBUG_ASSERT(ci->refs >= 0, "%px: ref wrap\n", ci); |
| |
| if (ci->refs > 0) { |
| int refs = ci->refs; |
| spin_unlock_bh(&ecm_db_lock); |
| return refs; |
| } |
| |
| #ifdef ECM_MULTICAST_ENABLE |
| /* |
| * For multicast connections, we need to deref the |
| * associated tuple instance as well |
| */ |
| if (ci->ti) { |
| _ecm_db_multicast_tuple_instance_deref(ci->ti); |
| } |
| #endif |
| /* |
| * Remove from database if inserted |
| */ |
| if (!ci->flags & ECM_DB_CONNECTION_FLAGS_INSERTED) { |
| spin_unlock_bh(&ecm_db_lock); |
| } else { |
| struct ecm_db_listener_instance *li; |
| #ifdef ECM_DB_XREF_ENABLE |
| struct ecm_db_iface_instance *iface[ECM_DB_OBJ_DIR_MAX]; |
| #endif |
| |
| /* |
| * Remove it from the connection hash table |
| */ |
| if (!ci->hash_prev) { |
| DEBUG_ASSERT(ecm_db_connection_table[ci->hash_index] == ci, "%px: hash table bad\n", ci); |
| ecm_db_connection_table[ci->hash_index] = ci->hash_next; |
| } else { |
| ci->hash_prev->hash_next = ci->hash_next; |
| } |
| if (ci->hash_next) { |
| ci->hash_next->hash_prev = ci->hash_prev; |
| } |
| ci->hash_prev = NULL; |
| ci->hash_next = NULL; |
| ecm_db_connection_table_lengths[ci->hash_index]--; |
| DEBUG_ASSERT(ecm_db_connection_table_lengths[ci->hash_index] >= 0, "%px: invalid table len %d\n", ci, ecm_db_connection_table_lengths[ci->hash_index]); |
| |
| /* |
| * Remove it from the connection serial hash table |
| */ |
| if (!ci->serial_hash_prev) { |
| DEBUG_ASSERT(ecm_db_connection_serial_table[ci->serial_hash_index] == ci, "%px: hash table bad\n", ci); |
| ecm_db_connection_serial_table[ci->serial_hash_index] = ci->serial_hash_next; |
| } else { |
| ci->serial_hash_prev->serial_hash_next = ci->serial_hash_next; |
| } |
| if (ci->serial_hash_next) { |
| ci->serial_hash_next->serial_hash_prev = ci->serial_hash_prev; |
| } |
| ci->serial_hash_prev = NULL; |
| ci->serial_hash_next = NULL; |
| ecm_db_connection_serial_table_lengths[ci->serial_hash_index]--; |
| DEBUG_ASSERT(ecm_db_connection_serial_table_lengths[ci->serial_hash_index] >= 0, "%px: invalid table len %d\n", ci, ecm_db_connection_serial_table_lengths[ci->serial_hash_index]); |
| |
| /* |
| * Remove from the global list |
| */ |
| if (!ci->prev) { |
| DEBUG_ASSERT(ecm_db_connections == ci, "%px: conn table bad\n", ci); |
| ecm_db_connections = ci->next; |
| } else { |
| ci->prev->next = ci->next; |
| } |
| if (ci->next) { |
| ci->next->prev = ci->prev; |
| } |
| ci->prev = NULL; |
| ci->next = NULL; |
| |
| #ifdef ECM_DB_XREF_ENABLE |
| /* |
| * Remove connection from the mappings' connection list |
| */ |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| if (!ci->mapping_prev[dir]) { |
| DEBUG_ASSERT(ci->mapping[dir]->connections[dir] == ci, "%px: %s conn table bad\n", ci, ecm_db_obj_dir_strings[dir]); |
| ci->mapping[dir]->connections[dir] = ci->mapping_next[dir]; |
| } else { |
| ci->mapping_prev[dir]->mapping_next[dir] = ci->mapping_next[dir]; |
| } |
| if (ci->mapping_next[dir]) { |
| ci->mapping_next[dir]->mapping_prev[dir] = ci->mapping_prev[dir]; |
| } |
| ci->mapping_prev[dir] = NULL; |
| ci->mapping_next[dir] = NULL; |
| } |
| |
| /* |
| * Remove connection from the ifaces' connection list |
| * GGG TODO Deprecated. Interface lists will be used instead. To be deleted. |
| */ |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| iface[dir] = ci->node[dir]->iface; |
| if (!ci->iface_prev[dir]) { |
| DEBUG_ASSERT(iface[dir]->connections[dir] == ci, |
| "%px: iface %s conn table bad\n", |
| ci, ecm_db_obj_dir_strings[dir]); |
| iface[dir]->connections[dir] = ci->iface_next[dir]; |
| } else { |
| ci->iface_prev[dir]->iface_next[dir] = ci->iface_next[dir]; |
| } |
| if (ci->iface_next[dir]) { |
| ci->iface_next[dir]->iface_prev[dir] = ci->iface_prev[dir]; |
| } |
| ci->iface_prev[dir] = NULL; |
| ci->iface_next[dir] = NULL; |
| } |
| |
| /* |
| * Remove connection from its nodes' connection list |
| */ |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| if (!ci->node_prev[dir]) { |
| DEBUG_ASSERT(ci->node[dir]->connections[dir] == ci, |
| "%px: %s node conn table bad, got: %px\n", |
| ci, ecm_db_obj_dir_strings[dir], ci->node[dir]->connections[dir]); |
| ci->node[dir]->connections[dir] = ci->node_next[dir]; |
| } else { |
| ci->node_prev[dir]->node_next[dir] = ci->node_next[dir]; |
| } |
| if (ci->node_next[dir]) { |
| ci->node_next[dir]->node_prev[dir] = ci->node_prev[dir]; |
| } |
| ci->node_prev[dir] = NULL; |
| ci->node_next[dir] = NULL; |
| ci->node[dir]->connections_count[dir]--; |
| DEBUG_ASSERT(ci->node[dir]->connections_count[dir] >= 0, "%px: %s node bad count\n", ci, ecm_db_obj_dir_strings[dir]); |
| } |
| #endif |
| |
| /* |
| * Update the counters in the mappings |
| */ |
| if (ci->protocol == IPPROTO_UDP) { |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| ci->mapping[dir]->udp_count[dir]--; |
| } |
| } else if (ci->protocol == IPPROTO_TCP) { |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| ci->mapping[dir]->tcp_count[dir]--; |
| } |
| } |
| |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| ci->mapping[dir]->conn_count[dir]--; |
| } |
| |
| /* |
| * Assert that the defunt timer has been detached |
| */ |
| DEBUG_ASSERT(ci->defunct_timer.group == ECM_DB_TIMER_GROUPS_MAX, "%px: unexpected timer group %d\n", ci, ci->defunct_timer.group); |
| |
| /* |
| * Decrement protocol counter stats |
| */ |
| ecm_db_connection_count_by_protocol[ci->protocol]--; |
| DEBUG_ASSERT(ecm_db_connection_count_by_protocol[ci->protocol] >= 0, "%px: Invalid protocol count %d\n", ci, ecm_db_connection_count_by_protocol[ci->protocol]); |
| |
| spin_unlock_bh(&ecm_db_lock); |
| |
| /* |
| * Throw removed event to listeners |
| */ |
| DEBUG_TRACE("%px: Throw connection removed event\n", ci); |
| li = ecm_db_listeners_get_and_ref_first(); |
| while (li) { |
| struct ecm_db_listener_instance *lin; |
| if (li->connection_removed) { |
| li->connection_removed(li->arg, ci); |
| } |
| |
| /* |
| * Get next listener |
| */ |
| lin = ecm_db_listener_get_and_ref_next(li); |
| ecm_db_listener_deref(li); |
| li = lin; |
| } |
| } |
| |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| /* |
| * Unlink from the "assignments by classifier type" lists. |
| * |
| * This is done whether the connection is inserted into the database or not - this is because |
| * classifier assignments take place before adding into the db. |
| * |
| * NOTE: We know that the ci is not being iterated in any of these lists because otherwise |
| * ci would be being held as part of iteration and so we would not be here! |
| * Equally we know that if the assignments_by_type[] element is non-null then it must also be in the relevant list too. |
| * |
| * Default classifier is not in the classifier type assignement list, so we should start the loop index |
| * with the first assigned classifier type. |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| for (ca_type = ECM_CLASSIFIER_TYPE_DEFAULT + 1; ca_type < ECM_CLASSIFIER_TYPES; ++ca_type) { |
| struct ecm_classifier_instance *cci = ci->assignments_by_type[ca_type]; |
| if (!cci) { |
| /* |
| * No assignment of this type, so would not be in the classifier type assignments list |
| */ |
| continue; |
| } |
| _ecm_db_connection_classifier_unassign(ci, cci, ca_type); |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| #endif |
| |
| /* |
| * Throw final event |
| */ |
| if (ci->final) { |
| ci->final(ci->arg); |
| } |
| |
| /* |
| * Release instances to the objects referenced by the connection |
| */ |
| while (ci->assignments) { |
| struct ecm_classifier_instance *classi = ci->assignments; |
| ci->assignments = classi->ca_next; |
| classi->deref(classi); |
| } |
| |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| if (ci->mapping[dir]) { |
| ecm_db_mapping_deref(ci->mapping[dir]); |
| } |
| } |
| |
| if (ci->feci) { |
| ci->feci->deref(ci->feci); |
| } |
| |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| if (ci->node[dir]) { |
| ecm_db_node_deref(ci->node[dir]); |
| } |
| } |
| |
| /* |
| * Remove references to the interfaces in our heirarchy lists |
| */ |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| for (i = ci->interface_first[dir]; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| DEBUG_TRACE("%px: %s interface %d remove: %px\n", ci, ecm_db_obj_dir_strings[dir], i, ci->interfaces[dir][i]); |
| ecm_db_iface_deref(ci->interfaces[dir][i]); |
| } |
| } |
| |
| #ifdef ECM_MULTICAST_ENABLE |
| /* |
| * Remove references to the multicast interfaces of this connection. |
| */ |
| ecm_db_multicast_connection_to_interfaces_clear(ci); |
| #endif |
| /* |
| * We can now destroy the instance |
| */ |
| DEBUG_CLEAR_MAGIC(ci); |
| kfree(ci); |
| |
| /* |
| * Decrease global connection count |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| ecm_db_connection_count--; |
| DEBUG_ASSERT(ecm_db_connection_count >= 0, "%px: connection count wrap\n", ci); |
| spin_unlock_bh(&ecm_db_lock); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_deref); |
| |
| /* |
| * ecm_db_connection_defunct_all() |
| * Make defunct ALL connections. |
| * |
| * This API is typically used in shutdown situations commanded by the user. |
| * NOTE: Ensure all front ends are stopped to avoid further connections being created while this is running. |
| */ |
| void ecm_db_connection_defunct_all(void) |
| { |
| struct ecm_db_connection_instance *ci; |
| |
| DEBUG_INFO("Defuncting all\n"); |
| |
| /* |
| * Iterate all connections |
| */ |
| ci = ecm_db_connections_get_and_ref_first(); |
| while (ci) { |
| struct ecm_db_connection_instance *cin; |
| |
| DEBUG_TRACE("%px: defunct\n", ci); |
| ecm_db_connection_make_defunct(ci); |
| |
| cin = ecm_db_connection_get_and_ref_next(ci); |
| ecm_db_connection_deref(ci); |
| ci = cin; |
| } |
| DEBUG_INFO("Defuncting complete\n"); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_defunct_all); |
| |
| /* |
| * ecm_db_connection_defunct_by_classifier() |
| * Make defunct based on masked fields |
| */ |
| void ecm_db_connection_defunct_by_classifier(int ip_ver, ip_addr_t src_addr_mask, uint16_t src_port_mask, |
| ip_addr_t dest_addr_mask, uint16_t dest_port_mask, |
| int proto_mask, bool is_routed, ecm_classifier_type_t ca_type) |
| { |
| struct ecm_db_connection_instance *ci; |
| int cnt = 0; |
| char *direction = NULL; |
| |
| /* |
| * Iterate all connections |
| */ |
| ci = ecm_db_connections_get_and_ref_first(); |
| while (ci) { |
| struct ecm_db_connection_instance *cin; |
| struct ecm_classifier_instance *eci; |
| ip_addr_t sip; |
| ip_addr_t dip; |
| uint16_t sport, dport; |
| int proto; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed", ci); |
| |
| eci = ecm_db_connection_assigned_classifier_find_and_ref(ci, ca_type); |
| if (!eci) { |
| goto next_ci; |
| } |
| eci->deref(eci); |
| |
| /* |
| * Ignore connection with wrong version |
| */ |
| if (ip_ver != ECM_DB_IP_VERSION_IGNORE && (ecm_db_connection_ip_version_get(ci) != ip_ver)) { |
| goto next_ci; |
| } |
| |
| /* |
| * Skip routed connection for bridge flow |
| * Skip bridge connection for routed flow |
| */ |
| if (is_routed != ecm_db_connection_is_routed_get(ci)) { |
| 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_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 (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 (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_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 (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 (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++; |
| if (ECM_IP_ADDR_IS_V4(src_addr_mask)) { |
| DEBUG_TRACE("%px: Defunct CI masked 5 tuple match(%s) src=" ECM_IP_ADDR_DOT_FMT " sport=%d dest=" |
| ECM_IP_ADDR_DOT_FMT ", dport=%d, proto=%d cnt=%d\n", ci, direction, |
| ECM_IP_ADDR_TO_DOT(sip), sport, ECM_IP_ADDR_TO_DOT(dip), dport, proto, cnt); |
| } else { |
| DEBUG_TRACE("%px: Defunct CI masked 5 tuple match(%s) src=" ECM_IP_ADDR_OCTAL_FMT " sport=%d dest=" |
| ECM_IP_ADDR_OCTAL_FMT ", dport=%d, proto=%d, cnt=%d\n", ci, direction, |
| ECM_IP_ADDR_TO_OCTAL(sip), sport, ECM_IP_ADDR_TO_OCTAL(dip), dport, proto, cnt); |
| } |
| |
| ecm_db_connection_make_defunct(ci); |
| |
| next_ci: |
| cin = ecm_db_connection_get_and_ref_next(ci); |
| ecm_db_connection_deref(ci); |
| ci = cin; |
| } |
| |
| if (ECM_IP_ADDR_IS_V4(src_addr_mask)) { |
| DEBUG_TRACE("%px: Defunct request by masked 5 tuple src_mask=" ECM_IP_ADDR_DOT_FMT " sport_mask=%d, dest_mask=" |
| ECM_IP_ADDR_DOT_FMT " dport_mask=%d, proto_mask=%d, cnt=%d\n", ci, |
| ECM_IP_ADDR_TO_DOT(src_addr_mask), src_port_mask, ECM_IP_ADDR_TO_DOT(dest_addr_mask), |
| dest_port_mask, proto_mask, cnt); |
| } else { |
| DEBUG_TRACE("%px: Defunct request by masked 5 tuple src_mask=" ECM_IP_ADDR_OCTAL_FMT " sport_mask=%d dest_mask=" |
| ECM_IP_ADDR_OCTAL_FMT " dport_mask=%d, proto_mask=%d cnt=%d\n", ci, |
| ECM_IP_ADDR_TO_OCTAL(src_addr_mask), src_port_mask, |
| ECM_IP_ADDR_TO_OCTAL(dest_addr_mask), dest_port_mask, proto_mask, cnt); |
| } |
| } |
| |
| /* |
| * ecm_db_connection_defunct_by_port() |
| * Make defunct based on source or destination port. |
| */ |
| void ecm_db_connection_defunct_by_port(int port, ecm_db_obj_dir_t dir) |
| { |
| struct ecm_db_connection_instance *ci; |
| |
| DEBUG_INFO("Defuncting all matching connections by port %d\n", port); |
| |
| /* |
| * Iterate all connections |
| */ |
| ci = ecm_db_connections_get_and_ref_first(); |
| while (ci) { |
| struct ecm_db_connection_instance *cin; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| /* |
| * Flush the connection matching with given port address and flow direction |
| */ |
| if (port == htons(ecm_db_connection_port_get(ci, dir))) { |
| DEBUG_TRACE("%px: defunct\n", ci); |
| ecm_db_connection_make_defunct(ci); |
| } |
| |
| cin = ecm_db_connection_get_and_ref_next(ci); |
| ecm_db_connection_deref(ci); |
| ci = cin; |
| } |
| DEBUG_INFO("Port based Defuncting complete\n"); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_defunct_by_port); |
| |
| /* |
| * ecm_db_connection_defunct_by_protocol() |
| * Make defunct based on protocol. |
| */ |
| void ecm_db_connection_defunct_by_protocol(int protocol) |
| { |
| struct ecm_db_connection_instance *ci; |
| |
| DEBUG_INFO("Defuncting all matching connections by protocol %d\n", protocol); |
| |
| /* |
| * Iterate all connections |
| */ |
| ci = ecm_db_connections_get_and_ref_first(); |
| while (ci) { |
| struct ecm_db_connection_instance *cin; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| /* |
| * Flush the connection matching with given protocol |
| */ |
| if (protocol == ecm_db_connection_protocol_get(ci)) { |
| DEBUG_TRACE("%px: defunct\n", ci); |
| ecm_db_connection_make_defunct(ci); |
| } |
| |
| cin = ecm_db_connection_get_and_ref_next(ci); |
| ecm_db_connection_deref(ci); |
| ci = cin; |
| } |
| DEBUG_INFO("Protocol based defuncting complete\n"); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_defunct_by_protocol); |
| |
| /* |
| * ecm_db_connection_defunct_ip_version() |
| * Make defunct based on the IP version (IPv4 or IPv6). |
| */ |
| void ecm_db_connection_defunct_ip_version(int ip_version) |
| { |
| struct ecm_db_connection_instance *ci; |
| |
| DEBUG_ASSERT(ip_version == 4 || ip_version == 6, "Wrong ip_version: %d\n", ip_version); |
| |
| DEBUG_INFO("Defuncting IPv%d connections\n", ip_version); |
| |
| /* |
| * Iterate all connections |
| */ |
| ci = ecm_db_connections_get_and_ref_first(); |
| while (ci) { |
| struct ecm_db_connection_instance *cin; |
| |
| if (ci->ip_version == ip_version) { |
| DEBUG_TRACE("%px: defunct\n", ci); |
| ecm_db_connection_make_defunct(ci); |
| } |
| |
| cin = ecm_db_connection_get_and_ref_next(ci); |
| ecm_db_connection_deref(ci); |
| ci = cin; |
| } |
| DEBUG_INFO("Defuncting complete for IPv%d connections\n", ip_version); |
| } |
| |
| /* |
| * ecm_db_connection_generate_hash_index() |
| * Calculate the hash index. |
| * |
| * Note: The hash we produce is symmetric - i.e. we can swap the "from" and "to" |
| * details without generating a different hash index! |
| */ |
| static inline ecm_db_connection_hash_t ecm_db_connection_generate_hash_index(ip_addr_t host1_addr, uint32_t host1_port, ip_addr_t host2_addr, uint32_t host2_port, int protocol) |
| { |
| uint32_t hah1; |
| uint32_t hah2; |
| uint32_t ht1; |
| uint32_t hash_val; |
| |
| /* |
| * The hash function only uses both host 1 address/port, host 2 address/port |
| * and protocol fields. |
| */ |
| ECM_IP_ADDR_HASH(hah1, host1_addr); |
| ECM_IP_ADDR_HASH(hah2, host2_addr); |
| ht1 = (u32)hah1 + host1_port + hah2 + host2_port + (uint32_t)protocol; |
| hash_val = (uint32_t)jhash_1word(ht1, ecm_db_jhash_rnd); |
| return (ecm_db_connection_hash_t)(hash_val & (ECM_DB_CONNECTION_HASH_SLOTS - 1)); |
| } |
| |
| /* |
| * ecm_db_connection_generate_serial_hash_index() |
| * Calculate the serial hash index. |
| */ |
| static inline ecm_db_connection_serial_hash_t ecm_db_connection_generate_serial_hash_index(uint32_t serial) |
| { |
| uint32_t hash_val; |
| hash_val = (uint32_t)jhash_1word(serial, ecm_db_jhash_rnd); |
| |
| return (ecm_db_connection_serial_hash_t)(hash_val & (ECM_DB_CONNECTION_SERIAL_HASH_SLOTS - 1)); |
| } |
| |
| /* |
| * ecm_db_connection_find_and_ref_chain() |
| * Given a hash chain index locate the connection |
| */ |
| static struct ecm_db_connection_instance *ecm_db_connection_find_and_ref_chain(ecm_db_connection_hash_t hash_index, |
| ip_addr_t host1_addr, ip_addr_t host2_addr, |
| int protocol, int host1_port, int host2_port) |
| { |
| struct ecm_db_connection_instance *ci; |
| |
| /* |
| * Iterate the chain looking for a connection with matching details |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| ci = ecm_db_connection_table[hash_index]; |
| while (ci) { |
| /* |
| * The use of unlikely() is liberally used because under fast-hit scenarios the connection would always be at the start of a chain |
| */ |
| if (unlikely(ci->protocol != protocol)) { |
| goto try_next; |
| } |
| |
| if (unlikely(!ECM_IP_ADDR_MATCH(host1_addr, ci->mapping[ECM_DB_OBJ_DIR_FROM]->host->address))) { |
| goto try_reverse; |
| } |
| |
| if (unlikely(host1_port != ci->mapping[ECM_DB_OBJ_DIR_FROM]->port)) { |
| goto try_reverse; |
| } |
| |
| if (unlikely(!ECM_IP_ADDR_MATCH(host2_addr, ci->mapping[ECM_DB_OBJ_DIR_TO]->host->address))) { |
| goto try_reverse; |
| } |
| |
| if (unlikely(host2_port != ci->mapping[ECM_DB_OBJ_DIR_TO]->port)) { |
| goto try_reverse; |
| } |
| |
| goto connection_found; |
| |
| try_reverse: |
| if (unlikely(!ECM_IP_ADDR_MATCH(host1_addr, ci->mapping[ECM_DB_OBJ_DIR_TO]->host->address))) { |
| goto try_next; |
| } |
| |
| if (unlikely(host1_port != ci->mapping[ECM_DB_OBJ_DIR_TO]->port)) { |
| goto try_next; |
| } |
| |
| if (unlikely(!ECM_IP_ADDR_MATCH(host2_addr, ci->mapping[ECM_DB_OBJ_DIR_FROM]->host->address))) { |
| goto try_next; |
| } |
| |
| if (unlikely(host2_port != ci->mapping[ECM_DB_OBJ_DIR_FROM]->port)) { |
| goto try_next; |
| } |
| |
| goto connection_found; |
| |
| try_next: |
| ci = ci->hash_next; |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| DEBUG_TRACE("Connection not found in hash chain\n"); |
| return NULL; |
| |
| connection_found: |
| _ecm_db_connection_ref(ci); |
| spin_unlock_bh(&ecm_db_lock); |
| DEBUG_TRACE("Connection found %px\n", ci); |
| return ci; |
| } |
| |
| /* |
| * ecm_db_connection_find_and_ref() |
| * Locate a connection instance based on addressing, protocol and optional port information. |
| * |
| * NOTE: For non-port based protocols then ports are expected to be -(protocol). |
| */ |
| struct ecm_db_connection_instance *ecm_db_connection_find_and_ref(ip_addr_t host1_addr, ip_addr_t host2_addr, int protocol, int host1_port, int host2_port) |
| { |
| ecm_db_connection_hash_t hash_index; |
| |
| DEBUG_TRACE("Lookup connection " ECM_IP_ADDR_OCTAL_FMT ":%d <> " ECM_IP_ADDR_OCTAL_FMT ":%d protocol %d\n", ECM_IP_ADDR_TO_OCTAL(host1_addr), host1_port, ECM_IP_ADDR_TO_OCTAL(host2_addr), host2_port, protocol); |
| |
| /* |
| * Compute the hash chain index and prepare to walk the chain |
| */ |
| hash_index = ecm_db_connection_generate_hash_index(host1_addr, host1_port, host2_addr, host2_port, protocol); |
| return ecm_db_connection_find_and_ref_chain(hash_index, host1_addr, host2_addr, protocol, host1_port, host2_port); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_find_and_ref); |
| |
| /* |
| * ecm_db_connection_serial_find_and_ref() |
| * Locate a connection instance based on serial if it still exists |
| */ |
| struct ecm_db_connection_instance *ecm_db_connection_serial_find_and_ref(uint32_t serial) |
| { |
| ecm_db_connection_serial_hash_t serial_hash_index; |
| struct ecm_db_connection_instance *ci; |
| |
| DEBUG_TRACE("Lookup connection serial: %u\n", serial); |
| |
| /* |
| * Compute the hash chain index and prepare to walk the chain |
| */ |
| serial_hash_index = ecm_db_connection_generate_serial_hash_index(serial); |
| |
| /* |
| * Iterate the chain looking for a connection with matching serial |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| ci = ecm_db_connection_serial_table[serial_hash_index]; |
| while (ci) { |
| /* |
| * The use of likely() is used because under fast-hit scenarios the connection would always be at the start of a chain |
| */ |
| if (likely(ci->serial == serial)) { |
| _ecm_db_connection_ref(ci); |
| spin_unlock_bh(&ecm_db_lock); |
| DEBUG_TRACE("Connection found %px\n", ci); |
| return ci; |
| } |
| |
| ci = ci->serial_hash_next; |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| DEBUG_TRACE("Connection not found\n"); |
| return NULL; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_serial_find_and_ref); |
| |
| /* |
| * ecm_db_connection_node_get_and_ref() |
| * Return node reference |
| */ |
| struct ecm_db_node_instance *ecm_db_connection_node_get_and_ref(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| struct ecm_db_node_instance *ni; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| ni = ci->node[dir]; |
| _ecm_db_node_ref(ni); |
| spin_unlock_bh(&ecm_db_lock); |
| return ni; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_node_get_and_ref); |
| |
| /* |
| * ecm_db_connection_mapping_get_and_ref_next() |
| * Return reference to next connection in the mapping chain in the specified direction. |
| */ |
| struct ecm_db_connection_instance *ecm_db_connection_mapping_get_and_ref_next(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| struct ecm_db_connection_instance *nci; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| nci = ci->mapping_next[dir]; |
| if (nci) { |
| _ecm_db_connection_ref(nci); |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| |
| return nci; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_mapping_get_and_ref_next); |
| |
| /* |
| * ecm_db_connection_iface_get_and_ref_next() |
| * Return reference to next connection in iface chain in the specified direction. |
| */ |
| struct ecm_db_connection_instance *ecm_db_connection_iface_get_and_ref_next(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| struct ecm_db_connection_instance *nci; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| nci = ci->iface_next[dir]; |
| if (nci) { |
| _ecm_db_connection_ref(nci); |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| |
| return nci; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_iface_get_and_ref_next); |
| |
| /* |
| * ecm_db_connection_mapping_get_and_ref() |
| * Return a reference to the mapping of the connection in the specified direction. |
| */ |
| struct ecm_db_mapping_instance *ecm_db_connection_mapping_get_and_ref(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| struct ecm_db_mapping_instance *mi; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| mi = ci->mapping[dir]; |
| _ecm_db_mapping_ref(mi); |
| spin_unlock_bh(&ecm_db_lock); |
| return mi; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_mapping_get_and_ref); |
| |
| /* |
| * ecm_db_connection_classifier_assign() |
| * Assign a classifier to the connection assigned classifier list. |
| * |
| * This adds the classifier in the ci->assignments list in ascending priority order according to the classifier type. |
| * Only assigned classifiers are in this list, allowing fast retrival of current assignments, avoiding the need to skip over unassigned classifiers. |
| * Because there is only one of each type of classifier the classifier is also recorded in an array, the position in which is its type value. |
| * This allows fast lookup based on type too. |
| * Further, the connection is recorded in the classifier type assignment array too, this permits iterating of all connections that are assigned to a TYPE of classifier. |
| */ |
| void ecm_db_connection_classifier_assign(struct ecm_db_connection_instance *ci, struct ecm_classifier_instance *new_ca) |
| { |
| struct ecm_classifier_instance *ca; |
| struct ecm_classifier_instance *ca_prev; |
| ecm_classifier_type_t new_ca_type; |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| struct ecm_db_connection_classifier_type_assignment *ta; |
| struct ecm_db_connection_classifier_type_assignment_list *tal; |
| #endif |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| /* |
| * Get the type (which is also used as the priority) |
| */ |
| new_ca_type = new_ca->type_get(new_ca); |
| |
| /* |
| * Connection holds ref to the classifier |
| */ |
| new_ca->ref(new_ca); |
| |
| /* |
| * Find place to insert the classifier |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| ca = ci->assignments; |
| ca_prev = NULL; |
| while (ca) { |
| ecm_classifier_type_t ca_type; |
| ca_type = ca->type_get(ca); |
| |
| /* |
| * If new ca is less important that the current assigned classifier insert here |
| */ |
| if (new_ca_type < ca_type) { |
| break; |
| } |
| ca_prev = ca; |
| ca = ca->ca_next; |
| } |
| |
| /* |
| * Insert new_ca before ca and after ca_prev. |
| */ |
| new_ca->ca_prev = ca_prev; |
| if (ca_prev) { |
| ca_prev->ca_next = new_ca; |
| } else { |
| DEBUG_ASSERT(ci->assignments == ca, "%px: Bad assigmnment list, expecting: %px, got: %px\n", ci, ca, ci->assignments); |
| ci->assignments = new_ca; |
| } |
| |
| new_ca->ca_next = ca; |
| if (ca) { |
| ca->ca_prev = new_ca; |
| } |
| |
| /* |
| * Insert based on type too |
| */ |
| DEBUG_ASSERT(ci->assignments_by_type[new_ca_type] == NULL, "%px: Only one of each type: %d may be registered, new: %px, existing, %px\n", |
| ci, new_ca_type, new_ca, ci->assignments_by_type[new_ca_type]); |
| ci->assignments_by_type[new_ca_type] = new_ca; |
| |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| /* |
| * Default classifier will not be added to the classifier type assignment list. |
| * Only assigned classifiers can be added. |
| */ |
| if (new_ca_type == ECM_CLASSIFIER_TYPE_DEFAULT) { |
| spin_unlock_bh(&ecm_db_lock); |
| return; |
| } |
| |
| /* |
| * Add the connection into the type assignment list too. |
| */ |
| ta = &ci->type_assignment[new_ca_type]; |
| if (ta->pending_unassign) { |
| /* |
| * The connection is pending unassignment / removal from list, but since it has been |
| * re-assigned to the same type of classifier we can just clear the flag and avoid the removal. |
| * NOTE: pending_unassign is only ever true if the iteration count is non-zero i.e. iteration is in progress. |
| */ |
| DEBUG_CHECK_MAGIC(ta, ECM_DB_CLASSIFIER_TYPE_ASSIGNMENT_MAGIC, "%px: magic failed, ci: %px", ta, ci); |
| DEBUG_ASSERT(ta->iteration_count != 0, "%px: Bad pending_unassign: type: %d, Iteration count zero\n", ci, new_ca_type); |
| ta->pending_unassign = false; |
| spin_unlock_bh(&ecm_db_lock); |
| return; |
| } |
| |
| /* |
| * iteration_count should be zero as there should not be a duplicate assignment of the same type. |
| * This is because if iteration_count was non-zero then pending_unassign should have been true. |
| */ |
| DEBUG_ASSERT(ta->iteration_count == 0, "%px: Type: %d, Iteration count not zero: %d\n", ci, new_ca_type, ta->iteration_count); |
| |
| /* |
| * Insert the connection into the classifier type assignment list, at the head |
| */ |
| tal = &ecm_db_connection_classifier_type_assignments[new_ca_type]; |
| ta->next = tal->type_assignments_list; |
| ta->prev = NULL; |
| |
| /* |
| * If there is an existing head, it is no longer the head |
| */ |
| if (tal->type_assignments_list) { |
| struct ecm_db_connection_classifier_type_assignment *talh; |
| talh = &tal->type_assignments_list->type_assignment[new_ca_type]; |
| talh->prev = ci; |
| } |
| |
| /* |
| * Set new head |
| */ |
| tal->type_assignments_list = ci; |
| |
| /* |
| * Set magic |
| */ |
| DEBUG_SET_MAGIC(ta, ECM_DB_CLASSIFIER_TYPE_ASSIGNMENT_MAGIC); |
| |
| /* |
| * Increment assignment count |
| */ |
| tal->type_assignment_count++; |
| DEBUG_ASSERT(tal->type_assignment_count > 0, "Bad iteration count: %d\n", tal->type_assignment_count); |
| #endif |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_classifier_assign); |
| |
| /* |
| * ecm_db_connection_classifier_assignments_get_and_ref() |
| * Populate the given array with references to the currently assigned classifiers. |
| * |
| * This function returns the number of assignments starting from [0]. |
| * [0] is the lowest priority classifier, [return_val - 1] is the highest priority. |
| * Release each classifier when you are done, for convenience use ecm_db_connection_assignments_release(). |
| * |
| * NOTE: The array also contains the default classifier too which of course will always be at [0] |
| * |
| * WARNING: The array MUST be of size ECM_CLASSIFIER_TYPES. |
| */ |
| int ecm_db_connection_classifier_assignments_get_and_ref(struct ecm_db_connection_instance *ci, struct ecm_classifier_instance *assignments[]) |
| { |
| int aci_count; |
| struct ecm_classifier_instance *aci; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| aci_count = 0; |
| spin_lock_bh(&ecm_db_lock); |
| aci = ci->assignments; |
| while (aci) { |
| aci->ref(aci); |
| assignments[aci_count++] = aci; |
| aci = aci->ca_next; |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| DEBUG_ASSERT(aci_count >= 1, "%px: Must have at least default classifier!\n", ci); |
| return aci_count; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_classifier_assignments_get_and_ref); |
| |
| /* |
| * ecm_db_connection_assignments_release() |
| * Release references to classifiers in the assignments array |
| */ |
| void ecm_db_connection_assignments_release(int assignment_count, struct ecm_classifier_instance *assignments[]) |
| { |
| int i; |
| for (i = 0; i < assignment_count; ++i) { |
| struct ecm_classifier_instance *aci = assignments[i]; |
| if (aci) { |
| aci->deref(aci); |
| } |
| } |
| } |
| EXPORT_SYMBOL(ecm_db_connection_assignments_release); |
| |
| /* |
| * ecm_db_connection_assigned_classifier_find_and_ref() |
| * Return a ref to classifier of the requested type, if found |
| */ |
| struct ecm_classifier_instance *ecm_db_connection_assigned_classifier_find_and_ref(struct ecm_db_connection_instance *ci, ecm_classifier_type_t type) |
| { |
| struct ecm_classifier_instance *ca; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| spin_lock_bh(&ecm_db_lock); |
| ca = ci->assignments_by_type[type]; |
| if (ca) { |
| ca->ref(ca); |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| return ca; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_assigned_classifier_find_and_ref); |
| |
| /* |
| * ecm_db_connection_classifier_unassign() |
| * Unassign a classifier |
| * |
| * The default classifier cannot be unassigned. |
| */ |
| void ecm_db_connection_classifier_unassign(struct ecm_db_connection_instance *ci, struct ecm_classifier_instance *cci) |
| { |
| ecm_classifier_type_t ca_type; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| /* |
| * Get the type |
| */ |
| ca_type = cci->type_get(cci); |
| DEBUG_ASSERT(ca_type != ECM_CLASSIFIER_TYPE_DEFAULT, "%px: Cannot unassign default", ci); |
| |
| if (ca_type >= ECM_CLASSIFIER_TYPES) { |
| DEBUG_WARN("%px: ca_type: %d is higher than the max classifier type number: %d\n", ci, ca_type, (ECM_CLASSIFIER_TYPES - 1)); |
| return; |
| } |
| |
| DEBUG_TRACE("%px: Unassign type: %d, classifier: %px\n", ci, ca_type, cci); |
| |
| /* |
| * NOTE: It is possible that in SMP this classifier has already been unassigned. |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| if (ci->assignments_by_type[ca_type] == NULL) { |
| spin_unlock_bh(&ecm_db_lock); |
| DEBUG_TRACE("%px: Classifier type: %d already unassigned\n", ci, ca_type); |
| return; |
| } |
| _ecm_db_connection_classifier_unassign(ci, cci, ca_type); |
| spin_unlock_bh(&ecm_db_lock); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_classifier_unassign); |
| |
| #ifdef ECM_DB_CTA_TRACK_ENABLE |
| /* |
| * ecm_db_connection_by_classifier_type_assignment_get_and_ref_first() |
| * Return a reference to the first connection for which a classifier of the given type is associated with |
| * |
| * WARNING: YOU MUST NOT USE ecm_db_connection_deref() to release the references taken using this API. |
| * YOU MUST use ecm_db_connection_by_classifier_type_assignment_deref(), this ensures type assignment list integrity. |
| */ |
| struct ecm_db_connection_instance *ecm_db_connection_by_classifier_type_assignment_get_and_ref_first(ecm_classifier_type_t ca_type) |
| { |
| struct ecm_db_connection_classifier_type_assignment_list *tal; |
| struct ecm_db_connection_instance *ci; |
| |
| DEBUG_ASSERT(ca_type < ECM_CLASSIFIER_TYPES, "Bad type: %d\n", ca_type); |
| |
| DEBUG_TRACE("Get and ref first connection assigned with classifier type: %d\n", ca_type); |
| |
| tal = &ecm_db_connection_classifier_type_assignments[ca_type]; |
| spin_lock_bh(&ecm_db_lock); |
| ci = tal->type_assignments_list; |
| while (ci) { |
| struct ecm_db_connection_classifier_type_assignment *ta; |
| ta = &ci->type_assignment[ca_type]; |
| DEBUG_CHECK_MAGIC(ta, ECM_DB_CLASSIFIER_TYPE_ASSIGNMENT_MAGIC, "%px: magic failed, ci: %px", ta, ci); |
| |
| if (ta->pending_unassign) { |
| DEBUG_TRACE("Skip %px, pending unassign for type: %d\n", ci, ca_type); |
| ci = ta->next; |
| continue; |
| } |
| |
| /* |
| * Take reference to this connection. |
| * NOTE: Hold both the connection and the assignment entry so that when we unlock both the connection |
| * and the type assignment list entry maintains integrity. |
| */ |
| _ecm_db_connection_ref(ci); |
| ta->iteration_count++; |
| DEBUG_ASSERT(ta->iteration_count > 0, "Bad Iteration count: %d for type: %d, connection: %px\n", ta->iteration_count, ca_type, ci); |
| spin_unlock_bh(&ecm_db_lock); |
| return ci; |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| return NULL; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_by_classifier_type_assignment_get_and_ref_first); |
| |
| /* |
| * ecm_db_connection_by_classifier_type_assignment_get_and_ref_next() |
| * Return a reference to the next connection for which a classifier of the given type is associated with. |
| * |
| * WARNING: YOU MUST NOT USE ecm_db_connection_deref() to release the references taken using this API. |
| * YOU MUST use ecm_db_connection_by_classifier_type_assignment_deref(), this ensures type assignment list integrity. |
| */ |
| struct ecm_db_connection_instance *ecm_db_connection_by_classifier_type_assignment_get_and_ref_next(struct ecm_db_connection_instance *ci, ecm_classifier_type_t ca_type) |
| { |
| struct ecm_db_connection_classifier_type_assignment *ta; |
| struct ecm_db_connection_instance *cin; |
| |
| DEBUG_ASSERT(ca_type < ECM_CLASSIFIER_TYPES, "Bad type: %d\n", ca_type); |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| DEBUG_TRACE("Get and ref next connection assigned with classifier type: %d and ci: %px\n", ca_type, ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| ta = &ci->type_assignment[ca_type]; |
| cin = ta->next; |
| while (cin) { |
| struct ecm_db_connection_classifier_type_assignment *tan; |
| |
| tan = &cin->type_assignment[ca_type]; |
| DEBUG_CHECK_MAGIC(tan, ECM_DB_CLASSIFIER_TYPE_ASSIGNMENT_MAGIC, "%px: magic failed, ci: %px", tan, cin); |
| |
| if (tan->pending_unassign) { |
| DEBUG_TRACE("Skip %px, pending unassign for type: %d\n", cin, ca_type); |
| cin = tan->next; |
| continue; |
| } |
| |
| /* |
| * Take reference to this connection. |
| * NOTE: Hold both the connection and the assignment entry so that when we unlock both the connection |
| * and the type assignment list entry maintains integrity. |
| */ |
| _ecm_db_connection_ref(cin); |
| tan->iteration_count++; |
| DEBUG_ASSERT(tan->iteration_count > 0, "Bad Iteration count: %d for type: %d, connection: %px\n", tan->iteration_count, ca_type, cin); |
| spin_unlock_bh(&ecm_db_lock); |
| return cin; |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| return NULL; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_by_classifier_type_assignment_get_and_ref_next); |
| |
| /* |
| * ecm_db_connection_by_classifier_type_assignment_deref() |
| * Release a reference to a connection while iterating a classifier type assignment list |
| */ |
| void ecm_db_connection_by_classifier_type_assignment_deref(struct ecm_db_connection_instance *ci, ecm_classifier_type_t ca_type) |
| { |
| struct ecm_db_connection_classifier_type_assignment_list *tal; |
| struct ecm_db_connection_classifier_type_assignment *ta; |
| |
| DEBUG_ASSERT(ca_type < ECM_CLASSIFIER_TYPES, "Bad type: %d\n", ca_type); |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| tal = &ecm_db_connection_classifier_type_assignments[ca_type]; |
| |
| /* |
| * Drop the iteration count |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| ta = &ci->type_assignment[ca_type]; |
| DEBUG_CHECK_MAGIC(ta, ECM_DB_CLASSIFIER_TYPE_ASSIGNMENT_MAGIC, "%px: magic failed, ci: %px", ta, ci); |
| ta->iteration_count--; |
| DEBUG_ASSERT(ta->iteration_count >= 0, "Bad Iteration count: %d for type: %d, connection: %px\n", ta->iteration_count, ca_type, ci); |
| |
| /* |
| * If there are no more iterations on-going and this is pending unassign then we can remove it from the assignments list |
| */ |
| if (ta->pending_unassign && (ta->iteration_count == 0)) { |
| DEBUG_INFO("%px: Remove type assignment: %d\n", ci, ca_type); |
| _ecm_db_classifier_type_assignment_remove(ci, ca_type); |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| ecm_db_connection_deref(ci); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_by_classifier_type_assignment_deref); |
| |
| /* |
| * ecm_db_connection_make_defunct_by_assignment_type() |
| * Make defunct all connections that are currently assigned to a classifier of the given type |
| */ |
| void ecm_db_connection_make_defunct_by_assignment_type(ecm_classifier_type_t ca_type) |
| { |
| struct ecm_db_connection_instance *ci; |
| |
| DEBUG_INFO("Make defunct all assigned to type: %d\n", ca_type); |
| |
| ci = ecm_db_connection_by_classifier_type_assignment_get_and_ref_first(ca_type); |
| while (ci) { |
| struct ecm_db_connection_instance *cin; |
| |
| DEBUG_TRACE("%px: Make defunct: %d\n", ci, ca_type); |
| ecm_db_connection_make_defunct(ci); |
| |
| cin = ecm_db_connection_by_classifier_type_assignment_get_and_ref_next(ci, ca_type); |
| ecm_db_connection_by_classifier_type_assignment_deref(ci, ca_type); |
| ci = cin; |
| } |
| } |
| EXPORT_SYMBOL(ecm_db_connection_make_defunct_by_assignment_type); |
| |
| /* |
| * ecm_db_connection_regenerate_by_assignment_type() |
| * Cause regeneration all connections that are currently assigned to a classifier of the given type |
| */ |
| void ecm_db_connection_regenerate_by_assignment_type(ecm_classifier_type_t ca_type) |
| { |
| struct ecm_db_connection_instance *ci; |
| |
| DEBUG_INFO("Regenerate all assigned to type: %d\n", ca_type); |
| |
| ci = ecm_db_connection_by_classifier_type_assignment_get_and_ref_first(ca_type); |
| while (ci) { |
| struct ecm_db_connection_instance *cin; |
| |
| DEBUG_TRACE("%px: Re-generate: %d\n", ci, ca_type); |
| ecm_db_connection_regenerate(ci); |
| |
| cin = ecm_db_connection_by_classifier_type_assignment_get_and_ref_next(ci, ca_type); |
| ecm_db_connection_by_classifier_type_assignment_deref(ci, ca_type); |
| ci = cin; |
| } |
| } |
| EXPORT_SYMBOL(ecm_db_connection_regenerate_by_assignment_type); |
| #endif |
| |
| /* |
| * ecm_db_connection_interfaces_get_and_ref() |
| * Return the interface heirarchy in the specified direction which this connection is established. |
| * |
| * 'interfaces' MUST be an array as large as ECM_DB_IFACE_HEIRARCHY_MAX. |
| * Returns either ECM_DB_IFACE_HEIRARCHY_MAX if there are no interfaces / error. |
| * Returns the index into the interfaces[] of the first interface (so "for (i = <ret val>, i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i)" works) |
| * |
| * Each interface is referenced on return, be sure to release them individually or use ecm_db_connection_interfaces_deref() instead. |
| */ |
| int32_t ecm_db_connection_interfaces_get_and_ref(struct ecm_db_connection_instance *ci, |
| struct ecm_db_iface_instance *interfaces[], |
| ecm_db_obj_dir_t dir) |
| { |
| int32_t n; |
| int32_t i; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| n = ci->interface_first[dir]; |
| for (i = n; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| interfaces[i] = ci->interfaces[dir][i]; |
| _ecm_db_iface_ref(interfaces[i]); |
| } |
| spin_unlock_bh(&ecm_db_lock); |
| return n; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_interfaces_get_and_ref); |
| |
| /* |
| * ecm_db_connection_interfaces_deref() |
| * Release all interfaces in the given interfaces heirarchy array. |
| * |
| * 'first' is the number returned by one of the ecm_db_connection_xx_interfaces_get_and_ref(). |
| * You should NOT have released any references to any of the interfaces in the array youself, this releases them all. |
| */ |
| void ecm_db_connection_interfaces_deref(struct ecm_db_iface_instance *interfaces[], int32_t first) |
| { |
| int32_t i; |
| DEBUG_ASSERT((first >= 0) && (first <= ECM_DB_IFACE_HEIRARCHY_MAX), "Bad first: %d\n", first); |
| |
| for (i = first; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| ecm_db_iface_deref(interfaces[i]); |
| } |
| } |
| EXPORT_SYMBOL(ecm_db_connection_interfaces_deref); |
| |
| /* |
| * ecm_db_connection_interfaces_reset() |
| * Reset the interfaces heirarchy in the specified direction with a new set of interfaces |
| * |
| * NOTE: This will mark the list as set even if you specify no list as a replacement. |
| * This is deliberate - it's stating that there is no list :-) |
| */ |
| void ecm_db_connection_interfaces_reset(struct ecm_db_connection_instance *ci, |
| struct ecm_db_iface_instance *interfaces[], |
| int32_t new_first, ecm_db_obj_dir_t dir) |
| { |
| struct ecm_db_iface_instance *old[ECM_DB_IFACE_HEIRARCHY_MAX]; |
| int32_t old_first; |
| int32_t i; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| /* |
| * Iterate the from interface list, removing the old and adding in the new |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| for (i = 0; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| /* |
| * Put any previous interface into the old list |
| */ |
| old[i] = ci->interfaces[dir][i]; |
| ci->interfaces[dir][i] = NULL; |
| if (i < new_first) { |
| continue; |
| } |
| ci->interfaces[dir][i] = interfaces[i]; |
| _ecm_db_iface_ref(ci->interfaces[dir][i]); |
| } |
| |
| /* |
| * Get old first and update to new first |
| */ |
| old_first = ci->interface_first[dir]; |
| ci->interface_first[dir] = new_first; |
| ci->interface_set[dir] = true; |
| spin_unlock_bh(&ecm_db_lock); |
| |
| /* |
| * Release old |
| */ |
| ecm_db_connection_interfaces_deref(old, old_first); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_interfaces_reset); |
| |
| /* |
| * ecm_db_connection_interfaces_get_count() |
| * Return the number of interfaces in the list |
| */ |
| int32_t ecm_db_connection_interfaces_get_count(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| int32_t first; |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| spin_lock_bh(&ecm_db_lock); |
| first = ci->interface_first[dir]; |
| spin_unlock_bh(&ecm_db_lock); |
| return ECM_DB_IFACE_HEIRARCHY_MAX - first; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_interfaces_get_count); |
| |
| /* |
| * ecm_db_connection_interfaces_set_check() |
| * Returns true if the interface list has been set - even if set to an empty list! |
| */ |
| bool ecm_db_connection_interfaces_set_check(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| bool set; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| spin_lock_bh(&ecm_db_lock); |
| set = ci->interface_set[dir]; |
| spin_unlock_bh(&ecm_db_lock); |
| return set; |
| } |
| EXPORT_SYMBOL(ecm_db_connection_interfaces_set_check); |
| |
| /* |
| * ecm_db_connection_interfaces_clear() |
| * Clear down the interfaces list, marking the list as not set |
| */ |
| void ecm_db_connection_interfaces_clear(struct ecm_db_connection_instance *ci, ecm_db_obj_dir_t dir) |
| { |
| struct ecm_db_iface_instance *discard[ECM_DB_IFACE_HEIRARCHY_MAX]; |
| int32_t discard_first; |
| int32_t i; |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| |
| spin_lock_bh(&ecm_db_lock); |
| for (i = ci->interface_first[dir]; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i) { |
| discard[i] = ci->interfaces[dir][i]; |
| } |
| |
| discard_first = ci->interface_first[dir]; |
| ci->interface_set[dir] = false; |
| ci->interface_first[dir] = ECM_DB_IFACE_HEIRARCHY_MAX; |
| spin_unlock_bh(&ecm_db_lock); |
| |
| /* |
| * Release previous |
| */ |
| ecm_db_connection_interfaces_deref(discard, discard_first); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_interfaces_clear); |
| |
| /* |
| * ecm_db_connection_add() |
| * Add the connection into the database. |
| * |
| * NOTE: The parameters are DIRECTIONAL in terms of which mapping established the connection. |
| * NOTE: Dir confirms if this is an egressing or ingressing connection. |
| * This applies to firewalling front ends mostly. If INGRESS then mapping[ECM_DB_OBJ_DIR_FROM] is the WAN side. |
| * If EGRESS then mapping[ECM_DB_OBJ_DIR_TO] is the WAN side. |
| */ |
| void ecm_db_connection_add(struct ecm_db_connection_instance *ci, |
| struct ecm_db_mapping_instance *mapping[], |
| struct ecm_db_node_instance *node[], |
| int ip_version, |
| int protocol, ecm_db_direction_t ecm_dir, |
| ecm_db_connection_final_callback_t final, |
| ecm_db_connection_defunct_callback_t defunct, |
| ecm_db_timer_group_t tg, bool is_routed, |
| void *arg) |
| { |
| ecm_db_connection_hash_t hash_index; |
| ecm_db_connection_serial_hash_t serial_hash_index; |
| struct ecm_db_listener_instance *li; |
| int dir; |
| #ifdef ECM_DB_XREF_ENABLE |
| struct ecm_db_iface_instance *iface[ECM_DB_OBJ_DIR_MAX]; |
| #endif |
| |
| DEBUG_CHECK_MAGIC(ci, ECM_DB_CONNECTION_INSTANCE_MAGIC, "%px: magic failed\n", ci); |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| DEBUG_CHECK_MAGIC(mapping[dir], ECM_DB_MAPPING_INSTANCE_MAGIC, "%px: %s mapping magic failed \n", mapping[dir], ecm_db_obj_dir_strings[dir]); |
| DEBUG_CHECK_MAGIC(node[dir], ECM_DB_NODE_INSTANCE_MAGIC, "%px: %s node magic failed\n", node[dir], ecm_db_obj_dir_strings[dir]); |
| } |
| DEBUG_ASSERT((protocol >= 0) && (protocol <= 255), "%px: invalid protocol number %d\n", ci, protocol); |
| |
| spin_lock_bh(&ecm_db_lock); |
| DEBUG_ASSERT(!(ci->flags & ECM_DB_CONNECTION_FLAGS_INSERTED), "%px: inserted\n", ci); |
| spin_unlock_bh(&ecm_db_lock); |
| |
| /* |
| * Record owner arg and callbacks |
| */ |
| ci->final = final; |
| ci->defunct = defunct; |
| ci->arg = arg; |
| |
| #ifdef ECM_MULTICAST_ENABLE |
| ci->ti = NULL; |
| #endif |
| |
| /* |
| * Ensure default classifier has been assigned this is a must to ensure minimum level of classification |
| */ |
| DEBUG_ASSERT(ci->assignments_by_type[ECM_CLASSIFIER_TYPE_DEFAULT], "%px: No default classifier assigned\n", ci); |
| |
| /* |
| * Connection takes references to the mappings and nodes |
| */ |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| ecm_db_mapping_ref(mapping[dir]); |
| ci->mapping[dir] = mapping[dir]; |
| |
| ecm_db_node_ref(node[dir]); |
| ci->node[dir] = node[dir]; |
| } |
| |
| /* |
| * Set the protocol and routed flag |
| */ |
| ci->ip_version = ip_version; |
| ci->protocol = protocol; |
| ci->is_routed = is_routed; |
| |
| /* |
| * Set direction of connection |
| */ |
| ci->direction = ecm_dir; |
| |
| /* |
| * Identify which hash chain this connection will go into |
| */ |
| hash_index = ecm_db_connection_generate_hash_index(mapping[ECM_DB_OBJ_DIR_FROM]->host->address, |
| mapping[ECM_DB_OBJ_DIR_FROM]->port, |
| mapping[ECM_DB_OBJ_DIR_TO]->host->address, |
| mapping[ECM_DB_OBJ_DIR_TO]->port, protocol); |
| ci->hash_index = hash_index; |
| |
| /* |
| * Identify which serial hash chain this connection will go into |
| */ |
| serial_hash_index = ecm_db_connection_generate_serial_hash_index(ci->serial); |
| ci->serial_hash_index = serial_hash_index; |
| |
| /* |
| * Now we need to lock |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| |
| /* |
| * Increment protocol counter stats |
| */ |
| ecm_db_connection_count_by_protocol[protocol]++; |
| DEBUG_ASSERT(ecm_db_connection_count_by_protocol[protocol] > 0, "%px: Invalid protocol count %d\n", ci, ecm_db_connection_count_by_protocol[protocol]); |
| |
| /* |
| * Set time |
| */ |
| ci->time_added = ecm_db_time; |
| |
| /* |
| * Add connection into the global list |
| */ |
| ci->prev = NULL; |
| ci->next = ecm_db_connections; |
| if (ecm_db_connections) { |
| ecm_db_connections->prev = ci; |
| } |
| ecm_db_connections = ci; |
| |
| /* |
| * Add this connection into the connections hash table |
| */ |
| ci->flags |= ECM_DB_CONNECTION_FLAGS_INSERTED; |
| |
| /* |
| * Insert connection into the connections hash table |
| */ |
| ci->hash_next = ecm_db_connection_table[hash_index]; |
| if (ecm_db_connection_table[hash_index]) { |
| ecm_db_connection_table[hash_index]->hash_prev = ci; |
| } |
| ecm_db_connection_table[hash_index] = ci; |
| ecm_db_connection_table_lengths[hash_index]++; |
| DEBUG_ASSERT(ecm_db_connection_table_lengths[hash_index] > 0, "%px: invalid table len %d\n", ci, ecm_db_connection_table_lengths[hash_index]); |
| |
| /* |
| * Insert connection into the connections serial hash table |
| */ |
| ci->serial_hash_next = ecm_db_connection_serial_table[serial_hash_index]; |
| if (ecm_db_connection_serial_table[serial_hash_index]) { |
| ecm_db_connection_serial_table[serial_hash_index]->serial_hash_prev = ci; |
| } |
| ecm_db_connection_serial_table[serial_hash_index] = ci; |
| ecm_db_connection_serial_table_lengths[serial_hash_index]++; |
| DEBUG_ASSERT(ecm_db_connection_serial_table_lengths[serial_hash_index] > 0, "%px: invalid table len %d\n", ci, ecm_db_connection_serial_table_lengths[serial_hash_index]); |
| |
| #ifdef ECM_DB_XREF_ENABLE |
| /* |
| * Add this connection into the nodes. |
| */ |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| ci->node_prev[dir] = NULL; |
| ci->node_next[dir] = node[dir]->connections[dir]; |
| if (node[dir]->connections[dir]) { |
| node[dir]->connections[dir]->node_prev[dir] = ci; |
| } |
| node[dir]->connections[dir] = ci; |
| node[dir]->connections_count[dir]++; |
| DEBUG_ASSERT(node[dir]->connections_count[dir] > 0, "%px: invalid count for %s node connections\n", ci, ecm_db_obj_dir_strings[dir]); |
| } |
| |
| /* |
| * Add this connection into the mappings |
| */ |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| ci->mapping_prev[dir] = NULL; |
| ci->mapping_next[dir] = mapping[dir]->connections[dir]; |
| if (mapping[dir]->connections[dir]) { |
| mapping[dir]->connections[dir]->mapping_prev[dir] = ci; |
| } |
| mapping[dir]->connections[dir] = ci; |
| } |
| |
| /* |
| * Add this connection into the ifaces list of connections |
| * NOTE: There is no need to ref the iface because it will exist for as long as this connection exists |
| * due to the heirarchy of dependencies being kept by the database. |
| */ |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| iface[dir] = node[dir]->iface; |
| ci->iface_prev[dir] = NULL; |
| ci->iface_next[dir] = iface[dir]->connections[dir]; |
| if (iface[dir]->connections[dir]) { |
| iface[dir]->connections[dir]->iface_prev[dir] = ci; |
| } |
| iface[dir]->connections[dir] = ci; |
| } |
| #endif |
| |
| /* |
| * NOTE: The interface heirarchy lists are deliberately left empty - these are completed |
| * by the front end if it is appropriate to do so. |
| */ |
| |
| /* |
| * Update the counters in the mapping |
| */ |
| if (protocol == IPPROTO_UDP) { |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| mapping[dir]->udp_count[dir]++; |
| } |
| } else if (protocol == IPPROTO_TCP) { |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| mapping[dir]->tcp_count[dir]++; |
| } |
| } |
| |
| for (dir = 0; dir < ECM_DB_OBJ_DIR_MAX; dir++) { |
| mapping[dir]->conn_count[dir]++; |
| } |
| |
| /* |
| * Set the generation number to match global |
| */ |
| ci->generation = ecm_db_connection_generation; |
| |
| spin_unlock_bh(&ecm_db_lock); |
| |
| /* |
| * Throw add event to the listeners |
| */ |
| DEBUG_TRACE("%px: Throw connection added event\n", ci); |
| li = ecm_db_listeners_get_and_ref_first(); |
| while (li) { |
| struct ecm_db_listener_instance *lin; |
| if (li->connection_added) { |
| li->connection_added(li->arg, ci); |
| } |
| |
| /* |
| * Get next listener |
| */ |
| lin = ecm_db_listener_get_and_ref_next(li); |
| ecm_db_listener_deref(li); |
| li = lin; |
| } |
| |
| /* |
| * Set timer group. 'ref' the connection to ensure it persists for the timer. |
| */ |
| ecm_db_connection_ref(ci); |
| ecm_db_timer_group_entry_set(&ci->defunct_timer, tg); |
| } |
| EXPORT_SYMBOL(ecm_db_connection_add); |
| |
| /* |
| * ecm_db_connection_heirarchy_state_get() |
| * Output state for an interface heirarchy list. |
| */ |
| int ecm_db_connection_heirarchy_state_get(struct ecm_state_file_instance *sfi, struct ecm_db_iface_instance *interfaces[], int32_t first_interface) |
| { |
| int result; |
| int count; |
| int i; |
| int j; |
| |
| count = ECM_DB_IFACE_HEIRARCHY_MAX - first_interface; |
| if ((result = ecm_state_write(sfi, "interface_count", "%d", count))) { |
| return result; |
| } |
| |
| /* |
| * Iterate the interface heirarchy list and output the information |
| */ |
| for (i = first_interface, j = 0; i < ECM_DB_IFACE_HEIRARCHY_MAX; ++i, ++j) { |
| struct ecm_db_iface_instance *ii = interfaces[i]; |
| DEBUG_TRACE("Output interface @ %d: %px\n", i, ii); |
| |
| if ((result = ecm_state_prefix_index_add(sfi, j))) { |
| return result; |
| } |
| result = ii->state_get(ii, sfi); |
| if (result) { |
| return result; |
| } |
| if ((result = ecm_state_prefix_remove(sfi))) { |
| return result; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * ecm_db_connection_state_get() |
| * Prepare a connection message |
| */ |
| int ecm_db_connection_state_get(struct ecm_state_file_instance *sfi, struct ecm_db_connection_instance *ci) |
| { |
| int result; |
| long int expires_in; |
| int sport; |
| int sport_nat; |
| char snode_address[ECM_MAC_ADDR_STR_BUFF_SIZE]; |
| char snode_address_nat[ECM_MAC_ADDR_STR_BUFF_SIZE]; |
| char sip_address[ECM_IP_ADDR_STR_BUFF_SIZE]; |
| char sip_address_nat[ECM_IP_ADDR_STR_BUFF_SIZE]; |
| char dnode_address[ECM_MAC_ADDR_STR_BUFF_SIZE]; |
| char dnode_address_nat[ECM_MAC_ADDR_STR_BUFF_SIZE]; |
| int dport; |
| int dport_nat; |
| char dip_address[ECM_IP_ADDR_STR_BUFF_SIZE]; |
| char dip_address_nat[ECM_IP_ADDR_STR_BUFF_SIZE]; |
| ecm_db_direction_t direction; |
| int ip_version; |
| int protocol; |
| bool is_routed; |
| uint32_t regen_success; |
| uint32_t regen_fail; |
| uint16_t regen_required; |
| uint16_t regen_occurances; |
| bool regen_in_progress; |
| uint16_t generation; |
| uint16_t global_generation; |
| uint32_t time_added; |
| uint32_t serial; |
| 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; |
| struct ecm_db_host_instance *hi; |
| struct ecm_db_node_instance *ni; |
| int aci_index; |
| int aci_count; |
| ip_addr_t __attribute__((unused)) group_ip; |
| struct ecm_front_end_connection_instance *feci; |
| struct ecm_classifier_instance *assignments[ECM_CLASSIFIER_TYPES]; |
| int32_t first_interface; |
| struct ecm_db_iface_instance *interfaces[ECM_DB_IFACE_HEIRARCHY_MAX]; |
| |
| DEBUG_TRACE("Prep conn msg for %px\n", ci); |
| |
| /* |
| * Identify expiration |
| */ |
| spin_lock_bh(&ecm_db_lock); |
| if (ci->defunct_timer.group == ECM_DB_TIMER_GROUPS_MAX) { |
| expires_in = -1; |
| } else { |
| expires_in = (long int)(ci->defunct_timer.timeout - ecm_db_time); |
| if (expires_in <= 0) { |
| expires_in = 0; |
| } |
| } |
| |
| regen_success = ci->regen_success; |
| regen_fail = ci->regen_fail; |
| regen_required = ci->regen_required; |
| regen_occurances = ci->regen_occurances; |
| regen_in_progress = ci->regen_in_progress; |
| generation = ci->generation; |
| global_generation = ecm_db_connection_generation; |
| |
| spin_unlock_bh(&ecm_db_lock); |
| |
| /* |
| * Extract information from the connection for inclusion into the message |
| */ |
| sport = ci->mapping[ECM_DB_OBJ_DIR_FROM]->port; |
| sport_nat = ci->mapping[ECM_DB_OBJ_DIR_FROM_NAT]->port; |
| dport = ci->mapping[ECM_DB_OBJ_DIR_TO]->port; |
| dport_nat = ci->mapping[ECM_DB_OBJ_DIR_TO_NAT]->port; |
| |
| hi = ci->mapping[ECM_DB_OBJ_DIR_TO]->host; |
| ecm_ip_addr_to_string(dip_address, hi->address); |
| ni = ci->node[ECM_DB_OBJ_DIR_TO]; |
| snprintf(dnode_address, sizeof(dnode_address), "%pM", ni->address); |
| hi = ci->mapping[ECM_DB_OBJ_DIR_TO_NAT]->host; |
| ecm_ip_addr_to_string(dip_address_nat, hi->address); |
| |
| hi = ci->mapping[ECM_DB_OBJ_DIR_FROM]->host; |
| ecm_ip_addr_to_string(sip_address, hi->address); |
| ni = ci->node[ECM_DB_OBJ_DIR_FROM]; |
| snprintf(snode_address, sizeof(snode_address), "%pM", ni->address); |
| hi = ci->mapping[ECM_DB_OBJ_DIR_FROM_NAT]->host; |
| ecm_ip_addr_to_string(sip_address_nat, hi->address); |
| |
| ni = ci->node[ECM_DB_OBJ_DIR_TO_NAT]; |
| snprintf(dnode_address_nat, sizeof(dnode_address_nat), "%pM", ni->address); |
| |
| ni = ci->node[ECM_DB_OBJ_DIR_FROM_NAT]; |
| snprintf(snode_address_nat, sizeof(snode_address_nat), "%pM", ni->address); |
| |
| direction = ci->direction; |
| ip_version = ci->ip_version; |
| protocol = ci->protocol; |
| is_routed = ci->is_routed; |
| time_added = ci->time_added; |
| serial = ci->serial; |
| ecm_db_connection_data_stats_get(ci, &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); |
| |
| if ((result = ecm_state_prefix_add(sfi, "conn"))) { |
| return result; |
| } |
| if ((result = ecm_state_prefix_index_add(sfi, serial))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "serial", "%u", serial))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "sip_address", "%s", sip_address))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "sip_address_nat", "%s", sip_address_nat))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "sport", "%d", sport))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "sport_nat", "%d", sport_nat))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "snode_address", "%s", snode_address))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "snode_address_nat", "%s", snode_address_nat))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "dip_address", "%s", dip_address))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "dip_address_nat", "%s", dip_address_nat))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "dport", "%d", dport))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "dport_nat", "%d", dport_nat))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "dnode_address", "%s", dnode_address))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "dnode_address_nat", "%s", dnode_address_nat))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "ip_version", "%d", ip_version))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "protocol", "%d", protocol))) { |
| return result; |
| } |
| |
| if (ecm_db_connection_is_pppoe_bridged_get(ci)) { |
| /* |
| * PPPoE session ID is set in sport and dport. |
| */ |
| if ((result = ecm_state_write(sfi, "pppoe_session_id", "%u", sport))) { |
| return result; |
| } |
| } |
| |
| if ((result = ecm_state_write(sfi, "is_routed", "%d", is_routed))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "expires", "%ld", expires_in))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "direction", "%d", direction))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "time_added", "%u", time_added))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_write(sfi, "regen_success", "%u", regen_success))) { |
| return result; |
| } |
| if ((result = ecm_state_write(sfi, "regen_fail", "%u", regen_fail))) { |
| return result; |
| } |
| if ((result = ecm_state_write(sfi, "regen_required", "%u", regen_required))) { |
| return result; |
| } |
| if ((result = ecm_state_write(sfi, "regen_occurances", "%u", regen_occurances))) { |
| return result; |
| } |
| if ((result = ecm_state_write(sfi, "regen_in_progress", "%u", regen_in_progress))) { |
| return result; |
| } |
| if ((result = ecm_state_write(sfi, "generation", "%u/%u", generation, global_generation))) { |
| return result; |
| } |
| |
| /* |
| * NOTE: These advanced stats are not conditional compiled. |
| * Connections always contain these stats |
| */ |
| 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; |
| } |
| |
| if ((result = ecm_state_prefix_add(sfi, "from_interfaces"))) { |
| return result; |
| } |
| first_interface = ecm_db_connection_interfaces_get_and_ref(ci, interfaces, ECM_DB_OBJ_DIR_FROM); |
| result = ecm_db_connection_heirarchy_state_get(sfi, interfaces, first_interface); |
| ecm_db_connection_interfaces_deref(interfaces, first_interface); |
| if (result) { |
| return result; |
| } |
| if ((result = ecm_state_prefix_remove(sfi))) { |
| return result; |
| } |
| |
| #ifdef ECM_MULTICAST_ENABLE |
| ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, group_ip); |
| if (ecm_ip_addr_is_multicast(group_ip)) { |
| if ((result = ecm_state_prefix_add(sfi, "to_mc_interfaces"))) { |
| return result; |
| } |
| |
| if ((result = ecm_db_multicast_to_interfaces_xml_state_get(ci, sfi))) { |
| return result; |
| } |
| |
| if ((result = ecm_state_prefix_remove(sfi))) { |
| return result; |
| } |
| } |
| else { |
| if ((result = ecm_state_prefix_add(sfi, "to_interfaces"))) { |
| return result; |
| } |
| |
| first_interface = ecm_db_connection_interfaces_get_and_ref(ci, interfaces, ECM_DB_OBJ_DIR_TO); |
| result = ecm_db_connection_heirarchy_state_get(sfi, interfaces, first_interface); |
| ecm_db_connection_interfaces_deref(interfaces, first_interface); |
| if (result) { |
| return result; |
| } |
| |
| if (( |