From 5717ece9dc79b43a2a319eaf592e4215c8d722c8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 19 Mar 2021 17:09:59 +0100 Subject: bridge: T3415: add port isolation / private-vlan option Private VLAN, also known as port isolation, is a technique in computer networking where a VLAN contains switch ports that are restricted such that they can only communicate with a given "uplink". The restricted ports are called "private ports". Each private VLAN typically contains many private ports, and a single uplink. The uplink will typically be a port (or link aggregation group) connected to a router, firewall, server, provider network, or similar central resource. Q: https://en.wikipedia.org/wiki/Private_VLAN --- interface-definitions/interfaces-bridge.xml.in | 6 +++ python/vyos/ifconfig/bridge.py | 6 +++ python/vyos/ifconfig/interface.py | 18 +++++++++ smoketest/scripts/cli/test_interfaces_bridge.py | 52 ++++++++++++++++++------- 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index c6de58424..1af002142 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -178,6 +178,12 @@ 32 + + + Port is isolated (also known as Private-VLAN) + + + diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 600bd3db8..14f64a8de 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -312,9 +312,15 @@ class BridgeIf(Interface): # not have any addresses configured by CLI so just flush any # remaining ones lower.flush_addrs() + # enslave interface port to bridge self.add_port(interface) + # always set private-vlan/port isolation + tmp = dict_search('isolated', interface_config) + value = 'on' if (tmp != None) else 'off' + lower.set_port_isolation(value) + # set bridge port path cost if 'cost' in interface_config: value = interface_config.get('cost') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index fe6a3c95e..5a1605a18 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -113,6 +113,10 @@ class Interface(Control): 'convert': lambda name: name if name else '', 'shellcmd': 'ip link set dev {ifname} alias "{value}"', }, + 'bridge_port_isolation': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'shellcmd': 'bridge link set dev {ifname} isolated {value}', + }, 'mac': { 'validate': assert_mac, 'shellcmd': 'ip link set dev {ifname} address {value}', @@ -689,6 +693,20 @@ class Interface(Control): """ self.set_interface('path_priority', priority) + def set_port_isolation(self, on_or_off): + """ + Controls whether a given port will be isolated, which means it will be + able to communicate with non-isolated ports only. By default this flag + is off. + + Use enable=1 to enable or enable=0 to disable + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').set_port_isolation('on') + """ + self.set_interface('bridge_port_isolation', on_or_off) + def set_proxy_arp(self, enable): """ Set per interface proxy ARP configuration diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 5899d1620..21f20c781 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -25,6 +25,7 @@ from netifaces import interfaces from vyos.ifconfig import Section from vyos.util import cmd from vyos.util import read_file +from vyos.util import get_interface_config from vyos.validate import is_intf_addr_assigned class BridgeInterfaceTest(BasicInterfaceTest.TestCase): @@ -62,6 +63,32 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): super().tearDown() + def test_isolated_interfaces(self): + # Add member interfaces to bridge and set STP cost/priority + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['stp']) + + # assign members to bridge interface + for member in self._members: + base_member = base + ['member', 'interface', member] + self.cli_set(base_member + ['isolated']) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + tmp = get_interface_config(interface) + # STP must be enabled as configured above + self.assertEqual(1, tmp['linkinfo']['info_data']['stp_state']) + + # validate member interface configuration + for member in self._members: + tmp = get_interface_config(member) + # Isolated must be enabled as configured above + self.assertTrue(tmp['linkinfo']['info_slave_data']['isolated']) + + def test_add_remove_bridge_member(self): # Add member interfaces to bridge and set STP cost/priority for interface in self._interfaces: @@ -82,19 +109,20 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): # commit config self.cli_commit() - # check member interfaces are added on the bridge - bridge_members = [] - for tmp in glob(f'/sys/class/net/{interface}/lower_*'): - bridge_members.append(os.path.basename(tmp).replace('lower_', '')) - - for member in self._members: - self.assertIn(member, bridge_members) - - # delete all members + # Add member interfaces to bridge and set STP cost/priority for interface in self._interfaces: - self.cli_delete(self._base_path + [interface, 'member']) + cost = 1000 + priority = 10 + for member in self._members: + tmp = get_interface_config(member) + self.assertEqual(interface, tmp['master']) + self.assertFalse( tmp['linkinfo']['info_slave_data']['isolated']) + self.assertEqual(cost, tmp['linkinfo']['info_slave_data']['cost']) + self.assertEqual(priority, tmp['linkinfo']['info_slave_data']['priority']) + + cost += 1 + priority += 1 - self.cli_commit() def test_vif_8021q_interfaces(self): for interface in self._interfaces: @@ -109,7 +137,6 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): super().test_vif_8021q_interfaces() def test_bridge_vlan_filter(self): - vif_vlan = 2 # Add member interface to bridge and set VLAN filter for interface in self._interfaces: @@ -217,4 +244,3 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): if __name__ == '__main__': unittest.main(verbosity=2) - -- cgit v1.2.3