Backport fabric-spgw TNA changes to v1model

Allow building fabric.p4 with custom S1U_SGW_PREFIX

Change-Id: I4fcaf3f2b56e6d024e54f8897467c280be73e001

Backport fabric-spgw TNA changes to v1model

This branch ports the SPGW pipeline of fabric-tna back to v1model to
make the tables and actions consistent across the two versions. This
consistency will allow for testing of ONOS apps on both software and
hardware targets, since the only available software target that works
with ONOS requires v1model p4 programs. Changes to the fabric testing
suite (fabric-p4test) will be pushed in parallel.

Change-Id: Iad393b27f08a4fcd29f82e59d181871475ef2b10
diff --git a/pipelines/fabric/impl/src/main/resources/include/checksum.p4 b/pipelines/fabric/impl/src/main/resources/include/checksum.p4
index a10a393..2496002 100644
--- a/pipelines/fabric/impl/src/main/resources/include/checksum.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/checksum.p4
@@ -18,7 +18,7 @@
 #define __CHECKSUM__
 
 #ifdef WITH_SPGW
-#include "spgw.p4"
+#include "control/spgw.p4"
 #endif // WITH_SPGW
 
 control FabricComputeChecksum(inout parsed_headers_t hdr,
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4 b/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4
new file mode 100644
index 0000000..b1bbcaf
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/resources/include/control/spgw.p4
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2017-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 __SPGW__
+#define __SPGW__
+
+#define DEFAULT_PDR_CTR_ID 0
+#define DEFAULT_FAR_ID 0
+
+#ifndef MAX_UES
+#define MAX_UES        1024
+#endif // MAX_UES
+#define MAX_INTERFACES 128
+
+#define MAX_UPLINK_PDRS   MAX_UES
+#define MAX_DOWNLINK_PDRS MAX_UES
+#define MAX_PDR_COUNTERS  2 * MAX_UES
+#define MAX_FARS          2 * MAX_UES
+
+
+
+control SpgwIngress(inout parsed_headers_t hdr,
+                    inout fabric_metadata_t fabric_md,
+                    inout standard_metadata_t standard_metadata) {
+
+
+
+
+    //=============================//
+    //===== Interface Tables ======//
+    //=============================//
+
+    action set_source_iface(spgw_interface_t src_iface, direction_t direction,
+                            bit<1> skip_spgw) {
+        // Interface type can be access, core, n6_lan, etc (see InterfaceType enum)
+        // If interface is from the control plane, direction can be either up or down
+        fabric_md.spgw.src_iface = src_iface;
+        fabric_md.spgw.direction = direction;
+        fabric_md.spgw.skip_spgw = (_BOOL)skip_spgw;
+    }
+    // TODO: check also that gtpu.msgtype == GTP_GPDU... somewhere
+    table interface_lookup {
+        key = {
+            hdr.ipv4.dst_addr  : lpm    @name("ipv4_dst_addr");  // outermost header
+            hdr.gtpu.isValid() : exact  @name("gtpu_is_valid");
+        }
+        actions = {
+            set_source_iface;
+        }
+        const default_action = set_source_iface(SPGW_IFACE_UNKNOWN, SPGW_DIR_UNKNOWN, 1);
+        size = MAX_INTERFACES;
+    }
+
+
+    //=============================//
+    //===== PDR Tables ======//
+    //=============================//
+
+    action set_pdr_attributes(pdr_ctr_id_t ctr_id,
+                              far_id_t far_id,
+                              bit<1> needs_gtpu_decap) {
+        fabric_md.spgw.pdr_hit = _TRUE;
+        fabric_md.spgw.ctr_id = ctr_id;
+        fabric_md.spgw.far_id = far_id;
+        fabric_md.spgw.needs_gtpu_decap = (_BOOL)needs_gtpu_decap;
+    }
+
+    // These two tables scale well and cover the average case PDR
+    table downlink_pdr_lookup {
+        key = {
+            // only available ipv4 header
+            hdr.ipv4.dst_addr : exact @name("ue_addr");
+        }
+        actions = {
+            set_pdr_attributes;
+        }
+        const default_action = set_pdr_attributes(DEFAULT_PDR_CTR_ID, DEFAULT_FAR_ID, 0);
+        size = MAX_DOWNLINK_PDRS;
+    }
+    table uplink_pdr_lookup {
+        key = {
+            // tunnel_dst_addr will be static for Q2 target. Can remove if need more scaling
+            hdr.ipv4.dst_addr           : exact @name("tunnel_ipv4_dst");
+            hdr.gtpu.teid               : exact @name("teid");
+            hdr.inner_ipv4.src_addr     : exact @name("ue_addr");
+        }
+        actions = {
+            set_pdr_attributes;
+        }
+        const default_action = set_pdr_attributes(DEFAULT_PDR_CTR_ID, DEFAULT_FAR_ID, 0);
+        size = MAX_UPLINK_PDRS;
+    }
+    // This table scales poorly and covers uncommon PDRs
+    table flexible_pdr_lookup {
+        key = {
+            fabric_md.spgw.src_iface    : ternary @name("src_iface");
+            // GTPU
+            hdr.gtpu.isValid()          : ternary @name("gtpu_is_valid");
+            hdr.gtpu.teid               : ternary @name("teid");
+            // SDF
+            // outer 5-tuple
+            hdr.ipv4.src_addr           : ternary @name("ipv4_src");
+            hdr.ipv4.dst_addr           : ternary @name("ipv4_dst");
+            hdr.ipv4.protocol           : ternary @name("ip_proto");
+            fabric_md.l4_sport          : ternary @name("l4_sport");
+            fabric_md.l4_dport          : ternary @name("l4_dport");
+            // inner 5-tuple
+            hdr.inner_ipv4.src_addr     : ternary @name("inner_ipv4_src");
+            hdr.inner_ipv4.dst_addr     : ternary @name("inner_ipv4_dst");
+            hdr.inner_ipv4.protocol     : ternary @name("inner_ip_proto");
+            fabric_md.inner_l4_sport    : ternary @name("inner_l4_sport");
+            fabric_md.inner_l4_dport    : ternary @name("inner_l4_dport");
+        }
+        actions = {
+            set_pdr_attributes;
+        }
+        const default_action = set_pdr_attributes(DEFAULT_PDR_CTR_ID, DEFAULT_FAR_ID, 0);
+    }
+
+    //=============================//
+    //===== FAR Tables ======//
+    //=============================//
+
+    action load_normal_far_attributes(bit<1> drop,
+                                      bit<1> notify_cp) {
+        // general far attributes
+        fabric_md.spgw.far_dropped  = (_BOOL)drop;
+        fabric_md.spgw.notify_spgwc = (_BOOL)notify_cp;
+    }
+    action load_tunnel_far_attributes(bit<1>      drop,
+                                      bit<1>      notify_cp,
+                                      bit<16>   tunnel_src_port,
+                                      bit<32>   tunnel_src_addr,
+                                      bit<32>   tunnel_dst_addr,
+                                      teid_t    teid) {
+        // general far attributes
+        fabric_md.spgw.far_dropped  = (_BOOL)drop;
+        fabric_md.spgw.notify_spgwc = (_BOOL)notify_cp;
+        // GTP tunnel attributes
+        fabric_md.spgw.needs_gtpu_encap = _TRUE;
+        fabric_md.spgw.teid = teid;
+        fabric_md.spgw.tunnel_src_port = tunnel_src_port;
+        fabric_md.spgw.tunnel_src_addr = tunnel_src_addr;
+        fabric_md.spgw.tunnel_dst_addr = tunnel_dst_addr;
+        // update metadata for correct routing/hashing
+        fabric_md.ipv4_src_addr = tunnel_src_addr;
+        fabric_md.ipv4_dst_addr = tunnel_dst_addr;
+        fabric_md.l4_sport = tunnel_src_port;
+        fabric_md.l4_dport = UDP_PORT_GTPU;
+    }
+
+    table far_lookup {
+        key = {
+            fabric_md.spgw.far_id : exact @name("far_id");
+        }
+        actions = {
+            load_normal_far_attributes;
+            load_tunnel_far_attributes;
+        }
+        // default is drop and don't notify CP
+        const default_action = load_normal_far_attributes(1, 1);
+        size = MAX_FARS;
+    }
+
+    //=============================//
+    //===== Misc Things ======//
+    //=============================//
+    
+    counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
+
+
+    @hidden
+    action decap_inner_common() {
+        // Correct parser-set metadata to use the inner header values
+        fabric_md.ip_eth_type   = ETHERTYPE_IPV4;
+        fabric_md.ip_proto      = hdr.inner_ipv4.protocol;
+        fabric_md.ipv4_src_addr = hdr.inner_ipv4.src_addr;
+        fabric_md.ipv4_dst_addr = hdr.inner_ipv4.dst_addr;
+        fabric_md.l4_sport      = fabric_md.inner_l4_sport;
+        fabric_md.l4_dport      = fabric_md.inner_l4_dport;
+        // Move GTPU and inner L3 headers out
+        hdr.ipv4 = hdr.inner_ipv4;
+        hdr.inner_ipv4.setInvalid();
+        hdr.gtpu.setInvalid();
+    }
+    action decap_inner_tcp() {
+        decap_inner_common();
+        hdr.udp.setInvalid();
+        hdr.tcp = hdr.inner_tcp;
+        hdr.inner_tcp.setInvalid();
+    }
+    action decap_inner_udp() {
+        decap_inner_common();
+        hdr.udp = hdr.inner_udp;
+        hdr.inner_udp.setInvalid();
+    }
+    action decap_inner_icmp() {
+        decap_inner_common();
+        hdr.udp.setInvalid();
+        hdr.icmp = hdr.inner_icmp;
+        hdr.inner_icmp.setInvalid();
+    }
+    action decap_inner_unknown() {
+        decap_inner_common();
+        hdr.udp.setInvalid();
+    }
+    @hidden
+    table decap_gtpu {
+        key = {
+            hdr.inner_tcp.isValid()     : exact;
+            hdr.inner_udp.isValid()     : exact;
+            hdr.inner_icmp.isValid()    : exact;
+        }
+        actions = {
+            decap_inner_tcp;
+            decap_inner_udp;
+            decap_inner_icmp;
+            decap_inner_unknown;
+        }
+        const default_action = decap_inner_unknown;
+        const entries = {
+            (true,  false, false) : decap_inner_tcp();
+            (false, true,  false) : decap_inner_udp();
+            (false, false, true)  : decap_inner_icmp();
+        }
+    }
+
+
+    //=============================//
+    //===== Apply Block ======//
+    //=============================//
+    apply {
+
+        // Interfaces
+        interface_lookup.apply();
+
+        // If interface table missed, or the interface skips PDRs/FARs (TODO: is that a thing?)
+        if (fabric_md.spgw.skip_spgw == _TRUE) return;
+
+        // PDRs
+        // Currently only best-case PDR tables to make v1model-to-tofino compiler happy
+        if (hdr.gtpu.isValid()) {
+            uplink_pdr_lookup.apply();
+        } else {
+            downlink_pdr_lookup.apply();
+        }
+        // Inefficient PDR table if efficient tables missed
+        // Removed to make v1model-to-tofino compiler happy. Not removed in TNA port
+        //if (fabric_md.spgw.pdr_hit == _FALSE) {
+        //    flexible_pdr_lookup.apply();
+        //}
+        pdr_counter.count(fabric_md.spgw.ctr_id);
+
+        // GTPU Decapsulate
+        if (fabric_md.spgw.needs_gtpu_decap == _TRUE) { 
+            decap_gtpu.apply();
+        }
+
+        // FARs
+        // Load FAR info
+        far_lookup.apply();
+
+        if (fabric_md.spgw.notify_spgwc == _TRUE) {
+            // TODO: cpu clone action here
+        }
+        if (fabric_md.spgw.far_dropped == _TRUE) {
+            // Do dropping in the same way as fabric's filtering.p4, so we can traverse
+            // the ACL table, which is good for cases like DHCP.
+            fabric_md.skip_forwarding = _TRUE;
+            fabric_md.skip_next = _TRUE;
+        }
+
+        // Nothing to be done immediately for forwarding or encapsulation.
+        // Forwarding is done by other parts of fabric.p4, and
+        // encapsulation is done in the egress
+
+        // Needed for correct GTPU encapsulation in egress
+        fabric_md.spgw.ipv4_len = hdr.ipv4.total_len;
+    }
+}
+
+
+//====================================//
+//============== Egress ==============//
+//====================================//
+control SpgwEgress(
+        inout parsed_headers_t hdr,
+        inout fabric_metadata_t fabric_md) {
+
+    counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
+
+
+    @hidden
+    action gtpu_encap() {
+        hdr.gtpu_ipv4.setValid();
+        hdr.gtpu_ipv4.version = IP_VERSION_4;
+        hdr.gtpu_ipv4.ihl = IPV4_MIN_IHL;
+        hdr.gtpu_ipv4.dscp = 0;
+        hdr.gtpu_ipv4.ecn = 0;
+        hdr.gtpu_ipv4.total_len = hdr.ipv4.total_len
+                + (IPV4_HDR_SIZE + UDP_HDR_SIZE + GTP_HDR_SIZE);
+        hdr.gtpu_ipv4.identification = 0x1513; /* From NGIC. TODO: Needs to be dynamic */
+        hdr.gtpu_ipv4.flags = 0;
+        hdr.gtpu_ipv4.frag_offset = 0;
+        hdr.gtpu_ipv4.ttl = DEFAULT_IPV4_TTL;
+        hdr.gtpu_ipv4.protocol = PROTO_UDP;
+        hdr.gtpu_ipv4.src_addr = fabric_md.spgw.tunnel_src_addr;
+        hdr.gtpu_ipv4.dst_addr = fabric_md.spgw.tunnel_dst_addr;
+        hdr.gtpu_ipv4.hdr_checksum = 0; // Updated later
+
+        hdr.gtpu_udp.setValid();
+        hdr.gtpu_udp.sport = fabric_md.spgw.tunnel_src_port; 
+        hdr.gtpu_udp.dport = UDP_PORT_GTPU;
+        hdr.gtpu_udp.len = fabric_md.spgw.ipv4_len
+                + (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 = GTPU_VERSION;
+        hdr.outer_gtpu.pt = GTP_PROTOCOL_TYPE_GTP;
+        hdr.outer_gtpu.spare = 0;
+        hdr.outer_gtpu.ex_flag = 0;
+        hdr.outer_gtpu.seq_flag = 0;
+        hdr.outer_gtpu.npdu_flag = 0;
+        hdr.outer_gtpu.msgtype = GTP_GPDU;
+        hdr.outer_gtpu.msglen = fabric_md.spgw.ipv4_len;
+        hdr.outer_gtpu.teid = fabric_md.spgw.teid;
+    }
+
+    apply {
+        if (fabric_md.spgw.skip_spgw == _TRUE) return;
+        pdr_counter.count(fabric_md.spgw.ctr_id);
+
+        if (fabric_md.spgw.needs_gtpu_encap == _TRUE) {
+            gtpu_encap();
+        }
+    }
+}
+
+
+control update_gtpu_checksum(
+        inout ipv4_t gtpu_ipv4,
+        inout udp_t  gtpu_udp,
+        in    gtpu_t gtpu,
+        in    ipv4_t ipv4,
+        in    udp_t  udp
+    ) {
+    apply {
+        // Compute outer IPv4 checksum.
+        update_checksum(gtpu_ipv4.isValid(),
+            {
+                gtpu_ipv4.version,
+                gtpu_ipv4.ihl,
+                gtpu_ipv4.dscp,
+                gtpu_ipv4.ecn,
+                gtpu_ipv4.total_len,
+                gtpu_ipv4.identification,
+                gtpu_ipv4.flags,
+                gtpu_ipv4.frag_offset,
+                gtpu_ipv4.ttl,
+                gtpu_ipv4.protocol,
+                gtpu_ipv4.src_addr,
+                gtpu_ipv4.dst_addr
+            },
+            gtpu_ipv4.hdr_checksum,
+            HashAlgorithm.csum16
+        );
+
+#ifdef WITH_SPGW_UDP_CSUM_UPDATE
+        // Compute outer UDP checksum.
+        update_checksum_with_payload(gtpu_udp.isValid(),
+            {
+                gtpu_ipv4.src_addr,
+                gtpu_ipv4.dst_addr,
+                8w0,
+                gtpu_ipv4.protocol,
+                gtpu_udp.len,
+                gtpu_udp.sport,
+                gtpu_udp.dport,
+                gtpu_udp.len,
+                gtpu,
+                ipv4,
+                // FIXME: we are assuming only UDP for downlink packets
+                // How to conditionally switch between UDP/TCP/ICMP?
+                udp
+            },
+            gtpu_udp.checksum,
+            HashAlgorithm.csum16
+        );
+#endif // WITH_SPGW_UDP_CSUM_UPDATE
+    }
+}
+
+#endif
diff --git a/pipelines/fabric/impl/src/main/resources/include/define.p4 b/pipelines/fabric/impl/src/main/resources/include/define.p4
index 8501516..bfb7b44 100644
--- a/pipelines/fabric/impl/src/main/resources/include/define.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/define.p4
@@ -97,17 +97,28 @@
 
 // SPGW types
 typedef bit<2> direction_t;
+typedef bit<8> spgw_interface_t;
 typedef bit pcc_gate_status_t;
 typedef bit<32> sdf_rule_id_t;
 typedef bit<32> pcc_rule_id_t;
 typedef bit<32> far_id_t;
-typedef bit<32> ctr_id_t;
+typedef bit<32> pdr_ctr_id_t;
 typedef bit<32> teid_t;
 
-// spgw.p4 expects uplink packets with IP dst on this subnet
-// 140.0.0.0/8
-const ipv4_addr_t S1U_SGW_PREFIX = 2348810240;
+const spgw_interface_t SPGW_IFACE_UNKNOWN = 8w0;
+const spgw_interface_t SPGW_IFACE_ACCESS = 8w1;
+const spgw_interface_t SPGW_IFACE_CORE = 8w2;
+const direction_t SPGW_DIR_UNKNOWN = 2w0;
+const direction_t SPGW_DIR_UPLINK = 2w1;
+const direction_t SPGW_DIR_DOWNLINK = 2w2;
+
+#ifndef S1U_SGW_PREFIX
+// By default spgw.p4 expects uplink packets with IP dst matching 140.0.0.0/8
+// FIXME: refactor pipeline to remove dependency on this value or allow setting it at runtime
+//  (e.g. via parser value sets)
+#define S1U_SGW_PREFIX (8w140++8w0++8w0++8w0)
 #define S1U_SGW_PREFIX_LEN 8
+#endif
 
 const bit<16> ETHERTYPE_QINQ = 0x88A8;
 const bit<16> ETHERTYPE_QINQ_NON_STD = 0x9100;
@@ -144,13 +155,7 @@
 const bit<8> DEFAULT_MPLS_TTL = 64;
 const bit<8> DEFAULT_IPV4_TTL = 64;
 
-const sdf_rule_id_t DEFAULT_SDF_RULE_ID = 0;
-const pcc_rule_id_t DEFAULT_PCC_RULE_ID = 0;
-const direction_t SPGW_DIR_UNKNOWN = 2w0;
-const direction_t SPGW_DIR_UPLINK = 2w1;
-const direction_t SPGW_DIR_DOWNLINK = 2w2;
-const pcc_gate_status_t PCC_GATE_OPEN = 1w0;
-const pcc_gate_status_t PCC_GATE_CLOSED = 1w1;
+
 
 /* indicate INT at LSB of DSCP */
 const bit<6> INT_DSCP = 0x1;
diff --git a/pipelines/fabric/impl/src/main/resources/include/header.p4 b/pipelines/fabric/impl/src/main/resources/include/header.p4
index decad94..17b36fe 100644
--- a/pipelines/fabric/impl/src/main/resources/include/header.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/header.p4
@@ -142,14 +142,18 @@
     direction_t       direction;
     bit<16>           ipv4_len;
     teid_t            teid;
+    bit<16>           tunnel_src_port;
     bit<32>           tunnel_src_addr;
     bit<32>           tunnel_dst_addr;
-    ctr_id_t          ctr_id;
+    pdr_ctr_id_t      ctr_id;
     far_id_t          far_id;
+    spgw_interface_t  src_iface;
+    _BOOL             skip_spgw;
     _BOOL             pdr_hit;
     _BOOL             far_dropped;
-    _BOOL             notify_cp;
-    _BOOL             outer_header_creation;
+    _BOOL             notify_spgwc;
+    _BOOL             needs_gtpu_encap;
+    _BOOL             needs_gtpu_decap;
 }
 #endif // WITH_SPGW
 
@@ -196,6 +200,8 @@
     bit<32>       ipv4_src_addr;
     bit<32>       ipv4_dst_addr;
 #ifdef WITH_SPGW
+    bit<16>       inner_l4_sport;
+    bit<16>       inner_l4_dport;
     spgw_meta_t   spgw;
 #endif // WITH_SPGW
 #ifdef WITH_BNG
@@ -220,9 +226,12 @@
 #ifdef WITH_SPGW
     ipv4_t gtpu_ipv4;
     udp_t gtpu_udp;
+    gtpu_t outer_gtpu;
     gtpu_t gtpu;
     ipv4_t inner_ipv4;
     udp_t inner_udp;
+    tcp_t inner_tcp;
+    icmp_t inner_icmp;
 #endif // WITH_SPGW
     ipv4_t ipv4;
 #ifdef WITH_IPV6
diff --git a/pipelines/fabric/impl/src/main/resources/include/parser.p4 b/pipelines/fabric/impl/src/main/resources/include/parser.p4
index efbb74a..de0a76b 100644
--- a/pipelines/fabric/impl/src/main/resources/include/parser.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/parser.p4
@@ -185,17 +185,6 @@
 
 #ifdef WITH_SPGW
     state parse_gtpu {
-        transition select(hdr.ipv4.dst_addr[31:32-S1U_SGW_PREFIX_LEN]) {
-            // Avoid parsing GTP and inner headers if we know this GTP packet
-            // is not to be processed by this switch.
-            // FIXME: use parser value sets when support is ready in ONOS.
-            // To set the S1U_SGW_PREFIX value at runtime.
-            S1U_SGW_PREFIX[31:32-S1U_SGW_PREFIX_LEN]: do_parse_gtpu;
-            default: accept;
-        }
-    }
-
-    state do_parse_gtpu {
         packet.extract(hdr.gtpu);
         transition parse_inner_ipv4;
     }
@@ -213,14 +202,26 @@
 
     state parse_inner_udp {
         packet.extract(hdr.inner_udp);
-        fabric_metadata.l4_sport = hdr.inner_udp.sport;
-        fabric_metadata.l4_dport = hdr.inner_udp.dport;
+        fabric_metadata.inner_l4_sport = hdr.inner_udp.sport;
+        fabric_metadata.inner_l4_dport = hdr.inner_udp.dport;
 #ifdef WITH_INT
         transition parse_int;
 #else
         transition accept;
 #endif // WITH_INT
     }
+
+        state parse_inner_tcp {
+        packet.extract(hdr.inner_tcp);
+        fabric_metadata.inner_l4_sport = hdr.inner_tcp.sport;
+        fabric_metadata.inner_l4_dport = hdr.inner_tcp.dport;
+        transition accept;
+    }
+
+        state parse_inner_icmp {
+        packet.extract(hdr.inner_icmp);
+        transition accept;
+    }
 #endif // WITH_SPGW
 
 #ifdef WITH_INT
@@ -288,7 +289,7 @@
 #ifdef WITH_SPGW
         packet.emit(hdr.gtpu_ipv4);
         packet.emit(hdr.gtpu_udp);
-        packet.emit(hdr.gtpu);
+        packet.emit(hdr.outer_gtpu);
 #endif // WITH_SPGW
         packet.emit(hdr.ipv4);
 #ifdef WITH_IPV6
@@ -297,6 +298,14 @@
         packet.emit(hdr.tcp);
         packet.emit(hdr.udp);
         packet.emit(hdr.icmp);
+#ifdef WITH_SPGW
+        // if we parsed a GTPU packet but did not decap it
+        packet.emit(hdr.gtpu);
+        packet.emit(hdr.inner_ipv4);
+        packet.emit(hdr.inner_tcp);
+        packet.emit(hdr.inner_udp);
+        packet.emit(hdr.inner_icmp);
+#endif // WITH_SPGW
 #ifdef WITH_INT
         packet.emit(hdr.intl4_shim);
         packet.emit(hdr.int_header);
diff --git a/pipelines/fabric/impl/src/main/resources/include/spgw.p4 b/pipelines/fabric/impl/src/main/resources/include/spgw.p4
deleted file mode 100644
index 0aa78c0..0000000
--- a/pipelines/fabric/impl/src/main/resources/include/spgw.p4
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright 2017-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 __SPGW__
-#define __SPGW__
-
-#define MAX_PDR_COUNTERS 1024
-#define DEFAULT_PDR_CTR_ID 0
-#define DEFAULT_FAR_ID 0
-
-control spgw_normalizer(
-        in    bool   is_gtpu_encapped,
-        out   ipv4_t gtpu_ipv4,
-        out   udp_t  gtpu_udp,
-        inout ipv4_t ipv4,
-        inout udp_t  udp,
-        in    ipv4_t inner_ipv4,
-        in    udp_t  inner_udp
-    ) {
-    apply {
-        if (! is_gtpu_encapped) return;
-        gtpu_ipv4 = ipv4;
-        ipv4 = inner_ipv4;
-        gtpu_udp = udp;
-        if (inner_udp.isValid()) {
-            udp = inner_udp;
-        } else {
-            udp.setInvalid();
-        }
-    }
-}
-
-control spgw_ingress(
-        inout ipv4_t              gtpu_ipv4,
-        inout udp_t               gtpu_udp,
-        inout gtpu_t              gtpu,
-        inout ipv4_t              ipv4,
-        inout udp_t               udp,
-        inout fabric_metadata_t   fabric_meta,
-        inout standard_metadata_t standard_metadata
-    ) {
-
-    counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
-
-    @hidden
-    action gtpu_decap() {
-        // grab information from the tunnel that we'll need later
-        fabric_meta.spgw.teid = gtpu.teid;
-        fabric_meta.spgw.tunnel_dst_addr = gtpu_ipv4.dst_addr;
-        // update metadata src and dst addresses with the inner packet 
-        fabric_meta.ipv4_src_addr = ipv4.src_addr;
-        fabric_meta.ipv4_dst_addr = ipv4.dst_addr;
-        // decap
-        gtpu_ipv4.setInvalid();
-        gtpu_udp.setInvalid();
-        gtpu.setInvalid();
-
-    }
-
-    table downlink_filter_table {
-        key = {
-            // UE addr pool for downlink
-            ipv4.dst_addr : lpm @name("ipv4_prefix");
-        }
-        actions = {
-            nop();
-        }
-        const default_action = nop();
-    }
-
-    table uplink_filter_table {
-        key = {
-            // IP addresses of the S1U interfaces of this SPGW-U instance (when uplink)
-            gtpu_ipv4.dst_addr : exact @name("gtp_ipv4_dst");
-        }
-        actions = {
-            nop();
-        }
-        const default_action = nop();
-    }
-
-    action set_pdr_attributes(ctr_id_t ctr_id,
-                              far_id_t far_id) {
-        fabric_meta.spgw.pdr_hit = _TRUE;
-        fabric_meta.spgw.ctr_id = ctr_id;
-        fabric_meta.spgw.far_id = far_id;
-    }
-
-    // These two tables scale well and cover the average case PDR
-    table downlink_pdr_lookup {
-        key = {
-            ipv4.dst_addr : exact @name("ue_addr");
-        }
-        actions = {
-            set_pdr_attributes;
-        }
-    }
-    table uplink_pdr_lookup {
-        key = {
-            // tunnel_dst_addr will be static for Q2 target. Can remove if need more scaling
-            fabric_meta.spgw.tunnel_dst_addr  : exact @name("tunnel_ipv4_dst");
-            fabric_meta.spgw.teid          : exact @name("teid");
-            ipv4.src_addr                  : exact @name("ue_addr");
-        }
-        actions = {
-            set_pdr_attributes;
-        }
-    }
-    // This table scales poorly and covers uncommon PDRs
-    table flexible_pdr_lookup {
-        key = {
-            // Direction. Eventually change to interface
-            fabric_meta.spgw.direction    : ternary @name("spgw_direction");
-            // F-TEID
-            fabric_meta.spgw.tunnel_dst_addr : ternary @name("tunnel_ipv4_dst");
-            fabric_meta.spgw.teid            : ternary @name("teid");
-            // SDF (5-tuple)
-            ipv4.src_addr                 : ternary @name("ipv4_src");
-            ipv4.dst_addr                 : ternary @name("ipv4_dst");
-            ipv4.protocol                 : ternary @name("ip_proto");
-            fabric_meta.l4_sport          : ternary @name("l4_sport");
-            fabric_meta.l4_dport          : ternary @name("l4_dport");
-        }
-        actions = {
-            set_pdr_attributes;
-        }
-        const default_action = set_pdr_attributes(DEFAULT_PDR_CTR_ID, DEFAULT_FAR_ID);
-    }
-
-    action load_normal_far_attributes(bit<1> drop,
-                                      bit<1> notify_cp) {
-        // general far attributes
-        fabric_meta.spgw.far_dropped = (_BOOL)drop;
-        fabric_meta.spgw.notify_cp   = (_BOOL)notify_cp;
-    }
-    action load_tunnel_far_attributes(bit<1>         drop,
-                                      bit<1>         notify_cp,
-                                      ipv4_addr_t    tunnel_src_addr,
-                                      ipv4_addr_t    tunnel_dst_addr,
-                                      teid_t         teid) {
-        // general far attributes
-        fabric_meta.spgw.far_dropped = (_BOOL)drop;
-        fabric_meta.spgw.notify_cp = (_BOOL)notify_cp;
-        // GTP tunnel attributes
-        fabric_meta.spgw.outer_header_creation = _TRUE;
-        fabric_meta.spgw.teid = teid;
-        fabric_meta.spgw.tunnel_src_addr = tunnel_src_addr;
-        fabric_meta.spgw.tunnel_dst_addr = tunnel_dst_addr;
-        // update metadata IP addresses for correct routing/hashing
-        fabric_meta.ipv4_src_addr = tunnel_src_addr;
-        fabric_meta.ipv4_dst_addr = tunnel_dst_addr;
-    }
-
-
-    table far_lookup {
-        key = {
-            fabric_meta.spgw.far_id : exact @name("far_id");
-        }
-        actions = {
-            load_normal_far_attributes;
-            load_tunnel_far_attributes;
-        }
-        // default is drop and don't notify CP
-        const default_action = load_normal_far_attributes(1w1, 1w0);
-    }
-
-    apply {
-        if (gtpu.isValid()) {
-            // If here, pkt has outer IP dst on
-            // S1U_SGW_PREFIX/S1U_SGW_PREFIX_LEN subnet.
-            // TODO: check also that gtpu.msgtype == GTP_GPDU
-            if (!uplink_filter_table.apply().hit) {
-                // Should this be changed to a forwarding/next skip instead of a drop?
-                mark_to_drop(standard_metadata);
-            }
-            fabric_meta.spgw.direction = SPGW_DIR_UPLINK;
-            gtpu_decap();
-        } else if (downlink_filter_table.apply().hit) {
-            fabric_meta.spgw.direction = SPGW_DIR_DOWNLINK;
-        } else {
-            fabric_meta.spgw.direction = SPGW_DIR_UNKNOWN;
-            // No SPGW processing needed.
-            return;
-        }
-
-        // Try the efficient PDR tables first (This PDR partitioning only works
-        // if the PDRs do not overlap. Will need fixing later.)
-        if (fabric_meta.spgw.direction == SPGW_DIR_UPLINK) {
-            uplink_pdr_lookup.apply();
-        } else if (fabric_meta.spgw.direction == SPGW_DIR_DOWNLINK) {
-            downlink_pdr_lookup.apply();
-        } else { // SPGW_DIR_UNKNOWN
-            return;
-        }
-        // If those fail to find a match, use the wildcard tables
-        if (fabric_meta.spgw.pdr_hit == _FALSE) {
-            flexible_pdr_lookup.apply();
-        }
-
-        pdr_counter.count(fabric_meta.spgw.ctr_id);
-        // Load FAR info
-        far_lookup.apply();
-
-        if (fabric_meta.spgw.notify_cp == _TRUE) {
-            // TODO: cpu clone session here
-        }
-        if (fabric_meta.spgw.far_dropped == _TRUE) {
-            // Do dropping in the same way as fabric's filtering.p4, so we can traverse
-            // the ACL table, which is good for cases like DHCP.
-            fabric_meta.skip_forwarding = _TRUE;
-            fabric_meta.skip_next = _TRUE;
-        }
-
-        // Nothing to be done immediately for forwarding or encapsulation.
-        // Forwarding is done by other parts of fabric.p4, and
-        // encapsulation is done in the egress
-
-        // Needed for correct GTPU encapsulation in egress
-        fabric_meta.spgw.ipv4_len = ipv4.total_len;
-    }
-}
-
-
-control spgw_egress(
-        in    ipv4_t              ipv4,
-        inout ipv4_t              gtpu_ipv4,
-        inout udp_t               gtpu_udp,
-        inout gtpu_t              gtpu,
-        in    fabric_metadata_t   fabric_meta,
-        in    standard_metadata_t std_meta
-    ) {
-
-    counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
-
-
-    @hidden
-    action gtpu_encap() {
-        gtpu_ipv4.setValid();
-        gtpu_ipv4.version = IP_VERSION_4;
-        gtpu_ipv4.ihl = IPV4_MIN_IHL;
-        gtpu_ipv4.dscp = 0;
-        gtpu_ipv4.ecn = 0;
-        gtpu_ipv4.total_len = ipv4.total_len
-                + (IPV4_HDR_SIZE + UDP_HDR_SIZE + GTP_HDR_SIZE);
-        gtpu_ipv4.identification = 0x1513; /* From NGIC. TODO: Needs to be dynamic */
-        gtpu_ipv4.flags = 0;
-        gtpu_ipv4.frag_offset = 0;
-        gtpu_ipv4.ttl = DEFAULT_IPV4_TTL;
-        gtpu_ipv4.protocol = PROTO_UDP;
-        gtpu_ipv4.src_addr = fabric_meta.spgw.tunnel_src_addr;
-        gtpu_ipv4.dst_addr = fabric_meta.spgw.tunnel_dst_addr;
-        gtpu_ipv4.hdr_checksum = 0; // Updated later
-
-        gtpu_udp.setValid();
-        gtpu_udp.sport = UDP_PORT_GTPU; // TODO: make this dynamic per 3GPP specs
-        gtpu_udp.dport = UDP_PORT_GTPU;
-        gtpu_udp.len = fabric_meta.spgw.ipv4_len
-                + (UDP_HDR_SIZE + GTP_HDR_SIZE);
-        gtpu_udp.checksum = 0; // Updated later, if WITH_SPGW_UDP_CSUM_UPDATE
-
-
-        gtpu.setValid();
-        gtpu.version = GTPU_VERSION;
-        gtpu.pt = GTP_PROTOCOL_TYPE_GTP;
-        gtpu.spare = 0;
-        gtpu.ex_flag = 0;
-        gtpu.seq_flag = 0;
-        gtpu.npdu_flag = 0;
-        gtpu.msgtype = GTP_GPDU;
-        gtpu.msglen = fabric_meta.spgw.ipv4_len;
-        gtpu.teid = fabric_meta.spgw.teid;
-    }
-
-    apply {
-        pdr_counter.count(fabric_meta.spgw.ctr_id);
-
-        if (fabric_meta.spgw.outer_header_creation == _TRUE) {
-            gtpu_encap();
-        }
-    }
-}
-
-
-control update_gtpu_checksum(
-        inout ipv4_t gtpu_ipv4,
-        inout udp_t  gtpu_udp,
-        in    gtpu_t gtpu,
-        in    ipv4_t ipv4,
-        in    udp_t  udp
-    ) {
-    apply {
-        // Compute outer IPv4 checksum.
-        update_checksum(gtpu_ipv4.isValid(),
-            {
-                gtpu_ipv4.version,
-                gtpu_ipv4.ihl,
-                gtpu_ipv4.dscp,
-                gtpu_ipv4.ecn,
-                gtpu_ipv4.total_len,
-                gtpu_ipv4.identification,
-                gtpu_ipv4.flags,
-                gtpu_ipv4.frag_offset,
-                gtpu_ipv4.ttl,
-                gtpu_ipv4.protocol,
-                gtpu_ipv4.src_addr,
-                gtpu_ipv4.dst_addr
-            },
-            gtpu_ipv4.hdr_checksum,
-            HashAlgorithm.csum16
-        );
-
-#ifdef WITH_SPGW_UDP_CSUM_UPDATE
-        // Compute outer UDP checksum.
-        update_checksum_with_payload(gtpu_udp.isValid(),
-            {
-                gtpu_ipv4.src_addr,
-                gtpu_ipv4.dst_addr,
-                8w0,
-                gtpu_ipv4.protocol,
-                gtpu_udp.len,
-                gtpu_udp.sport,
-                gtpu_udp.dport,
-                gtpu_udp.len,
-                gtpu,
-                ipv4,
-                // FIXME: we are assuming only UDP for downlink packets
-                // How to conditionally switch between UDP/TCP/ICMP?
-                udp
-            },
-            gtpu_udp.checksum,
-            HashAlgorithm.csum16
-        );
-#endif // WITH_SPGW_UDP_CSUM_UPDATE
-    }
-}
-
-#endif