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);