[qca-nss-sfe] 3-tuple acceleration for PPPoE bridged flow

Change-Id: Ie42f735e22f9b3a46c81ede6c2d51a86fce37b52
Signed-off-by: Wayne Tan <quic_wtan@quicinc.com>
diff --git a/Makefile b/Makefile
index 0f0e9e6..63e287e 100644
--- a/Makefile
+++ b/Makefile
@@ -5,9 +5,9 @@
 KERNELVERSION := $(word 1, $(subst ., ,$(KERNELVERSION))).$(word 2, $(subst ., ,$(KERNELVERSION)))
 
 SFE_BASE_OBJS := sfe.o sfe_init.o
-SFE_IPV4_OBJS := sfe_ipv4.o sfe_ipv4_udp.o sfe_ipv4_tcp.o sfe_ipv4_icmp.o sfe_ipv4_tun6rd.o
-SFE_IPV6_OBJS := sfe_ipv6.o sfe_ipv6_udp.o sfe_ipv6_tcp.o sfe_ipv6_icmp.o sfe_ipv6_tunipip6.o
-SFE_PPPOE_OBJS := sfe_pppoe.o
+SFE_IPV4_OBJS := sfe_ipv4.o sfe_ipv4_udp.o sfe_ipv4_tcp.o sfe_ipv4_icmp.o sfe_ipv4_tun6rd.o sfe_ipv4_pppoe_br.o
+SFE_IPV6_OBJS := sfe_ipv6.o sfe_ipv6_udp.o sfe_ipv6_tcp.o sfe_ipv6_icmp.o sfe_ipv6_tunipip6.o sfe_ipv6_pppoe_br.o
+SFE_PPPOE_OBJS := sfe_pppoe.o sfe_pppoe_mgr.o
 
 
 ifeq ($(findstring 4.4, $(KERNELVERSION)),)
diff --git a/exports/sfe_api.h b/exports/sfe_api.h
index 96fc4ee..50a7733 100644
--- a/exports/sfe_api.h
+++ b/exports/sfe_api.h
@@ -243,6 +243,17 @@
 };
 
 /**
+ * sfe_pppoe_br_accel_mode_t
+ *	PPPoE bridge acceleration modes.
+ */
+typedef enum {
+	SFE_PPPOE_BR_ACCEL_MODE_DISABLED,       /**< No acceleration */
+	SFE_PPPOE_BR_ACCEL_MODE_EN_5T,          /**< 5-tuple (src_ip, dest_ip, src_port, dest_port, protocol) acceleration */
+	SFE_PPPOE_BR_ACCEL_MODE_EN_3T,          /**< 3-tuple (src_ip, dest_ip, pppoe session id) acceleration */
+	SFE_PPPOE_BR_ACCEL_MODE_MAX             /**< Indicates the last item */
+} __attribute__ ((__packed__)) sfe_pppoe_br_accel_mode_t;
+
+/**
  * PPPoE connection rules structure.
  */
 struct sfe_pppoe_rule {
@@ -807,6 +818,14 @@
 void sfe_ipv6_mark_rule_update(struct sfe_connection_mark *mark);
 
 /**
+ * Gets the acceleration mode of PPPoE bridge.
+ *
+ * @return
+ * The acceleration mode.
+ */
+sfe_pppoe_br_accel_mode_t sfe_pppoe_get_br_accel_mode(void);
+
+/**
  * @}
  */
 
diff --git a/sfe.c b/sfe.c
index 885ebd7..72b4f1d 100644
--- a/sfe.c
+++ b/sfe.c
@@ -32,6 +32,7 @@
 #include "sfe_api.h"
 #include "sfe.h"
 #include "sfe_pppoe.h"
+#include "sfe_pppoe_mgr.h"
 #include "sfe_vlan.h"
 #include "sfe_ipv4.h"
 #include "sfe_ipv6.h"
@@ -679,6 +680,7 @@
 			sfe_incr_exceptions(SFE_EXCEPTION_TCP_INVALID);
 			goto failed_ret;
 		}
+		break;
 
 	case IPPROTO_UDP:
 		break;
@@ -689,6 +691,12 @@
 	case IPPROTO_IPV6:
 		break;
 
+	case IPPROTO_RAW:
+		/*
+		 * for accelerating PPPoE bridged flows using 3-tuple information
+		 */
+		break;
+
 	default:
 		ret = SFE_CMN_RESPONSE_EMSG;
 		sfe_incr_exceptions(SFE_EXCEPTION_PROTOCOL_NOT_SUPPORT);
@@ -1128,6 +1136,12 @@
 	case IPPROTO_GRE:
 		break;
 
+	case IPPROTO_RAW:
+		/*
+		 * for accelerating PPPoE bridged flows using 3-tuple information
+		 */
+		break;
+
 	default:
 		ret = SFE_CMN_RESPONSE_EMSG;
 		sfe_incr_exceptions(SFE_EXCEPTION_PROTOCOL_NOT_SUPPORT);
@@ -1628,6 +1642,76 @@
 	__ATTR(l2_feature,  0644, sfe_get_l2_feature, sfe_set_l2_feature);
 
 /*
+ * sfe_get_pppoe_br_accel_mode()
+ *	Get PPPoE bridge acceleration mode
+ */
+static ssize_t sfe_get_pppoe_br_accel_mode(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	int len;
+	sfe_pppoe_br_accel_mode_t mode;
+	char *str;
+
+	mode = sfe_pppoe_get_br_accel_mode();
+	switch ((int)mode) {
+	case SFE_PPPOE_BR_ACCEL_MODE_DISABLED:
+		str = "ACCEL_MODE_DISABLED";
+		break;
+
+	case SFE_PPPOE_BR_ACCEL_MODE_EN_5T:
+		str = "ACCEL_MODE_5_TUPLE";
+		break;
+
+	case SFE_PPPOE_BR_ACCEL_MODE_EN_3T:
+		str = "ACCEL_MODE_3_TUPLE";
+		break;
+
+	default:
+		str = "Unknown ACCEL_MODE";
+		break;
+	}
+	len = snprintf(buf, PAGE_SIZE, "%s\n", str);
+
+	return len;
+}
+
+/*
+ * sfe_set_pppoe_br_accel_mode()
+ *	Set PPPoE bridge acceleration mode
+ */
+static ssize_t sfe_set_pppoe_br_accel_mode(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf,
+				size_t count)
+{
+        uint32_t val;
+	int ret;
+
+        ret = sscanf(buf, "%u", &val);
+	if (ret != 1) {
+		DEBUG_ERROR("Unable to write the mode\n");
+		return -EINVAL;
+	}
+
+	ret = sfe_pppoe_set_br_accel_mode(val);
+	if (ret) {
+		DEBUG_ERROR("Wrong input: %d\n"
+			    "Input should be %u or %u or %u\n"
+			    "(%u==ACCEL_MODE_DISABLED %u==ACCEL_MODE_EN_5T %u==ACCEL_MODE_EN_3T)\n",
+			val,
+			SFE_PPPOE_BR_ACCEL_MODE_DISABLED, SFE_PPPOE_BR_ACCEL_MODE_EN_5T, SFE_PPPOE_BR_ACCEL_MODE_EN_3T,
+			SFE_PPPOE_BR_ACCEL_MODE_DISABLED, SFE_PPPOE_BR_ACCEL_MODE_EN_5T, SFE_PPPOE_BR_ACCEL_MODE_EN_3T);
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static const struct device_attribute sfe_pppoe_br_accel_mode_attr =
+	__ATTR(pppoe_br_accel_mode, 0644, sfe_get_pppoe_br_accel_mode, sfe_set_pppoe_br_accel_mode);
+
+/*
  * sfe_init_if()
  */
 int sfe_init_if(void)
@@ -1658,12 +1742,26 @@
 		goto exit2;
 	}
 
+	/*
+	 * Create sys/sfe/l2_feature
+	 */
 	result = sysfs_create_file(sfe_ctx->sys_sfe, &sfe_l2_feature_attr.attr);
 	if (result) {
 		DEBUG_ERROR("failed to register L2 feature flag sysfs file: %d\n", result);
 		goto exit2;
 	}
 
+	/*
+	 * Create sys/sfe/pppoe_br_accel_mode
+	 */
+	result = sysfs_create_file(sfe_ctx->sys_sfe, &sfe_pppoe_br_accel_mode_attr.attr);
+	if (result) {
+		DEBUG_ERROR("failed to create pppoe_br_accel_mode: %d\n", result);
+		goto exit2;
+	}
+
+	sfe_pppoe_mgr_init();
+
 	spin_lock_init(&sfe_ctx->lock);
 
 	INIT_LIST_HEAD(&sfe_ctx->msg_queue);
@@ -1694,6 +1792,8 @@
 	 */
 	RCU_INIT_POINTER(athrs_fast_nat_recv, NULL);
 
+	sfe_pppoe_mgr_exit();
+
 	/*
 	 * Wait for all callbacks to complete.
 	 */
diff --git a/sfe_ipv4.c b/sfe_ipv4.c
index 637ad9a..21683bc 100644
--- a/sfe_ipv4.c
+++ b/sfe_ipv4.c
@@ -45,6 +45,8 @@
 #include "sfe_ipv4_tcp.h"
 #include "sfe_ipv4_icmp.h"
 #include "sfe_pppoe.h"
+#include "sfe_pppoe_mgr.h"
+#include "sfe_ipv4_pppoe_br.h"
 #include "sfe_ipv4_gre.h"
 #include "sfe_ipv4_tun6rd.h"
 
@@ -90,6 +92,7 @@
 	"INVALID_PPPOE_SESSION",
 	"INCORRECT_PPPOE_PARSING",
 	"PPPOE_NOT_SET_IN_CME",
+	"PPPOE_BR_NOT_IN_CME",
 	"INGRESS_VLAN_TAG_MISMATCH",
 	"INVALID_SOURCE_INTERFACE",
 	"TUN6RD_NO_CONNECTION",
@@ -325,6 +328,7 @@
 		stats->pppoe_encap_packets_forwarded64 += s->pppoe_encap_packets_forwarded64;
 		stats->pppoe_decap_packets_forwarded64 += s->pppoe_decap_packets_forwarded64;
 		stats->pppoe_bridge_packets_forwarded64 += s->pppoe_bridge_packets_forwarded64;
+		stats->pppoe_bridge_packets_3tuple_forwarded64 += s->pppoe_bridge_packets_3tuple_forwarded64;
 	}
 
 }
