Improvement in fabric.p4 and bng.p4

- fabric.p4 now supports double tagged hosts
- bng.p4 now only manages PPPoE termination
- bng_ingress moved at the end of the fabric pipeline

Change-Id: Iff62238fde9ec6ddf7311312a98c041e3ab3aa8d
diff --git a/pipelines/fabric/src/main/resources/include/bng.p4 b/pipelines/fabric/src/main/resources/include/bng.p4
index 11e4639..ede0001 100644
--- a/pipelines/fabric/src/main/resources/include/bng.p4
+++ b/pipelines/fabric/src/main/resources/include/bng.p4
@@ -26,10 +26,6 @@
 #ifndef __BNG__
 #define __BNG__
 
-#define BNG_MAX_SUBSC 8192
-#define BNG_MAX_NET_PER_SUBSC 4
-#define BNG_MAX_SUBSC_NET BNG_MAX_NET_PER_SUBSC * BNG_MAX_SUBSC
-
 #define BNG_SUBSC_IPV6_NET_PREFIX_LEN 64
 
 control bng_ingress_upstream(
@@ -41,38 +37,11 @@
     counter(BNG_MAX_SUBSC, CounterType.packets) c_dropped;
     counter(BNG_MAX_SUBSC, CounterType.packets) c_control;
 
-    vlan_id_t s_tag = hdr.vlan_tag.vlan_id;
-    vlan_id_t c_tag = hdr.inner_vlan_tag.vlan_id;
-
-    _BOOL drop = _FALSE;
-    // TABLE: t_line_map
-    // Maps double VLAN tags to line ID. Line IDs are used to uniquelly identify
-    // a subscriber.
-
-    action set_line(bit<32> line_id) {
-        fmeta.bng.line_id = line_id;
-    }
-
-    table t_line_map {
-        actions = {
-            @defaultonly nop;
-            set_line;
-        }
-        key = {
-            s_tag: exact @name("s_tag");
-            c_tag: exact @name("c_tag");
-        }
-        size = BNG_MAX_SUBSC;
-        const default_action = nop;
-    }
-
     // TABLE: t_pppoe_cp
     // Punt to CPU for PPPeE control packets.
 
     action punt_to_cpu() {
         smeta.egress_spec = CPU_PORT;
-        fmeta.skip_forwarding = _TRUE;
-        fmeta.skip_next = _TRUE;
         c_control.count(fmeta.bng.line_id);
     }
 
@@ -95,26 +64,24 @@
 
     @hidden
     action term_enabled(bit<16> eth_type) {
-        hdr.ethernet.eth_type = eth_type;
-        fmeta.eth_type = eth_type;
+        hdr.inner_vlan_tag.eth_type = eth_type;
+        fmeta.last_eth_type = eth_type;
         hdr.pppoe.setInvalid();
-        hdr.vlan_tag.setInvalid();
-        hdr.inner_vlan_tag.setInvalid();
         c_terminated.count(fmeta.bng.line_id);
     }
 
     action term_disabled() {
         fmeta.bng.type = BNG_TYPE_INVALID;
-        fmeta.skip_forwarding = _TRUE;
-        fmeta.skip_next = _TRUE;
         mark_to_drop(smeta);
-        drop = _TRUE;
     }
 
     action term_enabled_v4() {
         term_enabled(ETHERTYPE_IPV4);
     }
 
+    // TODO: add match on hdr.ethernet.src_addr for antispoofing
+    // Take into account that MAC src address is modified by the Next control block
+    // when doing routing functionality.
     table t_pppoe_term_v4 {
         key = {
             fmeta.bng.line_id    : exact @name("line_id");
@@ -134,6 +101,9 @@
         term_enabled(ETHERTYPE_IPV6);
     }
 
+    // TODO: add match on hdr.ethernet.src_addr for antispoofing
+    // Match on unmodified metadata field, taking into account that MAC src address
+    // is modified by the Next control block when doing routing functionality.
     table t_pppoe_term_v6 {
         key = {
             fmeta.bng.line_id         : exact @name("line_id");
@@ -150,25 +120,23 @@
 #endif // WITH_IPV6
 
     apply {
-        // If table miss, line_id will be 0 (default metadata value).
-        t_line_map.apply();
-
-        if (t_pppoe_cp.apply().hit) {
+        if(t_pppoe_cp.apply().hit) {
             return;
         }
-
         if (hdr.ipv4.isValid()) {
-            t_pppoe_term_v4.apply();
-            if (drop == _TRUE) {
-                c_dropped.count(fmeta.bng.line_id);
+            switch(t_pppoe_term_v4.apply().action_run) {
+                term_disabled: {
+                    c_dropped.count(fmeta.bng.line_id);
+                }
             }
         }
 #ifdef WITH_IPV6
         else if (hdr.ipv6.isValid()) {
-            t_pppoe_term_v6.apply();
-            if (drop == _TRUE) {
-                c_dropped.count(fmeta.bng.line_id);
-             }
+            switch(t_pppoe_term_v6.apply().action_run) {
+               term_disabled: {
+                   c_dropped.count(fmeta.bng.line_id);
+               }
+           }
         }
 #endif // WITH_IPV6
     }
@@ -184,65 +152,37 @@
     meter(BNG_MAX_SUBSC, MeterType.bytes) m_besteff;
     meter(BNG_MAX_SUBSC, MeterType.bytes) m_prio;
 
-    // Downstream line map tables.
-    // Map IP dest address to line ID and next ID. Setting a next ID here
-    // allows to skip the fabric.p4 forwarding stage later.
-    _BOOL prio = _FALSE;
-
-    @hidden
-    action set_line(bit<32> line_id) {
+    action set_session(bit<16> pppoe_session_id) {
         fmeta.bng.type = BNG_TYPE_DOWNSTREAM;
-        fmeta.bng.line_id = line_id;
-        c_line_rx.count(line_id);
+        fmeta.bng.pppoe_session_id = pppoe_session_id;
+        c_line_rx.count(fmeta.bng.line_id);
     }
 
-    action set_line_next(bit<32> line_id, next_id_t next_id) {
-        set_line(line_id);
-        fmeta.next_id = next_id;
-        fmeta.skip_forwarding = _TRUE;
-    }
-
-    action set_line_drop(bit<32> line_id) {
-        set_line(line_id);
-        fmeta.skip_forwarding = _TRUE;
-        fmeta.skip_next = _TRUE;
+    action drop() {
+        fmeta.bng.type = BNG_TYPE_DOWNSTREAM;
+        c_line_rx.count(fmeta.bng.line_id);
         mark_to_drop(smeta);
     }
 
-    table t_line_map_v4 {
+    table t_line_session_map {
         key = {
-            hdr.ipv4.dst_addr: exact @name("ipv4_dst");
+            fmeta.bng.line_id : exact @name("line_id");
         }
         actions = {
             @defaultonly nop;
-            set_line_next;
-            set_line_drop;
+            set_session;
+            drop;
         }
-        size = BNG_MAX_SUBSC_NET;
+        size = BNG_MAX_SUBSC;
         const default_action = nop;
     }
 
-#ifdef WITH_IPV6
-    table t_line_map_v6 {
-        key = {
-            hdr.ipv6.dst_addr[127:64]: exact @name("ipv6_dst_net_id");
-        }
-        actions = {
-            @defaultonly nop;
-            set_line_next;
-            set_line_drop;
-        }
-        size = BNG_MAX_SUBSC_NET;
-        const default_action = nop;
-    }
-#endif // WITH_IPV6
-
     // Downstream QoS tables.
     // Provide coarse metering before prioritazion in the OLT. By default
     // everything is tagged and metered as best-effort traffic.
 
     action qos_prio() {
-        prio = _TRUE;
+        // no-op
     }
 
     action qos_besteff() {
@@ -281,38 +221,39 @@
 #endif // WITH_IPV6
 
     apply {
+        // We are not sure the pkt is a BNG downstream one, first we need to
+        // verify the line_id matches the one of a subscriber...
+
         // IPv4
-        if (hdr.ipv4.isValid()) {
-            if (t_line_map_v4.apply().hit) {
-                // Apply QoS only to subscriber traffic. This makes sense only
-                // if the downstream ports are used to receive IP traffic NOT
-                // destined to subscribers, e.g. to services in the compute
-                // nodes.
-                t_qos_v4.apply();
-                if (prio == _TRUE) {
-                    m_prio.execute_meter((bit<32>)fmeta.bng.line_id,
-                                          fmeta.bng.ds_meter_result);
-                } else {
-                    m_besteff.execute_meter((bit<32>)fmeta.bng.line_id,
-                                            fmeta.bng.ds_meter_result);
+        if (t_line_session_map.apply().hit) {
+            // Apply QoS only to subscriber traffic. This makes sense only
+            // if the downstream ports are used to receive IP traffic NOT
+            // destined to subscribers, e.g. to services in the compute
+            // nodes.
+            if (hdr.ipv4.isValid()) {
+                switch (t_qos_v4.apply().action_run) {
+                    qos_prio: {
+                        m_prio.execute_meter(fmeta.bng.line_id, fmeta.bng.ds_meter_result);
+                    }
+                    qos_besteff: {
+                        m_besteff.execute_meter(fmeta.bng.line_id, fmeta.bng.ds_meter_result);
+                    }
                 }
             }
-        }
 #ifdef WITH_IPV6
-        // IPv6
-        else if (hdr.ipv6.isValid()) {
-            if (t_line_map_v6.apply().hit) {
-                t_qos_v6.apply();
-                if (prio == _TRUE) {
-                    m_prio.execute_meter((bit<32>)fmeta.bng.line_id,
-                                          fmeta.bng.ds_meter_result);
-                } else {
-                    m_besteff.execute_meter((bit<32>)fmeta.bng.line_id,
-                                            fmeta.bng.ds_meter_result);
+            // IPv6
+            else if (hdr.ipv6.isValid()) {
+                switch (t_qos_v6.apply().action_run) {
+                    qos_prio: {
+                        m_prio.execute_meter(fmeta.bng.line_id, fmeta.bng.ds_meter_result);
+                    }
+                    qos_besteff: {
+                        m_besteff.execute_meter(fmeta.bng.line_id, fmeta.bng.ds_meter_result);
+                    }
                 }
             }
-        }
 #endif // WITH_IPV6
+        }
     }
 }
 
@@ -324,52 +265,41 @@
     counter(BNG_MAX_SUBSC, CounterType.packets_and_bytes) c_line_tx;
 
     @hidden
-    action encap(vlan_id_t c_tag, bit<16> pppoe_session_id) {
-        // s_tag (outer VLAN) should be already set via the next_vlan table.
-        // Here we add c_tag (inner VLAN) and PPPoE.
-        hdr.vlan_tag.eth_type = ETHERTYPE_VLAN;
-        hdr.inner_vlan_tag.setValid();
-        hdr.inner_vlan_tag.vlan_id = c_tag;
+    action encap() {
+        // Here we add PPPoE and modify the inner_vlan_tag Ethernet Type.
         hdr.inner_vlan_tag.eth_type = ETHERTYPE_PPPOES;
         hdr.pppoe.setValid();
         hdr.pppoe.version = 4w1;
         hdr.pppoe.type_id = 4w1;
         hdr.pppoe.code = 8w0; // 0 means session stage.
-        hdr.pppoe.session_id = pppoe_session_id;
+        hdr.pppoe.session_id = fmeta.bng.pppoe_session_id;
         c_line_tx.count(fmeta.bng.line_id);
     }
 
-    action encap_v4(vlan_id_t c_tag, bit<16> pppoe_session_id) {
-        encap(c_tag, pppoe_session_id);
+    action encap_v4() {
+        encap();
         hdr.pppoe.length = hdr.ipv4.total_len + 16w2;
         hdr.pppoe.protocol = PPPOE_PROTOCOL_IP4;
     }
 
 #ifdef WITH_IPV6
-    action encap_v6(vlan_id_t c_tag, bit<16> pppoe_session_id) {
-        encap(c_tag, pppoe_session_id);
+    action encap_v6() {
+        encap();
         hdr.pppoe.length = hdr.ipv6.payload_len + 16w42;
         hdr.pppoe.protocol = PPPOE_PROTOCOL_IP6;
     }
 #endif // WITH_IPV6
 
-    table t_session_encap {
-        key = {
-            fmeta.bng.line_id : exact @name("line_id");
-        }
-        actions = {
-            @defaultonly nop;
-            encap_v4;
-#ifdef WITH_IPV6
-            encap_v6;
-#endif // WITH_IPV6
-        }
-        size = BNG_MAX_SUBSC;
-        const default_action = nop();
-    }
-
     apply {
-        t_session_encap.apply();
+        if (hdr.ipv4.isValid()) {
+            encap_v4();
+        }
+#ifdef WITH_IPV6
+        // IPv6
+        else if (hdr.ipv6.isValid()) {
+            encap_v6();
+        }
+#endif // WITH_IPV6
     }
 }
 
@@ -378,20 +308,54 @@
         inout fabric_metadata_t fmeta,
         inout standard_metadata_t smeta) {
 
-    bng_ingress_upstream() upstream;
-    bng_ingress_downstream() downstream;
+        bng_ingress_upstream() upstream;
+        bng_ingress_downstream() downstream;
 
-    apply {
-        if (hdr.pppoe.isValid()) {
-            fmeta.bng.type = BNG_TYPE_UPSTREAM;
-            upstream.apply(hdr, fmeta, smeta);
+        vlan_id_t s_tag = 0;
+        vlan_id_t c_tag = 0;
+
+        // TABLE: t_line_map
+        // Map s_tag and c_tag to a line ID to uniquely identify a subscriber
+
+        action set_line(bit<32> line_id) {
+            fmeta.bng.line_id = line_id;
         }
-        else {
-            // We are not sure the pkt is a BNG downstream one, first we need to
-            // verify the IP dst matches the IP addr of a subscriber...
-            downstream.apply(hdr, fmeta, smeta);
+
+        table t_line_map {
+            key = {
+                s_tag : exact @name("s_tag");
+                c_tag : exact @name("c_tag");
+            }
+             actions = {
+                @defaultonly nop;
+                set_line;
+            }
+            size = BNG_MAX_SUBSC;
+            const default_action = nop;
         }
-    }
+
+        apply {
+            if(hdr.pppoe.isValid()) {
+                s_tag = hdr.vlan_tag.vlan_id;
+                c_tag = hdr.inner_vlan_tag.vlan_id;
+            } else {
+                // We expect the packet to be downstream,
+                // the tags are set by the next stage in the metadata.
+                s_tag = fmeta.vlan_id;
+                c_tag = fmeta.inner_vlan_id;
+            }
+
+            // First map the double VLAN tags to a line ID
+            // If table miss line ID will be 0.
+            t_line_map.apply();
+
+            if (hdr.pppoe.isValid()) {
+                fmeta.bng.type = BNG_TYPE_UPSTREAM;
+                upstream.apply(hdr, fmeta, smeta);
+            } else {
+                downstream.apply(hdr, fmeta, smeta);
+            }
+        }
 }
 
 control bng_egress(
diff --git a/pipelines/fabric/src/main/resources/include/control/acl.p4 b/pipelines/fabric/src/main/resources/include/control/acl.p4
index bf70f15..4c2df67 100644
--- a/pipelines/fabric/src/main/resources/include/control/acl.p4
+++ b/pipelines/fabric/src/main/resources/include/control/acl.p4
@@ -66,7 +66,7 @@
             hdr.ethernet.dst_addr: ternary @name("eth_src"); // 48
             hdr.ethernet.src_addr: ternary @name("eth_dst"); // 48
             hdr.vlan_tag.vlan_id: ternary @name("vlan_id"); // 12
-            fabric_metadata.eth_type: ternary @name("eth_type"); //16
+            fabric_metadata.last_eth_type: ternary @name("eth_type"); //16
             hdr.ipv4.src_addr: ternary @name("ipv4_src"); // 32
             hdr.ipv4.dst_addr: ternary @name("ipv4_dst"); // 32
             hdr.icmp.icmp_type: ternary @name("icmp_type"); // 8
diff --git a/pipelines/fabric/src/main/resources/include/control/filtering.p4 b/pipelines/fabric/src/main/resources/include/control/filtering.p4
index cc8e313..a3213e9 100644
--- a/pipelines/fabric/src/main/resources/include/control/filtering.p4
+++ b/pipelines/fabric/src/main/resources/include/control/filtering.p4
@@ -45,14 +45,17 @@
 
     action permit_with_internal_vlan(vlan_id_t vlan_id) {
         fabric_metadata.vlan_id = vlan_id;
-        ingress_port_vlan_counter.count();
+        permit();
     }
 
+    // FIXME: remove the use of ternary match on valid inner VLAN.
+    // Use multi-table approach to remove ternary matching
     table ingress_port_vlan {
         key = {
-            standard_metadata.ingress_port: exact @name("ig_port");
-            hdr.vlan_tag.isValid(): exact @name("vlan_is_valid");
-            hdr.vlan_tag.vlan_id: ternary @name("vlan_id");
+            standard_metadata.ingress_port : exact @name("ig_port");
+            hdr.vlan_tag.isValid()         : exact @name("vlan_is_valid");
+            hdr.vlan_tag.vlan_id           : ternary @name("vlan_id");
+            hdr.inner_vlan_tag.vlan_id     : ternary @name("inner_vlan_id");
         }
         actions = {
             deny();
@@ -86,9 +89,11 @@
 
     table fwd_classifier {
         key = {
-            standard_metadata.ingress_port: exact @name("ig_port");
-            hdr.ethernet.dst_addr: ternary @name("eth_dst");
-            fabric_metadata.eth_type: exact @name("eth_type");
+            standard_metadata.ingress_port : exact @name("ig_port");
+            hdr.ethernet.dst_addr          : ternary @name("eth_dst");
+            fabric_metadata.is_ipv4        : exact @name("is_ipv4");
+            fabric_metadata.is_ipv6        : exact @name("is_ipv6");
+            fabric_metadata.is_mpls        : exact @name("is_mpls");
         }
         actions = {
             set_forwarding_type;
@@ -102,11 +107,17 @@
         // Initialize lookup metadata. Packets without a VLAN header will be
         // treated as belonging to a default VLAN ID (see parser).
         if (hdr.vlan_tag.isValid()) {
-            fabric_metadata.eth_type = hdr.vlan_tag.eth_type;
             fabric_metadata.vlan_id = hdr.vlan_tag.vlan_id;
             fabric_metadata.vlan_pri = hdr.vlan_tag.pri;
             fabric_metadata.vlan_cfi = hdr.vlan_tag.cfi;
         }
+        #ifdef WITH_DOUBLE_VLAN_TERMINATION
+        if (hdr.inner_vlan_tag.isValid()) {
+            fabric_metadata.inner_vlan_id = hdr.inner_vlan_tag.vlan_id;
+            fabric_metadata.inner_vlan_pri = hdr.inner_vlan_tag.pri;
+            fabric_metadata.inner_vlan_cfi = hdr.inner_vlan_tag.cfi;
+        }
+        #endif // WITH_DOUBLE_VLAN_TERMINATION
         if (!hdr.mpls.isValid()) {
             // Packets with a valid MPLS header will have
             // fabric_metadata.mpls_ttl set to the packet's MPLS ttl value (see
@@ -115,6 +126,22 @@
             fabric_metadata.mpls_ttl = DEFAULT_MPLS_TTL + 1;
         }
 
+        // Set last_eth_type checking the validity of the L2.5 headers
+        if (hdr.mpls.isValid()) {
+            fabric_metadata.last_eth_type = ETHERTYPE_MPLS;
+        } else {
+            if (hdr.vlan_tag.isValid()) {
+#if defined(WITH_XCONNECT) || defined(WITH_BNG) || defined(WITH_DOUBLE_VLAN_TERMINATION)
+                if(hdr.inner_vlan_tag.isValid()) {
+                    fabric_metadata.last_eth_type = hdr.inner_vlan_tag.eth_type;
+                } else
+#endif //  WITH_XCONNECT || WITH_BNG || WITH_DOUBLE_VLAN_TERMINATION
+                    fabric_metadata.last_eth_type = hdr.vlan_tag.eth_type;
+            } else {
+                fabric_metadata.last_eth_type = hdr.ethernet.eth_type;
+            }
+        }
+
         ingress_port_vlan.apply();
         fwd_classifier.apply();
     }
diff --git a/pipelines/fabric/src/main/resources/include/control/next.p4 b/pipelines/fabric/src/main/resources/include/control/next.p4
index 82b61eb..9868339 100644
--- a/pipelines/fabric/src/main/resources/include/control/next.p4
+++ b/pipelines/fabric/src/main/resources/include/control/next.p4
@@ -71,12 +71,23 @@
         next_vlan_counter.count();
     }
 
+#ifdef WITH_DOUBLE_VLAN_TERMINATION
+    action set_double_vlan(vlan_id_t outer_vlan_id, vlan_id_t inner_vlan_id) {
+        set_vlan(outer_vlan_id);
+        fabric_metadata.push_double_vlan = _TRUE;
+        fabric_metadata.inner_vlan_id = inner_vlan_id;
+    }
+#endif // WITH_DOUBLE_VLAN_TERMINATION
+
     table next_vlan {
         key = {
             fabric_metadata.next_id: exact @name("next_id");
         }
         actions = {
             set_vlan;
+#ifdef WITH_DOUBLE_VLAN_TERMINATION
+            set_double_vlan;
+#endif // WITH_DOUBLE_VLAN_TERMINATION
             @defaultonly nop;
         }
         const default_action = nop();
@@ -93,6 +104,7 @@
 
     action output_xconnect(port_num_t port_num) {
         output(port_num);
+        fabric_metadata.last_eth_type = ETHERTYPE_VLAN;
         xconnect_counter.count();
     }
 
@@ -251,7 +263,7 @@
     action pop_mpls_if_present() {
         hdr.mpls.setInvalid();
         // Assuming there's an IP header after the MPLS one.
-        fabric_metadata.eth_type = fabric_metadata.ip_eth_type;
+        fabric_metadata.last_eth_type = fabric_metadata.ip_eth_type;
     }
 
     @hidden
@@ -261,7 +273,7 @@
         hdr.mpls.tc = 3w0;
         hdr.mpls.bos = 1w1; // BOS = TRUE
         hdr.mpls.ttl = fabric_metadata.mpls_ttl; // Decrement after push.
-        fabric_metadata.eth_type = ETHERTYPE_MPLS;
+        fabric_metadata.last_eth_type = ETHERTYPE_MPLS;
     }
 
     @hidden
@@ -271,11 +283,25 @@
         hdr.vlan_tag.setValid();
         hdr.vlan_tag.cfi = fabric_metadata.vlan_cfi;
         hdr.vlan_tag.pri = fabric_metadata.vlan_pri;
-        hdr.vlan_tag.eth_type = fabric_metadata.eth_type;
+        hdr.vlan_tag.eth_type = fabric_metadata.last_eth_type;
         hdr.vlan_tag.vlan_id = fabric_metadata.vlan_id;
         hdr.ethernet.eth_type = ETHERTYPE_VLAN;
     }
 
+#ifdef WITH_DOUBLE_VLAN_TERMINATION
+    @hidden
+    action push_inner_vlan() {
+        // Then push inner VLAN TAG, rewriting correclty the outer vlan eth_type
+        // and the ethernet eth_type
+        hdr.inner_vlan_tag.setValid();
+        hdr.inner_vlan_tag.cfi = fabric_metadata.inner_vlan_cfi;
+        hdr.inner_vlan_tag.pri = fabric_metadata.inner_vlan_pri;
+        hdr.inner_vlan_tag.vlan_id = fabric_metadata.inner_vlan_id;
+        hdr.inner_vlan_tag.eth_type = fabric_metadata.last_eth_type;
+        hdr.vlan_tag.eth_type = ETHERTYPE_VLAN;
+    }
+#endif // WITH_DOUBLE_VLAN_TERMINATION
+
     /*
      * Egress VLAN Table.
      * Pops the VLAN tag if the pair egress port and VLAN ID is matched.
@@ -283,7 +309,7 @@
     direct_counter(CounterType.packets_and_bytes) egress_vlan_counter;
 
     action pop_vlan() {
-        hdr.ethernet.eth_type = fabric_metadata.eth_type;
+        hdr.ethernet.eth_type = fabric_metadata.last_eth_type;
         hdr.vlan_tag.setInvalid();
         egress_vlan_counter.count();
     }
@@ -314,12 +340,26 @@
             set_mpls();
         }
 
-        if (!egress_vlan.apply().hit) {
-            // Push VLAN tag if not the default one.
-            if (fabric_metadata.vlan_id != DEFAULT_VLAN_ID) {
-                push_vlan();
+#ifdef WITH_DOUBLE_VLAN_TERMINATION
+        if (fabric_metadata.push_double_vlan == _TRUE) {
+            // Double VLAN termination.
+            push_vlan();
+            push_inner_vlan();
+        } else {
+            // If no push double vlan, inner_vlan_tag must be popped
+            hdr.inner_vlan_tag.setInvalid();
+#endif // WITH_DOUBLE_VLAN_TERMINATION
+            // Port-based VLAN tagging (by default all
+            // ports are assumed tagged)
+            if (!egress_vlan.apply().hit) {
+                // Push VLAN tag if not the default one.
+                if (fabric_metadata.vlan_id != DEFAULT_VLAN_ID) {
+                    push_vlan();
+                }
             }
+#ifdef WITH_DOUBLE_VLAN_TERMINATION
         }
+#endif // WITH_DOUBLE_VLAN_TERMINATION
 
         // TTL decrement and check.
         if (hdr.mpls.isValid()) {
diff --git a/pipelines/fabric/src/main/resources/include/header.p4 b/pipelines/fabric/src/main/resources/include/header.p4
index 11204e8..09df685 100644
--- a/pipelines/fabric/src/main/resources/include/header.p4
+++ b/pipelines/fabric/src/main/resources/include/header.p4
@@ -159,17 +159,27 @@
 struct bng_meta_t {
     bit<2>  type; // upstream or downstream
     bit<32> line_id; // subscriber line
+    bit<16> pppoe_session_id;
     bit<32> ds_meter_result; // for downstream metering
 }
 #endif // WITH_BNG
 
 //Custom metadata definition
 struct fabric_metadata_t {
-    bit<16>       eth_type;
+    bit<16>       last_eth_type;
+    _BOOL         is_ipv4;
+    _BOOL         is_ipv6;
+    _BOOL         is_mpls;
     bit<16>       ip_eth_type;
     vlan_id_t     vlan_id;
     bit<3>        vlan_pri;
     bit<1>        vlan_cfi;
+#ifdef WITH_DOUBLE_VLAN_TERMINATION
+    _BOOL         push_double_vlan;
+    vlan_id_t     inner_vlan_id;
+    bit<3>        inner_vlan_pri;
+    bit<1>        inner_vlan_cfi;
+#endif // WITH_DOUBLE_VLAN_TERMINATION
     mpls_label_t  mpls_label;
     bit<8>        mpls_ttl;
     _BOOL         skip_forwarding;
@@ -195,9 +205,9 @@
 struct parsed_headers_t {
     ethernet_t ethernet;
     vlan_tag_t vlan_tag;
-#if defined(WITH_XCONNECT) || defined(WITH_BNG)
+#if defined(WITH_XCONNECT) || defined(WITH_BNG) || defined(WITH_DOUBLE_VLAN_TERMINATION)
     vlan_tag_t inner_vlan_tag;
-#endif // WITH_XCONNECT || WITH_BNG
+#endif // WITH_XCONNECT || WITH_BNG || WITH_DOUBLE_VLAN_TERMINATION
 #ifdef WITH_BNG
     pppoe_t pppoe;
 #endif // WITH_BNG
diff --git a/pipelines/fabric/src/main/resources/include/parser.p4 b/pipelines/fabric/src/main/resources/include/parser.p4
index e234676..2d6a1d4 100644
--- a/pipelines/fabric/src/main/resources/include/parser.p4
+++ b/pipelines/fabric/src/main/resources/include/parser.p4
@@ -40,14 +40,14 @@
 
     state parse_ethernet {
         packet.extract(hdr.ethernet);
-        fabric_metadata.eth_type = hdr.ethernet.eth_type;
+        fabric_metadata.last_eth_type = hdr.ethernet.eth_type;
         fabric_metadata.vlan_id = DEFAULT_VLAN_ID;
         transition select(hdr.ethernet.eth_type){
             ETHERTYPE_VLAN: parse_vlan_tag;
             ETHERTYPE_MPLS: parse_mpls;
-            ETHERTYPE_IPV4: parse_ipv4;
+            ETHERTYPE_IPV4: pre_parse_ipv4;
 #ifdef WITH_IPV6
-            ETHERTYPE_IPV6: parse_ipv6;
+            ETHERTYPE_IPV6: pre_parse_ipv6;
 #endif // WITH_IPV6
             default: accept;
         }
@@ -56,25 +56,27 @@
     state parse_vlan_tag {
         packet.extract(hdr.vlan_tag);
         transition select(hdr.vlan_tag.eth_type){
-            ETHERTYPE_IPV4: parse_ipv4;
+            ETHERTYPE_IPV4: pre_parse_ipv4;
 #ifdef WITH_IPV6
-            ETHERTYPE_IPV6: parse_ipv6;
+            ETHERTYPE_IPV6: pre_parse_ipv6;
 #endif // WITH_IPV6
             ETHERTYPE_MPLS: parse_mpls;
-#if defined(WITH_XCONNECT) || defined(WITH_BNG)
+#if defined(WITH_XCONNECT) || defined(WITH_BNG) || defined(WITH_DOUBLE_VLAN_TERMINATION)
             ETHERTYPE_VLAN: parse_inner_vlan_tag;
+            ETHERTYPE_QINQ: parse_inner_vlan_tag;
+            ETHERTYPE_QINQ_NON_STD: parse_inner_vlan_tag;
 #endif // WITH_XCONNECT
             default: accept;
         }
     }
 
-#if defined(WITH_XCONNECT) || defined(WITH_BNG)
+#if defined(WITH_XCONNECT) || defined(WITH_BNG) || defined(WITH_DOUBLE_VLAN_TERMINATION)
     state parse_inner_vlan_tag {
         packet.extract(hdr.inner_vlan_tag);
         transition select(hdr.inner_vlan_tag.eth_type){
-            ETHERTYPE_IPV4: parse_ipv4;
+            ETHERTYPE_IPV4: pre_parse_ipv4;
 #ifdef WITH_IPV6
-            ETHERTYPE_IPV6: parse_ipv6;
+            ETHERTYPE_IPV6: pre_parse_ipv6;
 #endif // WITH_IPV6
             ETHERTYPE_MPLS: parse_mpls;
 #ifdef WITH_BNG
@@ -84,16 +86,16 @@
             default: accept;
         }
     }
-#endif // WITH_XCONNECT
+#endif // WITH_XCONNECT || WITH_BNG || WITH_DOUBLE_VLAN_TERMINATION
 
 #ifdef WITH_BNG
     state parse_pppoe {
         packet.extract(hdr.pppoe);
         transition select(hdr.pppoe.protocol) {
             PPPOE_PROTOCOL_MPLS: parse_mpls;
-            PPPOE_PROTOCOL_IP4: parse_ipv4;
+            PPPOE_PROTOCOL_IP4: pre_parse_ipv4;
 #ifdef WITH_IPV6
-            PPPOE_PROTOCOL_IP6: parse_ipv6;
+            PPPOE_PROTOCOL_IP6: pre_parse_ipv6;
 #endif // WITH_IPV6
             default: accept;
         }
@@ -102,13 +104,16 @@
 
     state parse_mpls {
         packet.extract(hdr.mpls);
+        fabric_metadata.is_mpls = _TRUE;
         fabric_metadata.mpls_label = hdr.mpls.label;
         fabric_metadata.mpls_ttl = hdr.mpls.ttl;
         // There is only one MPLS label for this fabric.
         // Assume header after MPLS header is IPv4/IPv6
         // Lookup first 4 bits for version
         transition select(packet.lookahead<bit<IP_VER_LENGTH>>()) {
-            //The packet should be either IPv4 or IPv6.
+            // The packet should be either IPv4 or IPv6.
+            // If we have MPLS, go directly to parsing state without
+            // moving to pre_ states, the packet is considered MPLS
             IP_VERSION_4: parse_ipv4;
 #ifdef WITH_IPV6
             IP_VERSION_6: parse_ipv6;
@@ -117,6 +122,11 @@
         }
     }
 
+    // Intermediate state to set is_ipv4
+    state pre_parse_ipv4 {
+        fabric_metadata.is_ipv4 = _TRUE;
+        transition parse_ipv4;
+    }
     state parse_ipv4 {
         packet.extract(hdr.ipv4);
         fabric_metadata.ip_proto = hdr.ipv4.protocol;
@@ -132,6 +142,11 @@
     }
 
 #ifdef WITH_IPV6
+    // Intermediate state to set is_ipv6
+    state pre_parse_ipv6 {
+        fabric_metadata.is_ipv6 = _TRUE;
+        transition parse_ipv6;
+    }
     state parse_ipv6 {
         packet.extract(hdr.ipv6);
         fabric_metadata.ip_proto = hdr.ipv6.next_hdr;
@@ -270,9 +285,9 @@
 #endif // WITH_INT_SINK
         packet.emit(hdr.ethernet);
         packet.emit(hdr.vlan_tag);
-#if defined(WITH_XCONNECT) || defined(WITH_BNG)
+#if defined(WITH_XCONNECT) || defined(WITH_BNG) || defined(WITH_DOUBLE_VLAN_TERMINATION)
         packet.emit(hdr.inner_vlan_tag);
-#endif // WITH_XCONNECT
+#endif // WITH_XCONNECT || WITH_BNG || WITH_DOUBLE_VLAN_TERMINATION
 #ifdef WITH_BNG
         packet.emit(hdr.pppoe);
 #endif // WITH_BNG
diff --git a/pipelines/fabric/src/main/resources/include/size.p4 b/pipelines/fabric/src/main/resources/include/size.p4
index 8cd2fcc..2883094 100644
--- a/pipelines/fabric/src/main/resources/include/size.p4
+++ b/pipelines/fabric/src/main/resources/include/size.p4
@@ -2,8 +2,14 @@
 #define __TABLE_SIZE__
 
 // Default sizes when building for BMv2.
-
-#define PORT_VLAN_TABLE_SIZE 1024
+#define BNG_MAX_SUBSC 8192
+#define BNG_MAX_NET_PER_SUBSC 4
+#define BNG_MAX_SUBSC_NET BNG_MAX_NET_PER_SUBSC * BNG_MAX_SUBSC
+#ifdef WITH_BNG
+    #define PORT_VLAN_TABLE_SIZE BNG_MAX_SUBSC
+#else
+    #define PORT_VLAN_TABLE_SIZE 1024
+#endif // WITH_BNG
 #define FWD_CLASSIFIER_TABLE_SIZE 1024
 #define BRIDGING_TABLE_SIZE 1024
 #define MPLS_TABLE_SIZE 1024