summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/firewall.py70
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py34
-rwxr-xr-xsrc/conf_mode/http-api.py6
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py38
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py237
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py7
-rwxr-xr-xsrc/conf_mode/policy-local-route.py79
7 files changed, 428 insertions, 43 deletions
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index f6480ab0a..9791cf009 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -374,12 +374,82 @@ def verify(firewall):
for rule_id, rule_conf in name_conf['rule'].items():
verify_rule(firewall, rule_conf, True)
+ #### ZONESSSS
+ local_zone = False
+ zone_interfaces = []
+
+ if 'zone' in firewall:
+ for zone, zone_conf in firewall['zone'].items():
+ if 'local_zone' not in zone_conf and 'interface' not in zone_conf:
+ raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone')
+
+ if 'local_zone' in zone_conf:
+ if local_zone:
+ raise ConfigError('There cannot be multiple local zones')
+ if 'interface' in zone_conf:
+ raise ConfigError('Local zone cannot have interfaces assigned')
+ if 'intra_zone_filtering' in zone_conf:
+ raise ConfigError('Local zone cannot use intra-zone-filtering')
+ local_zone = True
+
+ if 'interface' in zone_conf:
+ found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces]
+
+ if found_duplicates:
+ raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
+
+ zone_interfaces += zone_conf['interface']
+
+ if 'intra_zone_filtering' in zone_conf:
+ intra_zone = zone_conf['intra_zone_filtering']
+
+ if len(intra_zone) > 1:
+ raise ConfigError('Only one intra-zone-filtering action must be specified')
+
+ if 'firewall' in intra_zone:
+ v4_name = dict_search_args(intra_zone, 'firewall', 'name')
+ if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+
+ v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name')
+ if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+
+ if not v4_name and not v6_name:
+ raise ConfigError('No firewall names specified for intra-zone-filtering')
+
+ if 'from' in zone_conf:
+ for from_zone, from_conf in zone_conf['from'].items():
+ if from_zone not in firewall['zone']:
+ raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"')
+
+ v4_name = dict_search_args(from_conf, 'firewall', 'name')
+ if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+
+ v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
+ if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+
return None
def generate(firewall):
if not os.path.exists(nftables_conf):
firewall['first_install'] = True
+ if 'zone' in firewall:
+ for local_zone, local_zone_conf in firewall['zone'].items():
+ if 'local_zone' not in local_zone_conf:
+ continue
+
+ local_zone_conf['from_local'] = {}
+
+ for zone, zone_conf in firewall['zone'].items():
+ if zone == local_zone or 'from' not in zone_conf:
+ continue
+ if local_zone in zone_conf['from']:
+ local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]
+
render(nftables_conf, 'firewall/nftables.j2', firewall)
return None
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 81ee39df1..206f513c8 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -28,6 +28,7 @@ from vyos.ifconfig import Section
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.process import cmd
+from vyos.utils.process import run
from vyos.utils.network import is_addr_assigned
from vyos import ConfigError
from vyos import airbag
@@ -116,6 +117,30 @@ def _nftables_config(configured_ifaces, direction, length=None):
cmd(command, raising=ConfigError)
+def _nftables_trigger_setup(operation: str) -> None:
+ """Add a dummy rule to unlock the main pmacct loop with a packet-trigger
+
+ Args:
+ operation (str): 'add' or 'delete' a trigger
+ """
+ # check if a chain exists
+ table_exists = False
+ if run('nft -snj list table ip pmacct') == 0:
+ table_exists = True
+
+ if operation == 'delete' and table_exists:
+ nft_cmd: str = 'nft delete table ip pmacct'
+ cmd(nft_cmd, raising=ConfigError)
+ if operation == 'add' and not table_exists:
+ nft_cmds: list[str] = [
+ 'nft add table ip pmacct',
+ 'nft add chain ip pmacct pmacct_out { type filter hook output priority raw - 50 \\; policy accept \\; }',
+ 'nft add rule ip pmacct pmacct_out oif lo ip daddr 127.0.254.0 counter log group 2 snaplen 1 queue-threshold 0 comment NFLOG_TRIGGER'
+ ]
+ for nft_cmd in nft_cmds:
+ cmd(nft_cmd, raising=ConfigError)
+
+
def get_config(config=None):
if config:
conf = config
@@ -252,7 +277,6 @@ def generate(flow_config):
call('systemctl daemon-reload')
def apply(flow_config):
- action = 'restart'
# Check if flow-accounting was removed and define command
if not flow_config:
_nftables_config([], 'ingress')
@@ -262,6 +286,10 @@ def apply(flow_config):
call(f'systemctl stop {systemd_service}')
if os.path.exists(uacctd_conf_path):
os.unlink(uacctd_conf_path)
+
+ # must be done after systemctl
+ _nftables_trigger_setup('delete')
+
return
# Start/reload flow-accounting daemon
@@ -277,6 +305,10 @@ def apply(flow_config):
else:
_nftables_config([], 'egress')
+ # add a trigger for signal processing
+ _nftables_trigger_setup('add')
+
+
if __name__ == '__main__':
try:
config = get_config()
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index 793a90d88..d8fe3b736 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -27,6 +27,7 @@ from vyos.config import Config
from vyos.configdep import set_dependents, call_dependents
from vyos.template import render
from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -130,7 +131,10 @@ def apply(http_api):
service_name = 'vyos-http-api.service'
if http_api is not None:
- call(f'systemctl restart {service_name}')
+ if is_systemd_service_running(f'{service_name}'):
+ call(f'systemctl reload {service_name}')
+ else:
+ call(f'systemctl restart {service_name}')
else:
call(f'systemctl stop {service_name}')
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index 0bd306ed0..1179e3e4f 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -18,7 +18,6 @@ import os
from sys import exit
from netifaces import interfaces
-
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
@@ -34,10 +33,13 @@ from vyos.configverify import verify_source_interface
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
from vyos.ifconfig import BondIf
+from vyos.ifconfig.ethernet import EthernetIf
from vyos.ifconfig import Section
from vyos.utils.dict import dict_search
+from vyos.utils.dict import dict_to_paths_values
from vyos.configdict import has_address_configured
from vyos.configdict import has_vrf_configured
+from vyos.configdep import set_dependents, call_dependents
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -90,7 +92,6 @@ def get_config(config=None):
# determine which members have been removed
interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface'])
-
# Reset config level to interfaces
old_level = conf.get_level()
conf.set_level(['interfaces'])
@@ -102,6 +103,10 @@ def get_config(config=None):
tmp = {}
for interface in interfaces_removed:
+ # if member is deleted from bond, add dependencies to call
+ # ethernet commit again in apply function
+ # to apply options under ethernet section
+ set_dependents('ethernet', conf, interface)
section = Section.section(interface) # this will be 'ethernet' for 'eth0'
if conf.exists([section, interface, 'disable']):
tmp[interface] = {'disable': ''}
@@ -116,9 +121,21 @@ def get_config(config=None):
if dict_search('member.interface', bond):
for interface, interface_config in bond['member']['interface'].items():
+
+ interface_ethernet_config = conf.get_config_dict(
+ ['interfaces', 'ethernet', interface],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_defaults=False,
+ with_recursive_defaults=False)
+
+ interface_config['config_paths'] = dict_to_paths_values(interface_ethernet_config)
+
# Check if member interface is a new member
if not conf.exists_effective(base + [ifname, 'member', 'interface', interface]):
bond['shutdown_required'] = {}
+ interface_config['new_added'] = {}
# Check if member interface is disabled
conf.set_level(['interfaces'])
@@ -151,7 +168,6 @@ def get_config(config=None):
# bond members must not have a VRF attached
tmp = has_vrf_configured(conf, interface)
if tmp: interface_config['has_vrf'] = {}
-
return bond
@@ -212,6 +228,14 @@ def verify(bond):
if 'has_vrf' in interface_config:
raise ConfigError(error_msg + 'it has a VRF assigned!')
+ if 'new_added' in interface_config and 'config_paths' in interface_config:
+ for option_path, option_value in interface_config['config_paths'].items():
+ if option_path in EthernetIf.get_bond_member_allowed_options() :
+ continue
+ if option_path in BondIf.get_inherit_bond_options():
+ continue
+ raise ConfigError(error_msg + f'it has a "{option_path.replace(".", " ")}" assigned!')
+
if 'primary' in bond:
if bond['primary'] not in bond['member']['interface']:
raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface')
@@ -227,13 +251,17 @@ def generate(bond):
def apply(bond):
b = BondIf(bond['ifname'])
-
if 'deleted' in bond:
# delete interface
b.remove()
else:
b.update(bond)
-
+ if dict_search('member.interface_remove', bond):
+ try:
+ call_dependents()
+ except ConfigError:
+ raise ConfigError('Error in updating ethernet interface '
+ 'after deleting it from bond')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index f3e65ad5e..7374a29f7 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import pprint
from glob import glob
from sys import exit
@@ -35,6 +36,7 @@ from vyos.configverify import verify_vrf
from vyos.configverify import verify_bond_bridge_member
from vyos.ethtool import Ethtool
from vyos.ifconfig import EthernetIf
+from vyos.ifconfig import BondIf
from vyos.pki import find_chain
from vyos.pki import encode_certificate
from vyos.pki import load_certificate
@@ -42,6 +44,9 @@ from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.dict import dict_search
+from vyos.utils.dict import dict_to_paths_values
+from vyos.utils.dict import dict_set
+from vyos.utils.dict import dict_delete
from vyos.utils.file import write_file
from vyos import ConfigError
from vyos import airbag
@@ -51,6 +56,90 @@ airbag.enable()
cfg_dir = '/run/wpa_supplicant'
wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf'
+def update_bond_options(conf: Config, eth_conf: dict) -> list:
+ """
+ Return list of blocked options if interface is a bond member
+ :param conf: Config object
+ :type conf: Config
+ :param eth_conf: Ethernet config dictionary
+ :type eth_conf: dict
+ :return: List of blocked options
+ :rtype: list
+ """
+ blocked_list = []
+ bond_name = list(eth_conf['is_bond_member'].keys())[0]
+ config_without_defaults = conf.get_config_dict(
+ ['interfaces', 'ethernet', eth_conf['ifname']],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_defaults=False,
+ with_recursive_defaults=False)
+ config_with_defaults = conf.get_config_dict(
+ ['interfaces', 'ethernet', eth_conf['ifname']],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_defaults=True,
+ with_recursive_defaults=True)
+ bond_config_with_defaults = conf.get_config_dict(
+ ['interfaces', 'bonding', bond_name],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_defaults=True,
+ with_recursive_defaults=True)
+ eth_dict_paths = dict_to_paths_values(config_without_defaults)
+ eth_path_base = ['interfaces', 'ethernet', eth_conf['ifname']]
+
+ #if option is configured under ethernet section
+ for option_path, option_value in eth_dict_paths.items():
+ bond_option_value = dict_search(option_path, bond_config_with_defaults)
+
+ #If option is allowed for changing then continue
+ if option_path in EthernetIf.get_bond_member_allowed_options():
+ continue
+ # if option is inherited from bond then set valued from bond interface
+ if option_path in BondIf.get_inherit_bond_options():
+ # If option equals to bond option then do nothing
+ if option_value == bond_option_value:
+ continue
+ else:
+ # if ethernet has option and bond interface has
+ # then copy it from bond
+ if bond_option_value is not None:
+ if is_node_changed(conf, eth_path_base + option_path.split('.')):
+ Warning(
+ f'Cannot apply "{option_path.replace(".", " ")}" to "{option_value}".' \
+ f' Interface "{eth_conf["ifname"]}" is a bond member.' \
+ f' Option is inherited from bond "{bond_name}"')
+ dict_set(option_path, bond_option_value, eth_conf)
+ continue
+ # if ethernet has option and bond interface does not have
+ # then delete it form dict and do not apply it
+ else:
+ if is_node_changed(conf, eth_path_base + option_path.split('.')):
+ Warning(
+ f'Cannot apply "{option_path.replace(".", " ")}".' \
+ f' Interface "{eth_conf["ifname"]}" is a bond member.' \
+ f' Option is inherited from bond "{bond_name}"')
+ dict_delete(option_path, eth_conf)
+ blocked_list.append(option_path)
+
+ # if inherited option is not configured under ethernet section but configured under bond section
+ for option_path in BondIf.get_inherit_bond_options():
+ bond_option_value = dict_search(option_path, bond_config_with_defaults)
+ if bond_option_value is not None:
+ if option_path not in eth_dict_paths:
+ if is_node_changed(conf, eth_path_base + option_path.split('.')):
+ Warning(
+ f'Cannot apply "{option_path.replace(".", " ")}" to "{dict_search(option_path, config_with_defaults)}".' \
+ f' Interface "{eth_conf["ifname"]}" is a bond member. ' \
+ f'Option is inherited from bond "{bond_name}"')
+ dict_set(option_path, bond_option_value, eth_conf)
+ eth_conf['bond_blocked_changes'] = blocked_list
+ return None
+
def get_config(config=None):
"""
Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
@@ -68,6 +157,8 @@ def get_config(config=None):
base = ['interfaces', 'ethernet']
ifname, ethernet = get_interface_dict(conf, base)
+ if 'is_bond_member' in ethernet:
+ update_bond_options(conf, ethernet)
if 'deleted' not in ethernet:
if pki: ethernet['pki'] = pki
@@ -80,26 +171,20 @@ def get_config(config=None):
return ethernet
-def verify(ethernet):
- if 'deleted' in ethernet:
- return None
- ifname = ethernet['ifname']
- verify_interface_exists(ifname)
- verify_mtu(ethernet)
- verify_mtu_ipv6(ethernet)
- verify_dhcpv6(ethernet)
- verify_address(ethernet)
- verify_vrf(ethernet)
- verify_bond_bridge_member(ethernet)
- verify_eapol(ethernet)
- verify_mirror_redirect(ethernet)
- ethtool = Ethtool(ifname)
- # No need to check speed and duplex keys as both have default values.
+def verify_speed_duplex(ethernet: dict, ethtool: Ethtool):
+ """
+ Verify speed and duplex
+ :param ethernet: dictionary which is received from get_interface_dict
+ :type ethernet: dict
+ :param ethtool: Ethernet object
+ :type ethtool: Ethtool
+ """
if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or
- (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')):
- raise ConfigError('Speed/Duplex missmatch. Must be both auto or manually configured')
+ (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')):
+ raise ConfigError(
+ 'Speed/Duplex missmatch. Must be both auto or manually configured')
if ethernet['speed'] != 'auto' and ethernet['duplex'] != 'auto':
# We need to verify if the requested speed and duplex setting is
@@ -107,37 +192,66 @@ def verify(ethernet):
speed = ethernet['speed']
duplex = ethernet['duplex']
if not ethtool.check_speed_duplex(speed, duplex):
- raise ConfigError(f'Adapter does not support changing speed and duplex '\
- f'settings to: {speed}/{duplex}!')
+ raise ConfigError(
+ f'Adapter does not support changing speed ' \
+ f'and duplex settings to: {speed}/{duplex}!')
+
+def verify_flow_control(ethernet: dict, ethtool: Ethtool):
+ """
+ Verify flow control
+ :param ethernet: dictionary which is received from get_interface_dict
+ :type ethernet: dict
+ :param ethtool: Ethernet object
+ :type ethtool: Ethtool
+ """
if 'disable_flow_control' in ethernet:
if not ethtool.check_flow_control():
- raise ConfigError('Adapter does not support changing flow-control settings!')
+ raise ConfigError(
+ 'Adapter does not support changing flow-control settings!')
+
+def verify_ring_buffer(ethernet: dict, ethtool: Ethtool):
+ """
+ Verify ring buffer
+ :param ethernet: dictionary which is received from get_interface_dict
+ :type ethernet: dict
+ :param ethtool: Ethernet object
+ :type ethtool: Ethtool
+ """
if 'ring_buffer' in ethernet:
max_rx = ethtool.get_ring_buffer_max('rx')
if not max_rx:
- raise ConfigError('Driver does not support RX ring-buffer configuration!')
+ raise ConfigError(
+ 'Driver does not support RX ring-buffer configuration!')
max_tx = ethtool.get_ring_buffer_max('tx')
if not max_tx:
- raise ConfigError('Driver does not support TX ring-buffer configuration!')
+ raise ConfigError(
+ 'Driver does not support TX ring-buffer configuration!')
rx = dict_search('ring_buffer.rx', ethernet)
if rx and int(rx) > int(max_rx):
- raise ConfigError(f'Driver only supports a maximum RX ring-buffer '\
+ raise ConfigError(f'Driver only supports a maximum RX ring-buffer ' \
f'size of "{max_rx}" bytes!')
tx = dict_search('ring_buffer.tx', ethernet)
if tx and int(tx) > int(max_tx):
- raise ConfigError(f'Driver only supports a maximum TX ring-buffer '\
+ raise ConfigError(f'Driver only supports a maximum TX ring-buffer ' \
f'size of "{max_tx}" bytes!')
- # verify offloading capabilities
+
+def verify_offload(ethernet: dict, ethtool: Ethtool):
+ """
+ Verify offloading capabilities
+ :param ethernet: dictionary which is received from get_interface_dict
+ :type ethernet: dict
+ :param ethtool: Ethernet object
+ :type ethtool: Ethtool
+ """
if dict_search('offload.rps', ethernet) != None:
- if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'):
+ if not os.path.exists(f'/sys/class/net/{ethernet["ifname"]}/queues/rx-0/rps_cpus'):
raise ConfigError('Interface does not suport RPS!')
-
driver = ethtool.get_driver_name()
# T3342 - Xen driver requires special treatment
if driver == 'vif':
@@ -145,14 +259,73 @@ def verify(ethernet):
raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\
'for MTU size larger then 1500 bytes')
- if {'is_bond_member', 'mac'} <= set(ethernet):
- Warning(f'changing mac address "{mac}" will be ignored as "{ifname}" ' \
- f'is a member of bond "{is_bond_member}"'.format(**ethernet))
+def verify_allowedbond_changes(ethernet: dict):
+ """
+ Verify changed options if interface is in bonding
+ :param ethernet: dictionary which is received from get_interface_dict
+ :type ethernet: dict
+ """
+ if 'bond_blocked_changes' in ethernet:
+ for option in ethernet['bond_blocked_changes']:
+ raise ConfigError(f'Cannot configure "{option.replace(".", " ")}"' \
+ f' on interface "{ethernet["ifname"]}".' \
+ f' Interface is a bond member')
+
+
+def verify(ethernet):
+ if 'deleted' in ethernet:
+ return None
+ if 'is_bond_member' in ethernet:
+ verify_bond_member(ethernet)
+ else:
+ verify_ethernet(ethernet)
+
+
+def verify_bond_member(ethernet):
+ """
+ Verification function for ethernet interface which is in bonding
+ :param ethernet: dictionary which is received from get_interface_dict
+ :type ethernet: dict
+ """
+ ifname = ethernet['ifname']
+ verify_interface_exists(ifname)
+ verify_eapol(ethernet)
+ verify_mirror_redirect(ethernet)
+ ethtool = Ethtool(ifname)
+ verify_speed_duplex(ethernet, ethtool)
+ verify_flow_control(ethernet, ethtool)
+ verify_ring_buffer(ethernet, ethtool)
+ verify_offload(ethernet, ethtool)
+ verify_allowedbond_changes(ethernet)
+
+def verify_ethernet(ethernet):
+ """
+ Verification function for simple ethernet interface
+ :param ethernet: dictionary which is received from get_interface_dict
+ :type ethernet: dict
+ """
+ ifname = ethernet['ifname']
+ verify_interface_exists(ifname)
+ verify_mtu(ethernet)
+ verify_mtu_ipv6(ethernet)
+ verify_dhcpv6(ethernet)
+ verify_address(ethernet)
+ verify_vrf(ethernet)
+ verify_bond_bridge_member(ethernet)
+ verify_eapol(ethernet)
+ verify_mirror_redirect(ethernet)
+ ethtool = Ethtool(ifname)
+ # No need to check speed and duplex keys as both have default values.
+ verify_speed_duplex(ethernet, ethtool)
+ verify_flow_control(ethernet, ethtool)
+ verify_ring_buffer(ethernet, ethtool)
+ verify_offload(ethernet, ethtool)
# use common function to verify VLAN configuration
verify_vlan_config(ethernet)
return None
+
def generate(ethernet):
# render real configuration file once
wpa_supplicant_conf = wpa_suppl_conf.format(**ethernet)
@@ -192,7 +365,8 @@ def generate(ethernet):
pki_ca_cert = ethernet['pki']['ca'][ca_cert_name]
loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
- ca_chains.append('\n'.join(encode_certificate(c) for c in ca_full_chain))
+ ca_chains.append(
+ '\n'.join(encode_certificate(c) for c in ca_full_chain))
write_file(ca_cert_file_path, '\n'.join(ca_chains))
@@ -219,6 +393,7 @@ if __name__ == '__main__':
c = get_config()
verify(c)
generate(c)
+
apply(c)
except ConfigError as e:
print(e)
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 05f68112a..ff8144e74 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -168,6 +168,13 @@ def verify(vxlan):
verify_address(vxlan)
verify_bond_bridge_member(vxlan)
verify_mirror_redirect(vxlan)
+
+ # We use a defaultValue for port, thus it's always safe to use
+ if vxlan['port'] == '8472':
+ Warning('Starting from VyOS 1.4, the default port for VXLAN '\
+ 'has been changed to 4789. This matches the IANA assigned '\
+ 'standard port number!')
+
return None
def generate(vxlan):
diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py
index 2e8aabb80..91e4fce2c 100755
--- a/src/conf_mode/policy-local-route.py
+++ b/src/conf_mode/policy-local-route.py
@@ -52,19 +52,28 @@ def get_config(config=None):
if tmp:
for rule in (tmp or []):
src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address'])
+ src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port'])
fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address'])
+ dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port'])
+ table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table'])
proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])
rule_def = {}
if src:
rule_def = dict_merge({'source': {'address': src}}, rule_def)
+ if src_port:
+ rule_def = dict_merge({'source': {'port': src_port}}, rule_def)
if fwmk:
rule_def = dict_merge({'fwmark' : fwmk}, rule_def)
if iif:
rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
if dst:
rule_def = dict_merge({'destination': {'address': dst}}, rule_def)
+ if dst_port:
+ rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def)
+ if table:
+ rule_def = dict_merge({'table' : table}, rule_def)
if proto:
rule_def = dict_merge({'protocol' : proto}, rule_def)
dict = dict_merge({dict_id : {rule : rule_def}}, dict)
@@ -79,9 +88,12 @@ def get_config(config=None):
if 'rule' in pbr[route]:
for rule, rule_config in pbr[route]['rule'].items():
src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address'])
+ src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port'])
fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address'])
+ dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port'])
+ table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table'])
proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])
# keep track of changes in configuration
# otherwise we might remove an existing node although nothing else has changed
@@ -105,14 +117,32 @@ def get_config(config=None):
if len(src) > 0:
rule_def = dict_merge({'source': {'address': src}}, rule_def)
+ # source port
+ if src_port is None:
+ if 'source' in rule_config:
+ if 'port' in rule_config['source']:
+ tmp = rule_config['source']['port']
+ if isinstance(tmp, str):
+ tmp = [tmp]
+ rule_def = dict_merge({'source': {'port': tmp}}, rule_def)
+ else:
+ changed = True
+ if len(src_port) > 0:
+ rule_def = dict_merge({'source': {'port': src_port}}, rule_def)
+
+ # fwmark
if fwmk is None:
if 'fwmark' in rule_config:
- rule_def = dict_merge({'fwmark': rule_config['fwmark']}, rule_def)
+ tmp = rule_config['fwmark']
+ if isinstance(tmp, str):
+ tmp = [tmp]
+ rule_def = dict_merge({'fwmark': tmp}, rule_def)
else:
changed = True
if len(fwmk) > 0:
rule_def = dict_merge({'fwmark' : fwmk}, rule_def)
+ # inbound-interface
if iif is None:
if 'inbound_interface' in rule_config:
rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def)
@@ -121,6 +151,7 @@ def get_config(config=None):
if len(iif) > 0:
rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
+ # destination address
if dst is None:
if 'destination' in rule_config:
if 'address' in rule_config['destination']:
@@ -130,9 +161,35 @@ def get_config(config=None):
if len(dst) > 0:
rule_def = dict_merge({'destination': {'address': dst}}, rule_def)
+ # destination port
+ if dst_port is None:
+ if 'destination' in rule_config:
+ if 'port' in rule_config['destination']:
+ tmp = rule_config['destination']['port']
+ if isinstance(tmp, str):
+ tmp = [tmp]
+ rule_def = dict_merge({'destination': {'port': tmp}}, rule_def)
+ else:
+ changed = True
+ if len(dst_port) > 0:
+ rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def)
+
+ # table
+ if table is None:
+ if 'set' in rule_config and 'table' in rule_config['set']:
+ rule_def = dict_merge({'table': [rule_config['set']['table']]}, rule_def)
+ else:
+ changed = True
+ if len(table) > 0:
+ rule_def = dict_merge({'table' : table}, rule_def)
+
+ # protocol
if proto is None:
if 'protocol' in rule_config:
- rule_def = dict_merge({'protocol': rule_config['protocol']}, rule_def)
+ tmp = rule_config['protocol']
+ if isinstance(tmp, str):
+ tmp = [tmp]
+ rule_def = dict_merge({'protocol': tmp}, rule_def)
else:
changed = True
if len(proto) > 0:
@@ -192,19 +249,27 @@ def apply(pbr):
for rule, rule_config in pbr[rule_rm].items():
source = rule_config.get('source', {}).get('address', [''])
+ source_port = rule_config.get('source', {}).get('port', [''])
destination = rule_config.get('destination', {}).get('address', [''])
+ destination_port = rule_config.get('destination', {}).get('port', [''])
fwmark = rule_config.get('fwmark', [''])
inbound_interface = rule_config.get('inbound_interface', [''])
protocol = rule_config.get('protocol', [''])
+ table = rule_config.get('table', [''])
- for src, dst, fwmk, iif, proto in product(source, destination, fwmark, inbound_interface, protocol):
+ for src, dst, src_port, dst_port, fwmk, iif, proto, table in product(
+ source, destination, source_port, destination_port,
+ fwmark, inbound_interface, protocol, table):
f_src = '' if src == '' else f' from {src} '
+ f_src_port = '' if src_port == '' else f' sport {src_port} '
f_dst = '' if dst == '' else f' to {dst} '
+ f_dst_port = '' if dst_port == '' else f' dport {dst_port} '
f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '
f_iif = '' if iif == '' else f' iif {iif} '
f_proto = '' if proto == '' else f' ipproto {proto} '
+ f_table = '' if table == '' else f' lookup {table} '
- call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}')
+ call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif}{f_table}')
# Generate new config
for route in ['local_route', 'local_route6']:
@@ -218,7 +283,9 @@ def apply(pbr):
for rule, rule_config in pbr_route['rule'].items():
table = rule_config['set'].get('table', '')
source = rule_config.get('source', {}).get('address', ['all'])
+ source_port = rule_config.get('source', {}).get('port', '')
destination = rule_config.get('destination', {}).get('address', ['all'])
+ destination_port = rule_config.get('destination', {}).get('port', '')
fwmark = rule_config.get('fwmark', '')
inbound_interface = rule_config.get('inbound_interface', '')
protocol = rule_config.get('protocol', '')
@@ -227,11 +294,13 @@ def apply(pbr):
f_src = f' from {src} ' if src else ''
for dst in destination:
f_dst = f' to {dst} ' if dst else ''
+ f_src_port = f' sport {source_port} ' if source_port else ''
+ f_dst_port = f' dport {destination_port} ' if destination_port else ''
f_fwmk = f' fwmark {fwmark} ' if fwmark else ''
f_iif = f' iif {inbound_interface} ' if inbound_interface else ''
f_proto = f' ipproto {protocol} ' if protocol else ''
- call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_fwmk}{f_iif} lookup {table}')
+ call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif} lookup {table}')
return None