@@ -425,7 +429,7 @@
 static inline unsigned int sfe_ipv4_get_connection_hash(u8 protocol, __be32 src_ip, __be16 src_port,
 							__be32 dest_ip, __be16 dest_port)
 {
-	u32 hash = ntohl(src_ip ^ dest_ip) ^ protocol ^ ntohs(src_port ^ dest_port);
+	u32 hash = ntohl(src_ip ^ dest_ip) ^ protocol ^ ntohs(src_port) ^ dest_port;
 	return ((hash >> SFE_IPV4_CONNECTION_HASH_SHIFT) ^ hash) & SFE_IPV4_CONNECTION_HASH_MASK;
 }
 
@@ -889,6 +893,17 @@
 		sync_on_find = true;
 	}
 
+	/*
+	 * Handle PPPoE bridge packets using 3-tuple acceleration if SFE_PPPOE_BR_ACCEL_MODE_EN_3T
+	 */
+	if (unlikely(sfe_l2_parse_flag_check(l2_info, SFE_L2_PARSE_FLAGS_PPPOE_INGRESS)) &&
+	    unlikely(sfe_pppoe_get_br_accel_mode() == SFE_PPPOE_BR_ACCEL_MODE_EN_3T)) {
+		struct ethhdr *eth = eth_hdr(skb);
+		if (!sfe_pppoe_mgr_find_session(l2_info->pppoe_session_id, eth->h_source)) {
+			return sfe_ipv4_recv_pppoe_bridge(si, skb, dev, len, iph, ihl, l2_info);
+		}
+	}
+
 	protocol = iph->protocol;
 	if (IPPROTO_UDP == protocol) {
 		return sfe_ipv4_recv_udp(si, skb, dev, len, iph, ihl, sync_on_find, l2_info, tun_outer);
@@ -1416,14 +1431,14 @@
 		ether_addr_copy(original_cm->pppoe_remote_mac, msg->pppoe_rule.flow_pppoe_remote_mac);
 
 		reply_cm->flags |= SFE_IPV4_CONNECTION_MATCH_FLAG_PPPOE_ENCAP;
-		reply_cm->l2_hdr_size += SFE_PPPOE_SESSION_HEADER_SIZE;
+		reply_cm->l2_hdr_size += PPPOE_SES_HLEN;
 		reply_cm->pppoe_session_id = msg->pppoe_rule.flow_pppoe_session_id;
 		ether_addr_copy(reply_cm->pppoe_remote_mac, msg->pppoe_rule.flow_pppoe_remote_mac);
 	}
 
 	if (msg->valid_flags & SFE_RULE_CREATE_PPPOE_ENCAP_VALID) {
 		original_cm->flags |= SFE_IPV4_CONNECTION_MATCH_FLAG_PPPOE_ENCAP;
-		original_cm->l2_hdr_size += SFE_PPPOE_SESSION_HEADER_SIZE;
+		original_cm->l2_hdr_size += PPPOE_SES_HLEN;
 		original_cm->pppoe_session_id = msg->pppoe_rule.return_pppoe_session_id;
 		ether_addr_copy(original_cm->pppoe_remote_mac, msg->pppoe_rule.return_pppoe_remote_mac);
 
@@ -1782,6 +1797,16 @@
 			reply_cm->flags |= SFE_IPV4_CONNECTION_MATCH_FLAG_NO_SEQ_CHECK;
 		}
 		break;
+
+	case IPPROTO_RAW:
+		/*
+		 * Set src_port to 0 to avoid hash collision in connection match lookups.
+		 */
+		original_cm->match_src_port = 0;
+		original_cm->xlate_src_port = 0;
+		reply_cm->match_src_port = 0;
+		reply_cm->xlate_src_port = 0;
+		break;
 	}
 
 	/*
@@ -1813,7 +1838,7 @@
 	/*
 	 * We have everything we need!
 	 */
-	DEBUG_INFO("NEW connection - p: %d\n"
+	DEBUG_INFO("%px: NEW connection - p: %d\n"
 		   "original_cm: match_dev=src_dev: %s %d %pM\n"
 		   " xmit_dev=dest_dev: %s %d %pM\n"
 		   " xmit_src_mac: %pM\n"
@@ -1831,7 +1856,7 @@
 		   "return_ip_xlate: %pI4:%u\n"
 		   "return_mac: %pM\n"
 		   "flags: valid=%x src_mac_valid=%x\n",
-		   tuple->protocol,
+		   c, tuple->protocol,
 		   original_cm->match_dev->name, original_cm->match_dev->ifindex, original_cm->match_dev->dev_addr,
 		   original_cm->xmit_dev->name, original_cm->xmit_dev->ifindex, original_cm->xmit_dev->dev_addr,
 		   original_cm->xmit_src_mac, original_cm->xmit_dest_mac, original_cm->flags, original_cm->l2_hdr_size,
@@ -2433,7 +2458,8 @@
 			      "hash_hits=\"%llu\" hash_reorders=\"%llu\" "
 			      "pppoe_encap_pkts_fwded=\"%llu\" "
 			      "pppoe_decap_pkts_fwded=\"%llu\" "
-			      "pppoe_bridge_pkts_fwded=\"%llu\" />\n",
+			      "pppoe_bridge_pkts_fwded=\"%llu\" "
+			      "pppoe_bridge_pkts_3tuple_fwded=\"%llu\" />\n",
 				num_conn,
 				stats.packets_dropped64,
 				stats.packets_fast_xmited64,
@@ -2449,7 +2475,8 @@
 				stats.connection_match_hash_reorders64,
 				stats.pppoe_encap_packets_forwarded64,
 				stats.pppoe_decap_packets_forwarded64,
-				stats.pppoe_bridge_packets_forwarded64);
+				stats.pppoe_bridge_packets_forwarded64,
+				stats.pppoe_bridge_packets_3tuple_forwarded64);
 	if (copy_to_user(buffer + *total_read, msg, CHAR_DEV_MSG_SIZE)) {
 		return false;
 	}
diff --git a/sfe_ipv4.h b/sfe_ipv4.h
index 6c058e0..53e8557 100644
--- a/sfe_ipv4.h
+++ b/sfe_ipv4.h
@@ -289,6 +289,7 @@
 	SFE_IPV4_EXCEPTION_EVENT_INVALID_PPPOE_SESSION,
 	SFE_IPV4_EXCEPTION_EVENT_INCORRECT_PPPOE_PARSING,
 	SFE_IPV4_EXCEPTION_EVENT_PPPOE_NOT_SET_IN_CME,
+	SFE_IPV4_EXCEPTION_EVENT_PPPOE_BR_NOT_IN_CME,
 	SFE_IPV4_EXCEPTION_EVENT_INGRESS_VLAN_TAG_MISMATCH,
 	SFE_IPV4_EXCEPTION_EVENT_INVALID_SRC_IFACE,
 	SFE_IPV4_EXCEPTION_EVENT_TUN6RD_NO_CONNECTION,
