[SDFAB-357] Backport slicing in fabric and add support for QFI in PDR and fabric

Change-Id: Ieb10140dc0029a0cbf59ddfbb77f64f9a8c7379e
(cherry picked from commit 411f6f7f461db6491d627c2cb31642bdd6e7c8d8)
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/acl.p4 b/pipelines/fabric/impl/src/main/resources/include/control/acl.p4
index ae86e3b..9d7c7ba 100644
--- a/pipelines/fabric/impl/src/main/resources/include/control/acl.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/control/acl.p4
@@ -21,29 +21,22 @@
 #include "../header.p4"
 
 control Acl (inout parsed_headers_t hdr,
-             inout fabric_metadata_t fabric_metadata,
+             inout fabric_metadata_t fabric_md,
              inout standard_metadata_t standard_metadata) {
-
-    ipv4_addr_t ipv4_src    = 0;
-    ipv4_addr_t ipv4_dst    = 0;
-    bit<8> ip_proto         = 0;
-    l4_port_t l4_sport      = 0;
-    l4_port_t l4_dport      = 0;
-
     /*
      * ACL Table.
      */
     direct_counter(CounterType.packets_and_bytes) acl_counter;
 
     action set_next_id_acl(next_id_t next_id) {
-        fabric_metadata.next_id = next_id;
+        fabric_md.next_id = next_id;
         acl_counter.count();
     }
 
     // Send immendiatelly to CPU - skip the rest of ingress.
     action punt_to_cpu() {
         standard_metadata.egress_spec = CPU_PORT;
-        fabric_metadata.skip_next = _TRUE;
+        fabric_md.skip_next = _TRUE;
         acl_counter.count();
     }
 
@@ -55,7 +48,7 @@
 
     action drop() {
         mark_to_drop(standard_metadata);
-        fabric_metadata.skip_next = _TRUE;
+        fabric_md.skip_next = _TRUE;
         acl_counter.count();
     }
 
@@ -70,14 +63,14 @@
             hdr.ethernet.src_addr           : ternary @name("eth_src");   // 48
             hdr.vlan_tag.vlan_id            : ternary @name("vlan_id");   // 12
             hdr.eth_type.value              : ternary @name("eth_type");  // 16
-            ipv4_src                        : ternary @name("ipv4_src");  // 32
-            ipv4_dst                        : ternary @name("ipv4_dst");  // 32
-            ip_proto                        : ternary @name("ip_proto");  // 8
+            fabric_md.lkp.ipv4_src          : ternary @name("ipv4_src");  // 32
+            fabric_md.lkp.ipv4_dst          : ternary @name("ipv4_dst");  // 32
+            fabric_md.lkp.ip_proto          : ternary @name("ip_proto");  // 8
             hdr.icmp.icmp_type              : ternary @name("icmp_type"); // 8
             hdr.icmp.icmp_code              : ternary @name("icmp_code"); // 8
-            l4_sport                        : ternary @name("l4_sport");  // 16
-            l4_dport                        : ternary @name("l4_dport");  // 16
-            fabric_metadata.port_type       : ternary @name("port_type"); // 2
+            fabric_md.lkp.l4_sport          : ternary @name("l4_sport");  // 16
+            fabric_md.lkp.l4_dport          : ternary @name("l4_dport");  // 16
+            fabric_md.port_type             : ternary @name("port_type"); // 2
         }
 
         actions = {
@@ -94,29 +87,6 @@
     }
 
     apply {
-        if (hdr.gtpu.isValid() && hdr.inner_ipv4.isValid()) {
-            ipv4_src = hdr.inner_ipv4.src_addr;
-            ipv4_dst = hdr.inner_ipv4.dst_addr;
-            ip_proto = hdr.inner_ipv4.protocol;
-            if (hdr.inner_tcp.isValid()) {
-                l4_sport = hdr.inner_tcp.sport;
-                l4_dport = hdr.inner_tcp.dport;
-            } else if (hdr.inner_udp.isValid()) {
-                l4_sport = hdr.inner_udp.sport;
-                l4_dport = hdr.inner_udp.dport;
-            }
-        } else if (hdr.ipv4.isValid()) {
-            ipv4_src = hdr.ipv4.src_addr;
-            ipv4_dst = hdr.ipv4.dst_addr;
-            ip_proto = hdr.ipv4.protocol;
-            if (hdr.tcp.isValid()) {
-                l4_sport = hdr.tcp.sport;
-                l4_dport = hdr.tcp.dport;
-            } else if (hdr.udp.isValid()) {
-                l4_sport = hdr.udp.sport;
-                l4_dport = hdr.udp.dport;
-            }
-        }
         acl.apply();
     }
 }
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/lookup_md_init.p4 b/pipelines/fabric/impl/src/main/resources/include/control/lookup_md_init.p4
new file mode 100644
index 0000000..c672bc9
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/resources/include/control/lookup_md_init.p4
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __LOOKUP__
+#define __LOOKUP__
+
+control LookupMdInit (in parsed_headers_t hdr,
+                      out lookup_metadata_t lkp_md) {
+    apply {
+        lkp_md.is_ipv4 = _FALSE;
+        lkp_md.ipv4_src = 0;
+        lkp_md.ipv4_dst = 0;
+        lkp_md.ip_proto = 0;
+        lkp_md.l4_sport = 0;
+        lkp_md.l4_dport = 0;
+        lkp_md.icmp_type = 0;
+        lkp_md.icmp_code = 0;
+        if (hdr.inner_ipv4.isValid()) {
+            lkp_md.is_ipv4 = true;
+            lkp_md.ipv4_src = hdr.inner_ipv4.src_addr;
+            lkp_md.ipv4_dst = hdr.inner_ipv4.dst_addr;
+            lkp_md.ip_proto = hdr.inner_ipv4.protocol;
+            if (hdr.inner_tcp.isValid()) {
+                lkp_md.l4_sport = hdr.inner_tcp.sport;
+                lkp_md.l4_dport = hdr.inner_tcp.dport;
+            } else if (hdr.inner_udp.isValid()) {
+                lkp_md.l4_sport = hdr.inner_udp.sport;
+                lkp_md.l4_dport = hdr.inner_udp.dport;
+            } else if (hdr.inner_icmp.isValid()) {
+                lkp_md.icmp_type = hdr.inner_icmp.icmp_type;
+                lkp_md.icmp_code = hdr.inner_icmp.icmp_code;
+            }
+        } else if (hdr.ipv4.isValid()) {
+            lkp_md.is_ipv4 = true;
+            lkp_md.ipv4_src = hdr.ipv4.src_addr;
+            lkp_md.ipv4_dst = hdr.ipv4.dst_addr;
+            lkp_md.ip_proto = hdr.ipv4.protocol;
+            if (hdr.tcp.isValid()) {
+                lkp_md.l4_sport = hdr.tcp.sport;
+                lkp_md.l4_dport = hdr.tcp.dport;
+            } else if (hdr.udp.isValid()) {
+                lkp_md.l4_sport = hdr.udp.sport;
+                lkp_md.l4_dport = hdr.udp.dport;
+            } else if (hdr.icmp.isValid()) {
+                lkp_md.icmp_type = hdr.icmp.icmp_type;
+                lkp_md.icmp_code = hdr.icmp.icmp_code;
+            }
+        }
+    }
+}
+
+#endif
+
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/slicing.p4 b/pipelines/fabric/impl/src/main/resources/include/control/slicing.p4
new file mode 100644
index 0000000..87d887a
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/resources/include/control/slicing.p4
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __SLICING__
+#define __SLICING__
+
+// ACL-like classification, maps lookup metadata to slice_id and tc. For UE
+// traffic, values can be overriden later by the SPGW PDR tables.
+// To apply the same slicing and QoS treatment end-to-end, we use the IPv4 DSCP
+// field to piggyback slice_id and tc (see EgressDscpRewriter). This is
+// especially important for UE traffic, where classification based on PDRs can
+// only happen at the ingress leaf switch (implementing the UPF function).
+// As such, for traffic coming from selected ports, we allow trusting the
+// slice_id and tc values carried in the dscp.
+control IngressSliceTcClassifier (in parsed_headers_t hdr,
+                                  inout fabric_metadata_t fabric_md,
+                                  in standard_metadata_t standard_metadata) {
+
+    direct_counter(CounterType.packets) classifier_stats;
+
+    action set_slice_id_tc(slice_id_t slice_id, tc_t tc) {
+        fabric_md.slice_id = slice_id;
+        fabric_md.tc = tc;
+        classifier_stats.count();
+    }
+
+    // Should be used only for infrastructure ports (leaf-leaf, or leaf-spine),
+    // or ports facing servers that implement early classification based on the
+    // SDFAB DSCP encoding (slice_id++tc).
+    action trust_dscp() {
+        fabric_md.slice_id = hdr.ipv4.dscp[SLICE_ID_WIDTH+TC_WIDTH-1:TC_WIDTH];
+        fabric_md.tc = hdr.ipv4.dscp[TC_WIDTH-1:0];
+        classifier_stats.count();
+    }
+
+    table classifier {
+        key = {
+            standard_metadata.ingress_port  : ternary @name("ig_port");
+            fabric_md.lkp.ipv4_src          : ternary @name("ipv4_src");
+            fabric_md.lkp.ipv4_dst          : ternary @name("ipv4_dst");
+            fabric_md.lkp.ip_proto          : ternary @name("ip_proto");
+            fabric_md.lkp.l4_sport          : ternary @name("l4_sport");
+            fabric_md.lkp.l4_dport          : ternary @name("l4_dport");
+        }
+        actions = {
+            set_slice_id_tc;
+            trust_dscp;
+        }
+        const default_action = set_slice_id_tc(DEFAULT_SLICE_ID, DEFAULT_TC);
+        counters = classifier_stats;
+        size = QOS_CLASSIFIER_TABLE_SIZE;
+    }
+
+    apply {
+        classifier.apply();
+    }
+}
+
+// Provides metering and mapping to queues based on slice_id and tc. Should be
+// applied after any other block writing slice_id and tc.
+control IngressQos (inout fabric_metadata_t fabric_md,
+                    inout standard_metadata_t standard_metadata) {
+
+    // One meter per tc per slice. The index should be slice_id++tc.
+    meter(1 << SLICE_TC_WIDTH, MeterType.bytes) slice_tc_meter;
+
+    direct_counter(CounterType.packets) queues_stats;
+
+    action set_queue(qid_t qid) {
+        // We can't set the queue id in bmv2.
+        queues_stats.count();
+    }
+
+    // For policing.
+    action meter_drop() {
+        mark_to_drop(standard_metadata);
+        queues_stats.count();
+    }
+
+    table queues {
+        key = {
+            fabric_md.slice_id:     exact   @name("slice_id");
+            fabric_md.tc:           exact   @name("tc");
+            fabric_md.packet_color: ternary @name("color"); // 0=GREEN, 1=YELLOW, 2=RED
+        }
+        actions = {
+            set_queue;
+            meter_drop;
+        }
+        const default_action = set_queue(0); // 0 = Best Effort
+        counters = queues_stats;
+        // Two times the number of tcs for all slices, because we might need to
+        // match on different colors for the same slice and tc.
+        size = 1 << (SLICE_TC_WIDTH + 1);
+    }
+
+    slice_tc_t slice_tc = fabric_md.slice_id++fabric_md.tc;
+
+    apply {
+        // Meter index should be 0 for all packets with default slice_id and tc.
+        slice_tc_meter.execute_meter((bit<32>) slice_tc, fabric_md.packet_color);
+        fabric_md.dscp = slice_tc;
+        queues.apply();
+    }
+}
+
+// Allows per-egress port rewriting of the outermost IPv4 DSCP field to
+// piggyback slice_id and tc across the fabric.
+control EgressDscpRewriter (inout parsed_headers_t hdr,
+                            in fabric_metadata_t fabric_md,
+                            in standard_metadata_t standard_metadata) {
+
+    bit<6> tmp_dscp = fabric_md.dscp;
+
+    action rewrite() {
+        // Do nothing, tmp_dscp is already initialized.
+    }
+
+    // Sets the DSCP field to zero. Should be used for edge ports facing devices
+    // that do not support the SDFAB DSCP encoding.
+    action clear() {
+        tmp_dscp = 0;
+    }
+
+    table rewriter {
+        key = {
+            standard_metadata.egress_port : exact @name("eg_port");
+        }
+        actions = {
+            rewrite;
+            clear;
+            @defaultonly nop;
+        }
+        const default_action = nop;
+        size = DSCP_REWRITER_TABLE_SIZE;
+    }
+
+    apply {
+        if (rewriter.apply().hit) {
+#ifdef WITH_SPGW
+            if (hdr.gtpu_ipv4.isValid()) {
+                hdr.ipv4.dscp = tmp_dscp;
+            } else
+#endif // WITH_SPGW
+            if (hdr.ipv4.isValid()) {
+                hdr.inner_ipv4.dscp = tmp_dscp;
+            }
+        }
+    }
+}
+
+#endif
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4 b/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4
index adacd9a..6750dbd 100644
--- a/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4
@@ -45,6 +45,8 @@
         hdr.ipv4 = hdr.inner_ipv4;
         hdr.inner_ipv4.setInvalid();
         hdr.gtpu.setInvalid();
