-
Notifications
You must be signed in to change notification settings - Fork 336
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wlb: T4470: Migrate WAN load balancer to Python/XML
- Loading branch information
1 parent
d51765c
commit 821f751
Showing
12 changed files
with
658 additions
and
262 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
#!/usr/sbin/nft -f | ||
|
||
{% if first_install is not vyos_defined %} | ||
delete table ip vyos_wanloadbalance | ||
{% endif %} | ||
table ip vyos_wanloadbalance { | ||
chain wlb_nat_postrouting { | ||
type nat hook postrouting priority srcnat - 1; policy accept; | ||
{% for ifname, health_conf in interface_health.items() if health_state[ifname].if_addr %} | ||
{% if disable_source_nat is not vyos_defined %} | ||
{% set state = health_state[ifname] %} | ||
ct mark {{ state.mark }} counter snat to {{ state.if_addr }} | ||
{% endif %} | ||
{% endfor %} | ||
} | ||
|
||
chain wlb_mangle_prerouting { | ||
type filter hook prerouting priority mangle; policy accept; | ||
{% for ifname, health_conf in interface_health.items() %} | ||
{% set state = health_state[ifname] %} | ||
{% if sticky_connections is vyos_defined %} | ||
iifname "{{ ifname }}" ct state new ct mark set {{ state.mark }} | ||
{% endif %} | ||
{% endfor %} | ||
{% if rule is vyos_defined %} | ||
{% for rule_id, rule_conf in rule.items() %} | ||
{% if rule_conf.exclude is vyos_defined %} | ||
{{ rule_conf | wlb_nft_rule(rule_id, exclude=True, action='accept') }} | ||
{% else %} | ||
{% set limit = rule_conf.limit is vyos_defined %} | ||
{{ rule_conf | wlb_nft_rule(rule_id, limit=limit, weight=True, health_state=health_state) }} | ||
{{ rule_conf | wlb_nft_rule(rule_id, restore_mark=True) }} | ||
{% endif %} | ||
{% endfor %} | ||
{% endif %} | ||
} | ||
|
||
chain wlb_mangle_output { | ||
type filter hook output priority -150; policy accept; | ||
{% if enable_local_traffic is vyos_defined %} | ||
meta mark != 0x0 counter accept | ||
meta l4proto icmp counter accept | ||
ip saddr 127.0.0.0/8 ip daddr 127.0.0.0/8 counter accept | ||
{% if rule is vyos_defined %} | ||
{% for rule_id, rule_conf in rule.items() %} | ||
{% if rule_conf.exclude is vyos_defined %} | ||
{{ rule_conf | wlb_nft_rule(rule_id, local=True, exclude=True, action='accept') }} | ||
{% else %} | ||
{% set limit = rule_conf.limit is vyos_defined %} | ||
{{ rule_conf | wlb_nft_rule(rule_id, local=True, limit=limit, weight=True, health_state=health_state) }} | ||
{{ rule_conf | wlb_nft_rule(rule_id, local=True, restore_mark=True) }} | ||
{% endif %} | ||
{% endfor %} | ||
{% endif %} | ||
{% endif %} | ||
} | ||
|
||
{% for ifname, health_conf in interface_health.items() %} | ||
{% set state = health_state[ifname] %} | ||
chain wlb_mangle_isp_{{ ifname }} { | ||
meta mark set {{ state.mark }} ct mark set {{ state.mark }} counter accept | ||
} | ||
{% endfor %} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# Copyright (C) 2024 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 | ||
# published by the Free Software Foundation. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
import os | ||
|
||
from vyos.defaults import directories | ||
from vyos.utils.process import run | ||
|
||
dhclient_lease = 'dhclient_{0}.lease' | ||
|
||
def nft_rule(rule_conf, rule_id, local=False, exclude=False, limit=False, weight=None, health_state=None, action=None, restore_mark=False): | ||
output = [] | ||
|
||
if 'inbound_interface' in rule_conf: | ||
ifname = rule_conf['inbound_interface'] | ||
if local and not exclude: | ||
output.append(f'oifname != "{ifname}"') | ||
elif not local: | ||
output.append(f'iifname "{ifname}"') | ||
|
||
if 'protocol' in rule_conf and rule_conf['protocol'] != 'all': | ||
protocol = rule_conf['protocol'] | ||
operator = '' | ||
|
||
if protocol[:1] == '!': | ||
operator = '!=' | ||
protocol = protocol[1:] | ||
|
||
if protocol == 'tcp_udp': | ||
protocol = '{ tcp, udp }' | ||
|
||
output.append(f'meta l4proto {operator} {protocol}') | ||
|
||
for direction in ['source', 'destination']: | ||
if direction not in rule_conf: | ||
continue | ||
|
||
direction_conf = rule_conf[direction] | ||
prefix = direction[:1] | ||
|
||
if 'address' in direction_conf: | ||
operator = '' | ||
address = direction_conf['address'] | ||
if address[:1] == '!': | ||
operator = '!=' | ||
address = address[1:] | ||
output.append(f'ip {prefix}addr {operator} {address}') | ||
|
||
if 'port' in direction_conf: | ||
operator = '' | ||
port = direction_conf['port'] | ||
if port[:1] == '!': | ||
operator = '!=' | ||
port = port[1:] | ||
output.append(f'th {prefix}port {operator} {port}') | ||
|
||
if 'source_based_routing' not in rule_conf and not restore_mark: | ||
output.append('ct state new') | ||
|
||
if limit and 'limit' in rule_conf and 'rate' in rule_conf['limit']: | ||
output.append(f'limit rate {rule_conf["limit"]["rate"]}/{rule_conf["limit"]["period"]}') | ||
if 'burst' in rule_conf['limit']: | ||
output.append(f'burst {rule_conf["limit"]["burst"]} packets') | ||
|
||
output.append('counter') | ||
|
||
if restore_mark: | ||
output.append('meta mark set ct mark') | ||
elif weight: | ||
weights, total_weight = wlb_weight_interfaces(rule_conf, health_state) | ||
if len(weights) > 1: # Create weight-based verdict map | ||
vmap_str = ", ".join(f'{weight} : jump wlb_mangle_isp_{ifname}' for ifname, weight in weights) | ||
output.append(f'numgen random mod {total_weight} vmap {{ {vmap_str} }}') | ||
elif len(weights) == 1: # Jump to single ISP | ||
ifname, _ = weights[0] | ||
output.append(f'jump wlb_mangle_isp_{ifname}') | ||
else: # No healthy interfaces | ||
return "" | ||
elif action: | ||
output.append(action) | ||
|
||
return " ".join(output) | ||
|
||
def wlb_weight_interfaces(rule_conf, health_state): | ||
interfaces = [(ifname, int(if_conf.get('weight', 1))) for ifname, if_conf in rule_conf['interface'].items() if health_state[ifname]['state']] | ||
|
||
if not interfaces: | ||
return [], 0 | ||
|
||
if 'failover' in rule_conf: | ||
for ifpair in sorted(interfaces, key=lambda i: i[1], reverse=True): | ||
return [ifpair], ifpair[1] # Return highest weight interface that is ACTIVE when in failover | ||
|
||
total_weight = sum(weight for ifname, weight in interfaces) | ||
out = [] | ||
start = 0 | ||
for ifname, weight in sorted(interfaces, key=lambda i: i[1]): # build weight ranges | ||
end = start + weight - 1 | ||
out.append((ifname, f'{start}-{end}' if end > start else start)) | ||
start = weight | ||
|
||
return out, total_weight | ||
|
||
def health_ping_host(host, ifname, count=1, wait_time=0): | ||
cmd_str = f'ping -c {count} -W {wait_time} -I {ifname} {host}' | ||
rc = run(cmd_str) | ||
return rc == 0 | ||
|
||
def health_ping_host_ttl(host, ifname, count=1, ttl_limit=0): | ||
cmd_str = f'ping -c {count} -t {ttl_limit} -I {ifname} {host}' | ||
rc = run(cmd_str) | ||
return rc != 0 | ||
|
||
def parse_dhcp_nexthop(ifname): | ||
lease_file = os.path.join(directories['isc_dhclient_dir'], dhclient_lease.format(ifname)) | ||
|
||
if not os.path.exists(lease_file): | ||
return False | ||
|
||
with open(lease_file, 'r') as f: | ||
for line in f.readlines(): | ||
data = line.replace('\n', '').split('=') | ||
if data[0] == 'new_routers': | ||
return data[1].replace("'", '').split(" ")[0] | ||
|
||
return None | ||
|
||
def parse_ppp_nexthop(ifname): | ||
nexthop_file = os.path.join(directories['ppp_nexthop_dir'], ifname) | ||
|
||
if not os.path.exists(nexthop_file): | ||
return False | ||
|
||
with open(nexthop_file, 'r') as f: | ||
return f.read() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.