@@ -333,6 +334,7 @@
 	u64 pppoe_encap_packets_forwarded64;	/* Number of IPv4 PPPoE encap packets forwarded */
 	u64 pppoe_decap_packets_forwarded64;	/* Number of IPv4 PPPoE decap packets forwarded */
 	u64 pppoe_bridge_packets_forwarded64;	/* Number of IPv4 PPPoE bridge packets forwarded */
+	u64 pppoe_bridge_packets_3tuple_forwarded64;    /* Number of IPv4 PPPoE bridge packets forwarded based on 3-tuple info */
 };
 
 /*
diff --git a/sfe_ipv4_pppoe_br.c b/sfe_ipv4_pppoe_br.c
new file mode 100644
index 0000000..6d12853
--- /dev/null
+++ b/sfe_ipv4_pppoe_br.c
@@ -0,0 +1,204 @@
+/*
+ * sfe_ipv4_pppoe_br.c
+ *	Shortcut forwarding engine - IPv4 PPPoE bridge implementation
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/skbuff.h>
+#include <net/udp.h>
+#include <linux/etherdevice.h>
+#include <linux/version.h>
+
+#include "sfe_debug.h"
+#include "sfe_api.h"
+#include "sfe.h"
+#include "sfe_ipv4.h"
+#include "sfe_pppoe.h"
+#include "sfe_vlan.h"
+
+/*
+ * sfe_ipv4_recv_pppoe_bridge()
+ *	Process PPPoE bridge packets using 3-tuple acceleration
+ *
+ */
+int sfe_ipv4_recv_pppoe_bridge(struct sfe_ipv4 *si, struct sk_buff *skb, struct net_device *dev,
+			      unsigned int len, struct iphdr *iph, unsigned int ihl, struct sfe_l2_info *l2_info)
+{
+	struct sfe_ipv4_connection_match *cm;
+	u32 service_class_id;
+	struct net_device *xmit_dev;
+	int ret;
+	bool fast_xmit;
+	netdev_features_t features;
+
+	rcu_read_lock();
+
+	cm = sfe_ipv4_find_connection_match_rcu(si, dev, IPPROTO_RAW, iph->saddr, 0, iph->daddr, htons(sfe_l2_pppoe_session_id_get(l2_info)));
+	if (unlikely(!cm)) {
+		rcu_read_unlock();
+		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_PPPOE_BR_NOT_IN_CME);
+		DEBUG_TRACE("%px: no connection found in 3-tuple lookup for PPPoE bridge flow\n", skb);
+		return 0;
+	}
+
+	/*
+	 * Source interface validate.
+	 */
+	if (unlikely((cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_SRC_INTERFACE_CHECK) && (cm->match_dev != dev))) {
+		if (!(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_SRC_INTERFACE_CHECK_NO_FLUSH)) {
+			struct sfe_ipv4_connection *c = cm->connection;
+			DEBUG_TRACE("flush on source interface check failure\n");
+			spin_lock_bh(&si->lock);
+			ret = sfe_ipv4_remove_connection(si, c);
+			spin_unlock_bh(&si->lock);
+
+			if (ret) {
+				sfe_ipv4_flush_connection(si, c, SFE_SYNC_REASON_FLUSH);
+			}
+		}
+		rcu_read_unlock();
+		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_INVALID_SRC_IFACE);
+		DEBUG_TRACE("exception the packet on source interface check failure\n");
+		return 0;
+	}
+
+	/*
+	 * Do we expect an ingress VLAN tag for this flow?
+	 */
+	if (unlikely(!sfe_vlan_validate_ingress_tag(skb, cm->ingress_vlan_hdr_cnt, cm->ingress_vlan_hdr, l2_info))) {
+		rcu_read_unlock();
+		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_INGRESS_VLAN_TAG_MISMATCH);
+		DEBUG_TRACE("VLAN tag mismatch. skb=%px\n", skb);
+		return 0;
+	}
+
+	/*
+	 * Check if skb has enough headroom to write L2 headers
+	 */
+	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
+		rcu_read_unlock();
+		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
+		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_NO_HEADROOM);
+		return 0;
+	}
+
+	/*
+	 * Restore PPPoE header back
+	 */
+	__skb_push(skb, PPPOE_SES_HLEN);
+
+	/*
+	 * Update traffic stats.
+	 */
+	atomic_inc(&cm->rx_packet_count);
+	atomic_add(len, &cm->rx_byte_count);
+
+	xmit_dev = cm->xmit_dev;
+	skb->dev = xmit_dev;
+
+	/*
+	 * Check to see if we need to add VLAN tags
+	 */
+	if (unlikely(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_INSERT_EGRESS_VLAN_TAG)) {
+		sfe_vlan_add_tag(skb, cm->egress_vlan_hdr_cnt, cm->egress_vlan_hdr);
+	}
+
+	/*
+	 * Check to see if we need to write an Ethernet header.
+	 */
+	if (likely(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_WRITE_L2_HDR)) {
+		if (unlikely(!(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_WRITE_FAST_ETH_HDR))) {
+			dev_hard_header(skb, xmit_dev, ntohs(skb->protocol),
+					cm->xmit_dest_mac, cm->xmit_src_mac, len);
+		} else {
+			/*
+			 * For the simple case we write this really fast.
+			 */
+			struct ethhdr *eth = (struct ethhdr *)__skb_push(skb, ETH_HLEN);
+			eth->h_proto = skb->protocol;
+			ether_addr_copy((u8 *)eth->h_dest, (u8 *)cm->xmit_dest_mac);
+			ether_addr_copy((u8 *)eth->h_source, (u8 *)cm->xmit_src_mac);
+		}
+	}
+
+	/*
+	 * Update priority of skb.
+	 */
+	if (unlikely(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_PRIORITY_REMARK)) {
+		skb->priority = cm->priority;
+	}
+
+	/*
+	 * Mark outgoing packet.
+	 */
+	if (unlikely(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_MARK)) {
+		skb->mark = cm->mark;
+		/*
+		 * Update service class stats if SAWF is valid.
+		 */
+		if (likely(cm->sawf_valid)) {
+			service_class_id = SFE_GET_SAWF_SERVICE_CLASS(cm->mark);
+			sfe_ipv4_service_class_stats_inc(si, service_class_id, len);
+		}
+	}
+
+	/*
+	 * For the first packets, check if it could got fast xmit.
+	 */
+	if (unlikely(!(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_FAST_XMIT_FLOW_CHECKED)
+				&& (cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_FAST_XMIT_DEV_ADMISSION))){
+		cm->features = netif_skb_features(skb);
+		if (likely(sfe_fast_xmit_check(skb, cm->features))) {
+			cm->flags |= SFE_IPV4_CONNECTION_MATCH_FLAG_FAST_XMIT;
+		}
+		cm->flags |= SFE_IPV4_CONNECTION_MATCH_FLAG_FAST_XMIT_FLOW_CHECKED;
+	}
+	features = cm->features;
+
+	fast_xmit = !!(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_FAST_XMIT);
+
+	rcu_read_unlock();
+
+	this_cpu_inc(si->stats_pcpu->pppoe_bridge_packets_3tuple_forwarded64);
+	this_cpu_inc(si->stats_pcpu->packets_forwarded64);
+
+	/*
+	 * We're going to check for GSO flags when we transmit the packet so
+	 * start fetching the necessary cache line now.
+	 */
+	prefetch(skb_shinfo(skb));
+
+	/*
+	 * We do per packet condition check before we could fast xmit the
+	 * packet.
+	 */
+	if (likely(fast_xmit && dev_fast_xmit(skb, xmit_dev, features))) {
+		this_cpu_inc(si->stats_pcpu->packets_fast_xmited64);
+		return 1;
+	}
+
+	/*
+	 * Mark that this packet has been fast forwarded.
+	 */
+	skb->fast_forwarded = 1;
+
+	/*
+	 * Send the packet on its way.
+	 */
+	dev_queue_xmit(skb);
+
+	return 1;
+}
diff --git a/sfe_ipv4_pppoe_br.h b/sfe_ipv4_pppoe_br.h
new file mode 100644
index 0000000..1491495
--- /dev/null
+++ b/sfe_ipv4_pppoe_br.h
@@ -0,0 +1,20 @@
+/*
+ * sfe_ipv4_pppoe_br.h
+ *	Shortcut forwarding engine - IPv4 PPPoE bridge header file
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+int sfe_ipv4_recv_pppoe_bridge(struct sfe_ipv4 *si, struct sk_buff *skb, struct net_device *dev,
+			      unsigned int len, struct iphdr *iph, unsigned int ihl, struct sfe_l2_info *l2_info);
diff --git a/sfe_ipv4_tcp.c b/sfe_ipv4_tcp.c
index aea240e..8ed26fd 100644
--- a/sfe_ipv4_tcp.c
+++ b/sfe_ipv4_tcp.c
@@ -168,6 +168,9 @@
 		cm = sfe_ipv4_find_connection_match_rcu(si, dev, IPPROTO_TCP, src_ip, src_port, dest_ip, dest_port);
 	}
 #else
+	/*
+	 * 5-tuple lookup for TCP flow.
+	 */
 	cm = sfe_ipv4_find_connection_match_rcu(si, dev, IPPROTO_TCP, src_ip, src_port, dest_ip, dest_port);
 #endif
 	if (unlikely(!cm)) {
@@ -213,7 +216,7 @@
 	}
 
 	/*
-	 * If our packet has beern marked as "flush on find" we can't actually
+	 * If our packet has been marked as "sync on find" we can't actually
 	 * forward it in the fast path, but now that we've found an associated
 	 * connection we need sync its status before throw it slow path.
 	 */
