Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test case test_custom_acl to verify custom ACL table #6905

Merged
merged 10 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions tests/acl/custom_acl_table/acl_rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"ACL_RULE": {
"CUSTOM_TABLE|RULE_1": {
"SRC_IP": "192.168.0.2/32",
"IP_PROTOCOL": "6",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9999"
},
"CUSTOM_TABLE|RULE_2": {
"DST_IP": "103.23.2.1/32",
"IP_PROTOCOL": "6",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9998"
},
"CUSTOM_TABLE|RULE_3": {
"SRC_IPV6": "fc02:1000::2/128",
"NEXT_HEADER": "6",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9997"
},
"CUSTOM_TABLE|RULE_4": {
"DST_IPV6": "103:23:2:1::1/128",
"NEXT_HEADER": "6",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9996"
},
"CUSTOM_TABLE|RULE_5": {
"L4_SRC_PORT": "8080",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9995"
},
"CUSTOM_TABLE|RULE_6": {
"L4_DST_PORT": "8080",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9994"
},
"CUSTOM_TABLE|RULE_7": {
"L4_SRC_PORT_RANGE": "8081-8090",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9993"
},
"CUSTOM_TABLE|RULE_8": {
"L4_DST_PORT_RANGE": "8081-8090",
"PACKET_ACTION": "FORWARD",
"PRIORITY": "9992"
},
"CUSTOM_TABLE|DEFAULT_DROP_RULE_V4": {
"ETHER_TYPE": "2048",
"PACKET_ACTION": "DROP",
"PRIORITY": "2"
},
"CUSTOM_TABLE|DEFAULT_DROP_RULE_V6": {
"IP_TYPE": "IPV6ANY",
"PACKET_ACTION": "DROP",
"PRIORITY": "1"
}
}
}
9 changes: 9 additions & 0 deletions tests/acl/custom_acl_table/custom_acl_table.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"ACL_TABLE_TYPE": {
"CUSTOM_TYPE": {
"MATCHES": "SRC_IP,DST_IP,SRC_IPV6,DST_IPV6,ETHER_TYPE,IP_TYPE,IP_PROTOCOL,NEXT_HEADER,L4_SRC_PORT,L4_DST_PORT,L4_SRC_PORT_RANGE,L4_DST_PORT_RANGE",
"ACTIONS": "PACKET_ACTION",
"BIND_POINTS": "PORT"
}
}
}
274 changes: 274 additions & 0 deletions tests/acl/custom_acl_table/test_custom_acl_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import logging
import json
import pytest
import time

from ptf.mask import Mask
import ptf.packet as scapy


import ptf.testutils as testutils
from tests.common.utilities import skip_release
from tests.common.helpers.assertions import pytest_assert
from tests.common.plugins.loganalyzer.loganalyzer import LogAnalyzer, LogAnalyzerError
from tests.common.dualtor.mux_simulator_control import toggle_all_simulator_ports_to_rand_selected_tor

logger = logging.getLogger(__name__)

pytestmark = [
pytest.mark.topology("t0"), # Only run on T0 testbed
pytest.mark.disable_loganalyzer, # Disable automatic loganalyzer, since we use it for the test
]

CUSTOM_ACL_TABLE_TYPE_SRC_FILE = "acl/custom_acl_table/custom_acl_table.json"
CUSTOM_ACL_TABLE_TYPE_DST_FILE = "/tmp/custom_acl_table.json"

ACL_RULE_SRC_FILE = "acl/custom_acl_table/acl_rules.json"
ACL_RULE_DST_FILE = "/tmp/acl_rules.json"

LOG_EXPECT_ACL_TABLE_CREATE_RE = ".*Created ACL table.*"
LOG_EXPECT_ACL_RULE_FAILED_RE = ".*Failed to create ACL rule.*"


@pytest.fixture(scope='module', autouse=True)
def check_release(rand_selected_dut):
skip_release(rand_selected_dut, ["201811", "201911", "202012"])
bingwang-ms marked this conversation as resolved.
Show resolved Hide resolved


@pytest.fixture(scope='module')
def setup_counterpoll_interval(rand_selected_dut):
"""
Set the counterpoll interval for acl to 1 second (10 seconds by default)
"""
# Set polling interval to 1 second
ZhaohuiS marked this conversation as resolved.
Show resolved Hide resolved
rand_selected_dut.shell('counterpoll acl interval 1000')
time.sleep(10)
yield
# Restore default value 10 seconds
bingwang-ms marked this conversation as resolved.
Show resolved Hide resolved
rand_selected_dut.shell('counterpoll acl interval 1000')
bingwang-ms marked this conversation as resolved.
Show resolved Hide resolved


def clear_acl_counter(dut):
"""
Clear the counter of ACL
"""
dut.shell('aclshow -c')


