[qca-nss-ecm] Add OVS VLAN support

A new external classifier module is addded.
This module will process the flow and decide
if there is VLAN tag[s] for this flow in the OVS
flow table.

Change-Id: I6a9705a82b6b2a8cb5fbb1083a541e50b111a757
Signed-off-by: Murat Sezgin <msezgin@codeaurora.org>
diff --git a/Makefile b/Makefile
index 45c86bd..5f1d1ab 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,9 @@
 ifeq ($(EXAMPLES_BUILD_MARK),y)
 obj-m += examples/ecm_mark_test.o
 endif
+ifeq ($(EXAMPLES_BUILD_OVS),y)
+obj-m += examples/ecm_ovs.o
+endif
 
 ecm-y := \
 	 ecm_tracker_udp.o \
diff --git a/ecm_classifier.h b/ecm_classifier.h
index f24fd70..6679e73 100644
--- a/ecm_classifier.h
+++ b/ecm_classifier.h
@@ -86,6 +86,12 @@
 #define ECM_CLASSIFIER_PROCESS_ACTION_IGS_QOS_TAG 0x00000040	/* Contains flow & return ingress qos tags */
 #endif
 
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+#define ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG 0x00000080	/* Contains OVS VLAN tags */
+#define ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG 0x00000100
+								/* Contains OVS QinQ VLAN tags */
+#endif
+
 /*
  * struct ecm_classifier_process_response
  *	Response structure returned by a process call
@@ -108,6 +114,10 @@
 	uint8_t flow_dscp;				/* DSCP mark for flow */
 	uint8_t return_dscp;				/* DSCP mark for return */
 #endif
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+	uint32_t ingress_vlan_tag[2];			/* Ingress VLAN tags */
+	uint32_t egress_vlan_tag[2];			/* Egress VLAN tags */
+#endif
 	ecm_classifier_acceleration_mode_t accel_mode;	/* Acceleration needed for this connection */
 	ecm_db_timer_group_t timer_group;		/* Timer group the connection should be in */
 };
@@ -264,6 +274,26 @@
 		}
 	}
 
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+	if (pr->process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG) {
+		if ((result = ecm_state_write(sfi, "ingress_vlan_tag[0]", "0x%x", pr->ingress_vlan_tag[0]))) {
+			return result;
+		}
+		if ((result = ecm_state_write(sfi, "egress_vlan_tag[0]", "0x%x", pr->egress_vlan_tag[0]))) {
+			return result;
+		}
+	}
+
+	if (pr->process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG) {
+		if ((result = ecm_state_write(sfi, "ingress_vlan_tag[1]", "0x%x", pr->ingress_vlan_tag[1]))) {
+			return result;
+		}
+		if ((result = ecm_state_write(sfi, "egress_vlan_tag[1]", "0x%x", pr->egress_vlan_tag[1]))) {
+			return result;
+		}
+	}
+#endif
+
 #ifdef ECM_CLASSIFIER_DSCP_ENABLE
 	if (pr->process_actions & ECM_CLASSIFIER_PROCESS_ACTION_QOS_TAG) {
 		if ((result = ecm_state_write(sfi, "flow_qos_tag", "%u", pr->flow_qos_tag))) {
diff --git a/ecm_classifier_ovs.c b/ecm_classifier_ovs.c
index f0e7208..721b243 100644
--- a/ecm_classifier_ovs.c
+++ b/ecm_classifier_ovs.c
@@ -58,6 +58,7 @@
 #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
@@ -118,6 +119,11 @@
 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
  */
@@ -239,6 +245,281 @@
 }
 
 /*
+ * 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);
+
+	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;
+	spin_unlock_bh(&ecm_classifier_ovs_lock);
+
+	/*
+	 * 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
+	 */
+	if (from_dev) {
+		/*
+		 * Case 1/2
+		 * from_dev = eth1
+		 * br_dev = ovs-br1
+		 *
+		 * Case 3/4
+		 * from_dev = eth2
+		 * br_dev = ovs-br2
+		 */
+		br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, false);
+		if (!br_dev) {
+			DEBUG_WARN("%p: from_dev = %s is a OVS bridge port, bridge interface is not found\n",
+					ecvi, from_dev->name);
+			goto route_deny_accel;
+		}
+
+		DEBUG_TRACE("%p: 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, "%p: 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));
+
+		/*
+		 * 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("%p: 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("%p: 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("%p: 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("%p: to_dev = %s is a OVS bridge port, bridge interface is not found\n",
+					ecvi, to_dev->name);
+			goto route_deny_accel;
+		}
+
+		DEBUG_TRACE("%p: 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, "%p: 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));
+
+		/*
+		 * 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("%p: 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("%p: 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("%p: 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);
+}
+
+/*
  * ecm_classifier_ovs_process()
  *	Process new packet
  *
@@ -249,7 +530,13 @@
 									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;
 
@@ -294,21 +581,118 @@
 	}
 
 	/*
-	 * If the connection is a bridge flow, both devices should be present.
+	 * Is there an external callback to get the ovs value from the packet?
 	 */
