diff options
Diffstat (limited to 'src/helpers/vyos-failover.py')
-rwxr-xr-x | src/helpers/vyos-failover.py | 107 |
1 files changed, 79 insertions, 28 deletions
diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py index 1ac193423..ce4cf8fa4 100755 --- a/src/helpers/vyos-failover.py +++ b/src/helpers/vyos-failover.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2023 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 @@ -28,6 +28,17 @@ from systemd import journal my_name = Path(__file__).stem +def is_route_exists(route, gateway, interface, metric): + """Check if route with expected gateway, dev and metric exists""" + rc, data = rc_cmd(f'ip --json route show protocol failover {route} ' + f'via {gateway} dev {interface} metric {metric}') + if rc == 0: + data = json.loads(data) + if len(data) > 0: + return True + return False + + def get_best_route_options(route, debug=False): """ Return current best route ('gateway, interface, metric) @@ -61,6 +72,7 @@ def get_best_route_options(route, debug=False): f'best_metric: {best_metric}, best_iface: {best_interface}') return best_gateway, best_interface, best_metric + def is_port_open(ip, port): """ Check connection to remote host and port @@ -80,32 +92,70 @@ def is_port_open(ip, port): finally: s.close() -def is_target_alive(target=None, iface='', proto='icmp', port=None, debug=False): - """ - Host availability check by ICMP, ARP, TCP - Return True if target checks is successful - % is_target_alive('192.0.2.1', 'eth1', proto='arp') - True +def is_target_alive(target_list=None, + iface='', + proto='icmp', + port=None, + debug=False, + policy='any-available') -> bool: + """Check the availability of each target in the target_list using + the specified protocol ICMP, ARP, TCP + + Args: + target_list (list): A list of IP addresses or hostnames to check. + iface (str): The name of the network interface to use for the check. + proto (str): The protocol to use for the check. Options are 'icmp', 'arp', or 'tcp'. + port (int): The port number to use for the TCP check. Only applicable if proto is 'tcp'. + debug (bool): If True, print debug information during the check. + policy (str): The policy to use for the check. Options are 'any-available' or 'all-available'. + + Returns: + bool: True if all targets are reachable according to the policy, False otherwise. + + Example: + % is_target_alive(['192.0.2.1', '192.0.2.5'], 'eth1', proto='arp', policy='all-available') + True """ if iface != '': iface = f'-I {iface}' - if proto == 'icmp': - command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1' - rc, response = rc_cmd(command) - if debug: print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') - if rc == 0: - return True - elif proto == 'arp': - command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}' - rc, response = rc_cmd(command) - if debug: print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') - if rc == 0: + + num_reachable_targets = 0 + for target in target_list: + match proto: + case 'icmp': + command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1' + rc, response = rc_cmd(command) + if debug: + print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') + if rc == 0: + num_reachable_targets += 1 + if policy == 'any-available': + return True + + case 'arp': + command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}' + rc, response = rc_cmd(command) + if debug: + print(f' [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') + if rc == 0: + num_reachable_targets += 1 + if policy == 'any-available': + return True + + case _ if proto == 'tcp' and port is not None: + if is_port_open(target, port): + num_reachable_targets += 1 + if policy == 'any-available': + return True + + case _: + return False + + if policy == 'all-available' and num_reachable_targets == len(target_list): return True - elif proto == 'tcp' and port is not None: - return True if is_port_open(target, port) else False - else: - return False + + return False if __name__ == '__main__': @@ -137,22 +187,23 @@ if __name__ == '__main__': for route, route_config in config.get('route').items(): - exists_route = exists_gateway, exists_iface, exists_metric = get_best_route_options(route, debug=debug) + exists_gateway, exists_iface, exists_metric = get_best_route_options(route, debug=debug) for next_hop, nexthop_config in route_config.get('next_hop').items(): conf_iface = nexthop_config.get('interface') conf_metric = int(nexthop_config.get('metric')) port = nexthop_config.get('check').get('port') port_opt = f'port {port}' if port else '' + policy = nexthop_config.get('check').get('policy') proto = nexthop_config.get('check').get('type') target = nexthop_config.get('check').get('target') timeout = nexthop_config.get('check').get('timeout') - # Best route not fonund in the current routing table - if exists_route == (None, None, None): + # Route not found in the current routing table + if not is_route_exists(route, next_hop, conf_iface, conf_metric): if debug: print(f" [NEW_ROUTE_DETECTED] route: [{route}]") # Add route if check-target alive - if is_target_alive(target, conf_iface, proto, port, debug=debug): + if is_target_alive(target, conf_iface, proto, port, debug=debug, policy=policy): if debug: print(f' [ ADD ] -- ip route add {route} via {next_hop} dev {conf_iface} ' f'metric {conf_metric} proto failover\n###') rc, command = rc_cmd(f'ip route add {route} via {next_hop} dev {conf_iface} ' @@ -171,8 +222,8 @@ if __name__ == '__main__': # Route was added, check if the target is alive # We should delete route if check fails only if route exists in the routing table - if not is_target_alive(target, conf_iface, proto, port, debug=debug) and \ - exists_route != (None, None, None): + if not is_target_alive(target, conf_iface, proto, port, debug=debug, policy=policy) and \ + is_route_exists(route, next_hop, conf_iface, conf_metric): if debug: print(f'Nexh_hop {next_hop} fail, target not response') print(f' [ DEL ] -- ip route del {route} via {next_hop} dev {conf_iface} ' |