blob: a57a13e017b45f3bb3c265bfa64f6b719b6d311c [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2014-2016, 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 <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 <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>
#include <hyfi_ecm.h>
#include <hyfi_hash.h>
/*
* Debug output levels
* 0 = OFF
* 1 = ASSERTS / ERRORS
* 2 = 1 + WARN
* 3 = 2 + INFO
* 4 = 3 + TRACE
*/
#define DEBUG_LEVEL ECM_CLASSIFIER_HYFI_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_tracker_udp.h"
#include "ecm_tracker_tcp.h"
#include "ecm_db.h"
#include "ecm_classifier_hyfi.h"
#include "ecm_front_end_ipv4.h"
#include "ecm_interface.h"
/*
* Magic numbers
*/
#define ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC 0xFE25
/*
* Definitions
*/
#define ECM_CLASSIFIER_HYFI_STATE_INIT ( 1 << 0 )
#define ECM_CLASSIFIER_HYFI_STATE_REGISTERED ( 1 << 1 )
#define ECM_CLASSIFIER_HYFI_STATE_IGNORE ( 1 << 2 )
/*
* struct ecm_classifier_hyfi_instance
* State to allow tracking of dynamic qos for a connection
*/
struct ecm_classifier_hyfi_instance {
struct ecm_classifier_instance base; /* Base type */
struct ecm_classifier_hyfi_instance *next; /* Next classifier state instance (for accouting and reporting purposes) */
struct ecm_classifier_hyfi_instance *prev; /* Next classifier state instance (for accouting and reporting purposes) */
uint32_t ci_serial; /* RO: Serial of the connection */
struct ecm_classifier_process_response process_response;/* Last process response computed */
uint32_t hyfi_state;
struct hyfi_ecm_flow_data_t flow;
int refs; /* Integer to trap we never go negative */
#if (DEBUG_LEVEL > 0)
uint16_t magic;
#endif
bool multi_bridge_flow;
char bridge_name[IFNAMSIZ]; /* Destination bridge device of this hyfi instance */
};
/*
* Listener for db events
*/
struct ecm_db_listener_instance *ecm_classifier_hyfi_li = NULL;
/*
* Operational control - defaults to false (disabled)
*/
static int ecm_classifier_hyfi_enabled; /* Operational behaviour */
/*
* Management thread control
*/
static bool ecm_classifier_hyfi_terminate_pending = false; /* True when the user wants us to terminate */
/*
* Debugfs dentry object.
*/
static struct dentry *ecm_classifier_hyfi_dentry;
/*
* Locking of the classifier structures
*/
static DEFINE_SPINLOCK(ecm_classifier_hyfi_lock); /* Protect SMP access. */
/*
* List of our classifier instances
*/
static struct ecm_classifier_hyfi_instance *ecm_classifier_hyfi_instances = NULL;
/* list of all active instances */
static int ecm_classifier_hyfi_count = 0; /* Tracks number of instances allocated */
/*
* ecm_classifier_hyfi_ref()
* Ref
*/
static void ecm_classifier_hyfi_ref(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)ci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed\n", chfi);
spin_lock_bh(&ecm_classifier_hyfi_lock);
chfi->refs++;
DEBUG_TRACE("%px: chfi ref %d\n", chfi, chfi->refs);
DEBUG_ASSERT(chfi->refs > 0, "%px: ref wrap\n", chfi);
spin_unlock_bh(&ecm_classifier_hyfi_lock);
}
/*
* ecm_classifier_hyfi_deref()
* Deref
*/
static int ecm_classifier_hyfi_deref(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)ci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed\n", chfi);
spin_lock_bh(&ecm_classifier_hyfi_lock);
chfi->refs--;
DEBUG_ASSERT(chfi->refs >= 0, "%px: refs wrapped\n", chfi);
DEBUG_TRACE("%px: HyFi classifier deref %d\n", chfi, chfi->refs);
if (chfi->refs) {
int refs = chfi->refs;
spin_unlock_bh(&ecm_classifier_hyfi_lock);
return refs;
}
/*
* Object to be destroyed
*/
ecm_classifier_hyfi_count--;
DEBUG_ASSERT(ecm_classifier_hyfi_count >= 0, "%px: ecm_classifier_hyfi_count wrap\n", chfi);
/*
* UnLink the instance from our list
*/
if (chfi->next) {
chfi->next->prev = chfi->prev;
}
if (chfi->prev) {
chfi->prev->next = chfi->next;
} else {
DEBUG_ASSERT(ecm_classifier_hyfi_instances == chfi, "%px: list bad %px\n", chfi, ecm_classifier_hyfi_instances);
ecm_classifier_hyfi_instances = chfi->next;
}
chfi->next = NULL;
chfi->prev = NULL;
spin_unlock_bh(&ecm_classifier_hyfi_lock);
/*
* Final
*/
DEBUG_INFO("%px: Final HyFi classifier instance\n", chfi);
kfree(chfi);
return 0;
}
/*
* ecm_classifier_hyfi_process()
* Process new data for connection
*/
static void ecm_classifier_hyfi_process(struct ecm_classifier_instance *aci, ecm_tracker_sender_type_t sender,
struct ecm_tracker_ip_header *ip_hdr, struct sk_buff *skb,
struct ecm_classifier_process_response *process_response)
{
struct ecm_classifier_hyfi_instance *chfi;
ecm_classifier_relevence_t relevance;
bool enabled;
struct ecm_db_connection_instance *ci;
struct ecm_front_end_connection_instance *feci;
ecm_front_end_acceleration_mode_t accel_mode;
uint32_t became_relevant = 0;
uint32_t flag = 0;
chfi = (struct ecm_classifier_hyfi_instance *)aci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed\n", chfi);
/*
* Are we yet to decide if this instance is relevant to the connection?
*/
spin_lock_bh(&ecm_classifier_hyfi_lock);
relevance = chfi->process_response.relevance;
if (relevance != ECM_CLASSIFIER_RELEVANCE_MAYBE) {
/*
* We already know
* NOTE: Lock still held
*/
goto hyfi_classifier_out;
}
enabled = ecm_classifier_hyfi_enabled;
spin_unlock_bh(&ecm_classifier_hyfi_lock);
/*
* Need to decide our relevance to this connection.
* If classifier is enabled and the front end says it can accel then we are "relevant".
* Any other condition and we are not and will stop analysing this connection.
*/
relevance = ECM_CLASSIFIER_RELEVANCE_NO;
ci = ecm_db_connection_serial_find_and_ref(chfi->ci_serial);
if (!ci) {
DEBUG_TRACE("%px: No ci found for %u\n", chfi, chfi->ci_serial);
accel_mode = ECM_FRONT_END_ACCELERATION_MODE_FAIL_DENIED;
} else {
feci = ecm_db_connection_front_end_get_and_ref(ci);
accel_mode = feci->accel_state_get(feci);
feci->deref(feci);
ecm_db_connection_deref(ci);
}
if (enabled && ECM_FRONT_END_ACCELERATION_POSSIBLE(accel_mode) &&
hyfi_ecm_bridge_attached(chfi->bridge_name)) {
relevance = ECM_CLASSIFIER_RELEVANCE_YES;
became_relevant = ecm_db_time_get();
}
spin_lock_bh(&ecm_classifier_hyfi_lock);
chfi->process_response.relevance = relevance;
chfi->process_response.became_relevant = became_relevant;
hyfi_classifier_out:
;
/*
* Return our process response
*/
*process_response = chfi->process_response;
if (relevance == ECM_CLASSIFIER_RELEVANCE_NO) {
goto hyfi_classifier_done;
}
/*
* Fast path, already accelerated or ignored
*/
if (chfi->hyfi_state & (ECM_CLASSIFIER_HYFI_STATE_REGISTERED | ECM_CLASSIFIER_HYFI_STATE_IGNORE)) {
if (chfi->hyfi_state & ECM_CLASSIFIER_HYFI_STATE_REGISTERED) {
DEBUG_INFO("%px: Regen of Flow serial: %d Flow hash: 0x%02x (@%lu)\n",
aci, chfi->flow.ecm_serial, chfi->flow.hash, jiffies);
}
goto hyfi_classifier_done;
}
/*
* Compute the hashes in both forward and reverse directions
*/
if (unlikely(hyfi_hash_skbuf(skb, &chfi->flow.hash, &flag,
&chfi->flow.priority, &chfi->flow.seq)))
goto hyfi_classifier_done;
if (unlikely(hyfi_hash_skbuf_reverse(skb, &chfi->flow.reverse_hash)))
goto hyfi_classifier_done;
chfi->flow.ecm_serial = chfi->ci_serial;
if (flag & ECM_HYFI_IS_IPPROTO_UDP)
hyfi_ecm_set_flag(&chfi->flow, ECM_HYFI_IS_IPPROTO_UDP);
else
hyfi_ecm_clear_flag(&chfi->flow, ECM_HYFI_IS_IPPROTO_UDP);
memcpy(&chfi->flow.sa, eth_hdr(skb)->h_source, ETH_ALEN);
memcpy(&chfi->flow.da, eth_hdr(skb)->h_dest, ETH_ALEN);
DEBUG_INFO("%px: Flow serial: %d\nFlow hash: 0x%02x, priority 0x%08x, "
"flag: %d\nSA: %pM\nDA: %pM (@%lu)\n\n",
aci, chfi->flow.ecm_serial, chfi->flow.hash,
chfi->flow.priority, chfi->flow.flag,
chfi->flow.sa, chfi->flow.da, jiffies);
chfi->hyfi_state = ECM_CLASSIFIER_HYFI_STATE_REGISTERED;
hyfi_classifier_done:
;
spin_unlock_bh(&ecm_classifier_hyfi_lock);
}
/*
* ecm_classifier_hyfi_get_intf_id()
* Get the interface ID from the level of the
* connection hierarchy that matches the interface selected by HyFi.
*
* If the hierarchy contains a bridge, the correct interface will be in the level
* of the hierarchy after the bridge.
* If the hierarchy doesn't contain a bridge, look for an interface that has its
* master set to the HyFi bridge.
*
* If neither of these conditions hold, this hierarchy can't be relevant to HyFi,
* and return -1.
*/
static int32_t ecm_classifier_hyfi_get_intf_id(struct ecm_db_connection_instance *ci,
ecm_db_obj_dir_t dir)
{
int32_t intf_first;
int32_t system_index = -1;
struct ecm_db_iface_instance *interfaces[ECM_DB_IFACE_HEIRARCHY_MAX];
int32_t i;
char name[IFNAMSIZ];
intf_first = ecm_db_connection_interfaces_get_and_ref(ci, interfaces, dir);
if (intf_first == ECM_DB_IFACE_HEIRARCHY_MAX) {
DEBUG_WARN("%px: Error fetching interfaces\n", ci);
return -1;
}
/*
* Search from innermost to outermost interface
*/
for (i = ECM_DB_IFACE_HEIRARCHY_MAX - 1; i >= intf_first; i--) {
int32_t index;
if (ecm_db_iface_type_get(interfaces[i]) == ECM_DB_IFACE_TYPE_BRIDGE) {
/*
* Found the bridge - next interface should be the one we want
*/
if (i <= intf_first) {
DEBUG_WARN("%px: Found bridge at position %d, but first interface "
"is %d, can't fetch HyFi relevant interface\n", ci,
i, intf_first);
break;
}
index = ecm_db_iface_interface_identifier_get(interfaces[i-1]);
if (!hyfi_ecm_is_port_on_hyfi_bridge(index)) {
DEBUG_TRACE("%px: Found non-Hy-Fi bridge, ignoring\n", ci);
break;
}
ecm_db_iface_interface_name_get(interfaces[i-1], &name[0]);
DEBUG_TRACE("%px: Found bridge: '%s' interface is %d (%s)\n", ci,
ecm_db_obj_dir_strings[dir], index, name);
system_index = index;
break;
}
/*
* Bridge not found yet - check if this interface belongs to the bridge
*/
index = ecm_db_iface_interface_identifier_get(interfaces[i]);
if (hyfi_ecm_is_port_on_hyfi_bridge(index)) {
ecm_db_iface_interface_name_get(interfaces[i], &name[0]);
DEBUG_TRACE("%px: Found bridge port: '%s' interface is %d (%s)\n", ci,
ecm_db_obj_dir_strings[dir], index,
name);
system_index = index;
break;
}
/*
* Match not found - keep searching
*/
}
ecm_db_connection_interfaces_deref(interfaces, intf_first);
return system_index;
}
/* ecm_classifier_hyfi_get_bridge_name()
* Get the bridge name from the connection hierarchy
*/
static void ecm_classifier_hyfi_get_bridge_name(struct ecm_db_connection_instance *ci,
char *name_buffer, ecm_db_obj_dir_t dir)
{
int32_t intf_first;
struct ecm_db_iface_instance *interfaces[ECM_DB_IFACE_HEIRARCHY_MAX];
int32_t i;
intf_first = ecm_db_connection_interfaces_get_and_ref(ci, interfaces, dir);
if (intf_first == ECM_DB_IFACE_HEIRARCHY_MAX) {
DEBUG_WARN("%px: Error fetching bridge name\n", ci);
return;
}
for (i = intf_first; i < ECM_DB_IFACE_HEIRARCHY_MAX; i++) {
struct net_device *dev;
dev = dev_get_by_index(&init_net, ecm_db_iface_interface_identifier_get(interfaces[i]));
if (dev && ecm_front_end_is_bridge_port(dev)) {
struct net_device *master;
master = ecm_interface_get_and_hold_dev_master(dev);
if (master) {
strlcpy(name_buffer, master->name, IFNAMSIZ);
dev_put(master);
dev_put(dev);
break;
}
}
if (dev)
dev_put(dev);
}
ecm_db_connection_interfaces_deref(interfaces, intf_first);
}
/*
* ecm_classifier_hyfi_sync_to_v4()
* Front end is pushing accel engine state to us
*/
static void ecm_classifier_hyfi_sync_to_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
{
struct ecm_classifier_hyfi_instance *chfi;
struct ecm_db_connection_instance *ci;
uint64_t from_packets = 0;
uint64_t from_bytes = 0;
uint64_t to_packets = 0;
uint64_t to_bytes = 0;
uint64_t from_packets_dropped = 0;
uint64_t from_bytes_dropped = 0;
uint64_t to_packets_dropped = 0;
uint64_t to_bytes_dropped = 0;
int32_t ret_fwd, ret_rev;
uint32_t time_now;
uint32_t time_elapsed_fwd = 0;
uint32_t time_elapsed_rev = 0;
uint8_t flow_dest_addr[ETH_ALEN];
uint64_t fwd_bytes, rev_bytes, fwd_packets, rev_packets;
bool should_keep_on_fdb_update_fwd = false;
bool should_keep_on_fdb_update_rev = false;
int32_t to_system_index;
int32_t from_system_index;
if (sync->reason != ECM_FRONT_END_IPV4_RULE_SYNC_REASON_STATS) {
DEBUG_TRACE("%px: Update not due to stats: %d\n",
aci, sync->reason);
return;
}
chfi = (struct ecm_classifier_hyfi_instance *)aci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed", chfi);
if (chfi->hyfi_state &
(ECM_CLASSIFIER_HYFI_STATE_IGNORE)) {
return;
}
ci = ecm_db_connection_serial_find_and_ref(chfi->ci_serial);
if (!ci) {
DEBUG_TRACE("%px: No ci found for %u\n", chfi, chfi->ci_serial);
return;
}
/*
* Update the stats on Hy-Fi side
*/
to_system_index = ecm_classifier_hyfi_get_intf_id(ci, ECM_DB_OBJ_DIR_TO);
from_system_index = ecm_classifier_hyfi_get_intf_id(ci, ECM_DB_OBJ_DIR_FROM);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, &flow_dest_addr[0]);
ecm_db_connection_data_stats_get(ci, &from_bytes, &to_bytes,
&from_packets, &to_packets,
&from_bytes_dropped, &to_bytes_dropped,
&from_packets_dropped, &to_packets_dropped);
DEBUG_INFO("UPDATE STATS: Flow serial: %d\npriority 0x%08x, flag: %d\nSA: %pM\nDA: %pM\n\n",
chfi->flow.ecm_serial, chfi->flow.priority, chfi->flow.flag,
chfi->flow.sa, chfi->flow.da);
time_now = jiffies;
/*
* Make sure destination address still matches
*/
if (memcmp(&flow_dest_addr[0], &chfi->flow.da[0], ETH_ALEN)) {
DEBUG_INFO("UPDATE STATS: Direction of flow is "
"reverse of expectation\n");
fwd_bytes = (to_bytes - to_bytes_dropped);
fwd_packets = (to_packets - to_packets_dropped);
rev_bytes = (from_bytes - from_bytes_dropped);
rev_packets = (from_packets - from_packets_dropped);
} else {
fwd_bytes = (from_bytes - from_bytes_dropped);
fwd_packets = (from_packets - from_packets_dropped);
rev_bytes = (to_bytes - to_bytes_dropped);
rev_packets = (to_packets - to_packets_dropped);
}
/*
* Update forward direction 'from' bytes
*/
ret_fwd = hyfi_ecm_update_stats(&chfi->flow, chfi->flow.hash,
&chfi->flow.da[0], &chfi->flow.sa[0],
fwd_bytes, fwd_packets, time_now,
&should_keep_on_fdb_update_fwd,
&time_elapsed_fwd, chfi->bridge_name);
DEBUG_INFO("ret_fwd %d Forward hash: 0x%02x, from_bytes = %lld, from_packets = %lld, "
"num_bytes_dropped = %lld, num_packets_dropped = %lld\n",
ret_fwd, chfi->flow.hash, from_bytes, from_packets,
from_bytes_dropped, from_packets_dropped);
/*
* Update reverse direction 'to' bytes
*/
ret_rev = hyfi_ecm_update_stats(&chfi->flow, chfi->flow.reverse_hash,
&chfi->flow.sa[0], &chfi->flow.da[0],
rev_bytes, rev_packets, time_now,
&should_keep_on_fdb_update_rev,
&time_elapsed_rev, chfi->bridge_name);
DEBUG_INFO("ret_rev %d Reverse hash: 0x%02x, to_bytes = %lld, to_packets = %lld, "
"num_bytes_dropped = %lld, num_packets_dropped = %lld\n",
ret_rev, chfi->flow.reverse_hash, to_bytes, to_packets,
to_bytes_dropped, to_packets_dropped);
chfi->flow.last_update = time_now;
chfi->flow.last_elapsed_time = time_elapsed_fwd >= time_elapsed_rev ?
time_elapsed_fwd : time_elapsed_rev;
if (!time_elapsed_fwd) {
if (should_keep_on_fdb_update_fwd) {
hyfi_ecm_set_flag(&chfi->flow,
ECM_HYFI_SHOULD_KEEP_ON_FDB_UPDATE_FWD);
} else {
hyfi_ecm_clear_flag(&chfi->flow,
ECM_HYFI_SHOULD_KEEP_ON_FDB_UPDATE_FWD);
}
}
if (!time_elapsed_rev) {
if (should_keep_on_fdb_update_rev) {
hyfi_ecm_set_flag(&chfi->flow,
ECM_HYFI_SHOULD_KEEP_ON_FDB_UPDATE_REV);
} else {
hyfi_ecm_clear_flag(&chfi->flow,
ECM_HYFI_SHOULD_KEEP_ON_FDB_UPDATE_REV);
}
}
if (!hyfi_ecm_port_matches(&chfi->flow, to_system_index,
from_system_index, chfi->bridge_name)) {
struct ecm_front_end_connection_instance *feci;
unsigned long start_time, end_time;
feci = ecm_db_connection_front_end_get_and_ref(ci);
start_time = feci->stats.cmd_time_begun;
end_time = feci->stats.cmd_time_completed;
if (chfi->flow.cmd_time_begun != start_time &&
chfi->flow.cmd_time_completed != end_time) {
/*
* Only do another regenerate operation if the timestamps have
* changed (indicating that the front-end has completed the
* previously requested operation)
*/
feci->regenerate(feci, ci);
DEBUG_INFO("%px: Mismatch for %u egress port between HyFi and "
"ECM,\nregenerate start %lu, end %lu, "
"previous start %lu, end %lu (@%u)\n",
ci, chfi->flow.ecm_serial, start_time, end_time,
chfi->flow.cmd_time_begun, chfi->flow.cmd_time_completed,
time_now);
chfi->flow.cmd_time_begun = start_time;
chfi->flow.cmd_time_completed = end_time;
} else {
DEBUG_INFO("%px: Mismatch for %u egress port between HyFi and "
"ECM,\nbut previous regeneration in progress start "
" %lu, end %lu (@%u)\n",
ci, chfi->flow.ecm_serial, start_time, end_time, time_now);
}
feci->deref(feci);
}
ecm_db_connection_deref(ci);
if (ret_fwd < 0 || ret_rev < 0) {
DEBUG_ERROR_RATELIMITED("%px: Error updating stats", aci);
return;
}
/*
* Only need to be interested in forward or reverse direction
*/
if (ret_fwd == 0 || ret_rev == 0) {
chfi->hyfi_state = ECM_CLASSIFIER_HYFI_STATE_REGISTERED;
} else if (ret_fwd == 2 || ret_rev == 2) {
/*
* Not attached, may be interested in the future
*/
chfi->hyfi_state = ECM_CLASSIFIER_HYFI_STATE_INIT;
} else {
/*
* Not interested
*/
chfi->hyfi_state = ECM_CLASSIFIER_HYFI_STATE_IGNORE;
}
}
/*
* ecm_classifier_hyfi_sync_from_v4()
* Front end is retrieving accel engine state from us
*/
static void ecm_classifier_hyfi_sync_from_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)aci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed", chfi);
}
/*
* ecm_classifier_hyfi_sync_to_v6()
* Front end is pushing accel engine state to us
*/
static void ecm_classifier_hyfi_sync_to_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
{
/* Same processing as v4 */
ecm_classifier_hyfi_sync_to_v4(aci, sync);
}
/*
* ecm_classifier_hyfi_sync_from_v6()
* Front end is retrieving accel engine state from us
*/
static void ecm_classifier_hyfi_sync_from_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)aci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed", chfi);
}
/*
* ecm_classifier_hyfi_type_get()
* Get type of classifier this is
*/
static ecm_classifier_type_t ecm_classifier_hyfi_type_get(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)ci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed\n", chfi);
return ECM_CLASSIFIER_TYPE_HYFI;
}
/*
* ecm_classifier_hyfi_last_process_response_get()
* Get result code returned by the last process call
*/
static void ecm_classifier_hyfi_last_process_response_get(struct ecm_classifier_instance *ci,
struct ecm_classifier_process_response *process_response)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)ci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed\n", chfi);
spin_lock_bh(&ecm_classifier_hyfi_lock);
*process_response = chfi->process_response;
spin_unlock_bh(&ecm_classifier_hyfi_lock);
}
/*
* ecm_classifier_hyfi_reclassify_allowed()
* Indicate if reclassify is allowed
*/
static bool ecm_classifier_hyfi_reclassify_allowed(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)ci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed\n", chfi);
return true;
}
/*
* ecm_classifier_hyfi_reclassify()
* Reclassify
*/
static void ecm_classifier_hyfi_reclassify(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)ci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed\n", chfi);
}
#ifdef ECM_STATE_OUTPUT_ENABLE
/*
* ecm_classifier_hyfi_state_get()
* Return state
*/
static int ecm_classifier_hyfi_state_get(struct ecm_classifier_instance *ci, struct ecm_state_file_instance *sfi)
{
int result;
struct ecm_classifier_hyfi_instance *chfi;
struct ecm_classifier_process_response process_response;
chfi = (struct ecm_classifier_hyfi_instance *)ci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC, "%px: magic failed", chfi);
if ((result = ecm_state_prefix_add(sfi, "hyfi"))) {
return result;
}
spin_lock_bh(&ecm_classifier_hyfi_lock);
process_response = chfi->process_response;
spin_unlock_bh(&ecm_classifier_hyfi_lock);
/*
* Output our last process response
*/
if ((result = ecm_classifier_process_response_state_get(sfi, &process_response))) {
return result;
}
return ecm_state_prefix_remove(sfi);
}
#endif
static bool ecm_classifier_hyfi_should_keep_connection(
struct ecm_classifier_instance *aci, uint8_t *mac)
{
struct ecm_classifier_hyfi_instance *chfi;
chfi = (struct ecm_classifier_hyfi_instance *)aci;
DEBUG_CHECK_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC,
"%px: magic failed", chfi);
if (chfi->hyfi_state &
(ECM_CLASSIFIER_HYFI_STATE_IGNORE)) {
/* HyFi doesn't care if connection deleted */
return false;
}
return hyfi_ecm_should_keep(&chfi->flow, mac, chfi->bridge_name);
}
/*
* ecm_classifier_hyfi_instance_alloc()
* Allocate an instance of the HyFi classifier
*/
struct ecm_classifier_hyfi_instance *ecm_classifier_hyfi_instance_alloc(struct ecm_db_connection_instance *ci)
{
struct ecm_classifier_hyfi_instance *chfi;
char to_bridge[IFNAMSIZ];
char from_bridge[IFNAMSIZ];
/*
* Allocate the instance
*/
chfi = (struct ecm_classifier_hyfi_instance *)kzalloc(sizeof(struct ecm_classifier_hyfi_instance), GFP_ATOMIC | __GFP_NOWARN);
if (!chfi) {
DEBUG_WARN("Failed to allocate HyFi instance\n");
return NULL;
}
DEBUG_SET_MAGIC(chfi, ECM_CLASSIFIER_HYFI_INSTANCE_MAGIC);
chfi->refs = 1;
chfi->base.process = ecm_classifier_hyfi_process;
chfi->base.sync_from_v4 = ecm_classifier_hyfi_sync_from_v4;
chfi->base.sync_to_v4 = ecm_classifier_hyfi_sync_to_v4;
chfi->base.sync_from_v6 = ecm_classifier_hyfi_sync_from_v6;
chfi->base.sync_to_v6 = ecm_classifier_hyfi_sync_to_v6;
chfi->base.type_get = ecm_classifier_hyfi_type_get;
chfi->base.last_process_response_get = ecm_classifier_hyfi_last_process_response_get;
chfi->base.reclassify_allowed = ecm_classifier_hyfi_reclassify_allowed;
chfi->base.reclassify = ecm_classifier_hyfi_reclassify;
chfi->base.should_keep_connection =
ecm_classifier_hyfi_should_keep_connection;
#ifdef ECM_STATE_OUTPUT_ENABLE
chfi->base.state_get = ecm_classifier_hyfi_state_get;
#endif
chfi->base.ref = ecm_classifier_hyfi_ref;
chfi->base.deref = ecm_classifier_hyfi_deref;
chfi->ci_serial = ecm_db_connection_serial_get(ci);
chfi->process_response.process_actions = 0;
chfi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE;
/*
* Find and save the bridge name. This will be passed to hyfi module later
*/
to_bridge[0] = 0;
from_bridge[0] = 0;
ecm_classifier_hyfi_get_bridge_name(ci, to_bridge, ECM_DB_OBJ_DIR_TO);
ecm_classifier_hyfi_get_bridge_name(ci, from_bridge, ECM_DB_OBJ_DIR_FROM);
if (!strlen(to_bridge) || !strlen(from_bridge)) {
/* one of the bridge name is null, typical
* routed connection. Consider valid bridge*/
if (strlen(to_bridge)) {
strlcpy(chfi->bridge_name, to_bridge, IFNAMSIZ);
} else if (strlen(from_bridge)) {
strlcpy(chfi->bridge_name, from_bridge, IFNAMSIZ);
}
} else if (!strncmp(to_bridge, from_bridge, IFNAMSIZ)) {
/* Pure bridge connection. Consider any one bridge */
strlcpy(chfi->bridge_name, to_bridge, IFNAMSIZ);
} else {
/* multi-bridge connection */
chfi->multi_bridge_flow = true;
/* TODO: Currently we are not supporting
* multi-bridge connection, stats wont be
* updated for multi-bridge flow */
}
/*
* Init Hy-Fi state
*/
chfi->hyfi_state = ECM_CLASSIFIER_HYFI_STATE_INIT;
spin_lock_bh(&ecm_classifier_hyfi_lock);
/*
* Final check if we are pending termination
*/
if (ecm_classifier_hyfi_terminate_pending) {
spin_unlock_bh(&ecm_classifier_hyfi_lock);
DEBUG_INFO("%px: Terminating\n", ci);
kfree(chfi);
return NULL;
}
/*
* Link the new instance into our list at the head
*/
chfi->next = ecm_classifier_hyfi_instances;
if (ecm_classifier_hyfi_instances) {
ecm_classifier_hyfi_instances->prev = chfi;
}
ecm_classifier_hyfi_instances = chfi;
/*
* Increment stats
*/
ecm_classifier_hyfi_count++;
DEBUG_ASSERT(ecm_classifier_hyfi_count > 0, "%px: ecm_classifier_hyfi_count wrap\n", chfi);
spin_unlock_bh(&ecm_classifier_hyfi_lock);
DEBUG_INFO("HyFi instance alloc: %px\n", chfi);
return chfi;
}
EXPORT_SYMBOL(ecm_classifier_hyfi_instance_alloc);
/*
* ecm_classifier_hyfi_connection_added()
* Invoked when a connection is added to the DB
*/
static void ecm_classifier_hyfi_connection_added(void *arg, struct ecm_db_connection_instance *ci)
{
#if (DEBUG_LEVEL > 2)
uint32_t serial = ecm_db_connection_serial_get(ci);
DEBUG_INFO("%px: HYFI LISTENER: added conn with serial: %u\n", ci, serial);
#endif
}
/*
* ecm_classifier_hyfi_connection_removed()
* Invoked when a connection is removed from the DB
*/
static void ecm_classifier_hyfi_connection_removed(void *arg, struct ecm_db_connection_instance *ci)
{
struct ecm_classifier_instance *aci;
struct ecm_classifier_hyfi_instance *chfi;
uint32_t serial = ecm_db_connection_serial_get(ci);
DEBUG_INFO("%px: HYFI LISTENER: removed conn with serial: %u\n", ci, serial);
/*
* Only handle events if there is an HyFi classifier attached
*/
aci = ecm_db_connection_assigned_classifier_find_and_ref(ci, ECM_CLASSIFIER_TYPE_HYFI);
if (!aci) {
DEBUG_TRACE("%px: Connection removed ignored"
" - no HyFi classifier\n", ci);
return;
}
chfi = (struct ecm_classifier_hyfi_instance *)aci;
DEBUG_INFO("%px: removed conn with serial: %u, "
"hash 0x%x, rev hash 0x%x\n",
aci, serial, chfi->flow.hash, chfi->flow.reverse_hash);
/*
* Mark as decelerated
*/
hyfi_ecm_decelerate(chfi->flow.hash, serial, &chfi->flow.da[0], chfi->bridge_name);
hyfi_ecm_decelerate(chfi->flow.reverse_hash, serial, &chfi->flow.sa[0], chfi->bridge_name);
aci->deref(aci);
}
/*
* ecm_classifier_hyfi_set_set_command()
* Set hyfi command to accel/decel connection.
*/
static ssize_t ecm_classifier_hyfi_set_command(struct file *file,
const char __user *user_buf,
size_t sz, loff_t *ppos)
{
#define ECM_CLASSIFIER_HYFI_SET_IP_COMMAND_FIELDS 2
char *cmd_buf;
int field_count;
char *field_ptr;
char *fields[ECM_CLASSIFIER_HYFI_SET_IP_COMMAND_FIELDS];
char cmd;
uint32_t serial;
struct ecm_db_connection_instance *ci;
struct ecm_front_end_connection_instance *feci;
struct ecm_classifier_hyfi_instance *chfi;
struct ecm_classifier_instance *classi;
/*
* Check if we are enabled
*/
spin_lock_bh(&ecm_classifier_hyfi_lock);
if (!ecm_classifier_hyfi_enabled) {
spin_unlock_bh(&ecm_classifier_hyfi_lock);
return -EINVAL;
}
spin_unlock_bh(&ecm_classifier_hyfi_lock);
/*
* buf is formed as:
* [0] [1]
* <CMD>/<SERIAL>
* CMD:
* s = Decelerate based on <SERIAL> number given.
*/
cmd_buf = (char *)kzalloc(sz + 1, GFP_ATOMIC);
if (!cmd_buf) {
return -ENOMEM;
}
sz = simple_write_to_buffer(cmd_buf, sz, ppos, user_buf, sz);
/*
* Split the buffer into its fields
*/
field_count = 0;
field_ptr = cmd_buf;
fields[field_count] = strsep(&field_ptr, "/");
while (fields[field_count] != NULL) {
DEBUG_TRACE("FIELD %d: %s\n", field_count, fields[field_count]);
field_count++;
if (field_count == ECM_CLASSIFIER_HYFI_SET_IP_COMMAND_FIELDS) {
break;
}
fields[field_count] = strsep(&field_ptr, "/");
}
if (field_count != ECM_CLASSIFIER_HYFI_SET_IP_COMMAND_FIELDS) {
DEBUG_WARN("invalid field count %d\n", field_count);
kfree(cmd_buf);
return -EINVAL;
}
if (!sscanf(fields[0], "%c", &cmd)) {
DEBUG_WARN("invalid cmd\n");
kfree(cmd_buf);
return -EINVAL;
}
if (!sscanf(fields[1], "%u", &serial)) {
DEBUG_WARN("invalid serial\n");
kfree(cmd_buf);
return -EINVAL;
}
kfree(cmd_buf);
/*
* Locate the connection using the serial or tuple given
*/
switch (cmd) {
case 's':
DEBUG_TRACE("Lookup connection using serial: %u\n", serial);
ci = ecm_db_connection_serial_find_and_ref(serial);
break;
default:
DEBUG_WARN("invalid cmd %c\n", cmd);
return -EINVAL;
}
if (!ci) {
DEBUG_WARN("database connection not found\n");
return -ENOMEM;
}
DEBUG_TRACE("Connection found: %px\n", ci);
/*
* Get the Hy-Fi classifier instance
*/
classi = ecm_db_connection_assigned_classifier_find_and_ref(
ci, ECM_CLASSIFIER_TYPE_HYFI);
if (!classi) {
DEBUG_WARN("%px: No Hy-Fi classifier instance\n", ci);
ecm_db_connection_deref(ci);
return -ENOMEM;
}
chfi = (struct ecm_classifier_hyfi_instance *)classi;
/*
* Now action the command
*/
switch (cmd) {
case 's':
case 'f':
/*
* Regenerate the connection
*/
feci = ecm_db_connection_front_end_get_and_ref(ci);
/*
* Store the begin / end timestamps here (from the previous
* operation). Will not attempt another regen if
* ports don't match until this regenerate is completed (which
* can be detected by a change in the timestamps).
*/
chfi->flow.cmd_time_begun = feci->stats.cmd_time_begun;
chfi->flow.cmd_time_completed = feci->stats.cmd_time_completed;
feci->regenerate(feci, ci);
feci->deref(feci);
DEBUG_TRACE("%px: Force regeneration %u start_time %lu end_time"
" %lu (@%lu)\n", ci, serial,
chfi->flow.cmd_time_begun,
chfi->flow.cmd_time_completed, jiffies);
break;
}
classi->deref(classi);
ecm_db_connection_deref(ci);
return sz;
}
/*
* File operations for hyfi classifier command.
*/
static struct file_operations ecm_classifier_hyfi_cmd_fops = {
.write = ecm_classifier_hyfi_set_command,
.llseek = generic_file_llseek,
.owner = THIS_MODULE
};
/*
* ecm_classifier_hyfi_rules_init()
*/
int ecm_classifier_hyfi_rules_init(struct dentry *dentry)
{
DEBUG_INFO("HyFi classifier Module init\n");
ecm_classifier_hyfi_dentry = debugfs_create_dir("ecm_classifier_hyfi", dentry);
if (!ecm_classifier_hyfi_dentry) {
DEBUG_ERROR("Failed to create ecm hyfi classifier directory in debugfs\n");
goto classifier_task_cleanup;
}
if (!debugfs_create_u32("enabled", S_IRUGO | S_IWUSR, ecm_classifier_hyfi_dentry,
(u32 *)&ecm_classifier_hyfi_enabled)) {
DEBUG_ERROR("Failed to create ecm hyfi classifier enabled file in debugfs\n");
goto classifier_task_cleanup;
}
if (!debugfs_create_file("cmd", S_IWUSR, ecm_classifier_hyfi_dentry,
NULL, &ecm_classifier_hyfi_cmd_fops)) {
DEBUG_ERROR("Failed to create ecm hyfi classifier cmd file in debugfs\n");
goto classifier_task_cleanup;
}
/*
* Allocate listener instance to listen for db events
*/
ecm_classifier_hyfi_li = ecm_db_listener_alloc();
if (!ecm_classifier_hyfi_li) {
DEBUG_ERROR("Failed to allocate listener\n");
goto classifier_task_cleanup;
}
/*
* Add the listener into the database
* NOTE: Ref the thread count for the listener
*/
ecm_db_listener_add(ecm_classifier_hyfi_li,
NULL /* ecm_classifier_hyfi_iface_added */,
NULL /* ecm_classifier_hyfi_iface_removed */,
NULL /* ecm_classifier_hyfi_node_added */,
NULL /* ecm_classifier_hyfi_node_removed */,
NULL /* ecm_classifier_hyfi_host_added */,
NULL /* ecm_classifier_hyfi_host_removed */,
NULL /* ecm_classifier_hyfi_mapping_added */,
NULL /* ecm_classifier_hyfi_mapping_removed */,
ecm_classifier_hyfi_connection_added,
ecm_classifier_hyfi_connection_removed,
NULL /* ecm_classifier_hyfi_listener_final */,
ecm_classifier_hyfi_li);
return 0;
classifier_task_cleanup:
debugfs_remove_recursive(ecm_classifier_hyfi_dentry);
return -1;
}
EXPORT_SYMBOL(ecm_classifier_hyfi_rules_init);
/*
* ecm_classifier_hyfi_rules_exit()
*/
void ecm_classifier_hyfi_rules_exit(void)
{
DEBUG_INFO("HyFi classifier Module exit\n");
spin_lock_bh(&ecm_classifier_hyfi_lock);
ecm_classifier_hyfi_terminate_pending = true;
spin_unlock_bh(&ecm_classifier_hyfi_lock);
/*
* Release our ref to the listener.
* This will cause it to be unattached to the db listener list.
* NOTE: Our thread refs will be released on final callback when
* we know there will be no more callbacks to it.
*/
if (ecm_classifier_hyfi_li) {
ecm_db_listener_deref(ecm_classifier_hyfi_li);
ecm_classifier_hyfi_li = NULL;
}
/*
* Remove the debugfs files recursively.
*/
if (ecm_classifier_hyfi_dentry) {
debugfs_remove_recursive(ecm_classifier_hyfi_dentry);
}
}
EXPORT_SYMBOL(ecm_classifier_hyfi_rules_exit);