-	if (!ecm_db_connection_is_routed_get(ci)) {
-		if (!from_dev || !to_dev) {
-			DEBUG_ERROR("%p: One of the ports is NULL from_dev: %p to_dev: %p\n", aci, from_dev, to_dev);
-			if (from_dev)
-				dev_put(from_dev);
+	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("%p: No external process callback set\n", aci);
+		if (from_dev)
+			dev_put(from_dev);
 
-			if (to_dev)
-				dev_put(to_dev);
+		if (to_dev)
+			dev_put(to_dev);
 
-			ecm_db_connection_deref(ci);
-			goto not_relevant;
-		}
+		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("%p: One of the ports is NULL from_dev: %p to_dev: %p\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("%p: 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("%p: 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, "%p: unexpected ip_version: %d", aci, flow.tuple.ip_version );
+	}
+
+	memset(&resp, 0, sizeof(struct ecm_classifier_ovs_process_response));
+
+	/*
+	 * Call the external callback and get the result.
+	 */
+	result = cb(&flow, skb, &resp);
 
 	if (from_dev)
 		dev_put(from_dev);
@@ -317,9 +701,117 @@
 		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("%p: 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;
+			}
+		}
+		goto allow_accel;
+
+	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("%p: 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("%p: External callback didn't find any VLAN relation\n", aci);
+		spin_lock_bh(&ecm_classifier_ovs_lock);
+		goto allow_accel;
+
+	default:
+		DEBUG_ASSERT(false, "Unhandled result: %d\n", result);
+	}
+
+allow_accel:
+	/*
 	 * 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;
@@ -337,6 +829,18 @@
 	*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);
 }
 
 /*
@@ -408,7 +912,7 @@
 					  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 sport, uint16_t dport, uint16_t tci, uint16_t tpid)
 {
 	struct ovsmgr_dp_flow_stats stats;
 
@@ -418,6 +922,17 @@
 	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;
 
@@ -463,6 +978,7 @@
 	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;
 
@@ -474,6 +990,7 @@
 		return;
 	}
 
+	memset(&flow, 0, sizeof(flow));
 	/*
 	 * Get the possible OVS bridge ports.
 	 */
@@ -489,6 +1006,15 @@
 	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("%p: Ingress VLAN : %x:%x\n", aci, tci, tpid);
+	}
+
+	/*
 	 * Bridge flow
 	 */
 	if (!flow.is_routed) {
@@ -504,25 +1030,38 @@
 		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("%p: Flow direction stats update\n", aci);
 		ecm_classifier_ovs_stats_sync(&flow,
 				  sync->flow_rx_packet_count, sync->flow_rx_byte_count,
 				  sync->return_tx_packet_count, sync->return_tx_byte_count,
 				  from_dev, to_dev,
 				  smac, dmac,
 				  src_ip, dst_ip,
-				  sport, dport);
+				  sport, dport, tci, tpid);
 
 		/*
 		 * Sync the return direction (eth2 to eth1)
 		 * All the flow parameters are reversed.
 		 */
