[caclmgrd][dualtor] add iptables rule for dualtor gRPC to allow packets getting forwarded from loopback IP (#11458)

This PR is a required for changing the L3 IP forwarding Behavior to SoC in active-active toplogy. Basically, for getting a packet to be forwarded to the SoC IP in active-active topology, the requirement is to use the the LoopBack 3 IP inside SONiC device as the SRC IP. This is required because in active-active topology by default if the ToR wants to send packet to the SoC, it would pick the Vlan IP since that's the IP in the subnet, but since there are firewalls inside the SoC , the IP packets with Vlan IP as src IP in the IP header will be dropped. Hence to overcome this limitation, there is an iptable nat rule that is installed inside the kernel, with which all the packets which have SoC IP as destination IP, use Loopnack 3 IP as src in IP header

How I did it
check the config DB if the ToR is a DualToR and has an SoC IP assigned.
put an iptable rule
iptables -t nat -A POSTROUTING --destination -j SNAT --to-source "
Signed-off-by: vaibhav-dahiya vdahiya@microsoft.com
This commit is contained in:
vdahiya12 2022-07-20 09:00:28 -07:00 committed by GitHub
parent 3e9c1d16c1
commit 3829faf2c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 139 additions and 0 deletions

View File

@ -41,6 +41,22 @@ def _ip_prefix_in_key(key):
"""
return (isinstance(key, tuple))
def get_ip_from_interface_table(table, intf_name):
if table:
for key, _ in table.items():
if not _ip_prefix_in_key(key):
continue
iface_name, iface_cidr = key
if iface_name.startswith(intf_name):
ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False)
if isinstance(ip_ntwrk, ipaddress.IPv4Network):
ip_addr = ip_ntwrk.network_address
return ip_addr
return None
# ============================== Classes ==============================
@ -57,6 +73,8 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
ACL_RULE = "ACL_RULE"
DEVICE_METADATA_TABLE = "DEVICE_METADATA"
MUX_CABLE_TABLE = "MUX_CABLE_TABLE"
CONFIG_MUX_CABLE = "MUX_CABLE"
LOOPBACK_TABLE = "LOOPBACK_INTERFACE"
ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE"
@ -284,6 +302,28 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
return allow_internal_docker_ip_cmds
def generate_fwd_traffic_from_host_to_soc(self, namespace):
fwd_dualtor_grpc_traffic_from_host_to_soc_cmds = []
if self.DualToR:
loopback_table = self.config_db_map[DEFAULT_NAMESPACE].get_table(self.LOOPBACK_TABLE)
loopback_name = 'Loopback3'
loopback_address = get_ip_from_interface_table(loopback_table, loopback_name)
fwd_dualtor_grpc_traffic_from_host_to_soc_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
"iptables -t nat --flush POSTROUTING")
if loopback_address is not None:
mux_table = self.config_db_map[DEFAULT_NAMESPACE].get_table(self.CONFIG_MUX_CABLE)
mux_table_keys = mux_table.keys()
for key in mux_table_keys:
kvp = mux_table.get(key)
if 'cable_type' in kvp and kvp['cable_type'] == 'active-active':
fwd_dualtor_grpc_traffic_from_host_to_soc_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
"iptables -t nat -A POSTROUTING --destination {} -j SNAT --to-source {}".format(kvp['soc_ipv4'], loopback_address))
return fwd_dualtor_grpc_traffic_from_host_to_soc_cmds
def generate_fwd_traffic_from_namespace_to_host_commands(self, namespace, acl_source_ip_map):
"""
The below SNAT and DNAT rules are added in asic namespace in multi-ASIC platforms. It helps to forward request coming
@ -685,6 +725,13 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
self.run_commands(iptables_cmds)
if self.DualToR:
dualtor_iptables_cmds = self.generate_fwd_traffic_from_host_to_soc(namespace)
for cmd in dualtor_iptables_cmds:
self.log_info(" " + cmd)
self.run_commands(dualtor_iptables_cmds)
def check_and_update_control_plane_acls(self, namespace, num_changes):
"""
This function is intended to be spawned in a separate thread.

View File

@ -0,0 +1,51 @@
import os
import sys
import swsscommon
from parameterized import parameterized
from sonic_py_common.general import load_module_from_source
from unittest import TestCase, mock
from pyfakefs.fake_filesystem_unittest import patchfs
from .test_soc_rules_vectors import CACLMGRD_SOC_TEST_VECTOR
from tests.common.mock_configdb import MockConfigDb
from unittest.mock import MagicMock, patch
DBCONFIG_PATH = '/var/run/redis/sonic-db/database_config.json'
class TestCaclmgrdSoc(TestCase):
"""
Test caclmgrd soc
"""
def setUp(self):
swsscommon.swsscommon.ConfigDBConnector = MockConfigDb
test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
modules_path = os.path.dirname(test_path)
scripts_path = os.path.join(modules_path, "scripts")
sys.path.insert(0, modules_path)
caclmgrd_path = os.path.join(scripts_path, 'caclmgrd')
self.caclmgrd = load_module_from_source('caclmgrd', caclmgrd_path)
@parameterized.expand(CACLMGRD_SOC_TEST_VECTOR)
@patchfs
@patch('caclmgrd.get_ip_from_interface_table', MagicMock(return_value="10.10.10.10"))
def test_caclmgrd_soc(self, test_name, test_data, fs):
if not os.path.exists(DBCONFIG_PATH):
fs.create_file(DBCONFIG_PATH) # fake database_config.json
MockConfigDb.set_config_db(test_data["config_db"])
with mock.patch("caclmgrd.subprocess") as mocked_subprocess:
popen_mock = mock.Mock()
popen_attrs = test_data["popen_attributes"]
popen_mock.configure_mock(**popen_attrs)
mocked_subprocess.Popen.return_value = popen_mock
mocked_subprocess.PIPE = -1
call_rc = test_data["call_rc"]
mocked_subprocess.call.return_value = call_rc
caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd")
caclmgrd_daemon.update_control_plane_nat_acls('', {})
mocked_subprocess.Popen.assert_has_calls(test_data["expected_subprocess_calls"], any_order=True)

View File

@ -0,0 +1,41 @@
from unittest.mock import call
import subprocess
"""
caclmgrd soc test vector
"""
CACLMGRD_SOC_TEST_VECTOR = [
[
"SOC_SESSION_TEST",
{
"config_db": {
"DEVICE_METADATA": {
"localhost": {
"subtype": "DualToR",
"type": "ToRRouter",
}
},
"MUX_CABLE": {
"Ethernet4": {
"cable_type": "active-active",
"soc_ipv4": "192.168.1.0/32",
}
},
"LOOPBACK_INTERFACE": {
"Loopback3|10.10.10.10/32": {
"NULL": "NULL",
}
},
"FEATURE": {
},
},
"expected_subprocess_calls": [
call("iptables -t nat -A POSTROUTING --destination 192.168.1.0/32 -j SNAT --to-source 10.10.10.10",shell=True, universal_newlines=True, stdout=-1)
],
"popen_attributes": {
'communicate.return_value': ('output', 'error'),
},
"call_rc": 0,
}
]
]