summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/accel-ppp/ipoe.config.j28
-rw-r--r--data/templates/getty/serial-getty.service.j22
-rw-r--r--data/templates/ipsec/swanctl.conf.j24
-rw-r--r--data/templates/mdns-repeater/avahi-daemon.conf.j23
-rw-r--r--interface-definitions/container.xml.in1
-rw-r--r--interface-definitions/include/qos/hfsc-m1.xml.i1
-rw-r--r--interface-definitions/include/qos/hfsc-m2.xml.i1
-rw-r--r--interface-definitions/include/version/qos-version.xml.i2
-rw-r--r--interface-definitions/pki.xml.in6
-rw-r--r--interface-definitions/qos.xml.in111
-rw-r--r--interface-definitions/service_ipoe-server.xml.in48
-rw-r--r--interface-definitions/service_mdns_repeater.xml.in17
-rw-r--r--interface-definitions/vpn_ipsec.xml.in12
-rw-r--r--op-mode-definitions/show-configuration.xml.in7
-rw-r--r--python/vyos/defaults.py3
-rw-r--r--python/vyos/ifconfig/interface.py15
-rw-r--r--python/vyos/ifconfig/wireguard.py3
-rw-r--r--python/vyos/qos/base.py10
-rw-r--r--python/vyos/qos/cake.py47
-rw-r--r--python/vyos/qos/trafficshaper.py116
-rw-r--r--python/vyos/utils/convert.py26
-rwxr-xr-xsmoketest/scripts/cli/test_container.py16
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py220
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py23
-rwxr-xr-xsmoketest/scripts/cli/test_service_mdns_repeater.py76
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py7
-rwxr-xr-xsrc/conf_mode/container.py6
-rwxr-xr-xsrc/conf_mode/interfaces_wireguard.py36
-rwxr-xr-xsrc/conf_mode/pki.py61
-rwxr-xr-xsrc/conf_mode/protocols_static_multicast.py6
-rwxr-xr-xsrc/conf_mode/qos.py55
-rwxr-xr-xsrc/conf_mode/service_dhcp-server.py8
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py35
-rwxr-xr-xsrc/init/vyos-router6
-rw-r--r--src/migration-scripts/qos/2-to-334
-rwxr-xr-xsrc/op_mode/show_configuration_files.sh10
-rwxr-xr-xsrc/services/vyos-configd28
-rw-r--r--src/shim/vyshim.c12
38 files changed, 798 insertions, 284 deletions
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index 81f63c53b..34dfa529a 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -38,6 +38,9 @@ level={{ log.level }}
[ipoe]
verbose=1
+{% if lua_file is vyos_defined %}
+lua-file={{ lua_file }}
+{% endif %}
{% if interface is vyos_defined %}
{% for iface, iface_config in interface.items() %}
{% set tmp = 'interface=' %}
@@ -55,7 +58,10 @@ verbose=1
{% set range = 'range=' ~ iface_config.client_subnet ~ ',' if iface_config.client_subnet is vyos_defined else '' %}
{% set relay = ',' ~ 'relay=' ~ iface_config.external_dhcp.dhcp_relay if iface_config.external_dhcp.dhcp_relay is vyos_defined else '' %}
{% set giaddr = ',' ~ 'giaddr=' ~ iface_config.external_dhcp.giaddr if iface_config.external_dhcp.giaddr is vyos_defined else '' %}
-{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1{{ relay }}{{ giaddr }}
+{% set username = ',' ~ 'username=lua:' ~ iface_config.lua_username if iface_config.lua_username is vyos_defined else '' %}
+{% set start_map = {'dhcp': 'dhcpv4', 'unclassified-packet': 'up', 'auto': 'auto'} %}
+{% set start = start_map[iface_config.start_session] %}
+{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start={{ start }},ipv6=1{{ relay }}{{ giaddr }}{{ username }}
{% if iface_config.vlan_mon is vyos_defined %}
vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}
{% endif %}
diff --git a/data/templates/getty/serial-getty.service.j2 b/data/templates/getty/serial-getty.service.j2
index 0183eae7d..687b05b6d 100644
--- a/data/templates/getty/serial-getty.service.j2
+++ b/data/templates/getty/serial-getty.service.j2
@@ -22,7 +22,7 @@ Before=rescue.service
# The '-o' option value tells agetty to replace 'login' arguments with an
# option to preserve environment (-p), followed by '--' for safety, and then
# the entered username.
-ExecStart=-/sbin/agetty -o '-p -- \\u' --keep-baud {{ speed }} %I $TERM
+ExecStart=-/sbin/agetty -o '-p -- \\u' %I {{ speed }} $TERM
Type=idle
Restart=always
UtmpIdentifier=%I
diff --git a/data/templates/ipsec/swanctl.conf.j2 b/data/templates/ipsec/swanctl.conf.j2
index 698a9135e..64e7ea860 100644
--- a/data/templates/ipsec/swanctl.conf.j2
+++ b/data/templates/ipsec/swanctl.conf.j2
@@ -87,7 +87,11 @@ secrets {
id-{{ gen_uuid }} = "{{ id }}"
{% endfor %}
{% endif %}
+{% if psk_config.secret_type is vyos_defined('base64') %}
+ secret = 0s{{ psk_config.secret }}
+{% elif psk_config.secret_type is vyos_defined('plaintext') %}
secret = "{{ psk_config.secret }}"
+{% endif %}
}
{% endfor %}
{% endif %}
diff --git a/data/templates/mdns-repeater/avahi-daemon.conf.j2 b/data/templates/mdns-repeater/avahi-daemon.conf.j2
index cc6495817..a5031945c 100644
--- a/data/templates/mdns-repeater/avahi-daemon.conf.j2
+++ b/data/templates/mdns-repeater/avahi-daemon.conf.j2
@@ -6,6 +6,9 @@ allow-interfaces={{ interface | join(', ') }}
{% if browse_domain is vyos_defined and browse_domain | length %}
browse-domains={{ browse_domain | join(', ') }}
{% endif %}
+{% if cache_entries is vyos_defined %}
+cache-entries-max={{ cache_entries }}
+{% endif %}
disallow-other-stacks=no
[wide-area]
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 3dd1b3249..bd2ff820d 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -501,6 +501,7 @@
</properties>
<children>
#include <include/generic-description.xml.i>
+ #include <include/interface/mtu-68-16000.xml.i>
<leafNode name="prefix">
<properties>
<help>Prefix which allocated to that network</help>
diff --git a/interface-definitions/include/qos/hfsc-m1.xml.i b/interface-definitions/include/qos/hfsc-m1.xml.i
index 21b9c4f32..ca37f6ecf 100644
--- a/interface-definitions/include/qos/hfsc-m1.xml.i
+++ b/interface-definitions/include/qos/hfsc-m1.xml.i
@@ -27,6 +27,5 @@
<description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description>
</valueHelp>
</properties>
- <defaultValue>0bit</defaultValue>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/qos/hfsc-m2.xml.i b/interface-definitions/include/qos/hfsc-m2.xml.i
index 24e8f5d63..816546657 100644
--- a/interface-definitions/include/qos/hfsc-m2.xml.i
+++ b/interface-definitions/include/qos/hfsc-m2.xml.i
@@ -27,6 +27,5 @@
<description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description>
</valueHelp>
</properties>
- <defaultValue>100%</defaultValue>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/version/qos-version.xml.i b/interface-definitions/include/version/qos-version.xml.i
index c67e61e91..127f771a9 100644
--- a/interface-definitions/include/version/qos-version.xml.i
+++ b/interface-definitions/include/version/qos-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/qos-version.xml.i -->
-<syntaxVersion component='qos' version='2'></syntaxVersion>
+<syntaxVersion component='qos' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in
index b922771c1..c69886a4b 100644
--- a/interface-definitions/pki.xml.in
+++ b/interface-definitions/pki.xml.in
@@ -35,6 +35,12 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="system-install">
+ <properties>
+ <help>Install into CA certificate store on router</help>
+ <valueless/>
+ </properties>
+ </leafNode>
#include <include/pki/cli-revoke.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in
index 907fd5e4c..c6ecb742e 100644
--- a/interface-definitions/qos.xml.in
+++ b/interface-definitions/qos.xml.in
@@ -85,78 +85,67 @@
<children>
#include <include/generic-description.xml.i>
#include <include/qos/bandwidth.xml.i>
- <node name="flow-isolation">
+ <leafNode name="flow-isolation">
<properties>
<help>Flow isolation settings</help>
+ <completionHelp>
+ <list>blind src-host dst-host host flow dual-src-host dual-dst-host triple-isolate</list>
+ </completionHelp>
+ <valueHelp>
+ <format>blind</format>
+ <description>Disables flow isolation, all traffic passes through a single queue</description>
+ </valueHelp>
+ <valueHelp>
+ <format>src-host</format>
+ <description>Flows are defined only by source address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dst-host</format>
+ <description>Flows are defined only by destination address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host</format>
+ <description>Flows are defined by source-destination host pairs</description>
+ </valueHelp>
+ <valueHelp>
+ <format>flow</format>
+ <description>Flows are defined by the entire 5-tuple</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dual-src-host</format>
+ <description>Flows are defined by the 5-tuple, fairness is applied first over source addresses, then over individual flows</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dual-dst-host</format>
+ <description>Flows are defined by the 5-tuple, fairness is applied first over destination addresses, then over individual flows</description>
+ </valueHelp>
+ <valueHelp>
+ <format>triple-isolate</format>
+ <description>Flows are defined by the 5-tuple, fairness is applied over source and destination addresses and also over individual flows (default)</description>
+ </valueHelp>
+ <constraint>
+ <regex>(blind|src-host|dst-host|host|flow|dual-src-host|dual-dst-host|triple-isolate)</regex>
+ </constraint>
</properties>
- <children>
- <leafNode name="blind">
- <properties>
- <help>Disables flow isolation, all traffic passes through a single queue</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="src-host">
- <properties>
- <help>Flows are defined only by source address</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="dst-host">
- <properties>
- <help>Flows are defined only by destination address</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="host">
- <properties>
- <help>Flows are defined by source-destination host pairs</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="flow">
- <properties>
- <help>Flows are defined by the entire 5-tuple</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="dual-src-host">
- <properties>
- <help>Flows are defined by the 5-tuple, fairness is applied first over source addresses, then over individual flows</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="dual-dst-host">
- <properties>
- <help>Flows are defined by the 5-tuple, fairness is applied first over destination addresses, then over individual flows</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="triple-isolate">
- <properties>
- <help>Flows are defined by the 5-tuple, fairness is applied over source and destination addresses and also over individual flows (default)</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="nat">
- <properties>
- <help>Perform NAT lookup before applying flow-isolation rules</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
- </node>
+ <defaultValue>triple-isolate</defaultValue>
+ </leafNode>
+ <leafNode name="flow-isolation-nat">
+ <properties>
+ <help>Perform NAT lookup before applying flow-isolation rules</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="rtt">
<properties>
<help>Round-Trip-Time for Active Queue Management (AQM)</help>
<valueHelp>
- <format>u32:1-3600000</format>
+ <format>u32:1-1000000000</format>
<description>RTT in ms</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-3600000"/>
+ <validator name="numeric" argument="--range 1-1000000000"/>
</constraint>
- <constraintErrorMessage>RTT must be in range 1 to 3600000 milli-seconds</constraintErrorMessage>
+ <constraintErrorMessage>RTT must be in range 1 to 1000000000 milli-seconds</constraintErrorMessage>
</properties>
<defaultValue>100</defaultValue>
</leafNode>
diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in
index 25bc43cc6..39cfb7889 100644
--- a/interface-definitions/service_ipoe-server.xml.in
+++ b/interface-definitions/service_ipoe-server.xml.in
@@ -131,6 +131,30 @@
</properties>
<defaultValue>shared</defaultValue>
</leafNode>
+ <leafNode name="start-session">
+ <properties>
+ <help>Start session options</help>
+ <completionHelp>
+ <list>auto dhcp unclassified-packet</list>
+ </completionHelp>
+ <valueHelp>
+ <format>auto</format>
+ <description>Start session with username as the interface name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dhcp</format>
+ <description>Start session on DHCPv4 Discover</description>
+ </valueHelp>
+ <valueHelp>
+ <format>unclassified-packet</format>
+ <description>Start session on unclassified-packet</description>
+ </valueHelp>
+ <constraint>
+ <regex>(auto|dhcp|unclassified-packet)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>dhcp</defaultValue>
+ </leafNode>
<leafNode name="client-subnet">
<properties>
<help>Client address pool</help>
@@ -174,10 +198,34 @@
</leafNode>
</children>
</node>
+ <leafNode name="lua-username">
+ <properties>
+ <help>Username function</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of the function in the Lua file to construct usernames with</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ </properties>
+ </leafNode>
#include <include/accel-ppp/vlan.xml.i>
#include <include/accel-ppp/vlan-mon.xml.i>
</children>
</tagNode>
+ <leafNode name="lua-file">
+ <properties>
+ <help>Lua script file for constructing user names</help>
+ <valueHelp>
+ <format>filename</format>
+ <description>File with Lua script in /config/scripts directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-path" argument="--strict --parent-dir /config/scripts"/>
+ </constraint>
+ </properties>
+ </leafNode>
#include <include/accel-ppp/client-ip-pool.xml.i>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
#include <include/accel-ppp/default-pool.xml.i>
diff --git a/interface-definitions/service_mdns_repeater.xml.in b/interface-definitions/service_mdns_repeater.xml.in
index 5d6f61d74..9d626bf6a 100644
--- a/interface-definitions/service_mdns_repeater.xml.in
+++ b/interface-definitions/service_mdns_repeater.xml.in
@@ -67,6 +67,23 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="cache-entries">
+ <properties>
+ <help>Number of resource records cached per interface</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>Disable caching</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Resource records to cache per interface</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>4096</defaultValue>
+ </leafNode>
<leafNode name="vrrp-disable">
<properties>
<help>Disables mDNS repeater on VRRP interfaces not in MASTER state</help>
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index d9d6fd93b..5540021e2 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -41,6 +41,18 @@
</valueHelp>
</properties>
</leafNode>
+ <leafNode name="secret-type">
+ <properties>
+ <help>Secret type</help>
+ <completionHelp>
+ <list>base64 plaintext</list>
+ </completionHelp>
+ <constraint>
+ <regex>(base64|plaintext)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>plaintext</defaultValue>
+ </leafNode>
</children>
</tagNode>
</children>
diff --git a/op-mode-definitions/show-configuration.xml.in b/op-mode-definitions/show-configuration.xml.in
index 5a2fdedfa..7ec718890 100644
--- a/op-mode-definitions/show-configuration.xml.in
+++ b/op-mode-definitions/show-configuration.xml.in
@@ -23,13 +23,6 @@
<!-- no admin check -->
<command>cli-shell-api showCfg --show-active-only | vyos-config-to-commands</command>
</node>
- <node name="files">
- <properties>
- <help> Show available saved configurations </help>
- </properties>
- <!-- no admin check -->
- <command>${vyos_op_scripts_dir}/show_configuration_files.sh</command>
- </node>
<node name="json">
<properties>
<help>Show running configuration in JSON format</help>
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index dec619d3e..425990967 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -36,7 +36,8 @@ directories = {
'isc_dhclient_dir' : '/run/dhclient',
'dhcp6_client_dir' : '/run/dhcp6c',
'vyos_configdir' : '/opt/vyatta/config',
- 'completion_dir' : f'{base_dir}/completion'
+ 'completion_dir' : f'{base_dir}/completion',
+ 'ca_certificates' : '/usr/local/share/ca-certificates/vyos'
}
config_status = '/tmp/vyos-config-status'
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 002d3da9e..cd562e1fe 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -98,6 +98,10 @@ class Interface(Control):
'shellcmd': 'ip -json -detail link list dev {ifname}',
'format': lambda j: jmespath.search('[*].ifalias | [0]', json.loads(j)) or '',
},
+ 'ifindex': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].ifindex | [0]', json.loads(j)) or '',
+ },
'mac': {
'shellcmd': 'ip -json -detail link list dev {ifname}',
'format': lambda j: jmespath.search('[*].address | [0]', json.loads(j)),
@@ -428,6 +432,17 @@ class Interface(Control):
nft_command = f'add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}'
self._nft_check_and_run(nft_command)
+ def get_ifindex(self):
+ """
+ Get interface index by name
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_ifindex()
+ '2'
+ """
+ return int(self.get_interface('ifindex'))
+
def get_min_mtu(self):
"""
Get hardware minimum supported MTU
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index 9030b1302..cccac361d 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -195,6 +195,9 @@ class WireGuardIf(Interface):
base_cmd += f' private-key {tmp_file.name}'
base_cmd = base_cmd.format(**config)
+ # T6490: execute command to ensure interface configured
+ self._cmd(base_cmd)
+
if 'peer' in config:
for peer, peer_config in config['peer'].items():
# T4702: No need to configure this peer when it was explicitly
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index 322cdca44..12d940e3c 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -17,6 +17,7 @@ import os
import jmespath
from vyos.base import Warning
+from vyos.ifconfig import Interface
from vyos.utils.process import cmd
from vyos.utils.dict import dict_search
from vyos.utils.file import read_file
@@ -253,19 +254,24 @@ class QoSBase:
for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1):
filter_cmd = filter_cmd_base
if not has_filter:
- for key in ['mark', 'vif', 'ip', 'ipv6']:
+ for key in ['mark', 'vif', 'ip', 'ipv6', 'interface']:
if key in match_config:
has_filter = True
break
- if self.qostype == 'shaper' and 'prio ' not in filter_cmd:
+ if self.qostype in ['shaper', 'shaper_hfsc'] and 'prio ' not in filter_cmd:
filter_cmd += f' prio {index}'
if 'mark' in match_config:
mark = match_config['mark']
filter_cmd += f' handle {mark} fw'
+
if 'vif' in match_config:
vif = match_config['vif']
filter_cmd += f' basic match "meta(vlan mask 0xfff eq {vif})"'
+ elif 'interface' in match_config:
+ iif_name = match_config['interface']
+ iif = Interface(iif_name).get_ifindex()
+ filter_cmd += f' basic match "meta(rt_iif eq {iif})"'
for af in ['ip', 'ipv6']:
tc_af = af
diff --git a/python/vyos/qos/cake.py b/python/vyos/qos/cake.py
index 1ee7d0fc3..ca5a26917 100644
--- a/python/vyos/qos/cake.py
+++ b/python/vyos/qos/cake.py
@@ -15,10 +15,25 @@
from vyos.qos.base import QoSBase
+
class CAKE(QoSBase):
+ """
+ https://man7.org/linux/man-pages/man8/tc-cake.8.html
+ """
+
_direction = ['egress']
- # https://man7.org/linux/man-pages/man8/tc-cake.8.html
+ flow_isolation_map = {
+ 'blind': 'flowblind',
+ 'src-host': 'srchost',
+ 'dst-host': 'dsthost',
+ 'dual-dst-host': 'dual-dsthost',
+ 'dual-src-host': 'dual-srchost',
+ 'triple-isolate': 'triple-isolate',
+ 'flow': 'flows',
+ 'host': 'hosts',
+ }
+
def update(self, config, direction):
tmp = f'tc qdisc add dev {self._interface} root handle 1: cake {direction}'
if 'bandwidth' in config:
@@ -30,26 +45,16 @@ class CAKE(QoSBase):
tmp += f' rtt {rtt}ms'
if 'flow_isolation' in config:
- if 'blind' in config['flow_isolation']:
- tmp += f' flowblind'
- if 'dst_host' in config['flow_isolation']:
- tmp += f' dsthost'
- if 'dual_dst_host' in config['flow_isolation']:
- tmp += f' dual-dsthost'
- if 'dual_src_host' in config['flow_isolation']:
- tmp += f' dual-srchost'
- if 'triple_isolate' in config['flow_isolation']:
- tmp += f' triple-isolate'
- if 'flow' in config['flow_isolation']:
- tmp += f' flows'
- if 'host' in config['flow_isolation']:
- tmp += f' hosts'
- if 'nat' in config['flow_isolation']:
- tmp += f' nat'
- if 'src_host' in config['flow_isolation']:
- tmp += f' srchost '
- else:
- tmp += f' nonat'
+ isolation_value = self.flow_isolation_map.get(config['flow_isolation'])
+
+ if isolation_value is not None:
+ tmp += f' {isolation_value}'
+ else:
+ raise ValueError(
+ f'Invalid flow isolation parameter: {config["flow_isolation"]}'
+ )
+
+ tmp += ' nat' if 'flow_isolation_nat' in config else ' nonat'
self._cmd(tmp)
diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py
index 8b0333c21..9f92ccd8b 100644
--- a/python/vyos/qos/trafficshaper.py
+++ b/python/vyos/qos/trafficshaper.py
@@ -126,91 +126,71 @@ class TrafficShaper(QoSBase):
# call base class
super().update(config, direction)
+
class TrafficShaperHFSC(QoSBase):
+ """
+ Traffic shaper using Hierarchical Fair Service Curve (HFSC).
+ Documentation: https://man7.org/linux/man-pages/man8/tc-hfsc.8.html
+ """
+
_parent = 1
qostype = 'shaper_hfsc'
- # https://man7.org/linux/man-pages/man8/tc-hfsc.8.html
- def update(self, config, direction):
- class_id_max = 0
- if 'class' in config:
- tmp = list(config['class'])
- tmp.sort()
- class_id_max = tmp[-1]
+ criteria = ['linkshare', 'realtime', 'upperlimit']
+ short_criterion = {
+ 'linkshare': 'ls',
+ 'realtime': 'rt',
+ 'upperlimit': 'ul',
+ }
+
+ def _gen_class(self, cls: int, cls_config: dict):
+ """
+ Generate HFSC class and add Stochastic Fair Queueing (SFQ) qdisc.
+
+ Args:
+ cls (int): Class ID
+ cls_config (dict): Configuration for the class
+ """
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} hfsc'
+
+ for crit in self.criteria:
+ param = cls_config.get(crit)
+ if param:
+ tmp += (
+ f' {self.short_criterion[crit]}'
+ f' m1 {self._rate_convert(param["m1"]) if param.get("m1") else 0}'
+ f' d {param.get("d", 0)}ms'
+ f' m2 {self._rate_convert(param["m2"])}'
+ )
- r2q = 10
- # bandwidth is a mandatory CLI node
- speed = self._rate_convert(config['bandwidth'])
- speed_bps = int(speed) // 8
+ self._cmd(tmp)
- # need a bigger r2q if going fast than 16 mbits/sec
- if (speed_bps // r2q) >= MAXQUANTUM: # integer division
- r2q = ceil(speed_bps // MAXQUANTUM)
- else:
- # if there is a slow class then may need smaller value
- if 'class' in config:
- min_speed = speed_bps
- for cls, cls_options in config['class'].items():
- # find class with the lowest bandwidth used
- if 'bandwidth' in cls_options:
- bw_bps = int(self._rate_convert(cls_options['bandwidth'])) // 8 # bandwidth in bytes per second
- if bw_bps < min_speed:
- min_speed = bw_bps
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq perturb 10'
+ self._cmd(tmp)
- while (r2q > 1) and (min_speed // r2q) < MINQUANTUM:
- tmp = r2q -1
- if (speed_bps // tmp) >= MAXQUANTUM:
- break
- r2q = tmp
+ def update(self, config, direction):
+ class_id_max = self._get_class_max_id(config)
+ default_cls_id = int(class_id_max) + 1 if class_id_max else 2
- default_minor_id = int(class_id_max) +1
- tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: hfsc default {default_minor_id:x}' # default is in hex
+ speed = self._rate_convert(config['bandwidth'])
+
+ tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: hfsc default {default_cls_id:x}' # default is in hex
self._cmd(tmp)
tmp = f'tc class replace dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 hfsc sc rate {speed} ul rate {speed}'
self._cmd(tmp)
+ # tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:1 handle f1: sfq perturb 10'
+ # self._cmd(tmp)
+
if 'class' in config:
for cls, cls_config in config['class'].items():
- # class id is used later on and passed as hex, thus this needs to be an int
- cls = int(cls)
- # ls m1
- if cls_config.get('linkshare', {}).get('m1').endswith('%'):
- percent = cls_config['linkshare']['m1'].rstrip('%')
- m_one_rate = self._rate_convert(config['bandwidth']) * int(percent) // 100
- else:
- m_one_rate = cls_config['linkshare']['m1']
- # ls m2
- if cls_config.get('linkshare', {}).get('m2').endswith('%'):
- percent = cls_config['linkshare']['m2'].rstrip('%')
- m_two_rate = self._rate_convert(config['bandwidth']) * int(percent) // 100
- else:
- m_two_rate = self._rate_convert(cls_config['linkshare']['m2'])
-
- tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} hfsc ls m1 {m_one_rate} m2 {m_two_rate} '
- self._cmd(tmp)
-
- tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq perturb 10'
- self._cmd(tmp)
+ self._gen_class(cls=int(cls), cls_config=cls_config)
if 'default' in config:
- # ls m1
- if config.get('default', {}).get('linkshare', {}).get('m1').endswith('%'):
- percent = config['default']['linkshare']['m1'].rstrip('%')
- m_one_rate = self._rate_convert(config['default']['linkshare']['m1']) * int(percent) // 100
- else:
- m_one_rate = config['default']['linkshare']['m1']
- # ls m2
- if config.get('default', {}).get('linkshare', {}).get('m2').endswith('%'):
- percent = config['default']['linkshare']['m2'].rstrip('%')
- m_two_rate = self._rate_convert(config['default']['linkshare']['m2']) * int(percent) // 100
- else:
- m_two_rate = self._rate_convert(config['default']['linkshare']['m2'])
- tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} hfsc ls m1 {m_one_rate} m2 {m_two_rate} '
- self._cmd(tmp)
-
- tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq perturb 10'
- self._cmd(tmp)
+ self._gen_class(
+ cls=int(default_cls_id), cls_config=config.get('default', {})
+ )
# call base class
super().update(config, direction)
diff --git a/python/vyos/utils/convert.py b/python/vyos/utils/convert.py
index dd4266f57..2f587405d 100644
--- a/python/vyos/utils/convert.py
+++ b/python/vyos/utils/convert.py
@@ -235,3 +235,29 @@ def convert_data(data) -> dict | list | tuple | str | int | float | bool | None:
# which cannot be converted to JSON
# for example: complex | range | memoryview
return
+
+
+def encode_to_base64(input_string):
+ """
+ Encodes a given string to its base64 representation.
+
+ Args:
+ input_string (str): The string to be encoded.
+
+ Returns:
+ str: The base64-encoded version of the input string.
+
+ Example:
+ input_string = "Hello, World!"
+ encoded_string = encode_to_base64(input_string)
+ print(encoded_string) # Output: SGVsbG8sIFdvcmxkIQ==
+ """
+ import base64
+ # Convert the string to bytes
+ byte_string = input_string.encode('utf-8')
+
+ # Encode the byte string to base64
+ encoded_string = base64.b64encode(byte_string)
+
+ # Decode the base64 bytes back to a string
+ return encoded_string.decode('utf-8')
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index c03b9eb44..0541384da 100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -224,6 +224,22 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
n = cmd_to_json(f'sudo podman network inspect {net_name}')
self.assertEqual(n['dns_enabled'], False)
+ def test_network_mtu(self):
+ prefix = '192.0.2.0/24'
+ base_name = 'ipv4'
+ net_name = 'NET01'
+
+ self.cli_set(base_path + ['network', net_name, 'prefix', prefix])
+ self.cli_set(base_path + ['network', net_name, 'mtu', '1280'])
+
+ name = f'{base_name}-2'
+ self.cli_set(base_path + ['name', name, 'image', cont_image])
+ self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + 2)])
+ self.cli_commit()
+
+ n = cmd_to_json(f'sudo podman network inspect {net_name}')
+ self.assertEqual(n['options']['mtu'], '1280')
+
def test_uid_gid(self):
cont_name = 'uid-test'
gid = '100'
diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py
index aaeebcdae..79b791288 100755
--- a/smoketest/scripts/cli/test_qos.py
+++ b/smoketest/scripts/cli/test_qos.py
@@ -21,7 +21,8 @@ from json import loads
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.ifconfig import Section
+from vyos.ifconfig import Section, Interface
+from vyos.qos import CAKE
from vyos.utils.process import cmd
base_path = ['qos']
@@ -871,6 +872,68 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.cli_set(['qos', 'traffic-match-group', '3', 'match-group', 'unexpected'])
self.cli_commit()
+ def test_17_cake_updates(self):
+ bandwidth = 1000000
+ rtt = 200
+ interface = self._interfaces[0]
+ policy_name = f'qos-policy-{interface}'
+
+ self.cli_set(base_path + ['interface', interface, 'egress', policy_name])
+ self.cli_set(
+ base_path + ['policy', 'cake', policy_name, 'bandwidth', str(bandwidth)]
+ )
+ self.cli_set(base_path + ['policy', 'cake', policy_name, 'rtt', str(rtt)])
+
+ # commit changes
+ self.cli_commit()
+
+ tmp = get_tc_qdisc_json(interface)
+
+ self.assertEqual('cake', tmp['kind'])
+ # TC store rates as a 32-bit unsigned integer in bps (Bytes per second)
+ self.assertEqual(int(bandwidth * 125), tmp['options']['bandwidth'])
+ # RTT internally is in us
+ self.assertEqual(int(rtt * 1000), tmp['options']['rtt'])
+ self.assertEqual('triple-isolate', tmp['options']['flowmode'])
+ self.assertFalse(tmp['options']['ingress'])
+ self.assertFalse(tmp['options']['nat'])
+ self.assertTrue(tmp['options']['raw'])
+
+ nat = True
+ for flow_isolation in [
+ 'blind',
+ 'src-host',
+ 'dst-host',
+ 'dual-dst-host',
+ 'dual-src-host',
+ 'triple-isolate',
+ 'flow',
+ 'host',
+ ]:
+ self.cli_set(
+ base_path
+ + ['policy', 'cake', policy_name, 'flow-isolation', flow_isolation]
+ )
+
+ if nat:
+ self.cli_set(
+ base_path + ['policy', 'cake', policy_name, 'flow-isolation-nat']
+ )
+ else:
+ self.cli_delete(
+ base_path + ['policy', 'cake', policy_name, 'flow-isolation-nat']
+ )
+
+ self.cli_commit()
+
+ tmp = get_tc_qdisc_json(interface)
+ self.assertEqual(
+ CAKE.flow_isolation_map.get(flow_isolation), tmp['options']['flowmode']
+ )
+
+ self.assertEqual(nat, tmp['options']['nat'])
+ nat = not nat
+
def test_20_round_robin_policy_default(self):
interface = self._interfaces[0]
policy_name = f'qos-policy-{interface}'
@@ -922,6 +985,137 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
tmp[2]['options'],
)
+ def test_21_shaper_hfsc(self):
+ interface = self._interfaces[0]
+ policy_name = f'qos-policy-{interface}'
+ ul = {
+ 'm1': '100kbit',
+ 'm2': '150kbit',
+ 'd': '100',
+ }
+ ls = {'m2': '120kbit'}
+ rt = {
+ 'm1': '110kbit',
+ 'm2': '130kbit',
+ 'd': '75',
+ }
+ self.cli_set(base_path + ['interface', interface, 'egress', policy_name])
+ self.cli_set(base_path + ['policy', 'shaper-hfsc', policy_name])
+
+ # Policy {policy_name} misses "default" class!
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'default', 'upperlimit']
+ )
+
+ # At least one m2 value needs to be set for class: {class_name}
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'default', 'upperlimit', 'm1', ul['m1']]
+ )
+ # {class_name} upperlimit m1 value is set, but no m2 was found!
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'default', 'upperlimit', 'm2', ul['m2']]
+ )
+ # {class_name} upperlimit m1 value is set, but no d was found!
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'default', 'upperlimit', 'd', ul['d']]
+ )
+ # Linkshare m2 needs to be defined to use upperlimit m2 for class: {class_name}
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'default', 'linkshare', 'm2', ls['m2']]
+ )
+ self.cli_commit()
+
+ # use raw because tc json is incorrect here
+ tmp = cmd(f'tc -details qdisc show dev {interface}')
+ for rec in tmp.split('\n'):
+ rec = rec.strip()
+ if 'root' in rec:
+ self.assertEqual(rec, 'qdisc hfsc 1: root refcnt 2 default 2')
+ else:
+ self.assertRegex(
+ rec,
+ r'qdisc sfq \S+: parent 1:2 limit 127p quantum 1514b depth 127 flows 128 divisor 1024 perturb 10sec',
+ )
+ # use raw because tc json is incorrect here
+ tmp = cmd(f'tc -details class show dev {interface}')
+ for rec in tmp.split('\n'):
+ rec = rec.strip().lower()
+ if 'root' in rec:
+ self.assertEqual(rec, 'class hfsc 1: root')
+ elif 'hfsc 1:1' in rec:
+ # m2 \S+bit is auto bandwidth
+ self.assertRegex(
+ rec,
+ r'class hfsc 1:1 parent 1: sc m1 0bit d 0us m2 \S+bit ul m1 0bit d 0us m2 \S+bit',
+ )
+ else:
+ self.assertRegex(
+ rec,
+ rf'class hfsc 1:2 parent 1:1 leaf \S+: ls m1 0bit d 0us m2 {ls["m2"]} ul m1 {ul["m1"]} d {ul["d"]}ms m2 {ul["m2"]}',
+ )
+
+ for key, val in rt.items():
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'default', 'realtime', key, val]
+ )
+ self.cli_commit()
+
+ tmp = cmd(f'tc -details class show dev {interface}')
+ for rec in tmp.split('\n'):
+ rec = rec.strip().lower()
+ if 'hfsc 1:2' in rec:
+ self.assertTrue(
+ f'rt m1 {rt["m1"]} d {rt["d"]}ms m2 {rt["m2"]} ls m1 0bit d 0us m2 {ls["m2"]} ul m1 {ul["m1"]} d {ul["d"]}ms m2 {ul["m2"]}'
+ in rec
+ )
+
+ # add some class
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'class', '10', 'linkshare', 'm2', '300kbit']
+ )
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'class', '10', 'match', 'tst', 'ip', 'dscp', 'internet']
+ )
+
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'class', '30', 'realtime', 'm2', '250kbit']
+ )
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'class', '30', 'realtime', 'd', '77']
+ )
+ self.cli_set(
+ base_path + ['policy', 'shaper-hfsc', policy_name, 'class', '30', 'match', 'tst30', 'ip', 'dscp', 'critical']
+ )
+ self.cli_commit()
+
+ tmp = cmd(f'tc -details qdisc show dev {interface}')
+ self.assertEqual(4, len(tmp.split('\n')))
+
+ tmp = cmd(f'tc -details class show dev {interface}')
+ tmp = tmp.lower()
+
+ self.assertTrue(
+ f'rt m1 {rt["m1"]} d {rt["d"]}ms m2 {rt["m2"]} ls m1 0bit d 0us m2 {ls["m2"]} ul m1 {ul["m1"]} d {ul["d"]}ms m2 {ul["m2"]}'
+ in tmp
+ )
+ self.assertTrue(': ls m1 0bit d 0us m2 300kbit' in tmp)
+ self.assertTrue(': rt m1 0bit d 77ms m2 250kbit' in tmp)
+
def test_22_rate_control_default(self):
interface = self._interfaces[0]
policy_name = f'qos-policy-{interface}'
@@ -943,6 +1137,30 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
# TC store rates as a 32-bit unsigned integer in bps (Bytes per second)
self.assertEqual(int(bandwidth * 125), tmp['options']['rate'])
+ def test_23_policy_limiter_iif_filter(self):
+ policy_name = 'smoke_test'
+ base_policy_path = ['qos', 'policy', 'limiter', policy_name]
+
+ self.cli_set(['qos', 'interface', self._interfaces[0], 'ingress', policy_name])
+ self.cli_set(base_policy_path + ['class', '100', 'bandwidth', '20gbit'])
+ self.cli_set(base_policy_path + ['class', '100', 'burst', '3760k'])
+ self.cli_set(base_policy_path + ['class', '100', 'match', 'test', 'interface', self._interfaces[0]])
+ self.cli_set(base_policy_path + ['class', '100', 'priority', '20'])
+ self.cli_set(base_policy_path + ['default', 'bandwidth', '1gbit'])
+ self.cli_set(base_policy_path + ['default', 'burst', '125000000b'])
+ self.cli_commit()
+
+ iif = Interface(self._interfaces[0]).get_ifindex()
+ tc_filters = cmd(f'tc filter show dev {self._interfaces[0]} ingress')
+
+ # class 100
+ self.assertIn('filter parent ffff: protocol all pref 20 basic chain 0', tc_filters)
+ self.assertIn(f'meta(rt_iif eq {iif})', tc_filters)
+ self.assertIn('action order 1: police 0x1 rate 20Gbit burst 3847500b mtu 2Kb action drop overhead 0b', tc_filters)
+ # default
+ self.assertIn('filter parent ffff: protocol all pref 255 basic chain 0', tc_filters)
+ self.assertIn('action order 1: police 0x2 rate 1Gbit burst 125000000b mtu 2Kb action drop overhead 0b', tc_filters)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index 46c4e25a1..f891bf295 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -557,6 +557,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(pool + ['subnet-id', '1'])
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['exclude', router])
+ self.cli_set(pool + ['range', '0', 'option', 'default-router', router])
self.cli_set(pool + ['range', '0', 'start', range_0_start])
self.cli_set(pool + ['range', '0', 'stop', range_0_stop])
@@ -569,6 +570,11 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST')
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ pool_obj = {
+ 'pool': f'{range_0_start} - {range_0_stop}',
+ 'option-data': [{'name': 'routers', 'data': router}]
+ }
+
# Verify options
self.verify_config_object(
obj,
@@ -579,7 +585,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_object(
obj,
['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start} - {range_0_stop}'})
+ pool_obj)
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -600,6 +606,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(pool + ['subnet-id', '1'])
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['exclude', exclude_addr])
+ self.cli_set(pool + ['range', '0', 'option', 'default-router', router])
self.cli_set(pool + ['range', '0', 'start', range_0_start])
self.cli_set(pool + ['range', '0', 'stop', range_0_stop])
@@ -612,6 +619,16 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST-2')
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ pool_obj = {
+ 'pool': f'{range_0_start} - {range_0_stop_excl}',
+ 'option-data': [{'name': 'routers', 'data': router}]
+ }
+
+ pool_exclude_obj = {
+ 'pool': f'{range_0_start_excl} - {range_0_stop}',
+ 'option-data': [{'name': 'routers', 'data': router}]
+ }
+
# Verify options
self.verify_config_object(
obj,
@@ -621,12 +638,12 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_object(
obj,
['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start} - {range_0_stop_excl}'})
+ pool_obj)
self.verify_config_object(
obj,
['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start_excl} - {range_0_stop}'})
+ pool_exclude_obj)
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
diff --git a/smoketest/scripts/cli/test_service_mdns_repeater.py b/smoketest/scripts/cli/test_service_mdns_repeater.py
index f2fb3b509..30e48683f 100755
--- a/smoketest/scripts/cli/test_service_mdns_repeater.py
+++ b/smoketest/scripts/cli/test_service_mdns_repeater.py
@@ -21,36 +21,45 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from configparser import ConfigParser
from vyos.configsession import ConfigSessionError
from vyos.utils.process import process_named_running
+from vyos.xml_ref import default_value
base_path = ['service', 'mdns', 'repeater']
intf_base = ['interfaces', 'dummy']
config_file = '/run/avahi-daemon/avahi-daemon.conf'
-
class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- # Start with a clean CLI instance
- self.cli_delete(base_path)
+ @classmethod
+ def setUpClass(cls):
+ super(TestServiceMDNSrepeater, cls).setUpClass()
- # Service required a configured IP address on the interface
- self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30'])
- self.cli_set(intf_base + ['dum10', 'ipv6', 'address', 'no-default-link-local'])
- self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30'])
- self.cli_set(intf_base + ['dum20', 'address', '2001:db8:0:2::5/64'])
- self.cli_set(intf_base + ['dum30', 'address', '192.0.2.9/30'])
- self.cli_set(intf_base + ['dum30', 'address', '2001:db8:0:2::9/64'])
- self.cli_set(intf_base + ['dum40', 'address', '2001:db8:0:2::11/64'])
- self.cli_commit()
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ cls.cli_set(cls, intf_base + ['dum10', 'address', '192.0.2.1/30'])
+ cls.cli_set(cls, intf_base + ['dum10', 'ipv6', 'address', 'no-default-link-local'])
+ cls.cli_set(cls, intf_base + ['dum20', 'address', '192.0.2.5/30'])
+ cls.cli_set(cls, intf_base + ['dum20', 'address', '2001:db8:0:2::5/64'])
+ cls.cli_set(cls, intf_base + ['dum30', 'address', '192.0.2.9/30'])
+ cls.cli_set(cls, intf_base + ['dum30', 'address', '2001:db8:0:2::9/64'])
+ cls.cli_set(cls, intf_base + ['dum40', 'address', '2001:db8:0:2::11/64'])
+
+ cls.cli_commit(cls)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, intf_base + ['dum10'])
+ cls.cli_delete(cls, intf_base + ['dum20'])
+ cls.cli_delete(cls, intf_base + ['dum30'])
+ cls.cli_delete(cls, intf_base + ['dum40'])
+
+ cls.cli_commit(cls)
def tearDown(self):
# Check for running process
self.assertTrue(process_named_running('avahi-daemon'))
self.cli_delete(base_path)
- self.cli_delete(intf_base + ['dum10'])
- self.cli_delete(intf_base + ['dum20'])
- self.cli_delete(intf_base + ['dum30'])
- self.cli_delete(intf_base + ['dum40'])
self.cli_commit()
# Check that there is no longer a running process
@@ -130,5 +139,38 @@ class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase):
self.assertEqual(conf['server']['allow-interfaces'], 'dum30, dum40')
self.assertEqual(conf['reflector']['enable-reflector'], 'yes')
+ def test_service_max_cache_entries(self):
+ cli_default_max_cache = default_value(base_path + ['cache-entries'])
+ self.cli_set(base_path)
+
+ # Need at least two interfaces
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + ['interface', 'dum20'])
+
+ # Need at least two interfaces
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + ['interface', 'dum30'])
+
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(delimiters='=')
+ conf.read(config_file)
+ self.assertEqual(conf['server']['cache-entries-max'], cli_default_max_cache)
+
+ # Set max cache entries
+ cache_entries = '1234'
+ self.cli_set(base_path + ['cache-entries', cache_entries])
+
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(delimiters='=')
+ conf.read(config_file)
+
+ self.assertEqual(conf['server']['cache-entries-max'], cache_entries)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index de18d0427..f2bea58d1 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -21,6 +21,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Interface
+from vyos.utils.convert import encode_to_base64
from vyos.utils.process import process_named_running
from vyos.utils.file import read_file
@@ -495,6 +496,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
local_id = 'vyos-r1'
remote_id = 'vyos-r2'
peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
+ secret_base64 = encode_to_base64(secret)
self.cli_set(tunnel_path + ['tun1', 'encapsulation', 'gre'])
self.cli_set(tunnel_path + ['tun1', 'source-address', local_address])
@@ -509,7 +511,8 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address])
self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
- self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret_base64])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret-type', 'base64'])
self.cli_set(peer_base_path + ['authentication', 'local-id', local_id])
self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
@@ -546,7 +549,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
f'id-{regex_uuid4} = "{remote_id}"',
f'id-{regex_uuid4} = "{peer_ip}"',
f'id-{regex_uuid4} = "{local_address}"',
- f'secret = "{secret}"',
+ f'secret = 0s{secret_base64}',
]
for line in swanctl_secrets_lines:
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 14387cbbf..a7dc33d9d 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -419,12 +419,18 @@ def generate(container):
'dns_enabled': True,
'ipam_options': {
'driver': 'host-local'
+ },
+ 'options': {
+ 'mtu': '1500'
}
}
if 'no_name_server' in network_config:
tmp['dns_enabled'] = False
+ if 'mtu' in network_config:
+ tmp['options']['mtu'] = network_config['mtu']
+
for prefix in network_config['prefix']:
net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
tmp['subnets'].append(net)
diff --git a/src/conf_mode/interfaces_wireguard.py b/src/conf_mode/interfaces_wireguard.py
index 7abdfdbfa..b6fd6b0b2 100755
--- a/src/conf_mode/interfaces_wireguard.py
+++ b/src/conf_mode/interfaces_wireguard.py
@@ -70,9 +70,6 @@ def verify(wireguard):
if 'private_key' not in wireguard:
raise ConfigError('Wireguard private-key not defined')
- if 'peer' not in wireguard:
- raise ConfigError('At least one Wireguard peer is required!')
-
if 'port' in wireguard and 'port_changed' in wireguard:
listen_port = int(wireguard['port'])
if check_port_availability('0.0.0.0', listen_port, 'udp') is not True:
@@ -80,28 +77,29 @@ def verify(wireguard):
'cannot be used for the interface!')
# run checks on individual configured WireGuard peer
- public_keys = []
- for tmp in wireguard['peer']:
- peer = wireguard['peer'][tmp]
+ if 'peer' in wireguard:
+ public_keys = []
+ for tmp in wireguard['peer']:
+ peer = wireguard['peer'][tmp]
- if 'allowed_ips' not in peer:
- raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!')
+ if 'allowed_ips' not in peer:
+ raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!')
- if 'public_key' not in peer:
- raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!')
+ if 'public_key' not in peer:
+ raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!')
- if ('address' in peer and 'port' not in peer) or ('port' in peer and 'address' not in peer):
- raise ConfigError('Both Wireguard port and address must be defined '
- f'for peer "{tmp}" if either one of them is set!')
+ if ('address' in peer and 'port' not in peer) or ('port' in peer and 'address' not in peer):
+ raise ConfigError('Both Wireguard port and address must be defined '
+ f'for peer "{tmp}" if either one of them is set!')
- if peer['public_key'] in public_keys:
- raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"')
+ if peer['public_key'] in public_keys:
+ raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"')
- if 'disable' not in peer:
- if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']):
- raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"')
+ if 'disable' not in peer:
+ if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']):
+ raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"')
- public_keys.append(peer['public_key'])
+ public_keys.append(peer['public_key'])
def generate(wireguard):
return None
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index 45e0129a3..acea2c9be 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -50,6 +50,7 @@ from vyos import airbag
airbag.enable()
vyos_certbot_dir = directories['certbot']
+vyos_ca_certificates_dir = directories['ca_certificates']
# keys to recursively search for under specified path
sync_search = [
@@ -149,35 +150,15 @@ def get_config(config=None):
if len(argv) > 1 and argv[1] == 'certbot_renew':
pki['certbot_renew'] = {}
- tmp = node_changed(conf, base + ['ca'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
- if tmp:
- if 'changed' not in pki: pki.update({'changed':{}})
- pki['changed'].update({'ca' : tmp})
-
- tmp = node_changed(conf, base + ['certificate'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
- if tmp:
- if 'changed' not in pki: pki.update({'changed':{}})
- pki['changed'].update({'certificate' : tmp})
+ changed_keys = ['ca', 'certificate', 'dh', 'key-pair', 'openssh', 'openvpn']
- tmp = node_changed(conf, base + ['dh'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
- if tmp:
- if 'changed' not in pki: pki.update({'changed':{}})
- pki['changed'].update({'dh' : tmp})
+ for key in changed_keys:
+ tmp = node_changed(conf, base + [key], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
- tmp = node_changed(conf, base + ['key-pair'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
- if tmp:
- if 'changed' not in pki: pki.update({'changed':{}})
- pki['changed'].update({'key_pair' : tmp})
-
- tmp = node_changed(conf, base + ['openssh'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
- if tmp:
- if 'changed' not in pki: pki.update({'changed':{}})
- pki['changed'].update({'openssh' : tmp})
+ if 'changed' not in pki:
+ pki.update({'changed':{}})
- tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
- if tmp:
- if 'changed' not in pki: pki.update({'changed':{}})
- pki['changed'].update({'openvpn' : tmp})
+ pki['changed'].update({key.replace('-', '_') : tmp})
# We only merge on the defaults of there is a configuration at all
if conf.exists(base):
@@ -417,10 +398,33 @@ def verify(pki):
return None
+def cleanup_system_ca():
+ if not os.path.exists(vyos_ca_certificates_dir):
+ os.mkdir(vyos_ca_certificates_dir)
+ else:
+ for filename in os.listdir(vyos_ca_certificates_dir):
+ full_path = os.path.join(vyos_ca_certificates_dir, filename)
+ if os.path.isfile(full_path):
+ os.unlink(full_path)
+
def generate(pki):
if not pki:
+ cleanup_system_ca()
return None
+ # Create or cleanup CA install directory
+ if 'changed' in pki and 'ca' in pki['changed']:
+ cleanup_system_ca()
+
+ if 'ca' in pki:
+ for ca, ca_conf in pki['ca'].items():
+ if 'system_install' in ca_conf:
+ ca_obj = load_certificate(ca_conf['certificate'])
+ ca_path = os.path.join(vyos_ca_certificates_dir, f'{ca}.crt')
+
+ with open(ca_path, 'w') as f:
+ f.write(encode_certificate(ca_obj))
+
# Certbot renewal only needs to re-trigger the services to load up the
# new PEM file
if 'certbot_renew' in pki:
@@ -487,6 +491,7 @@ def apply(pki):
systemd_certbot_name = 'certbot.timer'
if not pki:
call(f'systemctl stop {systemd_certbot_name}')
+ call('update-ca-certificates')
return None
has_certbot = False
@@ -504,6 +509,10 @@ def apply(pki):
if 'changed' in pki:
call_dependents()
+ # Rebuild ca-certificates bundle
+ if 'ca' in pki['changed']:
+ call('update-ca-certificates')
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py
index d323ceb4f..c8894fd41 100755
--- a/src/conf_mode/protocols_static_multicast.py
+++ b/src/conf_mode/protocols_static_multicast.py
@@ -86,10 +86,10 @@ def verify(mroute):
if mroute is None:
return None
- for route in mroute['mroute']:
- route = route.split('/')
+ for mcast_route in mroute['mroute']:
+ route = mcast_route.split('/')
if IPv4Address(route[0]) < IPv4Address('224.0.0.0'):
- raise ConfigError(route + " not a multicast network")
+ raise ConfigError(f'{mcast_route} not a multicast network')
def generate(mroute):
diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py
index a4d5f44e7..59e307a39 100755
--- a/src/conf_mode/qos.py
+++ b/src/conf_mode/qos.py
@@ -198,10 +198,16 @@ def get_config(config=None):
def _verify_match(cls_config: dict) -> None:
if 'match' in cls_config:
for match, match_config in cls_config['match'].items():
- if {'ip', 'ipv6'} <= set(match_config):
+ filters = set(match_config)
+ if {'ip', 'ipv6'} <= filters:
raise ConfigError(
f'Can not use both IPv6 and IPv4 in one match ({match})!')
+ if {'interface', 'vif'} & filters:
+ if {'ip', 'ipv6', 'ether'} & filters:
+ raise ConfigError(
+ f'Can not combine protocol and interface or vlan tag match ({match})!')
+
def _verify_match_group_exist(cls_config, qos):
if 'match_group' in cls_config:
@@ -210,6 +216,46 @@ def _verify_match_group_exist(cls_config, qos):
Warning(f'Match group "{group}" does not exist!')
+def _verify_default_policy_exist(policy, policy_config):
+ if 'default' not in policy_config:
+ raise ConfigError(f'Policy {policy} misses "default" class!')
+
+
+def _check_shaper_hfsc_rate(cls, cls_conf):
+ is_m2_exist = False
+ for crit in TrafficShaperHFSC.criteria:
+ if cls_conf.get(crit, {}).get('m2') is not None:
+ is_m2_exist = True
+
+ if cls_conf.get(crit, {}).get('m1') is not None:
+ for crit_val in ['m2', 'd']:
+ if cls_conf.get(crit, {}).get(crit_val) is None:
+ raise ConfigError(
+ f'{cls} {crit} m1 value is set, but no {crit_val} was found!'
+ )
+
+ if not is_m2_exist:
+ raise ConfigError(f'At least one m2 value needs to be set for class: {cls}')
+
+ if (
+ cls_conf.get('upperlimit', {}).get('m2') is not None
+ and cls_conf.get('linkshare', {}).get('m2') is None
+ ):
+ raise ConfigError(
+ f'Linkshare m2 needs to be defined to use upperlimit m2 for class: {cls}'
+ )
+
+
+def _verify_shaper_hfsc(policy, policy_config):
+ _verify_default_policy_exist(policy, policy_config)
+
+ _check_shaper_hfsc_rate('default', policy_config.get('default'))
+
+ if 'class' in policy_config:
+ for cls, cls_conf in policy_config['class'].items():
+ _check_shaper_hfsc_rate(cls, cls_conf)
+
+
def verify(qos):
if not qos or 'interface' not in qos:
return None
@@ -253,11 +299,13 @@ def verify(qos):
if queue_lim < max_tr:
raise ConfigError(f'Policy "{policy}" uses queue-limit "{queue_lim}" < max-threshold "{max_tr}"!')
if policy_type in ['priority_queue']:
- if 'default' not in policy_config:
- raise ConfigError(f'Policy {policy} misses "default" class!')
+ _verify_default_policy_exist(policy, policy_config)
if policy_type in ['rate_control']:
if 'bandwidth' not in policy_config:
raise ConfigError('Bandwidth not defined')
+ if policy_type in ['shaper_hfsc']:
+ _verify_shaper_hfsc(policy, policy_config)
+
if 'default' in policy_config:
if 'bandwidth' not in policy_config['default'] and policy_type not in ['priority_queue', 'round_robin', 'shaper_hfsc']:
raise ConfigError('Bandwidth not defined for default traffic!')
@@ -293,6 +341,7 @@ def generate(qos):
return None
+
def apply(qos):
# Always delete "old" shapers first
for interface in interfaces():
diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py
index e89448e2d..9c59aa63d 100755
--- a/src/conf_mode/service_dhcp-server.py
+++ b/src/conf_mode/service_dhcp-server.py
@@ -87,6 +87,10 @@ def dhcp_slice_range(exclude_list, range_dict):
'start' : range_start,
'stop' : str(ip_address(e) -1)
}
+
+ if 'option' in range_dict:
+ r['option'] = range_dict['option']
+
# On the next run our address range will start one address after
# the exclude address
range_start = str(ip_address(e) + 1)
@@ -104,6 +108,10 @@ def dhcp_slice_range(exclude_list, range_dict):
'start': str(ip_address(e) + 1),
'stop': str(range_stop)
}
+
+ if 'option' in range_dict:
+ r['option'] = range_dict['option']
+
if not (ip_address(r['start']) > ip_address(r['stop'])):
output.append(r)
else:
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index c7e3ef033..a14d4b5b6 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -31,6 +31,7 @@ from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
from vyos.accel_ppp_util import verify_accel_ppp_authentication
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
@@ -52,7 +53,9 @@ def get_config(config=None):
if dict_search('client_ip_pool', ipoe):
# Multiple named pools require ordered values T5099
- ipoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', ipoe))
+ ipoe['ordered_named_pools'] = get_pools_in_order(
+ dict_search('client_ip_pool', ipoe)
+ )
ipoe['server_type'] = 'ipoe'
return ipoe
@@ -68,11 +71,23 @@ def verify(ipoe):
for interface, iface_config in ipoe['interface'].items():
verify_interface_exists(ipoe, interface, warning_only=True)
if 'client_subnet' in iface_config and 'vlan' in iface_config:
- raise ConfigError('Option "client-subnet" and "vlan" are mutually exclusive, '
- 'use "client-ip-pool" instead!')
- if 'vlan_mon' in iface_config and not 'vlan' in iface_config:
+ raise ConfigError(
+ 'Options "client-subnet" and "vlan" are mutually exclusive, '
+ 'use "client-ip-pool" instead!'
+ )
+ if 'vlan_mon' in iface_config and 'vlan' not in iface_config:
raise ConfigError('Option "vlan-mon" requires "vlan" to be set!')
+ if 'lua_username' in iface_config:
+ if 'lua_file' not in ipoe:
+ raise ConfigError(
+ 'Option "lua-username" requires "lua-file" to be set!'
+ )
+ if dict_search('authentication.mode', ipoe) != 'radius':
+ raise ConfigError(
+ 'Can configure username with Lua script only for RADIUS authentication'
+ )
+
verify_accel_ppp_authentication(ipoe, local_users=False)
verify_accel_ppp_ip_pool(ipoe)
verify_accel_ppp_name_servers(ipoe)
@@ -88,14 +103,15 @@ def generate(ipoe):
render(ipoe_conf, 'accel-ppp/ipoe.config.j2', ipoe)
if dict_search('authentication.mode', ipoe) == 'local':
- render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2',
- ipoe, permission=0o640)
+ render(
+ ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2', ipoe, permission=0o640
+ )
return None
def apply(ipoe):
systemd_service = 'accel-ppp@ipoe.service'
- if ipoe == None:
+ if ipoe is None:
call(f'systemctl stop {systemd_service}')
for file in [ipoe_conf, ipoe_chap_secrets]:
if os.path.exists(file):
@@ -103,7 +119,10 @@ def apply(ipoe):
return None
- call(f'systemctl reload-or-restart {systemd_service}')
+ # Accel-pppd does not do soft-reload correctly.
+ # Most of the changes require restarting the service
+ call(f'systemctl restart {systemd_service}')
+
if __name__ == '__main__':
try:
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 8825cc16a..f8cc87507 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -471,6 +471,12 @@ start ()
touch /tmp/vyos.smoketest.debug
fi
+ # Cleanup PKI CAs
+ if [ -d /usr/local/share/ca-certificates/vyos ]; then
+ rm -f /usr/local/share/ca-certificates/vyos/*.crt
+ update-ca-certificates >/dev/null 2>&1
+ fi
+
log_action_begin_msg "Mounting VyOS Config"
# ensure the vyatta_configdir supports a large number of inodes since
# the config hierarchy is often inode-bound (instead of size).
diff --git a/src/migration-scripts/qos/2-to-3 b/src/migration-scripts/qos/2-to-3
new file mode 100644
index 000000000..284fe828e
--- /dev/null
+++ b/src/migration-scripts/qos/2-to-3
@@ -0,0 +1,34 @@
+# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from vyos.configtree import ConfigTree
+
+
+def migrate(config: ConfigTree) -> None:
+ base = ['qos', 'policy', 'cake']
+ if config.exists(base):
+ for policy in config.list_nodes(base):
+ if config.exists(base + [policy, 'flow-isolation']):
+ isolation = None
+ for isol in config.list_nodes(base + [policy, 'flow-isolation']):
+ if isol == 'nat':
+ config.set(base + [policy, 'flow-isolation-nat'])
+ else:
+ isolation = isol
+
+ config.delete(base + [policy, 'flow-isolation'])
+
+ if isolation:
+ config.set(base + [policy, 'flow-isolation'], value=isolation)
diff --git a/src/op_mode/show_configuration_files.sh b/src/op_mode/show_configuration_files.sh
deleted file mode 100755
index ad8e0747c..000000000
--- a/src/op_mode/show_configuration_files.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-# Wrapper script for the show configuration files command
-find ${vyatta_sysconfdir}/config/ \
- -type f \
- -not -name ".*" \
- -not -name "config.boot.*" \
- -printf "%f\t(%Tc)\t%T@\n" \
- | sort -r -k3 \
- | awk -F"\t" '{printf ("%-20s\t%s\n", $1,$2) ;}'
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index cb23642dc..d977ba2cb 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -56,6 +56,7 @@ else:
SOCKET_PATH = 'ipc:///run/vyos-configd.sock'
MAX_MSG_SIZE = 65535
+PAD_MSG_SIZE = 6
# Response error codes
R_SUCCESS = 1
@@ -256,25 +257,14 @@ def process_node_data(config, data, _last: bool = False) -> tuple[int, str]:
def send_result(sock, err, msg):
- msg_size = min(MAX_MSG_SIZE, len(msg)) if msg else 0
-
- err_rep = err.to_bytes(1, byteorder=sys.byteorder)
- logger.debug(f'Sending reply: {err}')
- sock.send(err_rep)
-
- # size req from vyshim client
- size_req = sock.recv().decode()
- logger.debug(f'Received request: {size_req}')
- msg_size_rep = hex(msg_size).encode()
- sock.send(msg_size_rep)
- logger.debug(f'Sending reply: {msg_size}')
-
- if msg_size > 0:
- # send req is sent from vyshim client only if msg_size > 0
- send_req = sock.recv().decode()
- logger.debug(f'Received request: {send_req}')
- sock.send(msg.encode())
- logger.debug('Sending reply with output')
+ msg = msg if msg else ''
+ msg_size = min(MAX_MSG_SIZE, len(msg))
+
+ err_rep = err.to_bytes(1)
+ msg_size_rep = f'{msg_size:#0{PAD_MSG_SIZE}x}'
+
+ logger.debug(f'Sending reply: error_code {err} with output')
+ sock.send_multipart([err_rep, msg_size_rep.encode(), msg.encode()])
write_stdout_log(script_stdout_log, msg)
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index 68e6c4015..1eb653cbf 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -119,21 +119,17 @@ int main(int argc, char* argv[])
zmq_send(requester, string_node_data_msg, strlen(string_node_data_msg), 0);
zmq_recv(requester, error_code, 1, 0);
- debug_print("Received node data receipt\n");
+ debug_print("Received node data receipt with error_code\n");
char msg_size_str[7];
- zmq_send(requester, "msg_size", 8, 0);
zmq_recv(requester, msg_size_str, 6, 0);
msg_size_str[6] = '\0';
int msg_size = (int)strtol(msg_size_str, NULL, 16);
debug_print("msg_size: %d\n", msg_size);
- if (msg_size > 0) {
- zmq_send(requester, "send", 4, 0);
- char *msg = s_recv_string(requester, msg_size);
- printf("%s", msg);
- free(msg);
- }
+ char *msg = s_recv_string(requester, msg_size);
+ printf("%s", msg);
+ free(msg);
free(string_node_data_msg);