+		DEBUG_TRACE("%p: 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("%p: Egress VLAN : %x:%x\n", aci, tci, tpid);
+		}
+
 		ecm_classifier_ovs_stats_sync(&flow,
 				  sync->flow_tx_packet_count, sync->flow_tx_byte_count,
 				  sync->return_rx_packet_count, sync->return_rx_byte_count,
 				  to_dev, from_dev,
 				  dmac, smac,
 				  dst_ip, src_ip,
-				  dport, sport);
+				  dport, sport, tci, tpid);
 		goto done;
 	}
 
@@ -562,7 +1101,7 @@
 				  from_dev, br_dev,
 				  smac, dmac,
 				  src_ip, dst_ip,
-				  sport, dport);
+				  sport, dport, tci, tpid);
 
 		/*
 		 * Sync the return direction (ovs-br1 to eth1)
@@ -574,7 +1113,7 @@
 				  br_dev, from_dev,
 				  dmac, smac,
 				  dst_ip, src_ip,
-				  dport, sport);
+				  dport, sport, tci, tpid);
 		dev_put(br_dev);
 	}
 
@@ -608,7 +1147,7 @@
 				  br_dev, to_dev,
 				  smac, dmac,
 				  src_ip, dst_ip,
-				  sport, dport);
+				  sport, dport, tci, tpid);
 		/*
 		 * Sync the return direction (eth2 to ovs-br2)
 		 * All the flow parameters are reversed.
@@ -619,7 +1158,7 @@
 				  to_dev, br_dev,
 				  dmac, smac,
 				  dst_ip, src_ip,
-				  dport, sport);
+				  dport, sport, tci, tpid);
 		dev_put(br_dev);
 	}
 
@@ -818,6 +1357,36 @@
 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)
diff --git a/ecm_classifier_ovs_public.h b/ecm_classifier_ovs_public.h
new file mode 100644
index 0000000..910d9a5
--- /dev/null
+++ b/ecm_classifier_ovs_public.h
@@ -0,0 +1,52 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2019-2020, 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.
+ **************************************************************************
+ */
+
+/*
+ * Result values of the external inspection module to the ECM's ovs classifier.
+ * Based on the result the ovs classifier takes the action for the inspected connection.
+ */
+enum ecm_classifier_ovs_results {
+	ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_ACCEL,		/* VLAN process succeeded */
+	ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL,	/* VLAN process succeeded for QinQ */
+	ECM_CLASSIFIER_OVS_RESULT_ALLOW_ACCEL,			/* No VLAN present, just accelerate */
+	ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL_EGRESS,		/* Flow egress is not allowed for acceleration */
+	ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL			/* Do not accelerate */
+};
+typedef enum ecm_classifier_ovs_results ecm_classifier_ovs_result_t;
+
+struct ecm_classifier_ovs_process_response {
+	struct vlan_hdr ingress_vlan[2];
+	struct vlan_hdr egress_vlan[2];
+};
+
+
+/*
+ * Callback function which processes the connection information.
+ */
+typedef ecm_classifier_ovs_result_t (*ecm_classifier_ovs_process_callback_t)(struct ovsmgr_dp_flow *flow,
+									     struct sk_buff *skb,
+									     struct ecm_classifier_ovs_process_response *resp);
+
+struct ecm_classifier_ovs_callbacks {
+	ecm_classifier_ovs_process_callback_t ovs_process;
+};
+
+/*
+ * Register/Unregister callback functions which are called from the external modules.
+ */
+int ecm_classifier_ovs_register_callbacks(struct ecm_classifier_ovs_callbacks *callbacks);
+void ecm_classifier_ovs_unregister_callbacks(void);
+
diff --git a/ecm_interface.c b/ecm_interface.c
index 0ab963a..2ac2e98 100644
--- a/ecm_interface.c
+++ b/ecm_interface.c
@@ -2415,6 +2415,8 @@
 	DEBUG_TRACE("%p: br_dev = %s, src_addr: " ECM_IP_ADDR_DOT_FMT " dest_addr: " ECM_IP_ADDR_DOT_FMT ", ip_version: %d, protocol: %d (smac:%pM, dmac:%pM)\n",
 				skb, br_dev->name, ECM_IP_ADDR_TO_DOT(src_ip), ECM_IP_ADDR_TO_DOT(dst_ip), ip_version, protocol, smac, dmac);
 
+	memset(&flow, 0, sizeof(flow));
+
 	flow.indev = br_dev;
 	flow.outdev = NULL;
 
