Skip to content

Commit b38562a

Browse files
authored
multi-asic support for test_cacl_application.py (#3070)
Enhanced test_cacl_application.py for multi-asic platforms. Also it adds new test case to cover all multi-asic specific changes as done in PR's:- sonic-net/sonic-buildimage#5022 sonic-net/sonic-buildimage#5420 sonic-net/sonic-buildimage#5364 sonic-net/sonic-buildimage#6765 Also fix some of API in common/devices.py and bug in config_facts
1 parent 724e05f commit b38562a

File tree

3 files changed

+155
-32
lines changed

3 files changed

+155
-32
lines changed

ansible/library/config_facts.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def create_maps(config):
7474
for idx, val in enumerate(port_name_list_sorted):
7575
port_index_map[val] = idx
7676

77-
port_name_to_alias_map = { name : v['alias'] for name, v in config["PORT"].iteritems()}
77+
port_name_to_alias_map = { name : v['alias'] if 'alias' in v else '' for name, v in config["PORT"].iteritems()}
7878

7979
# Create inverse mapping between port name and alias
8080
port_alias_to_name_map = {v: k for k, v in port_name_to_alias_map.iteritems()}

tests/cacl/test_cacl_application.py

+144-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ipaddress
2+
import json
23

34
import pytest
45

@@ -11,21 +12,42 @@
1112
pytest.mark.topology('any')
1213
]
1314

15+
@pytest.fixture(scope="module")
16+
def docker_network(duthost):
17+
18+
output = duthost.command("docker inspect bridge")
19+
20+
docker_containers_info = json.loads(output['stdout'])[0]['Containers']
21+
ipam_info = json.loads(output['stdout'])[0]['IPAM']
22+
23+
docker_network = {}
24+
docker_network['bridge'] = {'IPv4Address' : ipam_info['Config'][0]['Gateway'],
25+
'IPv6Address' : ipam_info['Config'][1]['Gateway'] }
26+
27+
docker_network['container'] = {}
28+
for k,v in docker_containers_info.items():
29+
docker_network['container'][v['Name']] = {'IPv4Address' : v['IPv4Address'].split('/')[0], 'IPv6Address' : v['IPv6Address'].split('/')[0]}
30+
31+
return docker_network
32+
1433

1534
# To specify a port range instead of a single port, use iptables format:
1635
# separate start and end ports with a colon, e.g., "1000:2000"
1736
ACL_SERVICES = {
1837
"NTP": {
1938
"ip_protocols": ["udp"],
20-
"dst_ports": ["123"]
39+
"dst_ports": ["123"],
40+
"multi_asic_ns_to_host_fwd": False
2141
},
2242
"SNMP": {
2343
"ip_protocols": ["tcp", "udp"],
24-
"dst_ports": ["161"]
44+
"dst_ports": ["161"],
45+
"multi_asic_ns_to_host_fwd": True
2546
},
2647
"SSH": {
2748
"ip_protocols": ["tcp"],
28-
"dst_ports": ["22"]
49+
"dst_ports": ["22"],
50+
"multi_asic_ns_to_host_fwd": True
2951
}
3052
}
3153

@@ -129,7 +151,7 @@ def get_cacl_tables_and_rules(duthost):
129151
return cacl_tables
130152

131153

