First stab at BNG support in fabric.p4

This implementation is derived from Deutsche Telekom contribution:
https://github.com/opencord/p4se

It supports basic upstream and downstream termination based on double
VLAN tags and PPPoE, including counters and downstream metering.

Change-Id: I940959f2338d7319654cf665f6cfe2de7200616b
diff --git a/pipelines/fabric/src/main/resources/include/bng.p4 b/pipelines/fabric/src/main/resources/include/bng.p4
new file mode 100644
index 0000000..9e56fe4
--- /dev/null
+++ b/pipelines/fabric/src/main/resources/include/bng.p4
@@ -0,0 +1,391 @@
+/*
+ * Copyright 2019-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.
+ */
+
+ /*
+  * BNG processor implementation. Provides upstream and downstream termination
+  * based on double VLAN tags (s_tag, c_tag) and PPPoE.
+  *
+  * This implementation is based on the P4 Service Edge (p4se) contribution from
+  * Deutsche Telekom:
+  * https://github.com/opencord/p4se
+  */
+
+#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(
+        inout parsed_headers_t hdr,
+        inout fabric_metadata_t fmeta,
+        inout standard_metadata_t smeta) {
+
+    counter(BNG_MAX_SUBSC, CounterType.packets) c_terminated;
+    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;
+
+    // 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);
+    }
+
+    table t_pppoe_cp {
+        key = {
+            hdr.pppoe.code     : exact   @name("pppoe_code");
+            hdr.pppoe.protocol : ternary @name("pppoe_protocol");
+        }
+        actions = {
+            punt_to_cpu;
+            @defaultonly nop;
+        }
+        size = 16;
+        const default_action = nop;
+    }
+
+    // TABLE: PPPoE termination for IPv4
+    // Check subscriber IPv4 source address, line_id, and pppoe_session_id
+    // (antispoofing), if line is enabled, pop PPPoE and double VLANs.
+
+    @hidden
+    action term_enabled(bit<16> eth_type) {
+        hdr.ethernet.eth_type = eth_type;
+        fmeta.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);
+        c_dropped.count(fmeta.bng.line_id);
+    }
+
+    action term_enabled_v4() {
+        term_enabled(ETHERTYPE_IPV4);
+    }
+
+    table t_pppoe_term_v4 {
+        key = {
+            fmeta.bng.line_id    : exact @name("line_id");
+            hdr.ipv4.src_addr    : exact @name("ipv4_src");
+            hdr.pppoe.session_id : exact @name("pppoe_session_id");
+        }
+        actions = {
+            term_enabled_v4;
+            @defaultonly term_disabled;
+        }
+        size = BNG_MAX_SUBSC_NET;
+        const default_action = term_disabled;
+    }
+
+#ifdef WITH_IPV6
+    action term_enabled_v6() {
+        term_enabled(ETHERTYPE_IPV6);
+    }
+
+    table t_pppoe_term_v6 {
+        key = {
+            fmeta.bng.line_id         : exact @name("line_id");
+            hdr.ipv6.src_addr[127:64] : exact @name("ipv6_src_net_id");
+            hdr.pppoe.session_id      : exact @name("pppoe_session_id");
+        }
+        actions = {
+            term_enabled_v6;
+            @defaultonly term_disabled;
+        }
+        size = BNG_MAX_SUBSC_NET;
+        const default_action = term_disabled;
+    }
+#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) {
+            return;
+        }
+
+        if (hdr.ipv4.isValid()) {
+            t_pppoe_term_v4.apply();
+        }
+#ifdef WITH_IPV6
+        else if (hdr.ipv6.isValid()) {
+            t_pppoe_term_v6.apply();
+        }
+#endif // WITH_IPV6
+    }
+}
+
+control bng_ingress_downstream(
+        inout parsed_headers_t hdr,
+        inout fabric_metadata_t fmeta,
+        inout standard_metadata_t smeta) {
+
+    counter(BNG_MAX_SUBSC, CounterType.packets_and_bytes) c_line_rx;
+
+    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.
+
+    @hidden
+    action set_line(bit<32> line_id) {
+        fmeta.bng.type = BNG_TYPE_DOWNSTREAM;
+        fmeta.bng.line_id = line_id;
+        c_line_rx.count(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;
+        mark_to_drop(smeta);
+    }
+
+    table t_line_map_v4 {
+        key = {
+            hdr.ipv4.dst_addr: exact @name("ipv4_dst");
+        }
+        actions = {
+            @defaultonly nop;
+            set_line_next;
+            set_line_drop;
+        }
+        size = BNG_MAX_SUBSC_NET;
+        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() {
+        m_prio.execute_meter((bit<32>)fmeta.bng.line_id,
+                             fmeta.bng.ds_meter_result);
+    }
+
+    action qos_besteff() {
+        m_besteff.execute_meter((bit<32>)fmeta.bng.line_id,
+                                fmeta.bng.ds_meter_result);
+    }
+
+    table t_qos_v4 {
+        key = {
+            fmeta.bng.line_id : ternary @name("line_id");
+            hdr.ipv4.src_addr : lpm     @name("ipv4_src");
+            hdr.ipv4.dscp     : ternary @name("ipv4_dscp");
+            hdr.ipv4.ecn      : ternary @name("ipv4_ecn");
+        }
+        actions = {
+            qos_prio;
+            qos_besteff;
+        }
+        size = 256;
+        const default_action = qos_besteff;
+    }
+
+#ifdef WITH_IPV6
+    table t_qos_v6 {
+        key = {
+            fmeta.bng.line_id      : ternary @name("line_id");
+            hdr.ipv6.src_addr      : lpm     @name("ipv6_src");
+            hdr.ipv6.traffic_class : ternary @name("ipv6_traffic_class");
+        }
+        actions = {
+            qos_prio;
+            qos_besteff;
+        }
+        size = 256;
+        const default_action = qos_besteff;
+    }
+#endif // WITH_IPV6
+
+    apply {
+        // 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();
+            }
+        }
+#ifdef WITH_IPV6
+        // IPv6
+        else if (hdr.ipv6.isValid()) {
+            if (t_line_map_v6.apply().hit) {
+                t_qos_v6.apply();
+            }
+        }
+#endif // WITH_IPV6
+    }
+}
+
+control bng_egress_downstream(
+        inout parsed_headers_t hdr,
+        inout fabric_metadata_t fmeta,
+        inout standard_metadata_t smeta) {
+
+    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;
+        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;
+        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);
+        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);
+        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();
+    }
+}
+
+control bng_ingress(
+        inout parsed_headers_t hdr,
+        inout fabric_metadata_t fmeta,
+        inout standard_metadata_t smeta) {
+
+    bng_ingress_upstream() upstream;
+    bng_ingress_downstream() downstream;
+
+    apply {
+        if (hdr.pppoe.isValid()) {
+            fmeta.bng.type = BNG_TYPE_UPSTREAM;
+            upstream.apply(hdr, fmeta, smeta);
+        }
+        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);
+        }
+    }
+}
+
+control bng_egress(
+        inout parsed_headers_t hdr,
+        inout fabric_metadata_t fmeta,
+        inout standard_metadata_t smeta) {
+
+    bng_egress_downstream() downstream;
+
+    apply {
+        if (fmeta.bng.type == BNG_TYPE_DOWNSTREAM) {
+            downstream.apply(hdr, fmeta, smeta);
+        }
+    }
+}
+
+#endif
diff --git a/pipelines/fabric/src/main/resources/include/define.p4 b/pipelines/fabric/src/main/resources/include/define.p4
index f7a03c3..8f70fb4 100644
--- a/pipelines/fabric/src/main/resources/include/define.p4
+++ b/pipelines/fabric/src/main/resources/include/define.p4
@@ -106,10 +106,15 @@
 const bit<16> ETHERTYPE_QINQ_NON_STD = 0x9100;
 const bit<16> ETHERTYPE_VLAN = 0x8100;
 const bit<16> ETHERTYPE_MPLS = 0x8847;
