summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyrylo Yatsenko <hedrok@gmail.com>2025-09-26 22:59:43 +0300
committerKyrylo Yatsenko <hedrok@gmail.com>2025-10-14 15:05:07 +0300
commit47d5f21b7770b0b8dcd29272e6c93bea9028adfe (patch)
treefac2466429dfdccf6d68c99f91aadd244c62cefd
parent1c181499a3af4b6c170d3fea2b96af58bb0a34a7 (diff)
downloadvyos-1x-47d5f21b7770b0b8dcd29272e6c93bea9028adfe.tar.gz
vyos-1x-47d5f21b7770b0b8dcd29272e6c93bea9028adfe.zip
T5942: Make failover support dhcp-interface
* Refactor XML a little: move common dhcp-interface properties to separate include file * Add failover support for dhcp-interface * Add test for DHCP in failover protocol
-rw-r--r--interface-definitions/include/dhcp-interface-multi.xml.i22
-rw-r--r--interface-definitions/include/dhcp-interface-properties.xml.i13
-rw-r--r--interface-definitions/include/dhcp-interface.xml.i14
-rw-r--r--interface-definitions/include/failover/common-failover.xml.i105
-rw-r--r--interface-definitions/include/failover/protocol-common-config.xml.i110
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_failover.py138
-rwxr-xr-xsrc/conf_mode/protocols_failover.py84
-rwxr-xr-xsrc/helpers/vyos-failover.py92
8 files changed, 410 insertions, 168 deletions
diff --git a/interface-definitions/include/dhcp-interface-multi.xml.i b/interface-definitions/include/dhcp-interface-multi.xml.i
index 0db11cf79..5f5c54cfd 100644
--- a/interface-definitions/include/dhcp-interface-multi.xml.i
+++ b/interface-definitions/include/dhcp-interface-multi.xml.i
@@ -1,18 +1,8 @@
<!-- include start from dhcp-interface-multi.xml.i -->
-<leafNode name="dhcp-interface">
- <properties>
- <help>DHCP interface supplying next-hop IP address</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- <valueHelp>
- <format>txt</format>
- <description>DHCP interface name</description>
- </valueHelp>
- <constraint>
- #include <include/constraint/interface-name.xml.i>
- </constraint>
- <multi/>
- </properties>
+ <leafNode name="dhcp-interface">
+ <properties>
+ #include <include/dhcp-interface-properties.xml.i>
+ <multi/>
+ </properties>
</leafNode>
-<!-- include end --> \ No newline at end of file
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp-interface-properties.xml.i b/interface-definitions/include/dhcp-interface-properties.xml.i
new file mode 100644
index 000000000..b184b8f0a
--- /dev/null
+++ b/interface-definitions/include/dhcp-interface-properties.xml.i
@@ -0,0 +1,13 @@
+<!-- include end -->
+ <help>DHCP interface supplying next-hop IP address</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>DHCP interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp-interface.xml.i b/interface-definitions/include/dhcp-interface.xml.i
index b5c94cb24..e056b3fe1 100644
--- a/interface-definitions/include/dhcp-interface.xml.i
+++ b/interface-definitions/include/dhcp-interface.xml.i
@@ -1,15 +1,7 @@
+<!-- include start from dhcp-interface.xml.i -->
<leafNode name="dhcp-interface">
<properties>
- <help>DHCP interface supplying next-hop IP address</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- <valueHelp>
- <format>txt</format>
- <description>DHCP interface name</description>
- </valueHelp>
- <constraint>
- #include <include/constraint/interface-name.xml.i>
- </constraint>
+ #include <include/dhcp-interface-properties.xml.i>
</properties>
</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/failover/common-failover.xml.i b/interface-definitions/include/failover/common-failover.xml.i
new file mode 100644
index 000000000..b147a0f8d
--- /dev/null
+++ b/interface-definitions/include/failover/common-failover.xml.i
@@ -0,0 +1,105 @@
+<!-- include start from include/failover/common-failover.xml.i -->
+<children>
+ <node name="check">
+ <properties>
+ <help>Check target options</help>
+ </properties>
+ <children>
+ <leafNode name="policy">
+ <properties>
+ <help>Policy for check targets</help>
+ <completionHelp>
+ <list>any-available all-available</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all-available</format>
+ <description>All targets must be alive</description>
+ </valueHelp>
+ <valueHelp>
+ <format>any-available</format>
+ <description>Any target must be alive</description>
+ </valueHelp>
+ <constraint>
+ <regex>(all-available|any-available)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>any-available</defaultValue>
+ </leafNode>
+ #include <include/port-number.xml.i>
+ <tagNode name="target">
+ <properties>
+ <help>Check target address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Address to check</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/interface/vrf.xml.i>
+ #include <include/generic-interface.xml.i>
+ </children>
+ </tagNode>
+ <leafNode name="timeout">
+ <properties>
+ <help>Timeout between checks</help>
+ <valueHelp>
+ <format>u32:1-300</format>
+ <description>Timeout in seconds between checks</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Check type</help>
+ <completionHelp>
+ <list>arp icmp tcp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>arp</format>
+ <description>Check target by ARP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>icmp</format>
+ <description>Check target by ICMP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>Check target by TCP</description>
+ </valueHelp>
+ <constraint>
+ <regex>(arp|icmp|tcp)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>icmp</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/generic-interface.xml.i>
+ <leafNode name="metric">
+ <properties>
+ <help>Route metric for this gateway</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Route metric</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>1</defaultValue>
+ </leafNode>
+ <leafNode name="onlink">
+ <properties>
+ <help>The next hop is directly connected to the interface, even if it does not match interface prefix</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+</children>
+<!-- include end -->
diff --git a/interface-definitions/include/failover/protocol-common-config.xml.i b/interface-definitions/include/failover/protocol-common-config.xml.i
index a106fdc74..56c9d8f90 100644
--- a/interface-definitions/include/failover/protocol-common-config.xml.i
+++ b/interface-definitions/include/failover/protocol-common-config.xml.i
@@ -22,109 +22,13 @@
<validator name="ipv4-address"/>
</constraint>
</properties>
- <children>
- <node name="check">
- <properties>
- <help>Check target options</help>
- </properties>
- <children>
- <leafNode name="policy">
- <properties>
- <help>Policy for check targets</help>
- <completionHelp>
- <list>any-available all-available</list>
- </completionHelp>
- <valueHelp>
- <format>all-available</format>
- <description>All targets must be alive</description>
- </valueHelp>
- <valueHelp>
- <format>any-available</format>
- <description>Any target must be alive</description>
- </valueHelp>
- <constraint>
- <regex>(all-available|any-available)</regex>
- </constraint>
- </properties>
- <defaultValue>any-available</defaultValue>
- </leafNode>
- #include <include/port-number.xml.i>
- <tagNode name="target">
- <properties>
- <help>Check target address</help>
- <valueHelp>
- <format>ipv4</format>
- <description>Address to check</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- <children>
- #include <include/interface/vrf.xml.i>
- #include <include/generic-interface.xml.i>
- </children>
- </tagNode>
- <leafNode name="timeout">
- <properties>
- <help>Timeout between checks</help>
- <valueHelp>
- <format>u32:1-300</format>
- <description>Timeout in seconds between checks</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- <defaultValue>10</defaultValue>
- </leafNode>
- <leafNode name="type">
- <properties>
- <help>Check type</help>
- <completionHelp>
- <list>arp icmp tcp</list>
- </completionHelp>
- <valueHelp>
- <format>arp</format>
- <description>Check target by ARP</description>
- </valueHelp>
- <valueHelp>
- <format>icmp</format>
- <description>Check target by ICMP</description>
- </valueHelp>
- <valueHelp>
- <format>tcp</format>
- <description>Check target by TCP</description>
- </valueHelp>
- <constraint>
- <regex>(arp|icmp|tcp)</regex>
- </constraint>
- </properties>
- <defaultValue>icmp</defaultValue>
- </leafNode>
- </children>
- </node>
- #include <include/generic-interface.xml.i>
- <leafNode name="metric">
- <properties>
- <help>Route metric for this gateway</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Route metric</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- <defaultValue>1</defaultValue>
- </leafNode>
- <leafNode name="onlink">
- <properties>
- <help>The next hop is directly connected to the interface, even if it does not match interface prefix</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
+ #include <include/failover/common-failover.xml.i>
+ </tagNode>
+ <tagNode name="dhcp-interface">
+ <properties>
+ #include <include/dhcp-interface-properties.xml.i>
+ </properties>
+ #include <include/failover/common-failover.xml.i>
</tagNode>
</children>
</tagNode>
diff --git a/smoketest/scripts/cli/test_protocols_failover.py b/smoketest/scripts/cli/test_protocols_failover.py
index 26f5e87b5..132d427ec 100755
--- a/smoketest/scripts/cli/test_protocols_failover.py
+++ b/smoketest/scripts/cli/test_protocols_failover.py
@@ -34,6 +34,7 @@ config_dir_root = '/run/vyos-failover.conf.d'
check_timeout = 1
wait_timeout = 5
+wait_dhcp_timeout = 10
# Use numeric value to not get ip errors while
# /etc/iproute2/rt_protos.d/failover.conf is not installed yet
@@ -43,6 +44,9 @@ dummy_if1 = 'dum3711'
dummy_if2 = 'dum3712'
dummy_if3 = 'dum3713'
+veth_if1 = 'veth71'
+veth_if2 = 'veth72'
+
route_prefix = '203.0.113.0/24'
route2_prefix = '172.16.0.0/24'
route_base_path = base_path + ['route', route_prefix]
@@ -51,6 +55,12 @@ dummy_if1_addr = '192.168.30.1'
dummy_if2_addr = '10.0.70.1'
dummy_if3_addr = '10.20.0.1'
+# These three must be in same subnet:
+dhcp_prefix = '10.133.0'
+veth_if1_addr = f'{dhcp_prefix}.1'
+dhcp_gateway_addr_1 = f'{dhcp_prefix}.99'
+dhcp_gateway_addr_2 = f'{dhcp_prefix}.117'
+
class RoutesChecker:
def __init__(self, required_routes, allow_extra=False):
@@ -101,13 +111,26 @@ class TestProtocolsFailover(VyOSUnitTestSHIM.TestCase):
self.cli_set(['interfaces', 'dummy', dummy_if1])
self.cli_set(['interfaces', 'dummy', dummy_if2])
self.cli_set(['interfaces', 'dummy', dummy_if3])
+ self.cli_set(
+ ['interfaces', 'virtual-ethernet', veth_if1, 'peer-name', veth_if2]
+ )
+ self.cli_set(
+ ['interfaces', 'virtual-ethernet', veth_if2, 'peer-name', veth_if1]
+ )
self.clean_and_stop_daemon()
+ self.clean_dhclient_lease_files = set()
+ self.need_dhcp_dir_cleanup = False
+
def tearDown(self):
+ self.cli_delete(['interfaces', 'virtual-ethernet', veth_if2])
+ self.cli_delete(['interfaces', 'virtual-ethernet', veth_if1])
self.cli_delete(['interfaces', 'dummy', dummy_if3])
self.cli_delete(['interfaces', 'dummy', dummy_if2])
self.cli_delete(['interfaces', 'dummy', dummy_if1])
+ self.cli_delete(['service', 'dhcp-server'])
+ self.cli_delete(['service', 'dns'])
self.clean_and_stop_daemon()
@@ -503,5 +526,120 @@ class TestProtocolsFailover(VyOSUnitTestSHIM.TestCase):
self.assertTrue(res, f"No routes should have been left, got: {output}")
+ def test_04_dhcp(self):
+ res, output = self.wait_for_ip_output(
+ f'route show proto {failover_protocol_value}',
+ [],
+ timeout=wait_timeout,
+ )
+ self.assertTrue(
+ res, f"No failover routes must exist before test, last result: {output}"
+ )
+
+ # Setup DHCP server
+ self.cli_set(
+ [
+ 'interfaces',
+ 'virtual-ethernet',
+ veth_if1,
+ 'address',
+ f'{dhcp_prefix}.1/24',
+ ]
+ )
+ self.cli_set(['interfaces', 'virtual-ethernet', veth_if1, 'description', 'LAN'])
+
+ service_base = [
+ 'service',
+ 'dhcp-server',
+ 'shared-network-name',
+ 'LAN',
+ 'subnet',
+ f'{dhcp_prefix}.0/24',
+ ]
+ self.cli_set(service_base + ['option', 'name-server', f'{dhcp_prefix}.1'])
+ self.cli_set(service_base + ['option', 'domain-name', 'vyos'])
+ self.cli_set(service_base + ['lease', '86400'])
+ self.cli_set(service_base + ['range', '0', 'start', f'{dhcp_prefix}.9'])
+ self.cli_set(service_base + ['range', '0', 'stop', f'{dhcp_prefix}.254'])
+ self.cli_set(service_base + ['subnet-id', '1952'])
+
+ self.cli_set(['service', 'dns', 'forwarding', 'cache-size', '0'])
+ self.cli_set(
+ ['service', 'dns', 'forwarding', 'listen-address', f'{dhcp_prefix}.1']
+ )
+ self.cli_set(
+ ['service', 'dns', 'forwarding', 'allow-from', f'{dhcp_prefix}.0/24']
+ )
+ # End setup DHCP server
+
+ # Setting first DHCP Gateway address
+ self.cli_set(service_base + ['option', 'default-router', dhcp_gateway_addr_1])
+
+ self.cli_set(
+ ['interfaces', 'dummy', dummy_if1, 'address', dummy_if1_addr + '/24']
+ )
+ self.cli_set(
+ ['interfaces', 'dummy', dummy_if2, 'address', dummy_if2_addr + '/24']
+ )
+ self.cli_set(['interfaces', 'virtual-ethernet', veth_if2, 'address', 'dhcp'])
+ self.cli_set(route_base_path + ['dhcp-interface', veth_if2])
+ base_dhcp_interface = route_base_path + ['dhcp-interface', veth_if2]
+ self.cli_set(base_dhcp_interface + ['metric', '30'])
+ self.cli_set(
+ base_dhcp_interface
+ + [
+ 'check',
+ 'target',
+ dummy_if1_addr,
+ 'interface',
+ dummy_if1,
+ ]
+ )
+ self.cli_set(base_dhcp_interface + ['check', 'timeout', str(check_timeout)])
+ self.cli_commit()
+
+ # Now vyos-failover must be launched, it should create route to first dhcp address
+ checker = RoutesChecker([{'dst': route_prefix, 'gateway': dhcp_gateway_addr_1}])
+ res, output = self.wait_for_ip_output(
+ f"route show proto {failover_protocol_value}",
+ checker,
+ timeout=wait_dhcp_timeout,
+ )
+ self.assertTrue(
+ res,
+ f"Route must have been created via fist dhcp address. Checker error: {checker.error}",
+ )
+
+ # Change DHCP gateway address
+ renew_cmd = ['renew', 'dhcp', 'interface', veth_if2]
+ self.cli_set(service_base + ['option', 'default-router', dhcp_gateway_addr_2])
+ self.cli_commit()
+ self.op_mode(renew_cmd)
+
+ checker = RoutesChecker([{'dst': route_prefix, 'gateway': dhcp_gateway_addr_2}])
+ res, output = self.wait_for_ip_output(
+ f"route show proto {failover_protocol_value}",
+ checker,
+ timeout=wait_dhcp_timeout,
+ )
+ self.assertTrue(
+ res,
+ f"Route must have been created via second dhcp address. Checker error: {checker.error}",
+ )
+
+ # DHCP server down
+ self.cli_delete(['service', 'dhcp-server'])
+ self.cli_delete(['service', 'dns'])
+ self.cli_commit()
+ self.op_mode(renew_cmd)
+
+ res, output = self.wait_for_ip_output(
+ f'route show proto {failover_protocol_value}',
+ [],
+ timeout=wait_dhcp_timeout,
+ )
+ self.assertTrue(res, f"Route must have been deleted, last result: {output}")
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py
index 956c060f4..752bd6011 100755
--- a/src/conf_mode/protocols_failover.py
+++ b/src/conf_mode/protocols_failover.py
@@ -85,38 +85,60 @@ def verify(failover):
if 'route' not in failover:
raise ConfigError(f'Failover "route" is mandatory!')
+ def _verify_route_item(item_config, item_description, interface_mandatory):
+ if interface_mandatory and 'interface' not in item_config:
+ raise ConfigError(
+ f'Interface for route "{route}" {item_description} is mandatory!'
+ )
+
+ if not item_config.get('check'):
+ raise ConfigError(f'Check target for {item_description} is mandatory!')
+
+ if 'target' not in item_config['check']:
+ raise ConfigError(f'Check target for {item_description} is mandatory!')
+
+ check_type = item_config['check']['type']
+ if check_type == 'tcp' and 'port' not in item_config['check']:
+ raise ConfigError(
+ f'Check port for {item_description} and type TCP is mandatory!'
+ )
+
+ errors = {
+ 'icmp': {},
+ 'tcp': {
+ 'interface': 'Check target "interface" option does nothing for type TCP. Use "vrf" if needed',
+ },
+ 'arp': {
+ 'vrf': 'Check target "vrf" option is incompatible with type ARP, use "interface" option if needed',
+ },
+ }
+
+ for target, target_config in item_config['check']['target'].items():
+ for key, msg in errors[check_type].items():
+ if key in target_config:
+ raise ConfigError(msg)
+
for route, route_config in failover['route'].items():
- if not route_config.get('next_hop'):
- raise ConfigError(f'Next-hop for "{route}" is mandatory!')
-
- for next_hop, next_hop_config in route_config.get('next_hop').items():
- if 'interface' not in next_hop_config:
- raise ConfigError(f'Interface for route "{route}" next-hop "{next_hop}" is mandatory!')
-
- if not next_hop_config.get('check'):
- raise ConfigError(f'Check target for next-hop "{next_hop}" is mandatory!')
-
- if 'target' not in next_hop_config['check']:
- raise ConfigError(f'Check target for next-hop "{next_hop}" is mandatory!')
-
- check_type = next_hop_config['check']['type']
- if check_type == 'tcp' and 'port' not in next_hop_config['check']:
- raise ConfigError(f'Check port for next-hop "{next_hop}" and type TCP is mandatory!')
-
- errors = {
- 'icmp': {},
- 'tcp': {
- 'interface': 'Check target "interface" option does nothing for type TCP. Use "vrf" if needed',
- },
- 'arp': {
- 'vrf': 'Check target "vrf" option is incompatible with type ARP, use "interface" option if needed',
- },
- }
-
- for target, target_config in next_hop_config['check']['target'].items():
- for key, msg in errors[check_type].items():
- if key in target_config:
- raise ConfigError(msg)
+ if not route_config.get('next_hop') and not route_config.get('dhcp_interface'):
+ raise ConfigError(
+ f'Either next-hop or dhcp-interface for "{route}" is mandatory!'
+ )
+
+ if route_config.get('next_hop'):
+ for next_hop, next_hop_config in route_config.get('next_hop').items():
+ _verify_route_item(
+ next_hop_config, f'next-hop "{next_hop}"', interface_mandatory=True
+ )
+
+ if route_config.get('dhcp_interface'):
+ for dhcp_interface, dhcp_interface_config in route_config.get(
+ 'dhcp_interface'
+ ).items():
+ _verify_route_item(
+ dhcp_interface_config,
+ f'dhcp-interface "{dhcp_interface}"',
+ interface_mandatory=False,
+ )
return None
diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py
index d255f2303..de89f20d0 100755
--- a/src/helpers/vyos-failover.py
+++ b/src/helpers/vyos-failover.py
@@ -21,6 +21,7 @@ import signal
import time
from collections import namedtuple
+from vyos.template import get_dhcp_router
from vyos.utils.process import rc_cmd
from vyos.utils.process import run
from pathlib import Path
@@ -170,6 +171,7 @@ NextHopNamedTuple = namedtuple(
'NextHopConfig',
[
'route',
+ 'dhcp_interface',
'next_hop',
'vrf',
'vrf_opt',
@@ -187,7 +189,9 @@ NextHopNamedTuple = namedtuple(
)
-def get_nexthop_config_vars(destination, vrf, vrf_opt, nexthop_config, next_hop):
+def get_nexthop_config_vars(
+ destination, vrf, vrf_opt, nexthop_config, next_hop, dhcp_interface
+):
port = nexthop_config.get('check').get('port')
targets = tuple(
@@ -215,10 +219,13 @@ def get_nexthop_config_vars(destination, vrf, vrf_opt, nexthop_config, next_hop)
return NextHopNamedTuple(
route=destination,
+ dhcp_interface=dhcp_interface,
next_hop=next_hop,
vrf=vrf,
vrf_opt=vrf_opt,
- conf_iface=nexthop_config.get('interface'),
+ # For next-hop interface is mandatory
+ # For dhcp-interface it may be not given, then dhcp-interface is used
+ conf_iface=nexthop_config.get('interface', dhcp_interface),
conf_metric=int(nexthop_config.get('metric')),
port=port,
port_opt=f'port {port}' if port else '',
@@ -245,10 +252,22 @@ RouteNamedTuple = namedtuple(
def get_route_config(route, route_config, config_path, vrf):
vrf_opt = f'vrf {vrf}' if vrf else ''
- nexthops = tuple(
- get_nexthop_config_vars(route, vrf, vrf_opt, nexthop_config, next_hop)
- for next_hop, nexthop_config in route_config.get('next_hop').items()
- )
+ nexthops = []
+ if route_config.get('next_hop'):
+ nexthops.extend(
+ get_nexthop_config_vars(route, vrf, vrf_opt, nexthop_config, next_hop, None)
+ for next_hop, nexthop_config in route_config.get('next_hop').items()
+ )
+ if route_config.get('dhcp_interface'):
+ nexthops.extend(
+ get_nexthop_config_vars(
+ route, vrf, vrf_opt, dhcp_nexthop_config, None, interface
+ )
+ for interface, dhcp_nexthop_config in route_config.get(
+ 'dhcp_interface'
+ ).items()
+ )
+ nexthops = tuple(nexthops)
return RouteNamedTuple(
destination=route,
vrf=vrf,
@@ -399,6 +418,55 @@ def update_configuration(last_modification_times, all_routes, config_dir):
print_debug(f"All routes: {all_routes}")
+def process_dhcp_interface(nhc, nexthop_by_dhcp_nexthop):
+ """
+ Processes NextHopNamedTuple with dhcp_interface != None
+ Return NextHopNamedTuple with next_hop equal to DHCP gateway of nhc.
+ If there is no gateway for nhc, return None
+
+ Args:
+ nhc(NextHopNamedTuple): configuration with dhcp_interface
+ nexthop_by_dhcp_nexthop(dict): dict with previous returned values
+ """
+ cur_dhcpgw = get_dhcp_router(nhc.dhcp_interface)
+ if not cur_dhcpgw:
+ cur_dhcpgw = False
+
+ if nhc in nexthop_by_dhcp_nexthop:
+ prev_dhcpgw = nexthop_by_dhcp_nexthop[nhc].next_hop
+ else:
+ prev_dhcpgw = False
+
+ # Equal - do nothing, just return previous value
+ if prev_dhcpgw == cur_dhcpgw:
+ if not cur_dhcpgw:
+ return None
+ return nexthop_by_dhcp_nexthop[nhc]
+
+ print_debug(
+ f"DHCP Gateway changed for interface {nhc.dhcp_interface} from '{prev_dhcpgw}' to '{cur_dhcpgw}'"
+ )
+
+ # dhcpgw differ and there was previous dhcpgw
+ if prev_dhcpgw:
+ prevnhc = nexthop_by_dhcp_nexthop.pop(nhc)
+ print_debug(
+ f"Deleting previous nexthop {prevnhc} because of DHCP interface change"
+ )
+ ip_args = get_ip_command_args(prevnhc)
+ if is_route_exists(ip_args):
+ delete_route(ip_args)
+
+ newnhc = None
+ # dhcpgw differ and there is new dhcpgw
+ if cur_dhcpgw:
+ newnhc = nhc._replace(next_hop=cur_dhcpgw, dhcp_interface=None)
+ print_debug(f"Saving new nexthop {newnhc} because of DHCP interface change")
+ nexthop_by_dhcp_nexthop[nhc] = newnhc
+
+ return newnhc
+
+
if __name__ == '__main__':
print_debug(f"{my_name} started")
@@ -426,6 +494,11 @@ if __name__ == '__main__':
signal.signal(signal.SIGINT, kill_handler)
signal.signal(signal.SIGTERM, kill_handler)
+ # keys: NextHopNamedTuple with dhcp_interface != None
+ # values: NextHopNamedTuple with next_hop != None
+ # Translates nexthop with dhcp_interface to ususal nexthop
+ nexthop_by_dhcp_nexthop = {}
+
had_sleeps = True
while not kill_called:
# Check in case daemon was launched without routes
@@ -443,6 +516,11 @@ if __name__ == '__main__':
vrf_opt = route_config.vrf_opt
for nhc in route_config.nexthops:
+ if nhc.dhcp_interface:
+ nhc = process_dhcp_interface(nhc, nexthop_by_dhcp_nexthop)
+ if not nhc:
+ continue
+
next_hop = nhc.next_hop
ip_args = get_ip_command_args(nhc)
@@ -478,7 +556,7 @@ if __name__ == '__main__':
f' [ TARGET_FAIL ] target checks fails for [{nhc.pretty_targets}], do nothing'
)
journal.send(
- f'Check fail for route {route} target {nhc.pretty_targets} proto {nhc.proto} '
+ f'Check fail for route {route} interface "{nhc.conf_iface}" target {nhc.pretty_targets} proto {nhc.proto} '
f'{nhc.port_opt}',
SYSLOG_IDENTIFIER=my_name,
)