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

caclmgrd: correct IP2ME address logic #9826

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
33 changes: 22 additions & 11 deletions src/sonic-host-services/scripts/caclmgrd
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,10 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
return tcp_flags_str

def generate_block_ip2me_traffic_iptables_commands(self, namespace):
# We do not block MGMT_INTERFACE IPs by default to allow users
# to manage the box with default configuration.
INTERFACE_TABLE_NAME_LIST = [
"LOOPBACK_INTERFACE",
"MGMT_INTERFACE",
"VLAN_INTERFACE",
"PORTCHANNEL_INTERFACE",
"INTERFACE"
Expand All @@ -215,20 +216,30 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
for key, _ in iface_table.items():
if not _ip_prefix_in_key(key):
continue

iface_name, iface_cidr = key
ip_iface = ipaddress.ip_interface(iface_cidr)
ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False)
ip_first_addr = next(iter(ip_ntwrk.hosts()), None)

# For VLAN interfaces, the IP address we want to block is the default gateway (i.e.,
bluecmd marked this conversation as resolved.
Show resolved Hide resolved
# the first available host IP address of the VLAN subnet)
ip_addr = next(ip_ntwrk.hosts()) if iface_table_name == "VLAN_INTERFACE" else ip_ntwrk.network_address

if isinstance(ip_ntwrk, ipaddress.IPv4Network):
block_ip2me_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -d {}/{} -j DROP".format(ip_addr, ip_ntwrk.max_prefixlen))
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
block_ip2me_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -d {}/{} -j DROP".format(ip_addr, ip_ntwrk.max_prefixlen))
if isinstance(ip_iface, ipaddress.IPv4Interface):
block_ip2me_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -d {} -j DROP -m comment --comment 'Block IP2ME on interface {}'".format(ip_iface.ip, iface_name))
elif isinstance(ip_iface, ipaddress.IPv6Interface):
block_ip2me_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -d {} -j DROP -m comment --comment 'Block IP2ME on interface {}'".format(ip_iface.ip, iface_name))
else:
self.log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))
self.log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_iface))

# For more information about this part, see
# https://github.com/Azure/sonic-buildimage/pull/9826
# For VLAN interfaces, we additionally block the first IP
# in the subnet.
if (iface_table_name == "VLAN_INTERFACE" and
ip_first_addr is not None and ip_first_addr != ip_iface.ip):
if isinstance(ip_ntwrk, ipaddress.IPv4Network):
block_ip2me_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -d {} -j DROP -m comment --comment 'Block IP2ME (first IP) on interface {}'".format(ip_first_addr, iface_name))
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
block_ip2me_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -d {} -j DROP -m comment --comment 'Block IP2ME (first IP) on interface {}'".format(ip_first_addr, iface_name))
else:
self.log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))

return block_ip2me_cmds

Expand Down
21 changes: 11 additions & 10 deletions src/sonic-host-services/tests/caclmgrd/caclmgrd_dhcp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@
DBCONFIG_PATH = '/var/run/redis/sonic-db/database_config.json'


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')
caclmgrd = load_module_from_source('caclmgrd', caclmgrd_path)


class TestCaclmgrdDhcp(TestCase):
"""
Test caclmgrd dhcp
"""
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_DHCP_TEST_VECTOR)
@patchfs
def test_caclmgrd_dhcp(self, test_name, test_data, fs):
Expand All @@ -46,7 +47,7 @@ def test_caclmgrd_dhcp(self, test_name, test_data, fs):

mark = test_data["mark"]

caclmgrd_daemon = caclmgrd.ControlPlaneAclManager("caclmgrd")
caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd")
mux_update = test_data["mux_update"]

for key,data in mux_update:
Expand Down
42 changes: 42 additions & 0 deletions src/sonic-host-services/tests/caclmgrd/caclmgrd_ip2me_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import os
import sys

from swsscommon 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_ip2me_vectors import CACLMGRD_IP2ME_TEST_VECTOR
from tests.common.mock_configdb import MockConfigDb


DBCONFIG_PATH = '/var/run/redis/sonic-db/database_config.json'


class TestCaclmgrdIP2Me(TestCase):
"""
Test caclmgrd IP2Me
"""
def setUp(self):
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)
self.maxDiff = None

