Skip to content

Commit

Permalink
Skeleton code for new ToR ACL tables. (sonic-net#201)
Browse files Browse the repository at this point in the history
* Skeleton code for new ToR ACL tables..

---------

Co-authored-by: kheradmandG <[email protected]>
Co-authored-by: kishanps <[email protected]>
  • Loading branch information
3 people authored Jun 18, 2024
1 parent 35536f7 commit e17050e
Show file tree
Hide file tree
Showing 15 changed files with 1,201 additions and 20 deletions.
1 change: 1 addition & 0 deletions sai_p4/fixed/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ filegroup(
"roles.h",
"routing.p4",
"ttl.p4",
"vlan.p4",
],
)

Expand Down
4 changes: 4 additions & 0 deletions sai_p4/fixed/bitwidths.p4
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
#define ROUTE_METADATA_BITWIDTH 6
#endif

#ifndef ACL_METADATA_BITWIDTH
#define ACL_METADATA_BITWIDTH 8
#endif

#ifndef TUNNEL_ID_BITWIDTH
#define TUNNEL_ID_BITWIDTH 16
#endif
Expand Down
25 changes: 24 additions & 1 deletion sai_p4/fixed/headers.p4
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
typedef bit<48> ethernet_addr_t;
typedef bit<32> ipv4_addr_t;
typedef bit<128> ipv6_addr_t;
typedef bit<12> vlan_id_t;
typedef bit<16> ether_type_t;

// "The VID value 0xFFF is reserved for implementation use; it must not be
// configured or transmitted." (https://en.wikipedia.org/wiki/IEEE_802.1Q).
const vlan_id_t INTERNAL_VLAN_ID = 0xfff;
// "The reserved value 0x000 indicates that the frame does not carry a VLAN ID;
// in this case, the 802.1Q tag specifies only a priority"
// (https://en.wikipedia.org/wiki/IEEE_802.1Q).
const vlan_id_t NO_VLAN_ID = 0x000;

// -- Protocol headers ---------------------------------------------------------

Expand All @@ -27,7 +37,20 @@ typedef bit<128> ipv6_addr_t;
header ethernet_t {
ethernet_addr_t dst_addr;
ethernet_addr_t src_addr;
bit<16> ether_type;
ether_type_t ether_type;
}

header vlan_t {
// Note: Tag Protocol Identifier (TPID) will be parsed as the ether_type of
// the ethernet header. It is technically a part of the vlan header but is
// modeled like this to facilitate parsing and deparsing.
bit<3> priority_code_point; // PCP
bit<1> drop_eligible_indicator; // DEI
vlan_id_t vlan_id; // VID
// Note: The next ether_type will be parsed as part of the vlan
// header. It is technically a part of the ethernet header but is modeled like
// this to facilitate parsing and deparsing.
ether_type_t ether_type;
}

#define IPV4_HEADER_BYTES 20
Expand Down
19 changes: 19 additions & 0 deletions sai_p4/fixed/metadata.p4
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type bit<QOS_QUEUE_BITWIDTH> qos_queue_t;
// -- Untranslated Types -------------------------------------------------------

typedef bit<ROUTE_METADATA_BITWIDTH> route_metadata_t;
typedef bit<ACL_METADATA_BITWIDTH> acl_metadata_t;

// -- Meters -------------------------------------------------------------------