diff --git a/examples/ecm_ovs.c b/examples/ecm_ovs.c
new file mode 100644
index 0000000..105e443
--- /dev/null
+++ b/examples/ecm_ovs.c
@@ -0,0 +1,134 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2019-2020, 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/module.h>
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <linux/ctype.h>
+
+#include <ovsmgr.h>
+#include "ecm_classifier_ovs_public.h"
+
+/*
+ * This is a test module for the ECM's ovs CLassifier.
+ * It is extracting the VLAN information based on the flow information.
+ */
+
+/*
+ * ecm_ovs_get_ovs()
+ *	OVS get callback function registered with ECM.
+ */
+static ecm_classifier_ovs_result_t ecm_ovs_process(struct ovsmgr_dp_flow *flow, struct sk_buff *skb, struct ecm_classifier_ovs_process_response *resp)
+{
+	struct ovsmgr_vlan_info ovi;
+	enum ovsmgr_flow_status status;
+	pr_debug("ecm_ovs_process\n");
+
+	memset((void *)&ovi, 0, sizeof(ovi));
+
+	status = ovsmgr_flow_info_get(flow, skb, &ovi);
+	switch (status) {
+	case OVSMGR_FLOW_STATUS_DENY_ACCEL:
+	case OVSMGR_FLOW_STATUS_UNKNOWN:
+		pr_debug("%p: Deny accelerating the flow\n", flow);
+		return ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL;
+	case OVSMGR_FLOW_STATUS_DENY_ACCEL_EGRESS:
+		pr_debug("%p: Deny accelerating the flow, egress %s is not allowed\n", flow, flow->outdev->name);
+		return ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL_EGRESS;
+	case OVSMGR_FLOW_STATUS_ALLOW_VLAN_ACCEL:
+	case OVSMGR_FLOW_STATUS_ALLOW_VLAN_QINQ_ACCEL:
+		pr_debug("%p: Accelerate, VLAN data is valid\n", flow);
+		/*
+		 * Outer ingress VLAN
+		 */
+		resp->ingress_vlan[0].h_vlan_TCI = ovi.ingress[0].h_vlan_TCI;
+		resp->ingress_vlan[0].h_vlan_encapsulated_proto = ovi.ingress[0].h_vlan_encapsulated_proto;
+
+		/*
+		 * Outer egress VLAN
+		 */
+		resp->egress_vlan[0].h_vlan_TCI = ovi.egress[0].h_vlan_TCI;
+		resp->egress_vlan[0].h_vlan_encapsulated_proto = ovi.egress[0].h_vlan_encapsulated_proto;
+
+		if (status == OVSMGR_FLOW_STATUS_ALLOW_VLAN_QINQ_ACCEL) {
+			/*
+			 * Inner ingress VLAN
+			 */
+			resp->ingress_vlan[1].h_vlan_TCI = ovi.ingress[1].h_vlan_TCI;
+			resp->ingress_vlan[1].h_vlan_encapsulated_proto = ovi.ingress[1].h_vlan_encapsulated_proto;
+
+			/*
+			 * Inner egress VLAN
+			 */
+			resp->egress_vlan[1].h_vlan_TCI = ovi.egress[1].h_vlan_TCI;
+			resp->egress_vlan[1].h_vlan_encapsulated_proto = ovi.egress[1].h_vlan_encapsulated_proto;
+
+			return ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_QINQ_ACCEL;
+		}
+		return ECM_CLASSIFIER_OVS_RESULT_ALLOW_VLAN_ACCEL;
+	case OVSMGR_FLOW_STATUS_ALLOW_ACCEL:
+		return ECM_CLASSIFIER_OVS_RESULT_ALLOW_ACCEL;
+	}
+
+	return ECM_CLASSIFIER_OVS_RESULT_DENY_ACCEL;
+}
+
+static struct ecm_classifier_ovs_callbacks callbacks = {
+	.ovs_process = ecm_ovs_process,
+};
+
+/*
+ * ecm_ovs_init()
+ */
+static int __init ecm_ovs_init(void)
+{
+	int res;
+
+	pr_info("ECM OVS Test INIT\n");
+
+	/*
+	 * Register the callbacks with the ECM ovs classifier.
+	 */
+	res = ecm_classifier_ovs_register_callbacks(&callbacks);
+	if (res < 0) {
+		pr_warn("Failed to register callbacks for OVS classifier\n");
+		return res;
+	}
+
+	return 0;
+}
+
+/*
+ * ecm_ovs_exit()
+ */
+static void __exit ecm_ovs_exit(void)
+{
+	pr_info("ECM OVS Test EXIT\n");
+
+	/*
+	 * Unregister the callbacks.
+	 */
+	ecm_classifier_ovs_unregister_callbacks();
+}
+
+module_init(ecm_ovs_init)
+module_exit(ecm_ovs_exit)
+
+MODULE_DESCRIPTION("ECM OVS Test");
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("Dual BSD/GPL");
+#endif
+
diff --git a/frontends/include/ecm_front_end_types.h b/frontends/include/ecm_front_end_types.h
index 5dd33a1..3ba727c 100644
--- a/frontends/include/ecm_front_end_types.h
+++ b/frontends/include/ecm_front_end_types.h
@@ -1,6 +1,6 @@
 /*
  **************************************************************************
- * Copyright (c) 2014-2019 The Linux Foundation.  All rights reserved.
+ * Copyright (c) 2014-2020, 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.
@@ -19,6 +19,11 @@
 #include <linux/of.h>
 
 /*
+ * Constant used with constructing acceleration rules.
+ */
+#define ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED 0xFFF
+
+/*
  * Bridge device macros
  */
 #define ecm_front_end_is_bridge_port(dev) (dev && (dev->priv_flags & IFF_BRIDGE_PORT))
