Skip to content

Commit

Permalink
Merge pull request #50 from dgsudharsan/vxlan_allow
Browse files Browse the repository at this point in the history
[caclmgrd]Allow Vxlan udp port on receiving vxlan tunnel configuration
  • Loading branch information
prsunny authored and StormLiangMS committed Apr 20, 2023
1 parent 63fed10 commit b1f3b21
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 0 deletions.
66 changes: 66 additions & 0 deletions scripts/caclmgrd
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE"

BFD_SESSION_TABLE = "BFD_SESSION_TABLE"
VXLAN_TUNNEL_TABLE = "VXLAN_TUNNEL"

# To specify a port range instead of a single port, use iptables format:
# separate start and end ports with a colon, e.g., "1000:2000"
Expand Down Expand Up @@ -115,6 +116,8 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):

DualToR = False
bfdAllowed = False
VxlanAllowed = False
VxlanSrcIP = ""

def __init__(self, log_identifier):
super(ControlPlaneAclManager, self).__init__(log_identifier)
Expand Down Expand Up @@ -817,6 +820,54 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-I', 'INPUT', '2', '-p', 'udp', '-m', 'multiport', '--dports', '3784,4784', '-j', 'ACCEPT'])
self.run_commands(iptables_cmds)

def allow_vxlan_port(self, namespace, data):
for fv in data:
if (fv[0] == "src_ip"):
self.VxlanSrcIP = fv[1]
break

if not self.VxlanSrcIP:
self.log_info("Received vxlan tunnel configuration without source ip")
return False

iptables_cmds = []

# Add iptables/ip6tables commands to allow VxLAN packets
ip_addr = ipaddress.ip_address(self.VxlanSrcIP)
if isinstance(ip_addr, ipaddress.IPv6Address):
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
['ip6tables', '-I', 'INPUT', '2', '-p', 'udp', '-d', self.VxlanSrcIP, '--dport', '4789', '-j', 'ACCEPT'])
elif isinstance(ip_addr, ipaddress.IPv4Address):
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
['iptables', '-I', 'INPUT', '2', '-p', 'udp', '-d', self.VxlanSrcIP, '--dport', '4789', '-j', 'ACCEPT'])

self.run_commands(iptables_cmds)
self.log_info("Enabled vxlan port for source ip " + self.VxlanSrcIP)
self.VxlanAllowed = True
return True

def block_vxlan_port(self, namespace):
if not self.VxlanSrcIP:
self.log_info("Cannot remove vxlan tunnel configuration without source ip")
return False

iptables_cmds = []

# Remove iptables/ip6tables commands that allow VxLAN packets
ip_addr = ipaddress.ip_address(self.VxlanSrcIP)
if isinstance(ip_addr, ipaddress.IPv6Address):
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
['ip6tables', '-D', 'INPUT', '-p', 'udp', '-d', self.VxlanSrcIP, '--dport', '4789', '-j', 'ACCEPT'])
elif isinstance(ip_addr, ipaddress.IPv4Address):
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
['iptables', '-D', 'INPUT', '-p', 'udp', '-d', self.VxlanSrcIP, '--dport', '4789', '-j', 'ACCEPT'])

self.run_commands(iptables_cmds)
self.VxlanAllowed = False
self.log_info("Disabled vxlan port for source ip " + self.VxlanSrcIP)
self.VxlanSrcIP = ""
return True

def run(self):
# Set select timeout to 1 second
SELECT_TIMEOUT_MS = 1000
Expand All @@ -837,13 +888,16 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):

# Set up STATE_DB connector to monitor the change in MUX_CABLE_TABLE
state_db_connector = None
config_db_connector = None
subscribe_mux_cable = None
subscribe_dhcp_packet_mark = None
state_db_id = swsscommon.SonicDBConfig.getDbId("STATE_DB")
config_db_id = swsscommon.SonicDBConfig.getDbId("CONFIG_DB")
dhcp_packet_mark_tbl = {}

# set up state_db connector
state_db_connector = swsscommon.DBConnector("STATE_DB", 0)
config_db_connector = swsscommon.DBConnector("CONFIG_DB", 0)

if self.DualToR:
self.log_info("Dual ToR mode")
Expand All @@ -862,6 +916,8 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
subscribe_bfd_session = swsscommon.SubscriberStateTable(state_db_connector, self.BFD_SESSION_TABLE)
sel.addSelectable(subscribe_bfd_session)