@parameterized.expand(CACLMGRD_IP2ME_TEST_VECTOR)
@patchfs
def test_caclmgrd_ip2me(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"])
self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ip = mock.MagicMock()
self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ipv6 = mock.MagicMock()
caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd")
ret = caclmgrd_daemon.generate_block_ip2me_traffic_iptables_commands('')
self.assertListEqual(test_data["return"], ret)
159 changes: 159 additions & 0 deletions src/sonic-host-services/tests/caclmgrd/test_ip2me_vectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
from unittest.mock import call

"""
caclmgrd ip2me block test vector
"""
CACLMGRD_IP2ME_TEST_VECTOR = [
[
"Only MGMT interface, no block",
{
"config_db": {
"MGMT_INTERFACE": {
"eth0|172.18.0.100/24": {
"gwaddr": "172.18.0.1"
}
},
"LOOPBACK_INTERFACE": {},
"VLAN_INTERFACE": {},
"PORTCHANNEL_INTERFACE": {},
"INTERFACE": {},
"DEVICE_METADATA": {
"localhost": {
}
},
},
"return": [
],
},
],
[
"One interface of each type, /32 - block all interfaces but MGMT",
{
"config_db": {
"LOOPBACK_INTERFACE": {
"Loopback0|10.10.10.10/32": {},
},
"VLAN_INTERFACE": {
"Vlan110|10.10.11.10/32": {},
},
"PORTCHANNEL_INTERFACE": {
"PortChannel0001|10.10.12.10/32": {},
},
"INTERFACE": {
"Ethernet0|10.10.13.10/32": {}
},
"MGMT_INTERFACE": {
"eth0|172.18.0.100/24": {
"gwaddr": "172.18.0.1"
}
},
"DEVICE_METADATA": {
"localhost": {
}
},
},
"return": [
"iptables -A INPUT -d 10.10.10.10 -j DROP -m comment --comment 'Block IP2ME on interface Loopback0'",
"iptables -A INPUT -d 10.10.11.10 -j DROP -m comment --comment 'Block IP2ME on interface Vlan110'",
"iptables -A INPUT -d 10.10.12.10 -j DROP -m comment --comment 'Block IP2ME on interface PortChannel0001'",
"iptables -A INPUT -d 10.10.13.10 -j DROP -m comment --comment 'Block IP2ME on interface Ethernet0'"
],
},
],
[
"One interface of each type, /25 - block all interfaces but MGMT",
{
"config_db": {
"LOOPBACK_INTERFACE": {
"Loopback0|10.10.10.150/25": {},
},
"VLAN_INTERFACE": {
"Vlan110|10.10.11.100/25": {},
},
"PORTCHANNEL_INTERFACE": {
"PortChannel0001|10.10.12.100/25": {},
},
"INTERFACE": {
"Ethernet0|10.10.13.1/25": {}
},
"MGMT_INTERFACE": {
"eth0|172.18.0.100/24": {
"gwaddr": "172.18.0.1"
}
},
"DEVICE_METADATA": {
"localhost": {
}
},
},
"return": [
"iptables -A INPUT -d 10.10.10.150 -j DROP -m comment --comment 'Block IP2ME on interface Loopback0'",
"iptables -A INPUT -d 10.10.11.100 -j DROP -m comment --comment 'Block IP2ME on interface Vlan110'",
"iptables -A INPUT -d 10.10.11.1 -j DROP -m comment --comment 'Block IP2ME (first IP) on interface Vlan110'",
"iptables -A INPUT -d 10.10.12.100 -j DROP -m comment --comment 'Block IP2ME on interface PortChannel0001'",
"iptables -A INPUT -d 10.10.13.1 -j DROP -m comment --comment 'Block IP2ME on interface Ethernet0'"
],
},
],
[
"One VLAN interface, /24, we are .1 -- regression testing to ensure compatibility with old behavior",
{
"config_db": {
"MGMT_INTERFACE": {
"eth0|172.18.0.100/24": {
"gwaddr": "172.18.0.1"
}
},
"LOOPBACK_INTERFACE": {},
"VLAN_INTERFACE": {
"Vlan110|10.10.11.1/24": {},
},
"PORTCHANNEL_INTERFACE": {},
"INTERFACE": {},
"DEVICE_METADATA": {
"localhost": {
}
},
},
"return": [
"iptables -A INPUT -d 10.10.11.1 -j DROP -m comment --comment 'Block IP2ME on interface Vlan110'",
],
},
],
[
"One interface of each type, IPv6, /64 - block all interfaces but MGMT",
{
"config_db": {
"LOOPBACK_INTERFACE": {
"Loopback0|2001:db8:10::/64": {},
},
"VLAN_INTERFACE": {
"Vlan110|2001:db8:11::/64": {},
},
"PORTCHANNEL_INTERFACE": {
"PortChannel0001|2001:db8:12::/64": {},
},
"INTERFACE": {
"Ethernet0|2001:db8:13::/64": {}
},
"MGMT_INTERFACE": {
"eth0|2001:db8:200::200/64": {
"gwaddr": "2001:db8:200::100"
}
},
"DEVICE_METADATA": {
"localhost": {
}
},
},
"return": [
"ip6tables -A INPUT -d 2001:db8:10:: -j DROP -m comment --comment 'Block IP2ME on interface Loopback0'",
"ip6tables -A INPUT -d 2001:db8:11:: -j DROP -m comment --comment 'Block IP2ME on interface Vlan110'",
"ip6tables -A INPUT -d 2001:db8:11::1 -j DROP -m comment --comment 'Block IP2ME (first IP) on interface Vlan110'",
"ip6tables -A INPUT -d 2001:db8:12:: -j DROP -m comment --comment 'Block IP2ME on interface PortChannel0001'",
"ip6tables -A INPUT -d 2001:db8:13:: -j DROP -m comment --comment 'Block IP2ME on interface Ethernet0'"
],
},
]

]
5 changes: 4 additions & 1 deletion src/sonic-host-services/tests/common/mock_configdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ def set_entry(self, key, field, data):
MockConfigDb.CONFIG_DB[key][field] = data

def get_table(self, table_name):
return MockConfigDb.CONFIG_DB[table_name]
data = {}
for k, v in MockConfigDb.CONFIG_DB[table_name].items():
data[self.deserialize_key(k)] = v
return data

def subscribe(self, table_name, callback):
self.handlers[table_name] = callback
Expand Down