Initial integration of SPGW in fabric.p4

Change-Id: Idd78399212039e44c982f50d343f824d516f938a
diff --git a/pipelines/fabric/src/main/resources/include/checksum.p4 b/pipelines/fabric/src/main/resources/include/checksum.p4
index 00b4e70..4d1782d 100644
--- a/pipelines/fabric/src/main/resources/include/checksum.p4
+++ b/pipelines/fabric/src/main/resources/include/checksum.p4
@@ -17,6 +17,10 @@
 #ifndef __CHECKSUM__
 #define __CHECKSUM__
 
+#ifdef WITH_SPGW
+#include "spgw.p4"
+#endif // WITH_SPGW
+
 control FabricComputeChecksum(inout parsed_headers_t hdr,
                               inout fabric_metadata_t meta)
 {
@@ -38,6 +42,9 @@
             hdr.ipv4.hdr_checksum,
             HashAlgorithm.csum16
         );
+#ifdef WITH_SPGW
+        update_gtpu_checksum.apply(hdr.gtpu_ipv4);
+#endif // WITH_SPGW
     }
 }
 
@@ -62,6 +69,9 @@
             hdr.ipv4.hdr_checksum,
             HashAlgorithm.csum16
         );
+#ifdef WITH_SPGW
+        verify_gtpu_checksum.apply(hdr.gtpu_ipv4);
+#endif // WITH_SPGW
     }
 }
 
diff --git a/pipelines/fabric/src/main/resources/include/define.p4 b/pipelines/fabric/src/main/resources/include/define.p4
index 4765824..d3a7811 100644
--- a/pipelines/fabric/src/main/resources/include/define.p4
+++ b/pipelines/fabric/src/main/resources/include/define.p4
@@ -44,6 +44,8 @@
 const bit<8> PROTO_UDP = 17;
 const bit<8> PROTO_ICMPV6 = 58;
 
+const bit<4> IPV4_MIN_IHL = 5;
+
 #ifndef CPU_PORT
 const port_num_t CPU_PORT = 255;
 #endif
@@ -56,5 +58,29 @@
 const fwd_type_t FWD_IPV6_MULTICAST = 5;
 
 const bit<8> DEFAULT_MPLS_TTL = 64;
+const bit<8> DEFAULT_IPV4_TTL = 64;
+
+#define ETH_HDR_SIZE 14
+#define IPV4_HDR_SIZE 20
+#define UDP_HDR_SIZE 8
+
+#define UDP_PORT_GTPU 2152
+#define GTP_GPDU 0xff
+#define GTPU_VERSION 0x01
+#define GTP_PROTOCOL_TYPE_GTP 0x01
+
+typedef bit direction_t;
+typedef bit pcc_gate_status_t;
+typedef bit<32> sdf_rule_id_t;
+typedef bit<32> pcc_rule_id_t;
+
+const sdf_rule_id_t DEFAULT_SDF_RULE_ID = 0;
+const pcc_rule_id_t DEFAULT_PCC_RULE_ID = 0;
+
+const direction_t DIR_UPLINK = 1w0;
+const direction_t DIR_DOWNLINK = 1w1;
+
+const pcc_gate_status_t PCC_GATE_OPEN = 1w0;
+const pcc_gate_status_t PCC_GATE_CLOSED = 1w1;
 
 #endif
diff --git a/pipelines/fabric/src/main/resources/include/header.p4 b/pipelines/fabric/src/main/resources/include/header.p4
index c177ad5..b9f36b1 100644
--- a/pipelines/fabric/src/main/resources/include/header.p4
+++ b/pipelines/fabric/src/main/resources/include/header.p4
@@ -115,6 +115,34 @@
     bit<64> timestamp;
 }
 
