Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F117520359
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
23 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2
index 23abb66dc..6ea5f91d0 100644
--- a/data/templates/high-availability/keepalived.conf.j2
+++ b/data/templates/high-availability/keepalived.conf.j2
@@ -1,216 +1,217 @@
# Autogenerated by VyOS
# Do not edit this file, all your changes will be lost
# on next commit or reboot
+# Global definitions configuration block
global_defs {
dynamic_interfaces
script_user root
{% if vrrp.global_parameters.startup_delay is vyos_defined %}
vrrp_startup_delay {{ vrrp.global_parameters.startup_delay }}
{% endif %}
{% if vrrp.global_parameters.garp is vyos_defined %}
{% if vrrp.global_parameters.garp.interval is vyos_defined %}
vrrp_garp_interval {{ vrrp.global_parameters.garp.interval }}
{% endif %}
{% if vrrp.global_parameters.garp.master_delay is vyos_defined %}
vrrp_garp_master_delay {{ vrrp.global_parameters.garp.master_delay }}
{% endif %}
{% if vrrp.global_parameters.garp.master_refresh is vyos_defined %}
vrrp_garp_master_refresh {{ vrrp.global_parameters.garp.master_refresh }}
{% endif %}
{% if vrrp.global_parameters.garp.master_refresh_repeat is vyos_defined %}
vrrp_garp_master_refresh_repeat {{ vrrp.global_parameters.garp.master_refresh_repeat }}
{% endif %}
{% if vrrp.global_parameters.garp.master_repeat is vyos_defined %}
vrrp_garp_master_repeat {{ vrrp.global_parameters.garp.master_repeat }}
{% endif %}
{% endif %}
notify_fifo /run/keepalived/keepalived_notify_fifo
notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py
}
{% if vrrp.group is vyos_defined %}
{% for name, group_config in vrrp.group.items() if group_config.disable is not vyos_defined %}
{% if group_config.health_check.script is vyos_defined %}
vrrp_script healthcheck_{{ name }} {
script "{{ group_config.health_check.script }}"
interval {{ group_config.health_check.interval }}
fall {{ group_config.health_check.failure_count }}
rise 1
}
{% endif %}
vrrp_instance {{ name }} {
{% if group_config.description is vyos_defined %}
# {{ group_config.description }}
{% endif %}
state BACKUP
interface {{ group_config.interface }}
virtual_router_id {{ group_config.vrid }}
priority {{ group_config.priority }}
advert_int {{ group_config.advertise_interval }}
{% if group_config.garp is vyos_defined %}
{% if group_config.garp.interval is vyos_defined %}
garp_interval {{ group_config.garp.interval }}
{% endif %}
{% if group_config.garp.master_delay is vyos_defined %}
garp_master_delay {{ group_config.garp.master_delay }}
{% endif %}
{% if group_config.garp.master_repeat is vyos_defined %}
garp_master_repeat {{ group_config.garp.master_repeat }}
{% endif %}
{% if group_config.garp.master_refresh is vyos_defined %}
garp_master_refresh {{ group_config.garp.master_refresh }}
{% endif %}
{% if group_config.garp.master_refresh_repeat is vyos_defined %}
garp_master_refresh_repeat {{ group_config.garp.master_refresh_repeat }}
{% endif %}
{% endif %}
{% if group_config.track.exclude_vrrp_interface is vyos_defined %}
dont_track_primary
{% endif %}
{% if group_config.no_preempt is not vyos_defined and group_config.preempt_delay is vyos_defined %}
preempt_delay {{ group_config.preempt_delay }}
{% elif group_config.no_preempt is vyos_defined %}
nopreempt
{% endif %}
{% if group_config.peer_address is vyos_defined %}
unicast_peer { {{ group_config.peer_address }} }
{% endif %}
{% if group_config.hello_source_address is vyos_defined %}
{% if group_config.peer_address is vyos_defined %}
unicast_src_ip {{ group_config.hello_source_address }}
{% else %}
mcast_src_ip {{ group_config.hello_source_address }}
{% endif %}
{% endif %}
{% if group_config.rfc3768_compatibility is vyos_defined and group_config.peer_address is vyos_defined %}
use_vmac {{ group_config.interface }}v{{ group_config.vrid }}v{{ '4' if group_config['address'] | first | is_ipv4 else '6' }}
vmac_xmit_base
{% elif group_config.rfc3768_compatibility is vyos_defined %}
use_vmac {{ group_config.interface }}v{{ group_config.vrid }}v{{ '4' if group_config['address'] | first | is_ipv4 else '6' }}
{% endif %}
{% if group_config.authentication is vyos_defined %}
authentication {
auth_pass "{{ group_config.authentication.password }}"
{% if group_config.authentication.type is vyos_defined('plaintext-password') %}
auth_type PASS
{% else %}
auth_type {{ group_config.authentication.type | upper }}
{% endif %}
}
{% endif %}
{% if group_config.address is vyos_defined %}
virtual_ipaddress {
{% for addr, addr_config in group_config.address.items() %}
{{ addr }}{{ ' dev ' + addr_config.interface if addr_config.interface is vyos_defined }}
{% endfor %}
}
{% endif %}
{% if group_config.excluded_address is vyos_defined %}
virtual_ipaddress_excluded {
{% for addr in group_config.excluded_address %}
{{ addr }}
{% endfor %}
}
{% endif %}
{% if group_config.track.interface is vyos_defined %}
track_interface {
{% for interface in group_config.track.interface %}
{{ interface }}
{% endfor %}
}
{% endif %}
{% if group_config.health_check.script is vyos_defined %}
track_script {
healthcheck_{{ name }}
}
{% endif %}
}
{% endfor %}
{% endif %}
{% if vrrp.sync_group is vyos_defined %}
{% for name, sync_group_config in vrrp.sync_group.items() if sync_group_config.disable is not vyos_defined %}
vrrp_sync_group {{ name }} {
group {
{% if sync_group_config.member is vyos_defined %}
{% for member in sync_group_config.member %}
{{ member }}
{% endfor %}
{% endif %}
}
{# Health-check scripts should be in section sync-group if member is part of the sync-group T4081 #}
{% if vrrp.group is vyos_defined %}
{% for name, group_config in vrrp.group.items() if group_config.disable is not vyos_defined %}
{% if group_config.health_check.script is vyos_defined and name in sync_group_config.member %}
track_script {
healthcheck_{{ name }}
}
{% endif %}
{% endfor %}
{% endif %}
{% if conntrack_sync_group is vyos_defined(name) %}
{% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %}
notify_master "{{ vyos_helper }} master {{ name }}"
notify_backup "{{ vyos_helper }} backup {{ name }}"
notify_fault "{{ vyos_helper }} fault {{ name }}"
{% endif %}
}
{% endfor %}
{% endif %}
{% if virtual_server is vyos_defined %}
# Virtual-server configuration
{% for vserver, vserver_config in virtual_server.items() %}
# Vserver {{ vserver }}
{% if vserver_config.port is vyos_defined %}
virtual_server {{ vserver }} {{ vserver_config.port }} {
{% else %}
virtual_server fwmark {{ vserver_config.fwmark }} {
{% endif %}
delay_loop {{ vserver_config.delay_loop }}
{% if vserver_config.algorithm is vyos_defined('round-robin') %}
lb_algo rr
{% elif vserver_config.algorithm is vyos_defined('weighted-round-robin') %}
lb_algo wrr
{% elif vserver_config.algorithm is vyos_defined('least-connection') %}
lb_algo lc
{% elif vserver_config.algorithm is vyos_defined('weighted-least-connection') %}
lb_algo wlc
{% elif vserver_config.algorithm is vyos_defined('source-hashing') %}
lb_algo sh
{% elif vserver_config.algorithm is vyos_defined('destination-hashing') %}
lb_algo dh
{% elif vserver_config.algorithm is vyos_defined('locality-based-least-connection') %}
lb_algo lblc
{% endif %}
{% if vserver_config.forward_method is vyos_defined('nat') %}
lb_kind NAT
{% elif vserver_config.forward_method is vyos_defined('direct') %}
lb_kind DR
{% elif vserver_config.forward_method is vyos_defined('tunnel') %}
lb_kind TUN
{% endif %}
persistence_timeout {{ vserver_config.persistence_timeout }}
protocol {{ vserver_config.protocol | upper }}
{% if vserver_config.real_server is vyos_defined %}
{% for rserver, rserver_config in vserver_config.real_server.items() %}
real_server {{ rserver }} {{ rserver_config.port }} {
weight 1
{% if rserver_config.health_check.script is vyos_defined %}
MISC_CHECK {
misc_path {{ rserver_config.health_check.script }}
{% else %}
{{ vserver_config.protocol | upper }}_CHECK {
{% if rserver_config.connection_timeout is vyos_defined %}
connect_timeout {{ rserver_config.connection_timeout }}
{% endif %}
{% endif %}
}
}
{% endfor %}
{% endif %}
}
{% endfor %}
{% endif %}
diff --git a/interface-definitions/include/vrrp/garp.xml.i b/interface-definitions/include/vrrp/garp.xml.i
index b321c9591..b56b490df 100644
--- a/interface-definitions/include/vrrp/garp.xml.i
+++ b/interface-definitions/include/vrrp/garp.xml.i
@@ -1,74 +1,78 @@
<!-- include start from vrrp/garp.xml.i -->
<node name="garp">
<properties>
<help>Gratuitous ARP parameters</help>
</properties>
<children>
- <leafNode name="master-delay">
+ <leafNode name="interval">
<properties>
- <help>Delay for second set of gratuitous ARPs after transition to MASTER</help>
+ <help>Interval between Gratuitous ARP</help>
<valueHelp>
- <format>u32:1-1000</format>
- <description>Delay for second set of gratuitous ARPs after transition to MASTER</description>
+ <format><0.000-1000></format>
+ <description>Interval in seconds, resolution microseconds</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-1000"/>
+ <validator name="numeric" argument="--range 0.000-1000 --float"/>
</constraint>
</properties>
- <defaultValue>5</defaultValue>
+ <defaultValue>0</defaultValue>
</leafNode>
- <leafNode name="master-repeat">
+ <leafNode name="master-delay">
<properties>
- <help>Number of gratuitous ARP messages to send at a time after transition to MASTER</help>
+ <help>Delay for second set of gratuitous ARPs after transition to master</help>
<valueHelp>
- <format>u32:1-255</format>
- <description>Number of gratuitous ARP messages to send at a time after transition to MASTER</description>
+ <format>u32:1-1000</format>
+ <description>Delay in seconds</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-255"/>
+ <validator name="numeric" argument="--range 1-1000"/>
</constraint>
</properties>
<defaultValue>5</defaultValue>
</leafNode>
<leafNode name="master-refresh">
<properties>
- <help>Minimum time interval for refreshing gratuitous ARPs while MASTER. 0 means no refresh</help>
+ <help>Minimum time interval for refreshing gratuitous ARPs while beeing master</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>No refresh</description>
+ </valueHelp>
<valueHelp>
<format>u32:1-255</format>
- <description>Minimum time interval for refreshing gratuitous ARPs while MASTER. 0 means no refresh</description>
+ <description>Interval in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-255"/>
</constraint>
</properties>
<defaultValue>5</defaultValue>
</leafNode>
<leafNode name="master-refresh-repeat">
<properties>
- <help>Number of gratuitous ARP messages to send at a time while MASTER</help>
+ <help>Number of gratuitous ARP messages to send at a time while beeing master</help>
<valueHelp>
<format>u32:1-255</format>
- <description>Number of gratuitous ARP messages to send at a time while MASTER</description>
+ <description>Number of gratuitous ARP messages</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-255"/>
</constraint>
</properties>
<defaultValue>1</defaultValue>
</leafNode>
- <leafNode name="interval">
+ <leafNode name="master-repeat">
<properties>
- <help>Delay between gratuitous ARP messages sent on an interface</help>
+ <help>Number of gratuitous ARP messages to send at a time after transition to master</help>
<valueHelp>
- <format><0.000-1000></format>
- <description>Delay between gratuitous ARP messages sent on an interface</description>
+ <format>u32:1-255</format>
+ <description>Number of gratuitous ARP messages</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 0.000-1000 --float"/>
+ <validator name="numeric" argument="--range 1-255"/>
</constraint>
</properties>
- <defaultValue>0</defaultValue>
+ <defaultValue>5</defaultValue>
</leafNode>
</children>
</node>
<!-- include end -->
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index bc3e67b40..79e407efd 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -1,185 +1,201 @@
#!/usr/bin/env python3
#
# Copyright (C) 2018-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
# 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 sys import exit
from ipaddress import ip_interface
from ipaddress import IPv4Interface
from ipaddress import IPv6Interface
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.ifconfig.vrrp import VRRP
from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.util import call
+from vyos.util import dict_search
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
base = ['high-availability']
base_vrrp = ['high-availability', 'vrrp']
if not conf.exists(base):
return None
ha = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
if 'vrrp' in ha:
+ if dict_search('vrrp.global_parameters.garp', ha) != None:
+ default_values = defaults(base_vrrp + ['global-parameters', 'garp'])
+ ha['vrrp']['global_parameters']['garp'] = dict_merge(
+ default_values, ha['vrrp']['global_parameters']['garp'])
+
if 'group' in ha['vrrp']:
- default_values_vrrp = defaults(base_vrrp + ['group'])
- if 'garp' in default_values_vrrp:
- del default_values_vrrp['garp']
+ default_values = defaults(base_vrrp + ['group'])
+ default_values_garp = defaults(base_vrrp + ['group', 'garp'])
+
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if 'garp' in default_values:
+ del default_values['garp']
for group in ha['vrrp']['group']:
- ha['vrrp']['group'][group] = dict_merge(default_values_vrrp, ha['vrrp']['group'][group])
+ ha['vrrp']['group'][group] = dict_merge(default_values, ha['vrrp']['group'][group])
+
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if 'garp' in ha['vrrp']['group'][group]:
+ ha['vrrp']['group'][group]['garp'] = dict_merge(
+ default_values_garp, ha['vrrp']['group'][group]['garp'])
# Merge per virtual-server default values
if 'virtual_server' in ha:
default_values = defaults(base + ['virtual-server'])
for vs in ha['virtual_server']:
ha['virtual_server'][vs] = dict_merge(default_values, ha['virtual_server'][vs])
## Get the sync group used for conntrack-sync
conntrack_path = ['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group']
if conf.exists(conntrack_path):
ha['conntrack_sync_group'] = conf.return_value(conntrack_path)
return ha
def verify(ha):
if not ha:
return None
used_vrid_if = []
if 'vrrp' in ha and 'group' in ha['vrrp']:
for group, group_config in ha['vrrp']['group'].items():
# Check required fields
if 'vrid' not in group_config:
raise ConfigError(f'VRID is required but not set in VRRP group "{group}"')
if 'interface' not in group_config:
raise ConfigError(f'Interface is required but not set in VRRP group "{group}"')
if 'address' not in group_config:
raise ConfigError(f'Virtual IP address is required but not set in VRRP group "{group}"')
if 'authentication' in group_config:
if not {'password', 'type'} <= set(group_config['authentication']):
raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"')
# Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
# We also need to make sure VRID is not used twice on the same interface with the
# same address family.
interface = group_config['interface']
vrid = group_config['vrid']
# XXX: filter on map object is destructive, so we force it to list.
# Additionally, filter objects always evaluate to True, empty or not,
# so we force them to lists as well.
vaddrs = list(map(lambda i: ip_interface(i), group_config['address']))
vaddrs4 = list(filter(lambda x: isinstance(x, IPv4Interface), vaddrs))
vaddrs6 = list(filter(lambda x: isinstance(x, IPv6Interface), vaddrs))
if vaddrs4 and vaddrs6:
raise ConfigError(f'VRRP group "{group}" mixes IPv4 and IPv6 virtual addresses, this is not allowed.\n' \
'Create individual groups for IPv4 and IPv6!')
if vaddrs4:
tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv4'}
if tmp in used_vrid_if:
raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface} with address family IPv4"!')
used_vrid_if.append(tmp)
if 'hello_source_address' in group_config:
if is_ipv6(group_config['hello_source_address']):
raise ConfigError(f'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!')
if 'peer_address' in group_config:
if is_ipv6(group_config['peer_address']):
raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!')
if vaddrs6:
tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv6'}
if tmp in used_vrid_if:
raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface} with address family IPv6"!')
used_vrid_if.append(tmp)
if 'hello_source_address' in group_config:
if is_ipv4(group_config['hello_source_address']):
raise ConfigError(f'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!')
if 'peer_address' in group_config:
if is_ipv4(group_config['peer_address']):
raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!')
# Check sync groups
if 'vrrp' in ha and 'sync_group' in ha['vrrp']:
for sync_group, sync_config in ha['vrrp']['sync_group'].items():
if 'member' in sync_config:
for member in sync_config['member']:
if member not in ha['vrrp']['group']:
raise ConfigError(f'VRRP sync-group "{sync_group}" refers to VRRP group "{member}", '\
'but it does not exist!')
# Virtual-server
if 'virtual_server' in ha:
for vs, vs_config in ha['virtual_server'].items():
if 'port' not in vs_config and 'fwmark' not in vs_config:
raise ConfigError(f'Port or fwmark is required but not set for virtual-server "{vs}"')
if 'port' in vs_config and 'fwmark' in vs_config:
raise ConfigError(f'Cannot set both port and fwmark for virtual-server "{vs}"')
if 'real_server' not in vs_config:
raise ConfigError(f'Real-server ip is required but not set for virtual-server "{vs}"')
# Real-server
for rs, rs_config in vs_config['real_server'].items():
if 'port' not in rs_config:
raise ConfigError(f'Port is required but not set for virtual-server "{vs}" real-server "{rs}"')
def generate(ha):
if not ha:
return None
render(VRRP.location['config'], 'high-availability/keepalived.conf.j2', ha)
return None
def apply(ha):
service_name = 'keepalived.service'
if not ha:
call(f'systemctl stop {service_name}')
return None
call(f'systemctl reload-or-restart {service_name}')
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
exit(1)
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Sep 26, 10:09 AM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4284885
Default Alt Text
(23 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment