summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2021-03-19 17:09:59 +0100
committerChristian Poessinger <christian@poessinger.com>2021-03-19 17:09:59 +0100
commit5717ece9dc79b43a2a319eaf592e4215c8d722c8 (patch)
treec96cc6d11e06093f806761c26cda44b82b738a9c
parentfbc10c8ccdcba6b8625cc8286d59bd854e8b30f2 (diff)
downloadvyos-1x-5717ece9dc79b43a2a319eaf592e4215c8d722c8.tar.gz
vyos-1x-5717ece9dc79b43a2a319eaf592e4215c8d722c8.zip
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
-rw-r--r--interface-definitions/interfaces-bridge.xml.in6
-rw-r--r--python/vyos/ifconfig/bridge.py6
-rw-r--r--python/vyos/ifconfig/interface.py18
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py52
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 @@
</properties>
<defaultValue>32</defaultValue>
</leafNode>
+ <leafNode name="isolated">
+ <properties>
+ <help>Port is isolated (also known as Private-VLAN)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
</children>
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)
-