summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/pr-conflicts.yml18
-rw-r--r--data/templates/frr/isisd.frr.tmpl4
-rw-r--r--python/vyos/ifconfig/interface.py19
-rw-r--r--python/vyos/ifconfig/loopback.py13
-rw-r--r--python/vyos/util.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py47
-rwxr-xr-xsmoketest/scripts/cli/test_system_ipv6.py31
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py93
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py25
-rwxr-xr-xsrc/conf_mode/vrf.py8
-rw-r--r--src/tests/test_util.py16
11 files changed, 220 insertions, 58 deletions
diff --git a/.github/workflows/pr-conflicts.yml b/.github/workflows/pr-conflicts.yml
new file mode 100644
index 000000000..72ff3969b
--- /dev/null
+++ b/.github/workflows/pr-conflicts.yml
@@ -0,0 +1,18 @@
+name: "PR Conflicts checker"
+on:
+ pull_request_target:
+ types: [synchronize]
+
+jobs:
+ Conflict_Check:
+ name: 'Check PR status: conflicts and resolution'
+ runs-on: ubuntu-18.04
+ steps:
+ - name: check if PRs are dirty
+ uses: eps1lon/actions-label-merge-conflict@releases/2.x
+ with:
+ dirtyLabel: "state: conflict"
+ removeOnDirtyLabel: "state: conflict resolved"
+ repoToken: "${{ secrets.GITHUB_TOKEN }}"
+ commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
+ commentOnClean: "Conflicts have been resolved. A maintainer will review the pull request shortly."
diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl
index 6cfa076d0..40c7f8aa1 100644
--- a/data/templates/frr/isisd.frr.tmpl
+++ b/data/templates/frr/isisd.frr.tmpl
@@ -99,8 +99,8 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is defined and vrf is not none }}
{% endfor %}
{% endif %}
{% endif %}
-{% if spf_delay_ietf is defined and spf_delay_ietf.init_delay is defined and spf_delay_ietf.init_delay is not none %}
- spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }}
+{% if spf_delay_ietf is defined and spf_delay_ietf.init_delay is defined and spf_delay_ietf.short_delay is defined and spf_delay_ietf.long_delay is defined and spf_delay_ietf.holddown is defined and spf_delay_ietf.time_to_learn is defined %}
+ spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} short-delay {{ spf_delay_ietf.short_delay }} long-delay {{ spf_delay_ietf.long_delay }} holddown {{ spf_delay_ietf.holddown }} time-to-learn {{ spf_delay_ietf.time_to_learn }}
{% endif %}
{% if area_password is defined and area_password is not none %}
{% if area_password.md5 is defined and area_password.md5 is not none %}
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index a2fa96d82..214c8dcb1 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -38,7 +38,7 @@ from vyos.util import dict_search
from vyos.util import read_file
from vyos.util import get_interface_config
from vyos.util import is_systemd_service_active
-from vyos.util import sysctl_read
+from vyos.util import is_ipv6_enabled
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.validate import is_intf_addr_assigned
@@ -990,6 +990,10 @@ class Interface(Control):
"Can't configure both static IPv4 and DHCP address "
"on the same interface"))
+ # Failsave - do not add IPv6 address if IPv6 is disabled
+ if is_ipv6(addr) and not is_ipv6_enabled():
+ return False
+
# add to interface
if addr == 'dhcp':
self.set_dhcp(True)
@@ -1358,8 +1362,15 @@ class Interface(Control):
value = tmp if (tmp != None) else '0'
self.set_ipv4_source_validation(value)
+ # MTU - Maximum Transfer Unit has a default value. It must ALWAYS be set
+ # before mangling any IPv6 option. If MTU is less then 1280 IPv6 will be
+ # automatically disabled by the kernel. Also MTU must be increased before
+ # configuring any IPv6 address on the interface.
+ if 'mtu' in config:
+ self.set_mtu(config.get('mtu'))
+
# Only change IPv6 parameters if IPv6 was not explicitly disabled
- if sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0':
+ if is_ipv6_enabled():
# IPv6 forwarding
tmp = dict_search('ipv6.disable_forwarding', config)
value = '0' if (tmp != None) else '1'
@@ -1382,10 +1393,6 @@ class Interface(Control):
value = tmp if (tmp != None) else '1'
self.set_ipv6_dad_messages(value)
- # MTU - Maximum Transfer Unit
- if 'mtu' in config:
- self.set_mtu(config.get('mtu'))
-
# Delete old IPv6 EUI64 addresses before changing MAC
for addr in (dict_search('ipv6.address.eui64_old', config) or []):
self.del_ipv6_eui64_address(addr)
diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py
index de554ef44..30c890fdf 100644
--- a/python/vyos/ifconfig/loopback.py
+++ b/python/vyos/ifconfig/loopback.py
@@ -13,9 +13,8 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-import vyos.util
-
from vyos.ifconfig.interface import Interface
+from vyos.util import is_ipv6_enabled
@Interface.register
class LoopbackIf(Interface):
@@ -34,8 +33,6 @@ class LoopbackIf(Interface):
}
}
- name = 'loopback'
-
def remove(self):
"""
Loopback interface can not be deleted from operating system. We can
@@ -62,11 +59,11 @@ class LoopbackIf(Interface):
on any interface. """
addr = config.get('address', [])
- # We must ensure that the loopback addresses are never deleted from the system
- addr += ['127.0.0.1/8']
- if (vyos.util.sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0'):
- addr += ['::1/128']
+ # We must ensure that the loopback addresses are never deleted from the system
+ addr.append('127.0.0.1/8')
+ if is_ipv6_enabled():
+ addr.append('::1/128')
# Update IP address entry in our dictionary
config.update({'address' : addr})
diff --git a/python/vyos/util.py b/python/vyos/util.py
index b5d81fba5..4283e604c 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -827,3 +827,7 @@ def sysctl_write(name, value):
call(f'sysctl -wq {name}={value}')
return True
return False
+
+def is_ipv6_enabled() -> bool:
+ """ Check if IPv6 support on the system is enabled or not """
+ return (sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0')
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index 8abdd6d37..ede217065 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -165,5 +165,52 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' domain-password clear {password}', tmp)
self.assertIn(f' area-password clear {password}', tmp)
+ def test_isis_06_spf_delay(self):
+ network = 'point-to-point'
+ holddown = '10'
+ init_delay = '50'
+ long_delay = '200'
+ short_delay = '100'
+ time_to_learn = '75'
+
+ self.cli_set(base_path + ['net', net])
+ for interface in self._interfaces:
+ self.cli_set(base_path + ['interface', interface, 'network', network])
+
+ self.cli_set(base_path + ['spf-delay-ietf', 'holddown', holddown])
+ # verify() - All types of spf-delay must be configured
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(base_path + ['spf-delay-ietf', 'init-delay', init_delay])
+ # verify() - All types of spf-delay must be configured
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(base_path + ['spf-delay-ietf', 'long-delay', long_delay])
+ # verify() - All types of spf-delay must be configured
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(base_path + ['spf-delay-ietf', 'short-delay', short_delay])
+ # verify() - All types of spf-delay must be configured
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + ['spf-delay-ietf', 'time-to-learn', time_to_learn])
+
+ # Commit all changes
+ self.cli_commit()
+
+ # Verify all changes
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' spf-delay-ietf init-delay {init_delay} short-delay {short_delay} long-delay {long_delay} holddown {holddown} time-to-learn {time_to_learn}', tmp)
+
+ for interface in self._interfaces:
+ tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
+ self.assertIn(f' ip router isis {domain}', tmp)
+ self.assertIn(f' ipv6 router isis {domain}', tmp)
+ self.assertIn(f' isis network {network}', tmp)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py
index 3112d2e46..837d1dc12 100755
--- a/smoketest/scripts/cli/test_system_ipv6.py
+++ b/smoketest/scripts/cli/test_system_ipv6.py
@@ -17,7 +17,12 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.template import is_ipv4
from vyos.util import read_file
+from vyos.util import is_ipv6_enabled
+from vyos.util import get_interface_config
+from vyos.validate import is_intf_addr_assigned
base_path = ['system', 'ipv6']
@@ -42,6 +47,14 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
self.assertEqual(read_file(file_forwarding), '0')
def test_system_ipv6_disable(self):
+ # Verify previous "enable" state
+ self.assertEqual(read_file(file_disable), '0')
+ self.assertTrue(is_ipv6_enabled())
+
+ loopbacks = ['127.0.0.1', '::1']
+ for addr in loopbacks:
+ self.assertTrue(is_intf_addr_assigned('lo', addr))
+
# Do not assign any IPv6 address on interfaces, this requires a reboot
# which can not be tested, but we can read the config file :)
self.cli_set(base_path + ['disable'])
@@ -49,6 +62,24 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
# Verify configuration file
self.assertEqual(read_file(file_disable), '1')
+ self.assertFalse(is_ipv6_enabled())
+
+ for addr in loopbacks:
+ if is_ipv4(addr):
+ self.assertTrue(is_intf_addr_assigned('lo', addr))
+ else:
+ self.assertFalse(is_intf_addr_assigned('lo', addr))
+
+ # T4330: Verify MTU can be changed with IPv6 disabled
+ mtu = '1600'
+ eth_if = 'eth0'
+ self.cli_set(['interfaces', 'ethernet', eth_if, 'mtu', mtu])
+ self.cli_commit()
+
+ tmp = get_interface_config(eth_if)
+ self.assertEqual(tmp['mtu'], int(mtu))
+
+ self.cli_delete(['interfaces', 'ethernet', eth_if, 'mtu'])
def test_system_ipv6_strict_dad(self):
# This defaults to 1
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index 0f006ca3c..75be9e4f8 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -27,8 +27,10 @@ from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.template import is_ipv6
+from vyos.template import is_ipv4
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
base_path = ['vrf']
@@ -61,6 +63,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
def tearDown(self):
# delete all VRFs
self.cli_delete(base_path)
+ self.cli_delete(['interfaces', 'dummy'])
+ self.cli_delete(['protocols', 'vrf'])
self.cli_commit()
for vrf in vrfs:
self.assertNotIn(vrf, interfaces())
@@ -108,9 +112,14 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# ...
regex = f'{table}\s+{vrf}\s+#\s+{description}'
self.assertTrue(re.findall(regex, iproute2_config))
+
+ tmp = get_interface_config(vrf)
+ self.assertEqual(int(table), tmp['linkinfo']['info_data']['table'])
+
+ # Increment table ID for the next run
table = str(int(table) + 1)
- def test_vrf_loopback_ips(self):
+ def test_vrf_loopbacks_ips(self):
table = '2000'
for vrf in vrfs:
base = base_path + ['name', vrf]
@@ -121,10 +130,48 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify VRF configuration
+ loopbacks = ['127.0.0.1', '::1']
for vrf in vrfs:
- self.assertTrue(vrf in interfaces())
- self.assertTrue(is_intf_addr_assigned(vrf, '127.0.0.1'))
- self.assertTrue(is_intf_addr_assigned(vrf, '::1'))
+ # Ensure VRF was created
+ self.assertIn(vrf, interfaces())
+ # Test for proper loopback IP assignment
+ for addr in loopbacks:
+ self.assertTrue(is_intf_addr_assigned(vrf, addr))
+
+ def test_vrf_loopbacks_no_ipv6(self):
+ table = '2002'
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', str(table)])
+ table = str(int(table) + 1)
+
+ # Globally disable IPv6 - this will remove all IPv6 interface addresses
+ self.cli_set(['system', 'ipv6', 'disable'])
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify VRF configuration
+ table = '2002'
+ loopbacks = ['127.0.0.1', '::1']
+ for vrf in vrfs:
+ # Ensure VRF was created
+ self.assertIn(vrf, interfaces())
+
+ # Verify VRF table ID
+ tmp = get_interface_config(vrf)
+ self.assertEqual(int(table), tmp['linkinfo']['info_data']['table'])
+
+ # Test for proper loopback IP assignment
+ for addr in loopbacks:
+ if is_ipv4(addr):
+ self.assertTrue(is_intf_addr_assigned(vrf, addr))
+ else:
+ self.assertFalse(is_intf_addr_assigned(vrf, addr))
+
+ table = str(int(table) + 1)
+
+ self.cli_delete(['system', 'ipv6'])
def test_vrf_bind_all(self):
table = '2000'
@@ -204,14 +251,14 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
},
}
+ # required interface for leaking to default table
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '192.0.2.1/24'])
+
table = '2000'
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', str(table)])
- # required interface for leaking to default table
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '192.0.2.1/24'])
-
# we also need an interface in "UP" state to install routes
self.cli_set(['interfaces', 'dummy', f'dum{table}', 'vrf', vrf])
self.cli_set(['interfaces', 'dummy', f'dum{table}', 'address', '192.0.2.1/24'])
@@ -233,28 +280,24 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify routes
- table = '2000'
for vrf in vrfs:
- for route, route_config in routes.items():
- if is_ipv6(route):
- tmp = get_vrf_ipv6_routes(vrf)
- else:
- tmp = get_vrf_ipv4_routes(vrf)
+ self.assertIn(vrf, interfaces())
+ frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ for prefix, prefix_config in routes.items():
+ tmp = 'ip'
+ if is_ipv6(prefix):
+ tmp += 'v6'
- found = False
- for result in tmp:
- if 'dst' in result and result['dst'] == route:
- if 'gateway' in result and result['gateway'] == route_config['next_hop']:
- found = True
+ tmp += f' route {prefix} {prefix_config["next_hop"]}'
+ if 'distance' in prefix_config:
+ tmp += ' ' + prefix_config['distance']
+ if 'next_hop_vrf' in prefix_config:
+ tmp += ' nexthop-vrf ' + prefix_config['next_hop_vrf']
- self.assertTrue(found)
+ self.assertIn(tmp, frrconfig)
- # Cleanup
- self.cli_delete(['protocols', 'vrf', vrf])
- self.cli_delete(['interfaces', 'dummy', f'dum{table}'])
- self.cli_delete(['interfaces', 'ethernet', 'eth0', 'address', '192.0.2.1/24'])
+ self.cli_delete(['interfaces', 'ethernet', 'eth0', 'address'])
- table = str(int(table) + 1)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 06366362a..bc3821f61 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -16,6 +16,7 @@
import os
+from netifaces import interfaces
from sys import exit
from vyos.config import Config
@@ -62,10 +63,6 @@ def get_config(config=None):
if conf.exists(base_nameservers):
dns.update({'system_name_server': conf.return_values(base_nameservers)})
- base_nameservers_dhcp = ['system', 'name-servers-dhcp']
- if conf.exists(base_nameservers_dhcp):
- dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)})
-
return dns
def verify(dns):
@@ -87,9 +84,8 @@ def verify(dns):
raise ConfigError(f'No server configured for domain {domain}!')
if 'system' in dns:
- if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns):
- print("Warning: No 'system name-server' or 'system " \
- "name-servers-dhcp' configured")
+ if not 'system_name_server' in dns:
+ print('Warning: No "system name-server" configured')
return None
@@ -142,10 +138,15 @@ def apply(dns):
hc.delete_name_server_tags_recursor(['system'])
# add dhcp nameserver tags for configured interfaces
- if 'system_name_server_dhcp' in dns:
- for interface in dns['system_name_server_dhcp']:
- hc.add_name_server_tags_recursor(['dhcp-' + interface,
- 'dhcpv6-' + interface ])
+ if 'system_name_server' in dns:
+ for interface in dns['system_name_server']:
+ # system_name_server key contains both IP addresses and interface
+ # names (DHCP) to use DNS servers. We need to check if the
+ # value is an interface name - only if this is the case, add the
+ # interface based DNS forwarder.
+ if interface in interfaces():
+ hc.add_name_server_tags_recursor(['dhcp-' + interface,
+ 'dhcpv6-' + interface ])
# hostsd will generate the forward-zones file
# the list and keys() are required as get returns a dict, not list
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index dd1739087..fb2182fff 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -27,6 +27,7 @@ from vyos.util import call
from vyos.util import cmd
from vyos.util import dict_search
from vyos.util import sysctl_write
+from vyos.util import is_ipv6_enabled
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -194,10 +195,11 @@ def apply(vrf):
# set VRF description for e.g. SNMP monitoring
vrf_if = Interface(name)
- # We also should add proper loopback IP addresses to the newly
- # created VRFs for services bound to the loopback address (SNMP, NTP)
+ # We also should add proper loopback IP addresses to the newly added
+ # VRF for services bound to the loopback address (SNMP, NTP)
vrf_if.add_addr('127.0.0.1/8')
- vrf_if.add_addr('::1/128')
+ if is_ipv6_enabled():
+ vrf_if.add_addr('::1/128')
# add VRF description if available
vrf_if.set_alias(config.get('description', ''))
diff --git a/src/tests/test_util.py b/src/tests/test_util.py
index 22bc085c5..91890262c 100644
--- a/src/tests/test_util.py
+++ b/src/tests/test_util.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from unittest import TestCase
-from vyos.util import mangle_dict_keys
+from vyos.util import *
class TestVyOSUtil(TestCase):
def test_key_mangline(self):
@@ -24,3 +24,15 @@ class TestVyOSUtil(TestCase):
new_data = mangle_dict_keys(data, '-', '_')
self.assertEqual(new_data, expected_data)
+ def test_sysctl_read(self):
+ self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1')
+
+ def test_ipv6_enabled(self):
+ tmp = sysctl_read('net.ipv6.conf.all.disable_ipv6')
+ # We need to test for both variants as this depends on how the
+ # Docker container is started (with or without IPv6 support) - so we
+ # will simply check both cases to not make the users life miserable.
+ if tmp == '0':
+ self.assertTrue(is_ipv6_enabled())
+ else:
+ self.assertFalse(is_ipv6_enabled())