def read_acl_counter(dut, rule_name):
"""
Read the counter of given rule
RULE NAME TABLE NAME PRIO PACKETS COUNT BYTES COUNT
----------- ------------ ------ --------------- -------------
RULE_1 L3_MIX_TABLE 9999 0 0
"""
cmd = 'aclshow -a -r {}'.format(rule_name)
bingwang-ms marked this conversation as resolved.
Show resolved Hide resolved
time.sleep(2)
output = dut.shell(cmd)['stdout_lines']
for line in output:
fields = line.split()
if len(fields) != 5:
continue
if fields[0] == rule_name:
return int(fields[3])

return 0


# TODO: Move this fixture to a shared place of acl test
@pytest.fixture(scope="module", autouse=True)
def remove_dataacl_table(rand_selected_dut):
"""
Remove DATAACL to free TCAM resources
"""
TABLE_NAME = "DATAACL"
lines = rand_selected_dut.shell(cmd="show acl table {}".format(TABLE_NAME))['stdout_lines']
data_acl_existing = False
for line in lines:
if TABLE_NAME in line:
data_acl_existing = True
break
if not data_acl_existing:
yield
return
# Remove DATAACL
logger.info("Removing ACL table {}".format(TABLE_NAME))
rand_selected_dut.shell(cmd="config acl remove table {}".format(TABLE_NAME))
yield
# Recover DATAACL
config_db_json = "/etc/sonic/config_db.json"
output = rand_selected_dut.shell("sonic-cfggen -j {} --var-json \"ACL_TABLE\"".format(config_db_json))['stdout']
try:
entry = json.loads(output)[TABLE_NAME]
cmd_create_table = "config acl add table {} {} -p {} -s {}".format(TABLE_NAME, entry['type'], \
",".join(sorted(entry['ports'])), entry['stage'])
logger.info("Restoring ACL table {}".format(TABLE_NAME))
rand_selected_dut.shell(cmd_create_table)
except Exception as e:
pytest.fail(str(e))


@pytest.fixture(scope='module')
def setup_custom_acl_table(rand_selected_dut):
# Define a custom table type CUSTOM_TYPE by loading a json configuration
rand_selected_dut.copy(src=CUSTOM_ACL_TABLE_TYPE_SRC_FILE, dest=CUSTOM_ACL_TABLE_TYPE_DST_FILE)
rand_selected_dut.shell("sonic-cfggen -j {} -w".format(CUSTOM_ACL_TABLE_TYPE_DST_FILE))
# Create an ACL table and bind to Vlan1000 interface
cmd_create_table = "config acl add table CUSTOM_TABLE CUSTOM_TYPE -s ingress -p Vlan1000"
cmd_remove_table = "config acl remove table CUSTOM_TABLE"
loganalyzer = LogAnalyzer(ansible_host=rand_selected_dut, marker_prefix="custom_acl")
loganalyzer.load_common_config()

try:
logger.info("Creating ACL table CUSTOM_TABLE with type CUSTOM_TYPE")
loganalyzer.expect_regex = [LOG_EXPECT_ACL_TABLE_CREATE_RE]
# Ignore any other errors to reduce noise
loganalyzer.ignore_regex = [r".*"]
with loganalyzer:
rand_selected_dut.shell(cmd_create_table)
except LogAnalyzerError as err:
# Cleanup Config DB if table creation failed
logger.error("ACL table creation failed, attempting to clean-up...")
rand_selected_dut.shell(cmd_remove_table)
raise err

yield
logger.info("Removing ACL table and custom type")
# Remove ACL table
rand_selected_dut.shell(cmd_remove_table)
# Remove custom type
rand_selected_dut.shell("sonic-db-cli CONFIG_DB del \'ACL_TABLE_TYPE|CUSTOM_TYPE\'")


@pytest.fixture(scope='module')
def setup_acl_rules(rand_selected_dut, setup_custom_acl_table):
# Copy and load acl rules
rand_selected_dut.copy(src=ACL_RULE_SRC_FILE, dest=ACL_RULE_DST_FILE)
cmd_add_rules = "sonic-cfggen -j {} -w".format(ACL_RULE_DST_FILE)
cmd_rm_rules = "acl-loader delete CUSTOM_TABLE"

loganalyzer = LogAnalyzer(ansible_host=rand_selected_dut, marker_prefix="custom_acl")
loganalyzer.match_regex = [LOG_EXPECT_ACL_RULE_FAILED_RE]
try:
logger.info("Creating ACL rules in CUSTOM_TABLE")
with loganalyzer:
rand_selected_dut.shell(cmd_add_rules)
except LogAnalyzerError as err:
# Cleanup Config DB if failed
logger.error("ACL rule creation failed, attempting to clean-up...")
rand_selected_dut.shell(cmd_rm_rules)
raise err
yield
# Remove testing rules
logger.info("Removing testing ACL rules")
rand_selected_dut.shell(cmd_rm_rules)