subscribe_vxlan_table = swsscommon.SubscriberStateTable(config_db_connector, self.VXLAN_TUNNEL_TABLE)
sel.addSelectable(subscribe_vxlan_table)
# Map of Namespace <--> susbcriber table's object
config_db_subscriber_table_map = {}

Expand Down Expand Up @@ -936,6 +992,16 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
self.update_dhcp_acl(key, op, dict(fvs), mark)
continue

if db_id == config_db_id:
while True:
key, op, fvs = subscribe_vxlan_table.pop()
if not key:
break
if op == 'SET' and not self.VxlanAllowed:
self.allow_vxlan_port(namespace, fvs)
elif op == 'DEL' and self.VxlanAllowed:
self.block_vxlan_port(namespace)

ctrl_plane_acl_notification = set()

# Pop data of both Subscriber Table object of namespace that got config db acl table event
Expand Down
58 changes: 58 additions & 0 deletions tests/caclmgrd/caclmgrd_vxlan_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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_vxlan_vectors import CACLMGRD_VXLAN_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 TestCaclmgrdVxlan(TestCase):
"""
Test caclmgrd vxlan
"""
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)

@parameterized.expand(CACLMGRD_VXLAN_TEST_VECTOR)
@patchfs
def test_caclmgrd_vxlan(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.ControlPlaneAclManager.run_commands_pipe", return_value='sonic'):
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")
ret = caclmgrd_daemon.allow_vxlan_port('', [])
assert ret == False
caclmgrd_daemon.block_vxlan_port('')
assert ret == False
data = test_data["input"]
caclmgrd_daemon.allow_vxlan_port('', data)
mocked_subprocess.Popen.assert_has_calls(test_data["expected_add_subprocess_calls"], any_order=True)
caclmgrd_daemon.block_vxlan_port('')
mocked_subprocess.Popen.assert_has_calls(test_data["expected_del_subprocess_calls"], any_order=True)

66 changes: 66 additions & 0 deletions tests/caclmgrd/test_vxlan_vectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from unittest.mock import call
import subprocess

"""
caclmgrd bfd test vector
"""
CACLMGRD_VXLAN_TEST_VECTOR = [
[
"VXLAN_TUNNEL_TEST_V4",
{
"config_db": {
"DEVICE_METADATA": {
"localhost": {
"subtype": "DualToR",
"type": "ToRRouter",
}
},
"FEATURE": {
"bgp": {
"auto_restart": "enabled",
"state": "enabled",
}
},
},
"input" : [("src_ip", "10.1.1.1")],
"expected_add_subprocess_calls": [
call(['iptables', '-I', 'INPUT', '2', '-p', 'udp', '-d', '10.1.1.1', '--dport', '4789', '-j', 'ACCEPT'], universal_newlines=True, stdout=subprocess.PIPE)],
"expected_del_subprocess_calls": [
call(['iptables', '-D', 'INPUT', '-p', 'udp', '-d', '10.1.1.1', '--dport', '4789', '-j', 'ACCEPT'], universal_newlines=True, stdout=subprocess.PIPE)
],
"popen_attributes": {
'communicate.return_value': ('output', 'error'),
},
"call_rc": 0,
}
],
[
"VXLAN_TUNNEL_TEST_V6",
{
"config_db": {
"DEVICE_METADATA": {
"localhost": {
"subtype": "DualToR",
"type": "ToRRouter",
}
},
"FEATURE": {
"bgp": {
"auto_restart": "enabled",
"state": "enabled",
}
},
},
"input" : [("src_ip", "2001::1")],
"expected_add_subprocess_calls": [
call(['ip6tables', '-I', 'INPUT', '2', '-p', 'udp', '-d', '2001::1', '--dport', '4789', '-j', 'ACCEPT'], universal_newlines=True, stdout=subprocess.PIPE)],
"expected_del_subprocess_calls": [
call(['ip6tables', '-D', 'INPUT', '-p', 'udp', '-d', '2001::1', '--dport', '4789', '-j', 'ACCEPT'], universal_newlines=True, stdout=subprocess.PIPE)
],
"popen_attributes": {
'communicate.return_value': ('output', 'error'),
},
"call_rc": 0,
}
]
]

0 comments on commit b1f3b21

Please sign in to comment.