Expand Down Expand Up @@ -153,6 +154,7 @@ struct headers_t {
gre_t erspan_gre;

ethernet_t ethernet;
vlan_t vlan;

// Not extracted during parsing.
ipv6_t tunnel_encap_ipv6;
Expand All @@ -172,10 +174,24 @@ struct headers_t {
struct packet_rewrites_t {
ethernet_addr_t src_mac;
ethernet_addr_t dst_mac;
vlan_id_t vlan_id;
}

// Local metadata for each packet being processed.
struct local_metadata_t {
// The VLAN ID used for the packet throughout the pipeline. If the input
// packet has a VLAN tag, the VID from the outer VLAN tag is used
// (and the VLAN header gets invalidated at the beginning of the ingress
// pipeline). Otherwise, the ports' native VLAN ID (4095) is used.
// The pre-ingress stage can modify this value. The value is rewritten in
// router_interface table (unless VLAN rewrite is disabled). In that case,
// the actual rewrite takes place in the egress pipeline. This value is also
// used at the end of the egress pipeline to determine whether or not the
// packet should be VLAN tagged (if not dropped).
vlan_id_t vlan_id;
// True iff `vlan_id` is valid.
bool vlan_id_valid;

bool admit_to_l3;
vrf_id_t vrf_id;
packet_rewrites_t packet_rewrites;
Expand Down Expand Up @@ -224,6 +240,9 @@ struct local_metadata_t {
// The following field corresponds to SAI_ROUTE_ENTRY_ATTR_META_DATA/
// SAI_ACL_TABLE_ATTR_FIELD_ROUTE_DST_USER_META.
route_metadata_t route_metadata;
// ACL metadata can be set with SAI_ACL_ACTION_TYPE_SET_ACL_META_DATA, and
// read from SAI_ACL_TABLE_ATTR_FIELD_ACL_USER_META.
acl_metadata_t acl_metadata;
// When controller sends a packet-out packet, the packet will be submitted to
// the ingress pipleine by default. But sometimes we want to skip the ingress
// pipeline for packet-out, and we cannot skip using the 'exit' statement as
Expand Down
35 changes: 35 additions & 0 deletions sai_p4/fixed/vlan.p4
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// A preliminary, incomplete model of IEEE 802.1Q VLAN.

#ifndef SAI_VLAN_P4_
#define SAI_VLAN_P4_

#include <v1model.p4>
#include "headers.p4"
#include "metadata.p4"


control vlan_untag(inout headers_t headers,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
apply {
if (headers.vlan.isValid()) {
headers.ethernet.ether_type = headers.vlan.ether_type;
local_metadata.vlan_id_valid = true;
local_metadata.vlan_id = headers.vlan.vlan_id;
headers.vlan.setInvalid();
}
}
} // control vlan_ingress

control vlan_tag(inout headers_t headers,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
apply {
// Currently no VLAN tagged packet gets forwarded, thus tagging is not
// modeled yet. When modeling vlan tagging, note that ethernet.ether_type
// must be copied into vlan.ether_type due to the way it is currently
// modeled (see headers.p4).
}
} // control vlan_egress

#endif // SAI_VLAN_P4_
4 changes: 4 additions & 0 deletions sai_p4/instantiations/google/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ FABRIC_BORDER_ROUTER_DEPS = SHARED_DEPS + [
]

TOR_DEPS = SHARED_DEPS + [
"acl_ingress_qos.p4",
"acl_egress.p4",
"acl_egress_dhcp_to_host.p4",
"acl_pre_ingress_metadata.p4",
"acl_pre_ingress_vlan.p4",
"tor.p4",
]

Expand Down
98 changes: 98 additions & 0 deletions sai_p4/instantiations/google/acl_egress_dhcp_to_host.p4
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2024 Google LLC
//
// 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 SAI_P4_GOOGLE_ACL_EGRESS_DHCP_TO_HOST_P4_
#define SAI_P4_GOOGLE_ACL_EGRESS_DHCP_TO_HOST_P4_

#include <v1model.p4>
#include "../../fixed/headers.p4"
#include "../../fixed/metadata.p4"
#include "acl_common_actions.p4"
#include "ids.h"
#include "minimum_guaranteed_sizes.p4"
#include "roles.h"

control acl_egress_dhcp_to_host(in headers_t headers,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
// The IPv4 and IPv6 headers are both L3 protocols and share a lot of the same
// packet data. To save on harware resources we can use a single match field
// that extracts the packet value based on the header type.
bit<8> ip_protocol = 0; // IPv4=ip_protocol, IPv6=next_header, non-IP=0

@id(ACL_EGRESS_DHCP_TO_HOST_COUNTER_ID)
direct_counter(CounterType.packets_and_bytes) acl_egress_dhcp_to_host_counter;

@id(ACL_EGRESS_DHCP_TO_HOST_TABLE_ID)
@sai_acl(EGRESS)
@p4runtime_role(P4RUNTIME_ROLE_SDN_CONTROLLER)
@entry_restriction("
// Forbid using ether_type for IP packets (by convention, use is_ip* instead).
ether_type != 0x0800 && ether_type != 0x86dd;
// Only allow IP field matches for IP packets.
ip_protocol::mask != 0 -> (is_ip == 1 || is_ipv4 == 1 || is_ipv6 == 1);
// Only allow l4_dst_port matches for TCP/UDP packets.
l4_dst_port::mask != 0 -> (ip_protocol == 6 || ip_protocol == 17);
// Forbid illegal combinations of IP_TYPE fields.
is_ip::mask != 0 -> (is_ipv4::mask == 0 && is_ipv6::mask == 0);
is_ipv4::mask != 0 -> (is_ip::mask == 0 && is_ipv6::mask == 0);
is_ipv6::mask != 0 -> (is_ip::mask == 0 && is_ipv4::mask == 0);
// Forbid unsupported combinations of IP_TYPE fields.
is_ipv4::mask != 0 -> (is_ipv4 == 1);
is_ipv6::mask != 0 -> (is_ipv6 == 1);
")
table acl_egress_dhcp_to_host_table {
key = {
headers.ipv4.isValid() || headers.ipv6.isValid() : optional
@id(1) @name("is_ip")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IP);
headers.ipv4.isValid() : optional
@id(2) @name("is_ipv4")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IPV4ANY);
headers.ipv6.isValid() : optional
@id(3) @name("is_ipv6")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IPV6ANY);
headers.ethernet.ether_type : ternary
@id(4) @name("ether_type")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE);
ip_protocol : ternary
@id(5) @name("ip_protocol")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL);
local_metadata.l4_dst_port : ternary
@id(6) @name("l4_dst_port")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT);
(port_id_t)standard_metadata.egress_port: optional
@id(7) @name("out_port")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_OUT_PORT);
}
actions = {
@proto_id(1) acl_drop(standard_metadata);
@defaultonly NoAction;
}
const default_action = NoAction;
counters = acl_egress_dhcp_to_host_counter;
size = ACL_EGRESS_TABLE_MINIMUM_GUARANTEED_SIZE;
}

