summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/check-open-prs-conflict.yml17
-rw-r--r--data/templates/frr/daemons.frr.tmpl1
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/include/accel-ppp/thread-count.xml.i27
-rw-r--r--interface-definitions/include/firewall/geoip.xml.i2
-rw-r--r--interface-definitions/include/haproxy/rule-backend.xml.i8
-rw-r--r--interface-definitions/include/haproxy/rule-frontend.xml.i14
-rw-r--r--interface-definitions/interfaces_bonding.xml.in2
-rw-r--r--interface-definitions/load-balancing_haproxy.xml.in2
-rw-r--r--interface-definitions/policy.xml.in2
-rw-r--r--interface-definitions/service_ipoe-server.xml.in1
-rw-r--r--interface-definitions/service_pppoe-server.xml.in1
-rw-r--r--interface-definitions/service_snmp.xml.in2
-rw-r--r--interface-definitions/vpn_l2tp.xml.in1
-rw-r--r--interface-definitions/vpn_pptp.xml.in1
-rw-r--r--interface-definitions/vpn_sstp.xml.in1
-rw-r--r--op-mode-definitions/monitor-log.xml.in6
-rwxr-xr-xop-mode-definitions/show-log.xml.in6
-rw-r--r--python/vyos/configdict.py12
-rw-r--r--python/vyos/ifconfig/wireguard.py160
-rwxr-xr-xpython/vyos/template.py2
-rw-r--r--python/vyos/utils/cpu.py6
-rw-r--r--python/vyos/utils/network.py15
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireguard.py27
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py16
-rwxr-xr-xsrc/conf_mode/firewall.py11
-rwxr-xr-xsrc/conf_mode/interfaces_wireguard.py28
-rwxr-xr-xsrc/conf_mode/nat.py6
-rwxr-xr-xsrc/conf_mode/policy.py16
-rwxr-xr-xsrc/conf_mode/service_monitoring_prometheus.py18
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py4
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py2
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py2
-rwxr-xr-xsrc/etc/opennhrp/opennhrp-script.py371
-rw-r--r--src/systemd/opennhrp.service13
-rwxr-xr-xsrc/validators/bgp-large-community-list21
36 files changed, 305 insertions, 520 deletions
diff --git a/.github/workflows/check-open-prs-conflict.yml b/.github/workflows/check-open-prs-conflict.yml
new file mode 100644
index 000000000..52b11938e
--- /dev/null
+++ b/.github/workflows/check-open-prs-conflict.yml
@@ -0,0 +1,17 @@
+name: "Open PRs Conflicts checker"
+on:
+ push:
+ branches:
+ - current
+ - sagitta
+ - circinus
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ check-pr-conflict-call:
+ uses: vyos/.github/.github/workflows/check-open-prs-conflict.yml@current
+ secrets: inherit
diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
index 835dc382b..afd888122 100644
--- a/data/templates/frr/daemons.frr.tmpl
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -4,7 +4,6 @@
# Note: The following FRR-services must be kept disabled because they are replaced by other packages in VyOS:
#
# pimd Replaced by package igmpproxy.
-# nhrpd Replaced by package opennhrp.
# pbrd Replaced by PBR in nftables.
# vrrpd Replaced by package keepalived.
#
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 4e312a648..0fd5e3395 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -6,7 +6,6 @@ etc/dhcp
etc/ipsec.d
etc/logrotate.d
etc/netplug
-etc/opennhrp
etc/modprobe.d
etc/ppp
etc/securetty
diff --git a/interface-definitions/include/accel-ppp/thread-count.xml.i b/interface-definitions/include/accel-ppp/thread-count.xml.i
new file mode 100644
index 000000000..84d9224d0
--- /dev/null
+++ b/interface-definitions/include/accel-ppp/thread-count.xml.i
@@ -0,0 +1,27 @@
+<!-- include start from accel-ppp/thread-count.xml.i -->
+<leafNode name="thread-count">
+ <properties>
+ <help>Number of working threads</help>
+ <completionHelp>
+ <list>all half</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Use all available CPU cores</description>
+ </valueHelp>
+ <valueHelp>
+ <format>half</format>
+ <description>Use half of available CPU cores</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1-512</format>
+ <description>Thread count</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-512"/>
+ <regex>(all|half)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>all</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/geoip.xml.i b/interface-definitions/include/firewall/geoip.xml.i
index 9fb37a574..b8f2cbc45 100644
--- a/interface-definitions/include/firewall/geoip.xml.i
+++ b/interface-definitions/include/firewall/geoip.xml.i
@@ -12,7 +12,7 @@
<description>Country code (2 characters)</description>
</valueHelp>
<constraint>
- <regex>^(ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bl|bm|bn|bo|bq|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mf|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)$</regex>
+ <regex>(ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bl|bm|bn|bo|bq|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mf|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)</regex>
</constraint>
<multi />
</properties>
diff --git a/interface-definitions/include/haproxy/rule-backend.xml.i b/interface-definitions/include/haproxy/rule-backend.xml.i
index 1df9d5dcf..5faf09a96 100644
--- a/interface-definitions/include/haproxy/rule-backend.xml.i
+++ b/interface-definitions/include/haproxy/rule-backend.xml.i
@@ -38,7 +38,7 @@
<description>Set URL location</description>
</valueHelp>
<constraint>
- <regex>^\/[\w\-.\/]+$</regex>
+ <regex>\/[\w\-.\/]+</regex>
</constraint>
<constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
</properties>
@@ -90,7 +90,7 @@
<description>Begin URL</description>
</valueHelp>
<constraint>
- <regex>^\/[\w\-.\/]+$</regex>
+ <regex>\/[\w\-.\/]+</regex>
</constraint>
<constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
<multi/>
@@ -104,7 +104,7 @@
<description>End URL</description>
</valueHelp>
<constraint>
- <regex>^\/[\w\-.\/]+$</regex>
+ <regex>\/[\w\-.\/]+</regex>
</constraint>
<constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
<multi/>
@@ -118,7 +118,7 @@
<description>Exactly URL</description>
</valueHelp>
<constraint>
- <regex>^\/[\w\-.\/]*$</regex>
+ <regex>\/[\w\-.\/]*</regex>
</constraint>
<constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
<multi/>
diff --git a/interface-definitions/include/haproxy/rule-frontend.xml.i b/interface-definitions/include/haproxy/rule-frontend.xml.i
index eabdd8632..d2e7a38c3 100644
--- a/interface-definitions/include/haproxy/rule-frontend.xml.i
+++ b/interface-definitions/include/haproxy/rule-frontend.xml.i
@@ -32,15 +32,15 @@
<children>
<leafNode name="redirect-location">
<properties>
- <help>Set URL location</help>
+ <help>Set path location</help>
<valueHelp>
<format>url</format>
- <description>Set URL location</description>
+ <description>Set path location</description>
</valueHelp>
<constraint>
- <regex>^\/[\w\-.\/]+$</regex>
+ <regex>\/[\w\-.\/]+</regex>
</constraint>
- <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ <constraintErrorMessage>Incorrect path format</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="backend">
@@ -93,7 +93,7 @@
<description>Begin URL</description>
</valueHelp>
<constraint>
- <regex>^\/[\w\-.\/]+$</regex>
+ <regex>\/[\w\-.\/]+</regex>
</constraint>
<constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
<multi/>
@@ -107,7 +107,7 @@
<description>End URL</description>
</valueHelp>
<constraint>
- <regex>^\/[\w\-.\/]+$</regex>
+ <regex>\/[\w\-.\/]+</regex>
</constraint>
<constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
<multi/>
@@ -121,7 +121,7 @@
<description>Exactly URL</description>
</valueHelp>
<constraint>
- <regex>^\/[\w\-.\/]+$</regex>
+ <regex>\/[\w\-.\/]+</regex>
</constraint>
<constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
<multi/>
diff --git a/interface-definitions/interfaces_bonding.xml.in b/interface-definitions/interfaces_bonding.xml.in
index cdacae2d0..9945fc15d 100644
--- a/interface-definitions/interfaces_bonding.xml.in
+++ b/interface-definitions/interfaces_bonding.xml.in
@@ -240,7 +240,7 @@
<description>Distribute based on MAC address</description>
</valueHelp>
<constraint>
- <regex>(802.3ad|active-backup|broadcast|round-robin|transmit-load-balance|adaptive-load-balance|xor-hash)</regex>
+ <regex>(802\.3ad|active-backup|broadcast|round-robin|transmit-load-balance|adaptive-load-balance|xor-hash)</regex>
</constraint>
<constraintErrorMessage>mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/load-balancing_haproxy.xml.in b/interface-definitions/load-balancing_haproxy.xml.in
index f0f64e75a..61ff8bc81 100644
--- a/interface-definitions/load-balancing_haproxy.xml.in
+++ b/interface-definitions/load-balancing_haproxy.xml.in
@@ -159,7 +159,7 @@
<properties>
<help>URI used for HTTP health check (Example: '/' or '/health')</help>
<constraint>
- <regex>^\/([^?#\s]*)(\?[^#\s]*)?$</regex>
+ <regex>\/([^?#\s]*)(\?[^#\s]*)?</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 25dbf5581..31e01c68c 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -1519,7 +1519,7 @@
<constraint>
<validator name="numeric" argument="--relative --"/>
<validator name="numeric" argument="--range 0-4294967295"/>
- <regex>^[+|-]?rtt$</regex>
+ <regex>[+|-]?rtt</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in
index fe9d32bbd..3093151ea 100644
--- a/interface-definitions/service_ipoe-server.xml.in
+++ b/interface-definitions/service_ipoe-server.xml.in
@@ -237,6 +237,7 @@
#include <include/accel-ppp/max-concurrent-sessions.xml.i>
#include <include/accel-ppp/shaper.xml.i>
#include <include/accel-ppp/snmp.xml.i>
+ #include <include/accel-ppp/thread-count.xml.i>
#include <include/generic-description.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
#include <include/accel-ppp/log.xml.i>
diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in
index 32215e9d2..81a4a95e3 100644
--- a/interface-definitions/service_pppoe-server.xml.in
+++ b/interface-definitions/service_pppoe-server.xml.in
@@ -175,6 +175,7 @@
</node>
#include <include/accel-ppp/shaper.xml.i>
#include <include/accel-ppp/snmp.xml.i>
+ #include <include/accel-ppp/thread-count.xml.i>
#include <include/accel-ppp/wins-server.xml.i>
#include <include/generic-description.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
diff --git a/interface-definitions/service_snmp.xml.in b/interface-definitions/service_snmp.xml.in
index cc21f5b8b..bdc9f88fe 100644
--- a/interface-definitions/service_snmp.xml.in
+++ b/interface-definitions/service_snmp.xml.in
@@ -13,7 +13,7 @@
<properties>
<help>Community name</help>
<constraint>
- <regex>[[:alnum:]-_!@*#]{1,100}</regex>
+ <regex>[[:alnum:]\-_!@*#]{1,100}</regex>
</constraint>
<constraintErrorMessage>Community string is limited to alphanumerical characters, -, _, !, @, *, and # with a total lenght of 100</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in
index c00e82534..d28f86653 100644
--- a/interface-definitions/vpn_l2tp.xml.in
+++ b/interface-definitions/vpn_l2tp.xml.in
@@ -137,6 +137,7 @@
#include <include/accel-ppp/ppp-options.xml.i>
#include <include/accel-ppp/shaper.xml.i>
#include <include/accel-ppp/snmp.xml.i>
+ #include <include/accel-ppp/thread-count.xml.i>
#include <include/accel-ppp/wins-server.xml.i>
#include <include/generic-description.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn_pptp.xml.in
index 8aec0cb1c..3e985486d 100644
--- a/interface-definitions/vpn_pptp.xml.in
+++ b/interface-definitions/vpn_pptp.xml.in
@@ -53,6 +53,7 @@
#include <include/accel-ppp/ppp-options.xml.i>
#include <include/accel-ppp/shaper.xml.i>
#include <include/accel-ppp/snmp.xml.i>
+ #include <include/accel-ppp/thread-count.xml.i>
#include <include/accel-ppp/wins-server.xml.i>
#include <include/generic-description.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in
index 5fd5c95ca..851a202dc 100644
--- a/interface-definitions/vpn_sstp.xml.in
+++ b/interface-definitions/vpn_sstp.xml.in
@@ -50,6 +50,7 @@
#include <include/accel-ppp/ppp-options.xml.i>
#include <include/accel-ppp/shaper.xml.i>
#include <include/accel-ppp/snmp.xml.i>
+ #include <include/accel-ppp/thread-count.xml.i>
#include <include/accel-ppp/wins-server.xml.i>
#include <include/generic-description.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index a6aa6f05e..b6784d9ea 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -131,12 +131,6 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit ndppd.service</command>
</leafNode>
- <leafNode name="nhrp">
- <properties>
- <help>Monitor last lines of Next Hop Resolution Protocol log</help>
- </properties>
- <command>journalctl --no-hostname --boot --follow --unit opennhrp.service</command>
- </leafNode>
<leafNode name="ntp">
<properties>
<help>Monitor last lines of Network Time Protocol log</help>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index c2bc03910..b616f7ab9 100755
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -677,12 +677,6 @@
</properties>
<command>journalctl --no-hostname --boot --unit ndppd.service</command>
</leafNode>
- <leafNode name="nhrp">
- <properties>
- <help>Show log for Next Hop Resolution Protocol (NHRP)</help>
- </properties>
- <command>journalctl --no-hostname --boot --unit opennhrp.service</command>
- </leafNode>
<leafNode name="ntp">
<properties>
<help>Show log for Network Time Protocol (NTP)</help>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index ff0a15933..a34b0176a 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -661,6 +661,7 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False):
Return a dictionary with the necessary interface config keys.
"""
from vyos.utils.cpu import get_core_count
+ from vyos.utils.cpu import get_half_cpus
from vyos.template import is_ipv4
dict = config.get_config_dict(base, key_mangling=('-', '_'),
@@ -670,7 +671,16 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False):
with_pki=with_pki)
# set CPUs cores to process requests
- dict.update({'thread_count' : get_core_count()})
+ match dict.get('thread_count'):
+ case 'all':
+ dict['thread_count'] = get_core_count()
+ case 'half':
+ dict['thread_count'] = get_half_cpus()
+ case str(x) if x.isdigit():
+ dict['thread_count'] = int(x)
+ case _:
+ dict['thread_count'] = get_core_count()
+
# we need to store the path to the secrets file
dict.update({'chap_secrets_file' : chap_secrets})
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index f5217aecb..3a28723b3 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -22,12 +22,13 @@ from tempfile import NamedTemporaryFile
from hurry.filesize import size
from hurry.filesize import alternative
+from vyos.base import Warning
from vyos.configquery import ConfigTreeQuery
from vyos.ifconfig import Interface
from vyos.ifconfig import Operational
from vyos.template import is_ipv6
from vyos.template import is_ipv4
-
+from vyos.utils.network import get_wireguard_peers
class WireGuardOperational(Operational):
def _dump(self):
"""Dump wireguard data in a python friendly way."""
@@ -251,92 +252,131 @@ class WireGuardIf(Interface):
"""Get a synthetic MAC address."""
return self.get_mac_synthetic()
+ def get_peer_public_keys(self, config, disabled=False):
+ """Get list of configured peer public keys"""
+ if 'peer' not in config:
+ return []
+
+ public_keys = []
+
+ for _, peer_config in config['peer'].items():
+ if disabled == ('disable' in peer_config):
+ public_keys.append(peer_config['public_key'])
+
+ return public_keys
+
def update(self, config):
"""General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
interface setup code and provide a single point of entry when workin
on any interface."""
- tmp_file = NamedTemporaryFile('w')
- tmp_file.write(config['private_key'])
- tmp_file.flush()
# Wireguard base command is identical for every peer
base_cmd = f'wg set {self.ifname}'
+
interface_cmd = base_cmd
if 'port' in config:
interface_cmd += ' listen-port {port}'
if 'fwmark' in config:
interface_cmd += ' fwmark {fwmark}'
- interface_cmd += f' private-key {tmp_file.name}'
- interface_cmd = interface_cmd.format(**config)
- # T6490: execute command to ensure interface configured
- self._cmd(interface_cmd)
+ with NamedTemporaryFile('w') as tmp_file:
+ tmp_file.write(config['private_key'])
+ tmp_file.flush()
- # If no PSK is given remove it by using /dev/null - passing keys via
- # the shell (usually bash) is considered insecure, thus we use a file
- no_psk_file = '/dev/null'
+ interface_cmd += f' private-key {tmp_file.name}'
+ interface_cmd = interface_cmd.format(**config)
+ # T6490: execute command to ensure interface configured
+ self._cmd(interface_cmd)
+
+ current_peer_public_keys = get_wireguard_peers(self.ifname)
+
+ if 'rebuild_required' in config:
+ # Remove all existing peers that no longer exist in config
+ current_public_keys = self.get_peer_public_keys(config)
+ cmd_remove_peers = [f' peer {public_key} remove'
+ for public_key in current_peer_public_keys
+ if public_key not in current_public_keys]
+ if cmd_remove_peers:
+ self._cmd(base_cmd + ''.join(cmd_remove_peers))
if 'peer' in config:
+ # Group removal of disabled peers in one command
+ current_disabled_peers = self.get_peer_public_keys(config, disabled=True)
+ cmd_disabled_peers = [f' peer {public_key} remove'
+ for public_key in current_disabled_peers]
+ if cmd_disabled_peers:
+ self._cmd(base_cmd + ''.join(cmd_disabled_peers))
+
+ peer_cmds = []
+ peer_domain_cmds = []
+ peer_psk_files = []
+
for peer, peer_config in config['peer'].items():
# T4702: No need to configure this peer when it was explicitly
# marked as disabled - also active sessions are terminated as
# the public key was already removed when entering this method!
if 'disable' in peer_config:
- # remove peer if disabled, no error report even if peer not exists
- cmd = base_cmd + ' peer {public_key} remove'
- self._cmd(cmd.format(**peer_config))
continue
- psk_file = no_psk_file
-
# start of with a fresh 'wg' command
- peer_cmd = base_cmd + ' peer {public_key}'
+ peer_cmd = ' peer {public_key}'
- try:
- cmd = peer_cmd
-
- if 'preshared_key' in peer_config:
- psk_file = '/tmp/tmp.wireguard.psk'
- with open(psk_file, 'w') as f:
- f.write(peer_config['preshared_key'])
- cmd += f' preshared-key {psk_file}'
-
- # Persistent keepalive is optional
- if 'persistent_keepalive' in peer_config:
- cmd += ' persistent-keepalive {persistent_keepalive}'
-
- # Multiple allowed-ip ranges can be defined - ensure we are always
- # dealing with a list
- if isinstance(peer_config['allowed_ips'], str):
- peer_config['allowed_ips'] = [peer_config['allowed_ips']]
- cmd += ' allowed-ips ' + ','.join(peer_config['allowed_ips'])
-
- self._cmd(cmd.format(**peer_config))
-
- cmd = peer_cmd
-
- # Ensure peer is created even if dns not working
- if {'address', 'port'} <= set(peer_config):
- if is_ipv6(peer_config['address']):
- cmd += ' endpoint [{address}]:{port}'
- elif is_ipv4(peer_config['address']):
- cmd += ' endpoint {address}:{port}'
- else:
- # don't set endpoint if address uses domain name
- continue
- elif {'host_name', 'port'} <= set(peer_config):
- cmd += ' endpoint {host_name}:{port}'
-
- self._cmd(cmd.format(**peer_config), env={
+ cmd = peer_cmd
+
+ if 'preshared_key' in peer_config:
+ with NamedTemporaryFile(mode='w', delete=False) as tmp_file:
+ tmp_file.write(peer_config['preshared_key'])
+ tmp_file.flush()
+ cmd += f' preshared-key {tmp_file.name}'
+ peer_psk_files.append(tmp_file.name)
+ else:
+ # If no PSK is given remove it by using /dev/null - passing keys via
+ # the shell (usually bash) is considered insecure, thus we use a file
+ cmd += f' preshared-key /dev/null'
+
+ # Persistent keepalive is optional
+ if 'persistent_keepalive' in peer_config:
+ cmd += ' persistent-keepalive {persistent_keepalive}'
+
+ # Multiple allowed-ip ranges can be defined - ensure we are always
+ # dealing with a list
+ if isinstance(peer_config['allowed_ips'], str):
+ peer_config['allowed_ips'] = [peer_config['allowed_ips']]
+ cmd += ' allowed-ips ' + ','.join(peer_config['allowed_ips'])
+
+ peer_cmds.append(cmd.format(**peer_config))
+
+ cmd = peer_cmd
+
+ # Ensure peer is created even if dns not working
+ if {'address', 'port'} <= set(peer_config):
+ if is_ipv6(peer_config['address']):
+ cmd += ' endpoint [{address}]:{port}'
+ elif is_ipv4(peer_config['address']):
+ cmd += ' endpoint {address}:{port}'
+ else:
+ # don't set endpoint if address uses domain name
+ continue
+ elif {'host_name', 'port'} <= set(peer_config):
+ cmd += ' endpoint {host_name}:{port}'
+ else:
+ continue
+
+ peer_domain_cmds.append(cmd.format(**peer_config))
+
+ try:
+ if peer_cmds:
+ self._cmd(base_cmd + ''.join(peer_cmds))
+
+ if peer_domain_cmds:
+ self._cmd(base_cmd + ''.join(peer_domain_cmds), env={
'WG_ENDPOINT_RESOLUTION_RETRIES': config['max_dns_retry']})
- except:
- # todo: logging
- pass
- finally:
- # PSK key file is not required to be stored persistently as its backed by CLI
- if psk_file != no_psk_file and os.path.exists(psk_file):
- os.remove(psk_file)
+ except Exception as e:
+ Warning(f'Failed to apply Wireguard peers on {self.ifname}: {e}')
+ finally:
+ for tmp in peer_psk_files:
+ os.unlink(tmp)
# call base class
super().update(config)
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 11e1cc50f..aa215db95 100755
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -728,7 +728,7 @@ def conntrack_rule(rule_conf, rule_id, action, ipv6=False):
if port[0] == '!':
operator = '!='
port = port[1:]
- output.append(f'th {prefix}port {operator} {port}')
+ output.append(f'th {prefix}port {operator} {{ {port} }}')
if 'group' in side_conf:
group = side_conf['group']
diff --git a/python/vyos/utils/cpu.py b/python/vyos/utils/cpu.py
index 8ace77d15..6f21eb526 100644
--- a/python/vyos/utils/cpu.py
+++ b/python/vyos/utils/cpu.py
@@ -26,6 +26,7 @@ It has special cases for x86_64 and MAY work correctly on other architectures,
but nothing is certain.
"""
+import os
import re
def _read_cpuinfo():
@@ -114,3 +115,8 @@ def get_available_cpus():
out = json.loads(cmd('lscpu --extended -b --json'))
return out['cpus']
+
+
+def get_half_cpus():
+ """ return 1/2 of the numbers of available CPUs """
+ return max(1, os.cpu_count() // 2)
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 20b6a3c9e..0a84be478 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -416,6 +416,21 @@ def is_wireguard_key_pair(private_key: str, public_key:str) -> bool:
else:
return False
+def get_wireguard_peers(ifname: str) -> list:
+ """
+ Return list of configured Wireguard peers for interface
+ :param ifname: Interface name
+ :type ifname: str
+ :return: list of public keys
+ :rtype: list
+ """
+ if not interface_exists(ifname):
+ return []
+
+ from vyos.utils.process import cmd
+ peers = cmd(f'wg show {ifname} peers')
+ return peers.splitlines()
+
def is_subnet_connected(subnet, primary=False):
"""
Verify is the given IPv4/IPv6 subnet is connected to any interface on this
diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py
index f8cd18cf2..7bc82c187 100755
--- a/smoketest/scripts/cli/test_interfaces_wireguard.py
+++ b/smoketest/scripts/cli/test_interfaces_wireguard.py
@@ -154,13 +154,15 @@ class WireGuardInterfaceTest(BasicInterfaceTest.TestCase):
tmp = read_file(f'/sys/class/net/{intf}/threaded')
self.assertTrue(tmp, "1")
- def test_wireguard_peer_pubkey_change(self):
+ def test_wireguard_peer_change(self):
# T5707 changing WireGuard CLI public key of a peer - it's not removed
+ # Also check if allowed-ips update
- def get_peers(interface) -> list:
+ def get_peers(interface) -> list[tuple]:
tmp = cmd(f'sudo wg show {interface} dump')
first_line = True
peers = []
+ allowed_ips = []
for line in tmp.split('\n'):
if not line:
continue # Skip empty lines and last line
@@ -170,24 +172,27 @@ class WireGuardInterfaceTest(BasicInterfaceTest.TestCase):
first_line = False
else:
peers.append(items[0])
- return peers
+ allowed_ips.append(items[3])
+ return peers, allowed_ips
interface = 'wg1337'
port = '1337'
privkey = 'iJi4lb2HhkLx2KSAGOjji2alKkYsJjSPkHkrcpxgEVU='
pubkey_1 = 'srQ8VF6z/LDjKCzpxBzFpmaNUOeuHYzIfc2dcmoc/h4='
pubkey_2 = '8pbMHiQ7NECVP7F65Mb2W8+4ldGG2oaGvDSpSEsOBn8='
+ allowed_ips_1 = '10.205.212.10/32'
+ allowed_ips_2 = '10.205.212.11/32'
self.cli_set(base_path + [interface, 'address', '172.16.0.1/24'])
self.cli_set(base_path + [interface, 'port', port])
self.cli_set(base_path + [interface, 'private-key', privkey])
self.cli_set(base_path + [interface, 'peer', 'VyOS', 'public-key', pubkey_1])
- self.cli_set(base_path + [interface, 'peer', 'VyOS', 'allowed-ips', '10.205.212.10/32'])
+ self.cli_set(base_path + [interface, 'peer', 'VyOS', 'allowed-ips', allowed_ips_1])
self.cli_commit()
- peers = get_peers(interface)
+ peers, _ = get_peers(interface)
self.assertIn(pubkey_1, peers)
self.assertNotIn(pubkey_2, peers)
@@ -196,10 +201,20 @@ class WireGuardInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
# Verify config
- peers = get_peers(interface)
+ peers, _ = get_peers(interface)
self.assertNotIn(pubkey_1, peers)
self.assertIn(pubkey_2, peers)
+ # Update allowed-ips
+ self.cli_delete(base_path + [interface, 'peer', 'VyOS', 'allowed-ips', allowed_ips_1])
+ self.cli_set(base_path + [interface, 'peer', 'VyOS', 'allowed-ips', allowed_ips_2])
+ self.cli_commit()
+
+ # Verify config
+ _, allowed_ips = get_peers(interface)
+ self.assertNotIn(allowed_ips_1, allowed_ips)
+ self.assertIn(allowed_ips_2, allowed_ips)
+
def test_wireguard_hostname(self):
# T4930: Test dynamic endpoint support
interface = 'wg1234'
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index 72deb7525..f6bb3cf7c 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2024 VyOS maintainers and contributors
+# Copyright (C) 2021-2025 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
@@ -195,6 +195,8 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
def test_conntrack_ignore(self):
address_group = 'conntracktest'
address_group_member = '192.168.0.1'
+ port_single = '53'
+ ports_multi = '500,4500'
ipv6_address_group = 'conntracktest6'
ipv6_address_group_member = 'dead:beef::1'
@@ -211,6 +213,14 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group])
self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'protocol', 'all'])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '3', 'source', 'address', '192.0.2.1'])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '3', 'destination', 'port', ports_multi])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '3', 'protocol', 'udp'])
+
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '4', 'source', 'address', '192.0.2.1'])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '4', 'destination', 'port', port_single])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '4', 'protocol', 'udp'])
+
self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1'])
self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2'])
self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'port', '22'])
@@ -226,7 +236,9 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'tcp flags & syn == syn', 'notrack'],
- ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack']
+ ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack'],
+ ['ip saddr 192.0.2.1', 'udp dport { 500, 4500 }', 'notrack'],
+ ['ip saddr 192.0.2.1', 'udp dport 53', 'notrack']
]
nftables6_search = [
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 274ca2ce6..348eaeba3 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -17,6 +17,8 @@
import os
import re
+from glob import glob
+
from sys import exit
from vyos.base import Warning
from vyos.config import Config
@@ -30,6 +32,7 @@ from vyos.firewall import geoip_update
from vyos.template import render
from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
+from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import rc_cmd
@@ -37,7 +40,6 @@ from vyos.utils.network import get_vrf_members
from vyos.utils.network import get_interface_vrf
from vyos import ConfigError
from vyos import airbag
-from pathlib import Path
from subprocess import run as subp_run
airbag.enable()
@@ -626,10 +628,11 @@ def apply(firewall):
domain_action = 'restart'
if dict_search_args(firewall, 'group', 'remote_group') or dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'].items() or firewall['ip6_fqdn'].items():
text = f'# Automatically generated by firewall.py\nThis file indicates that vyos-domain-resolver service is used by the firewall.\n'
- Path(domain_resolver_usage).write_text(text)
+ write_file(domain_resolver_usage, text)
else:
- Path(domain_resolver_usage).unlink(missing_ok=True)
- if not Path('/run').glob('use-vyos-domain-resolver*'):
+ if os.path.exists(domain_resolver_usage):
+ os.unlink(domain_resolver_usage)
+ if not glob('/run/use-vyos-domain-resolver*'):
domain_action = 'stop'
call(f'systemctl {domain_action} vyos-domain-resolver.service')
diff --git a/src/conf_mode/interfaces_wireguard.py b/src/conf_mode/interfaces_wireguard.py
index 3ca6ecdca..770667df1 100755
--- a/src/conf_mode/interfaces_wireguard.py
+++ b/src/conf_mode/interfaces_wireguard.py
@@ -14,6 +14,9 @@
# 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 glob import glob
from sys import exit
from vyos.config import Config
@@ -35,7 +38,6 @@ from vyos.utils.network import is_wireguard_key_pair
from vyos.utils.process import call
from vyos import ConfigError
from vyos import airbag
-from pathlib import Path
airbag.enable()
@@ -145,19 +147,11 @@ def generate(wireguard):
def apply(wireguard):
check_kmod('wireguard')
- if 'rebuild_required' in wireguard or 'deleted' in wireguard:
- wg = WireGuardIf(**wireguard)
- # WireGuard only supports peer removal based on the configured public-key,
- # by deleting the entire interface this is the shortcut instead of parsing
- # out all peers and removing them one by one.
- #
- # Peer reconfiguration will always come with a short downtime while the
- # WireGuard interface is recreated (see below)
- wg.remove()
+ wg = WireGuardIf(**wireguard)
- # Create the new interface if required
- if 'deleted' not in wireguard:
- wg = WireGuardIf(**wireguard)
+ if 'deleted' in wireguard:
+ wg.remove()
+ else:
wg.update(wireguard)
domain_resolver_usage = '/run/use-vyos-domain-resolver-interfaces-wireguard-' + wireguard['ifname']
@@ -168,12 +162,12 @@ def apply(wireguard):
from vyos.utils.file import write_file
text = f'# Automatically generated by interfaces_wireguard.py\nThis file indicates that vyos-domain-resolver service is used by the interfaces_wireguard.\n'
- text += "intefaces:\n" + "".join([f" - {peer}\n" for peer in wireguard['peers_need_resolve']])
- Path(domain_resolver_usage).write_text(text)
+ text += "interfaces:\n" + "".join([f" - {peer}\n" for peer in wireguard['peers_need_resolve']])
write_file(domain_resolver_usage, text)
else:
- Path(domain_resolver_usage).unlink(missing_ok=True)
- if not Path('/run').glob('use-vyos-domain-resolver*'):
+ if os.path.exists(domain_resolver_usage):
+ os.unlink(domain_resolver_usage)
+ if not glob('/run/use-vyos-domain-resolver*'):
domain_action = 'stop'
call(f'systemctl {domain_action} vyos-domain-resolver.service')
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 504b3e82a..6c88e5cfd 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -16,8 +16,8 @@
import os
+from glob import glob
from sys import exit
-from pathlib import Path
from vyos.base import Warning
from vyos.config import Config
@@ -265,9 +265,9 @@ def apply(nat):
text = f'# Automatically generated by nat.py\nThis file indicates that vyos-domain-resolver service is used by nat.\n'
write_file(domain_resolver_usage, text)
elif os.path.exists(domain_resolver_usage):
- Path(domain_resolver_usage).unlink(missing_ok=True)
+ os.unlink(domain_resolver_usage)
- if not Path('/run').glob('use-vyos-domain-resolver*'):
+ if not glob('/run/use-vyos-domain-resolver*'):
domain_action = 'stop'
call(f'systemctl {domain_action} vyos-domain-resolver.service')
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index a90e33e81..ec9005890 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -14,6 +14,7 @@
# 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 re
from sys import exit
from vyos.config import Config
@@ -24,9 +25,20 @@ from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
+from vyos.base import Warning
from vyos import airbag
airbag.enable()
+# Sanity checks for large-community-list regex:
+# * Require complete 3-tuples, no blank members. Catch missed & doubled colons.
+# * Permit appropriate community separators (whitespace, underscore)
+# * Permit common regex between tuples while requiring at least one separator
+# (eg, "1:1:1_.*_4:4:4", matching "1:1:1 4:4:4" and "1:1:1 2:2:2 4:4:4",
+# but not "1:1:13 24:4:4")
+# Best practice: stick with basic patterns, mind your wildcards and whitespace.
+# Regex that doesn't match this pattern will be allowed with a warning.
+large_community_regex_pattern = r'([^: _]+):([^: _]+):([^: _]+)([ _]([^:]+):([^: _]+):([^: _]+))*'
+
def community_action_compatibility(actions: dict) -> bool:
"""
Check compatibility of values in community and large community sections
@@ -147,6 +159,10 @@ def verify(config_dict):
if 'regex' not in rule_config:
raise ConfigError(f'A regex {mandatory_error}')
+ if policy_type == 'large_community_list':
+ if not re.fullmatch(large_community_regex_pattern, rule_config['regex']):
+ Warning(f'"policy large-community-list {instance} rule {rule} regex" does not follow expected form and may not match as expected.')
+
if policy_type in ['prefix_list', 'prefix_list6']:
if 'prefix' not in rule_config:
raise ConfigError(f'A prefix {mandatory_error}')
diff --git a/src/conf_mode/service_monitoring_prometheus.py b/src/conf_mode/service_monitoring_prometheus.py
index 9a07d8593..f55b09f6c 100755
--- a/src/conf_mode/service_monitoring_prometheus.py
+++ b/src/conf_mode/service_monitoring_prometheus.py
@@ -48,9 +48,21 @@ def get_config(config=None):
if not conf.exists(base):
return None
- monitoring = conf.get_config_dict(
- base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True
- )
+ monitoring = {}
+ exporters = {
+ 'node_exporter': base + ['node-exporter'],
+ 'frr_exporter': base + ['frr-exporter'],
+ 'blackbox_exporter': base + ['blackbox-exporter'],
+ }
+
+ for exporter_name, exporter_base in exporters.items():
+ if conf.exists(exporter_base):
+ monitoring[exporter_name] = conf.get_config_dict(
+ exporter_base,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True,
+ )
tmp = is_node_changed(conf, base + ['node-exporter', 'vrf'])
if tmp:
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index ac697c509..f7cb3dcba 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -73,7 +73,9 @@ def get_config(config=None):
# https://phabricator.accel-ppp.org/T3
conditions = [is_node_changed(conf, base + ['client-ip-pool']),
is_node_changed(conf, base + ['client-ipv6-pool']),
- is_node_changed(conf, base + ['interface'])]
+ is_node_changed(conf, base + ['interface']),
+ is_node_changed(conf, base + ['authentication','radius','dynamic-author']),
+ is_node_changed(conf, base + ['authentication','mode'])]
if any(conditions):
pppoe.update({'restart_required': {}})
pppoe['server_type'] = 'pppoe'
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 2754314f7..ac25cd671 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -727,7 +727,7 @@ def generate(ipsec):
for remote_prefix in remote_prefixes:
local_net = ipaddress.ip_network(local_prefix)
remote_net = ipaddress.ip_network(remote_prefix)
- if local_net.overlaps(remote_net):
+ if local_net.subnet_of(remote_net):
if passthrough is None:
passthrough = []
passthrough.append(local_prefix)
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 42785134f..0346c7819 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -93,7 +93,7 @@ def verify(ocserv):
"radius" in ocserv["authentication"]["mode"]):
raise ConfigError('OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration')
if "radius" in ocserv["authentication"]["mode"]:
- if not ocserv["authentication"]['radius']['server']:
+ if 'server' not in ocserv['authentication']['radius']:
raise ConfigError('OpenConnect authentication mode radius requires at least one RADIUS server')
if "local" in ocserv["authentication"]["mode"]:
if not ocserv.get("authentication", {}).get("local_users"):
diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py
deleted file mode 100755
index f6f6d075c..000000000
--- a/src/etc/opennhrp/opennhrp-script.py
+++ /dev/null
@@ -1,371 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021-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
-import re
-import sys
-import vyos.ipsec
-
-from json import loads
-from pathlib import Path
-
-from vyos.logger import getLogger
-from vyos.utils.process import cmd
-from vyos.utils.process import process_named_running
-
-NHRP_CONFIG: str = '/run/opennhrp/opennhrp.conf'
-
-
-def vici_get_ipsec_uniqueid(conn: str, src_nbma: str,
- dst_nbma: str) -> list[str]:
- """ Find and return IKE SAs by src nbma and dst nbma
-
- Args:
- conn (str): a connection name
- src_nbma (str): an IP address of NBMA source
- dst_nbma (str): an IP address of NBMA destination
-
- Returns:
- list: a list of IKE connections that match a criteria
- """
- if not conn or not src_nbma or not dst_nbma:
- logger.error(
- f'Incomplete input data for resolving IKE unique ids: '
- f'conn: {conn}, src_nbma: {src_nbma}, dst_nbma: {dst_nbma}')
- return []
-
- try:
- logger.info(
- f'Resolving IKE unique ids for: conn: {conn}, '
- f'src_nbma: {src_nbma}, dst_nbma: {dst_nbma}')
- list_ikeid: list[str] = []
- list_sa: list = vyos.ipsec.get_vici_sas_by_name(conn, None)
- for sa in list_sa:
- if sa[conn]['local-host'].decode('ascii') == src_nbma \
- and sa[conn]['remote-host'].decode('ascii') == dst_nbma:
- list_ikeid.append(sa[conn]['uniqueid'].decode('ascii'))
- return list_ikeid
- except Exception as err:
- logger.error(f'Unable to find unique ids for IKE: {err}')
- return []
-
-
-def vici_ike_terminate(list_ikeid: list[str]) -> bool:
- """Terminating IKE SAs by list of IKE IDs
-
- Args:
- list_ikeid (list[str]): a list of IKE ids to terminate
-
- Returns:
- bool: result of termination action
- """
- if not list:
- logger.warning('An empty list for termination was provided')
- return False
-
- try:
- vyos.ipsec.terminate_vici_ikeid_list(list_ikeid)
- return True
- except Exception as err:
- logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}')
- return False
-
-
-def parse_type_ipsec(interface: str) -> tuple[str, str]:
- """Get DMVPN Type and NHRP Profile from the configuration
-
- Args:
- interface (str): a name of interface
-
- Returns:
- tuple[str, str]: `peer_type` and `profile_name`
- """
- if not interface:
- logger.error('Cannot find peer type - no input provided')
- return '', ''
-
- config_file: str = Path(NHRP_CONFIG).read_text()
- regex: str = rf'^interface {interface} #(?P<peer_type>hub|spoke) ?(?P<profile_name>[^\n]*)$'
- match = re.search(regex, config_file, re.M)
- if match:
- return match.groupdict()['peer_type'], match.groupdict()[
- 'profile_name']
- return '', ''
-
-
-def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None:
- """Add a route to a NBMA peer
-
- Args:
- nbma_src (str): a local IP address
- nbma_dst (str): a remote IP address
- mtu (str): a MTU for a route
- """
- logger.info(f'Adding route from {nbma_src} to {nbma_dst} with MTU {mtu}')
- # Find routes to a peer
- route_get_cmd: str = f'sudo ip --json route get {nbma_dst} from {nbma_src}'
- try:
- route_info_data = loads(cmd(route_get_cmd))
- except Exception as err:
- logger.error(f'Unable to find a route to {nbma_dst}: {err}')
- return
-
- # Check if an output has an expected format
- if not isinstance(route_info_data, list):
- logger.error(
- f'Garbage returned from the "{route_get_cmd}" '
- f'command: {route_info_data}')
- return
-
- # Add static routes to a peer
- for route_item in route_info_data:
- route_dev = route_item.get('dev')
- route_dst = route_item.get('dst')
- route_gateway = route_item.get('gateway')
- # Prepare a command to add a route
- route_add_cmd = 'sudo ip route add'
- if route_dst:
- route_add_cmd = f'{route_add_cmd} {route_dst}'
- if route_gateway:
- route_add_cmd = f'{route_add_cmd} via {route_gateway}'
- if route_dev:
- route_add_cmd = f'{route_add_cmd} dev {route_dev}'
- route_add_cmd = f'{route_add_cmd} proto 42 mtu {mtu}'
- # Add a route
- try:
- cmd(route_add_cmd)
- except Exception as err:
- logger.error(
- f'Unable to add a route using command "{route_add_cmd}": '
- f'{err}')
-
-
-def vici_initiate(conn: str, child_sa: str, src_addr: str,
- dest_addr: str) -> bool:
- """Initiate IKE SA connection with specific peer
-
- Args:
- conn (str): an IKE connection name
- child_sa (str): a child SA profile name
- src_addr (str): NBMA local address
- dest_addr (str): NBMA address of a peer
-
- Returns:
- bool: a result of initiation command
- """
- logger.info(
- f'Trying to initiate connection. Name: {conn}, child sa: {child_sa}, '
- f'src_addr: {src_addr}, dst_addr: {dest_addr}')
- try:
- vyos.ipsec.vici_initiate(conn, child_sa, src_addr, dest_addr)
- return True
- except Exception as err:
- logger.error(f'Unable to initiate connection {err}')
- return False
-
-
-def vici_terminate(conn: str, src_addr: str, dest_addr: str) -> None:
- """Find and terminate IKE SAs by local NBMA and remote NBMA addresses
-
- Args:
- conn (str): IKE connection name
- src_addr (str): NBMA local address
- dest_addr (str): NBMA address of a peer
- """
- logger.info(
- f'Terminating IKE connection {conn} between {src_addr} '
- f'and {dest_addr}')
-
- ikeid_list: list[str] = vici_get_ipsec_uniqueid(conn, src_addr, dest_addr)
-
- if not ikeid_list:
- logger.warning(
- f'No active sessions found for IKE profile {conn}, '
- f'local NBMA {src_addr}, remote NBMA {dest_addr}')
- else:
- try:
- vyos.ipsec.terminate_vici_ikeid_list(ikeid_list)
- except Exception as err:
- logger.error(
- f'Failed to terminate SA for IKE ids {ikeid_list}: {err}')
-
-def iface_up(interface: str) -> None:
- """Proceed tunnel interface UP event
-
- Args:
- interface (str): an interface name
- """
- if not interface:
- logger.warning('No interface name provided for UP event')
-
- logger.info(f'Turning up interface {interface}')
- try:
- cmd(f'sudo ip route flush proto 42 dev {interface}')
- cmd(f'sudo ip neigh flush dev {interface}')
- except Exception as err:
- logger.error(
- f'Unable to flush route on interface "{interface}": {err}')
-
-
-def peer_up(dmvpn_type: str, conn: str) -> None:
- """Proceed NHRP peer UP event
-
- Args:
- dmvpn_type (str): a type of peer
- conn (str): an IKE profile name
- """
- logger.info(f'Peer UP event for {dmvpn_type} using IKE profile {conn}')
- src_nbma = os.getenv('NHRP_SRCNBMA')
- dest_nbma = os.getenv('NHRP_DESTNBMA')
- dest_mtu = os.getenv('NHRP_DESTMTU')
-
- if not src_nbma or not dest_nbma:
- logger.error(
- f'Can not get NHRP NBMA addresses: local {src_nbma}, '
- f'remote {dest_nbma}')
- return
-
- logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}')
- if dest_mtu:
- add_peer_route(src_nbma, dest_nbma, dest_mtu)
- if conn and dmvpn_type == 'spoke' and process_named_running('charon'):
- vici_terminate(conn, src_nbma, dest_nbma)
- vici_initiate(conn, 'dmvpn', src_nbma, dest_nbma)
-
-
-def peer_down(dmvpn_type: str, conn: str) -> None:
- """Proceed NHRP peer DOWN event
-
- Args:
- dmvpn_type (str): a type of peer
- conn (str): an IKE profile name
- """
- logger.info(f'Peer DOWN event for {dmvpn_type} using IKE profile {conn}')
-
- src_nbma = os.getenv('NHRP_SRCNBMA')
- dest_nbma = os.getenv('NHRP_DESTNBMA')
-
- if not src_nbma or not dest_nbma:
- logger.error(
- f'Can not get NHRP NBMA addresses: local {src_nbma}, '
- f'remote {dest_nbma}')
- return
-
- logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}')
- if conn and dmvpn_type == 'spoke' and process_named_running('charon'):
- vici_terminate(conn, src_nbma, dest_nbma)
- try:
- cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42')
- except Exception as err:
- logger.error(
- f'Unable to del route from {src_nbma} to {dest_nbma}: {err}')
-
-
-def route_up(interface: str) -> None:
- """Proceed NHRP route UP event
-
- Args:
- interface (str): an interface name
- """
- logger.info(f'Route UP event for interface {interface}')
-
- dest_addr = os.getenv('NHRP_DESTADDR')
- dest_prefix = os.getenv('NHRP_DESTPREFIX')
- next_hop = os.getenv('NHRP_NEXTHOP')
-
- if not dest_addr or not dest_prefix or not next_hop:
- logger.error(
- f'Can not get route details: dest_addr {dest_addr}, '
- f'dest_prefix {dest_prefix}, next_hop {next_hop}')
- return
-
- logger.info(
- f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}, '
- f'next_hop {next_hop}')
-
- try:
- cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 \
- via {next_hop} dev {interface}')
- cmd('sudo ip route flush cache')
- except Exception as err:
- logger.error(
- f'Unable replace or flush route to {dest_addr}/{dest_prefix} '
- f'via {next_hop} dev {interface}: {err}')
-
-
-def route_down(interface: str) -> None:
- """Proceed NHRP route DOWN event
-
- Args:
- interface (str): an interface name
- """
- logger.info(f'Route DOWN event for interface {interface}')
-
- dest_addr = os.getenv('NHRP_DESTADDR')
- dest_prefix = os.getenv('NHRP_DESTPREFIX')
-
- if not dest_addr or not dest_prefix:
- logger.error(
- f'Can not get route details: dest_addr {dest_addr}, '
- f'dest_prefix {dest_prefix}')
- return
-
- logger.info(
- f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}')
- try:
- cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42')
- cmd('sudo ip route flush cache')
- except Exception as err:
- logger.error(
- f'Unable delete or flush route to {dest_addr}/{dest_prefix}: '
- f'{err}')
-
-
-if __name__ == '__main__':
- logger = getLogger('opennhrp-script', syslog=True)
- logger.debug(
- f'Running script with arguments: {sys.argv}, '
- f'environment: {os.environ}')
-
- action = sys.argv[1]
- interface = os.getenv('NHRP_INTERFACE')
-
- if not interface:
- logger.error('Can not get NHRP interface name')
- sys.exit(1)
-
- dmvpn_type, profile_name = parse_type_ipsec(interface)
- if not dmvpn_type:
- logger.info(f'Interface {interface} is not NHRP tunnel')
- sys.exit()
-
- dmvpn_conn: str = ''
- if profile_name:
- dmvpn_conn: str = f'dmvpn-{profile_name}-{interface}'
- if action == 'interface-up':
- iface_up(interface)
- elif action == 'peer-register':
- pass
- elif action == 'peer-up':
- peer_up(dmvpn_type, dmvpn_conn)
- elif action == 'peer-down':
- peer_down(dmvpn_type, dmvpn_conn)
- elif action == 'route-up':
- route_up(interface)
- elif action == 'route-down':
- route_down(interface)
-
- sys.exit()
diff --git a/src/systemd/opennhrp.service b/src/systemd/opennhrp.service
deleted file mode 100644
index c9a44de29..000000000
--- a/src/systemd/opennhrp.service
+++ /dev/null
@@ -1,13 +0,0 @@
-[Unit]
-Description=OpenNHRP
-After=vyos-router.service
-ConditionPathExists=/run/opennhrp/opennhrp.conf
-StartLimitIntervalSec=0
-
-[Service]
-Type=forking
-ExecStart=/usr/sbin/opennhrp -d -v -a /run/opennhrp.socket -c /run/opennhrp/opennhrp.conf -s /etc/opennhrp/opennhrp-script.py -p /run/opennhrp/opennhrp.pid
-ExecReload=/usr/bin/kill -HUP $MAINPID
-PIDFile=/run/opennhrp/opennhrp.pid
-Restart=on-failure
-RestartSec=20
diff --git a/src/validators/bgp-large-community-list b/src/validators/bgp-large-community-list
index 9ba5b27eb..75276630c 100755
--- a/src/validators/bgp-large-community-list
+++ b/src/validators/bgp-large-community-list
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-2025 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
@@ -17,18 +17,27 @@
import re
import sys
-pattern = '(.*):(.*):(.*)'
-allowedChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '*', '?', '^', '$', '(', ')', '[', ']', '{', '}', '|', '\\', ':', '-' }
+allowedChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '*', '?', '^', '$', '(', ')', '[', ']', '{', '}', '|', '\\', ':', '-', '_', ' ' }
if __name__ == '__main__':
if len(sys.argv) != 2:
sys.exit(1)
- value = sys.argv[1].split(':')
- if not len(value) == 3:
+ value = sys.argv[1]
+
+ # Require at least one well-formed large-community tuple in the pattern.
+ tmp = value.split(':')
+ if len(tmp) < 3:
+ sys.exit(1)
+
+ # Simple guard against invalid community & 1003.2 pattern chars
+ if not set(value).issubset(allowedChars):
sys.exit(1)
- if not (re.match(pattern, sys.argv[1]) and set(sys.argv[1]).issubset(allowedChars)):
+ # Don't feed FRR badly formed regex
+ try:
+ re.compile(value)
+ except re.error:
sys.exit(1)
sys.exit(0)