@@ -288,8 +291,8 @@
 		ret = sfe_ipv4_remove_connection(si, c);
 		spin_unlock_bh(&si->lock);
 
-		DEBUG_TRACE("TCP flags: 0x%x are not fast\n",
-			    flags & (TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_FIN | TCP_FLAG_ACK));
+		DEBUG_TRACE("TCP flags: %#x are not fast. %u->%u\n",
+				htonl(flags), htons(src_port), htons(dest_port));
 		if (ret) {
 			sfe_ipv4_flush_connection(si, c, SFE_SYNC_REASON_FLUSH);
 		}
@@ -500,6 +503,16 @@
 	}
 
 	/*
+	 * Check if skb has enough headroom to write L2 headers
+	 */
+	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
+		rcu_read_unlock();
+		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
+		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_NO_HEADROOM);
+		return 0;
+	}
+
+	/*
 	 * For PPPoE packets, match server MAC and session id
 	 */
 	if (unlikely(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_PPPOE_DECAP)) {
@@ -535,7 +548,7 @@
 		 * If packet contains PPPoE header but CME doesn't contain PPPoE flag yet we are exceptioning
 		 * the packet to linux
 		 */
-		if (unlikely(!(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_BRIDGE_FLOW))) {
+		if (unlikely(!bridge_flow)) {
 			rcu_read_unlock();
 			DEBUG_TRACE("%px: CME doesn't contain PPPoE flag but packet has PPPoE header\n", skb);
 			sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_PPPOE_NOT_SET_IN_CME);
@@ -546,21 +559,11 @@
 		 * For bridged flows when packet contains PPPoE header, restore the header back and forward
 		 * to xmit interface
 		 */
-		__skb_push(skb, (sizeof(struct pppoe_hdr) + sizeof(struct sfe_ppp_hdr)));
+		__skb_push(skb, PPPOE_SES_HLEN);
 		this_cpu_inc(si->stats_pcpu->pppoe_bridge_packets_forwarded64);
 	}
 
 	/*
-	 * Check if skb has enough headroom to write L2 headers
-	 */
-	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
-		rcu_read_unlock();
-		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
-		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_NO_HEADROOM);
-		return 0;
-	}
-
-	/*
 	 * From this point on we're good to modify the packet.
 	 */
 
diff --git a/sfe_ipv4_udp.c b/sfe_ipv4_udp.c
index 922e54e..9e71111 100644
--- a/sfe_ipv4_udp.c
+++ b/sfe_ipv4_udp.c
@@ -176,7 +176,7 @@
 	if (unlikely(!cm)) {
 
 		/*
-		 * try a 4-tuple lookup; required for tunnels like vxlan.
+		 * Try a 4-tuple lookup; required for tunnels like vxlan.
 		 */
 		cm = sfe_ipv4_find_connection_match_rcu(si, dev, IPPROTO_UDP, src_ip, 0, dest_ip, dest_port);
 		if (unlikely(!cm)) {
@@ -209,7 +209,7 @@
 	}
 
 	/*
-	 * If our packet has beern marked as "flush on find" we can't actually
+	 * If our packet has been marked as "sync on find" we can't actually
 	 * forward it in the fast path, but now that we've found an associated
 	 * connection we need sync its status before exception it to slow path.
 	 */
@@ -217,7 +217,7 @@
 		sfe_ipv4_sync_status(si, cm->connection, SFE_SYNC_REASON_STATS);
 		rcu_read_unlock();
 		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_UDP_IP_OPTIONS_OR_INITIAL_FRAGMENT);
-		DEBUG_TRACE("%px: sfe: sync on find\n", cm);
+		DEBUG_TRACE("%px: sync on find\n", cm);
 		return 0;
 	}
 
@@ -294,6 +294,16 @@
 	}
 
 	/*
+	 * Check if skb has enough headroom to write L2 headers
+	 */
+	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
+		rcu_read_unlock();
+		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
+		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_NO_HEADROOM);
+		return 0;
+	}
+
+	/*
 	 * For PPPoE packets, match server MAC and session id
 	 */
 	if (unlikely(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_PPPOE_DECAP)) {
@@ -329,33 +339,22 @@
 		 * If packet contains PPPoE header but CME doesn't contain PPPoE flag yet we are exceptioning
 		 * the packet to linux
 		 */
-		if (unlikely(!(cm->flags & SFE_IPV4_CONNECTION_MATCH_FLAG_BRIDGE_FLOW))) {
+		if (unlikely(!bridge_flow)) {
 			rcu_read_unlock();
 			DEBUG_TRACE("%px: CME doesn't contain PPPoE flag but packet has PPPoE header\n", skb);
 			sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_PPPOE_NOT_SET_IN_CME);
 			return 0;
-
 		}
 
 		/*
 		 * For bridged flows when packet contains PPPoE header, restore the header back and forward
 		 * to xmit interface
 		 */
-		__skb_push(skb, (sizeof(struct pppoe_hdr) + sizeof(struct sfe_ppp_hdr)));
+		__skb_push(skb, PPPOE_SES_HLEN);
 		this_cpu_inc(si->stats_pcpu->pppoe_bridge_packets_forwarded64);
 	}
 
 	/*
-	 * Check if skb has enough headroom to write L2 headers
-	 */
-	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
-		rcu_read_unlock();
-		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
-		sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_NO_HEADROOM);
-		return 0;
-	}
-
-	/*
 	 * From this point on we're good to modify the packet.
 	 */
 
diff --git a/sfe_ipv6.c b/sfe_ipv6.c
index 5f2d791..1a08f1e 100644
--- a/sfe_ipv6.c
+++ b/sfe_ipv6.c
@@ -45,6 +45,8 @@
 #include "sfe_ipv6_tcp.h"
 #include "sfe_ipv6_icmp.h"
 #include "sfe_pppoe.h"
+#include "sfe_pppoe_mgr.h"
+#include "sfe_ipv6_pppoe_br.h"
 #include "sfe_ipv6_tunipip6.h"
 #include "sfe_ipv6_gre.h"
 
@@ -92,6 +94,7 @@
 	"INVALID_PPPOE_SESSION",
 	"INCORRECT_PPPOE_PARSING",
 	"PPPOE_NOT_SET_IN_CME",
+	"PPPOE_BR_NOT_IN_CME",
 	"INGRESS_VLAN_TAG_MISMATCH",
 	"INVALID_SOURCE_INTERFACE",
 	"TUNIPIP6_HEADER_INCOMPLETE",
@@ -330,6 +333,7 @@
 		stats->pppoe_encap_packets_forwarded64 += s->pppoe_encap_packets_forwarded64;
 		stats->pppoe_decap_packets_forwarded64 += s->pppoe_decap_packets_forwarded64;
 		stats->pppoe_bridge_packets_forwarded64 += s->pppoe_bridge_packets_forwarded64;
+		stats->pppoe_bridge_packets_3tuple_forwarded64 += s->pppoe_bridge_packets_3tuple_forwarded64;
 	}
 }
 
@@ -436,7 +440,7 @@
 	for (idx = 0; idx < 4; idx++) {
 		hash ^= src_ip->addr[idx] ^ dest_ip->addr[idx];
 	}
-	hash = hash ^ protocol ^ ntohs(src_port ^ dest_port);
+	hash = hash ^ protocol ^ ntohs(src_port) ^ dest_port;
 	return ((hash >> SFE_IPV6_CONNECTION_HASH_SHIFT) ^ hash) & SFE_IPV6_CONNECTION_HASH_MASK;
 }
 
@@ -913,6 +917,17 @@
 		next_hdr = ext_hdr->next_hdr;
 	}
 