apply {
if (headers.ipv4.isValid()) {
ip_protocol = headers.ipv4.protocol;
} else if (headers.ipv6.isValid()) {
ip_protocol = headers.ipv6.next_header;
}

acl_egress_dhcp_to_host_table.apply();
}
} // control acl_egress_dhcp_to_host

#endif // SAI_P4_GOOGLE_ACL_EGRESS_DHCP_TO_HOST_P4_
138 changes: 138 additions & 0 deletions sai_p4/instantiations/google/acl_ingress_qos.p4
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2024 Google LLC
//
// 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 SAI_P4_GOOGLE_ACL_INGRESS_QOS_P4_
#define SAI_P4_GOOGLE_ACL_INGRESS_QOS_P4_

#include <v1model.p4>
#include "../../fixed/headers.p4"
#include "../../fixed/metadata.p4"
#include "acl_common_actions.p4"
#include "ids.h"
#include "roles.h"
#include "minimum_guaranteed_sizes.p4"

control acl_ingress_qos(in headers_t headers,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
// The IPv4 and IPv6 headers are both L3 protocols and share a lot of the same
// packet data. To save on harware resources we can use a single match field
// that extracts the packet value based on the header type.
bit<8> ttl = 0; // IPv4=TTL, IPv6=hoplimit, non-IP=0
bit<8> ip_protocol = 0; // IPv4=ip_protocol, IPv6=next_header, non-IP=0

@id(ACL_INGRESS_QOS_COUNTER_ID)
direct_counter(CounterType.packets_and_bytes) acl_ingress_qos_counter;

@id(ACL_INGRESS_RATE_LIMIT_COPY_ACTION_ID)
// TODO: OA does not support SAI_PACKET_ACTION_COPY_CANCEL
// @sai_action(SAI_PACKET_ACTION_FORWARD, SAI_PACKET_COLOR_GREEN)
// @sai_action(SAI_PACKET_ACTION_COPY_CANCEL, SAI_PACKET_COLOR_YELLOW)
// @sai_action(SAI_PACKET_ACTION_COPY_CANCEL, SAI_PACKET_COLOR_RED)
@sai_action(SAI_PACKET_ACTION_COPY)
action acl_rate_limit_copy(
@id(1) @sai_action_param(QOS_QUEUE) qos_queue_t qos_queue) {
// TODO: Implement rate-limit flows for ToR use-case. Changes
// needed:
// * acl_ingress.p4 shouldn't set rate limits.
// * acl_ingress_qos.p4 should have a meter.
// * This action should model behaviors.
}

@id(ACL_INGRESS_QOS_TABLE_ID)
@sai_acl(INGRESS)
@p4runtime_role(P4RUNTIME_ROLE_SDN_CONTROLLER)
@entry_restriction("
// Forbid using ether_type for IP packets (by convention, use is_ip* instead).
ether_type != 0x0800 && ether_type != 0x86dd;
// Only allow IP field matches for IP packets.
dst_ip::mask != 0 -> is_ipv4 == 1;
ttl::mask != 0 -> (is_ip == 1 || is_ipv4 == 1 || is_ipv6 == 1);
ip_protocol::mask != 0 -> (is_ip == 1 || is_ipv4 == 1 || is_ipv6 == 1);
// Only allow l4_dst_port and l4_src_port matches for TCP/UDP packets.
l4_src_port::mask != 0 -> (ip_protocol == 6 || ip_protocol == 17);
l4_dst_port::mask != 0 -> (ip_protocol == 6 || ip_protocol == 17);
// Forbid illegal combinations of IP_TYPE fields.
is_ip::mask != 0 -> (is_ipv4::mask == 0 && is_ipv6::mask == 0);
is_ipv4::mask != 0 -> (is_ip::mask == 0 && is_ipv6::mask == 0);
is_ipv6::mask != 0 -> (is_ip::mask == 0 && is_ipv4::mask == 0);
// Forbid unsupported combinations of IP_TYPE fields.
is_ipv4::mask != 0 -> (is_ipv4 == 1);
is_ipv6::mask != 0 -> (is_ipv6 == 1);
")
table acl_ingress_qos_table {
key = {
headers.ipv4.isValid() || headers.ipv6.isValid() : optional
@id(1) @name("is_ip")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IP);
headers.ipv4.isValid() : optional
@id(2) @name("is_ipv4")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IPV4ANY);
headers.ipv6.isValid() : optional
@id(3) @name("is_ipv6")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IPV6ANY);
headers.ethernet.ether_type : ternary
@id(4) @name("ether_type")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE);
headers.ethernet.dst_addr : ternary
@id(5) @name("dst_mac")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_DST_MAC) @format(MAC_ADDRESS);
headers.ipv4.dst_addr : ternary
@id(6) @name("dst_ip")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_DST_IP) @format(IPV4_ADDRESS);
ttl : ternary
@id(7) @name("ttl")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_TTL);
ip_protocol : ternary
@id(8) @name("ip_protocol")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL);
local_metadata.l4_src_port : ternary
@id(9) @name("l4_src_port")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT);
local_metadata.l4_dst_port : ternary
@id(10) @name("l4_dst_port")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT);
local_metadata.ingress_port : optional
@id(11) @name("in_port")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_IN_PORT);
(port_id_t)standard_metadata.egress_port: optional
@id(12) @name("out_port")
@sai_field(SAI_ACL_TABLE_ATTR_FIELD_OUT_PORT);
// TODO: OA does not support SAI_ACL_TABLE_ATTR_FIELD_ACL_USER_META.
// local_metadata.acl_metadata : optional
// @id(13) @name("acl_metadata")
// @sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_USER_META);
}
actions = {
@proto_id(1) acl_rate_limit_copy();
@defaultonly NoAction;
}
const default_action = NoAction;
counters = acl_ingress_qos_counter;
size = ACL_INGRESS_TABLE_MINIMUM_GUARANTEED_SIZE;
}

apply {
if (headers.ipv4.isValid()) {
ttl = headers.ipv4.ttl;
ip_protocol = headers.ipv4.protocol;
} else if (headers.ipv6.isValid()) {
ttl = headers.ipv6.hop_limit;
ip_protocol = headers.ipv6.next_header;
}

acl_ingress_qos_table.apply();
}
} // control acl_ingress_qos

#endif // SAI_P4_GOOGLE_ACL_INGRESS_QOS_P4_
Loading

0 comments on commit e17050e

Please sign in to comment.