+#ifdef WITH_SPGW
+// GTPU v1
+header gtpu_t {
+    bit<3>  version;    /* version */
+    bit<1>  pt;         /* protocol type */
+    bit<1>  spare;      /* reserved */
+    bit<1>  ex_flag;    /* next extension hdr present? */
+    bit<1>  seq_flag;   /* sequence no. */
+    bit<1>  npdu_flag;  /* n-pdn number present ? */
+    bit<8>  msgtype;    /* message type */
+    bit<16> msglen;     /* message length */
+    bit<32> teid;       /* tunnel endpoint id */
+}
+
+struct spgw_meta_t {
+    bool              do_spgw;
+    bit<16>           l4_src_port;
+    bit<16>           l4_dst_port;
+    direction_t       direction;
+    pcc_gate_status_t pcc_gate_status;
+    sdf_rule_id_t     sdf_rule_id;
+    pcc_rule_id_t     pcc_rule_id;
+    bit<32>           dl_sess_teid;
+    bit<32>           dl_sess_enb_addr;
+    bit<32>           dl_sess_s1u_addr;
+}
+#endif // WITH_SPGW
+
 //Custom metadata definition
 struct fabric_metadata_t {
     fwd_type_t fwd_type;
@@ -124,12 +152,20 @@
     bit<16> l4_src_port;
     bit<16> l4_dst_port;
     bit<16> original_ether_type;
+#ifdef WITH_SPGW
+    spgw_meta_t spgw;
+#endif // WITH_SPGW
 }
 
 struct parsed_headers_t {
     ethernet_t ethernet;
     vlan_tag_t vlan_tag;
     mpls_t mpls;
+#ifdef WITH_SPGW
+    ipv4_t gtpu_ipv4;
+    udp_t gtpu_udp;
+    gtpu_t gtpu;
+#endif // WITH_SPGW
     ipv4_t ipv4;
     ipv6_t ipv6;
     arp_t arp;
diff --git a/pipelines/fabric/src/main/resources/include/parser.p4 b/pipelines/fabric/src/main/resources/include/parser.p4
index 67cc443..8355c12 100644
--- a/pipelines/fabric/src/main/resources/include/parser.p4
+++ b/pipelines/fabric/src/main/resources/include/parser.p4
@@ -113,13 +113,46 @@
         packet.extract(hdr.udp);
         fabric_metadata.l4_src_port = hdr.udp.src_port;
         fabric_metadata.l4_dst_port = hdr.udp.dst_port;
+#ifdef WITH_SPGW
+        transition select(hdr.udp.dst_port) {
+            UDP_PORT_GTPU: parse_gtpu;
+            default: accept;
+        }
+#else
         transition accept;
+#endif // WITH_SPGW
     }
 
     state parse_icmp {
         packet.extract(hdr.icmp);
         transition accept;
     }