+	/*
+	 * Handle PPPoE bridge packets using 3-tuple acceleration if SFE_PPPOE_BR_ACCEL_MODE_EN_3T
+	 */
+	if (unlikely(sfe_l2_parse_flag_check(l2_info, SFE_L2_PARSE_FLAGS_PPPOE_INGRESS)) &&
+	    unlikely(sfe_pppoe_get_br_accel_mode() == SFE_PPPOE_BR_ACCEL_MODE_EN_3T)) {
+		struct ethhdr *eth = eth_hdr(skb);
+		if (!sfe_pppoe_mgr_find_session(l2_info->pppoe_session_id, eth->h_source)) {
+			return sfe_ipv6_recv_pppoe_bridge(si, skb, dev, len, iph, ihl, l2_info);
+		}
+	}
+
 	if (IPPROTO_UDP == next_hdr) {
 		return sfe_ipv6_recv_udp(si, skb, dev, len, iph, ihl, sync_on_find, l2_info, tun_outer);
 	}
@@ -1389,14 +1404,14 @@
 		ether_addr_copy(original_cm->pppoe_remote_mac, msg->pppoe_rule.flow_pppoe_remote_mac);
 
 		reply_cm->flags |= SFE_IPV6_CONNECTION_MATCH_FLAG_PPPOE_ENCAP;
-		reply_cm->l2_hdr_size += SFE_PPPOE_SESSION_HEADER_SIZE;
+		reply_cm->l2_hdr_size += PPPOE_SES_HLEN;
 		reply_cm->pppoe_session_id = msg->pppoe_rule.flow_pppoe_session_id;
 		ether_addr_copy(reply_cm->pppoe_remote_mac, msg->pppoe_rule.flow_pppoe_remote_mac);
 	}
 
 	if (msg->valid_flags & SFE_RULE_CREATE_PPPOE_ENCAP_VALID) {
 		original_cm->flags |= SFE_IPV6_CONNECTION_MATCH_FLAG_PPPOE_ENCAP;
-		original_cm->l2_hdr_size += SFE_PPPOE_SESSION_HEADER_SIZE;
+		original_cm->l2_hdr_size += PPPOE_SES_HLEN;
 		original_cm->pppoe_session_id = msg->pppoe_rule.return_pppoe_session_id;
 		ether_addr_copy(original_cm->pppoe_remote_mac, msg->pppoe_rule.return_pppoe_remote_mac);
 
@@ -1617,7 +1632,7 @@
 	reply_cm->top_interface_dev = NULL;
 
 #ifdef SFE_GRE_TUN_ENABLE
-	if (!(reply_cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_PASSTHROUGH)) {
+	if ((IPPROTO_GRE == tuple->protocol) && !(reply_cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_PASSTHROUGH)) {
 		rcu_read_lock();
 		reply_cm->proto = rcu_dereference(inet6_protos[tuple->protocol]);
 		rcu_read_unlock();
@@ -1745,6 +1760,16 @@
 			reply_cm->flags |= SFE_IPV6_CONNECTION_MATCH_FLAG_NO_SEQ_CHECK;
 		}
 		break;
+
+	case IPPROTO_RAW:
+		/*
+		 * Set src_port to 0 to avoid hash collision in connection match lookups.
+		 */
+		original_cm->match_src_port = 0;
+		original_cm->xlate_src_port = 0;
+		reply_cm->match_src_port = 0;
+		reply_cm->xlate_src_port = 0;
+		break;
 	}
 
 	/*
@@ -1778,10 +1803,10 @@
 	/*
 	 * We have everything we need!
 	 */
-	DEBUG_INFO("new connection - p: %d\n"
+	DEBUG_INFO("%px: new connection - p: %d\n"
 		   "  s: %s:%pxM(%pxM):%pI6(%pI6):%u(%u)\n"
 		   "  d: %s:%pxM(%pxM):%pI6(%pI6):%u(%u)\n",
-		   tuple->protocol,
+		   c, tuple->protocol,
 		   src_dev->name, msg->conn_rule.flow_mac, NULL,
 		   (void *)tuple->flow_ip, (void *)tuple->flow_ip, ntohs(tuple->flow_ident), ntohs(tuple->flow_ident),
 		   dest_dev->name, NULL, msg->conn_rule.return_mac,
@@ -2369,8 +2394,8 @@
 			      "hash_hits=\"%llu\" hash_reorders=\"%llu\" "
 			      "pppoe_encap_pkts_fwded=\"%llu\" "
 			      "pppoe_decap_pkts_fwded=\"%llu\" "
-			      "pppoe_bridge_pkts_fwded=\"%llu\" />\n",
-
+			      "pppoe_bridge_pkts_fwded=\"%llu\" "
+			      "pppoe_bridge_pkts_3tuple_fwded=\"%llu\" />\n",
 				num_conn,
 				stats.packets_dropped64,
 				stats.packets_fast_xmited64,
@@ -2386,7 +2411,8 @@
 				stats.connection_match_hash_reorders64,
 				stats.pppoe_encap_packets_forwarded64,
 				stats.pppoe_decap_packets_forwarded64,
-				stats.pppoe_bridge_packets_forwarded64);
+				stats.pppoe_bridge_packets_forwarded64,
+				stats.pppoe_bridge_packets_3tuple_forwarded64);
 	if (copy_to_user(buffer + *total_read, msg, CHAR_DEV_MSG_SIZE)) {
 		return false;
 	}
diff --git a/sfe_ipv6.h b/sfe_ipv6.h
index 865a7e1..2df32e7 100644
--- a/sfe_ipv6.h
+++ b/sfe_ipv6.h
@@ -296,6 +296,7 @@
 	SFE_IPV6_EXCEPTION_EVENT_INVALID_PPPOE_SESSION,
 	SFE_IPV6_EXCEPTION_EVENT_INCORRECT_PPPOE_PARSING,
 	SFE_IPV6_EXCEPTION_EVENT_PPPOE_NOT_SET_IN_CME,
+	SFE_IPV6_EXCEPTION_EVENT_PPPOE_BR_NOT_IN_CME,
 	SFE_IPV6_EXCEPTION_EVENT_INGRESS_VLAN_TAG_MISMATCH,
 	SFE_IPV6_EXCEPTION_EVENT_INVALID_SRC_IFACE,
 	SFE_IPV6_EXCEPTION_EVENT_TUNIPIP6_HEADER_INCOMPLETE,
@@ -344,6 +345,7 @@
 	u64 pppoe_encap_packets_forwarded64;	/* Number of IPv6 PPPoE encap packets forwarded */
 	u64 pppoe_decap_packets_forwarded64;	/* Number of IPv6 PPPoE decap packets forwarded */
 	u64 pppoe_bridge_packets_forwarded64;	/* Number of IPv6 PPPoE decap packets forwarded */
+	u64 pppoe_bridge_packets_3tuple_forwarded64;    /* Number of IPv6 PPPoE bridge packets forwarded based on 3-tuple info */
 };
 
 /*
diff --git a/sfe_ipv6_pppoe_br.c b/sfe_ipv6_pppoe_br.c
new file mode 100644
index 0000000..f3c80b7
--- /dev/null
+++ b/sfe_ipv6_pppoe_br.c
@@ -0,0 +1,207 @@
+/*
+ * sfe_ipv6_pppoe_br.c
+ *	Shortcut forwarding engine - IPv6 PPPoE bridge implementation
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/skbuff.h>
+#include <net/udp.h>
+#include <linux/etherdevice.h>
+#include <linux/version.h>
+
+#include "sfe_debug.h"
+#include "sfe_api.h"
+#include "sfe.h"
+#include "sfe_ipv6.h"
+#include "sfe_pppoe.h"
+#include "sfe_vlan.h"
+
+/*
+ * sfe_ipv6_recv_pppoe_bridge()
+ *	Process PPPoE bridge packets using 3-tuple acceleration
+ *
+ */
+int sfe_ipv6_recv_pppoe_bridge(struct sfe_ipv6 *si, struct sk_buff *skb, struct net_device *dev,
+			      unsigned int len, struct ipv6hdr *iph, unsigned int ihl, struct sfe_l2_info *l2_info)
+{
+	struct sfe_ipv6_connection_match *cm;
+	u32 service_class_id;
+	struct net_device *xmit_dev;
+	int ret;
+	bool fast_xmit;
+	netdev_features_t features;
+
+	rcu_read_lock();
+
+	cm = sfe_ipv6_find_connection_match_rcu(si, dev, IPPROTO_RAW,
+						(struct sfe_ipv6_addr *)iph->saddr.s6_addr32, 0,
+						(struct sfe_ipv6_addr *)iph->daddr.s6_addr32,
+						htons(sfe_l2_pppoe_session_id_get(l2_info)));
+	if (unlikely(!cm)) {
+		rcu_read_unlock();
+		sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_PPPOE_BR_NOT_IN_CME);
+		DEBUG_TRACE("%px: no connection found in 3-tuple lookup for PPPoE bridge flow\n", skb);
+		return 0;
+	}
+
+	/*
+	 * Source interface validate.
+	 */
+	if (unlikely((cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_SRC_INTERFACE_CHECK) && (cm->match_dev != dev))) {
+		if (!(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_SRC_INTERFACE_CHECK_NO_FLUSH)) {
+			struct sfe_ipv6_connection *c = cm->connection;
+			DEBUG_TRACE("flush on source interface check failure\n");
+			spin_lock_bh(&si->lock);
+			ret = sfe_ipv6_remove_connection(si, c);
+			spin_unlock_bh(&si->lock);
+
+			if (ret) {
+				sfe_ipv6_flush_connection(si, c, SFE_SYNC_REASON_FLUSH);
+			}
+		}
+		rcu_read_unlock();
+		sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_INVALID_SRC_IFACE);
+		DEBUG_TRACE("exception the packet on source interface check failure\n");
+		return 0;
+	}
+
+	/*
+	 * Do we expect an ingress VLAN tag for this flow?
+	 */
+	if (unlikely(!sfe_vlan_validate_ingress_tag(skb, cm->ingress_vlan_hdr_cnt, cm->ingress_vlan_hdr, l2_info))) {
+		rcu_read_unlock();
+		sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_INGRESS_VLAN_TAG_MISMATCH);
+		DEBUG_TRACE("VLAN tag mismatch. skb=%px\n", skb);
+		return 0;
+	}
+
+	/*
+	 * Check if skb has enough headroom to write L2 headers
+	 */
+	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
+		rcu_read_unlock();
+		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
+		sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_NO_HEADROOM);
+		return 0;
+	}
+
+	/*
+	 * Restore PPPoE header back
+	 */
+	__skb_push(skb, PPPOE_SES_HLEN);
+
+	/*
+	 * Update traffic stats.
+	 */
+	atomic_inc(&cm->rx_packet_count);
+	atomic_add(len, &cm->rx_byte_count);
+
+	xmit_dev = cm->xmit_dev;
+	skb->dev = xmit_dev;
+
+	/*
+	 * Check to see if we need to add VLAN tags
+	 */
+	if (unlikely(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_INSERT_EGRESS_VLAN_TAG)) {
+		sfe_vlan_add_tag(skb, cm->egress_vlan_hdr_cnt, cm->egress_vlan_hdr);
+	}
+
+	/*
+	 * Check to see if we need to write an Ethernet header.
+	 */
+	if (likely(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_WRITE_L2_HDR)) {
+		if (unlikely(!(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_WRITE_FAST_ETH_HDR))) {
+			dev_hard_header(skb, xmit_dev, ntohs(skb->protocol),
+					cm->xmit_dest_mac, cm->xmit_src_mac, len);
+		} else {
+			/*
+			 * For the simple case we write this really fast.
+			 */
+			struct ethhdr *eth = (struct ethhdr *)__skb_push(skb, ETH_HLEN);
+			eth->h_proto = skb->protocol;
+			ether_addr_copy((u8 *)eth->h_dest, (u8 *)cm->xmit_dest_mac);
+			ether_addr_copy((u8 *)eth->h_source, (u8 *)cm->xmit_src_mac);
+		}
+	}
+
+	/*
+	 * Update priority of skb.
+	 */
+	if (unlikely(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_PRIORITY_REMARK)) {
+		skb->priority = cm->priority;
+	}
+
+	/*
+	 * Mark outgoing packet.
+	 */
+	if (unlikely(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_MARK)) {
+		skb->mark = cm->mark;
+		/*
+		 * Update service class stats if SAWF is valid.
+		 */
+		if (likely(cm->sawf_valid)) {
+			service_class_id = SFE_GET_SAWF_SERVICE_CLASS(cm->mark);
+			sfe_ipv6_service_class_stats_inc(si, service_class_id, len);
+		}
+	}
+
+	/*
+	 * For the first packets, check if it could got fast xmit.
+	 */
+	if (unlikely(!(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_FAST_XMIT_FLOW_CHECKED)
+				&& (cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_FAST_XMIT_DEV_ADMISSION))){
+		cm->features = netif_skb_features(skb);
+		if (likely(sfe_fast_xmit_check(skb, cm->features))) {
+			cm->flags |= SFE_IPV6_CONNECTION_MATCH_FLAG_FAST_XMIT;
+		}
+		cm->flags |= SFE_IPV6_CONNECTION_MATCH_FLAG_FAST_XMIT_FLOW_CHECKED;
+	}
+	features = cm->features;
+
+	fast_xmit = !!(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_FAST_XMIT);
+
+	rcu_read_unlock();
+
+	this_cpu_inc(si->stats_pcpu->pppoe_bridge_packets_3tuple_forwarded64);
+	this_cpu_inc(si->stats_pcpu->packets_forwarded64);
+
+	/*
+	 * We're going to check for GSO flags when we transmit the packet so
+	 * start fetching the necessary cache line now.
+	 */
+	prefetch(skb_shinfo(skb));
+
+	/*
+	 * We do per packet condition check before we could fast xmit the
+	 * packet.
+	 */
+	if (likely(fast_xmit && dev_fast_xmit(skb, xmit_dev, features))) {
+		this_cpu_inc(si->stats_pcpu->packets_fast_xmited64);
+		return 1;
+	}
+
+	/*
+	 * Mark that this packet has been fast forwarded.
+	 */
+	skb->fast_forwarded = 1;
+
+	/*
+	 * Send the packet on its way.
+	 */
+	dev_queue_xmit(skb);
+
+	return 1;
+}
diff --git a/sfe_ipv6_pppoe_br.h b/sfe_ipv6_pppoe_br.h
new file mode 100644
index 0000000..84dc313
--- /dev/null
+++ b/sfe_ipv6_pppoe_br.h
@@ -0,0 +1,20 @@
+/*
+ * sfe_ipv6_pppoe_br.h
+ *	Shortcut forwarding engine - IPv6 PPPoE bridge header file
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+int sfe_ipv6_recv_pppoe_bridge(struct sfe_ipv6 *si, struct sk_buff *skb, struct net_device *dev,
+			      unsigned int len, struct ipv6hdr *iph, unsigned int ihl, struct sfe_l2_info *l2_info);
diff --git a/sfe_ipv6_tcp.c b/sfe_ipv6_tcp.c
index e2f2cd8..0cf867f 100644
--- a/sfe_ipv6_tcp.c
+++ b/sfe_ipv6_tcp.c
@@ -215,7 +215,7 @@
 	}
 
 	/*
-	 * If our packet has beern marked as "flush on find" we can't actually
+	 * If our packet has been marked as "sync on find" we can't actually
 	 * forward it in the fast path, but now that we've found an associated
 	 * connection we need sync its status before throw it slow path.
 	 */
