summaryrefslogtreecommitdiff
path: root/src/helpers
diff options
context:
space:
mode:
authorViacheslav Hletenko <v.gletenko@vyos.io>2025-11-03 15:35:41 +0200
committerGitHub <noreply@github.com>2025-11-03 15:35:41 +0200
commit78af000fb3be20c45514bea83b12f0eb630dd7f6 (patch)
tree2dc1ce21765f84ef09e0b0829a68fba4eaaf7338 /src/helpers
parent11d38ac032f1abb537614b9dfa8121d389ac6eb0 (diff)
parent7e5fd62fcf585fa2b72161a7b1d0ba37b565c9eb (diff)
downloadvyos-1x-78af000fb3be20c45514bea83b12f0eb630dd7f6.tar.gz
vyos-1x-78af000fb3be20c45514bea83b12f0eb630dd7f6.zip
Merge pull request #4818 from l0crian1/wlb-defaults
wlb: T7966: Restore default route when interface disconnects/reconnects
Diffstat (limited to 'src/helpers')
-rwxr-xr-xsrc/helpers/vyos-load-balancer.py66
1 files changed, 62 insertions, 4 deletions
diff --git a/src/helpers/vyos-load-balancer.py b/src/helpers/vyos-load-balancer.py
index 5df83b31e..bd62d1479 100755
--- a/src/helpers/vyos-load-balancer.py
+++ b/src/helpers/vyos-load-balancer.py
@@ -24,6 +24,7 @@ import time
from vyos.config import Config
from vyos.template import render
from vyos.utils.commit import commit_in_progress
+from vyos.utils.dict import dict_search_args
from vyos.utils.network import get_interface_address
from vyos.utils.process import rc_cmd
from vyos.utils.process import run
@@ -102,15 +103,36 @@ def get_ipv4_address(ifname):
return addr_json['addr_info'][0]['local']
return None
+def get_dynamic_nexthop(ifname: str) -> str | None | bool:
+ '''
+ Resolve the dynamic next-hop for a WAN interface.
+
+ Determines the current default gateway learned dynamically on the interface:
+ - PPPoE interfaces (`pppoe*`): uses `parse_ppp_nexthop`.
+ - Other interfaces (e.g. DHCP): uses `parse_dhcp_nexthop`.
+
+ Return values:
+ - str: IPv4 next-hop address
+ - None: when DHCP lease has no router value
+ - False: when PPPoE nexthop state file is missing
+
+ Args:
+ ifname: Interface name (e.g. 'pppoe0', 'eth0').
+
+ Returns:
+ See above for possible values.
+ '''
+ if ifname.startswith('pppoe'):
+ return parse_ppp_nexthop(ifname)
+ else:
+ return parse_dhcp_nexthop(ifname)
+
def dynamic_nexthop_update(lb, ifname):
# Update on DHCP/PPP address/nexthop changes
# Return True if nftables needs to be updated - IP change
if 'dhcp_nexthop' in lb['health_state'][ifname]:
- if ifname[:5] == 'pppoe':
- dhcp_nexthop_addr = parse_ppp_nexthop(ifname)
- else:
- dhcp_nexthop_addr = parse_dhcp_nexthop(ifname)
+ dhcp_nexthop_addr = get_dynamic_nexthop(ifname)
table_num = lb['health_state'][ifname]['table_number']
@@ -125,6 +147,40 @@ def dynamic_nexthop_update(lb, ifname):
return False
+def restore_default_route(lb: dict, ifname: str) -> None:
+ """
+ Restores a missing default route for a WAN interface in its policy routing table.
+
+ When a link flap or DHCP/PPP renegotiation removes the per-interface default route,
+ this function checks the interface’s assigned table for an existing default entry.
+ If none is found, it determines the proper next-hop (from DHCP, PPP, or static config)
+ and reinstalls the route using:
+ ip route replace table <table_num> default dev <ifname> via <nexthop>
+
+ @param lb Load-balancer state/config dictionary.
+ @param ifname Interface name whose default route should be verified and restored.
+ @returns None — exits quietly if the table number or next-hop cannot be found.
+ """
+ table_num = dict_search_args(lb, 'health_state', ifname, 'table_number')
+ if not table_num:
+ return
+
+ rc, out = rc_cmd(f'ip -j route show default table {table_num}')
+ if rc == 0:
+ rt_table = json.loads(out)
+ if len(rt_table) > 0:
+ return
+ else:
+ if 'dhcp_nexthop' in lb['health_state'][ifname]:
+ nexthop_addr = get_dynamic_nexthop(ifname)
+ else:
+ nexthop_addr = dict_search_args(lb, 'interface_health', ifname, 'nexthop')
+
+ if nexthop_addr:
+ run(f'ip route replace table {table_num} default dev {ifname} via {nexthop_addr}')
+ else:
+ return
+
def nftables_update(lb):
# Atomically reload nftables table from template
if not os.path.exists(nftables_wlb_conf):
@@ -295,6 +351,8 @@ if __name__ == '__main__':
if dynamic_nexthop_update(lb, ifname):
ip_change = True
+ restore_default_route(lb, ifname)
+
if any(state['state_changed'] for ifname, state in lb['health_state'].items()):
if not nftables_update(lb):
break