blob: b9d98d49cf39da8396e191bca5e3667b36e836d4 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2019-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.
**************************************************************************
*/
/*
* OVS Classifier.
* This classifier is called for the connections which are created
* on OVS interfaces. The connections can be routed or bridged connections.
* The classifier processes these connection flows and decides if the connection
* is ready to be accelerated. Moreover, it has an interface to an external classifier
* to make smart decisions on the flows, such as extracting the VLAN information from the
* OVS flow tables and gives it to the ECM front end to be passed to the acceleration engine.
*/
#include <linux/version.h>
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/debugfs.h>
#include <linux/ctype.h>
#include <net/tcp.h>
#include <net/ipv6.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/etherdevice.h>
#include <linux/netfilter/xt_dscp.h>
/*
* Debug output levels
* 0 = OFF
* 1 = ASSERTS / ERRORS
* 2 = 1 + WARN
* 3 = 2 + INFO
* 4 = 3 + TRACE
*/
#define DEBUG_LEVEL ECM_CLASSIFIER_OVS_DEBUG_LEVEL
#include <ovsmgr.h>
#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_datagram.h"
#include "ecm_tracker_udp.h"
#include "ecm_tracker_tcp.h"
#include "ecm_db.h"
#include "ecm_classifier_ovs.h"
#include "ecm_classifier_ovs_public.h"
#include "ecm_front_end_common.h"
#include "ecm_front_end_ipv4.h"
#ifdef ECM_IPV6_ENABLE
#include "ecm_front_end_ipv6.h"
#endif
#include "ecm_interface.h"
/*
* Magic numbers
*/
#define ECM_CLASSIFIER_OVS_INSTANCE_MAGIC 0x2568
/*
* struct ecm_classifier_ovs_instance
* State per connection for OVS classifier
*/
struct ecm_classifier_ovs_instance {
struct ecm_classifier_instance base; /* Base type */
uint32_t ci_serial; /* RO: Serial of the connection */
struct ecm_classifier_ovs_instance *next; /* Next classifier state instance (for accouting and reporting purposes) */
struct ecm_classifier_ovs_instance *prev; /* Next classifier state instance (for accouting and reporting purposes) */
struct ecm_classifier_process_response process_response;
/* Last process response computed */
int refs; /* Integer to trap we never go negative */
#if (DEBUG_LEVEL > 0)
uint16_t magic;
#endif
};
/*
* Operational control.
*/
static int ecm_classifier_ovs_enabled = 1; /* Operational behaviour */
/*
* Management thread control
*/
static bool ecm_classifier_ovs_terminate_pending; /* True when the user wants us to terminate */
/*
* Debugfs dentry object.
*/
static struct dentry *ecm_classifier_ovs_dentry;
/*
* Locking of the classifier structures
*/
static DEFINE_SPINLOCK(ecm_classifier_ovs_lock); /* Protect SMP access. */
/*
* List of our classifier instances
*/
static struct ecm_classifier_ovs_instance *ecm_classifier_ovs_instances = NULL;
/* list of all active instances */
static int ecm_classifier_ovs_count = 0; /* Tracks number of instances allocated */
/*
* Callback object.
*/
static struct ecm_classifier_ovs_callbacks ovs;
/*
* ecm_classifier_ovs_ref()
* Ref
*/
static void ecm_classifier_ovs_ref(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_ovs_instance *ecvi;
ecvi = (struct ecm_classifier_ovs_instance *)ci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->refs++;
DEBUG_ASSERT(ecvi->refs > 0, "%px: ref wrap\n", ecvi);
DEBUG_TRACE("%px: ecvi ref %d\n", ecvi, ecvi->refs);
spin_unlock_bh(&ecm_classifier_ovs_lock);
}
/*
* ecm_classifier_ovs_deref()
* Deref
*/
static int ecm_classifier_ovs_deref(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_ovs_instance *ecvi;
ecvi = (struct ecm_classifier_ovs_instance *)ci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->refs--;
DEBUG_ASSERT(ecvi->refs >= 0, "%px: refs wrapped\n", ecvi);
DEBUG_TRACE("%px: ecvi deref %d\n", ecvi, ecvi->refs);
if (ecvi->refs) {
int refs = ecvi->refs;
spin_unlock_bh(&ecm_classifier_ovs_lock);
return refs;
}
/*
* Object to be destroyed
*/
ecm_classifier_ovs_count--;
DEBUG_ASSERT(ecm_classifier_ovs_count >= 0, "%px: ecm_classifier_ovs_count wrap\n", ecvi);
/*
* UnLink the instance from our list
*/
if (ecvi->next) {
ecvi->next->prev = ecvi->prev;
}
if (ecvi->prev) {
ecvi->prev->next = ecvi->next;
} else {
DEBUG_ASSERT(ecm_classifier_ovs_instances == ecvi, "%px: list bad %px\n", ecvi, ecm_classifier_ovs_instances);
ecm_classifier_ovs_instances = ecvi->next;
}
spin_unlock_bh(&ecm_classifier_ovs_lock);
/*
* Final
*/
DEBUG_INFO("%px: Final ovs classifier instance\n", ecvi);
kfree(ecvi);
return 0;
}
/*
* ecm_classifier_ovs_interface_get_and_ref()
* Gets the OVS bridge port in the specified direction hierarchy.
*/
static inline struct net_device *ecm_classifier_ovs_interface_get_and_ref(struct ecm_db_connection_instance *ci,
ecm_db_obj_dir_t dir, bool ovs_port)
{
int32_t if_first;
struct ecm_db_iface_instance *interfaces[ECM_DB_IFACE_HEIRARCHY_MAX];
int32_t i;
/*
* Get the interface hierarchy.
*/
if_first = ecm_db_connection_interfaces_get_and_ref(ci, interfaces, dir);
if (if_first == ECM_DB_IFACE_HEIRARCHY_MAX) {
DEBUG_WARN("%px: Failed to get %s interfaces list\n", ci, ecm_db_obj_dir_strings[dir]);
return NULL;
}
/*
* Go through the hierarchy and get the OVS port.
*/
for (i = ECM_DB_IFACE_HEIRARCHY_MAX - 1; i >= if_first; i--) {
struct net_device *dev = dev_get_by_index(&init_net,
ecm_db_iface_interface_identifier_get(interfaces[i]));
if (!dev) {
DEBUG_WARN("%px: Failed to get net device with %d index\n", ci, i);
continue;
}
if (ovs_port) {
if (ecm_interface_is_ovs_bridge_port(dev)) {
ecm_db_connection_interfaces_deref(interfaces, if_first);
DEBUG_TRACE("%px: %s_dev: %s at %d index is an OVS bridge port\n", ci, ecm_db_obj_dir_strings[dir], dev->name, i);
return dev;
}
} else {
if (ovsmgr_is_ovs_master(dev)) {
ecm_db_connection_interfaces_deref(interfaces, if_first);
DEBUG_TRACE("%px: %s_dev: %s at %d index is an OVS bridge dev\n", ci, ecm_db_obj_dir_strings[dir], dev->name, i);
return dev;
}
}
DEBUG_TRACE("%px: dev: %s index: %d\n", ci, dev->name, i);
dev_put(dev);
}
DEBUG_TRACE("%px: No OVS bridge port on the %s direction\n", ci, ecm_db_obj_dir_strings[dir]);
ecm_db_connection_interfaces_deref(interfaces, if_first);
return NULL;
}
#ifdef ECM_MULTICAST_ENABLE
/*
* ecm_classifier_ovs_process_multicast()
* Process multicast packet
*/
static void ecm_classifier_ovs_process_multicast(struct ecm_db_connection_instance *ci, struct sk_buff *skb,
struct ecm_classifier_ovs_instance *ecvi,
struct ecm_classifier_process_response *process_response)
{
struct ecm_classifier_ovs_process_response resp;
ecm_classifier_ovs_process_callback_t cb;
struct ecm_db_iface_instance *to_mc_ifaces;
struct net_device *from_dev = NULL, *indev_master = NULL;
struct net_device *to_dev[ECM_DB_MULTICAST_IF_MAX] = {NULL};
struct ovsmgr_dp_flow flow;
ip_addr_t src_ip;
ip_addr_t dst_ip;
int32_t *to_mc_ifaces_first;
int if_cnt, i;
bool valid_ovs_ports = false;
/*
* Classifier is always relevant for multicast
* if we have enabled OVS support in ECM.
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_YES;
spin_unlock_bh(&ecm_classifier_ovs_lock);
/*
* No multicast port found, drop the connection.
*/
if_cnt = ecm_db_multicast_connection_to_interfaces_get_and_ref_all(ci, &to_mc_ifaces, &to_mc_ifaces_first);
if (unlikely(!if_cnt)) {
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_DROP;
ecvi->process_response.drop = true;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_WARN("%px: No multicast 'to' interface found\n", ci);
goto done;
}
for (i = 0; i < ECM_DB_MULTICAST_IF_MAX; i++) {
int32_t *to_iface_first;
int j;
/*
* Get to interface list, skip if the list is invalid.
*/
to_iface_first = ecm_db_multicast_if_first_get_at_index(to_mc_ifaces_first, i);
if (*to_iface_first == ECM_DB_IFACE_HEIRARCHY_MAX) {
continue;
}
for (j = to_mc_ifaces_first[i]; j < ECM_DB_IFACE_HEIRARCHY_MAX; j++) {
struct ecm_db_iface_instance **ifaces;
struct ecm_db_iface_instance *ii_temp;
struct net_device *to_dev_temp;
ii_temp = ecm_db_multicast_if_heirarchy_get(to_mc_ifaces, i);
ifaces = (struct ecm_db_iface_instance **)ecm_db_multicast_if_instance_get_at_index(ii_temp, j);
to_dev_temp = dev_get_by_index(&init_net, ecm_db_iface_interface_identifier_get(*ifaces));
if (unlikely(!to_dev_temp)) {
continue;
}
/*
* Get the physical OVS port.
*/
if (!ecm_interface_is_ovs_bridge_port(to_dev_temp)) {
dev_put(to_dev_temp);
continue;
}
to_dev[i] = to_dev_temp;
valid_ovs_ports = true;
}
}
ecm_db_multicast_connection_to_interfaces_deref_all(to_mc_ifaces, to_mc_ifaces_first);
from_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, true);
if (!from_dev && !valid_ovs_ports) {
spin_lock_bh(&ecm_classifier_ovs_lock);
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_WARN("%px: None of the from/to interfaces are OVS bridge port\n", ci);
goto done;
}
/*
* Below are the possible multicast flows.
*
* 1. L2 Multicast (Bridge)
* ovsbr (eth0, eth1,eth2)
* a. sender: eth0, receiver: eth1, eth2
*
* 2. L3 Multicast (Route)
* Upstream: br-wan (eth0)
* Downstream: br-home (eth1, eth2)
* a. Downstream flow => sender: eth0, receiver: eth1, eth2
* b. Upstream flow => sender: eth1, receiver: eth0
*
* 3. L2 + L3 Multicast (Bridge + Route)
* Upstream: br-wan (eth0)
* Downstream: br-home (eth1, eth2)
* a. sender: eth1, receiver: eth0, eth2
*/
/*
* Is there an external callback to get the ovs value from the packet?
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
cb = ovs.ovs_process;
if (!cb) {
/*
* Allow acceleration.
* Keep the classifier relevant to connection for stats update..
*/
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_WARN("%px: No external process callback set\n", ci);
goto allow_accel;
}
spin_unlock_bh(&ecm_classifier_ovs_lock);
memset(&flow, 0, sizeof(struct ovsmgr_dp_flow));
if (from_dev) {
flow.indev = from_dev;
indev_master = ovsmgr_dev_get_master(flow.indev);
DEBUG_ASSERT(indev_master, "%px: Expected a master\n", ci);
}
flow.is_routed = ecm_db_connection_is_routed_get(ci);
flow.tuple.ip_version = ecm_db_connection_ip_version_get(ci);
flow.tuple.protocol = ecm_db_connection_protocol_get(ci);
flow.tuple.src_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
flow.tuple.dst_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, flow.smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, flow.dmac);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
if (flow.tuple.ip_version == 4) {
ECM_IP_ADDR_TO_NIN4_ADDR(flow.tuple.ipv4.src, src_ip);
ECM_IP_ADDR_TO_NIN4_ADDR(flow.tuple.ipv4.dst, dst_ip);
} else if (flow.tuple.ip_version == 6) {
ECM_IP_ADDR_TO_NIN6_ADDR(flow.tuple.ipv6.src, src_ip);
ECM_IP_ADDR_TO_NIN6_ADDR(flow.tuple.ipv6.dst, dst_ip);
} else {
DEBUG_ASSERT(NULL, "%px: unexpected ip_version: %d", ci, flow.tuple.ip_version );
}
/*
* Check if the connection is a "bridge + route"
* flow.
*/
if (flow.is_routed) {
/*
* Get the ingress VLAN information from ovs-mgr
*/
if (from_dev) {
struct net_device *br_dev;
ecm_classifier_ovs_result_t result;
/*
* from_dev = eth1
* br_dev = ovs-br1
*/
br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, false);
DEBUG_TRACE("%px: processing route flow from_dev = %s, br_dev = %s", ecvi, from_dev->name, br_dev->name);
/*
* We always take the flow from bridge to port, so indev is port device and outdev is bridge device.
*/
flow.indev = from_dev;
flow.outdev = br_dev;
DEBUG_TRACE("%px: Route Flow Process (from): src MAC: %pM src_dev: %s src: %pI4:%d proto: %d dest: %pI4:%d dest_dev: %s dest MAC: %pM\n",
&flow, flow.smac, flow.indev->name, &flow.tuple.ipv4.src, flow.tuple.src_port, flow.tuple.protocol,
&flow.tuple.ipv4.dst, flow.tuple.dst_port, flow.outdev->name, flow.dmac);
memset(&resp, 0, sizeof(struct ecm_classifier_ovs_process_response));
/*
* Call the external callback and get the result.
*/
result = cb(&flow, skb, &resp);
dev_put(br_dev);
/*
* Handle the result
*/
switch (result) {
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_ACCEL:
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL:
/*
* Allow accel after setting the external module response.
*/
DEBUG_WARN("%px: External callback process succeeded\n", ecvi);
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.ingress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.ingress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
if (resp.egress_vlan[0].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[0] = resp.egress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[0].h_vlan_TCI;
}
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG;
if (result == ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL) {
if (resp.egress_vlan[1].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[1] = resp.egress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[1].h_vlan_TCI;
}
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG;
}
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_TRACE("%px: Multicast ingress vlan tag[0]: %x tag[1]: %x\n", ecvi, ecvi->process_response.ingress_vlan_tag[0],
ecvi->process_response.ingress_vlan_tag[1]);
break;
case ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL:
/*
* External callback failed to process VLAN process. So, let's deny the acceleration
* and try more with the subsequent packets.
*/
DEBUG_WARN("%px: External callback failed to process VLAN tags\n", ecvi);
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_MCAST_DENY_ACCEL;
ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
goto done;
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_ACCEL:
/*
* There is no VLAN tag in the flow. Just allow the acceleration.
*/
DEBUG_WARN("%px: External callback didn't find any VLAN relation\n", ecvi);
break;
default:
DEBUG_ASSERT(false, "%px: Unhandled result: %d\n", ci, result);
}
}
}
/*
* During multicast update path, skb is passed as NULL.
* We need to fill the ingress_vlan with the same vlan
* information that we have stored during connection instance
* creation. OVS manager needs this information to provide the
* right response.
*/
if (!skb) {
flow.ingress_vlan = ecm_db_multicast_tuple_get_ovs_ingress_vlan(ci->ti);
}
/*
* Call the external callback and get the result.
*/
for (i = 0; i < ECM_DB_MULTICAST_IF_MAX; i++) {
ecm_classifier_ovs_result_t result = 0;
if (!to_dev[i]) {
continue;
}
flow.outdev = to_dev[i];
/*
* For routed flows, indev should be the OVS bridge of the
* corresponding port.
*/
if (flow.is_routed) {
struct net_device *outdev_master;
/*
* During bridge+route scenario, the indev can be
* an ovs bridge port or and ovs bridge. if master device
* for indev and outdev are same then it a bridged traffic.
* Otherwise, set the indev to master of the egress port.
*/
outdev_master = ovsmgr_dev_get_master(flow.outdev);
DEBUG_ASSERT(outdev_master, "%px: Expected a master\n", ci);
if (indev_master && (indev_master->ifindex == outdev_master->ifindex)) {
flow.indev = from_dev;
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, flow.smac);
} else {
flow.indev = outdev_master;
ether_addr_copy(flow.smac, flow.indev->dev_addr);
}
}
DEBUG_TRACE("%px: src MAC: %pM src_dev: %s src: %pI4:%d proto: %d dest: %pI4:%d dest_dev: %s dest MAC: %pM\n",
ci, flow.smac, flow.indev->name, &flow.tuple.ipv4.src, flow.tuple.src_port, flow.tuple.protocol,
&flow.tuple.ipv4.dst, flow.tuple.dst_port, flow.outdev->name, flow.dmac);
memset(&resp, 0, sizeof(struct ecm_classifier_ovs_process_response));
result = cb(&flow, skb, &resp);
switch(result) {
case ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL_EGRESS:
DEBUG_TRACE("%px: %s is not a valid OVS port\n", ci, to_dev[i]->name);
ecm_db_multicast_connection_to_interfaces_clear_at_index(ci, i);
break;
case ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL:
/*
* If we receive "ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL"
* for any of the OVS port, then the connection should be
* deleted.
*/
DEBUG_TRACE("%px: flow does not exist for the OVS port: %s\n", ci, to_dev[i]->name);
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_MCAST_DENY_ACCEL;
ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
goto done;
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_ACCEL:
DEBUG_TRACE("%px: Acceleration allowed for multicast OVS port: %s\n", ci, to_dev[i]->name);
break;
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_ACCEL:
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL:
/*
* Allow accel after setting the external module response.
* Primary VLAN tag is always present even it is QinQ.
*/
DEBUG_WARN("%px: External callback process succeeded\n", ci);
/*
* Initialize the VLAN entries since the ports can join/leave
* in-between and that will change the position of an existing port
* in the "egress_mc_vlan_tag" array.
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.egress_mc_vlan_tag[i][0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.egress_mc_vlan_tag[i][1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG;
ecvi->process_response.egress_netdev_index[i] = flow.outdev->ifindex;
/*
* If we have a valid TCI for ingress VLAN then we assume
* the flow is bridged. Because for routed flows, ingress VLAN
* will not be available.
*/
if (resp.ingress_vlan[0].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[0] = resp.ingress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.ingress_vlan[0].h_vlan_TCI;
}
if (resp.egress_vlan[0].h_vlan_TCI) {
ecvi->process_response.egress_mc_vlan_tag[i][0] = resp.egress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[0].h_vlan_TCI;
}
if (result == ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL) {
if (resp.ingress_vlan[1].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[1] = resp.ingress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.ingress_vlan[1].h_vlan_TCI;
}
if (resp.egress_vlan[1].h_vlan_TCI) {
ecvi->process_response.egress_mc_vlan_tag[i][1] = resp.egress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[1].h_vlan_TCI;
}
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG;
}
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_TRACE("%px: Multicast ingress vlan tag[0] : 0x%x tag[1] : 0x%x\n", ci, ecvi->process_response.ingress_vlan_tag[0],
ecvi->process_response.ingress_vlan_tag[1]);
DEBUG_TRACE("%px: Multicast egress vlan tag[%d][0] : 0x%x tag[%d][1] : 0x%x\n", ci, i, ecvi->process_response.egress_mc_vlan_tag[i][0],
i, ecvi->process_response.egress_mc_vlan_tag[i][1]);
break;
default:
DEBUG_ASSERT(false, "Unhandled result: %d\n", result);
}
}
/*
* It is possible that after verifying each egress port with ovs manager,
* no egress ovs ports are allowed for acceleration.
*
* Deny the multicast connection as there are no active 'to' interface.
*/
if (!ecm_db_multicast_connection_to_interfaces_get_count(ci)) {
DEBUG_TRACE("%px: No valid multicast 'to' interfaces found\n", ci);
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_MCAST_DENY_ACCEL;
ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
goto done;
}
allow_accel:
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_ACCEL_MODE;
ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_ACCEL;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
done:
if (from_dev)
dev_put(from_dev);
/*
* Deref the ovs net devices
*/
if (valid_ovs_ports) {
for (i = 0; i < ECM_DB_MULTICAST_IF_MAX; i++) {
if (to_dev[i]) {
dev_put(to_dev[i]);
}
}
}
return;
}
#endif
/*
* ecm_classifier_ovs_process_route_flow()
* Process routed flows.
*/
static void ecm_classifier_ovs_process_route_flow(struct ecm_classifier_ovs_instance *ecvi, struct ecm_db_connection_instance *ci,
struct sk_buff *skb, struct net_device *from_dev, struct net_device *to_dev,
struct ecm_classifier_process_response *process_response,
ecm_classifier_ovs_process_callback_t cb)
{
ecm_classifier_ovs_result_t result;
struct ecm_classifier_ovs_process_response resp;
struct ovsmgr_dp_flow flow;
struct net_device *br_dev;
ip_addr_t src_ip, dst_ip;
memset(&flow, 0, sizeof(flow));
flow.is_routed = true;
flow.tuple.ip_version = ecm_db_connection_ip_version_get(ci);
flow.tuple.protocol = ecm_db_connection_protocol_get(ci);
/*
* For routed flows both from and to side can be OVS bridge port, if there
* is a routed flow between two OVS bridges. (e.g: ovs-br1 and ovs-br2)
*
* Connection Direction:
* PC1 -----> eth1-ovs-br1 (DUT) ovs-br2-eth2 -----> PC2
from_dev to_dev
*
* 1. SNAT/DNAT Enabled: FROM FROM_NAT TO/TO_NAT
* 2. SNAT/DNAT Disabled: FROM/FROM_NAT TO/TO_NAT
*
* Connection Direction:
* PC1 <----- eth1-ovs-br1 (DUT) ovs-br2-eth2 <----- PC2
* to_dev from_dev
*
* 3. SNAT/DNAT Enabled: TO TO_NAT FROM/FROM_NAT
* 4. SNAT/DNAT Disabled: TO/TO_NAT FROM/FROM_NAT
*
* 5. Tunnelled packet: PC1 ------------> eth1---[ovs-br1]---gretap (DUT) eth0----->[gretap device]
* After the packet is encapsulated, the packet is routed and the
* flow is not relavant flow.
*/
if (from_dev) {
/*
* Case 1/2
* from_dev = eth1
* br_dev = ovs-br1
*
* Case 3/4
* from_dev = eth2
* br_dev = ovs-br2
*
* case 5
* from_dev = greptap
* br_dev = NULL
*/
br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, false);
if (!br_dev) {
DEBUG_WARN("%px: from_dev = %s is a OVS bridge port, bridge interface is not found\n",
ecvi, from_dev->name);
goto route_not_relevant;
}
DEBUG_TRACE("%px: processing route flow from_dev = %s, br_dev = %s", ecvi, from_dev->name, br_dev->name);
/*
* We always take the flow from bridge to port, so indev is brdige and outdev is port device.
*/
flow.indev = br_dev;
flow.outdev = from_dev;
flow.tuple.src_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO_NAT));
flow.tuple.dst_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO_NAT, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, dst_ip);
if (flow.tuple.ip_version == 4) {
ECM_IP_ADDR_TO_NIN4_ADDR(flow.tuple.ipv4.src, src_ip);
ECM_IP_ADDR_TO_NIN4_ADDR(flow.tuple.ipv4.dst, dst_ip);
} else if (flow.tuple.ip_version == 6) {
ECM_IP_ADDR_TO_NIN6_ADDR(flow.tuple.ipv6.src, src_ip);
ECM_IP_ADDR_TO_NIN6_ADDR(flow.tuple.ipv6.dst, dst_ip);
} else {
DEBUG_ASSERT(NULL, "%px: unexpected ip_version: %d", ecvi, flow.tuple.ip_version );
}
ether_addr_copy(flow.smac, br_dev->dev_addr);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, flow.dmac);
memset(&resp, 0, sizeof(struct ecm_classifier_ovs_process_response));
/*
* Initialize the dscp with the default value.
*/
resp.dscp = OVSMGR_INVALID_DSCP;
DEBUG_TRACE("%px: Route Flow Process (from): src MAC: %pM src_dev: %s src: %pI4:%d proto: %d dest: %pI4:%d dest_dev: %s dest MAC: %pM\n",
&flow, flow.smac, flow.indev->name, &flow.tuple.ipv4.src, flow.tuple.src_port, flow.tuple.protocol,
&flow.tuple.ipv4.dst, flow.tuple.dst_port, flow.outdev->name, flow.dmac);
/*
* Call the external callback and get the result.
*/
result = cb(&flow, skb, &resp);
dev_put(br_dev);
if (resp.dscp != OVSMGR_INVALID_DSCP) {
/*
* Copy DSCP value to the classifier's process response's flow_dscp field,
* because this is the from_dev and the direction of the flow is flow direction.
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.flow_dscp = resp.dscp >> XT_DSCP_SHIFT;
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_DSCP;
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_TRACE("FLOW DSCP : 0x%x\n", ecvi->process_response.flow_dscp);
}
/*
* Handle the result
*/
switch (result) {
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_ACCEL:
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL:
/*
* Allow accel after setting the external module response.
*/
DEBUG_WARN("%px: External callback process succeeded\n", ecvi);
spin_lock_bh(&ecm_classifier_ovs_lock);
if (resp.egress_vlan[0].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[0] = resp.egress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[0].h_vlan_TCI;
DEBUG_TRACE("Ingress vlan tag[0] set : %x\n", ecvi->process_response.ingress_vlan_tag[0]);
}
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG;
if (result == ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL) {
if (resp.egress_vlan[1].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[1] = resp.egress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[1].h_vlan_TCI;
DEBUG_TRACE("Ingress vlan tag[0] set : %x\n", ecvi->process_response.ingress_vlan_tag[1]);
}
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG;
}
spin_unlock_bh(&ecm_classifier_ovs_lock);
break;
case ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL:
/*
* External callback failed to process VLAN process. So, let's deny the acceleration
* and try more with the subsequent packets.
*/
DEBUG_WARN("%px: External callback failed to process VLAN tags\n", ecvi);
goto route_deny_accel;
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_ACCEL:
/*
* There is no VLAN tag in the flow. Just allow the acceleration.
*/
DEBUG_WARN("%px: External callback didn't find any VLAN relation\n", ecvi);
break;
default:
DEBUG_ASSERT(false, "Unhandled result: %d\n", result);
}
}
if (to_dev) {
/*
* Case 1/2
* from_dev = eth2
* br_dev = ovs-br2
*
* Case 3/4
* from_dev = eth1
* br_dev = ovs-br1
*/
br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_TO, false);
if (!br_dev) {
DEBUG_WARN("%px: to_dev = %s is a OVS bridge port, bridge interface is not found\n",
ecvi, to_dev->name);
goto route_deny_accel;
}
DEBUG_TRACE("%px: processing route flow to_dev = %s, br_dev = %s", ecvi, to_dev->name, br_dev->name);
flow.indev = br_dev;
flow.outdev = to_dev;
flow.tuple.src_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM_NAT));
flow.tuple.dst_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM_NAT, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
if (flow.tuple.ip_version == 4) {
ECM_IP_ADDR_TO_NIN4_ADDR(flow.tuple.ipv4.src, src_ip);
ECM_IP_ADDR_TO_NIN4_ADDR(flow.tuple.ipv4.dst, dst_ip);
} else if (flow.tuple.ip_version == 6) {
ECM_IP_ADDR_TO_NIN6_ADDR(flow.tuple.ipv6.src, src_ip);
ECM_IP_ADDR_TO_NIN6_ADDR(flow.tuple.ipv6.dst, dst_ip);
} else {
DEBUG_ASSERT(NULL, "%px: unexpected ip_version: %d", ecvi, flow.tuple.ip_version );
}
ether_addr_copy(flow.smac, br_dev->dev_addr);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, flow.dmac);
memset(&resp, 0, sizeof(struct ecm_classifier_ovs_process_response));
/*
* Initialize the dscp with the default value.
*/
resp.dscp = OVSMGR_INVALID_DSCP;
DEBUG_TRACE("%px: Route Flow Process (to): src MAC: %pM src_dev: %s src: %pI4:%d proto: %d dest: %pI4:%d dest_dev: %s dest MAC: %pM\n",
&flow, flow.smac, flow.indev->name, &flow.tuple.ipv4.src, flow.tuple.src_port, flow.tuple.protocol,
&flow.tuple.ipv4.dst, flow.tuple.dst_port, flow.outdev->name, flow.dmac);
/*
* Call the external callback and get the result.
*/
result = cb(&flow, skb, &resp);
dev_put(br_dev);
if (resp.dscp != OVSMGR_INVALID_DSCP) {
/*
* Copy DSCP value to the classifier's process response's return_dscp field,
* because this is the to_dev and the direction of the flow is reply direction.
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.return_dscp = resp.dscp >> XT_DSCP_SHIFT;
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_DSCP;
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_TRACE("RETURN DSCP : 0x%x\n", ecvi->process_response.return_dscp);
}
/*
* Handle the result
*/
switch (result) {
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_ACCEL:
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL:
/*
* Allow accel after setting the external module response.
*/
DEBUG_WARN("%px: External callback process succeeded\n", ecvi);
spin_lock_bh(&ecm_classifier_ovs_lock);
if (resp.egress_vlan[0].h_vlan_TCI) {
ecvi->process_response.egress_vlan_tag[0] = resp.egress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[0].h_vlan_TCI;
DEBUG_TRACE("Egress vlan tag[0] set : %x\n", ecvi->process_response.egress_vlan_tag[0]);
}
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG;
if (result == ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL) {
if (resp.egress_vlan[1].h_vlan_TCI) {
ecvi->process_response.egress_vlan_tag[1] = resp.egress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[1].h_vlan_TCI;
DEBUG_TRACE("Ingress vlan tag[0] set : %x\n", ecvi->process_response.ingress_vlan_tag[1]);
}
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG;
}
spin_unlock_bh(&ecm_classifier_ovs_lock);
break;
case ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL:
/*
* External callback failed to process VLAN process. So, let's deny the acceleration
* and try more with the subsequent packets.
*/
DEBUG_WARN("%px: External callback failed to process VLAN tags\n", ecvi);
goto route_deny_accel;
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_ACCEL:
/*
* There is no VLAN tag in the flow. Just allow the acceleration.
*/
DEBUG_WARN("%px: External callback didn't find any VLAN relation\n", ecvi);
break;
default:
DEBUG_ASSERT(false, "Unhandled result: %d\n", result);
}
}
/*
* Acceleration is permitted
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_YES;
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_ACCEL_MODE;
ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_ACCEL;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
return;
route_deny_accel:
/*
* ecm_classifier_ovs_lock MUST be held
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_YES;
ecvi->process_response.process_actions = ECM_CLASSIFIER_PROCESS_ACTION_ACCEL_MODE;
ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
return;
route_not_relevant:
/*
* ecm_classifier_ovs_lock MUST be held
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
}
/*
* ecm_classifier_ovs_process()
* Process new packet
*
* NOTE: This function would only ever be called if all other classifiers have failed.
*/
static void ecm_classifier_ovs_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_ovs_instance *ecvi = (struct ecm_classifier_ovs_instance *)aci;
ecm_classifier_ovs_result_t result = 0;
struct ecm_db_connection_instance *ci;
ecm_classifier_ovs_process_callback_t cb = NULL;
ip_addr_t src_ip;
ip_addr_t dst_ip;
struct ecm_classifier_ovs_process_response resp;
struct ovsmgr_dp_flow flow;
struct net_device *from_dev = NULL;
struct net_device *to_dev = NULL;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: invalid state magic\n", ecvi);
/*
* Not relevant to the connection if not enabled.
*/
if (unlikely(!ecm_classifier_ovs_enabled)) {
/*
* Not relevant.
*/
DEBUG_WARN("%px: ovs classifier is not enabled\n", aci);
goto not_relevant;
}
/*
* Get connection
*/
ci = ecm_db_connection_serial_find_and_ref(ecvi->ci_serial);
if (!ci) {
/*
* Connection has gone from under us
*/
DEBUG_WARN("%px: connection instance gone while processing classifier\n", aci);
goto not_relevant;
}
#ifdef ECM_MULTICAST_ENABLE
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
if (ecm_ip_addr_is_multicast(dst_ip)) {
ecm_classifier_ovs_process_multicast(ci, skb, ecvi, process_response);
ecm_db_connection_deref(ci);
return;
}
#endif
/*
* Get the possible OVS bridge ports. If both are NULL, the classifier is not
* relevant to this connection.
*/
from_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, true);
to_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_TO, true);
if (!from_dev && !to_dev) {
/*
* So, the classifier is not relevant to this connection.
*/
DEBUG_WARN("%px: None of the from/to interfaces are OVS bridge port\n", aci);
ecm_db_connection_deref(ci);
goto not_relevant;
}
/*
* Is there an external callback to get the ovs value from the packet?
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
cb = ovs.ovs_process;
if (!cb) {
/*
* Allow acceleration.
* Keep the classifier relevant to connection for stats update..
*/
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_WARN("%px: No external process callback set\n", aci);
if (from_dev)
dev_put(from_dev);
if (to_dev)
dev_put(to_dev);
spin_lock_bh(&ecm_classifier_ovs_lock);
goto allow_accel;
}
spin_unlock_bh(&ecm_classifier_ovs_lock);
/*
* If the flow is a routed flow, set the is_routed flag of the flow.
*/
if (ecm_db_connection_is_routed_get(ci)) {
ecm_classifier_ovs_process_route_flow(ecvi, ci, skb, from_dev, to_dev, process_response, cb);
if (from_dev)
dev_put(from_dev);
if (to_dev)
dev_put(to_dev);
ecm_db_connection_deref(ci);
return;
}
memset(&flow, 0, sizeof(struct ovsmgr_dp_flow));
/*
* If the connection is a bridge flow, both devices should be present.
* TODO: This is an unexpected situation and should be assertion.
* We should also make sure that both devices are on the same OVS bridge.
*/
if (!from_dev || !to_dev) {
DEBUG_ERROR("%px: One of the ports is NULL from_dev: %px to_dev: %px\n", aci, from_dev, to_dev);
if (from_dev)
dev_put(from_dev);
if (to_dev)
dev_put(to_dev);
ecm_db_connection_deref(ci);
goto not_relevant;
}
/*
* Flow is an OVS bridge flow.
*/
flow.tuple.ip_version = ecm_db_connection_ip_version_get(ci);
flow.tuple.protocol = ecm_db_connection_protocol_get(ci);
/*
* For the flow lookup, we need to use the proper 5-tuple, in/out dev and
* src/dest MAC address. If the packet is coming from the destination side of the connection
* (e.g: ACK packets of the TCP connection) these values should be reversed.
*/
if (sender == ECM_TRACKER_SENDER_TYPE_SRC) {
DEBUG_TRACE("%px: sender is SRC\n", aci);
flow.indev = from_dev;
flow.outdev = to_dev;
flow.tuple.src_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
flow.tuple.dst_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, flow.smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, flow.dmac);
} else {
DEBUG_TRACE("%px: sender is DEST\n", aci);
flow.indev = to_dev;
flow.outdev = from_dev;
flow.tuple.src_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
flow.tuple.dst_port = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, dst_ip);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, flow.smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, flow.dmac);
}
if (flow.tuple.ip_version == 4) {
ECM_IP_ADDR_TO_NIN4_ADDR(flow.tuple.ipv4.src, src_ip);
ECM_IP_ADDR_TO_NIN4_ADDR(flow.tuple.ipv4.dst, dst_ip);
} else if (flow.tuple.ip_version == 6) {
ECM_IP_ADDR_TO_NIN6_ADDR(flow.tuple.ipv6.src, src_ip);
ECM_IP_ADDR_TO_NIN6_ADDR(flow.tuple.ipv6.dst, dst_ip);
} else {
DEBUG_ASSERT(NULL, "%px: unexpected ip_version: %d", aci, flow.tuple.ip_version );
}
memset(&resp, 0, sizeof(struct ecm_classifier_ovs_process_response));
/*
* Set default values for flow and return DSCP.
* External module will call the DSCP query twice to
* find both directions' values.
*/
resp.flow_dscp = OVSMGR_INVALID_DSCP;
resp.return_dscp = OVSMGR_INVALID_DSCP;
/*
* Call the external callback and get the result.
*/
result = cb(&flow, skb, &resp);
if (from_dev)
dev_put(from_dev);
if (to_dev)
dev_put(to_dev);
/*
* Handle the result
*/
switch (result) {
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_ACCEL:
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL:
/*
* Allow accel after setting the external module response.
*/
DEBUG_WARN("%px: External callback process succeeded\n", aci);
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.ingress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.egress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.ingress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.egress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
/*
* Primary VLAN tag is always present even it is QinQ.
*/
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG;
/*
* If the sender type is source, which means the packet is coming from the originator,
* we assign the ECM-ingress<>OVS-ingress, ECM-egress<>OVS-egress values.
*/
if (sender == ECM_TRACKER_SENDER_TYPE_SRC) {
if (resp.ingress_vlan[0].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[0] = resp.ingress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.ingress_vlan[0].h_vlan_TCI;
DEBUG_TRACE("Ingress vlan tag[0] set : 0x%x\n", ecvi->process_response.ingress_vlan_tag[0]);
}
if (resp.egress_vlan[0].h_vlan_TCI) {
ecvi->process_response.egress_vlan_tag[0] = resp.egress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[0].h_vlan_TCI;
DEBUG_TRACE("Egress vlan tag[0] set : 0x%x\n", ecvi->process_response.egress_vlan_tag[0]);
}
if (result == ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL) {
if (resp.ingress_vlan[1].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[1] = resp.ingress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.ingress_vlan[1].h_vlan_TCI;
DEBUG_TRACE("Ingress vlan tag[0] set : 0x%x\n", ecvi->process_response.ingress_vlan_tag[1]);
}
if (resp.egress_vlan[1].h_vlan_TCI) {
ecvi->process_response.egress_vlan_tag[1] = resp.egress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[1].h_vlan_TCI;
DEBUG_TRACE("Egress vlan tag[1] set : 0x%x\n", ecvi->process_response.egress_vlan_tag[1]);
}
/*
* QinQ tag is present. Let's pass this information to the frontend through the process action flag.
*/
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG;
}
} else {
/*
* Sender type is destination, which means packet si coming from the counter originator side,
* we assign the ECM-ingress<>OVS-egress and ECM-egress<>OVS-ingress values.
*/
if (resp.ingress_vlan[0].h_vlan_TCI) {
ecvi->process_response.egress_vlan_tag[0] = resp.ingress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.ingress_vlan[0].h_vlan_TCI;
DEBUG_TRACE("Egress vlan tag[0] set : 0x%x\n", ecvi->process_response.egress_vlan_tag[0]);
}
if (resp.egress_vlan[0].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[0] = resp.egress_vlan[0].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[0].h_vlan_TCI;
DEBUG_TRACE("Ingress vlan tag[0] set : 0x%x\n", ecvi->process_response.ingress_vlan_tag[0]);
}
if (result == ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL) {
if (resp.ingress_vlan[1].h_vlan_TCI) {
ecvi->process_response.egress_vlan_tag[1] = resp.ingress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.ingress_vlan[1].h_vlan_TCI;
DEBUG_TRACE("Egress vlan tag[0] set : 0x%x\n", ecvi->process_response.egress_vlan_tag[1]);
}
if (resp.egress_vlan[1].h_vlan_TCI) {
ecvi->process_response.ingress_vlan_tag[1] = resp.egress_vlan[1].h_vlan_encapsulated_proto << 16 | resp.egress_vlan[1].h_vlan_TCI;
DEBUG_TRACE("Ingress vlan tag[1] set : 0x%x\n", ecvi->process_response.ingress_vlan_tag[1]);
}
/*
* QinQ tag is present. Let's pass this information to the frontend through the process action flag.
*/
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG;
}
}
break;
case ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL:
/*
* External callback failed to process VLAN process. So, let's deny the acceleration
* and try more with the subsequent packets.
*/
DEBUG_WARN("%px: External callback failed to process VLAN tags\n", aci);
goto deny_accel;
case ECM_CLASSIFIER_OVS_RESULT_ALLOW_ACCEL:
/*
* There is no VLAN tag in the flow. Just allow the acceleration.
*/
DEBUG_WARN("%px: External callback didn't find any VLAN relation\n", aci);
spin_lock_bh(&ecm_classifier_ovs_lock);
break;
default:
DEBUG_ASSERT(false, "Unhandled result: %d\n", result);
}
/*
* If external module set any of the flow or return DSCP,
* we copy them to the classifier's process response based
* on the direction of the traffic. The external module
* should always set first the flow and then the return values.
*/
if ((resp.flow_dscp != OVSMGR_INVALID_DSCP) || (resp.return_dscp != OVSMGR_INVALID_DSCP)) {
if (sender == ECM_TRACKER_SENDER_TYPE_SRC) {
/*
* Copy DSCP values
*/
ecvi->process_response.flow_dscp = (resp.flow_dscp != OVSMGR_INVALID_DSCP) ? resp.flow_dscp >> XT_DSCP_SHIFT : 0;
ecvi->process_response.return_dscp = (resp.return_dscp != OVSMGR_INVALID_DSCP) ? resp.return_dscp >> XT_DSCP_SHIFT : ecvi->process_response.flow_dscp;
DEBUG_TRACE("FLOW DSCP: 0x%x RETURN DSCP: 0x%x\n",
ecvi->process_response.flow_dscp, ecvi->process_response.return_dscp);
} else {
/*
* Copy DSCP values
*/
ecvi->process_response.flow_dscp = (resp.return_dscp != OVSMGR_INVALID_DSCP) ? resp.return_dscp >> XT_DSCP_SHIFT : 0;
ecvi->process_response.return_dscp = (resp.flow_dscp != OVSMGR_INVALID_DSCP) ? resp.flow_dscp >> XT_DSCP_SHIFT : ecvi->process_response.flow_dscp;
DEBUG_TRACE("FLOW DSCP: 0x%x RETURN DSCP: 0x%x\n",
ecvi->process_response.flow_dscp, ecvi->process_response.return_dscp);
}
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_DSCP;
}
allow_accel:
/*
* Acceleration is permitted
*/
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_YES;
ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_ACCEL_MODE;
ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_ACCEL;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
ecm_db_connection_deref(ci);
return;
not_relevant:
/*
* ecm_classifier_ovs_lock MUST be held
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
return;
deny_accel:
/*
* ecm_classifier_ovs_lock MUST be held
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_YES;
ecvi->process_response.process_actions = ECM_CLASSIFIER_PROCESS_ACTION_ACCEL_MODE;
ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO;
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
ecm_db_connection_deref(ci);
}
/*
* ecm_classifier_ovs_type_get()
* Get type of classifier this is
*/
static ecm_classifier_type_t ecm_classifier_ovs_type_get(struct ecm_classifier_instance *aci)
{
struct ecm_classifier_ovs_instance *ecvi;
ecvi = (struct ecm_classifier_ovs_instance *)aci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
return ECM_CLASSIFIER_TYPE_OVS;
}
/*
* ecm_classifier_ovs_reclassify_allowed()
* Get whether reclassification is allowed
*/
static bool ecm_classifier_ovs_reclassify_allowed(struct ecm_classifier_instance *aci)
{
struct ecm_classifier_ovs_instance *ecvi;
ecvi = (struct ecm_classifier_ovs_instance *)aci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
return true;
}
/*
* ecm_classifier_ovs_reclassify()
* Reclassify
*/
static void ecm_classifier_ovs_reclassify(struct ecm_classifier_instance *aci)
{
struct ecm_classifier_ovs_instance *ecvi;
ecvi = (struct ecm_classifier_ovs_instance *)aci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
/*
* Revert back to MAYBE relevant - we will evaluate when we get the next process() call.
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE;
spin_unlock_bh(&ecm_classifier_ovs_lock);
}
/*
* ecm_classifier_ovs_last_process_response_get()
* Get result code returned by the last process call
*/
static void ecm_classifier_ovs_last_process_response_get(struct ecm_classifier_instance *aci,
struct ecm_classifier_process_response *process_response)
{
struct ecm_classifier_ovs_instance *ecvi;
ecvi = (struct ecm_classifier_ovs_instance *)aci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
spin_lock_bh(&ecm_classifier_ovs_lock);
*process_response = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
}
/*
* ecm_classifier_ovs_stats_sync()
* Sync the stats of the OVS flow.
*/
static inline void ecm_classifier_ovs_stats_sync(struct ovsmgr_dp_flow *flow,
uint32_t pkts, uint32_t bytes,
struct net_device *indev, struct net_device *outdev,
uint8_t *smac, uint8_t *dmac,
ip_addr_t sip, ip_addr_t dip,
uint16_t sport, uint16_t dport, uint16_t tci, uint16_t tpid)
{
struct ovsmgr_dp_flow_stats stats;
flow->indev = indev;
flow->outdev = outdev;
ether_addr_copy(flow->smac, smac);
ether_addr_copy(flow->dmac, dmac);
/*
* Set default VLAN tci and vlan encapsulated proto.
*/
flow->ingress_vlan.h_vlan_TCI = 0;
flow->ingress_vlan.h_vlan_encapsulated_proto = 0;
if (tci) {
flow->ingress_vlan.h_vlan_TCI = tci;
flow->ingress_vlan.h_vlan_encapsulated_proto = tpid;
}
flow->tuple.src_port = sport;
flow->tuple.dst_port = dport;
if (flow->tuple.ip_version == 4) {
ECM_IP_ADDR_TO_NIN4_ADDR(flow->tuple.ipv4.src, sip);
ECM_IP_ADDR_TO_NIN4_ADDR(flow->tuple.ipv4.dst, dip);
DEBUG_TRACE("%px: STATS: src MAC: %pM src_dev: %s src: %pI4:%d proto: %d dest: %pI4:%d dest_dev: %s dest MAC: %pM TCI: %x, TPID: %x\n",
flow, flow->smac, flow->indev->name, &flow->tuple.ipv4.src, flow->tuple.src_port, flow->tuple.protocol,
&flow->tuple.ipv4.dst, flow->tuple.dst_port, flow->outdev->name, flow->dmac,
flow->ingress_vlan.h_vlan_TCI, flow->ingress_vlan.h_vlan_encapsulated_proto);
} else {
ECM_IP_ADDR_TO_NIN6_ADDR(flow->tuple.ipv6.src, sip);
ECM_IP_ADDR_TO_NIN6_ADDR(flow->tuple.ipv6.dst, dip);
DEBUG_TRACE("%px: STATS: src MAC: %pM src_dev: %s src: %pI6:%d proto: %d dest: %pI6:%d dest_dev: %s dest MAC: %pM, TCI: %x, TPID: %x\n",
flow, flow->smac, flow->indev->name, &flow->tuple.ipv6.src, flow->tuple.src_port, flow->tuple.protocol,
&flow->tuple.ipv6.dst, flow->tuple.dst_port, flow->outdev->name, flow->dmac,
flow->ingress_vlan.h_vlan_TCI, flow->ingress_vlan.h_vlan_encapsulated_proto);
}
/*
* Set the flow stats and update the flow database.
*/
stats.pkts = pkts;
stats.bytes = bytes;
ovsmgr_flow_stats_update(flow, &stats);
}
#ifdef ECM_MULTICAST_ENABLE
/*
* ecm_classifier_ovs_multicast_sync_to_stats()
* Common multicast sync_to function for IPv4 and IPv6.
*/
static void ecm_classifier_ovs_multicast_sync_to_stats(struct ecm_classifier_ovs_instance *ecvi,
struct ecm_db_connection_instance *ci, struct ecm_classifier_rule_sync *sync)
{
struct net_device *from_dev;
struct net_device *to_ovs_port[ECM_DB_MULTICAST_IF_MAX];
struct net_device *to_ovs_brdev[ECM_DB_MULTICAST_IF_MAX];
struct net_device *br_dev;
ip_addr_t src_ip;
ip_addr_t dst_ip;
uint8_t smac[ETH_ALEN];
uint8_t dmac[ETH_ALEN];
uint16_t sport;
uint16_t dport;
struct ovsmgr_dp_flow flow;
int if_cnt, i, valid_ifcnt = 0, ifindex;
uint16_t tpid = 0, tci = 0;
/*
* Get the possible OVS bridge ports.
*/
from_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, true);
if_cnt = ecm_interface_multicast_ovs_to_interface_get_and_ref(ci, to_ovs_port, to_ovs_brdev);
if (!from_dev && !if_cnt) {
DEBUG_WARN("%px: None of the from/to interfaces is OVS bridge port\n", ci);
return;
}
memset(&flow, 0, sizeof(flow));
/*
* IP version and protocol are common for routed and bridge flows.
*/
flow.tuple.ip_version = ecm_db_connection_ip_version_get(ci);
flow.tuple.protocol = ecm_db_connection_protocol_get(ci);
flow.is_routed = ecm_db_connection_is_routed_get(ci);
if (!flow.is_routed) {
/* For multicast bridge flows, ovs creates a single rule with multiple
* egress interfaces. We need only one egress interface to sync the flow statistics.
*
* Sync the flow direction.
* eth1 to eth2
*/
sport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
dport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, dmac);
if (ecvi->process_response.ingress_vlan_tag[0] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
tci = ecvi->process_response.ingress_vlan_tag[0] & 0xffff;
tpid = (ecvi->process_response.ingress_vlan_tag[0] >> 16) & 0xffff;
DEBUG_TRACE("%px: Ingress VLAN : %x:%x\n", ci, tci, tpid);
}
ecm_classifier_ovs_stats_sync(&flow,
sync->rx_packet_count[ECM_CONN_DIR_FLOW], sync->rx_byte_count[ECM_CONN_DIR_FLOW],
from_dev, to_ovs_port[0],
smac, dmac,
src_ip, dst_ip,
sport, dport, tci, tpid);
goto done;
}
/*
* The 'to' interfaces ports can be part of different OVS bridges.
* ovs-br1-(eth0, eth1, eth2)
* ovs-br2-(eth3)
* ovs-br3 (eth4, eth5)
*
* Find unique ovs port and ovs bridge combination, as for statistics
* update we need only one port from each OVS bridge.
*
* The logic will derive the below output for the
* above mentioned ovs 'to' interface list.
* to_ovs_port: eth0, eth3, eth4
* to_ovs_brdev: ovs-br1, ovs-br2, ovs-br3
*
*/
if (if_cnt) {
ifindex = to_ovs_brdev[0]->ifindex;
for (i = 1, valid_ifcnt = 1; i < if_cnt; i++) {
if (ifindex == to_ovs_brdev[i]->ifindex) {
dev_put(to_ovs_port[i]);
continue;
}
to_ovs_port[valid_ifcnt] = to_ovs_port[i];
to_ovs_brdev[valid_ifcnt] = to_ovs_brdev[i];
valid_ifcnt++;
}
}
/*
* For routed flows below configurations are possible.
*
* 1. From interface is OVS bridge port, to interface is
* another OVS bridge port
* PC1 -----> eth1-ovs-br1--->ovs-br2-eth2----->PC2
*
* 2. From interface is OVS bridge port, multiple to
* OVS bridge port (OVS master is same).
* PC1 -----> eth1-ovs-br1--->ovs-br2-eth2,eth3----->PC2
*
* 3. From interface is OVS bridge port, multiple to
* OVS bridge port (OVS masters are different).
* PC1 -----> eth1-ovs-br1--->ovs-br2-eth2/ovs-br3-eth3----->PC2
*/
if (from_dev) {
/*
* from_dev = eth1
* br_dev = ovs-br1
*/
br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, false);
if (!br_dev) {
DEBUG_WARN("%px: from_dev = %s is a OVS bridge port, bridge interface is not found\n",
ci, from_dev->name);
goto done;
}
/*
* Sync the flow direction.
* eth1 to ovs-br1
*/
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, dmac);
sport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
dport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
if (ecvi->process_response.ingress_vlan_tag[0] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
tci = ecvi->process_response.ingress_vlan_tag[0] & 0xffff;
tpid = (ecvi->process_response.ingress_vlan_tag[0] >> 16) & 0xffff;
DEBUG_TRACE("%px: Ingress VLAN : %x:%x\n", ci, tci, tpid);
}
ecm_classifier_ovs_stats_sync(&flow,
sync->rx_packet_count[ECM_CONN_DIR_FLOW], sync->rx_byte_count[ECM_CONN_DIR_FLOW],
from_dev, br_dev,
smac, dmac,
src_ip, dst_ip,
sport, dport, tci, tpid);
dev_put(br_dev);
}
/*
* For routed flow, to side can be multiple OVS bridge ports and
* the ports can be part of different OVS bridge master.
*
* 1. outdev = eth2
* indev = ovs-br2
* Sync the flow direction.
* ovs-br2 to eth2
*
* 2. outdev = eth3
* indev = ovs-br2
* Sync the flow direction.
* ovs-br2 to eth3
*
* 3. outdev = eth3
* indev = ovs-br3
* Sync the flow direction.
* ovs-br3 to eth3
*
*/
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, dmac);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
for (i = 0; i < valid_ifcnt; i++) {
ether_addr_copy(smac, to_ovs_brdev[i]->dev_addr);
sport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
dport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
/*
* tci and tpid is 0 for pkts received from ovs bridge
*/
tci = tpid = 0;
ecm_classifier_ovs_stats_sync(&flow,
sync->rx_packet_count[ECM_CONN_DIR_FLOW], sync->rx_byte_count[ECM_CONN_DIR_FLOW],
to_ovs_brdev[i], to_ovs_port[i],
smac, dmac,
src_ip, dst_ip,
sport, dport, tci, tpid);
}
done:
if (from_dev)
dev_put(from_dev);
for (i = 0; i < valid_ifcnt; i++) {
dev_put(to_ovs_port[i]);
}
}
#endif
/*
* ecm_classifier_ovs_sync_to_stats()
* Common sync_to function for IPv4 and IPv6.
*/
static void ecm_classifier_ovs_sync_to_stats(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
{
ip_addr_t src_ip;
ip_addr_t dst_ip;
struct ecm_db_connection_instance *ci;
struct ovsmgr_dp_flow flow;
struct net_device *from_dev;
struct net_device *to_dev;
struct net_device *br_dev;
uint8_t smac[ETH_ALEN];
uint8_t dmac[ETH_ALEN];
uint16_t sport;
uint16_t dport;
uint16_t tpid = 0, tci = 0;
struct ecm_classifier_ovs_instance *ecvi = (struct ecm_classifier_ovs_instance *)aci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
ci = ecm_db_connection_serial_find_and_ref(ecvi->ci_serial);
if (!ci) {
DEBUG_TRACE("%px: No ci found for %u\n", ecvi, ecvi->ci_serial);
return;
}
#ifdef ECM_MULTICAST_ENABLE
/*
* Check for multicast connection.
*/
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
if (ecm_ip_addr_is_multicast(dst_ip)) {
ecm_classifier_ovs_multicast_sync_to_stats(ecvi, ci, sync);
ecm_db_connection_deref(ci);
return;
}
#endif
memset(&flow, 0, sizeof(flow));
/*
* Get the possible OVS bridge ports.
*/
from_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, true);
to_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_TO, true);
/*
* IP version and protocol are common for routed and bridge flows.
*/
flow.tuple.ip_version = ecm_db_connection_ip_version_get(ci);
flow.tuple.protocol = ecm_db_connection_protocol_get(ci);
flow.is_routed = ecm_db_connection_is_routed_get(ci);
/*
* Get the tci and tpid values of the ingress side of the flow.
*/
if (ecvi->process_response.ingress_vlan_tag[0] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
tci = ecvi->process_response.ingress_vlan_tag[0] & 0xffff;
tpid = (ecvi->process_response.ingress_vlan_tag[0] >> 16) & 0xffff;
DEBUG_TRACE("%px: Ingress VLAN : %x:%x\n", aci, tci, tpid);
}
/*
* Bridge flow
*/
if (!flow.is_routed) {
/*
* Sync the flow direction (eth1 to eth2)
*
* ECM depends on netdev private flags to identify
* if it is an OVS port, if the port is removed from
* bridge while traffic is running then the device is
* not part of bridge. Do not update statistics if ports
* are removed from bridge.
*/
if (!from_dev || !to_dev) {
ecm_db_connection_deref(ci);
return;
}
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, dmac);
sport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
dport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
DEBUG_TRACE("%px: Flow direction stats update\n", aci);
ecm_classifier_ovs_stats_sync(&flow,
sync->rx_packet_count[ECM_CONN_DIR_FLOW], sync->rx_byte_count[ECM_CONN_DIR_FLOW],
from_dev, to_dev,
smac, dmac,
src_ip, dst_ip,
sport, dport, tci, tpid);
/*
* Sync the return direction (eth2 to eth1)
* All the flow parameters are reversed.
*/
DEBUG_TRACE("%px: Return direction stats update\n", aci);
/*
* Reset the tci and tpid values and get the egress side of the flow.
*/
tci = tpid = 0;
if (ecvi->process_response.egress_vlan_tag[0] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
tci = ecvi->process_response.egress_vlan_tag[0] & 0xffff;
tpid = (ecvi->process_response.egress_vlan_tag[0] >> 16) & 0xffff;
DEBUG_TRACE("%px: Egress VLAN : %x:%x\n", aci, tci, tpid);
}
ecm_classifier_ovs_stats_sync(&flow,
sync->rx_packet_count[ECM_CONN_DIR_RETURN], sync->rx_byte_count[ECM_CONN_DIR_RETURN],
to_dev, from_dev,
dmac, smac,
dst_ip, src_ip,
dport, sport, tci, tpid);
goto done;
}
/*
* For routed flows both from and to side can be OVS bridge port, if there
* is a routed flow between two OVS bridges. (e.g: ovs-br1 and ovs-br2)
*
* These are the netdevice places and the IP address values based on the
* 4 different NAT cases.
*
* SNAT:
* PC1 -----> eth1-ovs-br1--->ovs-br2-eth2 -----> PC2
* (from_dev) (to_dev)
* src_ip src_ip_nat dest_ip/dest_ip_nat
*
* DNAT
* PC1 <----- eth1-ovs-br1<---ovs-br2-eth2 <----- PC2
* (to_dev) (from_dev)
* dest_ip dest_ip_nat src_ip/src_ip_nat
*
* Non-NAT - Egress
* PC1 -----> eth1-ovs-br1--->ovs-br2-eth2 -----> PC2
* (from_dev) (to_dev)
* src_ip/src_ip_nat dest_ip/dest_ip_nat
*
* Non-NAT - Ingress
* PC1 <----- eth1-ovs-br1<---ovs-br2-eth2 <----- PC2
* (to_dev) (from_dev)
* dest_ip/dest_ip_nat src_ip/src_ip_nat
*/
if (from_dev) {
/*
* from_dev = eth1/eth2 (can be tagged)
* br_dev = ovs-br1/ovs_br2 (untagged)
*/
br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, false);
if (!br_dev) {
DEBUG_WARN("%px: from_dev = %s is a OVS bridge port, bridge interface is not found\n",
aci, from_dev->name);
goto done;
}
/*
* Sync the flow direction (eth1/eth2 to ovs-br1/ovs_br2) based on the NAT case.
*/
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, smac);
ether_addr_copy(dmac, br_dev->dev_addr);
/*
* If from_dev is a bridge port, dest_ip_nat and dest_port_nat satisfies all the NAT cases.
* So, we need to get the ECM_DB_OBJ_DIR_TO_NAT direction's IP and port number from the connection.
*/
sport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM));
dport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO_NAT));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO_NAT, dst_ip);
ecm_classifier_ovs_stats_sync(&flow,
sync->rx_packet_count[ECM_CONN_DIR_FLOW], sync->rx_byte_count[ECM_CONN_DIR_FLOW],
from_dev, br_dev,
smac, dmac,
src_ip, dst_ip,
sport, dport, tci, tpid);
/*
* Sync the return direction (ovs-br1/ovs_br2 to eth1/eth2) based on the NAT case.
* All the flow parameters are reversed.
*/
ecm_classifier_ovs_stats_sync(&flow,
sync->tx_packet_count[ECM_CONN_DIR_FLOW], sync->tx_byte_count[ECM_CONN_DIR_FLOW],
br_dev, from_dev,
dmac, smac,
dst_ip, src_ip,
dport, sport, 0, 0);
dev_put(br_dev);
}
if (to_dev) {
/*
* to_dev = eth2/eth1 (can be tagged)
* br_dev = ovs-br2/ovs-br1 (untagged)
*/
br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_TO, false);
if (!br_dev) {
DEBUG_WARN("%px: to_dev = %s is a OVS bridge port, bridge interface is not found\n",
aci, to_dev->name);
goto done;
}
/*
* Reset the tci and tpid values and get the egress side of the flow.
*/
tci = tpid = 0;
if (ecvi->process_response.egress_vlan_tag[0] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
tci = ecvi->process_response.egress_vlan_tag[0] & 0xffff;
tpid = (ecvi->process_response.egress_vlan_tag[0] >> 16) & 0xffff;
DEBUG_TRACE("%px: Egress VLAN : %x:%x\n", aci, tci, tpid);
}
/*
* Sync the flow direction (ovs-br2/ovs_br1 to eth2/eth1) based on the NAT case.
*/
ether_addr_copy(smac, br_dev->dev_addr);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, dmac);
/*
* If to_dev is a bridge port, src_ip_nat and src_port_nat satisfies all the NAT cases.
* So, we need to get the ECM_DB_OBJ_DIR_FROM_NAT direction's IP and port number from the connection.
*/
sport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM_NAT));
dport = htons(ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO));
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM_NAT, src_ip);
ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
ecm_classifier_ovs_stats_sync(&flow,
sync->tx_packet_count[ECM_CONN_DIR_RETURN], sync->tx_byte_count[ECM_CONN_DIR_RETURN],
br_dev, to_dev,
smac, dmac,
src_ip, dst_ip,
sport, dport, 0, 0);
/*
* Sync the return direction (eth2/eth1 to ovs-br2/ovs-br1) based on the NAT case.
* All the flow parameters are reversed.
*/
ecm_classifier_ovs_stats_sync(&flow,
sync->rx_packet_count[ECM_CONN_DIR_RETURN], sync->rx_byte_count[ECM_CONN_DIR_RETURN],
to_dev, br_dev,
dmac, smac,
dst_ip, src_ip,
dport, sport, tci, tpid);
dev_put(br_dev);
}
done:
ecm_db_connection_deref(ci);
if (from_dev)
dev_put(from_dev);
if (to_dev)
dev_put(to_dev);
}
/*
* ecm_classifier_ovs_sync_to_v4()
* Front end is pushing accel engine state to us
*/
static void ecm_classifier_ovs_sync_to_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
{
/*
* Nothing to update.
* We only care about flows that are actively being accelerated.
*/
if (!(sync->tx_packet_count[ECM_CONN_DIR_FLOW] || sync->tx_packet_count[ECM_CONN_DIR_RETURN])) {
return;
}
/*
* Handle only the stats sync. We don't care about the evict or flush syncs.
*/
if (sync->reason != ECM_FRONT_END_IPV4_RULE_SYNC_REASON_STATS) {
return;
}
/*
* Common sync_to function call.
*/
ecm_classifier_ovs_sync_to_stats(aci, sync);
}
/*
* ecm_classifier_ovs_sync_from_v4()
* Front end is retrieving accel engine state from us
*/
static void ecm_classifier_ovs_sync_from_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc)
{
struct ecm_classifier_ovs_instance *ecvi __attribute__((unused));
ecvi = (struct ecm_classifier_ovs_instance *)aci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
}
/*
* ecm_classifier_ovs_sync_to_v6()
* Front end is pushing accel engine state to us
*/
static void ecm_classifier_ovs_sync_to_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
{
/*
* Nothing to update.
* We only care about flows that are actively being accelerated.
*/
if (!(sync->tx_packet_count[ECM_CONN_DIR_FLOW] || sync->tx_packet_count[ECM_CONN_DIR_RETURN])) {
return;
}
/*
* Handle only the stats sync. We don't care about the evict or flush syncs.
*/
if (sync->reason != ECM_FRONT_END_IPV6_RULE_SYNC_REASON_STATS) {
return;
}
/*
* Common sync_to function call.
*/
ecm_classifier_ovs_sync_to_stats(aci, sync);
}
/*
* ecm_classifier_ovs_sync_from_v6()
* Front end is retrieving accel engine state from us
*/
static void ecm_classifier_ovs_sync_from_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc)
{
struct ecm_classifier_ovs_instance *ecvi __attribute__((unused));
ecvi = (struct ecm_classifier_ovs_instance *)aci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
}
#ifdef ECM_STATE_OUTPUT_ENABLE
/*
* ecm_classifier_ovs_state_get()
* Gets the state of this classfier and outputs it to debugfs.
*/
static int ecm_classifier_ovs_state_get(struct ecm_classifier_instance *ci, struct ecm_state_file_instance *sfi)
{
int result;
struct ecm_classifier_ovs_instance *ecvi;
struct ecm_classifier_process_response pr;
ecvi = (struct ecm_classifier_ovs_instance *)ci;
DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%px: magic failed", ecvi);
if ((result = ecm_state_prefix_add(sfi, "ovs"))) {
return result;
}
spin_lock_bh(&ecm_classifier_ovs_lock);
pr = ecvi->process_response;
spin_unlock_bh(&ecm_classifier_ovs_lock);
/*
* Output our last process response
*/
if ((result = ecm_classifier_process_response_state_get(sfi, &pr))) {
goto done;
}
if (pr.process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG) {
#ifdef ECM_MULTICAST_ENABLE
int i;
#endif
/*
* TODO: Clean up the function later to print classifier
* specific data in each classifier’s state_get function.
*/
if (pr.ingress_vlan_tag[0] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
if ((result = ecm_state_write(sfi, "ingress_vlan_tag[0]", "0x%x", pr.ingress_vlan_tag[0]))) {
goto done;
}
}
if (pr.ingress_vlan_tag[1] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
if ((result = ecm_state_write(sfi, "ingress_vlan_tag[1]", "0x%x", pr.ingress_vlan_tag[1]))) {
goto done;
}
}
if (pr.egress_vlan_tag[0] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
if ((result = ecm_state_write(sfi, "egress_vlan_tag[0]", "0x%x", pr.egress_vlan_tag[0]))) {
goto done;
}
}
if (pr.egress_vlan_tag[1] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
if ((result = ecm_state_write(sfi, "egress_vlan_tag[1]", "0x%x", pr.egress_vlan_tag[1]))) {
goto done;
}
}
#ifdef ECM_MULTICAST_ENABLE
for (i = 0; i < ECM_DB_MULTICAST_IF_MAX; i++) {
struct net_device *dev;
if (pr.egress_mc_vlan_tag[i][0] == ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
continue;
}
dev = dev_get_by_index(&init_net, pr.egress_netdev_index[i]);
if (dev) {
if ((result = ecm_state_write(sfi, "port_egress", "%s", dev->name))) {
dev_put(dev);
goto done;
}
dev_put(dev);
}
if ((result = ecm_state_write(sfi, "port_egress_vlan_tag[0]", "0x%x", pr.egress_mc_vlan_tag[i][0]))) {
goto done;
}
if (pr.egress_mc_vlan_tag[i][1] != ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) {
if ((result = ecm_state_write(sfi, "port_egress_vlan_tag[1]", "0x%x", pr.egress_mc_vlan_tag[i][1]))) {
goto done;
}
}
}
#endif
}
done:
ecm_state_prefix_remove(sfi);
return result;
}
#endif
/*
* ecm_classifier_ovs_instance_alloc()
* Allocate an instance of the ovs classifier
*/
struct ecm_classifier_ovs_instance *ecm_classifier_ovs_instance_alloc(struct ecm_db_connection_instance *ci)
{
struct ecm_classifier_ovs_instance *ecvi;
#ifdef ECM_MULTICAST_ENABLE
int i;
#endif
/*
* Allocate the instance
*/
ecvi = (struct ecm_classifier_ovs_instance *)kzalloc(sizeof(struct ecm_classifier_ovs_instance), GFP_ATOMIC | __GFP_NOWARN);
if (!ecvi) {
DEBUG_WARN("i%px: Failed to allocate ovs Classifier instance\n", ci);
return NULL;
}
DEBUG_SET_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC);
ecvi->refs = 1;
/*
* Methods generic to all classifiers.
*/
ecvi->base.process = ecm_classifier_ovs_process;
ecvi->base.sync_from_v4 = ecm_classifier_ovs_sync_from_v4;
ecvi->base.sync_to_v4 = ecm_classifier_ovs_sync_to_v4;
ecvi->base.sync_from_v6 = ecm_classifier_ovs_sync_from_v6;
ecvi->base.sync_to_v6 = ecm_classifier_ovs_sync_to_v6;
ecvi->base.type_get = ecm_classifier_ovs_type_get;
ecvi->base.reclassify_allowed = ecm_classifier_ovs_reclassify_allowed;
ecvi->base.reclassify = ecm_classifier_ovs_reclassify;
ecvi->base.last_process_response_get = ecm_classifier_ovs_last_process_response_get;
#ifdef ECM_STATE_OUTPUT_ENABLE
ecvi->base.state_get = ecm_classifier_ovs_state_get;
#endif
ecvi->base.ref = ecm_classifier_ovs_ref;
ecvi->base.deref = ecm_classifier_ovs_deref;
ecvi->ci_serial = ecm_db_connection_serial_get(ci);
ecvi->process_response.process_actions = 0;
ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE;
ecvi->process_response.ingress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.egress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.ingress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.egress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
#ifdef ECM_MULTICAST_ENABLE
for (i = 0; i < ECM_DB_MULTICAST_IF_MAX; i++) {
ecvi->process_response.egress_mc_vlan_tag[i][0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
ecvi->process_response.egress_mc_vlan_tag[i][1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
}
#endif
/*
* Final check if we are pending termination
*/
spin_lock_bh(&ecm_classifier_ovs_lock);
if (ecm_classifier_ovs_terminate_pending) {
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_WARN("%px: Terminating\n", ci);
kfree(ecvi);
return NULL;
}
/*
* Link the new instance into our list at the head
*/
ecvi->next = ecm_classifier_ovs_instances;
if (ecm_classifier_ovs_instances) {
ecm_classifier_ovs_instances->prev = ecvi;
}
ecm_classifier_ovs_instances = ecvi;
/*
* Increment stats
*/
ecm_classifier_ovs_count++;
DEBUG_ASSERT(ecm_classifier_ovs_count > 0, "%px: ecm_classifier_ovs_count wrap for instance: %px\n", ci, ecvi);
spin_unlock_bh(&ecm_classifier_ovs_lock);
DEBUG_INFO("%px: ovs classifier instance alloc: %px\n", ci, ecvi);
return ecvi;
}
EXPORT_SYMBOL(ecm_classifier_ovs_instance_alloc);
/*
* ecm_classifier_ovs_register_callbacks()
*/
int ecm_classifier_ovs_register_callbacks(struct ecm_classifier_ovs_callbacks *ovs_cbs)
{
spin_lock_bh(&ecm_classifier_ovs_lock);
if (unlikely(!ecm_classifier_ovs_enabled)) {
spin_unlock_bh(&ecm_classifier_ovs_lock);
return -1;
}
ovs.ovs_process = ovs_cbs->ovs_process;
spin_unlock_bh(&ecm_classifier_ovs_lock);
return 0;
}
EXPORT_SYMBOL(ecm_classifier_ovs_register_callbacks);
/*
* ecm_classifier_ovs_unregister_callbacks()
*/
void ecm_classifier_ovs_unregister_callbacks(void)
{
spin_lock_bh(&ecm_classifier_ovs_lock);
ovs.ovs_process = NULL;
spin_unlock_bh(&ecm_classifier_ovs_lock);
}
EXPORT_SYMBOL(ecm_classifier_ovs_unregister_callbacks);
/*
* ecm_classifier_ovs_init()
*/
int ecm_classifier_ovs_init(struct dentry *dentry)
{
DEBUG_INFO("ovs classifier Module init\n");
ecm_classifier_ovs_dentry = debugfs_create_dir("ecm_classifier_ovs", dentry);
if (!ecm_classifier_ovs_dentry) {
DEBUG_ERROR("Failed to create ecm ovs directory in debugfs\n");
return -1;
}
if (!debugfs_create_u32("enabled", S_IRUGO | S_IWUSR, ecm_classifier_ovs_dentry,
(u32 *)&ecm_classifier_ovs_enabled)) {
DEBUG_ERROR("Failed to create ovs enabled file in debugfs\n");
debugfs_remove_recursive(ecm_classifier_ovs_dentry);
return -1;
}
return 0;
}
EXPORT_SYMBOL(ecm_classifier_ovs_init);
/*
* ecm_classifier_ovs_exit()
*/
void ecm_classifier_ovs_exit(void)
{
DEBUG_INFO("ovs classifier Module exit\n");
spin_lock_bh(&ecm_classifier_ovs_lock);
ecm_classifier_ovs_terminate_pending = true;
spin_unlock_bh(&ecm_classifier_ovs_lock);
/*
* Remove the debugfs files recursively.
*/
if (ecm_classifier_ovs_dentry) {
debugfs_remove_recursive(ecm_classifier_ovs_dentry);
}
}
EXPORT_SYMBOL(ecm_classifier_ovs_exit);