Skip to content

Commit

Permalink
[acl-loader] Support for ACL table type L3V4V6 (#2794)
Browse files Browse the repository at this point in the history
Support a new ACL table type called L3V4V6.
This table supports both v4 and v6 Match types.
Add unit tests for this new ACL table type.

HLD: sonic-net/SONiC#1267
  • Loading branch information
rck-innovium authored Jun 5, 2023
1 parent 7d803ae commit 1c1e22d
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 10 deletions.
57 changes: 50 additions & 7 deletions acl_loader/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class AclLoader(object):
"ETHERTYPE_LLDP": 0x88CC,
"ETHERTYPE_VLAN": 0x8100,
"ETHERTYPE_ROCE": 0x8915,
"ETHERTYPE_ARP": 0x0806,
"ETHERTYPE_ARP": 0x0806,
"ETHERTYPE_IPV4": 0x0800,
"ETHERTYPE_IPV6": 0x86DD,
"ETHERTYPE_MPLS": 0x8847
Expand Down Expand Up @@ -261,7 +261,7 @@ def read_acl_object_status_info(self, cfg_db_table_name, state_db_table_name):
else:
state_db_info = self.statedb.get_all(self.statedb.STATE_DB, "{}|{}".format(state_db_table_name, state_db_key))
status[key]['status'] = state_db_info.get("status", "N/A") if state_db_info else "N/A"

return status

def get_sessions_db_info(self):
Expand Down Expand Up @@ -346,6 +346,14 @@ def is_table_l3v6(self, tname):
"""
return self.tables_db_info[tname]["type"].upper() == "L3V6"

def is_table_l3v4v6(self, tname):
"""
Check if ACL table type is L3V4V6
:param tname: ACL table name
:return: True if table type is L3V4V6 else False
"""
return self.tables_db_info[tname]["type"].upper() == "L3V4V6"

def is_table_l3(self, tname):
"""
Check if ACL table type is L3
Expand Down Expand Up @@ -509,6 +517,17 @@ def convert_ip(self, table_name, rule_idx, rule):
# "IP_ICMP" we need to pick the correct protocol number for the IP version
if rule.ip.config.protocol == "IP_ICMP" and self.is_table_ipv6(table_name):
rule_props["IP_PROTOCOL"] = self.ip_protocol_map["IP_ICMPV6"]
elif rule.ip.config.protocol == "IP_ICMP" and self.is_table_l3v4v6(table_name):
# For L3V4V6 tables, both ICMP and ICMPv6 are supported,
# so find the IP_PROTOCOL using the ether_type.
try:
ether_type = rule.l2.config.ethertype
except Exception as e:
ether_type = None
if rule.l2.config.ethertype == "ETHERTYPE_IPV6":
rule_props["IP_PROTOCOL"] = self.ip_protocol_map["IP_ICMPV6"]
else:
rule_props["IP_PROTOCOL"] = self.ip_protocol_map[rule.ip.config.protocol]
else:
rule_props["IP_PROTOCOL"] = self.ip_protocol_map[rule.ip.config.protocol]
else:
Expand Down Expand Up @@ -544,9 +563,20 @@ def convert_ip(self, table_name, rule_idx, rule):
def convert_icmp(self, table_name, rule_idx, rule):
rule_props = {}

is_table_v6 = self.is_table_ipv6(table_name)
type_key = "ICMPV6_TYPE" if is_table_v6 else "ICMP_TYPE"
code_key = "ICMPV6_CODE" if is_table_v6 else "ICMP_CODE"
is_rule_v6 = False
if self.is_table_ipv6(table_name):
is_rule_v6 = True
elif self.is_table_l3v4v6(table_name):
# get the IP version type using Ether-Type.
try:
ether_type = rule.l2.config.ethertype
if ether_type == "ETHERTYPE_IPV6":
is_rule_v6 = True
except Exception as e:
pass

type_key = "ICMPV6_TYPE" if is_rule_v6 else "ICMP_TYPE"
code_key = "ICMPV6_CODE" if is_rule_v6 else "ICMP_CODE"

if rule.icmp.config.type != "" and rule.icmp.config.type != "null":
icmp_type = rule.icmp.config.type
Expand Down Expand Up @@ -651,7 +681,18 @@ def convert_rule_to_db_schema(self, table_name, rule):
rule_props["PRIORITY"] = str(self.max_priority - rule_idx)

# setup default ip type match to dataplane acl (could be overriden by rule later)
if self.is_table_l3v6(table_name):
if self.is_table_l3v4v6(table_name):
# ETHERTYPE must be passed and it should be one of IPv4 or IPv6
try:
ether_type = rule.l2.config.ethertype
except Exception as e:
raise AclLoaderException("l2:ethertype must be provided for rule #{} in table:{} of type L3V4V6".format(rule_idx, table_name))
if ether_type not in ["ETHERTYPE_IPV4", "ETHERTYPE_IPV6"]:
# Ether type must be v4 or v6 to match IP fields, L4 (TCP/UDP) fields or ICMP fields
if rule.ip or rule.transport:
raise AclLoaderException("ethertype={} is neither ETHERTYPE_IPV4 nor ETHERTYPE_IPV6 for IP rule #{} in table:{} type L3V4V6".format(rule.l2.config.ethertype, rule_idx, table_name))
rule_props["ETHER_TYPE"] = str(self.ethertype_map[ether_type])
elif self.is_table_l3v6(table_name):
rule_props["IP_TYPE"] = "IPV6ANY" # ETHERTYPE is not supported for DATAACLV6
elif self.is_table_l3(table_name):
rule_props["ETHER_TYPE"] = str(self.ethertype_map["ETHERTYPE_IPV4"])
Expand Down Expand Up @@ -682,6 +723,8 @@ def deny_rule(self, table_name):
rule_props["IP_TYPE"] = "IPV6ANY" # ETHERTYPE is not supported for DATAACLV6
elif self.is_table_l3(table_name):
rule_props["ETHER_TYPE"] = str(self.ethertype_map["ETHERTYPE_IPV4"])
elif self.is_table_l3v4v6(table_name):
rule_props["IP_TYPE"] = "IP" # Drop both v4 and v6 packets
else:
return {} # Don't add default deny rule if table is not [L3, L3V6]
return rule_data
Expand Down Expand Up @@ -835,7 +878,7 @@ def show_table(self, table_name):
for key, val in self.get_tables_db_info().items():
if table_name and key != table_name:
continue

stage = val.get("stage", Stage.INGRESS).lower()
# Get ACL table status from STATE_DB
if key in self.acl_table_status:
Expand Down
2 changes: 1 addition & 1 deletion doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1861,7 +1861,7 @@ This command is used to create new ACL tables.
- Parameters:
- table_name: The name of the ACL table to create.
- table_type: The type of ACL table to create (e.g. "L3", "L3V6", "MIRROR")
- table_type: The type of ACL table to create (e.g. "L3", "L3V6", "L3V4V6", "MIRROR")
- description: A description of the table for the user. (default is the table_name)
- ports: A comma-separated list of ports/interfaces to add to the table. The behavior is as follows:
- Physical ports will be bound as physical ports
Expand Down
84 changes: 84 additions & 0 deletions tests/acl_input/acl1.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,90 @@
"config": {
"name": "bmc_acl_northbound_v6"
}
},
"DATAACLV4V6": {
"acl-entries": {
"acl-entry": {
"1": {
"config": {
"sequence-id": 1
},
"actions": {
"config": {
"forwarding-action": "ACCEPT"
}
},
"l2": {
"config": {
"vlan-id": "369",
"ethertype": "ETHERTYPE_IPV4"
}
},
"ip": {
"config": {
"protocol": "IP_TCP",
"source-ip-address": "20.0.0.2/32",
"destination-ip-address": "30.0.0.3/32"
}
}
},
"2": {
"config": {
"sequence-id": 2
},
"actions": {
"config": {
"forwarding-action": "ACCEPT"
}
},
"l2": {
"config": {
"ethertype": "ETHERTYPE_IPV6"
}
},
"ip": {
"config": {
"protocol": "IP_ICMP",
"source-ip-address": "::1/128",
"destination-ip-address": "::1/128"
}
},
"icmp": {
"config": {
"type": "1",
"code": "0"
}
}
},
"3": {
"config": {
"sequence-id": 3
},
"actions": {
"config": {
"forwarding-action": "ACCEPT"
}
},
"l2": {
"config": {
"ethertype": "ETHERTYPE_IPV6"
}
},
"ip": {
"config": {
"protocol": "IP_ICMP",
"source-ip-address": "::1/128",
"destination-ip-address": "::1/128"
}
},
"icmp": {
"config": {
"type": "128"
}
}
}
}
}
}
}
}
Expand Down
109 changes: 109 additions & 0 deletions tests/acl_input/illegal_v4v6_rule_no_ethertype.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"acl": {
"acl-sets": {
"acl-set": {
"DATAACLV4V6": {
"acl-entries": {
"acl-entry": {
"1": {
"config": {
"sequence-id": 1
},
"actions": {
"config": {
"forwarding-action": "ACCEPT"
}
},
"ip": {
"config": {
"protocol": "IP_TCP",
"source-ip-address": "20.0.0.2/32",
"destination-ip-address": "30.0.0.3/32"
}
}
},
"2": {
"config": {
"sequence-id": 2
},
"actions": {
"config": {
"forwarding-action": "ACCEPT"
}
},
"l2": {
"config": {
"ethertype": "ETHERTYPE_IPV6"
}
},
"ip": {
"config": {
"protocol": "IP_ICMP",
"source-ip-address": "::1/128",
"destination-ip-address": "::1/128"
}
},
"icmp": {
"config": {
"type": "1",
"code": "0"
}
}
},
"3": {
"config": {
"sequence-id": 3
},
"actions": {
"config": {
"forwarding-action": "ACCEPT"
}
},
"ip": {
"config": {
"protocol": "IP_ICMP",
"source-ip-address": "::1/128",
"destination-ip-address": "::1/128"
}
},
"icmp": {
"config": {
"type": "1"
}
}
},
"4": {
"config": {
"sequence-id": 2
},
"actions": {
"config": {
"forwarding-action": "ACCEPT"
}
},
"l2": {
"config": {
"ethertype": "ETHERTYPE_IPV4"
}
},
"ip": {
"config": {
"protocol": "IP_ICMP",
"source-ip-address": "20.0.0.2/32",
"destination-ip-address": "30.0.0.3/32"
}
},
"icmp": {
"config": {
"type": "1",
"code": "0"
}
}
}
}
}
}
}
}
}
}
44 changes: 43 additions & 1 deletion tests/acl_loader_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_acl_empty(self):

