diff options
26 files changed, 367 insertions, 97 deletions
diff --git a/data/templates/firewall/nftables-bridge.j2 b/data/templates/firewall/nftables-bridge.j2 index 7f94e10d6..dec027bf9 100644 --- a/data/templates/firewall/nftables-bridge.j2 +++ b/data/templates/firewall/nftables-bridge.j2 @@ -2,9 +2,8 @@ {% set ns = namespace(sets=[]) %} {% if bridge.forward is vyos_defined %} {% for prior, conf in bridge.forward.items() %} -{% set def_action = conf.default_action %} chain VYOS_FORWARD_{{ prior }} { - type filter hook forward priority {{ prior }}; policy {{ def_action }}; + type filter hook forward priority {{ prior }}; policy accept; {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('FWD', prior, rule_id, 'bri') }} @@ -13,6 +12,7 @@ {% endif %} {% endfor %} {% endif %} + {{ conf | nft_default_rule('FWD-filter', 'bri') }} } {% endfor %} {% endif %} @@ -28,7 +28,7 @@ {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule(name_text) }} + {{ conf | nft_default_rule(name_text, 'bri') }} } {% endfor %} {% endif %} diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2 index 1e9351f97..beb14ff00 100644 --- a/data/templates/firewall/nftables-zone.j2 +++ b/data/templates/firewall/nftables-zone.j2 @@ -1,7 +1,13 @@ -{% macro zone_chains(zone, ipv6=False) %} -{% set fw_name = 'ipv6_name' if ipv6 else 'name' %} -{% set suffix = '6' if ipv6 else '' %} +{% macro zone_chains(zone, family) %} +{% if family == 'ipv6' %} +{% set fw_name = 'ipv6_name' %} +{% set suffix = '6' %} +{% else %} +{% set fw_name = 'name' %} +{% set suffix = '' %} +{% endif %} + chain VYOS_ZONE_FORWARD { type filter hook forward priority 1; policy accept; {% for zone_name, zone_conf in zone.items() %} @@ -36,7 +42,7 @@ iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} {% endif %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} + {{ zone_conf | nft_default_rule('zone_' + zone_name, family) }} } chain VZONE_{{ zone_name }}_OUT { oifname lo counter return @@ -46,7 +52,7 @@ oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} {% endif %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} + {{ zone_conf | nft_default_rule('zone_' + zone_name, family) }} } {% else %} chain VZONE_{{ zone_name }} { @@ -62,7 +68,7 @@ {% endif %} {% endfor %} {% endif %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} + {{ zone_conf | nft_default_rule('zone_' + zone_name, family) }} } {% endif %} {% endfor %} diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index e24a9655d..63195d25f 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -54,7 +54,7 @@ table ip vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule('FWD-filter') }} + {{ conf | nft_default_rule('FWD-filter', 'ipv4') }} } {% endfor %} {% endif %} @@ -71,7 +71,7 @@ table ip vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule('INP-filter') }} + {{ conf | nft_default_rule('INP-filter', 'ipv4') }} } {% endfor %} {% endif %} @@ -88,7 +88,7 @@ table ip vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule('OUT-filter') }} + {{ conf | nft_default_rule('OUT-filter', 'ipv4') }} } {% endfor %} {% endif %} @@ -108,7 +108,7 @@ table ip vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule('PRE-filter') }} + {{ conf | nft_default_rule('PRE-filter', 'ipv4') }} } {% endfor %} {% endif %} @@ -124,7 +124,7 @@ table ip vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule(name_text) }} + {{ conf | nft_default_rule(name_text, 'ipv4') }} } {% endfor %} {% endif %} @@ -154,7 +154,7 @@ table ip vyos_filter { {{ group_tmpl.groups(group, False, True) }} {% if zone is vyos_defined %} -{{ zone_tmpl.zone_chains(zone, False) }} +{{ zone_tmpl.zone_chains(zone, 'ipv4') }} {% endif %} } @@ -182,7 +182,7 @@ table ip6 vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule('FWD-filter', ipv6=True) }} + {{ conf | nft_default_rule('FWD-filter', 'ipv6') }} } {% endfor %} {% endif %} @@ -199,7 +199,7 @@ table ip6 vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule('INP-filter', ipv6=True) }} + {{ conf | nft_default_rule('INP-filter', 'ipv6') }} } {% endfor %} {% endif %} @@ -216,7 +216,7 @@ table ip6 vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule('OUT-filter', ipv6=True) }} + {{ conf | nft_default_rule('OUT-filter', 'ipv6') }} } {% endfor %} {% endif %} @@ -237,7 +237,7 @@ table ip6 vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule(name_text, ipv6=True) }} + {{ conf | nft_default_rule(name_text, 'ipv6') }} } {% endfor %} {% endif %} @@ -266,7 +266,7 @@ table ip6 vyos_filter { {% endif %} {{ group_tmpl.groups(group, True, True) }} {% if zone is vyos_defined %} -{{ zone_tmpl.zone_chains(zone, True) }} +{{ zone_tmpl.zone_chains(zone, 'ipv6') }} {% endif %} } diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index 448075b5b..05c552e6b 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -41,17 +41,9 @@ </constraint> </properties> </leafNode> - <leafNode name='listen-port'> - <properties> - <help>Port to listen for HTTPS requests; default 443</help> - <valueHelp> - <format>u32:1-65535</format> - <description>Numeric IP port</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> + #include <include/port-number.xml.i> + <leafNode name='port'> + <defaultValue>443</defaultValue> </leafNode> <leafNode name="server-name"> <properties> diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-forward.xml.i index 23d757070..ff86bf466 100644 --- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i +++ b/interface-definitions/include/firewall/bridge-hook-forward.xml.i @@ -10,6 +10,7 @@ </properties> <children> #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/enable-default-log.xml.i> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 30fcb8573..4542b8b01 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -111,7 +111,7 @@ </leafNode> <leafNode name="mru"> <properties> - <help>Maximum Receive Unit (MRU)</help> + <help>Maximum Receive Unit (MRU) (default: MTU value)</help> <valueHelp> <format>u32:128-16384</format> <description>Maximum Receive Unit in byte</description> @@ -121,7 +121,6 @@ </constraint> <constraintErrorMessage>MRU must be between 128 and 16384</constraintErrorMessage> </properties> - <defaultValue>1492</defaultValue> </leafNode> #include <include/interface/no-peer-dns.xml.i> <leafNode name="remote-address"> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index f20743a65..4461923d9 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -48,9 +48,6 @@ #include <include/interface/mac.xml.i> #include <include/interface/mtu-1200-16000.xml.i> #include <include/interface/mirror.xml.i> - <leafNode name="mtu"> - <defaultValue>1450</defaultValue> - </leafNode> <node name="parameters"> <properties> <help>VXLAN tunnel parameters</help> @@ -95,6 +92,12 @@ <valueless/> </properties> </leafNode> + <leafNode name="vni-filter"> + <properties> + <help>Enable VNI filter support</help> + <valueless/> + </properties> + </leafNode> </children> </node> #include <include/port-number.xml.i> diff --git a/op-mode-definitions/show-bridge.xml.in b/op-mode-definitions/show-bridge.xml.in index fad3f3418..5d8cc3847 100644 --- a/op-mode-definitions/show-bridge.xml.in +++ b/op-mode-definitions/show-bridge.xml.in @@ -21,6 +21,12 @@ </leafNode> </children> </node> + <leafNode name="vni"> + <properties> + <help>Virtual Network Identifier</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_vni</command> + </leafNode> </children> </node> <leafNode name="bridge"> diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index a229533bd..b7f39ecb0 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -51,9 +51,6 @@ https_data = { } api_data = { - 'listen_address' : '127.0.0.1', - 'port' : '8080', - 'socket' : False, 'strict' : False, 'debug' : False, 'api_keys' : [ {'id' : 'testapp', 'key' : 'qwerty'} ] diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 8c5a0220e..23b6daa3a 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -22,6 +22,7 @@ from vyos.utils.assertion import assert_list from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config from vyos.utils.network import get_vxlan_vlan_tunnels +from vyos.utils.network import get_vxlan_vni_filter @Interface.register class VXLANIf(Interface): @@ -79,6 +80,7 @@ class VXLANIf(Interface): 'parameters.ip.ttl' : 'ttl', 'parameters.ipv6.flowlabel' : 'flowlabel', 'parameters.nolearning' : 'nolearning', + 'parameters.vni_filter' : 'vnifilter', 'remote' : 'remote', 'source_address' : 'local', 'source_interface' : 'dev', @@ -138,10 +140,14 @@ class VXLANIf(Interface): if not isinstance(state, bool): raise ValueError('Value out of range') - cur_vlan_ids = [] if 'vlan_to_vni_removed' in self.config: - cur_vlan_ids = self.config['vlan_to_vni_removed'] - for vlan in cur_vlan_ids: + cur_vni_filter = get_vxlan_vni_filter(self.ifname) + for vlan, vlan_config in self.config['vlan_to_vni_removed'].items(): + # If VNI filtering is enabled, remove matching VNI filter + if dict_search('parameters.vni_filter', self.config) != None: + vni = vlan_config['vni'] + if vni in cur_vni_filter: + self._cmd(f'bridge vni delete dev {self.ifname} vni {vni}') self._cmd(f'bridge vlan del dev {self.ifname} vid {vlan}') # Determine current OS Kernel vlan_tunnel setting - only adjust when needed @@ -151,10 +157,9 @@ class VXLANIf(Interface): if cur_state != new_state: self.set_interface('vlan_tunnel', new_state) - # Determine current OS Kernel configured VLANs - os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname) - if 'vlan_to_vni' in self.config: + # Determine current OS Kernel configured VLANs + os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname) add_vlan = list_diff(list(self.config['vlan_to_vni'].keys()), os_configured_vlan_ids) for vlan, vlan_config in self.config['vlan_to_vni'].items(): @@ -168,6 +173,10 @@ class VXLANIf(Interface): self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan}') self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan} tunnel_info id {vni}') + # If VNI filtering is enabled, install matching VNI filter + if dict_search('parameters.vni_filter', self.config) != None: + self._cmd(f'bridge vni add dev {self.ifname} vni {vni}') + def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered diff --git a/python/vyos/template.py b/python/vyos/template.py index c778d0de8..1e683b605 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -579,10 +579,10 @@ def nft_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name='ip'): return parse_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name) @register_filter('nft_default_rule') -def nft_default_rule(fw_conf, fw_name, ipv6=False): +def nft_default_rule(fw_conf, fw_name, family): output = ['counter'] default_action = fw_conf['default_action'] - family = 'ipv6' if ipv6 else 'ipv4' + #family = 'ipv6' if ipv6 else 'ipv4' if 'enable_default_log' in fw_conf: action_suffix = default_action[:1].upper() @@ -592,7 +592,7 @@ def nft_default_rule(fw_conf, fw_name, ipv6=False): output.append(f'{default_action}') if 'default_jump_target' in fw_conf: target = fw_conf['default_jump_target'] - def_suffix = '6' if ipv6 else '' + def_suffix = '6' if family == 'ipv6' else '' output.append(f'NAME{def_suffix}_{target}') output.append(f'comment "{fw_name} default-action {default_action}"') diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index 5d19f256b..6a5de5423 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -483,3 +483,36 @@ def get_vxlan_vlan_tunnels(interface: str) -> list: os_configured_vlan_ids.append(str(vlanStart)) return os_configured_vlan_ids + +def get_vxlan_vni_filter(interface: str) -> list: + """ Return a list of strings with VNIs configured in the Kernel""" + from json import loads + from vyos.utils.process import cmd + + if not interface.startswith('vxlan'): + raise ValueError('Only applicable for VXLAN interfaces!') + + # Determine current OS Kernel configured VNI filters in VXLAN interface + # + # $ bridge -j vni show dev vxlan1 + # [{"ifname":"vxlan1","vnis":[{"vni":100},{"vni":200},{"vni":300,"vniEnd":399}]}] + # + # Example output: ['10010', '10020', '10021', '10022'] + os_configured_vnis = [] + tmp = loads(cmd(f'bridge --json vni show dev {interface}')) + if tmp: + for tunnel in tmp[0].get('vnis', {}): + vniStart = tunnel['vni'] + if 'vniEnd' in tunnel: + vniEnd = tunnel['vniEnd'] + # Build a real list for user VNIs + vni_list = list(range(vniStart, vniEnd +1)) + # Convert list of integers to list or strings + os_configured_vnis.extend(map(str, vni_list)) + # Proceed with next tunnel - this one is complete + continue + + # Add single tunel id - not part of a range + os_configured_vnis.append(str(vniStart)) + + return os_configured_vnis diff --git a/smoketest/config-tests/basic-api-service b/smoketest/config-tests/basic-api-service index d78062402..1d2dc3472 100644 --- a/smoketest/config-tests/basic-api-service +++ b/smoketest/config-tests/basic-api-service @@ -5,6 +5,14 @@ set service ntp server time1.vyos.net set service ntp server time2.vyos.net set service ntp server time3.vyos.net set service https api keys id 1 key 'S3cur3' +set service https virtual-host bar allow-client address '172.16.0.0/12' +set service https virtual-host bar port '5555' +set service https virtual-host foo allow-client address '10.0.0.0/8' +set service https virtual-host foo allow-client address '2001:db8::/32' +set service https virtual-host foo port '7777' +set service https virtual-host baz allow-client address '192.168.0.0/16' +set service https virtual-host baz port '6666' +set service https virtual-host baz server-name 'baz' set system config-management commit-revisions '100' set system host-name 'vyos' set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/' diff --git a/smoketest/configs/basic-api-service b/smoketest/configs/basic-api-service index 98b2ebcf8..f5b56ac98 100644 --- a/smoketest/configs/basic-api-service +++ b/smoketest/configs/basic-api-service @@ -18,8 +18,28 @@ service { } socket } - } - ssh { + virtual-host bar { + allow-client { + address 172.16.0.0/12 + } + listen-port 5555 + server-name bar + } + virtual-host baz { + allow-client { + address 192.168.0.0/16 + } + listen-port 6666 + server-name baz + } + virtual-host foo { + allow-client { + address 10.0.0.0/8 + address 2001:db8::/32 + } + listen-port 7777 + server-name foo + } } } system { diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index da196133a..3f42196f7 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -412,10 +412,9 @@ class BasicInterfaceTest: for intf in self._interfaces: base = self._base_path + [intf] - self.cli_set(base + ['mtu', self._mtu]) - for option in self._options.get(intf, []): self.cli_set(base + option.split()) + self.cli_set(base + ['mtu', self._mtu]) # check validate() - can not set low MTU if 'no-default-link-local' # is not set on CLI diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 8c3e00a2a..cffa1c0be 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -586,6 +586,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'enable-default-log']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id]) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump']) @@ -596,11 +597,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): nftables_search = [ ['chain VYOS_FORWARD_filter'], - ['type filter hook forward priority filter; policy drop;'], + ['type filter hook forward priority filter; policy accept;'], [f'vlan id {vlan_id}', 'accept'], [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'], + ['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'], [f'chain NAME_{name}'], - [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'] + [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'], + ['accept', f'{name} default-action accept'] ] self.verify_nftables(nftables_search, 'bridge vyos_filter') diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py index 7b702759f..e99d8b3d1 100755 --- a/smoketest/scripts/cli/test_interfaces_pppoe.py +++ b/smoketest/scripts/cli/test_interfaces_pppoe.py @@ -36,6 +36,9 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(PPPoEInterfaceTest, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) cls._interfaces = ['pppoe10', 'pppoe20', 'pppoe30'] cls._source_interface = 'eth0' @@ -53,18 +56,16 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() - def test_01_pppoe_client(self): + def test_pppoe_client(self): # Check if PPPoE dialer can be configured and runs for interface in self._interfaces: user = f'VyOS-user-{interface}' passwd = f'VyOS-passwd-{interface}' mtu = '1400' - mru = '1300' self.cli_set(base_path + [interface, 'authentication', 'username', user]) self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) self.cli_set(base_path + [interface, 'mtu', mtu]) - self.cli_set(base_path + [interface, 'mru', '9000']) self.cli_set(base_path + [interface, 'no-peer-dns']) # check validate() - a source-interface is required @@ -72,11 +73,6 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_commit() self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) - # check validate() - MRU needs to be less or equal then MTU - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_set(base_path + [interface, 'mru', mru]) - # commit changes self.cli_commit() @@ -87,8 +83,9 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): tmp = get_config_value(interface, 'mtu')[1] self.assertEqual(tmp, mtu) + # MRU must default to MTU if not specified on CLI tmp = get_config_value(interface, 'mru')[1] - self.assertEqual(tmp, mru) + self.assertEqual(tmp, mtu) tmp = get_config_value(interface, 'user')[1].replace('"', '') self.assertEqual(tmp, user) tmp = get_config_value(interface, 'password')[1].replace('"', '') @@ -96,7 +93,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): tmp = get_config_value(interface, 'ifname')[1] self.assertEqual(tmp, interface) - def test_02_pppoe_client_disabled_interface(self): + def test_pppoe_client_disabled_interface(self): # Check if PPPoE Client can be disabled for interface in self._interfaces: user = f'VyOS-user-{interface}' @@ -125,16 +122,16 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_commit() - def test_03_pppoe_authentication(self): + def test_pppoe_authentication(self): # When username or password is set - so must be the other for interface in self._interfaces: user = f'VyOS-user-{interface}' passwd = f'VyOS-passwd-{interface}' - self.cli_set(base_path + [interface, 'authentication', 'username', user]) self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) + self.cli_set(base_path + [interface, 'authentication', 'username', user]) # check validate() - if user is set, so must be the password with self.assertRaises(ConfigSessionError): self.cli_commit() @@ -143,7 +140,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_commit() - def test_04_pppoe_dhcpv6pd(self): + def test_pppoe_dhcpv6pd(self): # Check if PPPoE dialer can be configured with DHCPv6-PD address = '1' sla_id = '0' @@ -183,7 +180,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): tmp = get_config_value(interface, '+ipv6 ipv6cp-use-ipaddr') self.assertListEqual(tmp, ['+ipv6', 'ipv6cp-use-ipaddr']) - def test_05_pppoe_options(self): + def test_pppoe_options(self): # Check if PPPoE dialer can be configured with DHCPv6-PD for interface in self._interfaces: user = f'VyOS-user-{interface}' @@ -215,5 +212,47 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): tmp = get_config_value(interface, 'pppoe-host-uniq')[1] self.assertEqual(tmp, f'"{host_uniq}"') + def test_pppoe_mtu_mru(self): + # Check if PPPoE dialer can be configured and runs + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + mtu = '1400' + mru = '1300' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + self.cli_set(base_path + [interface, 'mtu', mtu]) + self.cli_set(base_path + [interface, 'mru', '9000']) + + # check validate() - a source-interface is required + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + + # check validate() - MRU needs to be less or equal then MTU + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + [interface, 'mru', mru]) + + # commit changes + self.cli_commit() + + # verify configuration file(s) + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + tmp = get_config_value(interface, 'mtu')[1] + self.assertEqual(tmp, mtu) + tmp = get_config_value(interface, 'mru')[1] + self.assertEqual(tmp, mru) + tmp = get_config_value(interface, 'user')[1].replace('"', '') + self.assertEqual(tmp, user) + tmp = get_config_value(interface, 'password')[1].replace('"', '') + self.assertEqual(tmp, passwd) + tmp = get_config_value(interface, 'ifname')[1] + self.assertEqual(tmp, interface) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index 17e4fc36f..18676491b 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -18,10 +18,12 @@ import unittest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface +from vyos.ifconfig import Section from vyos.utils.network import get_bridge_fdb from vyos.utils.network import get_interface_config from vyos.utils.network import interface_exists from vyos.utils.network import get_vxlan_vlan_tunnels +from vyos.utils.network import get_vxlan_vni_filter from vyos.template import is_ipv6 from base_interfaces_test import BasicInterfaceTest @@ -31,12 +33,13 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): cls._base_path = ['interfaces', 'vxlan'] cls._options = { 'vxlan10': ['vni 10', 'remote 127.0.0.2'], - 'vxlan20': ['vni 20', 'group 239.1.1.1', 'source-interface eth0'], + 'vxlan20': ['vni 20', 'group 239.1.1.1', 'source-interface eth0', 'mtu 1450'], 'vxlan30': ['vni 30', 'remote 2001:db8:2000::1', 'source-address 2001:db8:1000::1', 'parameters ipv6 flowlabel 0x1000'], 'vxlan40': ['vni 40', 'remote 127.0.0.2', 'remote 127.0.0.3'], 'vxlan50': ['vni 50', 'remote 2001:db8:2000::1', 'remote 2001:db8:2000::2', 'parameters ipv6 flowlabel 0x1000'], } cls._interfaces = list(cls._options) + cls._mtu = '1450' # call base-classes classmethod super(VXLANInterfaceTest, cls).setUpClass() @@ -138,7 +141,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): def test_vxlan_vlan_vni_mapping(self): bridge = 'br0' interface = 'vxlan0' - source_interface = 'eth0' + source_address = '192.0.2.99' vlan_to_vni = { '10': '10010', @@ -151,7 +154,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): } self.cli_set(self._base_path + [interface, 'parameters', 'external']) - self.cli_set(self._base_path + [interface, 'source-interface', source_interface]) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) for vlan, vni in vlan_to_vni.items(): self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) @@ -187,11 +190,12 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): def test_vxlan_neighbor_suppress(self): bridge = 'br555' interface = 'vxlan555' - source_interface = 'eth0' + source_interface = 'dum0' + + self.cli_set(['interfaces', Section.section(source_interface), source_interface, 'mtu', '9000']) self.cli_set(self._base_path + [interface, 'parameters', 'external']) self.cli_set(self._base_path + [interface, 'source-interface', source_interface]) - self.cli_set(self._base_path + [interface, 'parameters', 'neighbor-suppress']) # This must fail as this VXLAN interface is not associated with any bridge @@ -221,6 +225,95 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): self.assertTrue(tmp['linkinfo']['info_slave_data']['learning']) self.cli_delete(['interfaces', 'bridge', bridge]) + self.cli_delete(['interfaces', Section.section(source_interface), source_interface]) + + def test_vxlan_vni_filter(self): + interfaces = ['vxlan987', 'vxlan986', 'vxlan985'] + source_address = '192.0.2.77' + + for interface in interfaces: + self.cli_set(self._base_path + [interface, 'parameters', 'external']) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) + + # This must fail as there can only be one "external" VXLAN device unless "vni-filter" is defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Enable "vni-filter" on the first VXLAN interface + self.cli_set(self._base_path + [interfaces[0], 'parameters', 'vni-filter']) + + # This must fail as if it's enabled on one VXLAN interface, it must be enabled on all + # VXLAN interfaces + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(self._base_path + [interface, 'parameters', 'vni-filter']) + + # commit configuration + self.cli_commit() + + for interface in interfaces: + self.assertTrue(interface_exists(interface)) + + tmp = get_interface_config(interface) + self.assertTrue(tmp['linkinfo']['info_data']['vnifilter']) + + def test_vxlan_vni_filter_add_remove(self): + interface = 'vxlan987' + source_address = '192.0.2.66' + bridge = 'br0' + + self.cli_set(self._base_path + [interface, 'parameters', 'external']) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) + self.cli_set(self._base_path + [interface, 'parameters', 'vni-filter']) + + # commit configuration + self.cli_commit() + + # Check if VXLAN interface got created + self.assertTrue(interface_exists(interface)) + + # VNI filter configured? + tmp = get_interface_config(interface) + self.assertTrue(tmp['linkinfo']['info_data']['vnifilter']) + + # Now create some VLAN mappings and VNI filter + vlan_to_vni = { + '50': '10050', + '51': '10051', + '52': '10052', + '53': '10053', + '54': '10054', + '60': '10060', + '69': '10069', + } + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + # we need a bridge ... + self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', interface]) + # commit configuration + self.cli_commit() + + # All VNIs configured? + tmp = get_vxlan_vni_filter(interface) + self.assertListEqual(list(vlan_to_vni.values()), tmp) + + # + # Delete a VLAN mappings and check if all VNIs are properly set up + # + vlan_to_vni.popitem() + self.cli_delete(self._base_path + [interface, 'vlan-to-vni']) + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + + # commit configuration + self.cli_commit() + + # All VNIs configured? + tmp = get_vxlan_vni_filter(interface) + self.assertListEqual(list(vlan_to_vni.values()), tmp) + + self.cli_delete(['interfaces', 'bridge', bridge]) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 6484abc5d..24e1f1299 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -80,7 +80,7 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): test_path = base_path + ['virtual-host', vhost_id] self.cli_set(test_path + ['listen-address', address]) - self.cli_set(test_path + ['listen-port', port]) + self.cli_set(test_path + ['port', port]) self.cli_set(test_path + ['server-name', name]) self.cli_commit() @@ -111,7 +111,7 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): def test_api_auth(self): vhost_id = 'example' address = '127.0.0.1' - port = '443' + port = '443' # default value name = 'localhost' key = 'MySuperSecretVyOS' @@ -119,7 +119,6 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): test_path = base_path + ['virtual-host', vhost_id] self.cli_set(test_path + ['listen-address', address]) - self.cli_set(test_path + ['listen-port', port]) self.cli_set(test_path + ['server-name', name]) self.cli_commit() diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 88b26fdc7..5cbdd1651 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -124,7 +124,7 @@ def verify(https): server_block = deepcopy(default_server_block) data = vhost_dict.get(vhost, {}) server_block['address'] = data.get('listen-address', '*') - server_block['port'] = data.get('listen-port', '443') + server_block['port'] = data.get('port', '443') server_block_list.append(server_block) for entry in server_block_list: @@ -182,7 +182,7 @@ def generate(https): server_block['id'] = vhost data = vhost_dict.get(vhost, {}) server_block['address'] = data.get('listen-address', '*') - server_block['port'] = data.get('listen-port', '443') + server_block['port'] = data.get('port', '443') name = data.get('server-name', ['_']) server_block['name'] = name allow_client = data.get('allow-client', {}) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 0a03a172c..42f084309 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -61,6 +61,12 @@ def get_config(config=None): # bail out early - no need to further process other nodes break + if 'deleted' not in pppoe: + # We always set the MRU value to the MTU size. This code path only re-creates + # the old behavior if MRU is not set on the CLI. + if 'mru' not in pppoe: + pppoe['mru'] = pppoe['mtu'] + return pppoe def verify(pppoe): diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 6bf3227d5..4251e611b 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -60,8 +60,14 @@ def get_config(config=None): vxlan.update({'rebuild_required': {}}) break + # When dealing with VNI filtering we need to know what VNI was actually removed, + # so build up a dict matching the vlan_to_vni structure but with removed values. tmp = node_changed(conf, base + [ifname, 'vlan-to-vni'], recursive=True) - if tmp: vxlan.update({'vlan_to_vni_removed': tmp}) + if tmp: + vxlan.update({'vlan_to_vni_removed': {}}) + for vlan in tmp: + vni = leaf_node_changed(conf, base + [ifname, 'vlan-to-vni', vlan, 'vni']) + vxlan['vlan_to_vni_removed'].update({vlan : {'vni' : vni[0]}}) # We need to verify that no other VXLAN tunnel is configured when external # mode is in use - Linux Kernel limitation @@ -98,14 +104,31 @@ def verify(vxlan): if 'vni' not in vxlan and dict_search('parameters.external', vxlan) == None: raise ConfigError('Must either configure VXLAN "vni" or use "external" CLI option!') - if dict_search('parameters.external', vxlan): + if dict_search('parameters.external', vxlan) != None: if 'vni' in vxlan: raise ConfigError('Can not specify both "external" and "VNI"!') if 'other_tunnels' in vxlan: - other_tunnels = ', '.join(vxlan['other_tunnels']) - raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ - f'CLI option is used. Additional tunnels: {other_tunnels}') + # When multiple VXLAN interfaces are defined and "external" is used, + # all VXLAN interfaces need to have vni-filter enabled! + # See Linux Kernel commit f9c4bb0b245cee35ef66f75bf409c9573d934cf9 + other_vni_filter = False + for tunnel, tunnel_config in vxlan['other_tunnels'].items(): + if dict_search('parameters.vni_filter', tunnel_config) != None: + other_vni_filter = True + break + # eqivalent of the C foo ? 'a' : 'b' statement + vni_filter = True and (dict_search('parameters.vni_filter', vxlan) != None) or False + # If either one is enabled, so must be the other. Both can be off and both can be on + if (vni_filter and not other_vni_filter) or (not vni_filter and other_vni_filter): + raise ConfigError(f'Using multiple VXLAN interfaces with "external" '\ + 'requires all VXLAN interfaces to have "vni-filter" configured!') + + if not vni_filter and not other_vni_filter: + other_tunnels = ', '.join(vxlan['other_tunnels']) + raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ + f'CLI option is used and "vni-filter" is unset. '\ + f'Additional tunnels: {other_tunnels}') if 'gpe' in vxlan and 'external' not in vxlan: raise ConfigError(f'VXLAN-GPE is only supported when "external" '\ @@ -165,7 +188,7 @@ def verify(vxlan): raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!') vnis_used.append(vni) - if dict_search('parameters.neighbor_suppress', vxlan): + if dict_search('parameters.neighbor_suppress', vxlan) != None: if 'is_bridge_member' not in vxlan: raise ConfigError('Neighbor suppression requires that VXLAN interface '\ 'is member of a bridge interface!') diff --git a/src/migration-scripts/https/4-to-5 b/src/migration-scripts/https/4-to-5 index a503e0cb7..0dfb6ac19 100755 --- a/src/migration-scripts/https/4-to-5 +++ b/src/migration-scripts/https/4-to-5 @@ -48,6 +48,12 @@ if config.exists(base + ['api', 'socket']): if config.exists(base + ['api', 'port']): config.delete(base + ['api', 'port']) +# rename listen-port -> port ver virtual-host +if config.exists(base + ['virtual-host']): + for vhost in config.list_nodes(base + ['virtual-host']): + if config.exists(base + ['virtual-host', vhost, 'listen-port']): + config.rename(base + ['virtual-host', vhost, 'listen-port'], 'port') + try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/migration-scripts/interfaces/31-to-32 b/src/migration-scripts/interfaces/31-to-32 index ca3d19320..0fc27b70a 100755 --- a/src/migration-scripts/interfaces/31-to-32 +++ b/src/migration-scripts/interfaces/31-to-32 @@ -15,6 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # # T5671: change port to IANA assigned default port +# T5759: change default MTU 1450 -> 1500 from sys import argv from sys import exit @@ -43,6 +44,9 @@ for vxlan in config.list_nodes(base): if not config.exists(base + [vxlan, 'port']): config.set(base + [vxlan, 'port'], value='8472') + if not config.exists(base + [vxlan, 'mtu']): + config.set(base + [vxlan, 'mtu'], value='1450') + try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py index 185db4f20..412a4eba8 100755 --- a/src/op_mode/bridge.py +++ b/src/op_mode/bridge.py @@ -56,6 +56,13 @@ def _get_raw_data_vlan(tunnel:bool=False): data_dict = json.loads(json_data) return data_dict +def _get_raw_data_vni() -> dict: + """ + :returns dict + """ + json_data = cmd(f'bridge --json vni show') + data_dict = json.loads(json_data) + return data_dict def _get_raw_data_fdb(bridge): """Get MAC-address for the bridge brX @@ -165,6 +172,22 @@ def _get_formatted_output_vlan_tunnel(data): output = tabulate(data_entries, headers) return output +def _get_formatted_output_vni(data): + data_entries = [] + for entry in data: + interface = entry.get('ifname') + vlans = entry.get('vnis') + for vlan_entry in vlans: + vlan = vlan_entry.get('vni') + if vlan_entry.get('vniEnd'): + vlan_end = vlan_entry.get('vniEnd') + vlan = f'{vlan}-{vlan_end}' + data_entries.append([interface, vlan]) + + headers = ["Interface", "VNI"] + output = tabulate(data_entries, headers) + return output + def _get_formatted_output_fdb(data): data_entries = [] for entry in data: @@ -228,6 +251,12 @@ def show_vlan(raw: bool, tunnel: typing.Optional[bool]): else: return _get_formatted_output_vlan(bridge_vlan) +def show_vni(raw: bool): + bridge_vni = _get_raw_data_vni() + if raw: + return bridge_vni + else: + return _get_formatted_output_vni(bridge_vni) def show_fdb(raw: bool, interface: str): fdb_data = _get_raw_data_fdb(interface) diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 20f54b9ba..36bb013fe 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -113,19 +113,14 @@ def output_firewall_name(family, hook, priority, firewall_conf, single_rule_id=N if hook in ['input', 'forward', 'output']: def_action = firewall_conf['default_action'] if 'default_action' in firewall_conf else 'accept' - row = ['default', def_action, 'all'] - rule_details = details['default-action'] - row.append(rule_details.get('packets', 0)) - row.append(rule_details.get('bytes', 0)) - rows.append(row) + else: + def_action = firewall_conf['default_action'] if 'default_action' in firewall_conf else 'drop' + row = ['default', def_action, 'all'] + rule_details = details['default-action'] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) - elif 'default_action' in firewall_conf and not single_rule_id: - row = ['default', firewall_conf['default_action'], 'all'] - if 'default-action' in details: - rule_details = details['default-action'] - row.append(rule_details.get('packets', 0)) - row.append(rule_details.get('bytes', 0)) - rows.append(row) + rows.append(row) if rows: header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions'] @@ -314,7 +309,7 @@ def show_firewall_group(name=None): family = ['ipv6'] group_type = 'network_group' else: - family = ['ipv4', 'ipv6'] + family = ['ipv4', 'ipv6', 'bridge'] for item in family: # Look references in firewall |