@@ -289,8 +289,8 @@
 		ret = sfe_ipv6_remove_connection(si, c);
 		spin_unlock_bh(&si->lock);
 
-		DEBUG_TRACE("TCP flags: 0x%x are not fast\n",
-			    flags & (TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_FIN | TCP_FLAG_ACK));
+		DEBUG_TRACE("TCP flags: %#x are not fast. %u->%u skb=%px\n",
+				htonl(flags), htons(src_port), htons(dest_port), skb);
 		if (ret) {
 			sfe_ipv6_flush_connection(si, c, SFE_SYNC_REASON_FLUSH);
 		}
@@ -509,6 +509,16 @@
 	}
 
 	/*
+	 * Check if skb has enough headroom to write L2 headers
+	 */
+	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
+		rcu_read_unlock();
+		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
+		sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_NO_HEADROOM);
+		return 0;
+	}
+
+	/*
 	 * For PPPoE packets, match server MAC and session id
 	 */
 	if (unlikely(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_PPPOE_DECAP)) {
@@ -543,7 +553,7 @@
 		/*
 		 * If packet contains PPPoE header but CME doesn't contain PPPoE flag yet we are exceptioning the packet to linux
 		 */
-		if (unlikely(!(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_BRIDGE_FLOW))) {
+		if (unlikely(!bridge_flow)) {
 			rcu_read_unlock();
 			DEBUG_TRACE("%px: CME doesn't contain PPPoE flag but packet has PPPoE header\n", skb);
 			sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_PPPOE_NOT_SET_IN_CME);
@@ -554,21 +564,11 @@
 		/*
 		 * For bridged flows when packet contains PPPoE header, restore the header back and forward to xmit interface
 		 */
-		__skb_push(skb, (sizeof(struct pppoe_hdr) + sizeof(struct sfe_ppp_hdr)));
+		__skb_push(skb, PPPOE_SES_HLEN);
 		this_cpu_inc(si->stats_pcpu->pppoe_bridge_packets_forwarded64);
 	}
 
 	/*
-	 * Check if skb has enough headroom to write L2 headers
-	 */
-	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
-		rcu_read_unlock();
-		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
-		sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_NO_HEADROOM);
-		return 0;
-	}
-
-	/*
 	 * From this point on we're good to modify the packet.
 	 */
 
