blob: 7baf61363f01814f6ad8ad225058d01aae4ee0f0 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2014-2018, 2020-2021, The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
#include <linux/version.h>
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/icmp.h>
#include <linux/kthread.h>
#include <linux/debugfs.h>
#include <linux/pkt_sched.h>
#include <linux/string.h>
#include <linux/random.h>
#include <net/route.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <asm/unaligned.h>
#include <asm/uaccess.h> /* for put_user */
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_bridge.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_l4proto.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/ipv4/nf_conntrack_ipv4.h>
#include <net/netfilter/ipv4/nf_defrag_ipv4.h>
/*
* Debug output levels
* 0 = OFF
* 1 = ASSERTS / ERRORS
* 2 = 1 + WARN
* 3 = 2 + INFO
* 4 = 3 + TRACE
*/
#define DEBUG_LEVEL ECM_DB_DEBUG_LEVEL
#include "ecm_types.h"
#include "ecm_db_types.h"
#include "ecm_state.h"
#include "ecm_tracker.h"
#include "ecm_classifier.h"
#include "ecm_front_end_types.h"
#include "ecm_classifier_default.h"
#include "ecm_db.h"
/*
* Magic number.
*/
#define ECM_DB_LISTENER_INSTANCE_MAGIC 0x9876
/*
* Listeners
*/
static int ecm_db_listeners_count = 0; /* Number of listeners allocated */
static struct ecm_db_listener_instance *ecm_db_listeners = NULL;
/* Event listeners */
/*
* _ecm_db_listener_ref()
*/
static void _ecm_db_listener_ref(struct ecm_db_listener_instance *li)
{
DEBUG_CHECK_MAGIC(li, ECM_DB_LISTENER_INSTANCE_MAGIC, "%px: magic failed", li);
li->refs++;
DEBUG_ASSERT(li->refs > 0, "%px: ref wrap\n", li);
}
/*
* ecm_db_listener_ref()
*/
void ecm_db_listener_ref(struct ecm_db_listener_instance *li)
{
spin_lock_bh(&ecm_db_lock);
_ecm_db_listener_ref(li);
spin_unlock_bh(&ecm_db_lock);
}
EXPORT_SYMBOL(ecm_db_listener_ref);
/*
* ecm_db_listeners_get_and_ref_first()
* Obtain a ref to the first listener instance, if any
*/
struct ecm_db_listener_instance *ecm_db_listeners_get_and_ref_first(void)
{
struct ecm_db_listener_instance *li;
spin_lock_bh(&ecm_db_lock);
li = ecm_db_listeners;
if (li) {
_ecm_db_listener_ref(li);
}
spin_unlock_bh(&ecm_db_lock);
return li;
}
/*
* ecm_db_listener_get_and_ref_next()
* Return the next listener in the list given a listener
*/
struct ecm_db_listener_instance *ecm_db_listener_get_and_ref_next(struct ecm_db_listener_instance *li)
{
struct ecm_db_listener_instance *lin;
DEBUG_CHECK_MAGIC(li, ECM_DB_LISTENER_INSTANCE_MAGIC, "%px: magic failed", li);
spin_lock_bh(&ecm_db_lock);
lin = li->next;
if (lin) {
_ecm_db_listener_ref(lin);
}
spin_unlock_bh(&ecm_db_lock);
return lin;
}
/*
* ecm_db_listener_deref()
* Release reference to listener.
*
* On final reference release listener shall be removed from the database.
*/
int ecm_db_listener_deref(struct ecm_db_listener_instance *li)
{
struct ecm_db_listener_instance *cli;
struct ecm_db_listener_instance **cli_prev;
DEBUG_CHECK_MAGIC(li, ECM_DB_LISTENER_INSTANCE_MAGIC, "%px: magic failed", li);
spin_lock_bh(&ecm_db_lock);
li->refs--;
DEBUG_ASSERT(li->refs >= 0, "%px: ref wrap\n", li);
if (li->refs > 0) {
int refs;
refs = li->refs;
spin_unlock_bh(&ecm_db_lock);
return refs;
}
/*
* Instance is to be removed and destroyed.
* Link the listener out of the listener list.
*/
cli = ecm_db_listeners;
cli_prev = &ecm_db_listeners;
while (cli) {
if (cli == li) {
*cli_prev = cli->next;
break;
}
cli_prev = &cli->next;
cli = cli->next;
}
DEBUG_ASSERT(cli, "%px: not found\n", li);
spin_unlock_bh(&ecm_db_lock);
/*
* Invoke final callback
*/
if (li->final) {
li->final(li->arg);
}
DEBUG_CLEAR_MAGIC(li);
kfree(li);
/*
* Decrease global listener count
*/
spin_lock_bh(&ecm_db_lock);
ecm_db_listeners_count--;
DEBUG_ASSERT(ecm_db_listeners_count >= 0, "%px: listener count wrap\n", li);
spin_unlock_bh(&ecm_db_lock);
return 0;
}
EXPORT_SYMBOL(ecm_db_listener_deref);
/*
* ecm_db_listener_add()
* Add a listener instance into the database.
*/
void ecm_db_listener_add(struct ecm_db_listener_instance *li,
ecm_db_iface_listener_added_callback_t iface_added,
ecm_db_iface_listener_removed_callback_t iface_removed,
ecm_db_node_listener_added_callback_t node_added,
ecm_db_node_listener_removed_callback_t node_removed,
ecm_db_host_listener_added_callback_t host_added,
ecm_db_host_listener_removed_callback_t host_removed,
ecm_db_mapping_listener_added_callback_t mapping_added,
ecm_db_mapping_listener_removed_callback_t mapping_removed,
ecm_db_connection_listener_added_callback_t connection_added,
ecm_db_connection_listener_removed_callback_t connection_removed,
ecm_db_listener_final_callback_t final,
void *arg)
{
spin_lock_bh(&ecm_db_lock);
DEBUG_CHECK_MAGIC(li, ECM_DB_LISTENER_INSTANCE_MAGIC, "%px: magic failed\n", li);
DEBUG_ASSERT(!(li->flags & ECM_DB_LISTENER_FLAGS_INSERTED), "%px: inserted\n", li);
spin_unlock_bh(&ecm_db_lock);
li->arg = arg;
li->final = final;
li->iface_added = iface_added;
li->iface_removed = iface_removed;
li->node_added = node_added;
li->node_removed = node_removed;
li->host_added = host_added;
li->host_removed = host_removed;
li->mapping_added = mapping_added;
li->mapping_removed = mapping_removed;
li->connection_added = connection_added;
li->connection_removed = connection_removed;
/*
* Add instance into listener list
*/
spin_lock_bh(&ecm_db_lock);
li->flags |= ECM_DB_LISTENER_FLAGS_INSERTED;
li->next = ecm_db_listeners;
ecm_db_listeners = li;
spin_unlock_bh(&ecm_db_lock);
}
EXPORT_SYMBOL(ecm_db_listener_add);
/*
* ecm_db_listener_alloc()
* Allocate a listener instance
*/
struct ecm_db_listener_instance *ecm_db_listener_alloc(void)
{
struct ecm_db_listener_instance *li;
li = (struct ecm_db_listener_instance *)kzalloc(sizeof(struct ecm_db_listener_instance), GFP_ATOMIC | __GFP_NOWARN);
if (!li) {
DEBUG_WARN("Alloc failed\n");
return NULL;
}
li->refs = 1;
DEBUG_SET_MAGIC(li, ECM_DB_LISTENER_INSTANCE_MAGIC);
/*
* Alloc operation must be atomic to ensure thread and module can be held
*/
spin_lock_bh(&ecm_db_lock);
/*
* If the event processing thread is terminating then we cannot create new instances
*/
if (ecm_db_terminate_pending) {
spin_unlock_bh(&ecm_db_lock);
DEBUG_WARN("Thread terminating\n");
kfree(li);
return NULL;
}
ecm_db_listeners_count++;
DEBUG_ASSERT(ecm_db_listeners_count > 0, "%px: listener count wrap\n", li);
spin_unlock_bh(&ecm_db_lock);
DEBUG_TRACE("Listener created %px\n", li);
return li;
}
EXPORT_SYMBOL(ecm_db_listener_alloc);