+        hdr.gtpu_options.setInvalid();
+        hdr.gtpu_ext_psc.setInvalid();
     }
     @hidden
     action decap_inner_tcp() {
@@ -115,10 +117,11 @@
     //===== Interface Tables ======//
     //=============================//
 
-    action load_iface(spgw_interface_t src_iface) {
+    action load_iface(spgw_interface_t src_iface, slice_id_t slice_id) {
         // Interface type can be access, core, from_dbuf (see InterfaceType enum)
         fabric_md.spgw.src_iface = src_iface;
         fabric_md.spgw.skip_spgw = _FALSE;
+        fabric_md.slice_id = slice_id;
     }
     action iface_miss() {
         fabric_md.spgw.src_iface = SPGW_IFACE_UNKNOWN;
@@ -143,21 +146,26 @@
     //=============================//
     //===== PDR Tables ======//
     //=============================//
-
     action load_pdr(pdr_ctr_id_t  ctr_id,
                     far_id_t      far_id,
-                    bit<1>        needs_gtpu_decap) {
+                    bit<1>        needs_gtpu_decap,
+                    tc_t          tc) {
         fabric_md.spgw.ctr_id = ctr_id;
         fabric_md.spgw.far_id = far_id;
         fabric_md.spgw.needs_gtpu_decap = (_BOOL)needs_gtpu_decap;
+        fabric_md.tc = tc;
     }
 
-    action load_pdr_qos(pdr_ctr_id_t ctr_id,
-                        far_id_t     far_id,
-                        bit<1>       needs_gtpu_decap,
-                        qid_t        qid) {
-        load_pdr(ctr_id, far_id, needs_gtpu_decap);
-        // we cannot set the qid, since bmv2 does not support it
+    action load_pdr_qos(pdr_ctr_id_t  ctr_id,
+                        far_id_t      far_id,
+                        bit<1>        needs_gtpu_decap,
+                        // Used to push QFI, valid for 5G traffic only
+                        bit<1>        needs_qfi_push,
+                        qfi_t         qfi,
+                        tc_t          tc) {
+        load_pdr(ctr_id, far_id, needs_gtpu_decap, tc);
+        fabric_md.spgw.qfi = qfi;
+        fabric_md.spgw.needs_qfi_push = (_BOOL)needs_qfi_push;
     }
 
     // These two tables scale well and cover the average case PDR
@@ -177,6 +185,10 @@
         key = {
             hdr.ipv4.dst_addr           : exact @name("tunnel_ipv4_dst");
             hdr.gtpu.teid               : exact @name("teid");
+            // Match valid only for 5G traffic
+            hdr.gtpu_ext_psc.isValid()  : exact @name("has_qfi");
+            // QFI metadata is 0 when gptu_ext_psc is invalid.
+            fabric_md.spgw.qfi          : exact @name("qfi");
         }
         actions = {
             load_pdr;
@@ -294,7 +306,6 @@
 
     counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
 
-
     @hidden
     action gtpu_encap() {
         hdr.gtpu_ipv4.setValid();
@@ -320,7 +331,6 @@
                 + (UDP_HDR_SIZE + GTP_HDR_SIZE);
         hdr.gtpu_udp.checksum = 0; // Updated later, if WITH_SPGW_UDP_CSUM_UPDATE
 
-
         hdr.outer_gtpu.setValid();
         hdr.outer_gtpu.version = GTP_V1;
         hdr.outer_gtpu.pt = GTP_PROTOCOL_TYPE_GTP;
@@ -333,10 +343,35 @@
         hdr.outer_gtpu.teid = fabric_md.spgw.teid;
     }
 
+    @hidden
+    action gtpu_encap_qfi() {
+        gtpu_encap();
+        hdr.gtpu_ipv4.total_len = hdr.ipv4.total_len
+                    + IPV4_HDR_SIZE + UDP_HDR_SIZE + GTP_HDR_SIZE
+                    + GTPU_OPTIONS_HDR_BYTES + GTPU_EXT_PSC_HDR_BYTES;
+        hdr.gtpu_udp.len = fabric_md.spgw.ipv4_len
+                    + UDP_HDR_SIZE + GTP_HDR_SIZE
+                    + GTPU_OPTIONS_HDR_BYTES + GTPU_EXT_PSC_HDR_BYTES;
+        hdr.outer_gtpu.msglen = fabric_md.spgw.ipv4_len
+                    + GTPU_OPTIONS_HDR_BYTES + GTPU_EXT_PSC_HDR_BYTES;
+        hdr.outer_gtpu.ex_flag = 1;
+        hdr.outer_gtpu_options.setValid();
+        hdr.outer_gtpu_options.next_ext = GTPU_NEXT_EXT_PSC;
+        hdr.outer_gtpu_ext_psc.setValid();
+        hdr.outer_gtpu_ext_psc.type = GTPU_EXT_PSC_TYPE_DL;
+        hdr.outer_gtpu_ext_psc.len = GTPU_EXT_PSC_LEN;
+        hdr.outer_gtpu_ext_psc.qfi = fabric_md.spgw.qfi;
+        hdr.outer_gtpu_ext_psc.next_ext = GTPU_NEXT_EXT_NONE;
+    }
+
     apply {
         if (fabric_md.spgw.skip_spgw == _FALSE) {
             if (fabric_md.spgw.needs_gtpu_encap == _TRUE) {
-                gtpu_encap();
+                if (fabric_md.spgw.needs_qfi_push == _TRUE) {
+                    gtpu_encap_qfi();
+                } else {
+                    gtpu_encap();
+                }
             }
             if (fabric_md.spgw.skip_egress_pdr_ctr == _FALSE) {
                 pdr_counter.count(fabric_md.spgw.ctr_id);
diff --git a/pipelines/fabric/impl/src/main/resources/include/define.p4 b/pipelines/fabric/impl/src/main/resources/include/define.p4
index 36a5ec7..519d454 100644
--- a/pipelines/fabric/impl/src/main/resources/include/define.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/define.p4
@@ -73,10 +73,20 @@
 #define UDP_HDR_SIZE 8
 #define GTP_HDR_SIZE 8
 
+#define GTPU_OPTIONS_HDR_BYTES 4
+#define GTPU_EXT_PSC_HDR_BYTES 4
+
 #define UDP_PORT_GTPU 2152
 #define GTP_GPDU 0xff
 #define GTP_V1 0x01
 #define GTP_PROTOCOL_TYPE_GTP 0x01
+#define GTPU_NEXT_EXT_NONE 0x0
+#define GTPU_NEXT_EXT_PSC 0x85
+// 1*4-octets
+#define GTPU_EXT_PSC_LEN 8w1
+
+const bit<4> GTPU_EXT_PSC_TYPE_DL = 4w0; // Downlink
+const bit<4> GTPU_EXT_PSC_TYPE_UL = 4w1; // Uplink
 
 #define PKT_INSTANCE_TYPE_NORMAL 0
 #define PKT_INSTANCE_TYPE_INGRESS_CLONE 1
@@ -95,6 +105,12 @@
 typedef bit<12> vlan_id_t;
 typedef bit<32> ipv4_addr_t;
 typedef bit<16> l4_port_t;
+typedef bit<SLICE_ID_WIDTH> slice_id_t;
+typedef bit<TC_WIDTH> tc_t; // Traffic Class (for QoS) within a slice
+typedef bit<SLICE_TC_WIDTH> slice_tc_t; // Slice and TC identifier
+
+const slice_id_t DEFAULT_SLICE_ID = 0;
+const tc_t DEFAULT_TC = 0;
 
 // SPGW types
 typedef bit<2> direction_t;
@@ -105,6 +121,7 @@
 typedef bit<32> far_id_t;
 typedef bit<32> pdr_ctr_id_t;
 typedef bit<32> teid_t;
+typedef bit<6> qfi_t;
 typedef bit<5> qid_t;
 
 const spgw_interface_t SPGW_IFACE_UNKNOWN = 8w0;
diff --git a/pipelines/fabric/impl/src/main/resources/include/header.p4 b/pipelines/fabric/impl/src/main/resources/include/header.p4
index b76ea01..8e3537e 100644
--- a/pipelines/fabric/impl/src/main/resources/include/header.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/header.p4
@@ -139,6 +139,25 @@
     teid_t  teid;       /* tunnel endpoint id */
 }
 
+// Follows gtpu_t if any of ex_flag, seq_flag, or npdu_flag is 1.
+header gtpu_options_t {
+    bit<16> seq_num;   /* Sequence number */
+    bit<8>  n_pdu_num; /* N-PDU number */
+    bit<8>  next_ext;  /* Next extension header */
+}
+
+// GTPU extension: PDU Session Container (PSC) -- 3GPP TS 38.415 version 15.2.0
+// https://www.etsi.org/deliver/etsi_ts/138400_138499/138415/15.02.00_60/ts_138415v150200p.pdf
+header gtpu_ext_psc_t {
+    bit<8> len;      /* Length in 4-octet units (common to all extensions) */
+    bit<4> type;     /* Uplink or downlink */
+    bit<4> spare0;   /* Reserved */
+    bit<1> ppp;      /* Paging Policy Presence (UL only, not supported) */
+    bit<1> rqi;      /* Reflective QoS Indicator (UL only) */
+    qfi_t  qfi;      /* QoS Flow Identifier */
+    bit<8> next_ext;
+}
+
 #ifdef WITH_SPGW
 struct spgw_meta_t {
     bit<16>           ipv4_len;
@@ -149,11 +168,13 @@
     pdr_ctr_id_t      ctr_id;
     far_id_t          far_id;
     spgw_interface_t  src_iface;
+    qfi_t             qfi;
     _BOOL             skip_spgw;
     _BOOL             notify_spgwc;
     _BOOL             needs_gtpu_encap;
     _BOOL             needs_gtpu_decap;
     _BOOL             skip_egress_pdr_ctr;
+    _BOOL             needs_qfi_push;
 }
 #endif // WITH_SPGW
 
@@ -174,8 +195,25 @@
 }
 #endif // WITH_BNG
 
+// Used for table lookup. Initialized with the parsed headers, or 0 if invalid.
+// Never updated by the pipe. When both outer and inner IPv4 headers are valid,
+// this should always carry the inner ones. The assumption is that we terminate
+// GTP tunnels in the fabric, so we are more interested in observing/blocking
+// the inner flows. We might revisit this decision in the future.
+struct lookup_metadata_t {
+    _BOOL                   is_ipv4;
+    bit<32>                 ipv4_src;
+    bit<32>                 ipv4_dst;
+    bit<8>                  ip_proto;
+    l4_port_t               l4_sport;
+    l4_port_t               l4_dport;
+    bit<8>                  icmp_type;
+    bit<8>                  icmp_code;
+}
+
 //Custom metadata definition
 struct fabric_metadata_t {
+    lookup_metadata_t lkp;
     bit<16>       ip_eth_type;
     vlan_id_t     vlan_id;
     bit<3>        vlan_pri;
@@ -199,6 +237,10 @@
     bit<16>       l4_dport;
     bit<32>       ipv4_src_addr;
     bit<32>       ipv4_dst_addr;
+    slice_id_t    slice_id;
+    bit<2>        packet_color;
+    tc_t          tc;
+    bit<6>        dscp;
 #ifdef WITH_SPGW
     bit<16>       inner_l4_sport;
     bit<16>       inner_l4_dport;
@@ -228,8 +270,12 @@
     ipv4_t gtpu_ipv4;
     udp_t gtpu_udp;
     gtpu_t outer_gtpu;
+    gtpu_options_t outer_gtpu_options;
+    gtpu_ext_psc_t outer_gtpu_ext_psc;
 #endif // WITH_SPGW
     gtpu_t gtpu;
+    gtpu_options_t gtpu_options;
+    gtpu_ext_psc_t gtpu_ext_psc;
     ipv4_t inner_ipv4;
     udp_t inner_udp;
     tcp_t inner_tcp;
diff --git a/pipelines/fabric/impl/src/main/resources/include/parser.p4 b/pipelines/fabric/impl/src/main/resources/include/parser.p4
index fb9e764..0a77d4c 100644
--- a/pipelines/fabric/impl/src/main/resources/include/parser.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/parser.p4
@@ -200,7 +200,30 @@
 
     state parse_gtpu {
         packet.extract(hdr.gtpu);
-        transition parse_inner_ipv4;
+        transition select(hdr.gtpu.ex_flag, hdr.gtpu.seq_flag, hdr.gtpu.npdu_flag) {
+            (0, 0, 0): parse_inner_ipv4;
+            default: parse_gtpu_options;
+        }
+    }
+
+    state parse_gtpu_options {
+        packet.extract(hdr.gtpu_options);
+        bit<8> gtpu_ext_len = packet.lookahead<bit<8>>();
+        transition select(hdr.gtpu_options.next_ext, gtpu_ext_len) {
+            (GTPU_NEXT_EXT_PSC, GTPU_EXT_PSC_LEN): parse_gtpu_ext_psc;
+            default: accept;
+        }
+    }
+
+    state parse_gtpu_ext_psc {
+        packet.extract(hdr.gtpu_ext_psc);
+#ifdef WITH_SPGW
+        fabric_metadata.spgw.qfi = hdr.gtpu_ext_psc.qfi;
+#endif // WITH_SPGW
+        transition select(hdr.gtpu_ext_psc.next_ext) {
+            GTPU_NEXT_EXT_NONE: parse_inner_ipv4;
+            default: accept;
+        }
     }
 
     state parse_inner_ipv4 {
@@ -307,6 +330,8 @@
         packet.emit(hdr.gtpu_ipv4);
         packet.emit(hdr.gtpu_udp);
         packet.emit(hdr.outer_gtpu);
+        packet.emit(hdr.outer_gtpu_options);
+        packet.emit(hdr.outer_gtpu_ext_psc);
 #endif // WITH_SPGW
         packet.emit(hdr.ipv4);
 #ifdef WITH_IPV6
@@ -317,6 +342,8 @@
         packet.emit(hdr.icmp);
         // if we parsed a GTPU packet but did not decap it
         packet.emit(hdr.gtpu);
+        packet.emit(hdr.gtpu_options);
+        packet.emit(hdr.gtpu_ext_psc);
         packet.emit(hdr.inner_ipv4);
         packet.emit(hdr.inner_tcp);
         packet.emit(hdr.inner_udp);
diff --git a/pipelines/fabric/impl/src/main/resources/include/size.p4 b/pipelines/fabric/impl/src/main/resources/include/size.p4
index caa8c14..3a782f10 100644
--- a/pipelines/fabric/impl/src/main/resources/include/size.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/size.p4
@@ -25,5 +25,11 @@
 #define HASHED_ACT_PROFILE_SIZE 32w1024
 #define MULTICAST_NEXT_TABLE_SIZE 1024
 #define EGRESS_VLAN_TABLE_SIZE 1024
+#define QOS_CLASSIFIER_TABLE_SIZE 512
+#define DSCP_REWRITER_TABLE_SIZE 512
+
+#define SLICE_ID_WIDTH 4
+#define TC_WIDTH 2
+#define SLICE_TC_WIDTH (SLICE_ID_WIDTH + TC_WIDTH)
 
 #endif