Rewrite the SPGW P4 pipeline.

This commit makes two changes to the fabric.p4 source files:
1. Routing is now done on a metadata field instead of the ipv4 header.
Routing on the ipv4 header is incorrect if the packet is encapsulated
with an outer IPV4 header. The metadata field is updated each time the
ipv4 destination changes, or an outer header is added or removed. These
changes require no control plane modifications due to @name annotations.
2. The spgw control blocks have been rewritten to support the logical UP4
pipeline. Its location in the pipeline is unchanged, and its
interactions with other control blocks is unchanged.

These changes compile for both bmv2 and tofino, and There is currently a
PR for the fabric-p4test repo which updates the SPGW PTF tests to
account for these changes.

Change-Id: I80ccf30e136a2bb24a83029e22413af351e6eed6
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/forwarding.p4 b/pipelines/fabric/impl/src/main/resources/include/control/forwarding.p4
index 9b1e523..1a4e3f2 100644
--- a/pipelines/fabric/impl/src/main/resources/include/control/forwarding.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/control/forwarding.p4
@@ -107,7 +107,7 @@
     #endif
     table routing_v4 {
         key = {
-            hdr.ipv4.dst_addr: lpm @name("ipv4_dst");
+            fabric_metadata.ipv4_dst_addr: lpm @name("ipv4_dst");
         }
         actions = {
             set_next_id_routing_v4;
diff --git a/pipelines/fabric/impl/src/main/resources/include/control/next.p4 b/pipelines/fabric/impl/src/main/resources/include/control/next.p4
index 577307e..58b97b1 100644
--- a/pipelines/fabric/impl/src/main/resources/include/control/next.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/control/next.p4
@@ -202,8 +202,8 @@
     table hashed {
         key = {
             fabric_metadata.next_id: exact @name("next_id");
-            hdr.ipv4.dst_addr: selector;
-            hdr.ipv4.src_addr: selector;
+            fabric_metadata.ipv4_src_addr: selector;
+            fabric_metadata.ipv4_dst_addr: selector;
             fabric_metadata.ip_proto: selector;
             fabric_metadata.l4_sport: selector;
             fabric_metadata.l4_dport: selector;
diff --git a/pipelines/fabric/impl/src/main/resources/include/define.p4 b/pipelines/fabric/impl/src/main/resources/include/define.p4
index 92aa182..8501516 100644
--- a/pipelines/fabric/impl/src/main/resources/include/define.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/define.p4
@@ -100,6 +100,9 @@
 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> teid_t;
 
 // spgw.p4 expects uplink packets with IP dst on this subnet
 // 140.0.0.0/8
diff --git a/pipelines/fabric/impl/src/main/resources/include/header.p4 b/pipelines/fabric/impl/src/main/resources/include/header.p4
index bea9619..decad94 100644
--- a/pipelines/fabric/impl/src/main/resources/include/header.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/header.p4
@@ -135,22 +135,21 @@
     bit<1>  npdu_flag;  /* n-pdn number present ? */
     bit<8>  msgtype;    /* message type */
     bit<16> msglen;     /* message length */
-    bit<32> teid;       /* tunnel endpoint id */
+    teid_t  teid;       /* tunnel endpoint id */
 }
 
 struct spgw_meta_t {
     direction_t       direction;
     bit<16>           ipv4_len;
-    bit<32>           teid;
-    bit<32>           s1u_enb_addr;
-    bit<32>           s1u_sgw_addr;
-#ifdef WITH_SPGW_PCC_GATING
-    bit<16>           l4_sport;
-    bit<16>           l4_dport;
-    pcc_gate_status_t pcc_gate_status;
-    sdf_rule_id_t     sdf_rule_id;
-    pcc_rule_id_t     pcc_rule_id;
-#endif // WITH_SPGW_PCC_GATING
+    teid_t            teid;
+    bit<32>           tunnel_src_addr;
+    bit<32>           tunnel_dst_addr;
+    ctr_id_t          ctr_id;
+    far_id_t          far_id;
+    _BOOL             pdr_hit;
+    _BOOL             far_dropped;
+    _BOOL             notify_cp;
+    _BOOL             outer_header_creation;
 }
 #endif // WITH_SPGW
 
@@ -194,6 +193,8 @@
     bit<8>        ip_proto;
     bit<16>       l4_sport;
     bit<16>       l4_dport;
+    bit<32>       ipv4_src_addr;
+    bit<32>       ipv4_dst_addr;
 #ifdef WITH_SPGW
     spgw_meta_t   spgw;
 #endif // WITH_SPGW
diff --git a/pipelines/fabric/impl/src/main/resources/include/parser.p4 b/pipelines/fabric/impl/src/main/resources/include/parser.p4
index aa79179..efbb74a 100644
--- a/pipelines/fabric/impl/src/main/resources/include/parser.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/parser.p4
@@ -125,6 +125,8 @@
         packet.extract(hdr.ipv4);
         fabric_metadata.ip_proto = hdr.ipv4.protocol;
         fabric_metadata.ip_eth_type = ETHERTYPE_IPV4;
+        fabric_metadata.ipv4_src_addr = hdr.ipv4.src_addr;
+        fabric_metadata.ipv4_dst_addr = hdr.ipv4.dst_addr;
         last_ipv4_dscp = hdr.ipv4.dscp;
         //Need header verification?
         transition select(hdr.ipv4.protocol) {
@@ -316,4 +318,4 @@
     }
 }
 
-#endif
\ No newline at end of file
+#endif
diff --git a/pipelines/fabric/impl/src/main/resources/include/spgw.p4 b/pipelines/fabric/impl/src/main/resources/include/spgw.p4
index 6eb4c40..0aa78c0 100644
--- a/pipelines/fabric/impl/src/main/resources/include/spgw.p4
+++ b/pipelines/fabric/impl/src/main/resources/include/spgw.p4
@@ -17,6 +17,10 @@
 #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,
@@ -49,38 +53,35 @@
         inout standard_metadata_t standard_metadata
     ) {
 
-    direct_counter(CounterType.packets_and_bytes) ue_counter;
+    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();
+
     }
 
-    action set_dl_sess_info(bit<32> teid,
-                            bit<32> s1u_enb_addr,
-                            bit<32> s1u_sgw_addr) {
-        fabric_meta.spgw.teid = teid;
-        fabric_meta.spgw.s1u_enb_addr = s1u_enb_addr;
-        fabric_meta.spgw.s1u_sgw_addr = s1u_sgw_addr;
-        ue_counter.count();
-    }
-
-    table dl_sess_lookup {
+    table downlink_filter_table {
         key = {
-            // UE addr for downlink
-            ipv4.dst_addr : exact @name("ipv4_dst");
+            // UE addr pool for downlink
+            ipv4.dst_addr : lpm @name("ipv4_prefix");
         }
         actions = {
-            set_dl_sess_info();
-            @defaultonly nop();
+            nop();
         }
         const default_action = nop();
-        counters = ue_counter;
     }
 
-    table s1u_filter_table {
+    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");
@@ -91,66 +92,103 @@
         const default_action = nop();
     }
 
-#ifdef WITH_SPGW_PCC_GATING
-    action set_sdf_rule_id(sdf_rule_id_t id) {
-        fabric_meta.spgw.sdf_rule_id = id;
+    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;
     }
 
-    action set_pcc_rule_id(pcc_rule_id_t id) {
-        fabric_meta.spgw.pcc_rule_id = id;
-    }
-
-    action set_pcc_info(pcc_gate_status_t gate_status) {
-        fabric_meta.spgw.pcc_gate_status = gate_status;
-    }
-
-    table sdf_rule_lookup {
+    // These two tables scale well and cover the average case PDR
+    table downlink_pdr_lookup {
         key = {
-            fabric_meta.spgw.direction   : exact @name("spgw_direction");
-            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");
+            ipv4.dst_addr : exact @name("ue_addr");
         }
         actions = {
-            set_sdf_rule_id();
+            set_pdr_attributes;
         }
-        const default_action = set_sdf_rule_id(DEFAULT_SDF_RULE_ID);
     }
-
-    table pcc_rule_lookup {
+    table uplink_pdr_lookup {
         key = {
-            fabric_meta.spgw.sdf_rule_id : exact @name("sdf_rule_id");
+            // 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_pcc_rule_id();
+            set_pdr_attributes;
         }
-        const default_action = set_pcc_rule_id(DEFAULT_PCC_RULE_ID);
     }
-
-    table pcc_info_lookup {
+    // This table scales poorly and covers uncommon PDRs
+    table flexible_pdr_lookup {
         key = {
-            fabric_meta.spgw.pcc_rule_id : exact @name("pcc_rule_id");
+            // 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_pcc_info();
+            set_pdr_attributes;
         }
-        const default_action = set_pcc_info(PCC_GATE_OPEN);
+        const default_action = set_pdr_attributes(DEFAULT_PDR_CTR_ID, DEFAULT_FAR_ID);
     }
-#endif // WITH_SPGW_PCC_GATING
+
+    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 (!s1u_filter_table.apply().hit) {
+            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 (dl_sess_lookup.apply().hit) {
+        } else if (downlink_filter_table.apply().hit) {
             fabric_meta.spgw.direction = SPGW_DIR_DOWNLINK;
         } else {
             fabric_meta.spgw.direction = SPGW_DIR_UNKNOWN;
@@ -158,20 +196,39 @@
             return;
         }
 
-#ifdef WITH_SPGW_PCC_GATING
-        // Allow all traffic by default.
-        fabric_meta.spgw.pcc_gate_status = PCC_GATE_OPEN;
-
-        sdf_rule_lookup.apply();
-        pcc_rule_lookup.apply();
-        pcc_info_lookup.apply();
-
-        if (fabric_meta.spgw.pcc_gate_status == PCC_GATE_CLOSED) {
-            mark_to_drop(standard_metadata);
+        // 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;
         }
-#endif // WITH_SPGW_PCC_GATING
+        // If those fail to find a match, use the wildcard tables
+        if (fabric_meta.spgw.pdr_hit == _FALSE) {
+            flexible_pdr_lookup.apply();
+        }
 
-        // Don't ask why... we'll need this later.
+        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;
     }
 }
@@ -186,6 +243,9 @@
         in    standard_metadata_t std_meta
     ) {
 
+    counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
+
+
     @hidden
     action gtpu_encap() {
         gtpu_ipv4.setValid();
@@ -195,21 +255,22 @@
         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 */
+        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.dst_addr = fabric_meta.spgw.s1u_enb_addr;
-        gtpu_ipv4.src_addr = fabric_meta.spgw.s1u_sgw_addr;
+        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;
+        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
+        gtpu_udp.checksum = 0; // Updated later, if WITH_SPGW_UDP_CSUM_UPDATE
+
 
         gtpu.setValid();
         gtpu.version = GTPU_VERSION;
@@ -224,7 +285,9 @@
     }
 
     apply {
-        if (fabric_meta.spgw.direction == SPGW_DIR_DOWNLINK) {
+        pdr_counter.count(fabric_meta.spgw.ctr_id);
+
+        if (fabric_meta.spgw.outer_header_creation == _TRUE) {
             gtpu_encap();
         }
     }