-const bit<16> ETHERTYPE_MPLS_MULTICAST =0x8848;
+const bit<16> ETHERTYPE_MPLS_MULTICAST = 0x8848;
 const bit<16> ETHERTYPE_IPV4 = 0x0800;
 const bit<16> ETHERTYPE_IPV6 = 0x86dd;
 const bit<16> ETHERTYPE_ARP  = 0x0806;
+const bit<16> ETHERTYPE_PPPOED = 0x8863;
+const bit<16> ETHERTYPE_PPPOES = 0x8864;
+
+const bit<16> PPPOE_PROTOCOL_IP4 = 0x0021;
+const bit<16> PPPOE_PROTOCOL_IP6 = 0x0057;
 
 const bit<8> PROTO_ICMP = 1;
 const bit<8> PROTO_TCP = 6;
diff --git a/pipelines/fabric/src/main/resources/include/header.p4 b/pipelines/fabric/src/main/resources/include/header.p4
index f57e974..5971b38 100644
--- a/pipelines/fabric/src/main/resources/include/header.p4
+++ b/pipelines/fabric/src/main/resources/include/header.p4
@@ -53,6 +53,15 @@
     bit<8> ttl;
 }
 
+header pppoe_t {
+    bit<4>  version;
+    bit<4>  type_id;
+    bit<8>  code;
+    bit<16> session_id;
+    bit<16> length;
+    bit<16> protocol;
+}
+
 header ipv4_t {
     bit<4> version;
     bit<4> ihl;
@@ -140,6 +149,20 @@
 }
 #endif // WITH_SPGW
 