def test_valid(self):
yang_acl = AclLoader.parse_acl_json(os.path.join(test_path, 'acl_input/acl1.json'))
assert len(yang_acl.acl.acl_sets.acl_set) == 8
assert len(yang_acl.acl.acl_sets.acl_set) == 9

def test_invalid(self):
with pytest.raises(AclLoaderException):
Expand Down Expand Up @@ -95,6 +95,42 @@ def test_ethertype_translation(self, acl_loader):
"PRIORITY": "9997"
}

def test_v4_rule_inv4v6_table(self, acl_loader):
acl_loader.rules_info = {}
acl_loader.load_rules_from_file(os.path.join(test_path, 'acl_input/acl1.json'))
assert acl_loader.rules_info[("DATAACLV4V6", "RULE_1")]
assert acl_loader.rules_info[("DATAACLV4V6", "RULE_1")] == {
"VLAN_ID": 369,
"ETHER_TYPE": 2048,
"IP_PROTOCOL": 6,
"SRC_IP": "20.0.0.2/32",
"DST_IP": "30.0.0.3/32",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9999"
}

def test_v6_rule_inv4v6_table(self, acl_loader):
acl_loader.rules_info = {}
acl_loader.load_rules_from_file(os.path.join(test_path, 'acl_input/acl1.json'))
assert acl_loader.rules_info[("DATAACLV4V6", "RULE_2")]
assert acl_loader.rules_info[("DATAACLV4V6", "RULE_2")] == {
"ETHER_TYPE": 34525,
"IP_PROTOCOL": 58,
"SRC_IPV6": "::1/128",
"DST_IPV6": "::1/128",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9998",
'ICMPV6_CODE': 0,
'ICMPV6_TYPE': 1
}