132-
def generate_and_append_block_ip2me_traffic_rules(duthost, iptables_rules, ip6tables_rules):
154+
def generate_and_append_block_ip2me_traffic_rules(duthost, iptables_rules, ip6tables_rules, asic_index):
133155
INTERFACE_TABLE_NAME_LIST = [
134156
"LOOPBACK_INTERFACE",
135157
"MGMT_INTERFACE",
@@ -139,8 +161,8 @@ def generate_and_append_block_ip2me_traffic_rules(duthost, iptables_rules, ip6ta
139161
]
140162

141163
# Gather device configuration facts
142-
cfg_facts = duthost.config_facts(host=duthost.hostname, source="persistent")["ansible_facts"]
143-
164+
namespace = duthost.get_namespace_from_asic_id(asic_index)
165+
cfg_facts = duthost.config_facts(host=duthost.hostname, source="persistent", namespace=namespace)["ansible_facts"]
144166
# Add iptables/ip6tables rules to drop all packets destined for peer-to-peer interface IP addresses
145167
for iface_table_name in INTERFACE_TABLE_NAME_LIST:
146168
if iface_table_name in cfg_facts:
@@ -161,7 +183,7 @@ def generate_and_append_block_ip2me_traffic_rules(duthost, iptables_rules, ip6ta
161183
pytest.fail("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))
162184

163185

164-
def generate_expected_rules(duthost):
186+
def generate_expected_rules(duthost, docker_network, asic_index):
165187
iptables_rules = []
166188
ip6tables_rules = []
167189

@@ -177,6 +199,26 @@ def generate_expected_rules(duthost):
177199
iptables_rules.append("-A INPUT -s 127.0.0.1/32 -i lo -j ACCEPT")
178200
ip6tables_rules.append("-A INPUT -s ::1/128 -i lo -j ACCEPT")
179201

202+
if asic_index is None:
203+
# Allow Communication among docker containers
204+
for k, v in docker_network['container'].items():
205+
iptables_rules.append("-A INPUT -s {}/32 -d {}/32 -j ACCEPT".format(docker_network['bridge']['IPv4Address'], docker_network['bridge']['IPv4Address']))
206+
iptables_rules.append("-A INPUT -s {}/32 -d {}/32 -j ACCEPT".format(v['IPv4Address'], docker_network['bridge']['IPv4Address']))
207+
ip6tables_rules.append("-A INPUT -s {}/128 -d {}/128 -j ACCEPT".format(docker_network['bridge']['IPv6Address'], docker_network['bridge']['IPv6Address']))
208+
ip6tables_rules.append("-A INPUT -s {}/128 -d {}/128 -j ACCEPT".format(v['IPv6Address'], docker_network['bridge']['IPv6Address']))
209+
210+
else:
211+
iptables_rules.append("-A INPUT -s {}/32 -d {}/32 -j ACCEPT".format(docker_network['container']['database' + str(asic_index)]['IPv4Address'],
212+
docker_network['container']['database' + str(asic_index)]['IPv4Address']))
213+
iptables_rules.append("-A INPUT -s {}/32 -d {}/32 -j ACCEPT".format(docker_network['bridge']['IPv4Address'],
214+
docker_network['container']['database' + str(asic_index)]['IPv4Address']))
215+
ip6tables_rules.append("-A INPUT -s {}/128 -d {}/128 -j ACCEPT".format(docker_network['container']['database' + str(asic_index)]['IPv6Address'],
216+
docker_network['container']['database' + str(asic_index)]['IPv6Address']))
217+
ip6tables_rules.append("-A INPUT -s {}/128 -d {}/128 -j ACCEPT".format(docker_network['bridge']['IPv6Address'],
218+
docker_network['container']['database' + str(asic_index)]['IPv6Address']))
219+
220+
221+
180222
# Allow all incoming packets from established connections or new connections
181223
# which are related to established connections
182224
iptables_rules.append("-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT")
@@ -299,7 +341,7 @@ def generate_expected_rules(duthost):
299341
rules_applied_from_config += 1
300342

301343
# Append rules which block "ip2me" traffic on p2p interfaces
302-
generate_and_append_block_ip2me_traffic_rules(duthost, iptables_rules, ip6tables_rules)
344+
generate_and_append_block_ip2me_traffic_rules(duthost, iptables_rules, ip6tables_rules, asic_index)
303345

304346
# Allow all packets with a TTL/hop limit of 0 or 1
305347
iptables_rules.append("-A INPUT -m ttl --ttl-lt 2 -j ACCEPT")
@@ -313,19 +355,55 @@ def generate_expected_rules(duthost):
313355

314356
return iptables_rules, ip6tables_rules
315357

358+
def generate_nat_expected_rules(duthost, docker_network, asic_index):
359+
iptables_natrules = []
360+
ip6tables_natrules = []
316361

317-
def test_cacl_application(duthosts, rand_one_dut_hostname, localhost, creds):
318-
"""
319-
Test case to ensure caclmgrd is applying control plane ACLs properly
320-
321-
This is done by generating our own set of expected iptables and ip6tables
322-
rules based on the DuT's configuration and comparing them against the
323-
actual iptables/ip6tables rules on the DuT.
324-
"""
325-
duthost = duthosts[rand_one_dut_hostname]
326-
expected_iptables_rules, expected_ip6tables_rules = generate_expected_rules(duthost)
327-
328-
stdout = duthost.shell("sudo iptables -S")["stdout"]
362+
# Default policies
363+
iptables_natrules.append("-P PREROUTING ACCEPT")
364+
iptables_natrules.append("-P INPUT ACCEPT")
365+
iptables_natrules.append("-P OUTPUT ACCEPT")
366+
iptables_natrules.append("-P POSTROUTING ACCEPT")
367+
ip6tables_natrules.append("-P PREROUTING ACCEPT")
368+
ip6tables_natrules.append("-P INPUT ACCEPT")
369+
ip6tables_natrules.append("-P OUTPUT ACCEPT")
370+
ip6tables_natrules.append("-P POSTROUTING ACCEPT")
371+
372+
373+
for acl_service in ACL_SERVICES:
374+
if ACL_SERVICES[acl_service]["multi_asic_ns_to_host_fwd"]:
375+
for ip_protocol in ACL_SERVICES[acl_service]["ip_protocols"]:
376+
for dst_port in ACL_SERVICES[acl_service]["dst_ports"]:
377+
# IPv4 rules
378+
iptables_natrules.append(
379+
"-A PREROUTING -p {} -m {} --dport {} -j DNAT --to-destination {}".format
380+
(ip_protocol, ip_protocol, dst_port,
381+
docker_network['bridge']['IPv4Address']))
382+
383+
iptables_natrules.append(
384+
"-A POSTROUTING -p {} -m {} --dport {} -j SNAT --to-source {}".format
385+
(ip_protocol, ip_protocol, dst_port,
386+
docker_network['container']['database' + str(asic_index)]['IPv4Address']))
387+
388+
# IPv6 rules
389+
ip6tables_natrules.append(
390+
"-A PREROUTING -p {} -m {} --dport {} -j DNAT --to-destination {}".format
391+
(ip_protocol, ip_protocol, dst_port,
392+
docker_network['bridge']['IPv6Address']))
393+
394+
ip6tables_natrules.append(
395+
"-A POSTROUTING -p {} -m {} --dport {} -j SNAT --to-source {}".format
396+
(ip_protocol,ip_protocol, dst_port,
397+
docker_network['container']['database' + str(asic_index)]['IPv6Address']))
398+
399+
return iptables_natrules, ip6tables_natrules
400+
401+
402+
def verify_cacl(duthost, localhost, creds, docker_network, asic_index = None):
403+
expected_iptables_rules, expected_ip6tables_rules = generate_expected_rules(duthost, docker_network, asic_index)
404+
405+
406+
stdout = duthost.get_asic(asic_index).command("iptables -S")["stdout"]
329407
actual_iptables_rules = stdout.strip().split("\n")
330408

331409
# Ensure all expected iptables rules are present on the DuT
@@ -344,7 +422,7 @@ def test_cacl_application(duthosts, rand_one_dut_hostname, localhost, creds):
344422
#for i in range(len(expected_iptables_rules)):
345423
# pytest_assert(actual_iptables_rules[i] == expected_iptables_rules[i], "iptables rules not in expected order")
346424

347-
stdout = duthost.shell("sudo ip6tables -S")["stdout"]
425+
stdout = duthost.get_asic(asic_index).command("ip6tables -S")["stdout"]
348426
actual_ip6tables_rules = stdout.strip().split("\n")
349427

350428
# Ensure all expected ip6tables rules are present on the DuT
@@ -362,3 +440,48 @@ def test_cacl_application(duthosts, rand_one_dut_hostname, localhost, creds):
362440
# Ensure the ip6tables rules are applied in the correct order
363441
#for i in range(len(expected_ip6tables_rules)):
364442
# pytest_assert(actual_ip6tables_rules[i] == expected_ip6tables_rules[i], "ip6tables rules not in expected order")
443+
444+
def verify_nat_cacl(duthost, localhost, creds, docker_network, asic_index):
445+
expected_iptables_rules, expected_ip6tables_rules = generate_nat_expected_rules(duthost, docker_network, asic_index)
446+
447+
stdout = duthost.get_asic(asic_index).command("iptables -t nat -S")["stdout"]
448+
actual_iptables_rules = stdout.strip().split("\n")
449+
450+
# Ensure all expected iptables rules are present on the DuT
451+
missing_iptables_rules = set(expected_iptables_rules) - set(actual_iptables_rules)
452+
pytest_assert(len(missing_iptables_rules) == 0, "Missing expected iptables nat rules: {}".format(repr(missing_iptables_rules)))
453+
454+
# Ensure there are no unexpected iptables rules present on the DuT
455+
unexpected_iptables_rules = set(actual_iptables_rules) - set(expected_iptables_rules)
456+
pytest_assert(len(unexpected_iptables_rules) == 0, "Unexpected iptables nat rules: {}".format(repr(unexpected_iptables_rules)))
457+
458+
stdout = duthost.get_asic(asic_index).command("ip6tables -t nat -S")["stdout"]
459+
actual_ip6tables_rules = stdout.strip().split("\n")
460+
461+
# Ensure all expected ip6tables rules are present on the DuT
462+
missing_ip6tables_rules = set(expected_ip6tables_rules) - set(actual_ip6tables_rules)
463+
pytest_assert(len(missing_ip6tables_rules) == 0, "Missing expected ip6tables nat rules: {}".format(repr(missing_ip6tables_rules)))
464+
465+
# Ensure there are no unexpected ip6tables rules present on the DuT
466+
unexpected_ip6tables_rules = set(actual_ip6tables_rules) - set(expected_ip6tables_rules)
467+
pytest_assert(len(unexpected_ip6tables_rules) == 0, "Unexpected ip6tables nat rules: {}".format(repr(unexpected_ip6tables_rules)))
468+
469+
def test_cacl_application(duthosts, rand_one_dut_hostname, localhost, creds, docker_network):
470+
"""
471+
Test case to ensure caclmgrd is applying control plane ACLs properly
472+
473+
This is done by generating our own set of expected iptables and ip6tables
474+
rules based on the DuT's configuration and comparing them against the
475+
actual iptables/ip6tables rules on the DuT.
476+
"""
477+
duthost = duthosts[rand_one_dut_hostname]
478+
verify_cacl(duthost, localhost, creds, docker_network)
479+
480+
def test_multiasic_cacl_application(duthosts, rand_one_dut_hostname, localhost, creds,docker_network, enum_frontend_asic_index):
481+
482+
if enum_frontend_asic_index is None:
483+
pytest.skip("Not Multi-asic platform. Skipping !!")
484+
485+
duthost = duthosts[rand_one_dut_hostname]
486+
verify_cacl(duthost, localhost, creds, docker_network, enum_frontend_asic_index)
487+
verify_nat_cacl(duthost, localhost, creds, docker_network, enum_frontend_asic_index)

tests/common/devices.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -2156,21 +2156,21 @@ def create_ssh_tunnel_sai_rpc(self):
21562156
).format(ns_docker_if_ipv4)
21572157
)
21582158

2159-
def command(self, *args, **kwargs):
2159+
def command(self, cmdstr):
21602160
"""
21612161
Prepend 'ip netns' option for commands meant for this ASIC
21622162
21632163
Args:
2164-
*args and **kwargs
2164+
cmdstr
21652165
Returns:
21662166
Output from the ansible command module
21672167
"""
2168-
if not self.sonichost.is_multi_asic:
2169-
return self.sonichost.command(*args, **kwargs)
2168+
if not self.sonichost.is_multi_asic or self.namespace == DEFAULT_NAMESPACE:
2169+
return self.sonichost.command(cmdstr)
2170+
2171+
cmdstr = "sudo ip netns exec {} ".format(self.namespace) + cmdstr
21702172

2171-
ns_arg_list = ["ip", "netns", "exec", self.namespace]
2172-
kwargs["argv"] = ns_arg_list + kwargs["argv"]
2173-
return self.sonichost.command(*args, **kwargs)
2173+
return self.sonichost.command(cmdstr)
21742174

21752175
def run_redis_cmd(self, argv=[]):
21762176
"""
@@ -2326,7 +2326,7 @@ def get_asic_namespace_list(self):
23262326
return [asic.namespace for asic in self.asics]
23272327

23282328
def get_asic_id_from_namespace(self, namespace):
2329-
if self.sonichost.facts['num_asic'] == 1:
2329+
if self.sonichost.facts['num_asic'] == 1 or namespace == DEFAULT_NAMESPACE:
23302330
return DEFAULT_ASIC_ID
23312331

23322332
for asic in self.asics:
@@ -2337,7 +2337,7 @@ def get_asic_id_from_namespace(self, namespace):
23372337
raise ValueError("Invalid namespace '{}' passed as input".format(namespace))
23382338

23392339
def get_namespace_from_asic_id(self, asic_id):
2340-
if self.sonichost.facts['num_asic'] == 1:
2340+
if self.sonichost.facts['num_asic'] == 1 or asic_id == DEFAULT_ASIC_ID:
23412341
return DEFAULT_NAMESPACE
23422342

23432343
for asic in self.asics:
@@ -2384,7 +2384,7 @@ def __getattr__(self, attr):
23842384

23852385
def get_asic(self, asic_id):
23862386
if asic_id == DEFAULT_ASIC_ID:
2387-
return self.asics[0]
2387+
return self.sonichost
23882388
return self.asics[asic_id]
23892389

23902390
def stop_service(self, service):

0 commit comments

Comments
 (0)