diff --git a/sfe_ipv6_udp.c b/sfe_ipv6_udp.c
index 0afa6f3..18af651 100644
--- a/sfe_ipv6_udp.c
+++ b/sfe_ipv6_udp.c
@@ -225,7 +225,7 @@
 	}
 
 	/*
-	 * If our packet has been marked as "flush on find" we can't actually
+	 * If our packet has been marked as "sync on find" we can't actually
 	 * forward it in the fast path, but now that we've found an associated
 	 * connection we need sync its status before exception it to slow path.
 	 */
@@ -301,6 +301,16 @@
 	}
 
 	/*
+	 * Check if skb has enough headroom to write L2 headers
+	 */
+	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
+		rcu_read_unlock();
+		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
+		sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_NO_HEADROOM);
+		return 0;
+	}
+
+	/*
 	 * For PPPoE packets, match server MAC and session id
 	 */
 	if (unlikely(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_PPPOE_DECAP)) {
@@ -335,32 +345,21 @@
 		/*
 		 * If packet contains PPPoE header but CME doesn't contain PPPoE flag yet we are exceptioning the packet to linux
 		 */
-		if (unlikely(!(cm->flags & SFE_IPV6_CONNECTION_MATCH_FLAG_BRIDGE_FLOW))) {
+		if (unlikely(!bridge_flow)) {
 			rcu_read_unlock();
 			DEBUG_TRACE("%px: CME doesn't contain PPPoE flag but packet has PPPoE header\n", skb);
 			sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_PPPOE_NOT_SET_IN_CME);
 			return 0;
-
 		}
 
 		/*
 		 * For bridged flows when packet contains PPPoE header, restore the header back and forward to xmit interface
 		 */
-		__skb_push(skb, (sizeof(struct pppoe_hdr) + sizeof(struct sfe_ppp_hdr)));
+		__skb_push(skb, PPPOE_SES_HLEN);
 		this_cpu_inc(si->stats_pcpu->pppoe_bridge_packets_forwarded64);
 	}
 
 	/*
-	 * Check if skb has enough headroom to write L2 headers
-	 */
-	if (unlikely(skb_headroom(skb) < cm->l2_hdr_size)) {
-		rcu_read_unlock();
-		DEBUG_WARN("%px: Not enough headroom: %u\n", skb, skb_headroom(skb));
-		sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_NO_HEADROOM);
-		return 0;
-	}
-
-	/*
 	 * From this point on we're good to modify the packet.
 	 */
 
diff --git a/sfe_pppoe.c b/sfe_pppoe.c
index 770a77d..0944a87 100644
--- a/sfe_pppoe.c
+++ b/sfe_pppoe.c
@@ -28,6 +28,38 @@
 #include "sfe_pppoe.h"
 
 /*
+ * sfe_pppoe_br_accel_mode controls how to accelerate PPPoE bridge flow.
+ * - SFE_PPPOE_BR_ACCEL_MODE_EN_5T: 5-tuple (src_ip, dest_ip, src_port, dest_port, protocol) acceleration
+ * - SFE_PPPOE_BR_ACCEL_MODE_EN_3T: 3-tuple (src_ip, dest_ip, PPPoE session id) acceleration
+ * - SFE_PPPOE_BR_ACCEL_MODE_DISABLED: No acceleration
+ */
+static sfe_pppoe_br_accel_mode_t sfe_pppoe_br_accel_mode __read_mostly = SFE_PPPOE_BR_ACCEL_MODE_EN_5T;
+
+/*
+ * sfe_pppoe_get_br_accel_mode()
+ *	Gets PPPoE bridge acceleration mode
+ */
+sfe_pppoe_br_accel_mode_t sfe_pppoe_get_br_accel_mode(void)
+{
+	return sfe_pppoe_br_accel_mode;
+}
+EXPORT_SYMBOL(sfe_pppoe_get_br_accel_mode);
+
+/*
+ * sfe_pppoe_set_br_accel_mode()
+ *	Sets PPPoE bridge acceleration mode
+ */
+int sfe_pppoe_set_br_accel_mode(sfe_pppoe_br_accel_mode_t mode)
+{
+	if (mode >= SFE_PPPOE_BR_ACCEL_MODE_MAX) {
+		return -1;
+	}
+
+	sfe_pppoe_br_accel_mode = mode;
+	return 0;
+}
+
+/*
  * sfe_pppoe_add_header()
  *	Add PPPoE header.
  *
diff --git a/sfe_pppoe.h b/sfe_pppoe.h
index cb0af1e..8329e30 100644
--- a/sfe_pppoe.h
+++ b/sfe_pppoe.h
@@ -27,13 +27,9 @@
 	u16 protocol;
 };
 
-/*
- * PPPoE (6-byte) + PPP (2-byte) header.
- */
-#define SFE_PPPOE_SESSION_HEADER_SIZE 8
-
 void sfe_pppoe_add_header(struct sk_buff *skb, u16 pppoe_session_id, u16 ppp_protocol);
 bool sfe_pppoe_parse_hdr(struct sk_buff *skb, struct sfe_l2_info *l2_info);
 void sfe_pppoe_undo_parse(struct sk_buff *skb, struct sfe_l2_info *l2_info);
+int sfe_pppoe_set_br_accel_mode(sfe_pppoe_br_accel_mode_t mode);
 
 #endif /* __SFE_PPPOE_H */