def build_testing_pkts(router_mac):
"""
Generate packet for IO test
"""
# The IPs and ports must be exactly the same with rules defined in acl_rules.json
SRC_IP = "192.168.0.2"
SRC_IPV6 = "fc02:1000::2"
DST_IP = "103.23.2.1"
DST_IPV6 = "103:23:2:1::1"
SRC_PORT = 8080
DST_PORT = 8080
SRC_RANGE_PORT = 8085
DST_RANGE_PORT = 8085

test_packets = {}
# Verify matching source IP and protocol
test_packets['RULE_1'] = testutils.simple_tcp_packet(eth_dst=router_mac,
ip_src=SRC_IP,
ip_dst='1.1.1.1')
# Verify matching destination IP and protocol
test_packets['RULE_2'] = testutils.simple_tcp_packet(eth_dst=router_mac,
ip_src='192.168.0.3',
ip_dst=DST_IP)
# Verify matching IPV6 source and next header
test_packets['RULE_3'] = testutils.simple_tcpv6_packet(eth_dst=router_mac,
ipv6_src=SRC_IPV6,
ipv6_dst='103:23:2:1::2')
# Verify matching IPV6 destination and next header
test_packets['RULE_4'] = testutils.simple_tcpv6_packet(eth_dst=router_mac,
ipv6_src='fc02:1000::3',
ipv6_dst=DST_IPV6)

# Verify matching source port (IPV4)
test_packets['RULE_5'] = testutils.simple_tcp_packet(eth_dst=router_mac,
ip_src='192.168.0.3',
ip_dst='1.1.1.1',
tcp_sport=SRC_PORT)
# Verify matching destination port (IPV6)
test_packets['RULE_6'] = testutils.simple_tcpv6_packet(eth_dst=router_mac,
ipv6_src='fc02:1000::3',
ipv6_dst='103:23:2:1::2',
tcp_dport=DST_PORT)
# Verify matching source port range (IPV4)
test_packets['RULE_7'] = testutils.simple_tcp_packet(eth_dst=router_mac,
ip_src='192.168.0.3',
ip_dst='1.1.1.1',
tcp_sport=SRC_RANGE_PORT)
# Verify matching destination port (IPV6)
test_packets['RULE_8'] = testutils.simple_tcpv6_packet(eth_dst=router_mac,
ipv6_src='fc02:1000::3',
ipv6_dst='103:23:2:1::2',
tcp_dport=DST_RANGE_PORT)

return test_packets


def build_exp_pkt(input_pkt):
"""
Generate the expected packet for given packet
"""
exp_pkt = Mask(input_pkt)
exp_pkt.set_do_not_care_scapy(scapy.Ether, "dst")
exp_pkt.set_do_not_care_scapy(scapy.Ether, "src")
if input_pkt.haslayer('IP'):
exp_pkt.set_do_not_care_scapy(scapy.IP, "ttl")
exp_pkt.set_do_not_care_scapy(scapy.IP, "chksum")
else:
exp_pkt.set_do_not_care_scapy(scapy.IPv6, "hlim")

return exp_pkt


def test_custom_acl(rand_selected_dut, tbinfo, ptfadapter, setup_acl_rules, toggle_all_simulator_ports_to_rand_selected_tor, setup_counterpoll_interval, remove_dataacl_table):
"""
The test case is to verify the functionality of custom ACL table
Test steps
1. Define a custom ACL table type by loading json configuration
2. Create an ingress ACL table with the custom type
3. Toggle all ports to active if the test is running on dual-tor
4. Ingress packets from vlan port
5. Verify the packets are egressed to uplinks
6. Verify the counter of expected rule increases as expected
"""
router_mac = rand_selected_dut.facts['router_mac']
mg_facts = rand_selected_dut.get_extended_minigraph_facts(tbinfo)

# Selected the first vlan port as source port
src_port = list(mg_facts['minigraph_vlans'].values())[0]['members'][0]
src_port_indice = mg_facts['minigraph_ptf_indices'][src_port]
# Put all portchannel members into dst_ports
dst_port_indices = []
for _, v in mg_facts['minigraph_portchannels'].iteritems():
for member in v['members']:
dst_port_indices.append(mg_facts['minigraph_ptf_indices'][member])

test_pkts = build_testing_pkts(router_mac)
for rule, pkt in test_pkts.items():
logger.info("Testing ACL rule {}".format(rule))
exp_pkt = build_exp_pkt(pkt)
# Send and verify packet
clear_acl_counter(rand_selected_dut)
ptfadapter.dataplane.flush()
testutils.send(ptfadapter, pkt=pkt, port_id=src_port_indice)
testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=dst_port_indices, timeout=5)
acl_counter = read_acl_counter(rand_selected_dut, rule)
# Verify acl counter
pytest_assert(acl_counter == 1, "ACL counter for {} didn't increase as expected".format(rule))

bingwang-ms marked this conversation as resolved.
Show resolved Hide resolved