def test_rule_without_ethertype_inv4v6(self, acl_loader):
acl_loader.rules_info = {}
acl_loader.load_rules_from_file(os.path.join(test_path, 'acl_input/illegal_v4v6_rule_no_ethertype.json'))
assert not acl_loader.rules_info.get(("DATAACLV4V6", "RULE_1"))
assert acl_loader.rules_info[("DATAACLV4V6", "RULE_2")]
assert not acl_loader.rules_info.get(("DATAACLV4V6", "RULE_3"))

def test_icmp_translation(self, acl_loader):
acl_loader.rules_info = {}
acl_loader.load_rules_from_file(os.path.join(test_path, 'acl_input/acl1.json'))
Expand Down Expand Up @@ -151,6 +187,12 @@ def test_ingress_default_deny_rule(self, acl_loader):
'PACKET_ACTION': 'DROP',
'IP_TYPE': 'IPV6ANY'
}
assert acl_loader.rules_info[('DATAACLV4V6', 'DEFAULT_RULE')] == {
'PRIORITY': '1',
'PACKET_ACTION': 'DROP',
'IP_TYPE': 'IP'
}

# Verify acl-loader doesn't add default deny rule to MIRROR
assert ('EVERFLOW', 'DEFAULT_RULE') not in acl_loader.rules_info
# Verify acl-loader doesn't add default deny rule to MIRRORV6
Expand Down
2 changes: 1 addition & 1 deletion tests/aclshow_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
# Expected output for aclshow -r RULE_4,RULE_6 -vv
rule4_rule6_verbose_output = '' + \
"""Reading ACL info...
Total number of ACL Tables: 15
Total number of ACL Tables: 16
Total number of ACL Rules: 21
RULE NAME TABLE NAME PRIO PACKETS COUNT BYTES COUNT
Expand Down
Loading

0 comments on commit 1c1e22d

Please sign in to comment.