diff --git a/frontends/nss/ecm_nss_common.h b/frontends/nss/ecm_nss_common.h
index 5953be3..2f3d949 100644
--- a/frontends/nss/ecm_nss_common.h
+++ b/frontends/nss/ecm_nss_common.h
@@ -1,6 +1,6 @@
 /*
  **************************************************************************
- * Copyright (c) 2015, 2018-2019, The Linux Foundation.  All rights reserved.
+ * Copyright (c) 2015, 2018-2020, 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.
@@ -31,13 +31,6 @@
 #endif
 
 /*
- * Some constants used with constructing NSS acceleration rules.
- * GGG TODO These should be provided by the NSS driver itself!
- */
-#define ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED 0xFFF
-#define ECM_NSS_CONNMGR_VLAN_MARKING_NOT_CONFIGURED 0xFFFF
-
-/*
  * This macro converts ECM ip_addr_t to NSS IPv6 address
  */
 #define ECM_IP_ADDR_TO_NSS_IPV6_ADDR(nss6, ipaddrt) \
diff --git a/frontends/nss/ecm_nss_multicast_ipv4.c b/frontends/nss/ecm_nss_multicast_ipv4.c
index c0bfe9c..730ed82 100644
--- a/frontends/nss/ecm_nss_multicast_ipv4.c
+++ b/frontends/nss/ecm_nss_multicast_ipv4.c
@@ -1,6 +1,6 @@
 /*
  **************************************************************************
- * Copyright (c) 2014-2019 The Linux Foundation.  All rights reserved.
+ * Copyright (c) 2014-2020, 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.
@@ -484,8 +484,8 @@
 		return -1;
 	}
 
-	create->ingress_vlan_tag[0] =   ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	create->ingress_vlan_tag[1] =   ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+	create->ingress_vlan_tag[0] =   ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	create->ingress_vlan_tag[1] =   ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 
 	/*
 	 * Set the source NSS interface identifier
@@ -522,8 +522,8 @@
 	 */
 	for (vif = 0; vif < ECM_DB_MULTICAST_IF_MAX; vif++) {
 #ifdef ECM_INTERFACE_VLAN_ENABLE
-		create->if_rule[vif].egress_vlan_tag[0] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-		create->if_rule[vif].egress_vlan_tag[1] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+		create->if_rule[vif].egress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+		create->if_rule[vif].egress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 #endif
 
 		/*
@@ -941,8 +941,8 @@
 		return;
 	}
 
-	create->ingress_vlan_tag[0] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	create->ingress_vlan_tag[1] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+	create->ingress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	create->ingress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 	from_nss_iface = from_ifaces[from_ifaces_first];
 	from_nss_iface_id = ecm_db_iface_ae_interface_identifier_get(from_nss_iface);
 	if (from_nss_iface_id < 0) {
@@ -1046,8 +1046,8 @@
 
 		to_nss_iface_id = -1;
 
-		create->if_rule[vif].egress_vlan_tag[0] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-		create->if_rule[vif].egress_vlan_tag[1] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+		create->if_rule[vif].egress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+		create->if_rule[vif].egress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 
 		ii_temp = ecm_db_multicast_if_heirarchy_get(to_ifaces, vif);
 		to_ii_first = ecm_db_multicast_if_first_get_at_index(to_ifaces_first, vif);
diff --git a/frontends/nss/ecm_nss_multicast_ipv6.c b/frontends/nss/ecm_nss_multicast_ipv6.c
index 5c2b63a..45ff7af 100644
--- a/frontends/nss/ecm_nss_multicast_ipv6.c
+++ b/frontends/nss/ecm_nss_multicast_ipv6.c
@@ -1,6 +1,6 @@
 /*
  **************************************************************************
- * Copyright (c) 2014-2019 The Linux Foundation.  All rights reserved.
+ * Copyright (c) 2014-2020 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.
@@ -496,8 +496,8 @@
 		return -1;
 	}
 
-	create->ingress_vlan_tag[0] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	create->ingress_vlan_tag[1] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+	create->ingress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	create->ingress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 
 	/*
 	 * Set the source NSS interface identifier
@@ -529,8 +529,8 @@
 	 */
 	for (vif = 0; vif < ECM_DB_MULTICAST_IF_MAX; vif++) {
 #ifdef ECM_INTERFACE_VLAN_ENABLE
-		create->if_rule[vif].egress_vlan_tag[0] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-		create->if_rule[vif].egress_vlan_tag[1] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+		create->if_rule[vif].egress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+		create->if_rule[vif].egress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 #endif
 		/*
 		 * If there is no state change for an interface at this index,
@@ -947,8 +947,8 @@
 		return;
 	}
 
-	create->ingress_vlan_tag[0] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	create->ingress_vlan_tag[1] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+	create->ingress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	create->ingress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 	from_nss_iface = from_ifaces[from_ifaces_first];
 	from_nss_iface_id = ecm_db_iface_ae_interface_identifier_get(from_nss_iface);
 
@@ -1029,8 +1029,8 @@
 		to_nss_iface_id = -1;
 
 #ifdef ECM_INTERFACE_VLAN_ENABLE
-		create->if_rule[vif].egress_vlan_tag[0] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-		create->if_rule[vif].egress_vlan_tag[1] = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+		create->if_rule[vif].egress_vlan_tag[0] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+		create->if_rule[vif].egress_vlan_tag[1] = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 #endif
 
 		ii_temp = ecm_db_multicast_if_heirarchy_get(to_ifaces, vif);
diff --git a/frontends/nss/ecm_nss_non_ported_ipv4.c b/frontends/nss/ecm_nss_non_ported_ipv4.c
index 8c4bfb0..46b1a82 100644
--- a/frontends/nss/ecm_nss_non_ported_ipv4.c
+++ b/frontends/nss/ecm_nss_non_ported_ipv4.c
@@ -486,10 +486,10 @@
 	/*
 	 * Initialize VLAN tag information
 	 */
-	nircm->vlan_primary_rule.ingress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_primary_rule.egress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_secondary_rule.ingress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_secondary_rule.egress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_primary_rule.ingress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_primary_rule.egress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_secondary_rule.ingress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_secondary_rule.egress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 
 	/*
 	 * Get the interface lists of the connection, we must have at least one interface in the list to continue
diff --git a/frontends/nss/ecm_nss_non_ported_ipv6.c b/frontends/nss/ecm_nss_non_ported_ipv6.c
index 92287f0..ee19926 100644
--- a/frontends/nss/ecm_nss_non_ported_ipv6.c
+++ b/frontends/nss/ecm_nss_non_ported_ipv6.c
@@ -404,10 +404,10 @@
 	/*
 	 * Initialize VLAN tag information
 	 */
-	nircm->vlan_primary_rule.ingress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_primary_rule.egress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_secondary_rule.ingress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_secondary_rule.egress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_primary_rule.ingress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_primary_rule.egress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_secondary_rule.ingress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_secondary_rule.egress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 
 	/*
 	 * Get the interface lists of the connection, we must have at least one interface in the list to continue
diff --git a/frontends/nss/ecm_nss_ported_ipv4.c b/frontends/nss/ecm_nss_ported_ipv4.c
index c453e32..e80294a 100644
--- a/frontends/nss/ecm_nss_ported_ipv4.c
+++ b/frontends/nss/ecm_nss_ported_ipv4.c
@@ -413,10 +413,10 @@
 	/*
 	 * Initialize VLAN tag information
 	 */
-	nircm->vlan_primary_rule.ingress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_primary_rule.egress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_secondary_rule.ingress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_secondary_rule.egress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_primary_rule.ingress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_primary_rule.egress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_secondary_rule.ingress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_secondary_rule.egress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 
 	/*
 	 * Get the interface lists of the connection, we must have at least one interface in the list to continue
@@ -1021,8 +1021,8 @@
 #endif
 
 	if (ecm_nss_ipv4_vlan_passthrough_enable && !ecm_db_connection_is_routed_get(feci->ci) &&
-	   (nircm->vlan_primary_rule.ingress_vlan_tag == ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED) &&
-	   (nircm->vlan_primary_rule.egress_vlan_tag == ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED)) {
+	   (nircm->vlan_primary_rule.ingress_vlan_tag == ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) &&
+	   (nircm->vlan_primary_rule.egress_vlan_tag == ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED)) {
 		int vlan_present = 0;
 #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0))
 		vlan_present = vlan_tx_tag_present(skb);
@@ -1042,6 +1042,22 @@
 		}
 	}
 
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+		/*
+		 * Copy the both primary and secondary (if exist) VLAN tags.
+		 */
+		if (pr->process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG) {
+			nircm->vlan_primary_rule.ingress_vlan_tag = pr->ingress_vlan_tag[0];
+			nircm->vlan_primary_rule.egress_vlan_tag = pr->egress_vlan_tag[0];
+			nircm->valid_flags |= NSS_IPV4_RULE_CREATE_VLAN_VALID;
+		}
+
+		if (pr->process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG) {
+			nircm->vlan_secondary_rule.ingress_vlan_tag = pr->ingress_vlan_tag[1];
+			nircm->vlan_secondary_rule.egress_vlan_tag = pr->egress_vlan_tag[1];
+		}
+#endif
+
 	protocol = ecm_db_connection_protocol_get(feci->ci);
 
 	/*
@@ -2608,6 +2624,24 @@
 			prevalent_pr.timer_group = aci_pr.timer_group;
 		}
 
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+		if (aci_pr.process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG) {
+			DEBUG_TRACE("%p: aci: %p, type: %d, ingress vlan tags 0: %u, egress vlan tags 0: %u\n",
+					ci, aci, aci->type_get(aci), aci_pr.ingress_vlan_tag[0], aci_pr.egress_vlan_tag[0]);
+			prevalent_pr.ingress_vlan_tag[0] = aci_pr.ingress_vlan_tag[0];
+			prevalent_pr.egress_vlan_tag[0] = aci_pr.egress_vlan_tag[0];
+			prevalent_pr.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG;
+		}
+
+		if (aci_pr.process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG) {
+			DEBUG_TRACE("%p: aci: %p, type: %d, ingress vlan tags 1: %u, egress vlan tags 1: %u\n",
+					ci, aci, aci->type_get(aci), aci_pr.ingress_vlan_tag[1], aci_pr.egress_vlan_tag[1]);
+			prevalent_pr.ingress_vlan_tag[1] = aci_pr.ingress_vlan_tag[1];
+			prevalent_pr.egress_vlan_tag[1] = aci_pr.egress_vlan_tag[1];
+			prevalent_pr.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG;
+		}
+#endif
+
 #ifdef ECM_CLASSIFIER_DSCP_ENABLE
 		/*
 		 * Qos tag (the last classifier i.e. the highest priority one) will 'win'
diff --git a/frontends/nss/ecm_nss_ported_ipv6.c b/frontends/nss/ecm_nss_ported_ipv6.c
index a56716b..dab55db 100644
--- a/frontends/nss/ecm_nss_ported_ipv6.c
+++ b/frontends/nss/ecm_nss_ported_ipv6.c
@@ -418,10 +418,10 @@
 	/*
 	 * Initialize VLAN tag information
 	 */
-	nircm->vlan_primary_rule.ingress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_primary_rule.egress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_secondary_rule.ingress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
-	nircm->vlan_secondary_rule.egress_vlan_tag = ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_primary_rule.ingress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_primary_rule.egress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_secondary_rule.ingress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
+	nircm->vlan_secondary_rule.egress_vlan_tag = ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED;
 
 	/*
 	 * Get the interface lists of the connection, we must have at least one interface in the list to continue
@@ -989,8 +989,8 @@
 	}
 #endif
 	if (ecm_nss_ipv6_vlan_passthrough_enable && !ecm_db_connection_is_routed_get(feci->ci) &&
-	   (nircm->vlan_primary_rule.ingress_vlan_tag == ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED) &&
-	   (nircm->vlan_primary_rule.egress_vlan_tag == ECM_NSS_CONNMGR_VLAN_ID_NOT_CONFIGURED)) {
+	   (nircm->vlan_primary_rule.ingress_vlan_tag == ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED) &&
+	   (nircm->vlan_primary_rule.egress_vlan_tag == ECM_FRONT_END_VLAN_ID_NOT_CONFIGURED)) {
 		int vlan_present = 0;
 #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0))
 		vlan_present = vlan_tx_tag_present(skb);
@@ -1010,6 +1010,21 @@
 		}
 	}
 
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+		/*
+		 * Copy the both primary and secondary (if exist) VLAN tags.
+		 */
+		if (pr->process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG) {
+			nircm->vlan_primary_rule.ingress_vlan_tag = pr->ingress_vlan_tag[0];
+			nircm->vlan_primary_rule.egress_vlan_tag = pr->egress_vlan_tag[0];
+			nircm->valid_flags |= NSS_IPV6_RULE_CREATE_VLAN_VALID;
+		}
+
+		if (pr->process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG) {
+			nircm->vlan_secondary_rule.ingress_vlan_tag = pr->ingress_vlan_tag[1];
+			nircm->vlan_secondary_rule.egress_vlan_tag = pr->egress_vlan_tag[1];
+		}
+#endif
 	protocol = ecm_db_connection_protocol_get(feci->ci);
 
 	/*
@@ -2395,6 +2410,24 @@
 			prevalent_pr.timer_group = aci_pr.timer_group;
 		}
 
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+		if (aci_pr.process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG) {
+			DEBUG_TRACE("%p: aci: %p, type: %d, ingress vlan tags 0: %u, egress vlan tags 0: %u\n",
+					ci, aci, aci->type_get(aci), aci_pr.ingress_vlan_tag[0], aci_pr.egress_vlan_tag[0]);
+			prevalent_pr.ingress_vlan_tag[0] = aci_pr.ingress_vlan_tag[0];
+			prevalent_pr.egress_vlan_tag[0] = aci_pr.egress_vlan_tag[0];
+			prevalent_pr.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_TAG;
+		}
+
+		if (aci_pr.process_actions & ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG) {
+			DEBUG_TRACE("%p: aci: %p, type: %d, ingress vlan tags 1: %u, egress vlan tags 1: %u\n",
+					ci, aci, aci->type_get(aci), aci_pr.ingress_vlan_tag[1], aci_pr.egress_vlan_tag[1]);
+			prevalent_pr.ingress_vlan_tag[1] = aci_pr.ingress_vlan_tag[1];
+			prevalent_pr.egress_vlan_tag[1] = aci_pr.egress_vlan_tag[1];
+			prevalent_pr.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_OVS_VLAN_QINQ_TAG;
+		}
+#endif
+
 #ifdef ECM_CLASSIFIER_DSCP_ENABLE
 		/*
 		 * Qos tag (the last classifier i.e. the highest priority one) will 'win'