+
+#ifdef WITH_SPGW
+    state parse_gtpu {
+        packet.extract(hdr.gtpu);
+        transition parse_ipv4_inner;
+    }
+
+    state parse_ipv4_inner {
+        hdr.gtpu_ipv4 = hdr.ipv4;
+        packet.extract(hdr.ipv4);
+        transition select(hdr.ipv4.protocol) {
+            PROTO_TCP: parse_tcp;
+            PROTO_UDP: parse_udp_inner;
+            PROTO_ICMP: parse_icmp;
+            default: accept;
+        }
+    }
+
+    state parse_udp_inner {
+        hdr.gtpu_udp = hdr.udp;
+        packet.extract(hdr.udp);
+        fabric_metadata.l4_src_port = hdr.udp.src_port;
+        fabric_metadata.l4_dst_port = hdr.udp.dst_port;
+        transition accept;
+    }
+#endif // WITH_SPGW
 }
 
 control FabricDeparser(packet_out packet, in parsed_headers_t hdr) {
@@ -129,6 +162,11 @@
         packet.emit(hdr.vlan_tag);
         packet.emit(hdr.mpls);
         packet.emit(hdr.arp);
+#ifdef WITH_SPGW
+        packet.emit(hdr.gtpu_ipv4);
+        packet.emit(hdr.gtpu_udp);
+        packet.emit(hdr.gtpu);
+#endif // WITH_SPGW
         packet.emit(hdr.ipv4);
         packet.emit(hdr.ipv6);
         packet.emit(hdr.tcp);
diff --git a/pipelines/fabric/src/main/resources/include/spgw.p4 b/pipelines/fabric/src/main/resources/include/spgw.p4
new file mode 100644
index 0000000..ce39acb
--- /dev/null
+++ b/pipelines/fabric/src/main/resources/include/spgw.p4
@@ -0,0 +1,284 @@
+/*
+ * 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__
+
+
+control spgw_ingress(
+        inout ipv4_t      gtpu_ipv4,
+        inout udp_t       gtpu_udp,
+        inout gtpu_t      gtpu,
+        inout spgw_meta_t spgw_meta,
+        in    ipv4_t      ipv4
+    ) {
+
+    direct_counter(CounterType.packets_and_bytes) ue_counter;
+
+    action gtpu_decap() {
+        gtpu_ipv4.setInvalid();
+        gtpu_udp.setInvalid();
+        gtpu.setInvalid();
+    }
+
+    action set_sdf_rule_id(sdf_rule_id_t id) {
+        spgw_meta.sdf_rule_id = id;
+    }
+
+    action set_pcc_rule_id(pcc_rule_id_t id) {
+        spgw_meta.pcc_rule_id = id;
+    }
+
+    action set_pcc_info(pcc_gate_status_t gate_status) {
+        spgw_meta.pcc_gate_status = gate_status;
+    }
+
+    action set_dl_sess_info(bit<32> dl_sess_teid,
+                            bit<32> dl_sess_enb_addr,
+                            bit<32> dl_sess_s1u_addr) {
+        spgw_meta.dl_sess_teid = dl_sess_teid;
+        spgw_meta.dl_sess_enb_addr = dl_sess_enb_addr;
+        spgw_meta.dl_sess_s1u_addr = dl_sess_s1u_addr;
+    }
+
+    action update_ue_cdr() {
+        ue_counter.count();
+    }
+
+    table ue_filter_table {
+        key = {
+            // IP prefixes of the UEs managed by this switch.
+            ipv4.dst_addr : lpm;
+        }
+        actions = {
+            NoAction();
+        }
+    }
+
+    table s1u_filter_table {
+        key = {
+            // IP addresses of the S1U interfaces embodied by this switch.
+            gtpu_ipv4.dst_addr : exact;
+        }
+        actions = {
+            NoAction();
+        }
+    }
+
+    table sdf_rule_lookup {
+        key = {
+            spgw_meta.direction   : exact;
+            ipv4.src_addr         : ternary;
+            ipv4.dst_addr         : ternary;
+            ipv4.protocol         : ternary;
+            spgw_meta.l4_src_port : ternary;
+            spgw_meta.l4_dst_port : ternary;
+        }
+        actions = {
+            set_sdf_rule_id();
+        }
+        const default_action = set_sdf_rule_id(DEFAULT_SDF_RULE_ID);
+    }
+
+    table pcc_rule_lookup {
+        key = {
+            spgw_meta.sdf_rule_id : exact;
+        }
+        actions = {
+            set_pcc_rule_id();
+        }
+        const default_action = set_pcc_rule_id(DEFAULT_PCC_RULE_ID);
+    }
+
+    table pcc_info_lookup {
+        key = {
+            spgw_meta.pcc_rule_id : exact;
+        }
+        actions = {
+            set_pcc_info();
+        }
+        const default_action = set_pcc_info(PCC_GATE_OPEN);
+    }
+
+    table dl_sess_lookup {
+        key = {
+            // UE addr for downlink
+            ipv4.dst_addr : exact;
+        }
+        actions = {
+            set_dl_sess_info();
+        }
+    }
+
+    table ue_cdr_table {
+        key = {
+            // UE addr for downlink
+            ipv4.dst_addr : exact;
+        }
+        actions = {
+            update_ue_cdr();
+        }
+        counters = ue_counter;
+    }
+
+    apply {
+        // Admit only packets to known UE/S1U addresses, if so sets direction,
+        // otherwise skip SPGW processing.
+        spgw_meta.do_spgw = false;
+        if (gtpu.isValid() && gtpu.msgtype == GTP_GPDU) {
+            spgw_meta.direction = DIR_UPLINK;
+            if (s1u_filter_table.apply().hit) {
+                spgw_meta.do_spgw = true;
+            }
+        } else {
+            if (ue_filter_table.apply().hit) {
+                spgw_meta.do_spgw = true;
+            }
+        }
+
+        if (!spgw_meta.do_spgw) {
+            // Exit this control block.
+            return;
+        }
+
+        if (spgw_meta.direction == DIR_UPLINK) {
+            gtpu_decap();
+        }
+
+        // Allow all traffic by default.
+        spgw_meta.pcc_gate_status = PCC_GATE_OPEN;
+
+        sdf_rule_lookup.apply();
+        pcc_rule_lookup.apply();
+        pcc_info_lookup.apply();
+
+        if (spgw_meta.pcc_gate_status == PCC_GATE_CLOSED) {
+            mark_to_drop();
+            exit;
+        }
+
+        if (spgw_meta.direction == DIR_DOWNLINK) {
+            if (!dl_sess_lookup.apply().hit) {
+                // We have no other choice than drop, as we miss the session
+                // info necessary to properly GTPU encap the packet.
+                mark_to_drop();
+                exit;
+            }
+            ue_cdr_table.apply();
+        }
+    }
+}
+
+
+control spgw_egress(
+        out ipv4_t              gtpu_ipv4,
+        out udp_t               gtpu_udp,
+        out gtpu_t              gtpu,
+        in  spgw_meta_t         spgw_meta,
+        in  standard_metadata_t std_meta
+    ) {
+
+    action gtpu_encap() {
+        // GTPU
+        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 = (bit<16>) (std_meta.packet_length - ETH_HDR_SIZE);
+        gtpu.teid = spgw_meta.dl_sess_teid;
+        // Outer IPv4
+        gtpu_ipv4.setValid();
+        gtpu_ipv4.version = IP_VERSION_4;
+        gtpu_ipv4.ihl = IPV4_MIN_IHL;
+        gtpu_ipv4.diffserv = 0;
+        gtpu_ipv4.total_len = (bit<16>) (std_meta.packet_length
+            - ETH_HDR_SIZE + IPV4_HDR_SIZE + UDP_HDR_SIZE);
+        gtpu_ipv4.identification = 0x1513; /* From NGIC */
+        gtpu_ipv4.flags = 0;
+        gtpu_ipv4.frag_offset = 0;
+        gtpu_ipv4.ttl = DEFAULT_IPV4_TTL;
+        gtpu_ipv4.protocol = PROTO_UDP;
+        gtpu_ipv4.dst_addr = spgw_meta.dl_sess_enb_addr;
+        gtpu_ipv4.src_addr = spgw_meta.dl_sess_s1u_addr;
+        gtpu_ipv4.hdr_checksum = 0; /* Updated later */
+        // Outer UDP
+        gtpu_udp.setValid();
+        gtpu_udp.src_port = UDP_PORT_GTPU;
+        gtpu_udp.dst_port = UDP_PORT_GTPU;
+        gtpu_udp.len = (bit<16>) (std_meta.packet_length
+            - ETH_HDR_SIZE + UDP_HDR_SIZE);
+        gtpu_udp.checksum = 0; /* Ignore, won't be updated */
+    }
+
+    apply {
+        if (spgw_meta.do_spgw && spgw_meta.direction == DIR_DOWNLINK) {
+            gtpu_encap();
+        }
+    }
+}
+
+
+control verify_gtpu_checksum(inout ipv4_t gtpu_ipv4) {
+    apply {
+        verify_checksum(gtpu_ipv4.isValid(),
+            {
+                gtpu_ipv4.version,
+                gtpu_ipv4.ihl,
+                gtpu_ipv4.diffserv,
+                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
+        );
+    }
+}
+
+
+control update_gtpu_checksum(inout ipv4_t gtpu_ipv4) {
+    apply {
+        // Compute outer IPv4 checksum.
+        update_checksum(gtpu_ipv4.isValid(),
+            {
+                gtpu_ipv4.version,
+                gtpu_ipv4.ihl,
+                gtpu_ipv4.diffserv,
+                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
+        );
+    }
+}
+
+#endif