diff --git a/sfe_pppoe_mgr.c b/sfe_pppoe_mgr.c
new file mode 100644
index 0000000..6422966
--- /dev/null
+++ b/sfe_pppoe_mgr.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/rwlock_types.h>
+#include <linux/hashtable.h>
+#include <linux/inetdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/if_pppox.h>
+#include "sfe_pppoe_mgr.h"
+#include "sfe_debug.h"
+
+#define HASH_BUCKET_SIZE 2  /* ( 2^ HASH_BUCKET_SIZE ) == 4 */
+
+static DEFINE_HASHTABLE(pppoe_session_table, HASH_BUCKET_SIZE);
+
+/*
+ * sfe_pppoe_mgr_get_session_info()
+ *	Retrieve PPPoE session info associated with this netdevice
+ */
+static bool sfe_pppoe_mgr_get_session_info(struct net_device *dev, struct pppoe_opt *addressing)
+{
+	struct ppp_channel *channel[1] = {NULL};
+	int px_proto;
+	int ppp_ch_count;
+
+	if (ppp_is_multilink(dev)) {
+		DEBUG_WARN("%s: channel is multilink PPP\n", dev->name);
+		return false;
+	}
+
+	ppp_ch_count = ppp_hold_channels(dev, channel, 1);
+	DEBUG_INFO("%s: PPP hold channel ret %d\n", dev->name, ppp_ch_count);
+	if (ppp_ch_count != 1) {
+		DEBUG_WARN("%s: hold channel for netdevice %px failed\n", dev->name, dev);
+		return false;
+	}
+
+	px_proto = ppp_channel_get_protocol(channel[0]);
+	if (px_proto != PX_PROTO_OE) {
+		DEBUG_WARN("%s: session socket is not of type PX_PROTO_OE\n", dev->name);
+		ppp_release_channels(channel, 1);
+		return false;
+	}
+
+	if (pppoe_channel_addressing_get(channel[0], addressing)) {
+		DEBUG_WARN("%s: failed to get addressing information\n", dev->name);
+		ppp_release_channels(channel, 1);
+		return false;
+	}
+
+	DEBUG_TRACE("dev=%px %s %d: opt_dev=%px opt_dev_name=%s opt_dev_ifindex=%d opt_ifindex=%d\n",
+			dev, dev->name, dev->ifindex,
+			addressing->dev, addressing->dev->name, addressing->dev->ifindex, addressing->ifindex);
+
+	/*
+	 * pppoe_channel_addressing_get returns held device.
+	 * So, put it back here.
+	 */
+	dev_put(addressing->dev);
+	ppp_release_channels(channel, 1);
+	return true;
+}
+
+/*
+ * sfe_pppoe_mgr_remove_session()
+ *	Remove PPPoE session entry from hash table
+ */
+static void sfe_pppoe_mgr_remove_session(struct sfe_pppoe_mgr_session_entry *entry)
+{
+	struct sfe_pppoe_mgr_session_info *info;
+	info = &entry->info;
+
+	DEBUG_INFO("%px %s %d: Remove PPPoE session entry with session_id=%u server_mac=%pM\n",
+			       entry, entry->dev->name, entry->dev->ifindex,
+			       info->session_id, info->server_mac);
+
+	hash_del_rcu(&entry->hash_list);
+	synchronize_rcu();
+	kfree(entry);
+}
+
+/*
+ * sfe_pppoe_mgr_add_session()
+ *	Create a PPPoE session entry and add it into hash table
+ */
+static struct sfe_pppoe_mgr_session_entry *sfe_pppoe_mgr_add_session(struct net_device *dev, struct pppoe_opt *opt)
+
+{
+	struct sfe_pppoe_mgr_session_entry *entry;
+	struct sfe_pppoe_mgr_session_info *info;
+
+	entry = kzalloc(sizeof(struct sfe_pppoe_mgr_session_entry), GFP_KERNEL);
+	if (!entry) {
+		DEBUG_WARN("%px: failed to allocate pppoe session entry\n", dev);
+		return NULL;
+	}
+
+	info = &entry->info;
+
+	/*
+	 * Save session info
+	 */
+	info->session_id = (uint16_t)ntohs((uint16_t)opt->pa.sid);
+	ether_addr_copy(info->server_mac, opt->pa.remote);
+
+	entry->dev = dev;
+
+	/*
+	 * There is no need for protecting simultaneous addition &
+	 * deletion of pppoe sesion entry as the PPP notifier chain
+	 * call back is called with mutex lock.
+	 */
+	hash_add_rcu(pppoe_session_table,
+		&entry->hash_list,
+		dev->ifindex);
+
+	DEBUG_INFO("%px %s %d: Add PPPoE session entry with session_id=%u server_mac=%pM\n",
+			       entry, dev->name, dev->ifindex,
+			       info->session_id, info->server_mac);
+
+	return entry;
+}
+
+/*
+ * sfe_pppoe_mgr_disconnect()
+ *	PPPoE interface's disconnect event handler
+ */
+static int sfe_pppoe_mgr_disconnect(struct net_device *dev)
+{
+	struct sfe_pppoe_mgr_session_entry *entry;
+	struct sfe_pppoe_mgr_session_entry *found = NULL;
+	struct hlist_node *temp;
+	/*
+	 * check whether the interface is of type PPP
+	 */
+	if (dev->type != ARPHRD_PPP || !(dev->flags & IFF_POINTOPOINT)) {
+		return NOTIFY_DONE;
+	}
+
+	hash_for_each_possible_safe(pppoe_session_table, entry,
+				     temp, hash_list, dev->ifindex) {
+		if (entry->dev != dev) {
+			continue;
+		}
+
+		/*
+		 * In the hash list, there must be only one entry match with this net device.
+		 */
+		found = entry;
+		break;
+	}
+
+	if (!found) {
+		DEBUG_WARN("%px: PPPoE session is not found for %s\n", dev, dev->name);
+		return NOTIFY_DONE;
+	}
+
+	/*
+	 * Remove entry from hash table
+	 */
+	sfe_pppoe_mgr_remove_session(found);
+
+	return NOTIFY_DONE;
+}
+
+/*
+ * sfe_pppoe_mgr_connect()
+ *	PPPoE interface's connect event handler
+ */
+static int sfe_pppoe_mgr_connect(struct net_device *dev)
+{
+	struct pppoe_opt opt;
+	struct sfe_pppoe_mgr_session_entry *entry;
+
+	/*
+	 * check whether the interface is of type PPP
+	 */
+	if (dev->type != ARPHRD_PPP || !(dev->flags & IFF_POINTOPOINT)) {
+		return NOTIFY_DONE;
+	}
+
+	if (sfe_pppoe_mgr_get_session_info(dev, &opt) == false) {
+		DEBUG_WARN("%px: Unable to get pppoe session info from %s\n", dev, dev->name);
+		return NOTIFY_DONE;
+	}
+
+	/*
+	 * Create an session entry and add it to hash table
+	 */
+	entry = sfe_pppoe_mgr_add_session(dev, &opt);
+	if (!entry) {
+		DEBUG_WARN("%s: PPPoE session add failed\n", dev->name);
+	}
+
+	return NOTIFY_DONE;
+}
+
+/*
+ * sfe_pppoe_mgr_channel_notifier_handler()
+ *	PPPoE channel notifier handler.
+ */
+static int sfe_pppoe_mgr_channel_notifier_handler(struct notifier_block *nb,
+							unsigned long event,
+							void *arg)
+{
+	struct net_device *dev = (struct net_device *)arg;
+
+	switch (event) {
+	case PPP_CHANNEL_CONNECT:
+		DEBUG_INFO("%s: PPP_CHANNEL_CONNECT event\n", dev->name);
+		return sfe_pppoe_mgr_connect(dev);
+
+	case PPP_CHANNEL_DISCONNECT:
+		DEBUG_INFO("%s: PPP_CHANNEL_DISCONNECT event\n", dev->name);
+		return sfe_pppoe_mgr_disconnect(dev);
+
+	default:
+		DEBUG_INFO("%s: Unhandled channel event: %lu\n", dev->name, event);
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+struct notifier_block sfe_pppoe_mgr_channel_notifier_nb = {
+	.notifier_call = sfe_pppoe_mgr_channel_notifier_handler,
+};
+
+/*
+ * sfe_pppoe_mgr_find_session()
+ *	Find pppoe session entry given session ID and server MAC
+ */
+bool sfe_pppoe_mgr_find_session(uint16_t session_id, uint8_t *server_mac)
+{
+	struct sfe_pppoe_mgr_session_entry *entry;
+	struct sfe_pppoe_mgr_session_info *info;
+	struct hlist_node *temp;
+	int bkt;
+
+	hash_for_each_safe(pppoe_session_table, bkt, temp, entry, hash_list) {
+		info = &entry->info;
+		if ((uint16_t)info->session_id == session_id &&
+		    ether_addr_equal(info->server_mac, server_mac)) {
+
+			return true;
+		}
+	}
+
+	DEBUG_INFO("PPPoE session entry not found: session_id %d server_mac %pM\n", session_id, server_mac);
+
+	return false;
+}
+
+/*
+ * sfe_pppoe_mgr_exit
+ *     PPPoE mgr exit function
+ */
+void sfe_pppoe_mgr_exit(void)
+{
+	struct sfe_pppoe_mgr_session_entry *entry;
+	struct hlist_node *temp;
+	int bkt;
+
+	/*
+	 * Unregister the module from the PPP channel events.
+	 */
+	ppp_channel_connection_unregister_notify(&sfe_pppoe_mgr_channel_notifier_nb);
+
+	hash_for_each_safe(pppoe_session_table, bkt, temp, entry, hash_list) {
+		sfe_pppoe_mgr_remove_session(entry);
+	}
+}
+
+/*
+ * sfe_pppoe_mgr_init()
+ *	PPPoE mgr init function
+ */
+int sfe_pppoe_mgr_init(void)
+{
+	/*
+	 * Register the module to the PPP channel events.
+	 */
+	ppp_channel_connection_register_notify(&sfe_pppoe_mgr_channel_notifier_nb);
+	return 0;
+}
diff --git a/sfe_pppoe_mgr.h b/sfe_pppoe_mgr.h
new file mode 100644
index 0000000..f909660
--- /dev/null
+++ b/sfe_pppoe_mgr.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * sfe_pppoe_mgr.h
+ *	SFE PPPoE mgr definitions
+ */
+
+#ifndef _SFE_PPPOE_MGR_H_
+#define _SFE_PPPOE_MGR_H_
+
+/*
+ * struct sfe_pppoe_mgr_session_info
+ *	Structure for PPPoE client driver session info
+ */
+struct sfe_pppoe_mgr_session_info {
+	uint32_t session_id;		/* PPPoE Session ID */
+	uint8_t server_mac[ETH_ALEN];	/* PPPoE server's MAC address */
+};
+
+/*
+ * struct sfe_pppoe_mgr_session_entry
+ *	Structure for PPPoE session entry into HASH table
+ */
+struct sfe_pppoe_mgr_session_entry {
+	struct sfe_pppoe_mgr_session_info info;
+					/* Session information */
+	struct net_device *dev;		/* Net device */
+	struct hlist_node hash_list;	/* Hash list for sessions */
+};
+
+bool sfe_pppoe_mgr_find_session(uint16_t session_id, uint8_t *server_mac);
+int sfe_pppoe_mgr_init(void);
+void sfe_pppoe_mgr_exit(void);
+
+#endif