+#ifdef WITH_BNG
+
+typedef bit<2> bng_type_t;
+const bng_type_t BNG_TYPE_INVALID = 2w0x0;
+const bng_type_t BNG_TYPE_UPSTREAM = 2w0x1;
+const bng_type_t BNG_TYPE_DOWNSTREAM = 2w0x2;;
+
+struct bng_meta_t {
+    bit<2>  type; // upstream or downstream
+    bit<32> line_id; // subscriber line
+    bit<32> ds_meter_result; // for downstream metering
+}
+#endif // WITH_BNG
+
 //Custom metadata definition
 struct fabric_metadata_t {
     bit<16>       eth_type;
@@ -162,6 +185,9 @@
 #ifdef WITH_SPGW
     spgw_meta_t   spgw;
 #endif // WITH_SPGW
+#ifdef WITH_BNG
+    bng_meta_t    bng;
+#endif // WITH_BNG
 #ifdef WITH_INT
     int_metadata_t int_meta;
 #endif // WITH_INT
@@ -170,9 +196,12 @@
 struct parsed_headers_t {
     ethernet_t ethernet;
     vlan_tag_t vlan_tag;
-#ifdef WITH_XCONNECT
+#if defined(WITH_XCONNECT) || defined(WITH_BNG)
     vlan_tag_t inner_vlan_tag;
-#endif // WITH_XCONNECT
+#endif // WITH_XCONNECT || WITH_BNG
+#ifdef WITH_BNG
+    pppoe_t pppoe;
+#endif // WITH_BNG
     mpls_t mpls;
 #ifdef WITH_SPGW
     ipv4_t gtpu_ipv4;
diff --git a/pipelines/fabric/src/main/resources/include/parser.p4 b/pipelines/fabric/src/main/resources/include/parser.p4
index 4a2f85c..e0f9fc5 100644
--- a/pipelines/fabric/src/main/resources/include/parser.p4
+++ b/pipelines/fabric/src/main/resources/include/parser.p4
@@ -61,14 +61,14 @@
             ETHERTYPE_IPV6: parse_ipv6;
 #endif // WITH_IPV6
             ETHERTYPE_MPLS: parse_mpls;
-#ifdef WITH_XCONNECT
+#if defined(WITH_XCONNECT) || defined(WITH_BNG)
             ETHERTYPE_VLAN: parse_inner_vlan_tag;
 #endif // WITH_XCONNECT
             default: accept;
         }
     }
 
-#ifdef WITH_XCONNECT
+#if defined(WITH_XCONNECT) || defined(WITH_BNG)
     state parse_inner_vlan_tag {
         packet.extract(hdr.inner_vlan_tag);
         transition select(hdr.inner_vlan_tag.eth_type){
@@ -77,11 +77,28 @@
             ETHERTYPE_IPV6: parse_ipv6;
 #endif // WITH_IPV6
             ETHERTYPE_MPLS: parse_mpls;
+#ifdef WITH_BNG
+            ETHERTYPE_PPPOED: parse_pppoe;
+            ETHERTYPE_PPPOES: parse_pppoe;
+#endif // WITH_BNG
             default: accept;
         }
     }
 #endif // WITH_XCONNECT
 
+#ifdef WITH_BNG
+    state parse_pppoe {
+        packet.extract(hdr.pppoe);
+        transition select(hdr.pppoe.protocol) {
+            PPPOE_PROTOCOL_IP4: parse_ipv4;
+#ifdef WITH_IPV6
+            PPPOE_PROTOCOL_IP6: parse_ipv6;
+#endif // WITH_IPV6
+            default: accept;
+        }
+    }
+#endif // WITH_BNG
+
     state parse_mpls {
         packet.extract(hdr.mpls);
         fabric_metadata.mpls_label = hdr.mpls.label;
@@ -252,9 +269,12 @@
 #endif // WITH_INT_SINK
         packet.emit(hdr.ethernet);
         packet.emit(hdr.vlan_tag);
-#ifdef WITH_XCONNECT
+#if defined(WITH_XCONNECT) || defined(WITH_BNG)
         packet.emit(hdr.inner_vlan_tag);
 #endif // WITH_XCONNECT
+#ifdef WITH_BNG
+        packet.emit(hdr.pppoe);
+#endif // WITH_BNG
         packet.emit(hdr.mpls);
 #ifdef WITH_SPGW
         packet.emit(hdr.gtpu_ipv4);