summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/package-smoketest.yml3
-rw-r--r--CODEOWNERS2
-rw-r--r--Makefile16
-rw-r--r--data/config-mode-dependencies/vyos-1x.json3
-rw-r--r--data/config.boot.default2
-rw-r--r--data/op-mode-standardized.json1
-rw-r--r--data/templates/accel-ppp/chap-secrets.ipoe.j22
-rw-r--r--data/templates/container/registries.conf.j210
-rwxr-xr-xdata/templates/firewall/nftables.j26
-rw-r--r--data/templates/frr/bgpd.frr.j24
-rw-r--r--data/templates/frr/policy.frr.j23
-rw-r--r--data/templates/frr/rpki.frr.j24
-rw-r--r--data/templates/https/nginx.default.j22
-rw-r--r--data/templates/ipsec/ios_profile.j26
-rw-r--r--data/templates/lldp/vyos.conf.j211
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j212
-rw-r--r--data/templates/load-balancing/nftables-wlb.j264
-rw-r--r--data/templates/load-balancing/wlb.conf.j2134
-rw-r--r--data/templates/login/motd_vyos_nonproduction.j21
-rw-r--r--data/templates/rsyslog/override.conf.j211
-rw-r--r--data/templates/rsyslog/rsyslog.conf.j2164
-rw-r--r--data/templates/squid/squid.conf.j28
-rw-r--r--debian/control12
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--debian/vyos-1x.links1
-rw-r--r--debian/vyos-1x.preinst1
-rw-r--r--interface-definitions/container.xml.in85
-rw-r--r--interface-definitions/include/babel/redistribute-common.xml.i6
-rw-r--r--interface-definitions/include/bgp/afi-redistribute-common-protocols.xml.i62
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i84
-rw-r--r--interface-definitions/include/constraint/interface-name.xml.i2
-rw-r--r--interface-definitions/include/constraint/protocols-static-table.xml.i9
-rw-r--r--interface-definitions/include/haproxy/timeout-check.xml.i14
-rw-r--r--interface-definitions/include/haproxy/timeout-client.xml.i14
-rw-r--r--interface-definitions/include/haproxy/timeout-connect.xml.i14
-rw-r--r--interface-definitions/include/haproxy/timeout-server.xml.i14
-rw-r--r--interface-definitions/include/haproxy/timeout.xml.i39
-rw-r--r--interface-definitions/include/ip-address.xml.i14
-rw-r--r--interface-definitions/include/isis/protocol-common-config.xml.i8
-rw-r--r--interface-definitions/include/ospf/protocol-common-config.xml.i10
-rw-r--r--interface-definitions/include/policy/community-value-list.xml.i7
-rw-r--r--interface-definitions/include/version/bgp-version.xml.i2
-rw-r--r--interface-definitions/include/version/lldp-version.xml.i2
-rw-r--r--interface-definitions/include/version/policy-version.xml.i2
-rw-r--r--interface-definitions/include/version/system-version.xml.i2
-rw-r--r--interface-definitions/include/version/wanloadbalance-version.xml.i2
-rw-r--r--interface-definitions/interfaces_geneve.xml.in4
-rw-r--r--interface-definitions/load-balancing_haproxy.xml.in31
-rw-r--r--interface-definitions/policy.xml.in20
-rw-r--r--interface-definitions/protocols_rip.xml.in8
-rw-r--r--interface-definitions/protocols_rpki.xml.in1
-rw-r--r--interface-definitions/protocols_static.xml.in10
-rw-r--r--interface-definitions/service_dhcp-server.xml.in13
-rw-r--r--interface-definitions/service_ipoe-server.xml.in13
-rw-r--r--interface-definitions/service_lldp.xml.in29
-rw-r--r--interface-definitions/service_snmp.xml.in1
-rw-r--r--interface-definitions/system_syslog.xml.in119
-rw-r--r--op-mode-definitions/load-balancing_haproxy.xml.in (renamed from op-mode-definitions/load-balacing_haproxy.in)2
-rw-r--r--op-mode-definitions/load-balancing_wan.xml.in37
-rw-r--r--python/vyos/configdict.py12
-rw-r--r--python/vyos/configsession.py4
-rw-r--r--python/vyos/configtree.py4
-rw-r--r--python/vyos/defaults.py10
-rw-r--r--python/vyos/ifconfig/geneve.py2
-rw-r--r--python/vyos/ifconfig/interface.py69
-rw-r--r--python/vyos/ifconfig/wireguard.py78
-rw-r--r--python/vyos/kea.py66
-rwxr-xr-xpython/vyos/template.py18
-rw-r--r--python/vyos/utils/cpu.py15
-rw-r--r--python/vyos/utils/misc.py2
-rw-r--r--python/vyos/utils/process.py2
-rw-r--r--python/vyos/wanloadbalance.py153
-rw-r--r--smoketest/config-tests/basic-api-service4
-rw-r--r--smoketest/config-tests/basic-syslog25
-rw-r--r--smoketest/config-tests/basic-vyos18
-rw-r--r--smoketest/config-tests/basic-vyos-no-ntp4
-rw-r--r--smoketest/config-tests/bgp-azure-ipsec-gateway8
-rw-r--r--smoketest/config-tests/bgp-bfd-communities4
-rw-r--r--smoketest/config-tests/bgp-big-as-cloud4
-rw-r--r--smoketest/config-tests/bgp-dmvpn-hub4
-rw-r--r--smoketest/config-tests/bgp-dmvpn-spoke4
-rw-r--r--smoketest/config-tests/bgp-evpn-l2vpn-leaf4
-rw-r--r--smoketest/config-tests/bgp-evpn-l2vpn-spine4
-rw-r--r--smoketest/config-tests/bgp-evpn-l3vpn-pe-router4
-rw-r--r--smoketest/config-tests/bgp-medium-confederation4
-rw-r--r--smoketest/config-tests/bgp-rpki5
-rw-r--r--smoketest/config-tests/bgp-small-internet-exchange4
-rw-r--r--smoketest/config-tests/bgp-small-ipv4-unicast4
-rw-r--r--smoketest/config-tests/cluster-basic4
-rw-r--r--smoketest/config-tests/dialup-router-complex7
-rw-r--r--smoketest/config-tests/dialup-router-medium-vpn6
-rw-r--r--smoketest/config-tests/dialup-router-wireguard-ipv66
-rw-r--r--smoketest/config-tests/egp-igp-route-maps2
-rw-r--r--smoketest/config-tests/igmp-pim-small4
-rw-r--r--smoketest/config-tests/ipoe-server4
-rw-r--r--smoketest/config-tests/ipv6-disable4
-rw-r--r--smoketest/config-tests/isis-small4
-rw-r--r--smoketest/config-tests/nat-basic6
-rw-r--r--smoketest/config-tests/ospf-simple4
-rw-r--r--smoketest/config-tests/ospf-small4
-rw-r--r--smoketest/config-tests/pppoe-server4
-rw-r--r--smoketest/config-tests/qos-basic4
-rw-r--r--smoketest/config-tests/rip-router4
-rw-r--r--smoketest/config-tests/rpki-only4
-rw-r--r--smoketest/config-tests/static-route-basic4
-rw-r--r--smoketest/config-tests/tunnel-broker4
-rw-r--r--smoketest/config-tests/vpn-openconnect-sstp4
-rw-r--r--smoketest/config-tests/vrf-basic4
-rw-r--r--smoketest/config-tests/vrf-bgp-pppoe-underlay4
-rw-r--r--smoketest/config-tests/vrf-ospf4
-rw-r--r--smoketest/config-tests/wireless-basic4
-rw-r--r--smoketest/configs/basic-syslog70
-rw-r--r--smoketest/configs/basic-vyos4
-rw-r--r--smoketest/configs/bgp-rpki7
-rw-r--r--smoketest/configs/dialup-router-complex3
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py47
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py16
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py17
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bonding.py13
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py15
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vxlan.py27
-rwxr-xr-xsmoketest/scripts/cli/test_load-balancing_haproxy.py48
-rwxr-xr-xsmoketest/scripts/cli/test_load-balancing_wan.py156
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py15
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_babel.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py206
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py9
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_nhrp.py3
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rip.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rpki.py36
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py928
-rwxr-xr-xsmoketest/scripts/cli/test_service_ipoe-server.py26
-rwxr-xr-xsmoketest/scripts/cli/test_service_lldp.py49
-rwxr-xr-xsmoketest/scripts/cli/test_service_webproxy.py18
-rwxr-xr-xsmoketest/scripts/cli/test_system_syslog.py287
-rwxr-xr-xsrc/conf_mode/container.py33
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py28
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py5
-rwxr-xr-xsrc/conf_mode/interfaces_geneve.py2
-rwxr-xr-xsrc/conf_mode/interfaces_vxlan.py2
-rwxr-xr-xsrc/conf_mode/interfaces_wireguard.py23
-rwxr-xr-xsrc/conf_mode/load-balancing_wan.py119
-rwxr-xr-xsrc/conf_mode/service_snmp.py8
-rwxr-xr-xsrc/conf_mode/system_host-name.py9
-rwxr-xr-xsrc/conf_mode/system_option.py2
-rwxr-xr-xsrc/conf_mode/system_sflow.py2
-rwxr-xr-xsrc/conf_mode/system_syslog.py86
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py27
-rwxr-xr-xsrc/etc/netplug/vyos-netplug-dhcp-client43
-rwxr-xr-xsrc/etc/ppp/ip-up.d/99-vyos-pppoe-wlb61
-rw-r--r--src/etc/rsyslog.conf67
-rw-r--r--src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf2
-rw-r--r--src/etc/systemd/system/rsyslog.service.d/override.conf10
-rwxr-xr-xsrc/helpers/vyos-load-balancer.py312
-rwxr-xr-xsrc/init/vyos-router1
-rw-r--r--src/migration-scripts/bgp/5-to-639
-rw-r--r--src/migration-scripts/lldp/2-to-331
-rw-r--r--src/migration-scripts/policy/8-to-949
-rw-r--r--src/migration-scripts/system/28-to-2971
-rw-r--r--src/migration-scripts/wanloadbalance/3-to-433
-rwxr-xr-xsrc/op_mode/dhcp.py18
-rwxr-xr-xsrc/op_mode/image_installer.py70
-rwxr-xr-xsrc/op_mode/load-balancing_wan.py117
-rwxr-xr-xsrc/op_mode/restart.py5
-rw-r--r--src/services/api/rest/models.py7
-rwxr-xr-xsrc/services/vyos-configd44
-rwxr-xr-xsrc/services/vyos-domain-resolver77
-rwxr-xr-xsrc/services/vyos-http-api-server46
-rwxr-xr-xsrc/system/sync-dhcp-lease-to-hosts.py112
-rw-r--r--src/systemd/vyos-wan-load-balance.service12
-rw-r--r--src/validators/ethernet-interface13
172 files changed, 4066 insertions, 1334 deletions
diff --git a/.github/workflows/package-smoketest.yml b/.github/workflows/package-smoketest.yml
index ae34ea2f0..6ab04ac9d 100644
--- a/.github/workflows/package-smoketest.yml
+++ b/.github/workflows/package-smoketest.yml
@@ -24,6 +24,7 @@ jobs:
build_iso:
runs-on: ubuntu-24.04
timeout-minutes: 45
+ if: github.repository == 'vyos/vyos-1x'
container:
image: vyos/vyos-build:current
options: --sysctl net.ipv6.conf.lo.disable_ipv6=0 --privileged
@@ -43,6 +44,7 @@ jobs:
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Build vyos-1x package
run: |
+ eval $(opam env --root=/opt/opam --set-root)
cd packages/vyos-1x; dpkg-buildpackage -uc -us -tc -b
- name: Generate ISO version string
id: version
@@ -63,6 +65,7 @@ jobs:
generic
- uses: actions/upload-artifact@v4
with:
+ retention-days: 2
name: vyos-${{ steps.version.outputs.build_version }}
path: |
build/live-image-amd64.hybrid.iso
diff --git a/CODEOWNERS b/CODEOWNERS
index 4891a0325..72ddbde91 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,2 +1,2 @@
# Users from reviewers github team
-* @dmbaturin @sarthurdev @jestabro @sever-sever @c-po @fett0 @nicolas-fort @zdc
+* @vyos/reviewers
diff --git a/Makefile b/Makefile
index b5d114e59..3f3ba5ba7 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,7 @@ CFLAGS :=
BUILD_ARCH := $(shell dpkg-architecture -q DEB_BUILD_ARCH)
J2LINT := $(shell command -v j2lint 2> /dev/null)
PYLINT_FILES := $(shell git ls-files *.py src/migration-scripts)
+LIBVYOSCONFIG_BUILD_PATH := /tmp/libvyosconfig/_build/libvyosconfig.so
config_xml_src = $(wildcard interface-definitions/*.xml.in)
config_xml_obj = $(config_xml_src:.xml.in=.xml)
@@ -19,9 +20,20 @@ op_xml_obj = $(op_xml_src:.xml.in=.xml)
mkdir -p $(BUILD_DIR)/$(dir $@)
$(CURDIR)/scripts/transclude-template $< > $(BUILD_DIR)/$@
+.PHONY: libvyosconfig
+.ONESHELL:
+libvyosconfig:
+ if ! [ -f $(LIBVYOSCONFIG_BUILD_PATH) ]; then
+ rm -rf /tmp/libvyosconfig && \
+ git clone https://github.com/vyos/libvyosconfig.git /tmp/libvyosconfig || exit 1
+ cd /tmp/libvyosconfig && \
+ git checkout 677d1e2bf8109b9fd4da60e20376f992b747e384 || exit 1
+ ./build.sh
+ fi
+
.PHONY: interface_definitions
.ONESHELL:
-interface_definitions: $(config_xml_obj)
+interface_definitions: $(config_xml_obj) libvyosconfig
mkdir -p $(TMPL_DIR)
$(CURDIR)/scripts/override-default $(BUILD_DIR)/interface-definitions
@@ -75,7 +87,7 @@ vyshim:
$(MAKE) -C $(SHIM_DIR)
.PHONY: all
-all: clean interface_definitions op_mode_definitions test j2lint vyshim generate-configd-include-json
+all: clean libvyosconfig interface_definitions op_mode_definitions test j2lint vyshim generate-configd-include-json
.PHONY: clean
clean:
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index cbd14f7c6..7506a0908 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -14,6 +14,9 @@
"vxlan": ["interfaces_vxlan"],
"wlan": ["interfaces_wireless"]
},
+ "interfaces_wireguard": {
+ "vxlan": ["interfaces_vxlan"]
+ },
"load_balancing_wan": {
"conntrack": ["system_conntrack"]
},
diff --git a/data/config.boot.default b/data/config.boot.default
index 93369d9b7..db5d11ea1 100644
--- a/data/config.boot.default
+++ b/data/config.boot.default
@@ -41,7 +41,7 @@ system {
}
}
syslog {
- global {
+ local {
facility all {
level "info"
}
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index 170f0d259..c2bfc3094 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -13,6 +13,7 @@
"evpn.py",
"interfaces.py",
"ipsec.py",
+"load-balancing_wan.py",
"lldp.py",
"log.py",
"memory.py",
diff --git a/data/templates/accel-ppp/chap-secrets.ipoe.j2 b/data/templates/accel-ppp/chap-secrets.ipoe.j2
index dd85160c0..59b9dfc8d 100644
--- a/data/templates/accel-ppp/chap-secrets.ipoe.j2
+++ b/data/templates/accel-ppp/chap-secrets.ipoe.j2
@@ -6,7 +6,7 @@
{% if mac_config.vlan is vyos_defined %}
{% set iface = iface ~ '.' ~ mac_config.vlan %}
{% endif %}
-{{ "%-11s" | format(iface) }} * {{ mac | lower }} {{ mac_config.static_ip if mac_config.static_ip is vyos_defined else '*' }} {{ mac_config.rate_limit.download ~ '/' ~ mac_config.rate_limit.upload if mac_config.rate_limit.download is vyos_defined and mac_config.rate_limit.upload is vyos_defined }}
+{{ "%-11s" | format(iface) }} * {{ mac | lower }} {{ mac_config.ip_address if mac_config.ip_address is vyos_defined else '*' }} {{ mac_config.rate_limit.download ~ '/' ~ mac_config.rate_limit.upload if mac_config.rate_limit.download is vyos_defined and mac_config.rate_limit.upload is vyos_defined }}
{% endfor %}
{% endif %}
{% endfor %}
diff --git a/data/templates/container/registries.conf.j2 b/data/templates/container/registries.conf.j2
index eb7ff8775..b5c7eed9b 100644
--- a/data/templates/container/registries.conf.j2
+++ b/data/templates/container/registries.conf.j2
@@ -28,4 +28,14 @@
{% set _ = registry_list.append(r) %}
{% endfor %}
unqualified-search-registries = {{ registry_list }}
+{% for r, r_options in registry.items() if r_options.disable is not vyos_defined %}
+[[registry]]
+{% if r_options.mirror is vyos_defined %}
+location = "{{ r_options.mirror.host_name if r_options.mirror.host_name is vyos_defined else r_options.mirror.address }}{{ ":" + r_options.mirror.port if r_options.mirror.port is vyos_defined }}{{ r_options.mirror.path if r_options.mirror.path is vyos_defined }}"
+{% else %}
+location = "{{ r }}"
+{% endif %}
+insecure = {{ 'true' if r_options.insecure is vyos_defined else 'false' }}
+prefix = "{{ r }}"
+{% endfor %}
{% endif %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index a35143870..67473da8e 100755
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -435,13 +435,13 @@ table bridge vyos_filter {
{% if global_options.state_policy is vyos_defined %}
chain VYOS_STATE_POLICY {
{% if global_options.state_policy.established is vyos_defined %}
- {{ global_options.state_policy.established | nft_state_policy('established') }}
+ {{ global_options.state_policy.established | nft_state_policy('established', bridge=True) }}
{% endif %}
{% if global_options.state_policy.invalid is vyos_defined %}
- {{ global_options.state_policy.invalid | nft_state_policy('invalid') }}
+ {{ global_options.state_policy.invalid | nft_state_policy('invalid', bridge=True) }}
{% endif %}
{% if global_options.state_policy.related is vyos_defined %}
- {{ global_options.state_policy.related | nft_state_policy('related') }}
+ {{ global_options.state_policy.related | nft_state_policy('related', bridge=True) }}
{% endif %}
return
}
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 51a3f2564..3b462b4a9 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -310,7 +310,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if afi_config.redistribute is vyos_defined %}
{% for protocol, protocol_config in afi_config.redistribute.items() %}
{% if protocol == 'table' %}
- redistribute table {{ protocol_config.table }}
+{% for table, table_config in protocol_config.items() %}
+ redistribute table-direct {{ table }} {{ 'metric ' ~ table_config.metric if table_config.metric is vyos_defined }} {{ 'route-map ' ~ table_config.route_map if table_config.route_map is vyos_defined }}
+{% endfor %}
{% else %}
{% set redistribution_protocol = protocol %}
{% if protocol == 'ospfv3' %}
diff --git a/data/templates/frr/policy.frr.j2 b/data/templates/frr/policy.frr.j2
index ed5876ae9..c28633f6f 100644
--- a/data/templates/frr/policy.frr.j2
+++ b/data/templates/frr/policy.frr.j2
@@ -252,6 +252,9 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
{% if rule_config.match.rpki is vyos_defined %}
match rpki {{ rule_config.match.rpki }}
{% endif %}
+{% if rule_config.match.source_vrf is vyos_defined %}
+ match source-vrf {{ rule_config.match.source_vrf }}
+{% endif %}
{% if rule_config.match.tag is vyos_defined %}
match tag {{ rule_config.match.tag }}
{% endif %}
diff --git a/data/templates/frr/rpki.frr.j2 b/data/templates/frr/rpki.frr.j2
index 59d5bf0ac..edf0ccaa2 100644
--- a/data/templates/frr/rpki.frr.j2
+++ b/data/templates/frr/rpki.frr.j2
@@ -5,9 +5,9 @@ rpki
{% for peer, peer_config in cache.items() %}
{# port is mandatory and preference uses a default value #}
{% if peer_config.ssh.username is vyos_defined %}
- rpki cache ssh {{ peer | replace('_', '-') }} {{ peer_config.port }} {{ peer_config.ssh.username }} {{ peer_config.ssh.private_key_file }} {{ peer_config.ssh.public_key_file }} preference {{ peer_config.preference }}
+ rpki cache ssh {{ peer | replace('_', '-') }} {{ peer_config.port }} {{ peer_config.ssh.username }} {{ peer_config.ssh.private_key_file }} {{ peer_config.ssh.public_key_file }}{{ ' source ' ~ peer_config.source_address if peer_config.source_address is vyos_defined }} preference {{ peer_config.preference }}
{% else %}
- rpki cache tcp {{ peer | replace('_', '-') }} {{ peer_config.port }} preference {{ peer_config.preference }}
+ rpki cache tcp {{ peer | replace('_', '-') }} {{ peer_config.port }}{{ ' source ' ~ peer_config.source_address if peer_config.source_address is vyos_defined }} preference {{ peer_config.preference }}
{% endif %}
{% endfor %}
{% endif %}
diff --git a/data/templates/https/nginx.default.j2 b/data/templates/https/nginx.default.j2
index 51da46946..692ccbff7 100644
--- a/data/templates/https/nginx.default.j2
+++ b/data/templates/https/nginx.default.j2
@@ -48,7 +48,7 @@ server {
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
# proxy settings for HTTP API, if enabled; 503, if not
- location ~ ^/(retrieve|configure|config-file|image|import-pki|container-image|generate|show|reboot|reset|poweroff|traceroute|docs|openapi.json|redoc|graphql) {
+ location ~ ^/(retrieve|configure|config-file|image|import-pki|container-image|generate|show|reboot|reset|poweroff|traceroute|info|docs|openapi.json|redoc|graphql) {
{% if api is vyos_defined %}
proxy_pass http://unix:/run/api.sock;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/data/templates/ipsec/ios_profile.j2 b/data/templates/ipsec/ios_profile.j2
index 966fad433..6993f82bf 100644
--- a/data/templates/ipsec/ios_profile.j2
+++ b/data/templates/ipsec/ios_profile.j2
@@ -55,11 +55,9 @@
<!-- The server is authenticated using a certificate -->
<key>AuthenticationMethod</key>
<string>Certificate</string>
-{% if authentication.client_mode.startswith("eap") %}
<!-- The client uses EAP to authenticate -->
<key>ExtendedAuthEnabled</key>
<integer>1</integer>
-{% endif %}
<!-- The next two dictionaries are optional (as are the keys in them), but it is recommended to specify them as the default is to use 3DES.
IMPORTANT: Because only one proposal is sent (even if nothing is configured here) it must match the server configuration -->
<key>IKESecurityAssociationParameters</key>
@@ -80,9 +78,9 @@
<string>{{ esp_encryption.encryption }}</string>
<key>IntegrityAlgorithm</key>
<string>{{ esp_encryption.hash }}</string>
-{% if esp_encryption.pfs is vyos_defined %}
+{% if ike_encryption.dh_group is vyos_defined %}
<key>DiffieHellmanGroup</key>
- <integer>{{ esp_encryption.pfs }}</integer>
+ <integer>{{ ike_encryption.dh_group }}</integer>
{% endif %}
</dict>
<!-- Controls whether the client offers Perfect Forward Secrecy (PFS). This should be set to match the server. -->
diff --git a/data/templates/lldp/vyos.conf.j2 b/data/templates/lldp/vyos.conf.j2
index 4b4228cea..432a7a8e6 100644
--- a/data/templates/lldp/vyos.conf.j2
+++ b/data/templates/lldp/vyos.conf.j2
@@ -4,7 +4,7 @@ configure system platform VyOS
configure system description "VyOS {{ version }}"
{% if interface is vyos_defined %}
{% set tmp = [] %}
-{% for iface, iface_options in interface.items() if iface_options.disable is not vyos_defined %}
+{% for iface, iface_options in interface.items() %}
{% if iface == 'all' %}
{% set iface = '*' %}
{% endif %}
@@ -17,6 +17,15 @@ configure ports {{ iface }} med location elin "{{ iface_options.location.elin }}
configure ports {{ iface }} med location coordinate latitude "{{ iface_options.location.coordinate_based.latitude }}" longitude "{{ iface_options.location.coordinate_based.longitude }}" altitude "{{ iface_options.location.coordinate_based.altitude }}m" datum "{{ iface_options.location.coordinate_based.datum }}"
{% endif %}
{% endif %}
+{% set mode = iface_options.mode %}
+{% if mode == 'tx' %}
+{% set mode = 'tx-only' %}
+{% elif mode == 'rx' %}
+{% set mode = 'rx-only' %}
+{% elif mode == 'rx-tx' %}
+{% set mode = 'rx-and-tx' %}
+{% endif %}
+configure ports {{ iface }} lldp status {{ mode }}
{% endfor %}
configure system interface pattern "{{ tmp | join(",") }}"
{% endif %}
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index c98b739e2..70ea5d2b0 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -38,9 +38,10 @@ defaults
log global
mode http
option dontlognull
- timeout connect 10s
- timeout client 50s
- timeout server 50s
+ timeout check {{ timeout.check }}s
+ timeout connect {{ timeout.connect }}s
+ timeout client {{ timeout.client }}s
+ timeout server {{ timeout.server }}s
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
@@ -134,6 +135,11 @@ frontend {{ front }}
default_backend {{ backend }}
{% endfor %}
{% endif %}
+{% if front_config.timeout is vyos_defined %}
+{% if front_config.timeout.client is vyos_defined %}
+ timeout client {{ front_config.timeout.client }}s
+{% endif %}
+{% endif %}
{% endfor %}
{% endif %}
diff --git a/data/templates/load-balancing/nftables-wlb.j2 b/data/templates/load-balancing/nftables-wlb.j2
new file mode 100644
index 000000000..b3d7c3376
--- /dev/null
+++ b/data/templates/load-balancing/nftables-wlb.j2
@@ -0,0 +1,64 @@
+#!/usr/sbin/nft -f
+
+{% if first_install is not vyos_defined %}
+delete table ip vyos_wanloadbalance
+{% endif %}
+table ip vyos_wanloadbalance {
+ chain wlb_nat_postrouting {
+ type nat hook postrouting priority srcnat - 1; policy accept;
+{% for ifname, health_conf in interface_health.items() if health_state[ifname].if_addr %}
+{% if disable_source_nat is not vyos_defined %}
+{% set state = health_state[ifname] %}
+ ct mark {{ state.mark }} counter snat to {{ state.if_addr }}
+{% endif %}
+{% endfor %}
+ }
+
+ chain wlb_mangle_prerouting {
+ type filter hook prerouting priority mangle; policy accept;
+{% for ifname, health_conf in interface_health.items() %}
+{% set state = health_state[ifname] %}
+{% if sticky_connections is vyos_defined %}
+ iifname "{{ ifname }}" ct state new ct mark set {{ state.mark }}
+{% endif %}
+{% endfor %}
+{% if rule is vyos_defined %}
+{% for rule_id, rule_conf in rule.items() %}
+{% if rule_conf.exclude is vyos_defined %}
+ {{ rule_conf | wlb_nft_rule(rule_id, exclude=True, action='return') }}
+{% else %}
+{% set limit = rule_conf.limit is vyos_defined %}
+ {{ rule_conf | wlb_nft_rule(rule_id, limit=limit, weight=True, health_state=health_state) }}
+ {{ rule_conf | wlb_nft_rule(rule_id, restore_mark=True) }}
+{% endif %}
+{% endfor %}
+{% endif %}
+ }
+
+ chain wlb_mangle_output {
+ type filter hook output priority -150; policy accept;
+{% if enable_local_traffic is vyos_defined %}
+ meta mark != 0x0 counter return
+ meta l4proto icmp counter return
+ ip saddr 127.0.0.0/8 ip daddr 127.0.0.0/8 counter return
+{% if rule is vyos_defined %}
+{% for rule_id, rule_conf in rule.items() %}
+{% if rule_conf.exclude is vyos_defined %}
+ {{ rule_conf | wlb_nft_rule(rule_id, local=True, exclude=True, action='return') }}
+{% else %}
+{% set limit = rule_conf.limit is vyos_defined %}
+ {{ rule_conf | wlb_nft_rule(rule_id, local=True, limit=limit, weight=True, health_state=health_state) }}
+ {{ rule_conf | wlb_nft_rule(rule_id, local=True, restore_mark=True) }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endif %}
+ }
+
+{% for ifname, health_conf in interface_health.items() %}
+{% set state = health_state[ifname] %}
+ chain wlb_mangle_isp_{{ ifname }} {
+ meta mark set {{ state.mark }} ct mark set {{ state.mark }} counter accept
+ }
+{% endfor %}
+}
diff --git a/data/templates/load-balancing/wlb.conf.j2 b/data/templates/load-balancing/wlb.conf.j2
deleted file mode 100644
index 7f04d797e..000000000
--- a/data/templates/load-balancing/wlb.conf.j2
+++ /dev/null
@@ -1,134 +0,0 @@
-### Autogenerated by load-balancing_wan.py ###
-
-{% if disable_source_nat is vyos_defined %}
-disable-source-nat
-{% endif %}
-{% if enable_local_traffic is vyos_defined %}
-enable-local-traffic
-{% endif %}
-{% if sticky_connections is vyos_defined %}
-sticky-connections inbound
-{% endif %}
-{% if flush_connections is vyos_defined %}
-flush-conntrack
-{% endif %}
-{% if hook is vyos_defined %}
-hook "{{ hook }}"
-{% endif %}
-{% if interface_health is vyos_defined %}
-health {
-{% for interface, interface_config in interface_health.items() %}
- interface {{ interface }} {
-{% if interface_config.failure_count is vyos_defined %}
- failure-ct {{ interface_config.failure_count }}
-{% endif %}
-{% if interface_config.success_count is vyos_defined %}
- success-ct {{ interface_config.success_count }}
-{% endif %}
-{% if interface_config.nexthop is vyos_defined %}
- nexthop {{ interface_config.nexthop }}
-{% endif %}
-{% if interface_config.test is vyos_defined %}
-{% for test_rule, test_config in interface_config.test.items() %}
- rule {{ test_rule }} {
-{% if test_config.type is vyos_defined %}
-{% set type_translate = {'ping': 'ping', 'ttl': 'udp', 'user-defined': 'user-defined'} %}
- type {{ type_translate[test_config.type] }} {
-{% if test_config.ttl_limit is vyos_defined and test_config.type == 'ttl' %}
- ttl {{ test_config.ttl_limit }}
-{% endif %}
-{% if test_config.test_script is vyos_defined and test_config.type == 'user-defined' %}
- test-script {{ test_config.test_script }}
-{% endif %}
-{% if test_config.target is vyos_defined %}
- target {{ test_config.target }}
-{% endif %}
- resp-time {{ test_config.resp_time | int * 1000 }}
- }
-{% endif %}
- }
-{% endfor %}
-{% endif %}
- }
-{% endfor %}
-}
-{% endif %}
-
-{% if rule is vyos_defined %}
-{% for rule, rule_config in rule.items() %}
-rule {{ rule }} {
-{% if rule_config.exclude is vyos_defined %}
- exclude
-{% endif %}
-{% if rule_config.failover is vyos_defined %}
- failover
-{% endif %}
-{% if rule_config.limit is vyos_defined %}
- limit {
-{% if rule_config.limit.burst is vyos_defined %}
- burst {{ rule_config.limit.burst }}
-{% endif %}
-{% if rule_config.limit.rate is vyos_defined %}
- rate {{ rule_config.limit.rate }}
-{% endif %}
-{% if rule_config.limit.period is vyos_defined %}
- period {{ rule_config.limit.period }}
-{% endif %}
-{% if rule_config.limit.threshold is vyos_defined %}
- thresh {{ rule_config.limit.threshold }}
-{% endif %}
- }
-{% endif %}
-{% if rule_config.per_packet_balancing is vyos_defined %}
- per-packet-balancing
-{% endif %}
-{% if rule_config.protocol is vyos_defined %}
- protocol {{ rule_config.protocol }}
-{% endif %}
-{% if rule_config.destination is vyos_defined %}
- destination {
-{% if rule_config.destination.address is vyos_defined %}
- address "{{ rule_config.destination.address }}"
-{% endif %}
-{% if rule_config.destination.port is vyos_defined %}
-{% if '-' in rule_config.destination.port %}
- port-ipt "-m multiport --dports {{ rule_config.destination.port | replace('-', ':') }}"
-{% elif ',' in rule_config.destination.port %}
- port-ipt "-m multiport --dports {{ rule_config.destination.port }}"
-{% else %}
- port-ipt " --dport {{ rule_config.destination.port }}"
-{% endif %}
-{% endif %}
- }
-{% endif %}
-{% if rule_config.source is vyos_defined %}
- source {
-{% if rule_config.source.address is vyos_defined %}
- address "{{ rule_config.source.address }}"
-{% endif %}
-{% if rule_config.source.port is vyos_defined %}
-{% if '-' in rule_config.source.port %}
- port-ipt "-m multiport --sports {{ rule_config.source.port | replace('-', ':') }}"
-{% elif ',' in rule_config.destination.port %}
- port-ipt "-m multiport --sports {{ rule_config.source.port }}"
-{% else %}
- port.ipt " --sport {{ rule_config.source.port }}"
-{% endif %}
-{% endif %}
- }
-{% endif %}
-{% if rule_config.inbound_interface is vyos_defined %}
- inbound-interface {{ rule_config.inbound_interface }}
-{% endif %}
-{% if rule_config.interface is vyos_defined %}
-{% for interface, interface_config in rule_config.interface.items() %}
- interface {{ interface }} {
-{% if interface_config.weight is vyos_defined %}
- weight {{ interface_config.weight }}
-{% endif %}
- }
-{% endfor %}
-{% endif %}
-}
-{% endfor %}
-{% endif %}
diff --git a/data/templates/login/motd_vyos_nonproduction.j2 b/data/templates/login/motd_vyos_nonproduction.j2
index 3f10423ff..4b81acc5b 100644
--- a/data/templates/login/motd_vyos_nonproduction.j2
+++ b/data/templates/login/motd_vyos_nonproduction.j2
@@ -2,3 +2,4 @@
---
WARNING: This VyOS system is not a stable long-term support version and
is not intended for production use.
+
diff --git a/data/templates/rsyslog/override.conf.j2 b/data/templates/rsyslog/override.conf.j2
deleted file mode 100644
index 5f6a87edf..000000000
--- a/data/templates/rsyslog/override.conf.j2
+++ /dev/null
@@ -1,11 +0,0 @@
-{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %}
-[Unit]
-StartLimitIntervalSec=0
-
-[Service]
-ExecStart=
-ExecStart={{ vrf_command }}/usr/sbin/rsyslogd -n -iNONE
-Restart=always
-RestartPreventExitStatus=
-RestartSec=10
-RuntimeDirectoryPreserve=yes
diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2
index 253a4bee2..68e34f3f8 100644
--- a/data/templates/rsyslog/rsyslog.conf.j2
+++ b/data/templates/rsyslog/rsyslog.conf.j2
@@ -1,78 +1,122 @@
### Autogenerated by system_syslog.py ###
-{% if global.marker is vyos_defined %}
-$ModLoad immark
-{% if global.marker.interval is vyos_defined %}
-$MarkMessagePeriod {{ global.marker.interval }}
-{% endif %}
-{% endif %}
-{% if global.preserve_fqdn is vyos_defined %}
-$PreserveFQDN on
-{% endif %}
+#### MODULES ####
+# Load input modules for local logging and kernel logging
-{% if global.local_host_name is vyos_defined %}
-$LocalHostName {{ global.local_host_name }}
-{% endif %}
+# Old-style log file format with low-precision timestamps
+# A modern-style logfile format with high-precision timestamps and timezone info
+# RSYSLOG_FileFormat
+module(load="builtin:omfile" Template="RSYSLOG_TraditionalFileFormat")
+module(load="imuxsock") # provides support for local system logging
+module(load="imklog") # provides kernel logging support
-# We always log to /var/log/messages
-$outchannel global,/var/log/messages,262144,/usr/sbin/logrotate {{ logrotate }}
-{% if global.facility is vyos_defined %}
-{% set tmp = [] %}
-{% for facility, facility_options in global.facility.items() %}
-{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level.replace('all', '*')) %}
-{% endfor %}
-{{ tmp | join(';') }} :omfile:$global
+# Import logs from journald
+module(
+ load="imjournal"
+ StateFile="/var/spool/rsyslog/imjournal.state" # Persistent state file to track the journal cursor
+ Ratelimit.Interval="0" # Disable rate limiting (set to "0" for no limit)
+ RateLimit.Burst="0"
+)
+
+###########################
+#### GLOBAL DIRECTIVES ####
+###########################
+# Log specific programs to auth.log, then stop further processing
+if (
+ $programname == "CRON" or
+ $programname == "sudo" or
+ $programname == "su"
+) then {
+ action(type="omfile" file="/var/log/auth.log")
+ stop
+}
+
+global(workDirectory="/var/spool/rsyslog")
+
+###############
+#### RULES ####
+###############
+
+# Send emergency messages to all logged-in users
+*.emerg action(type="omusrmsg" users="*")
+
+{% if marker is vyos_defined and marker.disable is not vyos_defined %}
+# Load the immark module for periodic --MARK-- message capability
+module(load="immark" interval="{{ marker.interval }}")
+{% endif %}
+{% if preserve_fqdn is vyos_defined %}
+# Preserve the fully qualified domain name (FQDN) in log messages
+global(preserveFQDN="on")
+{% if preserve_fqdn.host_name is vyos_defined and preserve_fqdn.domain_name is vyos_defined %}
+# Set the local hostname for log messages
+global(localHostname="{{ preserve_fqdn.host_name }}.{{ preserve_fqdn.domain_name }}")
+{% endif %}
{% endif %}
-{% if file is vyos_defined %}
-# File based configuration section
-{% for file_name, file_options in file.items() %}
-{% set tmp = [] %}
-$outchannel {{ file_name }},/var/log/user/{{ file_name }},{{ file_options.archive.size }},/usr/sbin/logrotate {{ logrotate }}
-{% if file_options.facility is vyos_defined %}
-{% for facility, facility_options in file_options.facility.items() %}
-{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level.replace('all', '*')) %}
-{% endfor %}
-{% endif %}
-{{ tmp | join(';') }} :omfile:${{ file }}
-{% endfor %}
+#### GLOBAL LOGGING ####
+{% if local.facility is vyos_defined %}
+{% set tmp = [] %}
+{% if local.facility is vyos_defined %}
+{% for facility, facility_options in local.facility.items() %}
+{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %}
+{% endfor %}
+if prifilt("{{ tmp | join(',') }}") then {
+ action(
+ type="omfile"
+ file="/var/log/messages"
+ rotation.sizeLimit="524288" # 512Kib - maximum filesize before rotation
+ rotation.sizeLimitCommand="/usr/sbin/logrotate {{ logrotate }}"
+ )
+}
+{% endif %}
{% endif %}
+#### CONSOLE LOGGING ####
{% if console.facility is vyos_defined %}
-# Console logging
{% set tmp = [] %}
-{% for facility, facility_options in console.facility.items() %}
-{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level.replace('all', '*')) %}
-{% endfor %}
-{{ tmp | join(';') }} /dev/console
+{% if console.facility is vyos_defined %}
+{% for facility, facility_options in console.facility.items() %}
+{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %}
+{% endfor %}
+if prifilt("{{ tmp | join(',') }}") then {
+ action(type="omfile" file="/dev/console")
+}
+{% endif %}
{% endif %}
-{% if host is vyos_defined %}
-# Remote logging
-{% for host_name, host_options in host.items() %}
+#### REMOTE LOGGING ####
+{% if remote is vyos_defined %}
+{% for remote_name, remote_options in remote.items() %}
{% set tmp = [] %}
-{% if host_options.facility is vyos_defined %}
-{% for facility, facility_options in host_options.facility.items() %}
-{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level.replace('all', '*')) %}
+{% if remote_options.facility is vyos_defined %}
+{% for facility, facility_options in remote_options.facility.items() %}
+{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %}
{% endfor %}
-{% endif %}
-{% if host_options.protocol is vyos_defined('tcp') %}
-{{ tmp | join(';') }} @@{{ '(o)' if host_options.format.octet_counted is vyos_defined }}{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if host_options.format.include_timezone is vyos_defined }}
-{% else %}
-{{ tmp | join(';') }} @{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if host_options.format.include_timezone is vyos_defined }}
+{% set _ = tmp.sort() %}
+# Remote syslog to {{ remote_name }}
+if prifilt("{{ tmp | join(',') }}") then {
+ action(
+ type="omfwd"
+ # Remote syslog server where we send our logs to
+ target="{{ remote_name }}"
+ # Port on the remote syslog server
+ port="{{ remote_options.port }}"
+ protocol="{{ remote_options.protocol }}"
+{% if remote_options.format.include_timezone is vyos_defined %}
+ template="SyslogProtocol23Format"
+{% endif %}
+ TCP_Framing="{{ 'octed-counted' if remote_options.format.octet_counted is vyos_defined else 'traditional' }}"
+{% if remote_options.source_address is vyos_defined %}
+ Address="{{ remote_options.source_address }}"
+{% endif %}
+{% if remote_options.vrf is vyos_defined %}
+ Device="{{ remote_options.vrf }}"
+{% endif %}
+ )
+}
{% endif %}
{% endfor %}
{% endif %}
-{% if user is defined and user is not none %}
-# Log to user terminal
-{% for username, user_options in user.items() %}
-{% set tmp = [] %}
-{% if user_options.facility is vyos_defined %}
-{% for facility, facility_options in user_options.facility.items() %}
-{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level.replace('all', '*')) %}
-{% endfor %}
-{% endif %}
-{{ tmp | join(';') }} :omusrmsg:{{ username }}
-{% endfor %}
-{% endif %}
+# Include all configuration files in /etc/rsyslog.d/
+include(file="/etc/rsyslog.d/*.conf")
diff --git a/data/templates/squid/squid.conf.j2 b/data/templates/squid/squid.conf.j2
index b953c8b18..4e3d702a8 100644
--- a/data/templates/squid/squid.conf.j2
+++ b/data/templates/squid/squid.conf.j2
@@ -30,6 +30,14 @@ acl BLOCKDOMAIN dstdomain {{ domain }}
{% endfor %}
http_access deny BLOCKDOMAIN
{% endif %}
+
+{% if domain_noncache is vyos_defined %}
+{% for domain in domain_noncache %}
+acl NOCACHE dstdomain {{ domain }}
+{% endfor %}
+no_cache deny NOCACHE
+{% endif %}
+
{% if authentication is vyos_defined %}
{% if authentication.children is vyos_defined %}
auth_param basic children {{ authentication.children }}
diff --git a/debian/control b/debian/control
index 76fe5c331..efc008af2 100644
--- a/debian/control
+++ b/debian/control
@@ -8,7 +8,6 @@ Build-Depends:
fakeroot,
gcc,
iproute2,
- libvyosconfig0 (>= 0.0.7),
libzmq3-dev,
python3 (>= 3.10),
# For QA
@@ -203,9 +202,6 @@ Depends:
# For "load-balancing haproxy"
haproxy,
# End "load-balancing haproxy"
-# For "load-balancing wan"
- vyatta-wanloadbalance,
-# End "load-balancing wan"
# For "service dhcp-relay"
isc-dhcp-relay,
# For "service dhcp-server"
@@ -325,6 +321,14 @@ Depends:
# iptables is only used for containers now, not the the firewall CLI
iptables,
# End container
+# For "vpp"
+ libvppinfra,
+ python3-vpp-api,
+ vpp,
+ vpp-dev,
+ vpp-plugin-core,
+ vpp-plugin-dpdk,
+# End "vpp"
## End Configuration mode
## Operational mode
# Used for hypervisor model in "run show version"
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 5fcff959a..4e312a648 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -9,7 +9,6 @@ etc/netplug
etc/opennhrp
etc/modprobe.d
etc/ppp
-etc/rsyslog.conf
etc/securetty
etc/security
etc/skel
diff --git a/debian/vyos-1x.links b/debian/vyos-1x.links
index 402c91306..7e21f294c 100644
--- a/debian/vyos-1x.links
+++ b/debian/vyos-1x.links
@@ -1,2 +1,3 @@
/etc/netplug/linkup.d/vyos-python-helper /etc/netplug/linkdown.d/vyos-python-helper
/usr/libexec/vyos/system/standalone_root_pw_reset /opt/vyatta/sbin/standalone_root_pw_reset
+/lib/systemd/system/rsyslog.service /etc/systemd/system/syslog.service
diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst
index fbfc85566..08f48cac2 100644
--- a/debian/vyos-1x.preinst
+++ b/debian/vyos-1x.preinst
@@ -5,6 +5,7 @@ dpkg-divert --package vyos-1x --add --no-rename /etc/logrotate.d/conntrackd
dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.conf
dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.bashrc
dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.profile
+dpkg-divert --package vyos-1x --add --no-rename /etc/sysctl.d/80-vpp.conf
dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplugd.conf
dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplug
dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.d/45-frr.conf
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 04318a7c9..3a5cfbaa6 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -31,7 +31,7 @@
<properties>
<help>Grant individual Linux capability to container instance</help>
<completionHelp>
- <list>net-admin net-bind-service net-raw setpcap sys-admin sys-module sys-nice sys-time</list>
+ <list>net-admin net-bind-service net-raw mknod setpcap sys-admin sys-module sys-nice sys-time</list>
</completionHelp>
<valueHelp>
<format>net-admin</format>
@@ -46,6 +46,10 @@
<description>Permission to create raw network sockets</description>
</valueHelp>
<valueHelp>
+ <format>mknod</format>
+ <description>Permission to create special files</description>
+ </valueHelp>
+ <valueHelp>
<format>setpcap</format>
<description>Capability sets (from bounded or inherited set)</description>
</valueHelp>
@@ -66,7 +70,7 @@
<description>Permission to set system clock</description>
</valueHelp>
<constraint>
- <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-module|sys-nice|sys-time)</regex>
+ <regex>(net-admin|net-bind-service|net-raw|mknod|setpcap|sys-admin|sys-module|sys-nice|sys-time)</regex>
</constraint>
<multi/>
</properties>
@@ -412,6 +416,35 @@
</constraint>
</properties>
</leafNode>
+ <tagNode name="tmpfs">
+ <properties>
+ <help>Mount a tmpfs filesystem into the container</help>
+ </properties>
+ <children>
+ <leafNode name="destination">
+ <properties>
+ <help>Destination container directory</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Destination container directory</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="size">
+ <properties>
+ <help>tmpfs filesystem size in MB</help>
+ <valueHelp>
+ <format>u32:1-65536</format>
+ <description>tmpfs filesystem size in MB</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Container tmpfs size must be between 1 and 65535 MB</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
<tagNode name="volume">
<properties>
<help>Mount a volume into the container</help>
@@ -538,6 +571,54 @@
<children>
#include <include/interface/authentication.xml.i>
#include <include/generic-disable-node.xml.i>
+ <leafNode name="insecure">
+ <properties>
+ <help>Allow registry access over unencrypted HTTP or TLS connections with untrusted certificates</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="mirror">
+ <properties>
+ <help>Registry mirror, use host-name|address[:port][/path]</help>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IP address of container registry mirror</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address of container registry mirror</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of container registry mirror</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ <validator name="ipv6-link-local"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="host-name">
+ <properties>
+ <help>Hostname of container registry mirror</help>
+ <valueHelp>
+ <format>hostname</format>
+ <description>FQDN of container registry mirror</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/port-number.xml.i>
+ <leafNode name="path">
+ <properties>
+ <help>Path of container registry mirror, optional, must be start with '/' if not empty</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/include/babel/redistribute-common.xml.i b/interface-definitions/include/babel/redistribute-common.xml.i
index 93efe68dd..e988cc0d0 100644
--- a/interface-definitions/include/babel/redistribute-common.xml.i
+++ b/interface-definitions/include/babel/redistribute-common.xml.i
@@ -23,6 +23,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="nhrp">
+ <properties>
+ <help>Redistribute NHRP routes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="openfabric">
<properties>
<help>OpenFabric Routing Protocol</help>
diff --git a/interface-definitions/include/bgp/afi-redistribute-common-protocols.xml.i b/interface-definitions/include/bgp/afi-redistribute-common-protocols.xml.i
new file mode 100644
index 000000000..3f6517d03
--- /dev/null
+++ b/interface-definitions/include/bgp/afi-redistribute-common-protocols.xml.i
@@ -0,0 +1,62 @@
+<!-- include start from bgp/afi-redistribute-common-protocols.xml.i -->
+<node name="babel">
+ <properties>
+ <help>Redistribute Babel routes into BGP</help>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+</node>
+<node name="connected">
+ <properties>
+ <help>Redistribute connected routes into BGP</help>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+</node>
+<node name="isis">
+ <properties>
+ <help>Redistribute IS-IS routes into BGP</help>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+</node>
+<node name="kernel">
+ <properties>
+ <help>Redistribute kernel routes into BGP</help>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+</node>
+<node name="nhrp">
+ <properties>
+ <help>Redistribute NHRP routes into BGP</help>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+</node>
+<node name="static">
+ <properties>
+ <help>Redistribute static routes into BGP</help>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+</node>
+<tagNode name="table">
+ <properties>
+ <help>Redistribute non-main Kernel Routing Table</help>
+ <completionHelp>
+ <path>protocols static table</path>
+ </completionHelp>
+ #include <include/constraint/protocols-static-table.xml.i>
+ </properties>
+ <children>
+ #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index 4953251c5..21514e762 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -126,30 +126,7 @@
<help>Redistribute routes from other protocols into BGP</help>
</properties>
<children>
- <node name="connected">
- <properties>
- <help>Redistribute connected routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
- <node name="isis">
- <properties>
- <help>Redistribute IS-IS routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
- <node name="kernel">
- <properties>
- <help>Redistribute kernel routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
+ #include <include/bgp/afi-redistribute-common-protocols.xml.i>
<node name="ospf">
<properties>
<help>Redistribute OSPF routes into BGP</help>
@@ -166,27 +143,6 @@
#include <include/bgp/afi-redistribute-metric-route-map.xml.i>
</children>
</node>
- <node name="babel">
- <properties>
- <help>Redistribute Babel routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
- <node name="static">
- <properties>
- <help>Redistribute static routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
- <leafNode name="table">
- <properties>
- <help>Redistribute non-main Kernel Routing Table</help>
- </properties>
- </leafNode>
</children>
</node>
#include <include/bgp/afi-sid.xml.i>
@@ -503,22 +459,7 @@
<help>Redistribute routes from other protocols into BGP</help>
</properties>
<children>
- <node name="connected">
- <properties>
- <help>Redistribute connected routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
- <node name="kernel">
- <properties>
- <help>Redistribute kernel routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
+ #include <include/bgp/afi-redistribute-common-protocols.xml.i>
<node name="ospfv3">
<properties>
<help>Redistribute OSPFv3 routes into BGP</help>
@@ -535,27 +476,6 @@
#include <include/bgp/afi-redistribute-metric-route-map.xml.i>
</children>
</node>
- <node name="babel">
- <properties>
- <help>Redistribute Babel routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
- <node name="static">
- <properties>
- <help>Redistribute static routes into BGP</help>
- </properties>
- <children>
- #include <include/bgp/afi-redistribute-metric-route-map.xml.i>
- </children>
- </node>
- <leafNode name="table">
- <properties>
- <help>Redistribute non-main Kernel Routing Table</help>
- </properties>
- </leafNode>
</children>
</node>
#include <include/bgp/afi-sid.xml.i>
diff --git a/interface-definitions/include/constraint/interface-name.xml.i b/interface-definitions/include/constraint/interface-name.xml.i
index 3e7c4e667..bf1db243d 100644
--- a/interface-definitions/include/constraint/interface-name.xml.i
+++ b/interface-definitions/include/constraint/interface-name.xml.i
@@ -1,4 +1,4 @@
<!-- include start from constraint/interface-name.xml.i -->
-<regex>(bond|br|dum|en|ersp|eth|gnv|ifb|ipoe|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|sstpc|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo</regex>
+<regex>(bond|br|dum|en|ersp|eth|gnv|ifb|ipoe|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|sstpc|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|pod-[-_a-zA-Z0-9]{1,11}|lo</regex>
<validator name="file-path --lookup-path /sys/class/net --directory"/>
<!-- include end -->
diff --git a/interface-definitions/include/constraint/protocols-static-table.xml.i b/interface-definitions/include/constraint/protocols-static-table.xml.i
new file mode 100644
index 000000000..2d8b067a4
--- /dev/null
+++ b/interface-definitions/include/constraint/protocols-static-table.xml.i
@@ -0,0 +1,9 @@
+<!-- include start from constraint/host-name.xml.i -->
+<valueHelp>
+ <format>u32:1-200</format>
+ <description>Policy route table number</description>
+</valueHelp>
+<constraint>
+ <validator name="numeric" argument="--range 1-200"/>
+</constraint>
+<!-- include end -->
diff --git a/interface-definitions/include/haproxy/timeout-check.xml.i b/interface-definitions/include/haproxy/timeout-check.xml.i
new file mode 100644
index 000000000..d1217fac3
--- /dev/null
+++ b/interface-definitions/include/haproxy/timeout-check.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from haproxy/timeout-check.xml.i -->
+<leafNode name="check">
+ <properties>
+ <help>Timeout in seconds for established connections</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Check timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/haproxy/timeout-client.xml.i b/interface-definitions/include/haproxy/timeout-client.xml.i
new file mode 100644
index 000000000..2250ccdef
--- /dev/null
+++ b/interface-definitions/include/haproxy/timeout-client.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from haproxy/timeout-client.xml.i -->
+<leafNode name="client">
+ <properties>
+ <help>Maximum inactivity time on the client side</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/haproxy/timeout-connect.xml.i b/interface-definitions/include/haproxy/timeout-connect.xml.i
new file mode 100644
index 000000000..da4f983af
--- /dev/null
+++ b/interface-definitions/include/haproxy/timeout-connect.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from haproxy/timeout-connect.xml.i -->
+<leafNode name="connect">
+ <properties>
+ <help>Set the maximum time to wait for a connection attempt to a server to succeed</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Connect timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/haproxy/timeout-server.xml.i b/interface-definitions/include/haproxy/timeout-server.xml.i
new file mode 100644
index 000000000..f27d415c1
--- /dev/null
+++ b/interface-definitions/include/haproxy/timeout-server.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from haproxy/timeout-server.xml.i -->
+<leafNode name="server">
+ <properties>
+ <help>Set the maximum inactivity time on the server side</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Server timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/haproxy/timeout.xml.i b/interface-definitions/include/haproxy/timeout.xml.i
index 79e7303b1..a3a5a8a3e 100644
--- a/interface-definitions/include/haproxy/timeout.xml.i
+++ b/interface-definitions/include/haproxy/timeout.xml.i
@@ -4,42 +4,9 @@
<help>Timeout options</help>
</properties>
<children>
- <leafNode name="check">
- <properties>
- <help>Timeout in seconds for established connections</help>
- <valueHelp>
- <format>u32:1-3600</format>
- <description>Check timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-3600"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="connect">
- <properties>
- <help>Set the maximum time to wait for a connection attempt to a server to succeed</help>
- <valueHelp>
- <format>u32:1-3600</format>
- <description>Connect timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-3600"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="server">
- <properties>
- <help>Set the maximum inactivity time on the server side</help>
- <valueHelp>
- <format>u32:1-3600</format>
- <description>Server timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-3600"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/haproxy/timeout-check.xml.i>
+ #include <include/haproxy/timeout-connect.xml.i>
+ #include <include/haproxy/timeout-server.xml.i>
</children>
</node>
<!-- include end -->
diff --git a/interface-definitions/include/ip-address.xml.i b/interface-definitions/include/ip-address.xml.i
new file mode 100644
index 000000000..6027e97ee
--- /dev/null
+++ b/interface-definitions/include/ip-address.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from ip-address.xml.i -->
+<leafNode name="ip-address">
+ <properties>
+ <help>Fixed IP address of static mapping</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address used in static mapping</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i
index 35ce80be9..e0a7e62b6 100644
--- a/interface-definitions/include/isis/protocol-common-config.xml.i
+++ b/interface-definitions/include/isis/protocol-common-config.xml.i
@@ -418,6 +418,14 @@
#include <include/isis/redistribute-level-1-2.xml.i>
</children>
</node>
+ <node name="nhrp">
+ <properties>
+ <help>Redistribute NHRP routes into IS-IS</help>
+ </properties>
+ <children>
+ #include <include/isis/redistribute-level-1-2.xml.i>
+ </children>
+ </node>
<node name="ospf">
<properties>
<help>Redistribute OSPF routes into IS-IS</help>
diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i
index cef832381..f597be64e 100644
--- a/interface-definitions/include/ospf/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospf/protocol-common-config.xml.i
@@ -798,6 +798,16 @@
#include <include/route-map.xml.i>
</children>
</node>
+ <node name="nhrp">
+ <properties>
+ <help>Redistribute NHRP routes</help>
+ </properties>
+ <children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
+ #include <include/route-map.xml.i>
+ </children>
+ </node>
<node name="rip">
<properties>
<help>Redistribute RIP routes</help>
diff --git a/interface-definitions/include/policy/community-value-list.xml.i b/interface-definitions/include/policy/community-value-list.xml.i
index 8c665c5f0..b1499440a 100644
--- a/interface-definitions/include/policy/community-value-list.xml.i
+++ b/interface-definitions/include/policy/community-value-list.xml.i
@@ -4,7 +4,6 @@
local-as
no-advertise
no-export
- internet
graceful-shutdown
accept-own
route-filter-translated-v4
@@ -35,10 +34,6 @@
<description>Well-known communities value NO_EXPORT 0xFFFFFF01</description>
</valueHelp>
<valueHelp>
- <format>internet</format>
- <description>Well-known communities value 0</description>
-</valueHelp>
-<valueHelp>
<format>graceful-shutdown</format>
<description>Well-known communities value GRACEFUL_SHUTDOWN 0xFFFF0000</description>
</valueHelp>
@@ -84,7 +79,7 @@
</valueHelp>
<multi/>
<constraint>
- <regex>local-as|no-advertise|no-export|internet|graceful-shutdown|accept-own|route-filter-translated-v4|route-filter-v4|route-filter-translated-v6|route-filter-v6|llgr-stale|no-llgr|accept-own-nexthop|blackhole|no-peer</regex>
+ <regex>local-as|no-advertise|no-export|graceful-shutdown|accept-own|route-filter-translated-v4|route-filter-v4|route-filter-translated-v6|route-filter-v6|llgr-stale|no-llgr|accept-own-nexthop|blackhole|no-peer</regex>
<validator name="bgp-regular-community"/>
</constraint>
<!-- include end -->
diff --git a/interface-definitions/include/version/bgp-version.xml.i b/interface-definitions/include/version/bgp-version.xml.i
index 6bed7189f..c90276151 100644
--- a/interface-definitions/include/version/bgp-version.xml.i
+++ b/interface-definitions/include/version/bgp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/bgp-version.xml.i -->
-<syntaxVersion component='bgp' version='5'></syntaxVersion>
+<syntaxVersion component='bgp' version='6'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/lldp-version.xml.i b/interface-definitions/include/version/lldp-version.xml.i
index b41d80451..a7110691a 100644
--- a/interface-definitions/include/version/lldp-version.xml.i
+++ b/interface-definitions/include/version/lldp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/lldp-version.xml.i -->
-<syntaxVersion component='lldp' version='2'></syntaxVersion>
+<syntaxVersion component='lldp' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/policy-version.xml.i b/interface-definitions/include/version/policy-version.xml.i
index db727fea9..5c53a4032 100644
--- a/interface-definitions/include/version/policy-version.xml.i
+++ b/interface-definitions/include/version/policy-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/policy-version.xml.i -->
-<syntaxVersion component='policy' version='8'></syntaxVersion>
+<syntaxVersion component='policy' version='9'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/system-version.xml.i b/interface-definitions/include/version/system-version.xml.i
index 3ecf124c7..5cdece74a 100644
--- a/interface-definitions/include/version/system-version.xml.i
+++ b/interface-definitions/include/version/system-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/system-version.xml.i -->
-<syntaxVersion component='system' version='28'></syntaxVersion>
+<syntaxVersion component='system' version='29'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/wanloadbalance-version.xml.i b/interface-definitions/include/version/wanloadbalance-version.xml.i
index 59f8729cc..34c3c76ff 100644
--- a/interface-definitions/include/version/wanloadbalance-version.xml.i
+++ b/interface-definitions/include/version/wanloadbalance-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/wanloadbalance-version.xml.i -->
-<syntaxVersion component='wanloadbalance' version='3'></syntaxVersion>
+<syntaxVersion component='wanloadbalance' version='4'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces_geneve.xml.in b/interface-definitions/interfaces_geneve.xml.in
index 990c5bd91..c1e6c33d5 100644
--- a/interface-definitions/interfaces_geneve.xml.in
+++ b/interface-definitions/interfaces_geneve.xml.in
@@ -23,6 +23,10 @@
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1200-16000.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>6081</defaultValue>
+ </leafNode>
<node name="parameters">
<properties>
<help>GENEVE tunnel parameters</help>
diff --git a/interface-definitions/load-balancing_haproxy.xml.in b/interface-definitions/load-balancing_haproxy.xml.in
index ca089d3f0..b95e02337 100644
--- a/interface-definitions/load-balancing_haproxy.xml.in
+++ b/interface-definitions/load-balancing_haproxy.xml.in
@@ -48,6 +48,14 @@
<valueless/>
</properties>
</leafNode>
+ <node name="timeout">
+ <properties>
+ <help>Timeout options</help>
+ </properties>
+ <children>
+ #include <include/haproxy/timeout-client.xml.i>
+ </children>
+ </node>
<node name="http-compression">
<properties>
<help>Compress HTTP responses</help>
@@ -368,6 +376,29 @@
</leafNode>
</children>
</node>
+ <node name="timeout">
+ <properties>
+ <help>Timeout options</help>
+ </properties>
+ <children>
+ #include <include/haproxy/timeout-check.xml.i>
+ <leafNode name="check">
+ <defaultValue>5</defaultValue>
+ </leafNode>
+ #include <include/haproxy/timeout-connect.xml.i>
+ <leafNode name="connect">
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ #include <include/haproxy/timeout-client.xml.i>
+ <leafNode name="client">
+ <defaultValue>50</defaultValue>
+ </leafNode>
+ #include <include/haproxy/timeout-server.xml.i>
+ <leafNode name="server">
+ <defaultValue>50</defaultValue>
+ </leafNode>
+ </children>
+ </node>
#include <include/interface/vrf.xml.i>
</children>
</node>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index cbab6173f..25dbf5581 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -202,7 +202,7 @@
<properties>
<help>Regular expression to match against a community-list</help>
<completionHelp>
- <list>local-AS no-advertise no-export internet graceful-shutdown accept-own-nexthop accept-own route-filter-translated-v4 route-filter-v4 route-filter-translated-v6 route-filter-v6 llgr-stale no-llgr blackhole no-peer additive</list>
+ <list>local-AS no-advertise no-export graceful-shutdown accept-own-nexthop accept-own route-filter-translated-v4 route-filter-v4 route-filter-translated-v6 route-filter-v6 llgr-stale no-llgr blackhole no-peer additive</list>
</completionHelp>
<valueHelp>
<format>&lt;aa:nn&gt;</format>
@@ -221,10 +221,6 @@
<description>Well-known communities value NO_EXPORT 0xFFFFFF01</description>
</valueHelp>
<valueHelp>
- <format>internet</format>
- <description>Well-known communities value 0</description>
- </valueHelp>
- <valueHelp>
<format>graceful-shutdown</format>
<description>Well-known communities value GRACEFUL_SHUTDOWN 0xFFFF0000</description>
</valueHelp>
@@ -1096,6 +1092,20 @@
</constraint>
</properties>
</leafNode>
+ <leafNode name="source-vrf">
+ <properties>
+ <help>Source vrf</help>
+ #include <include/constraint/vrf.xml.i>
+ <valueHelp>
+ <format>txt</format>
+ <description>VRF instance name</description>
+ </valueHelp>
+ <completionHelp>
+ <path>vrf name</path>
+ <list>default</list>
+ </completionHelp>
+ </properties>
+ </leafNode>
#include <include/policy/tag.xml.i>
</children>
</node>
diff --git a/interface-definitions/protocols_rip.xml.in b/interface-definitions/protocols_rip.xml.in
index 0edd8f2ce..745280fd7 100644
--- a/interface-definitions/protocols_rip.xml.in
+++ b/interface-definitions/protocols_rip.xml.in
@@ -209,6 +209,14 @@
#include <include/rip/redistribute.xml.i>
</children>
</node>
+ <node name="nhrp">
+ <properties>
+ <help>Redistribute NHRP routes</help>
+ </properties>
+ <children>
+ #include <include/rip/redistribute.xml.i>
+ </children>
+ </node>
<node name="ospf">
<properties>
<help>Redistribute OSPF routes</help>
diff --git a/interface-definitions/protocols_rpki.xml.in b/interface-definitions/protocols_rpki.xml.in
index 54d69eadb..9e2e84717 100644
--- a/interface-definitions/protocols_rpki.xml.in
+++ b/interface-definitions/protocols_rpki.xml.in
@@ -42,6 +42,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/source-address-ipv4.xml.i>
<node name="ssh">
<properties>
<help>RPKI SSH connection settings</help>
diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in
index d8e0ee56b..c721bb3fc 100644
--- a/interface-definitions/protocols_static.xml.in
+++ b/interface-definitions/protocols_static.xml.in
@@ -65,14 +65,8 @@
#include <include/static/static-route6.xml.i>
<tagNode name="table">
<properties>
- <help>Policy route table number</help>
- <valueHelp>
- <format>u32:1-200</format>
- <description>Policy route table number</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-200"/>
- </constraint>
+ <help>Non-main Kernel Routing Table</help>
+ #include <include/constraint/protocols-static-table.xml.i>
</properties>
<children>
<!--
diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in
index cb5f9a804..9a194de4f 100644
--- a/interface-definitions/service_dhcp-server.xml.in
+++ b/interface-definitions/service_dhcp-server.xml.in
@@ -211,18 +211,7 @@
#include <include/dhcp/option-v4.xml.i>
#include <include/generic-description.xml.i>
#include <include/generic-disable-node.xml.i>
- <leafNode name="ip-address">
- <properties>
- <help>Fixed IP address of static mapping</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address used in static mapping</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/ip-address.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/duid.xml.i>
</children>
diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in
index 6cc4471af..fe9d32bbd 100644
--- a/interface-definitions/service_ipoe-server.xml.in
+++ b/interface-definitions/service_ipoe-server.xml.in
@@ -70,18 +70,7 @@
<constraintErrorMessage>VLAN IDs need to be in range 1-4094</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="static-ip">
- <properties>
- <help>Static client IP address</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/ip-address.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/service_lldp.xml.in b/interface-definitions/service_lldp.xml.in
index 51a9f9cce..a189cc13b 100644
--- a/interface-definitions/service_lldp.xml.in
+++ b/interface-definitions/service_lldp.xml.in
@@ -29,7 +29,34 @@
</constraint>
</properties>
<children>
- #include <include/generic-disable-node.xml.i>
+ <leafNode name="mode">
+ <properties>
+ <help>Set LLDP receive/transmit operation mode of this interface</help>
+ <completionHelp>
+ <list>disable rx-tx tx rx</list>
+ </completionHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Do not process or send LLDP messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rx-tx</format>
+ <description>Send and process LLDP messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rx</format>
+ <description>Process incoming LLDP messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tx</format>
+ <description>Send LLDP messages</description>
+ </valueHelp>
+ <constraint>
+ <regex>(disable|rx-tx|tx|rx)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>rx-tx</defaultValue>
+ </leafNode>
<node name="location">
<properties>
<help>LLDP-MED location data</help>
diff --git a/interface-definitions/service_snmp.xml.in b/interface-definitions/service_snmp.xml.in
index f23151ef9..cc21f5b8b 100644
--- a/interface-definitions/service_snmp.xml.in
+++ b/interface-definitions/service_snmp.xml.in
@@ -304,7 +304,6 @@
</constraint>
<constraintErrorMessage>ID must contain an even number (from 2 to 36) of hex digits</constraintErrorMessage>
</properties>
- <defaultValue></defaultValue>
</leafNode>
<tagNode name="group">
<properties>
diff --git a/interface-definitions/system_syslog.xml.in b/interface-definitions/system_syslog.xml.in
index 0a9a00572..8b2d9cab7 100644
--- a/interface-definitions/system_syslog.xml.in
+++ b/interface-definitions/system_syslog.xml.in
@@ -8,28 +8,17 @@
<priority>400</priority>
</properties>
<children>
- <tagNode name="user">
+ <node name="console">
<properties>
- <help>Logging to specific terminal of given user</help>
- <completionHelp>
- <path>system login user</path>
- </completionHelp>
- <valueHelp>
- <format>txt</format>
- <description>Local user account</description>
- </valueHelp>
- <constraint>
- #include <include/constraint/login-username.xml.i>
- </constraint>
- <constraintErrorMessage>illegal characters in user</constraintErrorMessage>
+ <help>Log to system console (/dev/console)</help>
</properties>
<children>
#include <include/syslog-facility.xml.i>
</children>
- </tagNode>
- <tagNode name="host">
+ </node>
+ <tagNode name="remote">
<properties>
- <help>Logging to remote host</help>
+ <help>Log to remote host</help>
<constraint>
<validator name="ip-address"/>
<validator name="fqdn"/>
@@ -49,11 +38,6 @@
</valueHelp>
</properties>
<children>
- #include <include/port-number.xml.i>
- <leafNode name="port">
- <defaultValue>514</defaultValue>
- </leafNode>
- #include <include/protocol-tcp-udp.xml.i>
#include <include/syslog-facility.xml.i>
<node name="format">
<properties>
@@ -74,86 +58,51 @@
</leafNode>
</children>
</node>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>514</defaultValue>
+ </leafNode>
+ #include <include/protocol-tcp-udp.xml.i>
+ #include <include/source-address-ipv4-ipv6.xml.i>
+ #include <include/interface/vrf.xml.i>
</children>
</tagNode>
- <node name="global">
+ <node name="local">
<properties>
- <help>Logging to system standard location</help>
+ <help>Log to standard system location /var/log/messages</help>
</properties>
<children>
#include <include/syslog-facility.xml.i>
- <node name="marker">
- <properties>
- <help>mark messages sent to syslog</help>
- </properties>
- <children>
- <leafNode name="interval">
- <properties>
- <help>time interval how often a mark message is being sent in seconds</help>
- <constraint>
- <validator name="numeric" argument="--positive"/>
- </constraint>
- </properties>
- <defaultValue>1200</defaultValue>
- </leafNode>
- </children>
- </node>
- <leafNode name="preserve-fqdn">
- <properties>
- <help>uses FQDN for logging</help>
- <valueless/>
- </properties>
- </leafNode>
</children>
</node>
- <tagNode name="file">
+ <node name="marker">
<properties>
- <help>Logging to a file</help>
- <constraint>
- <regex>[a-zA-Z0-9\-_.]{1,255}</regex>
- </constraint>
- <constraintErrorMessage>illegal characters in filename or filename longer than 255 characters</constraintErrorMessage>
+ <help>Mark messages sent to syslog</help>
</properties>
<children>
- <node name="archive">
+ #include <include/generic-disable-node.xml.i>
+ <leafNode name="interval">
<properties>
- <help>Log file size and rotation characteristics</help>
+ <help>Mark message interval</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage>
</properties>
- <children>
- <leafNode name="file">
- <properties>
- <help>Number of saved files</help>
- <constraint>
- <regex>[0-9]+</regex>
- </constraint>
- <constraintErrorMessage>illegal characters in number of files</constraintErrorMessage>
- </properties>
- <defaultValue>5</defaultValue>
- </leafNode>
- <leafNode name="size">
- <properties>
- <help>Size of log files in kbytes</help>
- <constraint>
- <regex>[0-9]+</regex>
- </constraint>
- <constraintErrorMessage>illegal characters in size</constraintErrorMessage>
- </properties>
- <defaultValue>256</defaultValue>
- </leafNode>
- </children>
- </node>
- #include <include/syslog-facility.xml.i>
+ <defaultValue>1200</defaultValue>
+ </leafNode>
</children>
- </tagNode>
- <node name="console">
+ </node>
+ <leafNode name="preserve-fqdn">
<properties>
- <help>logging to serial console</help>
+ <help>Always include domain portion in hostname</help>
+ <valueless/>
</properties>
- <children>
- #include <include/syslog-facility.xml.i>
- </children>
- </node>
- #include <include/interface/vrf.xml.i>
+ </leafNode>
</children>
</node>
</children>
diff --git a/op-mode-definitions/load-balacing_haproxy.in b/op-mode-definitions/load-balancing_haproxy.xml.in
index c3d6c799b..8de7ae97f 100644
--- a/op-mode-definitions/load-balacing_haproxy.in
+++ b/op-mode-definitions/load-balancing_haproxy.xml.in
@@ -16,7 +16,7 @@
<properties>
<help>Show load-balancing haproxy</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/load-balacing_haproxy.py show</command>
+ <command>sudo ${vyos_op_scripts_dir}/load-balancing_haproxy.py show</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/load-balancing_wan.xml.in b/op-mode-definitions/load-balancing_wan.xml.in
new file mode 100644
index 000000000..91c57c1f4
--- /dev/null
+++ b/op-mode-definitions/load-balancing_wan.xml.in
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="restart">
+ <children>
+ <node name="wan-load-balance">
+ <properties>
+ <help>Restart Wide Area Network (WAN) load-balancing daemon</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name load-balancing_wan</command>
+ </node>
+ </children>
+ </node>
+ <node name="show">
+ <children>
+ <node name="wan-load-balance">
+ <properties>
+ <help>Show Wide Area Network (WAN) load-balancing information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/load-balancing_wan.py show_summary</command>
+ <children>
+ <node name="connection">
+ <properties>
+ <help>Show Wide Area Network (WAN) load-balancing flow</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/load-balancing_wan.py show_connection</command>
+ </node>
+ <node name="status">
+ <properties>
+ <help>Show WAN load-balancing statistics</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/load-balancing_wan.py show_status</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition> \ No newline at end of file
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 5a353b110..78b98a3eb 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -491,10 +491,8 @@ def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pk
# Check if any DHCP options changed which require a client restat
dhcp = is_node_changed(config, base + [ifname, 'dhcp-options'])
if dhcp: dict.update({'dhcp_options_changed' : {}})
-
- # Changine interface VRF assignemnts require a DHCP restart, too
- dhcp = is_node_changed(config, base + [ifname, 'vrf'])
- if dhcp: dict.update({'dhcp_options_changed' : {}})
+ dhcpv6 = is_node_changed(config, base + [ifname, 'dhcpv6-options'])
+ if dhcpv6: dict.update({'dhcpv6_options_changed' : {}})
# Some interfaces come with a source_interface which must also not be part
# of any other bond or bridge interface as it is exclusivly assigned as the
@@ -543,6 +541,8 @@ def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pk
# Check if any DHCP options changed which require a client restat
dhcp = is_node_changed(config, base + [ifname, 'vif', vif, 'dhcp-options'])
if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : {}})
+ dhcpv6 = is_node_changed(config, base + [ifname, 'vif', vif, 'dhcpv6-options'])
+ if dhcpv6: dict['vif'][vif].update({'dhcpv6_options_changed' : {}})
for vif_s, vif_s_config in dict.get('vif_s', {}).items():
# Add subinterface name to dictionary
@@ -569,6 +569,8 @@ def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pk
# Check if any DHCP options changed which require a client restat
dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'dhcp-options'])
if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : {}})
+ dhcpv6 = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'dhcpv6-options'])
+ if dhcpv6: dict['vif_s'][vif_s].update({'dhcpv6_options_changed' : {}})
for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
# Add subinterface name to dictionary
@@ -597,6 +599,8 @@ def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pk
# Check if any DHCP options changed which require a client restat
dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'dhcp-options'])
if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : {}})
+ dhcpv6 = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'dhcpv6-options'])
+ if dhcpv6: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcpv6_options_changed' : {}})
# Check vif, vif-s/vif-c VLAN interfaces for removal
dict = get_removed_vlans(config, base + [ifname], dict)
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index dd3ad1e3d..90b96b88c 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -23,8 +23,8 @@ from vyos.utils.process import is_systemd_service_running
from vyos.utils.dict import dict_to_paths
CLI_SHELL_API = '/bin/cli-shell-api'
-SET = '/usr/libexec/vyos/vyconf/vy_set'
-DELETE = '/usr/libexec/vyos/vyconf/vy_delete'
+SET = '/opt/vyatta/sbin/my_set'
+DELETE = '/opt/vyatta/sbin/my_delete'
COMMENT = '/opt/vyatta/sbin/my_comment'
COMMIT = '/opt/vyatta/sbin/my_commit'
DISCARD = '/opt/vyatta/sbin/my_discard'
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index 8d27a7e46..4ad0620a5 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -19,7 +19,9 @@ import logging
from ctypes import cdll, c_char_p, c_void_p, c_int, c_bool
-LIBPATH = '/usr/lib/libvyosconfig.so.0'
+BUILD_PATH = '/tmp/libvyosconfig/_build/libvyosconfig.so'
+INSTALL_PATH = '/usr/lib/libvyosconfig.so.0'
+LIBPATH = BUILD_PATH if os.path.isfile(BUILD_PATH) else INSTALL_PATH
def replace_backslash(s, search, replace):
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 9757a34df..86194cd55 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2018-2025 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
@@ -37,7 +37,13 @@ directories = {
'dhcp6_client_dir' : '/run/dhcp6c',
'vyos_configdir' : '/opt/vyatta/config',
'completion_dir' : f'{base_dir}/completion',
- 'ca_certificates' : '/usr/local/share/ca-certificates/vyos'
+ 'ca_certificates' : '/usr/local/share/ca-certificates/vyos',
+ 'ppp_nexthop_dir' : '/run/ppp_nexthop'
+}
+
+systemd_services = {
+ 'rsyslog' : 'rsyslog.service',
+ 'snmpd' : 'snmpd.service',
}
config_status = '/tmp/vyos-config-status'
diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py
index f7fddb812..f53ef4166 100644
--- a/python/vyos/ifconfig/geneve.py
+++ b/python/vyos/ifconfig/geneve.py
@@ -48,7 +48,7 @@ class GeneveIf(Interface):
'parameters.ipv6.flowlabel' : 'flowlabel',
}
- cmd = 'ip link add name {ifname} type geneve id {vni} remote {remote}'
+ cmd = 'ip link add name {ifname} type geneve id {vni} remote {remote} dstport {port}'
for vyos_key, iproute2_key in mapping.items():
# dict_search will return an empty dict "{}" for valueless nodes like
# "parameters.nolearning" - thus we need to test the nodes existence
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index cb73e2597..979b62578 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -595,12 +595,16 @@ class Interface(Control):
"""
Add/Remove interface from given VRF instance.
+ Keyword arguments:
+ vrf: VRF instance name or empty string (default VRF)
+
+ Return True if VRF was changed, False otherwise
+
Example:
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_vrf('foo')
>>> Interface('eth0').set_vrf()
"""
-
# Don't allow for netns yet
if 'netns' in self.config:
return False
@@ -611,21 +615,33 @@ class Interface(Control):
# Get current VRF table ID
old_vrf_tableid = get_vrf_tableid(self.ifname)
- self.set_interface('vrf', vrf)
+ # Always stop the DHCP client process to clean up routes within the VRF
+ # where the process was originally started. There is no need to add a
+ # condition to only call the method if "address dhcp" was defined, as
+ # this is handled inside set_dhcp(v6) by only stopping if the daemon is
+ # running. DHCP client process restart will be handled later on once the
+ # interface is moved to the new VRF.
+ self.set_dhcp(False)
+ self.set_dhcpv6(False)
+
+ # Move interface in/out of VRF
+ self.set_interface('vrf', vrf)
if vrf:
# Get routing table ID number for VRF
vrf_table_id = get_vrf_tableid(vrf)
# Add map element with interface and zone ID
- if vrf_table_id:
+ if vrf_table_id and old_vrf_tableid != vrf_table_id:
# delete old table ID from nftables if it has changed, e.g. interface moved to a different VRF
- if old_vrf_tableid and old_vrf_tableid != int(vrf_table_id):
- self._del_interface_from_ct_iface_map()
+ self._del_interface_from_ct_iface_map()
self._add_interface_to_ct_iface_map(vrf_table_id)
+ return True
else:
- self._del_interface_from_ct_iface_map()
+ if old_vrf_tableid != get_vrf_tableid(self.ifname):
+ self._del_interface_from_ct_iface_map()
+ return True
- return True
+ return False
def set_arp_cache_tmo(self, tmo):
"""
@@ -1181,7 +1197,7 @@ class Interface(Control):
"""
return self.get_addr_v4() + self.get_addr_v6()
- def add_addr(self, addr):
+ def add_addr(self, addr: str, vrf_changed: bool=False) -> bool:
"""
Add IP(v6) address to interface. Address is only added if it is not
already assigned to that interface. Address format must be validated
@@ -1214,15 +1230,14 @@ class Interface(Control):
# add to interface
if addr == 'dhcp':
- self.set_dhcp(True)
+ self.set_dhcp(True, vrf_changed=vrf_changed)
elif addr == 'dhcpv6':
- self.set_dhcpv6(True)
+ self.set_dhcpv6(True, vrf_changed=vrf_changed)
elif not is_intf_addr_assigned(self.ifname, addr, netns=netns):
netns_cmd = f'ip netns exec {netns}' if netns else ''
tmp = f'{netns_cmd} ip addr add {addr} dev {self.ifname}'
# Add broadcast address for IPv4
if is_ipv4(addr): tmp += ' brd +'
-
self._cmd(tmp)
else:
return False
@@ -1232,7 +1247,7 @@ class Interface(Control):
return True
- def del_addr(self, addr):
+ def del_addr(self, addr: str) -> bool:
"""
Delete IP(v6) address from interface. Address is only deleted if it is
assigned to that interface. Address format must be exactly the same as
@@ -1356,7 +1371,7 @@ class Interface(Control):
cmd = f'bridge vlan add dev {ifname} vid {native_vlan_id} pvid untagged master'
self._cmd(cmd)
- def set_dhcp(self, enable):
+ def set_dhcp(self, enable: bool, vrf_changed: bool=False):
"""
Enable/Disable DHCP client on a given interface.
"""
@@ -1396,7 +1411,9 @@ class Interface(Control):
# the old lease is released a new one is acquired (T4203). We will
# only restart DHCP client if it's option changed, or if it's not
# running, but it should be running (e.g. on system startup)
- if 'dhcp_options_changed' in self.config or not is_systemd_service_active(systemd_service):
+ if (vrf_changed or
+ ('dhcp_options_changed' in self.config) or
+ (not is_systemd_service_active(systemd_service))):
return self._cmd(f'systemctl restart {systemd_service}')
else:
if is_systemd_service_active(systemd_service):
@@ -1423,7 +1440,7 @@ class Interface(Control):
return None
- def set_dhcpv6(self, enable):
+ def set_dhcpv6(self, enable: bool, vrf_changed: bool=False):
"""
Enable/Disable DHCPv6 client on a given interface.
"""
@@ -1452,7 +1469,10 @@ class Interface(Control):
# We must ignore any return codes. This is required to enable
# DHCPv6-PD for interfaces which are yet not up and running.
- return self._popen(f'systemctl restart {systemd_service}')
+ if (vrf_changed or
+ ('dhcpv6_options_changed' in self.config) or
+ (not is_systemd_service_active(systemd_service))):
+ return self._popen(f'systemctl restart {systemd_service}')
else:
if is_systemd_service_active(systemd_service):
self._cmd(f'systemctl stop {systemd_service}')
@@ -1669,30 +1689,31 @@ class Interface(Control):
else:
self.del_addr(addr)
- # start DHCPv6 client when only PD was configured
- if dhcpv6pd:
- self.set_dhcpv6(True)
-
# XXX: Bind interface to given VRF or unbind it if vrf is not set. Unbinding
# will call 'ip link set dev eth0 nomaster' which will also drop the
# interface out of any bridge or bond - thus this is checked before.
+ vrf_changed = False
if 'is_bond_member' in config:
bond_if = next(iter(config['is_bond_member']))
tmp = get_interface_config(config['ifname'])
if 'master' in tmp and tmp['master'] != bond_if:
- self.set_vrf('')
+ vrf_changed = self.set_vrf('')
elif 'is_bridge_member' in config:
bridge_if = next(iter(config['is_bridge_member']))
tmp = get_interface_config(config['ifname'])
if 'master' in tmp and tmp['master'] != bridge_if:
- self.set_vrf('')
+ vrf_changed = self.set_vrf('')
else:
- self.set_vrf(config.get('vrf', ''))
+ vrf_changed = self.set_vrf(config.get('vrf', ''))
+
+ # start DHCPv6 client when only PD was configured
+ if dhcpv6pd:
+ self.set_dhcpv6(True, vrf_changed=vrf_changed)
# Add this section after vrf T4331
for addr in new_addr:
- self.add_addr(addr)
+ self.add_addr(addr, vrf_changed=vrf_changed)
# Configure MSS value for IPv4 TCP connections
tmp = dict_search('ip.adjust_mss', config)
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index 341fd32ff..f5217aecb 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -82,6 +82,84 @@ class WireGuardOperational(Operational):
}
return output
+ def show_interface(self):
+ from vyos.config import Config
+
+ c = Config()
+
+ wgdump = self._dump().get(self.config['ifname'], None)
+
+ c.set_level(['interfaces', 'wireguard', self.config['ifname']])
+ description = c.return_effective_value(['description'])
+ ips = c.return_effective_values(['address'])
+ hostnames = c.return_effective_values(['host-name'])
+
+ answer = 'interface: {}\n'.format(self.config['ifname'])
+ if description:
+ answer += ' description: {}\n'.format(description)
+ if ips:
+ answer += ' address: {}\n'.format(', '.join(ips))
+ if hostnames:
+ answer += ' hostname: {}\n'.format(', '.join(hostnames))
+
+ answer += ' public key: {}\n'.format(wgdump['public_key'])
+ answer += ' private key: (hidden)\n'
+ answer += ' listening port: {}\n'.format(wgdump['listen_port'])
+ answer += '\n'
+
+ for peer in c.list_effective_nodes(['peer']):
+ if wgdump['peers']:
+ pubkey = c.return_effective_value(['peer', peer, 'public-key'])
+ if pubkey in wgdump['peers']:
+ wgpeer = wgdump['peers'][pubkey]
+
+ answer += ' peer: {}\n'.format(peer)
+ answer += ' public key: {}\n'.format(pubkey)
+
+ """ figure out if the tunnel is recently active or not """
+ status = 'inactive'
+ if wgpeer['latest_handshake'] is None:
+ """ no handshake ever """
+ status = 'inactive'
+ else:
+ if int(wgpeer['latest_handshake']) > 0:
+ delta = timedelta(
+ seconds=int(time.time() - wgpeer['latest_handshake'])
+ )
+ answer += ' latest handshake: {}\n'.format(delta)
+ if time.time() - int(wgpeer['latest_handshake']) < (60 * 5):
+ """ Five minutes and the tunnel is still active """
+ status = 'active'
+ else:
+ """ it's been longer than 5 minutes """
+ status = 'inactive'
+ elif int(wgpeer['latest_handshake']) == 0:
+ """ no handshake ever """
+ status = 'inactive'
+ answer += ' status: {}\n'.format(status)
+
+ if wgpeer['endpoint'] is not None:
+ answer += ' endpoint: {}\n'.format(wgpeer['endpoint'])
+
+ if wgpeer['allowed_ips'] is not None:
+ answer += ' allowed ips: {}\n'.format(
+ ','.join(wgpeer['allowed_ips']).replace(',', ', ')
+ )
+
+ if wgpeer['transfer_rx'] > 0 or wgpeer['transfer_tx'] > 0:
+ rx_size = size(wgpeer['transfer_rx'], system=alternative)
+ tx_size = size(wgpeer['transfer_tx'], system=alternative)
+ answer += ' transfer: {} received, {} sent\n'.format(
+ rx_size, tx_size
+ )
+
+ if wgpeer['persistent_keepalive'] is not None:
+ answer += ' persistent keepalive: every {} seconds\n'.format(
+ wgpeer['persistent_keepalive']
+ )
+ answer += '\n'
+ return answer
+
def get_latest_handshakes(self):
"""Get latest handshake time for each peer"""
output = {}
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index 65e2d99b4..c7947af3e 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -384,6 +384,41 @@ def kea_get_leases(inet):
return leases['arguments']['leases']
+def kea_add_lease(
+ inet,
+ ip_address,
+ host_name=None,
+ mac_address=None,
+ iaid=None,
+ duid=None,
+ subnet_id=None,
+):
+ args = {'ip-address': ip_address}
+
+ if host_name:
+ args['hostname'] = host_name
+
+ if subnet_id:
+ args['subnet-id'] = subnet_id
+
+ # IPv4 requires MAC address, IPv6 requires either MAC address or DUID
+ if mac_address:
+ args['hw-address'] = mac_address
+ if duid:
+ args['duid'] = duid
+
+ # IPv6 requires IAID
+ if inet == '6' and iaid:
+ args['iaid'] = iaid
+
+ result = _ctrl_socket_command(inet, f'lease{inet}-add', args)
+
+ if result and 'result' in result:
+ return result['result'] == 0
+
+ return False
+
+
def kea_delete_lease(inet, ip_address):
args = {'ip-address': ip_address}
@@ -430,6 +465,32 @@ def kea_get_pool_from_subnet_id(config, inet, subnet_id):
return None
+def kea_get_domain_from_subnet_id(config, inet, subnet_id):
+ shared_networks = dict_search_args(
+ config, 'arguments', f'Dhcp{inet}', 'shared-networks'
+ )
+
+ if not shared_networks:
+ return None
+
+ for network in shared_networks:
+ if f'subnet{inet}' not in network:
+ continue
+
+ for subnet in network[f'subnet{inet}']:
+ if 'id' in subnet and int(subnet['id']) == int(subnet_id):
+ for option in subnet['option-data']:
+ if option['name'] == 'domain-name':
+ return option['data']
+
+ # domain-name is not found in subnet, fallback to shared-network pool option
+ for option in network['option-data']:
+ if option['name'] == 'domain-name':
+ return option['data']
+
+ return None
+
+
def kea_get_static_mappings(config, inet, pools=[]) -> list:
"""
Get DHCP static mapping from active Kea DHCPv4 or DHCPv6 configuration
@@ -491,6 +552,11 @@ def kea_get_server_leases(config, inet, pools=[], state=[], origin=None) -> list
if config
else '-'
)
+ data_lease['domain'] = (
+ kea_get_domain_from_subnet_id(config, inet, lease['subnet-id'])
+ if config
+ else ''
+ )
data_lease['end'] = (
lease['expire_time'].timestamp() if lease['expire_time'] else None
)
diff --git a/python/vyos/template.py b/python/vyos/template.py
index be9f781a6..e75db1a8d 100755
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -612,12 +612,17 @@ def nft_default_rule(fw_conf, fw_name, family):
return " ".join(output)
@register_filter('nft_state_policy')
-def nft_state_policy(conf, state):
+def nft_state_policy(conf, state, bridge=False):
out = [f'ct state {state}']
+ action = conf['action'] if 'action' in conf else None
+
+ if bridge and action == 'reject':
+ action = 'drop' # T7148 - Bridge cannot use reject
+
if 'log' in conf:
log_state = state[:3].upper()
- log_action = (conf['action'] if 'action' in conf else 'accept')[:1].upper()
+ log_action = (action if action else 'accept')[:1].upper()
out.append(f'log prefix "[STATE-POLICY-{log_state}-{log_action}]"')
if 'log_level' in conf:
@@ -626,8 +631,8 @@ def nft_state_policy(conf, state):
out.append('counter')
- if 'action' in conf:
- out.append(conf['action'])
+ if action:
+ out.append(action)
return " ".join(out)
@@ -779,6 +784,11 @@ def conntrack_ct_policy(protocol_conf):
return ", ".join(output)
+@register_filter('wlb_nft_rule')
+def wlb_nft_rule(rule_conf, rule_id, local=False, exclude=False, limit=False, weight=None, health_state=None, action=None, restore_mark=False):
+ from vyos.wanloadbalance import nft_rule as wlb_nft_rule
+ return wlb_nft_rule(rule_conf, rule_id, local, exclude, limit, weight, health_state, action, restore_mark)
+
@register_filter('range_to_regex')
def range_to_regex(num_range):
"""Convert range of numbers or list of ranges
diff --git a/python/vyos/utils/cpu.py b/python/vyos/utils/cpu.py
index 3bea5ac12..8ace77d15 100644
--- a/python/vyos/utils/cpu.py
+++ b/python/vyos/utils/cpu.py
@@ -99,3 +99,18 @@ def get_core_count():
core_count += 1
return core_count
+
+
+def get_available_cpus():
+ """ List of cpus with ids that are available in the system
+ Uses 'lscpu' command
+
+ Returns: list[dict[str, str | int | bool]]: cpus details
+ """
+ import json
+
+ from vyos.utils.process import cmd
+
+ out = json.loads(cmd('lscpu --extended -b --json'))
+
+ return out['cpus']
diff --git a/python/vyos/utils/misc.py b/python/vyos/utils/misc.py
index ac8011b8d..d82655914 100644
--- a/python/vyos/utils/misc.py
+++ b/python/vyos/utils/misc.py
@@ -52,7 +52,7 @@ def install_into_config(conf, config_paths, override_prompt=True):
continue
try:
- cmd(f'/usr/libexec/vyos/vyconf/vy_set {path}')
+ cmd(f'/opt/vyatta/sbin/my_set {path}')
count += 1
except:
failed.append(path)
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py
index 054088325..121b6e240 100644
--- a/python/vyos/utils/process.py
+++ b/python/vyos/utils/process.py
@@ -83,7 +83,7 @@ def popen(command, flag='', shell=None, input=None, timeout=None, env=None,
)
wrapper = get_wrapper(vrf, netns, auth)
- command = f'{wrapper} {command}'
+ command = f'{wrapper} {command}' if wrapper else command
cmd_msg = f"cmd '{command}'"
debug.message(cmd_msg, flag)
diff --git a/python/vyos/wanloadbalance.py b/python/vyos/wanloadbalance.py
new file mode 100644
index 000000000..62e109f21
--- /dev/null
+++ b/python/vyos/wanloadbalance.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from vyos.defaults import directories
+from vyos.utils.process import run
+
+dhclient_lease = 'dhclient_{0}.lease'
+
+def nft_rule(rule_conf, rule_id, local=False, exclude=False, limit=False, weight=None, health_state=None, action=None, restore_mark=False):
+ output = []
+
+ if 'inbound_interface' in rule_conf:
+ ifname = rule_conf['inbound_interface']
+ if local and not exclude:
+ output.append(f'oifname != "{ifname}"')
+ elif not local:
+ output.append(f'iifname "{ifname}"')
+
+ if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
+ protocol = rule_conf['protocol']
+ operator = ''
+
+ if protocol[:1] == '!':
+ operator = '!='
+ protocol = protocol[1:]
+
+ if protocol == 'tcp_udp':
+ protocol = '{ tcp, udp }'
+
+ output.append(f'meta l4proto {operator} {protocol}')
+
+ for direction in ['source', 'destination']:
+ if direction not in rule_conf:
+ continue
+
+ direction_conf = rule_conf[direction]
+ prefix = direction[:1]
+
+ if 'address' in direction_conf:
+ operator = ''
+ address = direction_conf['address']
+ if address[:1] == '!':
+ operator = '!='
+ address = address[1:]
+ output.append(f'ip {prefix}addr {operator} {address}')
+
+ if 'port' in direction_conf:
+ operator = ''
+ port = direction_conf['port']
+ if port[:1] == '!':
+ operator = '!='
+ port = port[1:]
+ output.append(f'th {prefix}port {operator} {port}')
+
+ if 'source_based_routing' not in rule_conf and not restore_mark:
+ output.append('ct state new')
+
+ if limit and 'limit' in rule_conf and 'rate' in rule_conf['limit']:
+ output.append(f'limit rate {rule_conf["limit"]["rate"]}/{rule_conf["limit"]["period"]}')
+ if 'burst' in rule_conf['limit']:
+ output.append(f'burst {rule_conf["limit"]["burst"]} packets')
+
+ output.append('counter')
+
+ if restore_mark:
+ output.append('meta mark set ct mark')
+ elif weight:
+ weights, total_weight = wlb_weight_interfaces(rule_conf, health_state)
+ if len(weights) > 1: # Create weight-based verdict map
+ vmap_str = ", ".join(f'{weight} : jump wlb_mangle_isp_{ifname}' for ifname, weight in weights)
+ output.append(f'numgen random mod {total_weight} vmap {{ {vmap_str} }}')
+ elif len(weights) == 1: # Jump to single ISP
+ ifname, _ = weights[0]
+ output.append(f'jump wlb_mangle_isp_{ifname}')
+ else: # No healthy interfaces
+ return ""
+ elif action:
+ output.append(action)
+
+ return " ".join(output)
+
+def wlb_weight_interfaces(rule_conf, health_state):
+ interfaces = []
+
+ for ifname, if_conf in rule_conf['interface'].items():
+ if ifname in health_state and health_state[ifname]['state']:
+ weight = int(if_conf.get('weight', 1))
+ interfaces.append((ifname, weight))
+
+ if not interfaces:
+ return [], 0
+
+ if 'failover' in rule_conf:
+ for ifpair in sorted(interfaces, key=lambda i: i[1], reverse=True):
+ return [ifpair], ifpair[1] # Return highest weight interface that is ACTIVE when in failover
+
+ total_weight = sum(weight for _, weight in interfaces)
+ out = []
+ start = 0
+ for ifname, weight in sorted(interfaces, key=lambda i: i[1]): # build weight ranges
+ end = start + weight - 1
+ out.append((ifname, f'{start}-{end}' if end > start else start))
+ start = weight
+
+ return out, total_weight
+
+def health_ping_host(host, ifname, count=1, wait_time=0):
+ cmd_str = f'ping -c {count} -W {wait_time} -I {ifname} {host}'
+ rc = run(cmd_str)
+ return rc == 0
+
+def health_ping_host_ttl(host, ifname, count=1, ttl_limit=0):
+ cmd_str = f'ping -c {count} -t {ttl_limit} -I {ifname} {host}'
+ rc = run(cmd_str)
+ return rc != 0
+
+def parse_dhcp_nexthop(ifname):
+ lease_file = os.path.join(directories['isc_dhclient_dir'], dhclient_lease.format(ifname))
+
+ if not os.path.exists(lease_file):
+ return False
+
+ with open(lease_file, 'r') as f:
+ for line in f.readlines():
+ data = line.replace('\n', '').split('=')
+ if data[0] == 'new_routers':
+ return data[1].replace("'", '').split(" ")[0]
+
+ return None
+
+def parse_ppp_nexthop(ifname):
+ nexthop_file = os.path.join(directories['ppp_nexthop_dir'], ifname)
+
+ if not os.path.exists(nexthop_file):
+ return False
+
+ with open(nexthop_file, 'r') as f:
+ return f.read()
diff --git a/smoketest/config-tests/basic-api-service b/smoketest/config-tests/basic-api-service
index 3f796f35d..ca10cf4e9 100644
--- a/smoketest/config-tests/basic-api-service
+++ b/smoketest/config-tests/basic-api-service
@@ -24,5 +24,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/basic-syslog b/smoketest/config-tests/basic-syslog
new file mode 100644
index 000000000..349d642fd
--- /dev/null
+++ b/smoketest/config-tests/basic-syslog
@@ -0,0 +1,25 @@
+set interfaces ethernet eth0 duplex 'auto'
+set interfaces ethernet eth0 speed 'auto'
+set interfaces ethernet eth1 address '172.16.33.154/24'
+set interfaces ethernet eth1 duplex 'auto'
+set interfaces ethernet eth1 speed 'auto'
+set interfaces ethernet eth1 vrf 'red'
+set system console device ttyS0 speed '115200'
+set system domain-name 'vyos-ci-test.net'
+set system host-name 'vyos'
+set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
+set system login user vyos authentication plaintext-password ''
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
+set system syslog marker interval '999'
+set system syslog preserve-fqdn
+set system syslog remote syslog01.vyos.net facility local7 level 'notice'
+set system syslog remote syslog01.vyos.net port '8000'
+set system syslog remote syslog01.vyos.net vrf 'red'
+set system syslog remote syslog02.vyos.net facility all level 'debug'
+set system syslog remote syslog02.vyos.net format include-timezone
+set system syslog remote syslog02.vyos.net format octet-counted
+set system syslog remote syslog02.vyos.net port '8001'
+set system syslog remote syslog02.vyos.net protocol 'tcp'
+set system syslog remote syslog02.vyos.net vrf 'red'
+set vrf name red table '12321'
diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos
index 6ff28ec2e..4793e069e 100644
--- a/smoketest/config-tests/basic-vyos
+++ b/smoketest/config-tests/basic-vyos
@@ -92,12 +92,14 @@ set system login user vyos authentication plaintext-password ''
set system name-server '192.168.0.1'
set system syslog console facility all level 'emerg'
set system syslog console facility mail level 'info'
-set system syslog global facility all level 'info'
-set system syslog global facility auth level 'info'
-set system syslog global facility local7 level 'debug'
-set system syslog global preserve-fqdn
-set system syslog host syslog.vyos.net facility auth level 'warning'
-set system syslog host syslog.vyos.net facility local7 level 'notice'
-set system syslog host syslog.vyos.net format octet-counted
-set system syslog host syslog.vyos.net port '8000'
+set system syslog local facility all level 'info'
+set system syslog local facility auth level 'info'
+set system syslog local facility local7 level 'debug'
+set system syslog marker interval '1000'
+set system syslog preserve-fqdn
+set system syslog remote syslog.vyos.net facility auth level 'warning'
+set system syslog remote syslog.vyos.net facility local7 level 'notice'
+set system syslog remote syslog.vyos.net format octet-counted
+set system syslog remote syslog.vyos.net port '8000'
+set system syslog remote syslog.vyos.net protocol 'tcp'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/basic-vyos-no-ntp b/smoketest/config-tests/basic-vyos-no-ntp
index a18260108..f00dea5d4 100644
--- a/smoketest/config-tests/basic-vyos-no-ntp
+++ b/smoketest/config-tests/basic-vyos-no-ntp
@@ -48,6 +48,6 @@ set system host-name 'no-ntp'
set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0'
set system login user vyos authentication plaintext-password ''
set system name-server '172.16.254.30'
-set system syslog global facility all level 'debug'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'debug'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/bgp-azure-ipsec-gateway b/smoketest/config-tests/bgp-azure-ipsec-gateway
index bbd7b961f..0d683c921 100644
--- a/smoketest/config-tests/bgp-azure-ipsec-gateway
+++ b/smoketest/config-tests/bgp-azure-ipsec-gateway
@@ -135,10 +135,10 @@ set system login user vyos authentication plaintext-password ''
set system logs logrotate messages max-size '20'
set system logs logrotate messages rotate '10'
set system name-server '192.0.2.254'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
-set system syslog host 10.0.9.188 facility all level 'info'
-set system syslog host 10.0.9.188 protocol 'udp'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
+set system syslog remote 10.0.9.188 facility all level 'info'
+set system syslog remote 10.0.9.188 protocol 'udp'
set system time-zone 'Europe/Berlin'
set vpn ipsec authentication psk peer_51-105-0-1 id '51.105.0.1'
set vpn ipsec authentication psk peer_51-105-0-1 id '192.0.2.189'
diff --git a/smoketest/config-tests/bgp-bfd-communities b/smoketest/config-tests/bgp-bfd-communities
index 6eee0137e..06e412c55 100644
--- a/smoketest/config-tests/bgp-bfd-communities
+++ b/smoketest/config-tests/bgp-bfd-communities
@@ -196,6 +196,6 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/bgp-big-as-cloud b/smoketest/config-tests/bgp-big-as-cloud
index d6c17b3d2..f71a51be3 100644
--- a/smoketest/config-tests/bgp-big-as-cloud
+++ b/smoketest/config-tests/bgp-big-as-cloud
@@ -847,6 +847,6 @@ set system name-server '192.0.2.2'
set system sflow interface 'eth0.4088'
set system sflow interface 'eth0.4089'
set system sflow server 1.2.3.4 port '1234'
-set system syslog global facility all level 'all'
-set system syslog global preserve-fqdn
+set system syslog local facility all level 'all'
+set system syslog preserve-fqdn
set system time-zone 'Europe/Zurich'
diff --git a/smoketest/config-tests/bgp-dmvpn-hub b/smoketest/config-tests/bgp-dmvpn-hub
index 99f3799a4..f9ceba11c 100644
--- a/smoketest/config-tests/bgp-dmvpn-hub
+++ b/smoketest/config-tests/bgp-dmvpn-hub
@@ -50,8 +50,8 @@ set system login user vyos authentication plaintext-password ''
set system name-server '1.1.1.1'
set system name-server '8.8.8.8'
set system name-server '9.9.9.9'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set vpn ipsec esp-group ESP-DMVPN lifetime '1800'
set vpn ipsec esp-group ESP-DMVPN mode 'transport'
set vpn ipsec esp-group ESP-DMVPN pfs 'dh-group2'
diff --git a/smoketest/config-tests/bgp-dmvpn-spoke b/smoketest/config-tests/bgp-dmvpn-spoke
index e4fb82a0e..a98275ba4 100644
--- a/smoketest/config-tests/bgp-dmvpn-spoke
+++ b/smoketest/config-tests/bgp-dmvpn-spoke
@@ -56,8 +56,8 @@ set system login user vyos authentication plaintext-password ''
set system name-server '1.1.1.1'
set system name-server '8.8.8.8'
set system name-server '9.9.9.9'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set vpn ipsec esp-group ESP-DMVPN lifetime '1800'
set vpn ipsec esp-group ESP-DMVPN mode 'transport'
set vpn ipsec esp-group ESP-DMVPN pfs 'dh-group2'
diff --git a/smoketest/config-tests/bgp-evpn-l2vpn-leaf b/smoketest/config-tests/bgp-evpn-l2vpn-leaf
index 315cb9e06..5e42a269e 100644
--- a/smoketest/config-tests/bgp-evpn-l2vpn-leaf
+++ b/smoketest/config-tests/bgp-evpn-l2vpn-leaf
@@ -48,8 +48,8 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set vrf name MGMT protocols static route 0.0.0.0/0 next-hop 192.0.2.62
set vrf name MGMT protocols static route6 ::/0 next-hop 2001:db8::1
set vrf name MGMT table '1000'
diff --git a/smoketest/config-tests/bgp-evpn-l2vpn-spine b/smoketest/config-tests/bgp-evpn-l2vpn-spine
index dee29e021..e6d876af6 100644
--- a/smoketest/config-tests/bgp-evpn-l2vpn-spine
+++ b/smoketest/config-tests/bgp-evpn-l2vpn-spine
@@ -41,8 +41,8 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set vrf name MGMT protocols static route 0.0.0.0/0 next-hop 192.0.2.62
set vrf name MGMT protocols static route6 ::/0 next-hop 2001:db8::1
set vrf name MGMT table '1000'
diff --git a/smoketest/config-tests/bgp-evpn-l3vpn-pe-router b/smoketest/config-tests/bgp-evpn-l3vpn-pe-router
index 7a2ec9f91..f867c221e 100644
--- a/smoketest/config-tests/bgp-evpn-l3vpn-pe-router
+++ b/smoketest/config-tests/bgp-evpn-l3vpn-pe-router
@@ -101,8 +101,8 @@ set system login user vyos authentication plaintext-password ''
set system name-server '192.0.2.251'
set system name-server '192.0.2.252'
set system name-server '2001:db8::1'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set vrf name blue protocols bgp address-family ipv4-unicast redistribute connected
set vrf name blue protocols bgp address-family l2vpn-evpn advertise ipv4 unicast
set vrf name blue protocols bgp system-as '100'
diff --git a/smoketest/config-tests/bgp-medium-confederation b/smoketest/config-tests/bgp-medium-confederation
index 582e28047..71797fe93 100644
--- a/smoketest/config-tests/bgp-medium-confederation
+++ b/smoketest/config-tests/bgp-medium-confederation
@@ -69,5 +69,5 @@ set system host-name 'vyos'
set system ip protocol bgp route-map 'DEFAULT-ZEBRA-IN'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'notice'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'notice'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/bgp-rpki b/smoketest/config-tests/bgp-rpki
index 44e95ae98..657d4abcc 100644
--- a/smoketest/config-tests/bgp-rpki
+++ b/smoketest/config-tests/bgp-rpki
@@ -13,6 +13,7 @@ set policy route-map ebgp-transit-rpki rule 30 set local-preference '100'
set policy route-map ebgp-transit-rpki rule 40 action 'permit'
set policy route-map ebgp-transit-rpki rule 40 set extcommunity rt '192.0.2.100:100'
set policy route-map ebgp-transit-rpki rule 40 set extcommunity soo '64500:100'
+set protocols bgp address-family ipv4-unicast redistribute table 100
set protocols bgp neighbor 1.2.3.4 address-family ipv4-unicast nexthop-self
set protocols bgp neighbor 1.2.3.4 address-family ipv4-unicast route-map import 'ebgp-transit-rpki'
set protocols bgp neighbor 1.2.3.4 remote-as '10'
@@ -39,5 +40,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/bgp-small-internet-exchange b/smoketest/config-tests/bgp-small-internet-exchange
index a9dce4dd5..2adb3fbb5 100644
--- a/smoketest/config-tests/bgp-small-internet-exchange
+++ b/smoketest/config-tests/bgp-small-internet-exchange
@@ -205,5 +205,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/bgp-small-ipv4-unicast b/smoketest/config-tests/bgp-small-ipv4-unicast
index b8c0e1246..f8820cb3c 100644
--- a/smoketest/config-tests/bgp-small-ipv4-unicast
+++ b/smoketest/config-tests/bgp-small-ipv4-unicast
@@ -28,5 +28,5 @@ set system domain-name 'vyos.net'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'notice'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'notice'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/cluster-basic b/smoketest/config-tests/cluster-basic
index 744c117eb..871b40bbb 100644
--- a/smoketest/config-tests/cluster-basic
+++ b/smoketest/config-tests/cluster-basic
@@ -16,6 +16,6 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Antarctica/South_Pole'
diff --git a/smoketest/config-tests/dialup-router-complex b/smoketest/config-tests/dialup-router-complex
index c693cc382..12edcfef2 100644
--- a/smoketest/config-tests/dialup-router-complex
+++ b/smoketest/config-tests/dialup-router-complex
@@ -695,6 +695,7 @@ set service dns forwarding ignore-hosts-file
set service dns forwarding listen-address '172.16.254.30'
set service dns forwarding listen-address '172.31.0.254'
set service dns forwarding negative-ttl '60'
+set service lldp interface pppoe0 mode 'disable'
set service lldp legacy-protocols cdp
set service lldp snmp
set service mdns repeater interface 'eth0.35'
@@ -734,7 +735,7 @@ set system name-server '172.16.254.30'
set system option ctrl-alt-delete 'ignore'
set system option reboot-on-panic
set system option startup-beep
-set system syslog global facility all level 'debug'
-set system syslog global facility local7 level 'debug'
-set system syslog host 172.16.100.1 facility all level 'warning'
+set system syslog local facility all level 'debug'
+set system syslog local facility local7 level 'debug'
+set system syslog remote 172.16.100.1 facility all level 'warning'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn
index d6b00c678..ba3ed29f4 100644
--- a/smoketest/config-tests/dialup-router-medium-vpn
+++ b/smoketest/config-tests/dialup-router-medium-vpn
@@ -314,9 +314,9 @@ set system static-host-mapping host-name host107.vyos.net inet '192.168.0.107'
set system static-host-mapping host-name host109.vyos.net inet '192.168.0.109'
set system sysctl parameter net.core.default_qdisc value 'fq'
set system sysctl parameter net.ipv4.tcp_congestion_control value 'bbr'
-set system syslog global facility all level 'info'
-set system syslog host 192.168.0.252 facility all level 'debug'
-set system syslog host 192.168.0.252 protocol 'udp'
+set system syslog local facility all level 'info'
+set system syslog remote 192.168.0.252 facility all level 'debug'
+set system syslog remote 192.168.0.252 protocol 'udp'
set system task-scheduler task Update-Blacklists executable path '/config/scripts/vyos-foo-update.script'
set system task-scheduler task Update-Blacklists interval '3h'
set system time-zone 'Pacific/Auckland'
diff --git a/smoketest/config-tests/dialup-router-wireguard-ipv6 b/smoketest/config-tests/dialup-router-wireguard-ipv6
index 3e298fb82..269e9d722 100644
--- a/smoketest/config-tests/dialup-router-wireguard-ipv6
+++ b/smoketest/config-tests/dialup-router-wireguard-ipv6
@@ -691,7 +691,7 @@ set system option ctrl-alt-delete 'ignore'
set system option performance 'network-latency'
set system option reboot-on-panic
set system option startup-beep
-set system syslog global facility all level 'debug'
-set system syslog global facility local7 level 'debug'
-set system syslog host 172.16.100.1 facility all level 'warning'
+set system syslog local facility all level 'debug'
+set system syslog local facility local7 level 'debug'
+set system syslog remote 172.16.100.1 facility all level 'warning'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/egp-igp-route-maps b/smoketest/config-tests/egp-igp-route-maps
index fc46d25ff..222325cd7 100644
--- a/smoketest/config-tests/egp-igp-route-maps
+++ b/smoketest/config-tests/egp-igp-route-maps
@@ -42,5 +42,5 @@ set system login user vyos authentication plaintext-password ''
set system logs logrotate messages max-size '1'
set system logs logrotate messages rotate '5'
set system name-server '192.168.0.1'
-set system syslog global facility all level 'info'
+set system syslog local facility all level 'info'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/igmp-pim-small b/smoketest/config-tests/igmp-pim-small
index 909c3d67b..06051af41 100644
--- a/smoketest/config-tests/igmp-pim-small
+++ b/smoketest/config-tests/igmp-pim-small
@@ -32,6 +32,6 @@ set system domain-name 'vyos.io'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/ipoe-server b/smoketest/config-tests/ipoe-server
index f4a12f502..c21495ab2 100644
--- a/smoketest/config-tests/ipoe-server
+++ b/smoketest/config-tests/ipoe-server
@@ -44,5 +44,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/ipv6-disable b/smoketest/config-tests/ipv6-disable
index 40e34fa0c..5f906b5f7 100644
--- a/smoketest/config-tests/ipv6-disable
+++ b/smoketest/config-tests/ipv6-disable
@@ -27,5 +27,5 @@ set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX
set system login user vyos authentication plaintext-password ''
set system name-server '172.16.254.20'
set system name-server '172.16.254.30'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/isis-small b/smoketest/config-tests/isis-small
index b322f4e29..e61d0362e 100644
--- a/smoketest/config-tests/isis-small
+++ b/smoketest/config-tests/isis-small
@@ -39,6 +39,6 @@ set system login user vyos authentication plaintext-password ''
set service ntp server time1.vyos.net
set service ntp server time2.vyos.net
set service ntp server time3.vyos.net
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/nat-basic b/smoketest/config-tests/nat-basic
index 471add3b3..f1cc0121d 100644
--- a/smoketest/config-tests/nat-basic
+++ b/smoketest/config-tests/nat-basic
@@ -60,7 +60,7 @@ set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 range 0
set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 range 0 stop '192.168.189.254'
set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 subnet-id '1'
set service lldp interface all
-set service lldp interface eth1 disable
+set service lldp interface eth1 mode 'disable'
set service ntp allow-client address '192.168.189.0/24'
set service ntp listen-address '192.168.189.1'
set service ntp server time1.vyos.net
@@ -84,5 +84,5 @@ set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX
set system login user vyos authentication plaintext-password ''
set system name-server '1.1.1.1'
set system name-server '9.9.9.9'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/ospf-simple b/smoketest/config-tests/ospf-simple
index 355709448..4273e4b8f 100644
--- a/smoketest/config-tests/ospf-simple
+++ b/smoketest/config-tests/ospf-simple
@@ -20,5 +20,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'lab-vyos-r1'
set system login user vyos authentication encrypted-password '$6$R.OnGzfXSfl6J$Iba/hl9bmjBs0VPtZ2zdW.Snh/nHuvxUwi0R6ruypgW63iKEbicJH.uUst8xZCyByURblxRtjAC1lAnYfIt.b0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/ospf-small b/smoketest/config-tests/ospf-small
index a7f8b682c..af69e5702 100644
--- a/smoketest/config-tests/ospf-small
+++ b/smoketest/config-tests/ospf-small
@@ -77,6 +77,6 @@ set system sysctl parameter net.ipv4.igmp_max_memberships value '5'
set system sysctl parameter net.ipv4.ipfrag_time value '4'
set system sysctl parameter net.mpls.default_ttl value '10'
set system sysctl parameter net.mpls.ip_ttl_propagate value '0'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/pppoe-server b/smoketest/config-tests/pppoe-server
index 34fbea215..e488fc746 100644
--- a/smoketest/config-tests/pppoe-server
+++ b/smoketest/config-tests/pppoe-server
@@ -43,5 +43,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/qos-basic b/smoketest/config-tests/qos-basic
index 0e198b80c..655a5794e 100644
--- a/smoketest/config-tests/qos-basic
+++ b/smoketest/config-tests/qos-basic
@@ -71,5 +71,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/rip-router b/smoketest/config-tests/rip-router
index 829aafbd5..d22f424a5 100644
--- a/smoketest/config-tests/rip-router
+++ b/smoketest/config-tests/rip-router
@@ -79,5 +79,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/rpki-only b/smoketest/config-tests/rpki-only
index dcbc7673d..f3e2a74b9 100644
--- a/smoketest/config-tests/rpki-only
+++ b/smoketest/config-tests/rpki-only
@@ -38,5 +38,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'debug'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'debug'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/static-route-basic b/smoketest/config-tests/static-route-basic
index d2d33d043..a6135d2c4 100644
--- a/smoketest/config-tests/static-route-basic
+++ b/smoketest/config-tests/static-route-basic
@@ -32,6 +32,6 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Asia/Macau'
diff --git a/smoketest/config-tests/tunnel-broker b/smoketest/config-tests/tunnel-broker
index ee6301c85..5518c303b 100644
--- a/smoketest/config-tests/tunnel-broker
+++ b/smoketest/config-tests/tunnel-broker
@@ -71,5 +71,5 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
diff --git a/smoketest/config-tests/vpn-openconnect-sstp b/smoketest/config-tests/vpn-openconnect-sstp
index 28d7d5daa..e7969f633 100644
--- a/smoketest/config-tests/vpn-openconnect-sstp
+++ b/smoketest/config-tests/vpn-openconnect-sstp
@@ -16,8 +16,8 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set vpn openconnect authentication local-users username test password 'test'
set vpn openconnect authentication mode local 'password'
set vpn openconnect network-settings client-ip-settings subnet '192.168.160.0/24'
diff --git a/smoketest/config-tests/vrf-basic b/smoketest/config-tests/vrf-basic
index 1d2874a60..0c4e49c52 100644
--- a/smoketest/config-tests/vrf-basic
+++ b/smoketest/config-tests/vrf-basic
@@ -35,8 +35,8 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Europe/Berlin'
set vrf name green protocols static route 20.0.0.0/8 next-hop 1.1.1.1 interface 'eth1'
set vrf name green protocols static route 20.0.0.0/8 next-hop 1.1.1.1 vrf 'default'
diff --git a/smoketest/config-tests/vrf-bgp-pppoe-underlay b/smoketest/config-tests/vrf-bgp-pppoe-underlay
index bd64c914a..e3c765a9a 100644
--- a/smoketest/config-tests/vrf-bgp-pppoe-underlay
+++ b/smoketest/config-tests/vrf-bgp-pppoe-underlay
@@ -143,8 +143,8 @@ set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
set system name-server '192.168.0.1'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Europe/Berlin'
set vrf bind-to-all
set vrf name vyos-test-01 protocols bgp address-family ipv4-unicast network 100.64.50.0/23
diff --git a/smoketest/config-tests/vrf-ospf b/smoketest/config-tests/vrf-ospf
index fd14615e0..53207d565 100644
--- a/smoketest/config-tests/vrf-ospf
+++ b/smoketest/config-tests/vrf-ospf
@@ -28,8 +28,8 @@ set system console device ttyS0 speed '115200'
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
set system login user vyos authentication plaintext-password ''
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system time-zone 'Europe/Berlin'
set vrf name blue protocols ospf area 0 network '172.18.201.0/24'
set vrf name blue protocols ospf interface eth2 authentication md5 key-id 30 md5-key 'vyoskey456'
diff --git a/smoketest/config-tests/wireless-basic b/smoketest/config-tests/wireless-basic
index d9e6c8fac..e424b2b0f 100644
--- a/smoketest/config-tests/wireless-basic
+++ b/smoketest/config-tests/wireless-basic
@@ -20,6 +20,6 @@ set system console device ttyS0 speed '115200'
set system domain-name 'dev.vyos.net'
set system host-name 'WR1'
set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
-set system syslog global facility all level 'info'
-set system syslog global facility local7 level 'debug'
+set system syslog local facility all level 'info'
+set system syslog local facility local7 level 'debug'
set system wireless country-code 'es'
diff --git a/smoketest/configs/basic-syslog b/smoketest/configs/basic-syslog
new file mode 100644
index 000000000..9336b73bc
--- /dev/null
+++ b/smoketest/configs/basic-syslog
@@ -0,0 +1,70 @@
+interfaces {
+ ethernet eth0 {
+ duplex "auto"
+ speed "auto"
+ }
+ ethernet eth1 {
+ address 172.16.33.154/24
+ duplex auto
+ speed auto
+ vrf red
+ }
+}
+system {
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ domain-name vyos-ci-test.net
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0
+ plaintext-password ""
+ }
+ }
+ }
+ syslog {
+ global {
+ facility all {
+ level info
+ }
+ facility local7 {
+ level debug
+ }
+ marker {
+ interval 999
+ }
+ preserve-fqdn
+ }
+ host syslog01.vyos.net {
+ facility local7 {
+ level notice
+ }
+ port 8000
+ }
+ host syslog02.vyos.net {
+ facility all {
+ level debug
+ }
+ format {
+ include-timezone
+ octet-counted
+ }
+ protocol tcp
+ port 8001
+ }
+ vrf red
+ }
+}
+vrf {
+ name red {
+ table 12321
+ }
+}
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@8:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:reverse-proxy@1:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2"
+// Release version: 1.4.0
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index 242f3d1de..a6cd3b6e1 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -236,6 +236,9 @@ system {
facility security {
level info
}
+ marker {
+ interval 1000
+ }
preserve-fqdn
}
host syslog.vyos.net {
@@ -251,6 +254,7 @@ system {
format {
octet-counted
}
+ protocol tcp
port 8000
}
}
diff --git a/smoketest/configs/bgp-rpki b/smoketest/configs/bgp-rpki
index 5588f15c9..2d136d545 100644
--- a/smoketest/configs/bgp-rpki
+++ b/smoketest/configs/bgp-rpki
@@ -46,6 +46,13 @@ policy {
}
protocols {
bgp 64500 {
+ address-family {
+ ipv4-unicast {
+ redistribute {
+ table 100
+ }
+ }
+ }
neighbor 1.2.3.4 {
address-family {
ipv4-unicast {
diff --git a/smoketest/configs/dialup-router-complex b/smoketest/configs/dialup-router-complex
index aa9837fe9..018379bcd 100644
--- a/smoketest/configs/dialup-router-complex
+++ b/smoketest/configs/dialup-router-complex
@@ -1392,6 +1392,9 @@ service {
}
}
lldp {
+ interface pppoe0 {
+ disable
+ }
legacy-protocols {
cdp
}
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index c19bfcfe2..78c807d59 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -38,6 +38,7 @@ from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import is_ipv6_link_local
from vyos.utils.network import get_nft_vrf_zone_mapping
from vyos.xml_ref import cli_defined
+from vyos.xml_ref import default_value
dhclient_base_dir = directories['isc_dhclient_dir']
dhclient_process_name = 'dhclient'
@@ -282,6 +283,9 @@ class BasicInterfaceTest:
if not self._test_dhcp or not self._test_vrf:
self.skipTest('not supported')
+ cli_default_metric = default_value(self._base_path + [self._interfaces[0],
+ 'dhcp-options', 'default-route-distance'])
+
vrf_name = 'purple4'
self.cli_set(['vrf', 'name', vrf_name, 'table', '65000'])
@@ -307,7 +311,28 @@ class BasicInterfaceTest:
self.assertIn(str(dhclient_pid), vrf_pids)
# and the commandline has the appropriate options
cmdline = read_file(f'/proc/{dhclient_pid}/cmdline')
- self.assertIn('-e\x00IF_METRIC=210', cmdline) # 210 is the default value
+ self.assertIn(f'-e\x00IF_METRIC={cli_default_metric}', cmdline)
+
+ # T5103: remove interface from VRF instance and move DHCP client
+ # back to default VRF. This must restart the DHCP client process
+ for interface in self._interfaces:
+ self.cli_delete(self._base_path + [interface, 'vrf'])
+
+ self.cli_commit()
+
+ # Validate interface state
+ for interface in self._interfaces:
+ tmp = get_interface_vrf(interface)
+ self.assertEqual(tmp, 'default')
+ # Check if dhclient process runs
+ dhclient_pid = process_named_running(dhclient_process_name, cmdline=interface, timeout=10)
+ self.assertTrue(dhclient_pid)
+ # .. inside the appropriate VRF instance
+ vrf_pids = cmd(f'ip vrf pids {vrf_name}')
+ self.assertNotIn(str(dhclient_pid), vrf_pids)
+ # and the commandline has the appropriate options
+ cmdline = read_file(f'/proc/{dhclient_pid}/cmdline')
+ self.assertIn(f'-e\x00IF_METRIC={cli_default_metric}', cmdline)
self.cli_delete(['vrf', 'name', vrf_name])
@@ -341,6 +366,26 @@ class BasicInterfaceTest:
vrf_pids = cmd(f'ip vrf pids {vrf_name}')
self.assertIn(str(tmp), vrf_pids)
+ # T7135: remove interface from VRF instance and move DHCP client
+ # back to default VRF. This must restart the DHCP client process
+ for interface in self._interfaces:
+ self.cli_delete(self._base_path + [interface, 'vrf'])
+
+ self.cli_commit()
+
+ # Validate interface state
+ for interface in self._interfaces:
+ tmp = get_interface_vrf(interface)
+ self.assertEqual(tmp, 'default')
+
+ # Check if dhclient process runs
+ tmp = process_named_running(dhcp6c_process_name, cmdline=interface, timeout=10)
+ self.assertTrue(tmp)
+ # .. inside the appropriate VRF instance
+ vrf_pids = cmd(f'ip vrf pids {vrf_name}')
+ self.assertNotIn(str(tmp), vrf_pids)
+
+
self.cli_delete(['vrf', 'name', vrf_name])
def test_move_interface_between_vrf_instances(self):
diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py
index a54622700..edf940efd 100644
--- a/smoketest/scripts/cli/base_vyostest_shim.py
+++ b/smoketest/scripts/cli/base_vyostest_shim.py
@@ -75,10 +75,11 @@ class VyOSUnitTestSHIM:
cls._session.discard()
cls.fail(cls)
- def cli_set(self, config):
+ def cli_set(self, path, value=None):
if self.debug:
- print('set ' + ' '.join(config))
- self._session.set(config)
+ str = f'set {" ".join(path)} {value}' if value else f'set {" ".join(path)}'
+ print(str)
+ self._session.set(path, value)
def cli_delete(self, config):
if self.debug:
@@ -182,6 +183,15 @@ class VyOSUnitTestSHIM:
break
self.assertTrue(not matched if inverse else matched, msg=search)
+ def verify_nftables_chain_exists(self, table, chain, inverse=False):
+ try:
+ cmd(f'sudo nft list chain {table} {chain}')
+ if inverse:
+ self.fail(f'Chain exists: {table} {chain}')
+ except OSError:
+ if not inverse:
+ self.fail(f'Chain does not exist: {table} {chain}')
+
# Verify ip rule output
def verify_rules(self, rules_search, inverse=False, addr_family='inet'):
rule_output = cmd(f'ip -family {addr_family} rule show')
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 10301831e..33144c7fa 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -119,6 +119,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com'])
self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org'])
self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0'])
+ self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'pod-smoketest'])
self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'accept'])
@@ -133,6 +134,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'accept'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'group', '!smoketest_interface'])
+ # Create container network so test won't fail
+ self.cli_set(['container', 'network', 'smoketest', 'prefix', '10.0.0.0/24'])
+
self.cli_commit()
self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5')
@@ -654,6 +658,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip vyos_filter')
+ # T7148 - Ensure bridge rule reject -> drop
+ self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'reject'])
+ self.cli_commit()
+
+ self.verify_nftables([['ct state invalid', 'reject']], 'ip vyos_filter')
+ self.verify_nftables([['ct state invalid', 'drop']], 'bridge vyos_filter')
+
# Check conntrack is enabled from state-policy
self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
@@ -1167,7 +1178,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4'])
self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'jump'])
self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4'])
-
+
self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'action', 'jump'])
self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-out4'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump'])
@@ -1202,8 +1213,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'action', 'jump'])
self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'jump-target', 'smoketest-cycle-1'])
- # nft will fail to load cyclic jumps in any form, whether the rule is reachable or not.
- # It should be caught by conf validation.
+ # nft will fail to load cyclic jumps in any form, whether the rule is reachable or not.
+ # It should be caught by conf validation.
with self.assertRaises(ConfigSessionError):
self.cli_commit()
diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py
index 1a72f9dc4..f99fd0363 100755
--- a/smoketest/scripts/cli/test_interfaces_bonding.py
+++ b/smoketest/scripts/cli/test_interfaces_bonding.py
@@ -167,18 +167,25 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
def test_bonding_multi_use_member(self):
# Define available bonding hash policies
- for interface in ['bond10', 'bond20']:
+ bonds = ['bond10', 'bond20', 'bond30']
+ for interface in bonds:
for member in self._members:
self.cli_set(self._base_path + [interface, 'member', 'interface', member])
# check validate() - can not use the same member interfaces multiple times
with self.assertRaises(ConfigSessionError):
self.cli_commit()
-
- self.cli_delete(self._base_path + ['bond20'])
+ # only keep the first bond interface configuration
+ for interface in bonds[1:]:
+ self.cli_delete(self._base_path + [interface])
self.cli_commit()
+ bond = bonds[0]
+ member_ifaces = read_file(f'/sys/class/net/{bond}/bonding/slaves').split()
+ for member in self._members:
+ self.assertIn(member, member_ifaces)
+
def test_bonding_source_interface(self):
# Re-use member interface that is already a source-interface
bond = 'bond99'
diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py
index 54c981adc..4041b3ef3 100755
--- a/smoketest/scripts/cli/test_interfaces_bridge.py
+++ b/smoketest/scripts/cli/test_interfaces_bridge.py
@@ -158,6 +158,21 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
# verify member is assigned to the bridge
self.assertEqual(interface, tmp['master'])
+ def test_bridge_multi_use_member(self):
+ # Define available bonding hash policies
+ bridges = ['br10', 'br20', 'br30']
+ for interface in bridges:
+ for member in self._members:
+ self.cli_set(self._base_path + [interface, 'member', 'interface', member])
+
+ # check validate() - can not use the same member interfaces multiple times
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ # only keep the first bond interface configuration
+ for interface in bridges[1:]:
+ self.cli_delete(self._base_path + [interface])
+
+ self.cli_commit()
def test_add_remove_bridge_member(self):
# Add member interfaces to bridge and set STP cost/priority
diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py
index b2076b43b..05900a4ba 100755
--- a/smoketest/scripts/cli/test_interfaces_vxlan.py
+++ b/smoketest/scripts/cli/test_interfaces_vxlan.py
@@ -25,6 +25,7 @@ from vyos.utils.network import interface_exists
from vyos.utils.network import get_vxlan_vlan_tunnels
from vyos.utils.network import get_vxlan_vni_filter
from vyos.template import is_ipv6
+from vyos import ConfigError
from base_interfaces_test import BasicInterfaceTest
def convert_to_list(ranges_to_convert):
@@ -114,6 +115,32 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
self.assertEqual(Interface(interface).get_admin_state(), 'up')
ttl += 10
+
+ def test_vxlan_group_remote_error(self):
+ intf = 'vxlan60'
+ options = [
+ 'group 239.4.4.5',
+ 'mtu 1420',
+ 'remote 192.168.0.254',
+ 'source-address 192.168.0.1',
+ 'source-interface eth0',
+ 'vni 60'
+ ]
+ params = []
+ for option in options:
+ opts = option.split()
+ params.append(opts[0])
+ self.cli_set(self._base_path + [ intf ] + opts)
+
+ with self.assertRaises(ConfigSessionError) as cm:
+ self.cli_commit()
+
+ exception = cm.exception
+ self.assertIn('Both group and remote cannot be specified', str(exception))
+ for param in params:
+ self.cli_delete(self._base_path + [intf, param])
+
+
def test_vxlan_external(self):
interface = 'vxlan0'
source_address = '192.0.2.1'
diff --git a/smoketest/scripts/cli/test_load-balancing_haproxy.py b/smoketest/scripts/cli/test_load-balancing_haproxy.py
index 9f412aa95..077f1974f 100755
--- a/smoketest/scripts/cli/test_load-balancing_haproxy.py
+++ b/smoketest/scripts/cli/test_load-balancing_haproxy.py
@@ -521,5 +521,53 @@ class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase):
with self.assertRaises(ConfigSessionError) as e:
self.cli_commit()
+ def test_11_lb_haproxy_timeout(self):
+ t_default_check = '5'
+ t_default_client = '50'
+ t_default_connect = '10'
+ t_default_server ='50'
+ t_check = '4'
+ t_client = '300'
+ t_connect = '12'
+ t_server ='120'
+ t_front_client = '600'
+
+ self.base_config()
+ self.cli_commit()
+ # Check default timeout options
+ config_entries = (
+ f'timeout check {t_default_check}s',
+ f'timeout connect {t_default_connect}s',
+ f'timeout client {t_default_client}s',
+ f'timeout server {t_default_server}s',
+ )
+ # Check default timeout options
+ config = read_file(HAPROXY_CONF)
+ for config_entry in config_entries:
+ self.assertIn(config_entry, config)
+
+ # Set custom timeout options
+ self.cli_set(base_path + ['timeout', 'check', t_check])
+ self.cli_set(base_path + ['timeout', 'client', t_client])
+ self.cli_set(base_path + ['timeout', 'connect', t_connect])
+ self.cli_set(base_path + ['timeout', 'server', t_server])
+ self.cli_set(base_path + ['service', 'https_front', 'timeout', 'client', t_front_client])
+
+ self.cli_commit()
+
+ # Check custom timeout options
+ config_entries = (
+ f'timeout check {t_check}s',
+ f'timeout connect {t_connect}s',
+ f'timeout client {t_client}s',
+ f'timeout server {t_server}s',
+ f'timeout client {t_front_client}s',
+ )
+
+ # Check configured options
+ config = read_file(HAPROXY_CONF)
+ for config_entry in config_entries:
+ self.assertIn(config_entry, config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_load-balancing_wan.py b/smoketest/scripts/cli/test_load-balancing_wan.py
index 92b4000b8..32e5f6915 100755
--- a/smoketest/scripts/cli/test_load-balancing_wan.py
+++ b/smoketest/scripts/cli/test_load-balancing_wan.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022-2024 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -14,10 +14,13 @@
# 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 unittest
import time
from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.utils.file import chmod_755
+from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import cmd
@@ -54,6 +57,16 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ removed_chains = [
+ 'wlb_mangle_isp_veth1',
+ 'wlb_mangle_isp_veth2',
+ 'wlb_mangle_isp_eth201',
+ 'wlb_mangle_isp_eth202'
+ ]
+
+ for chain in removed_chains:
+ self.verify_nftables_chain_exists('ip vyos_wanloadbalance', chain, inverse=True)
+
def test_table_routes(self):
ns1 = 'ns201'
ns2 = 'ns202'
@@ -93,6 +106,7 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
cmd_in_netns(ns3, 'ip link set dev eth0 up')
# Set load-balancing configuration
+ self.cli_set(base_path + ['wan', 'hook', '/bin/true'])
self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2'])
self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1'])
self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1'])
@@ -102,7 +116,8 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3])
self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24'])
-
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface1])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2])
# commit changes
self.cli_commit()
@@ -127,7 +142,6 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
delete_netns(ns3)
def test_check_chains(self):
-
ns1 = 'nsA'
ns2 = 'nsB'
ns3 = 'nsC'
@@ -137,43 +151,28 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
container_iface1 = 'ceth0'
container_iface2 = 'ceth1'
container_iface3 = 'ceth2'
- mangle_isp1 = """table ip mangle {
- chain ISP_veth1 {
- counter ct mark set 0xc9
- counter meta mark set 0xc9
- counter accept
+ mangle_isp1 = """table ip vyos_wanloadbalance {
+ chain wlb_mangle_isp_veth1 {
+ meta mark set 0x000000c9 ct mark set 0x000000c9 counter accept
}
}"""
- mangle_isp2 = """table ip mangle {
- chain ISP_veth2 {
- counter ct mark set 0xca
- counter meta mark set 0xca
- counter accept
+ mangle_isp2 = """table ip vyos_wanloadbalance {
+ chain wlb_mangle_isp_veth2 {
+ meta mark set 0x000000ca ct mark set 0x000000ca counter accept
}
}"""
- mangle_prerouting = """table ip mangle {
- chain PREROUTING {
+ mangle_prerouting = """table ip vyos_wanloadbalance {
+ chain wlb_mangle_prerouting {
type filter hook prerouting priority mangle; policy accept;
- counter jump WANLOADBALANCE_PRE
- }
-}"""
- mangle_wanloadbalance_pre = """table ip mangle {
- chain WANLOADBALANCE_PRE {
- iifname "veth3" ip saddr 198.51.100.0/24 ct state new meta random & 2147483647 < 1073741824 counter jump ISP_veth1
- iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth2
+ iifname "veth3" ip saddr 198.51.100.0/24 ct state new limit rate 5/second burst 5 packets counter numgen random mod 11 vmap { 0 : jump wlb_mangle_isp_veth1, 1-10 : jump wlb_mangle_isp_veth2 }
iifname "veth3" ip saddr 198.51.100.0/24 counter meta mark set ct mark
}
}"""
- nat_wanloadbalance = """table ip nat {
- chain WANLOADBALANCE {
- ct mark 0xc9 counter snat to 203.0.113.10
- ct mark 0xca counter snat to 192.0.2.10
- }
-}"""
- nat_vyos_pre_snat_hook = """table ip nat {
- chain VYOS_PRE_SNAT_HOOK {
+ nat_wanloadbalance = """table ip vyos_wanloadbalance {
+ chain wlb_nat_postrouting {
type nat hook postrouting priority srcnat - 1; policy accept;
- counter jump WANLOADBALANCE
+ ct mark 0x000000c9 counter snat to 203.0.113.10
+ ct mark 0x000000ca counter snat to 192.0.2.10
}
}"""
@@ -214,7 +213,7 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3])
self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24'])
self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface1])
- self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2, 'weight', '10'])
# commit changes
self.cli_commit()
@@ -222,25 +221,19 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
time.sleep(5)
# Check mangle chains
- tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface1}')
+ tmp = cmd(f'sudo nft -s list chain ip vyos_wanloadbalance wlb_mangle_isp_{iface1}')
self.assertEqual(tmp, mangle_isp1)
- tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface2}')
+ tmp = cmd(f'sudo nft -s list chain ip vyos_wanloadbalance wlb_mangle_isp_{iface2}')
self.assertEqual(tmp, mangle_isp2)
- tmp = cmd(f'sudo nft -s list chain mangle PREROUTING')
+ tmp = cmd('sudo nft -s list chain ip vyos_wanloadbalance wlb_mangle_prerouting')
self.assertEqual(tmp, mangle_prerouting)
- tmp = cmd(f'sudo nft -s list chain mangle WANLOADBALANCE_PRE')
- self.assertEqual(tmp, mangle_wanloadbalance_pre)
-
# Check nat chains
- tmp = cmd(f'sudo nft -s list chain nat WANLOADBALANCE')
+ tmp = cmd('sudo nft -s list chain ip vyos_wanloadbalance wlb_nat_postrouting')
self.assertEqual(tmp, nat_wanloadbalance)
- tmp = cmd(f'sudo nft -s list chain nat VYOS_PRE_SNAT_HOOK')
- self.assertEqual(tmp, nat_vyos_pre_snat_hook)
-
# Delete veth interfaces and netns
for iface in [iface1, iface2, iface3]:
call(f'sudo ip link del dev {iface}')
@@ -249,6 +242,85 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
delete_netns(ns2)
delete_netns(ns3)
+ def test_criteria_failover_hook(self):
+ isp1_iface = 'eth0'
+ isp2_iface = 'eth1'
+ lan_iface = 'eth2'
+
+ hook_path = '/tmp/wlb_hook.sh'
+ hook_output_path = '/tmp/wlb_hook_output'
+ hook_script = f"""
+#!/bin/sh
+
+ifname=$WLB_INTERFACE_NAME
+state=$WLB_INTERFACE_STATE
+
+echo "$ifname - $state" > {hook_output_path}
+"""
+
+ write_file(hook_path, hook_script)
+ chmod_755(hook_path)
+
+ self.cli_set(['interfaces', 'ethernet', isp1_iface, 'address', '203.0.113.2/30'])
+ self.cli_set(['interfaces', 'ethernet', isp2_iface, 'address', '192.0.2.2/30'])
+ self.cli_set(['interfaces', 'ethernet', lan_iface, 'address', '198.51.100.2/30'])
+
+ self.cli_set(base_path + ['wan', 'hook', hook_path])
+ self.cli_set(base_path + ['wan', 'interface-health', isp1_iface, 'failure-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', isp1_iface, 'nexthop', '203.0.113.2'])
+ self.cli_set(base_path + ['wan', 'interface-health', isp1_iface, 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', isp2_iface, 'failure-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', isp2_iface, 'nexthop', '192.0.2.2'])
+ self.cli_set(base_path + ['wan', 'interface-health', isp2_iface, 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'rule', '5', 'exclude'])
+ self.cli_set(base_path + ['wan', 'rule', '5', 'inbound-interface', 'eth*'])
+ self.cli_set(base_path + ['wan', 'rule', '5', 'destination', 'address', '10.0.0.0/8'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'failover'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', lan_iface])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'protocol', 'udp'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'port', '53'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'destination', 'address', '192.0.2.0/24'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'destination', 'port', '53'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', isp1_iface])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', isp1_iface, 'weight', '10'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', isp2_iface])
+
+ # commit changes
+ self.cli_commit()
+
+ time.sleep(5)
+
+ # Verify isp1 + criteria
+
+ nftables_search = [
+ [f'iifname "eth*"', 'ip daddr 10.0.0.0/8', 'return'],
+ [f'iifname "{lan_iface}"', 'ip saddr 198.51.100.0/24', 'udp sport 53', 'ip daddr 192.0.2.0/24', 'udp dport 53', f'jump wlb_mangle_isp_{isp1_iface}']
+ ]
+
+ self.verify_nftables_chain(nftables_search, 'ip vyos_wanloadbalance', 'wlb_mangle_prerouting')
+
+ # Trigger failure on isp1 health check
+
+ self.cli_delete(['interfaces', 'ethernet', isp1_iface, 'address', '203.0.113.2/30'])
+ self.cli_commit()
+
+ time.sleep(10)
+
+ # Verify failover to isp2
+
+ nftables_search = [
+ [f'iifname "{lan_iface}"', f'jump wlb_mangle_isp_{isp2_iface}']
+ ]
+
+ self.verify_nftables_chain(nftables_search, 'ip vyos_wanloadbalance', 'wlb_mangle_prerouting')
+
+ # Verify hook output
+
+ self.assertTrue(os.path.exists(hook_output_path))
+
+ with open(hook_output_path, 'r') as f:
+ self.assertIn('eth0 - FAILED', f.read())
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index 9d4fc0845..985097726 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1149,6 +1149,16 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
},
},
},
+ 'vrf-match': {
+ 'rule': {
+ '10': {
+ 'action': 'permit',
+ 'match': {
+ 'source-vrf': 'TEST',
+ },
+ },
+ },
+ },
}
self.cli_set(['policy', 'access-list', access_list, 'rule', '10', 'action', 'permit'])
@@ -1260,6 +1270,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['rule', rule, 'match', 'rpki', 'valid'])
if 'protocol' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'protocol', rule_config['match']['protocol']])
+ if 'source-vrf' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'source-vrf', rule_config['match']['source-vrf']])
if 'tag' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'tag', rule_config['match']['tag']])
@@ -1438,6 +1450,9 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
if 'rpki-valid' in rule_config['match']:
tmp = f'match rpki valid'
self.assertIn(tmp, config)
+ if 'source-vrf' in rule_config['match']:
+ tmp = f'match source-vrf {rule_config["match"]["source-vrf"]}'
+ self.assertIn(tmp, config)
if 'tag' in rule_config['match']:
tmp = f'match tag {rule_config["match"]["tag"]}'
self.assertIn(tmp, config)
diff --git a/smoketest/scripts/cli/test_protocols_babel.py b/smoketest/scripts/cli/test_protocols_babel.py
index 7ecf54600..3a9ee2d62 100755
--- a/smoketest/scripts/cli/test_protocols_babel.py
+++ b/smoketest/scripts/cli/test_protocols_babel.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2024 VyOS maintainers and contributors
+# Copyright (C) 2024-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
@@ -72,7 +72,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' babel smoothing-half-life {smoothing_half_life}', frrconfig)
def test_02_redistribute(self):
- ipv4_protos = ['bgp', 'connected', 'isis', 'kernel', 'ospf', 'rip', 'static']
+ ipv4_protos = ['bgp', 'connected', 'isis', 'kernel', 'nhrp', 'ospf', 'rip', 'static']
ipv6_protos = ['bgp', 'connected', 'isis', 'kernel', 'ospfv3', 'ripng', 'static']
self.cli_set(base_path + ['interface', self._interfaces[0], 'enable-timestamps'])
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 761eb8bfe..d8d5415b5 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.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
@@ -655,10 +655,71 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
}
# We want to redistribute ...
- redistributes = ['connected', 'isis', 'kernel', 'ospf', 'rip', 'static']
- for redistribute in redistributes:
- self.cli_set(base_path + ['address-family', 'ipv4-unicast',
- 'redistribute', redistribute])
+ redistributes = {
+ 'babel' : {
+ 'metric' : '100',
+ 'route_map' : 'redistr-ipv4-babel',
+ },
+ 'connected' : {
+ 'metric' : '200',
+ 'route_map' : 'redistr-ipv4-connected',
+ },
+ 'isis' : {
+ 'metric' : '300',
+ 'route_map' : 'redistr-ipv4-isis',
+ },
+ 'kernel' : {
+ 'metric' : '400',
+ 'route_map' : 'redistr-ipv4-kernel',
+ },
+ 'nhrp': {
+ 'metric': '400',
+ 'route_map': 'redistr-ipv4-nhrp',
+ },
+ 'ospf' : {
+ 'metric' : '500',
+ 'route_map' : 'redistr-ipv4-ospf',
+ },
+ 'rip' : {
+ 'metric' : '600',
+ 'route_map' : 'redistr-ipv4-rip',
+ },
+ 'static' : {
+ 'metric' : '700',
+ 'route_map' : 'redistr-ipv4-static',
+ },
+ 'table' : {
+ '10' : {
+ 'metric' : '810',
+ 'route_map' : 'redistr-ipv4-table-10',
+ },
+ '20' : {
+ 'metric' : '820',
+ 'route_map' : 'redistr-ipv4-table-20',
+ },
+ '30' : {
+ 'metric' : '830',
+ 'route_map' : 'redistr-ipv4-table-30',
+ },
+ },
+ }
+ for proto, proto_config in redistributes.items():
+ proto_path = base_path + ['address-family', 'ipv4-unicast', 'redistribute', proto]
+ if proto == 'table':
+ for table, table_config in proto_config.items():
+ self.cli_set(proto_path + [table])
+ if 'metric' in table_config:
+ self.cli_set(proto_path + [table, 'metric'], value=table_config['metric'])
+ if 'route_map' in table_config:
+ self.cli_set(['policy', 'route-map', table_config['route_map'], 'rule', '10', 'action'], value='permit')
+ self.cli_set(proto_path + [table, 'route-map'], value=table_config['route_map'])
+ else:
+ self.cli_set(proto_path)
+ if 'metric' in proto_config:
+ self.cli_set(proto_path + ['metric', proto_config['metric']])
+ if 'route_map' in proto_config:
+ self.cli_set(['policy', 'route-map', proto_config['route_map'], 'rule', '10', 'action', 'permit'])
+ self.cli_set(proto_path + ['route-map', proto_config['route_map']])
for network, network_config in networks.items():
self.cli_set(base_path + ['address-family', 'ipv4-unicast',
@@ -679,10 +740,29 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
# Verify FRR bgpd configuration
frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit')
self.assertIn(f'router bgp {ASN}', frrconfig)
- self.assertIn(f' address-family ipv4 unicast', frrconfig)
-
- for redistribute in redistributes:
- self.assertIn(f' redistribute {redistribute}', frrconfig)
+ self.assertIn(' address-family ipv4 unicast', frrconfig)
+
+ for proto, proto_config in redistributes.items():
+ if proto == 'table':
+ for table, table_config in proto_config.items():
+ tmp = f' redistribute table-direct {table}'
+ if 'metric' in proto_config:
+ metric = proto_config['metric']
+ tmp += f' metric {metric}'
+ if 'route_map' in proto_config:
+ route_map = proto_config['route_map']
+ tmp += f' route-map {route_map}'
+ self.assertIn(tmp, frrconfig)
+ else:
+ tmp = f' redistribute {proto}'
+ if 'metric' in proto_config:
+ metric = proto_config['metric']
+ tmp += f' metric {metric}'
+ if 'route_map' in proto_config:
+ route_map = proto_config['route_map']
+ tmp += f' route-map {route_map}'
+
+ self.assertIn(tmp, frrconfig)
for network, network_config in networks.items():
self.assertIn(f' network {network}', frrconfig)
@@ -695,6 +775,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
command = f'{command} route-map {network_config["route_map"]}'
self.assertIn(command, frrconfig)
+ for proto, proto_config in redistributes.items():
+ if 'route_map' in proto_config:
+ self.cli_delete(['policy', 'route-map', proto_config['route_map']])
+
def test_bgp_05_afi_ipv6(self):
networks = {
'2001:db8:100::/48' : {
@@ -707,10 +791,67 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
}
# We want to redistribute ...
- redistributes = ['connected', 'kernel', 'ospfv3', 'ripng', 'static']
- for redistribute in redistributes:
- self.cli_set(base_path + ['address-family', 'ipv6-unicast',
- 'redistribute', redistribute])
+ redistributes = {
+ 'babel' : {
+ 'metric' : '100',
+ 'route_map' : 'redistr-ipv6-babel',
+ },
+ 'connected' : {
+ 'metric' : '200',
+ 'route_map' : 'redistr-ipv6-connected',
+ },
+ 'isis' : {
+ 'metric' : '300',
+ 'route_map' : 'redistr-ipv6-isis',
+ },
+ 'kernel' : {
+ 'metric' : '400',
+ 'route_map' : 'redistr-ipv6-kernel',
+ },
+ 'ospfv3' : {
+ 'metric' : '500',
+ 'route_map' : 'redistr-ipv6-ospfv3',
+ },
+ 'ripng' : {
+ 'metric' : '600',
+ 'route_map' : 'redistr-ipv6-ripng',
+ },
+ 'static' : {
+ 'metric' : '700',
+ 'route_map' : 'redistr-ipv6-static',
+ },
+ 'table' : {
+ '110' : {
+ 'metric' : '811',
+ 'route_map' : 'redistr-ipv6-table-110',
+ },
+ '120' : {
+ 'metric' : '821',
+ 'route_map' : 'redistr-ipv6-table-120',
+ },
+ '130' : {
+ 'metric' : '831',
+ 'route_map' : 'redistr-ipv6-table-130',
+ },
+ },
+ }
+ for proto, proto_config in redistributes.items():
+ proto_path = base_path + ['address-family', 'ipv6-unicast', 'redistribute', proto]
+ if proto == 'table':
+ for table, table_config in proto_config.items():
+ self.cli_set(proto_path + [table])
+ if 'metric' in table_config:
+ self.cli_set(proto_path + [table, 'metric'], value=table_config['metric'])
+ if 'route_map' in table_config:
+ self.cli_set(['policy', 'route-map', table_config['route_map'], 'rule', '10', 'action'], value='permit')
+ self.cli_set(proto_path + [table, 'route-map'], value=table_config['route_map'])
+ else:
+ self.cli_set(proto_path)
+ if 'metric' in proto_config:
+ self.cli_set(proto_path + ['metric', proto_config['metric']])
+ if 'route_map' in proto_config:
+ self.cli_set(['policy', 'route-map', proto_config['route_map'], 'rule', '20', 'action', 'permit'])
+ self.cli_set(proto_path + ['route-map', proto_config['route_map']])
for network, network_config in networks.items():
self.cli_set(base_path + ['address-family', 'ipv6-unicast',
@@ -725,22 +866,45 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
# Verify FRR bgpd configuration
frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit')
self.assertIn(f'router bgp {ASN}', frrconfig)
- self.assertIn(f' address-family ipv6 unicast', frrconfig)
+ self.assertIn(' address-family ipv6 unicast', frrconfig)
# T2100: By default ebgp-requires-policy is disabled to keep VyOS
# 1.3 and 1.2 backwards compatibility
- self.assertIn(f' no bgp ebgp-requires-policy', frrconfig)
-
- for redistribute in redistributes:
- # FRR calls this OSPF6
- if redistribute == 'ospfv3':
- redistribute = 'ospf6'
- self.assertIn(f' redistribute {redistribute}', frrconfig)
+ self.assertIn(' no bgp ebgp-requires-policy', frrconfig)
+
+ for proto, proto_config in redistributes.items():
+ if proto == 'table':
+ for table, table_config in proto_config.items():
+ tmp = f' redistribute table-direct {table}'
+ if 'metric' in proto_config:
+ metric = proto_config['metric']
+ tmp += f' metric {metric}'
+ if 'route_map' in proto_config:
+ route_map = proto_config['route_map']
+ tmp += f' route-map {route_map}'
+ self.assertIn(tmp, frrconfig)
+ else:
+ # FRR calls this OSPF6
+ if proto == 'ospfv3':
+ proto = 'ospf6'
+ tmp = f' redistribute {proto}'
+ if 'metric' in proto_config:
+ metric = proto_config['metric']
+ tmp += f' metric {metric}'
+ if 'route_map' in proto_config:
+ route_map = proto_config['route_map']
+ tmp += f' route-map {route_map}'
+
+ self.assertIn(tmp, frrconfig)
for network, network_config in networks.items():
self.assertIn(f' network {network}', frrconfig)
if 'as_set' in network_config:
self.assertIn(f' aggregate-address {network} summary-only', frrconfig)
+ for proto, proto_config in redistributes.items():
+ if 'route_map' in proto_config:
+ self.cli_delete(['policy', 'route-map', proto_config['route_map']])
+
def test_bgp_06_listen_range(self):
# Implemented via T1875
limit = '64'
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index 598250d28..14e833fd9 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -59,7 +59,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
route_map = 'EXPORT-ISIS'
rule = '10'
metric_style = 'transition'
-
+ redistribute = ['babel', 'bgp', 'connected', 'kernel', 'nhrp', 'ospf', 'rip', 'static']
self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', rule, 'action', 'permit'])
self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', rule, 'prefix', '203.0.113.0/24'])
self.cli_set(['policy', 'route-map', route_map, 'rule', rule, 'action', 'permit'])
@@ -80,7 +80,9 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(base_path + ['redistribute', 'ipv4', 'connected', 'level-2', 'route-map', route_map])
+ for proto in redistribute:
+ self.cli_set(base_path + ['redistribute', 'ipv4', proto, 'level-2', 'route-map', route_map])
+
self.cli_set(base_path + ['metric-style', metric_style])
self.cli_set(base_path + ['log-adjacency-changes'])
@@ -92,7 +94,8 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' net {net}', tmp)
self.assertIn(f' metric-style {metric_style}', tmp)
self.assertIn(f' log-adjacency-changes', tmp)
- self.assertIn(f' redistribute ipv4 connected level-2 route-map {route_map}', tmp)
+ for proto in redistribute:
+ self.assertIn(f' redistribute ipv4 {proto} level-2 route-map {route_map}', tmp)
for interface in self._interfaces:
tmp = self.getFRRconfig(f'interface {interface}', endsection='^exit')
diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py
index f6d1f1da5..73a760945 100755
--- a/smoketest/scripts/cli/test_protocols_nhrp.py
+++ b/smoketest/scripts/cli/test_protocols_nhrp.py
@@ -17,10 +17,7 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-
-from vyos.firewall import find_nftables_rule
from vyos.utils.process import process_named_running
-from vyos.utils.file import read_file
tunnel_path = ['interfaces', 'tunnel']
nhrp_path = ['protocols', 'nhrp']
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index 77882737f..ea55fa031 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.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
@@ -255,7 +255,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
def test_ospf_07_redistribute(self):
metric = '15'
metric_type = '1'
- redistribute = ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']
+ redistribute = ['babel', 'bgp', 'connected', 'isis', 'kernel', 'nhrp', 'rip', 'static']
for protocol in redistribute:
self.cli_set(base_path + ['redistribute', protocol, 'metric', metric])
diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py
index 671ef8cd5..27b543803 100755
--- a/smoketest/scripts/cli/test_protocols_rip.py
+++ b/smoketest/scripts/cli/test_protocols_rip.py
@@ -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
@@ -82,7 +82,7 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase):
interfaces = Section.interfaces('ethernet')
neighbors = ['1.2.3.4', '1.2.3.5', '1.2.3.6']
networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']
- redistribute = ['bgp', 'connected', 'isis', 'kernel', 'ospf', 'static']
+ redistribute = ['bgp', 'connected', 'isis', 'kernel', 'nhrp', 'ospf', 'static']
timer_garbage = '888'
timer_timeout = '1000'
timer_update = '90'
diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py
index ef2f30d3e..0addf7fee 100755
--- a/smoketest/scripts/cli/test_protocols_rpki.py
+++ b/smoketest/scripts/cli/test_protocols_rpki.py
@@ -248,5 +248,41 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
with self.assertRaises(ConfigSessionError):
self.cli_commit()
+ def test_rpki_source_address(self):
+ peer = '192.0.2.1'
+ port = '8080'
+ preference = '1'
+ username = 'foo'
+ source_address = '100.10.10.1'
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', f'{source_address}/24'])
+
+ # Configure a TCP cache server
+ self.cli_set(base_path + ['cache', peer, 'port', port])
+ self.cli_set(base_path + ['cache', peer, 'preference', preference])
+ self.cli_set(base_path + ['cache', peer, 'source-address', source_address])
+ self.cli_commit()
+
+ # Verify FRR configuration
+ frrconfig = self.getFRRconfig('rpki')
+ self.assertIn(f'rpki cache tcp {peer} {port} source {source_address} preference {preference}', frrconfig)
+
+ self.cli_set(['pki', 'openssh', rpki_key_name, 'private', 'key', rpki_ssh_key.replace('\n', '')])
+ self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'key', rpki_ssh_pub.replace('\n', '')])
+ self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'type', rpki_key_type])
+
+ # Configure a SSH cache server
+ self.cli_set(base_path + ['cache', peer, 'ssh', 'username', username])
+ self.cli_set(base_path + ['cache', peer, 'ssh', 'key', rpki_key_name])
+ self.cli_commit()
+
+ # Verify FRR configuration
+ frrconfig = self.getFRRconfig('rpki')
+ self.assertIn(
+ f'rpki cache ssh {peer} {port} {username} /run/frr/id_rpki_{peer} /run/frr/id_rpki_{peer}.pub source {source_address} preference {preference}',
+ frrconfig,
+ )
+
+
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 f891bf295..7c2ebff89 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2024 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import re
import unittest
from json import loads
@@ -22,6 +23,9 @@ from json import loads
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.kea import kea_add_lease
+from vyos.kea import kea_delete_lease
+from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
from vyos.utils.file import read_file
from vyos.template import inc_ip
@@ -31,6 +35,7 @@ PROCESS_NAME = 'kea-dhcp4'
CTRL_PROCESS_NAME = 'kea-ctrl-agent'
KEA4_CONF = '/run/kea/kea-dhcp4.conf'
KEA4_CTRL = '/run/kea/dhcp4-ctrl-socket'
+HOSTSD_CLIENT = '/usr/bin/vyos-hostsd-client'
base_path = ['service', 'dhcp-server']
interface = 'dum8765'
subnet = '192.0.2.0/25'
@@ -39,15 +44,18 @@ dns_1 = inc_ip(subnet, 2)
dns_2 = inc_ip(subnet, 3)
domain_name = 'vyos.net'
+
class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
super(TestServiceDHCPServer, cls).setUpClass()
- # Clear out current configuration to allow running this test on a live system
+ # Clear out current configuration to allow running this test on a live system
cls.cli_delete(cls, base_path)
cidr_mask = subnet.split('/')[-1]
- cls.cli_set(cls, ['interfaces', 'dummy', interface, 'address', f'{router}/{cidr_mask}'])
+ cls.cli_set(
+ cls, ['interfaces', 'dummy', interface, 'address', f'{router}/{cidr_mask}']
+ )
@classmethod
def tearDownClass(cls):
@@ -69,7 +77,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.assertTrue(isinstance(current, list), msg=f'Failed path: {path}')
self.assertTrue(0 <= key < len(current), msg=f'Failed path: {path}')
else:
- assert False, "Invalid type"
+ assert False, 'Invalid type'
current = current[key]
@@ -92,9 +100,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
shared_net_name = 'SMOKE-1'
range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
+ range_0_stop = inc_ip(subnet, 20)
range_1_start = inc_ip(subnet, 40)
- range_1_stop = inc_ip(subnet, 50)
+ range_1_stop = inc_ip(subnet, 50)
self.cli_set(base_path + ['listen-interface', interface])
@@ -121,37 +129,56 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
config = read_file(KEA4_CONF)
obj = loads(config)
- self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [interface])
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'match-client-id', False)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
+ self.verify_config_value(
+ obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [interface]
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'match-client-id', False
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400
+ )
# Verify options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'domain-name', 'data': domain_name})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
# Verify pools
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start} - {range_0_stop}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_1_start} - {range_1_stop}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_1_start} - {range_1_stop}'},
+ )
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -159,16 +186,16 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
def test_dhcp_single_pool_options(self):
shared_net_name = 'SMOKE-0815'
- range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
- smtp_server = '1.2.3.4'
- time_server = '4.3.2.1'
- tftp_server = 'tftp.vyos.io'
- search_domains = ['foo.vyos.net', 'bar.vyos.net']
- bootfile_name = 'vyos'
- bootfile_server = '192.0.2.1'
- wpad = 'http://wpad.vyos.io/foo/bar'
- server_identifier = bootfile_server
+ range_0_start = inc_ip(subnet, 10)
+ range_0_stop = inc_ip(subnet, 20)
+ smtp_server = '1.2.3.4'
+ time_server = '4.3.2.1'
+ tftp_server = 'tftp.vyos.io'
+ search_domains = ['foo.vyos.net', 'bar.vyos.net']
+ bootfile_name = 'vyos'
+ bootfile_server = '192.0.2.1'
+ wpad = 'http://wpad.vyos.io/foo/bar'
+ server_identifier = bootfile_server
ipv6_only_preferred = '300'
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
@@ -190,7 +217,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(pool + ['option', 'wpad-url', wpad])
self.cli_set(pool + ['option', 'server-identifier', server_identifier])
- self.cli_set(pool + ['option', 'static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1'])
+ self.cli_set(
+ pool + ['option', 'static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1']
+ )
self.cli_set(pool + ['option', 'ipv6-only-preferred', ipv6_only_preferred])
self.cli_set(pool + ['option', 'time-zone', 'Europe/London'])
@@ -203,86 +232,124 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
config = read_file(KEA4_CONF)
obj = loads(config)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'boot-file-name', bootfile_name)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'next-server', bootfile_server)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4'],
+ 'boot-file-name',
+ bootfile_name,
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4'],
+ 'next-server',
+ bootfile_server,
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400
+ )
# Verify options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'domain-name', 'data': domain_name})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'domain-search', 'data': ', '.join(search_domains)})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-search', 'data': ', '.join(search_domains)},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'pop-server', 'data': smtp_server})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'pop-server', 'data': smtp_server},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'smtp-server', 'data': smtp_server})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'smtp-server', 'data': smtp_server},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'time-servers', 'data': time_server})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'time-servers', 'data': time_server},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'dhcp-server-identifier', 'data': server_identifier})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'dhcp-server-identifier', 'data': server_identifier},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'tftp-server-name', 'data': tftp_server})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'tftp-server-name', 'data': tftp_server},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'wpad-url', 'data': wpad})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'wpad-url', 'data': wpad},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'rfc3442-static-route', 'data': '24,10,0,0,192,0,2,1, 0,192,0,2,1'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {
+ 'name': 'rfc3442-static-route',
+ 'data': '24,10,0,0,192,0,2,1, 0,192,0,2,1',
+ },
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'windows-static-route', 'data': '24,10,0,0,192,0,2,1'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'windows-static-route', 'data': '24,10,0,0,192,0,2,1'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'v6-only-preferred', 'data': ipv6_only_preferred})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'v6-only-preferred', 'data': ipv6_only_preferred},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'ip-forwarding', 'data': "true"})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'ip-forwarding', 'data': 'true'},
+ )
# Time zone
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'pcode', 'data': 'GMT0BST,M3.5.0/1,M10.5.0'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'pcode', 'data': 'GMT0BST,M3.5.0/1,M10.5.0'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'tcode', 'data': 'Europe/London'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'tcode', 'data': 'Europe/London'},
+ )
# Verify pools
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start} - {range_0_stop}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'},
+ )
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -291,7 +358,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
shared_net_name = 'SMOKE-2'
range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
+ range_0_stop = inc_ip(subnet, 20)
range_router = inc_ip(subnet, 5)
range_dns_1 = inc_ip(subnet, 6)
@@ -320,37 +387,55 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
config = read_file(KEA4_CONF)
obj = loads(config)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400
+ )
# Verify shared-network options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'option-data'],
- {'name': 'domain-name', 'data': domain_name})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'option-data'],
- {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
# Verify range options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'],
- {'name': 'domain-name-servers', 'data': f'{range_dns_1}, {range_dns_2}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{range_dns_1}, {range_dns_2}'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'],
- {'name': 'routers', 'data': range_router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'],
+ {'name': 'routers', 'data': range_router},
+ )
# Verify pool
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], 'pool', f'{range_0_start} - {range_0_stop}')
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ 'pool',
+ f'{range_0_start} - {range_0_stop}',
+ )
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -375,18 +460,31 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
for client in ['client1', 'client2', 'client3']:
mac = '00:50:00:00:00:{}'.format(client_base)
self.cli_set(pool + ['static-mapping', client, 'mac', mac])
- self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)])
+ self.cli_set(
+ pool
+ + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]
+ )
client_base += 1
# cannot have both mac-address and duid set
with self.assertRaises(ConfigSessionError):
- self.cli_set(pool + ['static-mapping', 'client1', 'duid', '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:11'])
+ self.cli_set(
+ pool
+ + [
+ 'static-mapping',
+ 'client1',
+ 'duid',
+ '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:11',
+ ]
+ )
self.cli_commit()
self.cli_delete(pool + ['static-mapping', 'client1', 'duid'])
# cannot have mappings with duplicate IP addresses
self.cli_set(pool + ['static-mapping', 'dupe1', 'mac', '00:50:00:00:fe:ff'])
- self.cli_set(pool + ['static-mapping', 'dupe1', 'ip-address', inc_ip(subnet, 10)])
+ self.cli_set(
+ pool + ['static-mapping', 'dupe1', 'ip-address', inc_ip(subnet, 10)]
+ )
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# Should allow disabled duplicate
@@ -396,17 +494,38 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# cannot have mappings with duplicate MAC addresses
self.cli_set(pool + ['static-mapping', 'dupe2', 'mac', '00:50:00:00:00:10'])
- self.cli_set(pool + ['static-mapping', 'dupe2', 'ip-address', inc_ip(subnet, 120)])
+ self.cli_set(
+ pool + ['static-mapping', 'dupe2', 'ip-address', inc_ip(subnet, 120)]
+ )
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(pool + ['static-mapping', 'dupe2'])
-
# cannot have mappings with duplicate MAC addresses
- self.cli_set(pool + ['static-mapping', 'dupe3', 'duid', '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01'])
- self.cli_set(pool + ['static-mapping', 'dupe3', 'ip-address', inc_ip(subnet, 121)])
- self.cli_set(pool + ['static-mapping', 'dupe4', 'duid', '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01'])
- self.cli_set(pool + ['static-mapping', 'dupe4', 'ip-address', inc_ip(subnet, 121)])
+ self.cli_set(
+ pool
+ + [
+ 'static-mapping',
+ 'dupe3',
+ 'duid',
+ '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01',
+ ]
+ )
+ self.cli_set(
+ pool + ['static-mapping', 'dupe3', 'ip-address', inc_ip(subnet, 121)]
+ )
+ self.cli_set(
+ pool
+ + [
+ 'static-mapping',
+ 'dupe4',
+ 'duid',
+ '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01',
+ ]
+ )
+ self.cli_set(
+ pool + ['static-mapping', 'dupe4', 'ip-address', inc_ip(subnet, 121)]
+ )
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(pool + ['static-mapping', 'dupe3'])
@@ -418,25 +537,38 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
config = read_file(KEA4_CONF)
obj = loads(config)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400
+ )
# Verify options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'domain-name', 'data': domain_name})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
client_base = 10
for client in ['client1', 'client2', 'client3']:
@@ -444,9 +576,10 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
ip = inc_ip(subnet, client_base)
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'reservations'],
- {'hostname': client, 'hw-address': mac, 'ip-address': ip})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'reservations'],
+ {'hostname': client, 'hw-address': mac, 'ip-address': ip},
+ )
client_base += 1
@@ -463,11 +596,16 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
dns_1 = inc_ip(subnet, 2)
range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
+ range_0_stop = inc_ip(subnet, 20)
range_1_start = inc_ip(subnet, 30)
- range_1_stop = inc_ip(subnet, 40)
-
- pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
+ range_1_stop = inc_ip(subnet, 40)
+
+ pool = base_path + [
+ 'shared-network-name',
+ shared_net_name,
+ 'subnet',
+ subnet,
+ ]
self.cli_set(pool + ['subnet-id', str(int(network) + 1)])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['option', 'default-router', router])
@@ -484,7 +622,15 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
for client in ['client1', 'client2', 'client3', 'client4']:
mac = '02:50:00:00:00:{}'.format(client_base)
self.cli_set(pool + ['static-mapping', client, 'mac', mac])
- self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)])
+ self.cli_set(
+ pool
+ + [
+ 'static-mapping',
+ client,
+ 'ip-address',
+ inc_ip(subnet, client_base),
+ ]
+ )
client_base += 1
# commit changes
@@ -500,37 +646,64 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
dns_1 = inc_ip(subnet, 2)
range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
+ range_0_stop = inc_ip(subnet, 20)
range_1_start = inc_ip(subnet, 30)
- range_1_stop = inc_ip(subnet, 40)
+ range_1_stop = inc_ip(subnet, 40)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'subnet', subnet)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'id', int(network) + 1)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'valid-lifetime', int(lease_time))
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'max-valid-lifetime', int(lease_time))
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4'],
+ 'subnet',
+ subnet,
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4'],
+ 'id',
+ int(network) + 1,
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4'],
+ 'valid-lifetime',
+ int(lease_time),
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4'],
+ 'max-valid-lifetime',
+ int(lease_time),
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
- {'name': 'domain-name', 'data': domain_name})
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
- {'name': 'domain-name-servers', 'data': dns_1})
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': dns_1},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start} - {range_0_stop}'})
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'],
- {'pool': f'{range_1_start} - {range_1_stop}'})
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'],
+ {'pool': f'{range_1_start} - {range_1_stop}'},
+ )
client_base = 60
for client in ['client1', 'client2', 'client3', 'client4']:
@@ -538,9 +711,17 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
ip = inc_ip(subnet, client_base)
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'reservations'],
- {'hostname': client, 'hw-address': mac, 'ip-address': ip})
+ obj,
+ [
+ 'Dhcp4',
+ 'shared-networks',
+ int(network),
+ 'subnet4',
+ 0,
+ 'reservations',
+ ],
+ {'hostname': client, 'hw-address': mac, 'ip-address': ip},
+ )
client_base += 1
@@ -551,7 +732,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# T3180: verify else path when slicing DHCP ranges and exclude address
# is not part of the DHCP range
range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
+ range_0_stop = inc_ip(subnet, 20)
pool = base_path + ['shared-network-name', 'EXCLUDE-TEST', 'subnet', subnet]
self.cli_set(pool + ['subnet-id', '1'])
@@ -567,25 +748,29 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
config = read_file(KEA4_CONF)
obj = loads(config)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST')
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ 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}]
+ 'option-data': [{'name': 'routers', 'data': router}],
}
# Verify options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
# Verify pools
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- pool_obj)
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], pool_obj
+ )
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -594,11 +779,11 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# T3180: verify else path when slicing DHCP ranges and exclude address
# is not part of the DHCP range
range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 100)
+ range_0_stop = inc_ip(subnet, 100)
# the DHCP exclude addresse is blanked out of the range which is done
# by slicing one range into two ranges
- exclude_addr = inc_ip(range_0_start, 20)
+ exclude_addr = inc_ip(range_0_start, 20)
range_0_stop_excl = dec_ip(exclude_addr, 1)
range_0_start_excl = inc_ip(exclude_addr, 1)
@@ -616,34 +801,39 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
config = read_file(KEA4_CONF)
obj = loads(config)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST-2')
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ 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}]
+ 'option-data': [{'name': 'routers', 'data': router}],
}
pool_exclude_obj = {
'pool': f'{range_0_start_excl} - {range_0_stop}',
- 'option-data': [{'name': 'routers', 'data': router}]
+ 'option-data': [{'name': 'routers', 'data': router}],
}
# Verify options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- pool_obj)
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], pool_obj
+ )
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- pool_exclude_obj)
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ pool_exclude_obj,
+ )
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -657,7 +847,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
relay_router = inc_ip(relay_subnet, 1)
range_0_start = '10.0.1.0'
- range_0_stop = '10.0.250.255'
+ range_0_stop = '10.0.250.255'
pool = base_path + ['shared-network-name', 'RELAY', 'subnet', relay_subnet]
self.cli_set(pool + ['subnet-id', '1'])
@@ -671,21 +861,27 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
config = read_file(KEA4_CONF)
obj = loads(config)
- self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [f'{interface}/{router}'])
+ self.verify_config_value(
+ obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [f'{interface}/{router}']
+ )
self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'RELAY')
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', relay_subnet)
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', relay_subnet
+ )
# Verify options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': relay_router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': relay_router},
+ )
# Verify pools
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start} - {range_0_stop}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'},
+ )
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -695,7 +891,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
failover_name = 'VyOS-Failover'
range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
+ range_0_stop = inc_ip(subnet, 20)
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.cli_set(pool + ['subnet-id', '1'])
@@ -712,7 +908,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
failover_local = router
failover_remote = inc_ip(router, 1)
- self.cli_set(base_path + ['high-availability', 'source-address', failover_local])
+ self.cli_set(
+ base_path + ['high-availability', 'source-address', failover_local]
+ )
self.cli_set(base_path + ['high-availability', 'name', failover_name])
self.cli_set(base_path + ['high-availability', 'remote', failover_remote])
self.cli_set(base_path + ['high-availability', 'status', 'primary'])
@@ -725,32 +923,68 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
obj = loads(config)
# Verify failover
- self.verify_config_value(obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL)
+ self.verify_config_value(
+ obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL
+ )
self.verify_config_object(
obj,
- ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'],
- {'name': os.uname()[1], 'url': f'http://{failover_local}:647/', 'role': 'primary', 'auto-failover': True})
+ [
+ 'Dhcp4',
+ 'hooks-libraries',
+ 0,
+ 'parameters',
+ 'high-availability',
+ 0,
+ 'peers',
+ ],
+ {
+ 'name': os.uname()[1],
+ 'url': f'http://{failover_local}:647/',
+ 'role': 'primary',
+ 'auto-failover': True,
+ },
+ )
self.verify_config_object(
obj,
- ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'],
- {'name': failover_name, 'url': f'http://{failover_remote}:647/', 'role': 'secondary', 'auto-failover': True})
-
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ [
+ 'Dhcp4',
+ 'hooks-libraries',
+ 0,
+ 'parameters',
+ 'high-availability',
+ 0,
+ 'peers',
+ ],
+ {
+ 'name': failover_name,
+ 'url': f'http://{failover_remote}:647/',
+ 'role': 'secondary',
+ 'auto-failover': True,
+ },
+ )
+
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet
+ )
# Verify options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
# Verify pools
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start} - {range_0_stop}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'},
+ )
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -761,7 +995,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
failover_name = 'VyOS-Failover'
range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
+ range_0_stop = inc_ip(subnet, 20)
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.cli_set(pool + ['subnet-id', '1'])
@@ -774,7 +1008,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
failover_local = router
failover_remote = inc_ip(router, 1)
- self.cli_set(base_path + ['high-availability', 'source-address', failover_local])
+ self.cli_set(
+ base_path + ['high-availability', 'source-address', failover_local]
+ )
self.cli_set(base_path + ['high-availability', 'name', failover_name])
self.cli_set(base_path + ['high-availability', 'remote', failover_remote])
self.cli_set(base_path + ['high-availability', 'status', 'secondary'])
@@ -787,32 +1023,68 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
obj = loads(config)
# Verify failover
- self.verify_config_value(obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL)
+ self.verify_config_value(
+ obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL
+ )
self.verify_config_object(
obj,
- ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'],
- {'name': os.uname()[1], 'url': f'http://{failover_local}:647/', 'role': 'standby', 'auto-failover': True})
+ [
+ 'Dhcp4',
+ 'hooks-libraries',
+ 0,
+ 'parameters',
+ 'high-availability',
+ 0,
+ 'peers',
+ ],
+ {
+ 'name': os.uname()[1],
+ 'url': f'http://{failover_local}:647/',
+ 'role': 'standby',
+ 'auto-failover': True,
+ },
+ )
self.verify_config_object(
obj,
- ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'],
- {'name': failover_name, 'url': f'http://{failover_remote}:647/', 'role': 'primary', 'auto-failover': True})
-
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
- self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ [
+ 'Dhcp4',
+ 'hooks-libraries',
+ 0,
+ 'parameters',
+ 'high-availability',
+ 0,
+ 'peers',
+ ],
+ {
+ 'name': failover_name,
+ 'url': f'http://{failover_remote}:647/',
+ 'role': 'primary',
+ 'auto-failover': True,
+ },
+ )
+
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet
+ )
# Verify options
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'routers', 'data': router})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
# Verify pools
self.verify_config_object(
- obj,
- ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
- {'pool': f'{range_0_start} - {range_0_stop}'})
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'},
+ )
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -821,27 +1093,187 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
def test_dhcp_on_interface_with_vrf(self):
self.cli_set(['interfaces', 'ethernet', 'eth1', 'address', '10.1.1.1/30'])
self.cli_set(['interfaces', 'ethernet', 'eth1', 'vrf', 'SMOKE-DHCP'])
- self.cli_set(['protocols', 'static', 'route', '10.1.10.0/24', 'interface', 'eth1', 'vrf', 'SMOKE-DHCP'])
- self.cli_set(['vrf', 'name', 'SMOKE-DHCP', 'protocols', 'static', 'route', '10.1.10.0/24', 'next-hop', '10.1.1.2'])
+ self.cli_set(
+ [
+ 'protocols',
+ 'static',
+ 'route',
+ '10.1.10.0/24',
+ 'interface',
+ 'eth1',
+ 'vrf',
+ 'SMOKE-DHCP',
+ ]
+ )
+ self.cli_set(
+ [
+ 'vrf',
+ 'name',
+ 'SMOKE-DHCP',
+ 'protocols',
+ 'static',
+ 'route',
+ '10.1.10.0/24',
+ 'next-hop',
+ '10.1.1.2',
+ ]
+ )
self.cli_set(['vrf', 'name', 'SMOKE-DHCP', 'table', '1000'])
- self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'subnet-id', '1'])
- self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'option', 'default-router', '10.1.10.1'])
- self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'option', 'name-server', '1.1.1.1'])
- self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'range', '1', 'start', '10.1.10.10'])
- self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'range', '1', 'stop', '10.1.10.20'])
+ self.cli_set(
+ base_path
+ + [
+ 'shared-network-name',
+ 'SMOKE-DHCP-NETWORK',
+ 'subnet',
+ '10.1.10.0/24',
+ 'subnet-id',
+ '1',
+ ]
+ )
+ self.cli_set(
+ base_path
+ + [
+ 'shared-network-name',
+ 'SMOKE-DHCP-NETWORK',
+ 'subnet',
+ '10.1.10.0/24',
+ 'option',
+ 'default-router',
+ '10.1.10.1',
+ ]
+ )
+ self.cli_set(
+ base_path
+ + [
+ 'shared-network-name',
+ 'SMOKE-DHCP-NETWORK',
+ 'subnet',
+ '10.1.10.0/24',
+ 'option',
+ 'name-server',
+ '1.1.1.1',
+ ]
+ )
+ self.cli_set(
+ base_path
+ + [
+ 'shared-network-name',
+ 'SMOKE-DHCP-NETWORK',
+ 'subnet',
+ '10.1.10.0/24',
+ 'range',
+ '1',
+ 'start',
+ '10.1.10.10',
+ ]
+ )
+ self.cli_set(
+ base_path
+ + [
+ 'shared-network-name',
+ 'SMOKE-DHCP-NETWORK',
+ 'subnet',
+ '10.1.10.0/24',
+ 'range',
+ '1',
+ 'stop',
+ '10.1.10.20',
+ ]
+ )
self.cli_set(base_path + ['listen-address', '10.1.1.1'])
self.cli_commit()
config = read_file(KEA4_CONF)
obj = loads(config)
- self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', ['eth1/10.1.1.1'])
+ self.verify_config_value(
+ obj, ['Dhcp4', 'interfaces-config'], 'interfaces', ['eth1/10.1.1.1']
+ )
self.cli_delete(['interfaces', 'ethernet', 'eth1', 'vrf', 'SMOKE-DHCP'])
- self.cli_delete(['protocols', 'static', 'route', '10.1.10.0/24', 'interface', 'eth1', 'vrf'])
+ self.cli_delete(
+ ['protocols', 'static', 'route', '10.1.10.0/24', 'interface', 'eth1', 'vrf']
+ )
self.cli_delete(['vrf', 'name', 'SMOKE-DHCP'])
self.cli_commit()
+ def test_dhcp_hostsd_lease_sync(self):
+ shared_net_name = 'SMOKE-LEASE-SYNC'
+ domain_name = 'sync.private'
+
+ client_range = range(1, 4)
+ subnet_range_start = inc_ip(subnet, 10)
+ subnet_range_stop = inc_ip(subnet, 20)
+
+ def internal_cleanup():
+ for seq in client_range:
+ ip_addr = inc_ip(subnet, seq)
+ kea_delete_lease(4, ip_addr)
+ cmd(
+ f'{HOSTSD_CLIENT} --delete-hosts --tag dhcp-server-{ip_addr} --apply'
+ )
+
+ self.addClassCleanup(internal_cleanup)
+
+ pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
+ self.cli_set(pool + ['option', 'domain-name', domain_name])
+ self.cli_set(pool + ['range', '0', 'start', subnet_range_start])
+ self.cli_set(pool + ['range', '0', 'stop', subnet_range_stop])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
+
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1
+ )
+
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{subnet_range_start} - {subnet_range_stop}'},
+ )
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ # All up and running, now test vyos-hostsd store
+
+ # 1. Inject leases into kea
+ for seq in client_range:
+ client = f'client{seq}'
+ mac = f'00:50:00:00:00:{seq:02}'
+ ip = inc_ip(subnet, seq)
+ kea_add_lease(4, ip, host_name=client, mac_address=mac)
+
+ # 2. Verify that leases are not available in vyos-hostsd
+ tag_regex = re.escape(f'dhcp-server-{subnet.rsplit(".", 1)[0]}')
+ host_json = cmd(f'{HOSTSD_CLIENT} --get-hosts {tag_regex}')
+ self.assertFalse(host_json.strip('{}'))
+
+ # 3. Restart the service to trigger vyos-hostsd sync and wait for it to start
+ self.assertTrue(process_named_running(PROCESS_NAME, timeout=30))
+
+ # 4. Verify that leases are synced and available in vyos-hostsd
+ tag_regex = re.escape(f'dhcp-server-{subnet.rsplit(".", 1)[0]}')
+ host_json = cmd(f'{HOSTSD_CLIENT} --get-hosts {tag_regex}')
+ self.assertTrue(host_json)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py
index ab0898d17..3b3c205cd 100755
--- a/smoketest/scripts/cli/test_service_ipoe-server.py
+++ b/smoketest/scripts/cli/test_service_ipoe-server.py
@@ -260,7 +260,7 @@ delegate={delegate_2_prefix},{delegate_mask},name={pool_name}"""
tmp = ','.join(vlans)
self.assertIn(f'{interface},{tmp}', conf['ipoe']['vlan-mon'])
- def test_ipoe_server_static_client_ip(self):
+ def test_ipoe_server_static_client_ip_address(self):
mac_address = '08:00:27:2f:d8:06'
ip_address = '192.0.2.100'
@@ -274,7 +274,7 @@ delegate={delegate_2_prefix},{delegate_mask},name={pool_name}"""
interface,
'mac',
mac_address,
- 'static-ip',
+ 'ip-address',
ip_address,
]
)
@@ -295,6 +295,28 @@ delegate={delegate_2_prefix},{delegate_mask},name={pool_name}"""
tmp = re.findall(regex, tmp)
self.assertTrue(tmp)
+ def test_ipoe_server_start_session(self):
+ start_session = 'auto'
+
+ # Configuration of local authentication for PPPoE server
+ self.basic_config()
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf.read(self._config_file)
+ # if 'start-session' option is not set the default value is 'dhcp'
+ self.assertIn(f'start=dhcpv4', conf['ipoe']['interface'])
+
+ # change 'start-session' option to 'auto'
+ self.set(['interface', interface, 'start-session', start_session])
+ self.cli_commit()
+
+ # Validate changed configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf.read(self._config_file)
+ self.assertIn(f'start={start_session}', conf['ipoe']['interface'])
+
@unittest.skip("PPP is not a part of IPoE")
def test_accel_ppp_options(self):
pass
diff --git a/smoketest/scripts/cli/test_service_lldp.py b/smoketest/scripts/cli/test_service_lldp.py
index 9d72ef78f..c73707e0d 100755
--- a/smoketest/scripts/cli/test_service_lldp.py
+++ b/smoketest/scripts/cli/test_service_lldp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022-2024 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -117,6 +117,8 @@ class TestServiceLLDP(VyOSUnitTestSHIM.TestCase):
config = read_file(LLDPD_CONF)
self.assertIn(f'configure ports {interface} med location elin "{elin}"', config)
+ # This is the CLI default mode
+ self.assertIn(f'configure ports {interface} lldp status rx-and-tx', config)
self.assertIn(f'configure system interface pattern "{interface}"', config)
def test_06_lldp_snmp(self):
@@ -134,5 +136,50 @@ class TestServiceLLDP(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['service', 'snmp'])
+ def test_07_lldp_interface_mode(self):
+ interfaces = Section.interfaces('ethernet', vlan=False)
+
+ # set interface mode to 'tx'
+ self.cli_set(base_path + ['interface', 'all'])
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'mode', 'disable'])
+ # commit changes
+ self.cli_commit()
+
+ # verify configuration
+ config = read_file(LLDPD_CONF)
+ for interface in interfaces:
+ self.assertIn(f'configure ports {interface} lldp status disable', config)
+
+ # Change configuration to rx-only
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'mode', 'rx'])
+ # commit changes
+ self.cli_commit()
+ # verify configuration
+ config = read_file(LLDPD_CONF)
+ for interface in interfaces:
+ self.assertIn(f'configure ports {interface} lldp status rx-only', config)
+
+ # Change configuration to tx-only
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'mode', 'tx'])
+ # commit changes
+ self.cli_commit()
+ # verify configuration
+ config = read_file(LLDPD_CONF)
+ for interface in interfaces:
+ self.assertIn(f'configure ports {interface} lldp status tx-only', config)
+
+ # Change configuration to rx-only
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'mode', 'rx-tx'])
+ # commit changes
+ self.cli_commit()
+ # verify configuration
+ config = read_file(LLDPD_CONF)
+ for interface in interfaces:
+ self.assertIn(f'configure ports {interface} lldp status rx-and-tx', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py
index 2b3f6d21c..ab4707a61 100755
--- a/smoketest/scripts/cli/test_service_webproxy.py
+++ b/smoketest/scripts/cli/test_service_webproxy.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -297,6 +297,22 @@ class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase):
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_06_nocache_domain_proxy(self):
+ domains_nocache = ['test1.net', 'test2.net']
+ self.cli_set(base_path + ['listen-address', listen_ip])
+ for domain in domains_nocache:
+ self.cli_set(base_path + ['domain-noncache', domain])
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(PROXY_CONF)
+
+ for domain in domains_nocache:
+ self.assertIn(f'acl NOCACHE dstdomain {domain}', config)
+ self.assertIn(f'no_cache deny NOCACHE', config)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_syslog.py b/smoketest/scripts/cli/test_system_syslog.py
index a86711119..ba325ced8 100755
--- a/smoketest/scripts/cli/test_system_syslog.py
+++ b/smoketest/scripts/cli/test_system_syslog.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2024 VyOS maintainers and contributors
+# Copyright (C) 2019-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
@@ -14,24 +14,32 @@
# 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
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
from vyos.utils.file import read_file
+from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
from vyos.xml_ref import default_value
PROCESS_NAME = 'rsyslogd'
-RSYSLOG_CONF = '/etc/rsyslog.d/00-vyos.conf'
+RSYSLOG_CONF = '/run/rsyslog/rsyslog.conf'
base_path = ['system', 'syslog']
-def get_config_value(key):
- tmp = read_file(RSYSLOG_CONF)
- tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp)
- return tmp[0]
+dummy_interface = 'dum372874'
+
+def get_config(string=''):
+ """
+ Retrieve current "running configuration" from FRR
+ string: search for a specific start string in the configuration
+ """
+ command = 'cat /run/rsyslog/rsyslog.conf'
+ if string:
+ command += f' | sed -n "/^{string}$/,/}}/p"' # }} required to escape } in f-string
+ return cmd(command)
class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
@classmethod
@@ -41,6 +49,7 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
# 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_delete(cls, ['vrf'])
def tearDown(self):
# Check for running process
@@ -53,79 +62,241 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
# Check for running process
self.assertFalse(process_named_running(PROCESS_NAME))
- def test_syslog_basic(self):
- host1 = '127.0.0.10'
- host2 = '127.0.0.20'
-
- self.cli_set(base_path + ['host', host1, 'port', '999'])
- self.cli_set(base_path + ['host', host1, 'facility', 'all', 'level', 'all'])
- self.cli_set(base_path + ['host', host2, 'facility', 'kern', 'level', 'err'])
- self.cli_set(base_path + ['console', 'facility', 'all', 'level', 'warning'])
-
+ def test_console(self):
+ level = 'warning'
+ self.cli_set(base_path + ['console', 'facility', 'all', 'level'], value=level)
self.cli_commit()
- # verify log level and facilities in config file
- # *.warning /dev/console
- # *.* @198.51.100.1:999
- # kern.err @192.0.2.1:514
+
+ rsyslog_conf = get_config()
config = [
- get_config_value('\*.\*'),
- get_config_value('kern.err'),
- get_config_value('\*.warning'),
+ f'if prifilt("*.{level}") then {{', # {{ required to escape { in f-string
+ 'action(type="omfile" file="/dev/console")',
]
- expected = [f'@{host1}:999', f'@{host2}:514', '/dev/console']
+ for tmp in config:
+ self.assertIn(tmp, rsyslog_conf)
- for i in range(0, 3):
- self.assertIn(expected[i], config[i])
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
- def test_syslog_global(self):
+ def test_basic(self):
hostname = 'vyos123'
- domainname = 'example.local'
- self.cli_set(['system', 'host-name', hostname])
- self.cli_set(['system', 'domain-name', domainname])
- self.cli_set(base_path + ['global', 'marker', 'interval', '600'])
- self.cli_set(base_path + ['global', 'preserve-fqdn'])
- self.cli_set(base_path + ['global', 'facility', 'kern', 'level', 'err'])
+ domain_name = 'example.local'
+ default_marker_interval = default_value(base_path + ['marker', 'interval'])
+
+ facility = {
+ 'auth': {'level': 'info'},
+ 'kern': {'level': 'debug'},
+ 'all': {'level': 'notice'},
+ }
+
+ self.cli_set(['system', 'host-name'], value=hostname)
+ self.cli_set(['system', 'domain-name'], value=domain_name)
+ self.cli_set(base_path + ['preserve-fqdn'])
+
+ for tmp, tmp_options in facility.items():
+ level = tmp_options['level']
+ self.cli_set(base_path + ['local', 'facility', tmp, 'level'], value=level)
self.cli_commit()
- config = read_file(RSYSLOG_CONF)
+ config = get_config('')
expected = [
- '$MarkMessagePeriod 600',
- '$PreserveFQDN on',
- 'kern.err',
- f'$LocalHostName {hostname}.{domainname}',
+ f'module(load="immark" interval="{default_marker_interval}")',
+ 'global(preserveFQDN="on")',
+ f'global(localHostname="{hostname}.{domain_name}")',
]
-
for e in expected:
self.assertIn(e, config)
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
- def test_syslog_remote(self):
- rhost = '169.254.0.1'
- default_port = default_value(base_path + ['host', rhost, 'port'])
+ config = get_config('#### GLOBAL LOGGING ####')
+ prifilt = []
+ for tmp, tmp_options in facility.items():
+ if tmp == 'all':
+ tmp = '*'
+ level = tmp_options['level']
+ prifilt.append(f'{tmp}.{level}')
- self.cli_set(base_path + ['global', 'facility', 'all', 'level', 'info'])
- self.cli_set(base_path + ['global', 'facility', 'local7', 'level', 'debug'])
- self.cli_set(base_path + ['host', rhost, 'facility', 'all', 'level', 'all'])
- self.cli_set(base_path + ['host', rhost, 'protocol', 'tcp'])
+ prifilt.sort()
+ prifilt = ','.join(prifilt)
+ self.assertIn(f'if prifilt("{prifilt}") then {{', config)
+ self.assertIn( ' action(', config)
+ self.assertIn( ' type="omfile"', config)
+ self.assertIn( ' file="/var/log/messages"', config)
+ self.assertIn( ' rotation.sizeLimit="524288"', config)
+ self.assertIn( ' rotation.sizeLimitCommand="/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog"', config)
+
+ self.cli_set(base_path + ['marker', 'disable'])
self.cli_commit()
- config = read_file(RSYSLOG_CONF)
- self.assertIn(f'*.* @@{rhost}:{default_port}', config)
+ config = get_config('')
+ self.assertNotIn('module(load="immark"', config)
+
+ def test_remote(self):
+ dummy_if_path = ['interfaces', 'dummy', dummy_interface]
+ rhosts = {
+ '169.254.0.1': {
+ 'facility': {'auth' : {'level': 'info'}},
+ 'protocol': 'udp',
+ },
+ '2001:db8::1': {
+ 'facility': {'all' : {'level': 'debug'}},
+ 'port': '1514',
+ 'protocol': 'udp',
+ },
+ 'syslog.vyos.net': {
+ 'facility': {'all' : {'level': 'debug'}},
+ 'port': '1515',
+ 'protocol': 'tcp',
+ },
+ '169.254.0.3': {
+ 'facility': {'auth' : {'level': 'info'},
+ 'kern' : {'level': 'debug'},
+ 'all' : {'level': 'notice'},
+ },
+ 'format': ['include-timezone', 'octet-counted'],
+ 'protocol': 'tcp',
+ 'port': '10514',
+ },
+ }
+ default_port = default_value(base_path + ['remote', next(iter(rhosts)), 'port'])
+ default_protocol = default_value(base_path + ['remote', next(iter(rhosts)), 'protocol'])
+
+ for remote, remote_options in rhosts.items():
+ remote_base = base_path + ['remote', remote]
+
+ if 'port' in remote_options:
+ self.cli_set(remote_base + ['port'], value=remote_options['port'])
+
+ if 'facility' in remote_options:
+ for facility, facility_options in remote_options['facility'].items():
+ level = facility_options['level']
+ self.cli_set(remote_base + ['facility', facility, 'level'],
+ value=level)
+
+ if 'format' in remote_options:
+ for format in remote_options['format']:
+ self.cli_set(remote_base + ['format'], value=format)
+
+ if 'protocol' in remote_options:
+ protocol = remote_options['protocol']
+ self.cli_set(remote_base + ['protocol'], value=protocol)
+
+ if 'source_address' in remote_options:
+ source_address = remote_options['source_address']
+ self.cli_set(remote_base + ['source-address', source_address])
+
+ # check validate() - source address does not exist
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(dummy_if_path + ['address', f'{source_address}/32'])
- # Change default port and enable "octet-counting" mode
- port = '10514'
- self.cli_set(base_path + ['host', rhost, 'port', port])
- self.cli_set(base_path + ['host', rhost, 'format', 'octet-counted'])
self.cli_commit()
config = read_file(RSYSLOG_CONF)
- self.assertIn(f'*.* @@(o){rhost}:{port}', config)
+ for remote, remote_options in rhosts.items():
+ config = get_config(f'# Remote syslog to {remote}')
+ prifilt = []
+ if 'facility' in remote_options:
+ for facility, facility_options in remote_options['facility'].items():
+ level = facility_options['level']
+ if facility == 'all':
+ facility = '*'
+ prifilt.append(f'{facility}.{level}')
+
+ prifilt.sort()
+ prifilt = ','.join(prifilt)
+ if not prifilt:
+ # Skip test - as we do not render anything if no facility is set
+ continue
+
+ self.assertIn(f'if prifilt("{prifilt}") then {{', config)
+ self.assertIn( ' type="omfwd"', config)
+ self.assertIn(f' target="{remote}"', config)
+
+ port = default_port
+ if 'port' in remote_options:
+ port = remote_options['port']
+ self.assertIn(f'port="{port}"', config)
+
+ protocol = default_protocol
+ if 'protocol' in remote_options:
+ protocol = remote_options['protocol']
+ self.assertIn(f'protocol="{protocol}"', config)
+
+ if 'format' in remote_options:
+ if 'include-timezone' in remote_options['format']:
+ self.assertIn( ' template="SyslogProtocol23Format"', config)
+
+ if 'octet-counted' in remote_options['format']:
+ self.assertIn( ' TCP_Framing="octed-counted"', config)
+ else:
+ self.assertIn( ' TCP_Framing="traditional"', config)
+
+ # cleanup dummy interface
+ self.cli_delete(dummy_if_path)
+
+ def test_vrf_source_address(self):
+ rhosts = {
+ '169.254.0.10': { },
+ '169.254.0.11': {
+ 'vrf': {'name' : 'red', 'table' : '12321'},
+ 'source_address' : '169.254.0.11',
+ },
+ '169.254.0.12': {
+ 'vrf': {'name' : 'green', 'table' : '12322'},
+ 'source_address' : '169.254.0.12',
+ },
+ '169.254.0.13': {
+ 'vrf': {'name' : 'blue', 'table' : '12323'},
+ 'source_address' : '169.254.0.13',
+ },
+ }
+
+ for remote, remote_options in rhosts.items():
+ remote_base = base_path + ['remote', remote]
+ self.cli_set(remote_base + ['facility', 'all'])
+
+ vrf = None
+ if 'vrf' in remote_options:
+ vrf = remote_options['vrf']['name']
+ self.cli_set(['vrf', 'name', vrf, 'table'],
+ value=remote_options['vrf']['table'])
+ self.cli_set(remote_base + ['vrf'], value=vrf)
+
+ if 'source_address' in remote_options:
+ source_address = remote_options['source_address']
+ self.cli_set(remote_base + ['source-address'],
+ value=source_address)
+
+ idx = source_address.split('.')[-1]
+ self.cli_set(['interfaces', 'dummy', f'dum{idx}', 'address'],
+ value=f'{source_address}/32')
+ if vrf:
+ self.cli_set(['interfaces', 'dummy', f'dum{idx}', 'vrf'],
+ value=vrf)
+
+ self.cli_commit()
+
+ for remote, remote_options in rhosts.items():
+ config = get_config(f'# Remote syslog to {remote}')
+
+ self.assertIn(f'target="{remote}"', config)
+ if 'vrf' in remote_options:
+ vrf = remote_options['vrf']['name']
+ self.assertIn(f'Device="{vrf}"', config)
+
+ if 'source_address' in remote_options:
+ source_address = remote_options['source_address']
+ self.assertIn(f'Address="{source_address}"', config)
+
+ # Cleanup VRF/Dummy interfaces
+ for remote, remote_options in rhosts.items():
+ if 'vrf' in remote_options:
+ vrf = remote_options['vrf']['name']
+ self.cli_delete(['vrf', 'name', vrf])
+ if 'source_address' in remote_options:
+ source_address = remote_options['source_address']
+ idx = source_address.split('.')[-1]
+ self.cli_delete(['interfaces', 'dummy', f'dum{idx}'])
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 594de3eb0..18d660a4e 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -22,6 +22,7 @@ from ipaddress import ip_address
from ipaddress import ip_network
from json import dumps as json_write
+import psutil
from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
@@ -223,6 +224,21 @@ def verify(container):
if not os.path.exists(source):
raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!')
+ if 'tmpfs' in container_config:
+ for tmpfs, tmpfs_config in container_config['tmpfs'].items():
+ if 'destination' not in tmpfs_config:
+ raise ConfigError(f'tmpfs "{tmpfs}" has no destination path configured!')
+ if 'size' in tmpfs_config:
+ free_mem_mb: int = psutil.virtual_memory().available / 1024 / 1024
+ if int(tmpfs_config['size']) > free_mem_mb:
+ Warning(f'tmpfs "{tmpfs}" size is greater than the current free memory!')
+
+ total_mem_mb: int = (psutil.virtual_memory().total / 1024 / 1024) / 2
+ if int(tmpfs_config['size']) > total_mem_mb:
+ raise ConfigError(f'tmpfs "{tmpfs}" size should not be more than 50% of total system memory!')
+ else:
+ raise ConfigError(f'tmpfs "{tmpfs}" has no size configured!')
+
if 'port' in container_config:
for tmp in container_config['port']:
if not {'source', 'destination'} <= set(container_config['port'][tmp]):
@@ -273,6 +289,13 @@ def verify(container):
if 'registry' in container:
for registry, registry_config in container['registry'].items():
+ if 'mirror' in registry_config:
+ if 'host_name' in registry_config['mirror'] and 'address' in registry_config['mirror']:
+ raise ConfigError(f'Container registry mirror address/host-name are mutually exclusive!')
+
+ if 'path' in registry_config['mirror'] and not registry_config['mirror']['path'].startswith('/'):
+ raise ConfigError('Container registry mirror path must start with "/"!')
+
if 'authentication' not in registry_config:
continue
if not {'username', 'password'} <= set(registry_config['authentication']):
@@ -362,6 +385,14 @@ def generate_run_arguments(name, container_config):
prop = vol_config['propagation']
volume += f' --volume {svol}:{dvol}:{mode},{prop}'
+ # Mount tmpfs
+ tmpfs = ''
+ if 'tmpfs' in container_config:
+ for tmpfs_config in container_config['tmpfs'].values():
+ dest = tmpfs_config['destination']
+ size = tmpfs_config['size']
+ tmpfs += f' --mount=type=tmpfs,tmpfs-size={size}M,destination={dest}'
+
host_pid = ''
if 'allow_host_pid' in container_config:
host_pid = '--pid host'
@@ -373,7 +404,7 @@ def generate_run_arguments(name, container_config):
container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
- f'--name {name} {hostname} {device} {port} {name_server} {volume} {env_opt} {label} {uid} {host_pid}'
+ f'--name {name} {hostname} {device} {port} {name_server} {volume} {tmpfs} {env_opt} {label} {uid} {host_pid}'
entrypoint = ''
if 'entrypoint' in container_config:
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index 4f1141dcb..84316c16e 100755
--- a/src/conf_mode/interfaces_bonding.py
+++ b/src/conf_mode/interfaces_bonding.py
@@ -126,9 +126,8 @@ def get_config(config=None):
# Restore existing config level
conf.set_level(old_level)
- if dict_search('member.interface', bond):
- for interface, interface_config in bond['member']['interface'].items():
-
+ if dict_search('member.interface', bond) is not None:
+ for interface in bond['member']['interface']:
interface_ethernet_config = conf.get_config_dict(
['interfaces', 'ethernet', interface],
key_mangling=('-', '_'),
@@ -137,44 +136,45 @@ def get_config(config=None):
with_defaults=False,
with_recursive_defaults=False)
- interface_config['config_paths'] = dict_to_paths_values(interface_ethernet_config)
+ bond['member']['interface'][interface].update({'config_paths' :
+ dict_to_paths_values(interface_ethernet_config)})
# Check if member interface is a new member
if not conf.exists_effective(base + [ifname, 'member', 'interface', interface]):
bond['shutdown_required'] = {}
- interface_config['new_added'] = {}
+ bond['member']['interface'][interface].update({'new_added' : {}})
# Check if member interface is disabled
conf.set_level(['interfaces'])
section = Section.section(interface) # this will be 'ethernet' for 'eth0'
if conf.exists([section, interface, 'disable']):
- interface_config['disable'] = ''
+ if tmp: bond['member']['interface'][interface].update({'disable': ''})
conf.set_level(old_level)
# Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
- if tmp: interface_config['is_bridge_member'] = tmp
+ if tmp: bond['member']['interface'][interface].update({'is_bridge_member' : tmp})
# Check if member interface is already member of a bond
tmp = is_member(conf, interface, 'bonding')
- for tmp in is_member(conf, interface, 'bonding'):
- if bond['ifname'] == tmp:
- continue
- interface_config['is_bond_member'] = tmp
+ if ifname in tmp:
+ del tmp[ifname]
+ if tmp: bond['member']['interface'][interface].update({'is_bond_member' : tmp})
# Check if member interface is used as source-interface on another interface
tmp = is_source_interface(conf, interface)
- if tmp: interface_config['is_source_interface'] = tmp
+ if tmp: bond['member']['interface'][interface].update({'is_source_interface' : tmp})
# bond members must not have an assigned address
tmp = has_address_configured(conf, interface)
- if tmp: interface_config['has_address'] = {}
+ if tmp: bond['member']['interface'][interface].update({'has_address' : ''})
# bond members must not have a VRF attached
tmp = has_vrf_configured(conf, interface)
- if tmp: interface_config['has_vrf'] = {}
+ if tmp: bond['member']['interface'][interface].update({'has_vrf' : ''})
+
return bond
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index 637db442a..aff93af2a 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -74,8 +74,9 @@ def get_config(config=None):
for interface in list(bridge['member']['interface']):
# Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
- if tmp and bridge['ifname'] not in tmp:
- bridge['member']['interface'][interface].update({'is_bridge_member' : tmp})
+ if ifname in tmp:
+ del tmp[ifname]
+ if tmp: bridge['member']['interface'][interface].update({'is_bridge_member' : tmp})
# Check if member interface is already member of a bond
tmp = is_member(conf, interface, 'bonding')
diff --git a/src/conf_mode/interfaces_geneve.py b/src/conf_mode/interfaces_geneve.py
index 007708d4a..1c5b4d0e7 100755
--- a/src/conf_mode/interfaces_geneve.py
+++ b/src/conf_mode/interfaces_geneve.py
@@ -47,7 +47,7 @@ def get_config(config=None):
# GENEVE interfaces are picky and require recreation if certain parameters
# change. But a GENEVE interface should - of course - not be re-created if
# it's description or IP address is adjusted. Feels somehow logic doesn't it?
- for cli_option in ['remote', 'vni', 'parameters']:
+ for cli_option in ['remote', 'vni', 'parameters', 'port']:
if is_node_changed(conf, base + [ifname, cli_option]):
geneve.update({'rebuild_required': {}})
diff --git a/src/conf_mode/interfaces_vxlan.py b/src/conf_mode/interfaces_vxlan.py
index 68646e8ff..256b65708 100755
--- a/src/conf_mode/interfaces_vxlan.py
+++ b/src/conf_mode/interfaces_vxlan.py
@@ -95,6 +95,8 @@ def verify(vxlan):
if 'group' in vxlan:
if 'source_interface' not in vxlan:
raise ConfigError('Multicast VXLAN requires an underlaying interface')
+ if 'remote' in vxlan:
+ raise ConfigError('Both group and remote cannot be specified')
verify_source_interface(vxlan)
if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan):
diff --git a/src/conf_mode/interfaces_wireguard.py b/src/conf_mode/interfaces_wireguard.py
index 877d013cf..192937dba 100755
--- a/src/conf_mode/interfaces_wireguard.py
+++ b/src/conf_mode/interfaces_wireguard.py
@@ -19,6 +19,9 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
+from vyos.configdict import is_source_interface
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
@@ -35,6 +38,7 @@ from vyos import airbag
from pathlib import Path
airbag.enable()
+
def get_config(config=None):
"""
Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
@@ -61,11 +65,25 @@ def get_config(config=None):
if 'disable' not in peer_config and 'host_name' in peer_config:
wireguard['peers_need_resolve'].append(peer)
+ # Check if interface is used as source-interface on VXLAN interface
+ tmp = is_source_interface(conf, ifname, 'vxlan')
+ if tmp:
+ if 'deleted' not in wireguard:
+ set_dependents('vxlan', conf, tmp)
+ else:
+ wireguard['is_source_interface'] = tmp
+
return wireguard
+
def verify(wireguard):
if 'deleted' in wireguard:
verify_bridge_delete(wireguard)
+ if 'is_source_interface' in wireguard:
+ raise ConfigError(
+ f'Interface "{wireguard["ifname"]}" cannot be deleted as it is used '
+ f'as source interface for "{wireguard["is_source_interface"]}"!'
+ )
return None
verify_mtu_ipv6(wireguard)
@@ -119,9 +137,11 @@ def verify(wireguard):
public_keys.append(peer['public_key'])
+
def generate(wireguard):
return None
+
def apply(wireguard):
check_kmod('wireguard')
@@ -157,8 +177,11 @@ def apply(wireguard):
domain_action = 'stop'
call(f'systemctl {domain_action} vyos-domain-resolver.service')
+ call_dependents()
+
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/load-balancing_wan.py b/src/conf_mode/load-balancing_wan.py
index 5da0b906b..92d9acfba 100755
--- a/src/conf_mode/load-balancing_wan.py
+++ b/src/conf_mode/load-balancing_wan.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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
@@ -14,24 +14,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import os
-
from sys import exit
-from shutil import rmtree
-from vyos.base import Warning
from vyos.config import Config
from vyos.configdep import set_dependents, call_dependents
from vyos.utils.process import cmd
-from vyos.template import render
from vyos import ConfigError
from vyos import airbag
airbag.enable()
-load_balancing_dir = '/run/load-balance'
-load_balancing_conf_file = f'{load_balancing_dir}/wlb.conf'
-systemd_service = 'vyos-wan-load-balance.service'
-
+service = 'vyos-wan-load-balance.service'
def get_config(config=None):
if config:
@@ -40,6 +32,7 @@ def get_config(config=None):
conf = Config()
base = ['load-balancing', 'wan']
+
lb = conf.get_config_dict(base, key_mangling=('-', '_'),
no_tag_node_value_mangle=True,
get_first_key=True,
@@ -59,87 +52,61 @@ def verify(lb):
if not lb:
return None
- if 'interface_health' not in lb:
- raise ConfigError(
- 'A valid WAN load-balance configuration requires an interface with a nexthop!'
- )
-
- for interface, interface_config in lb['interface_health'].items():
- if 'nexthop' not in interface_config:
- raise ConfigError(
- f'interface-health {interface} nexthop must be specified!')
-
- if 'test' in interface_config:
- for test_rule, test_config in interface_config['test'].items():
- if 'type' in test_config:
- if test_config['type'] == 'user-defined' and 'test_script' not in test_config:
- raise ConfigError(
- f'test {test_rule} script must be defined for test-script!'
- )
-
- if 'rule' not in lb:
- Warning(
- 'At least one rule with an (outbound) interface must be defined for WAN load balancing to be active!'
- )
+ if 'interface_health' in lb:
+ for ifname, health_conf in lb['interface_health'].items():
+ if 'nexthop' not in health_conf:
+ raise ConfigError(f'Nexthop must be configured for interface {ifname}')
+
+ if 'test' not in health_conf:
+ continue
+
+ for test_id, test_conf in health_conf['test'].items():
+ if 'type' not in test_conf:
+ raise ConfigError(f'No type configured for health test on interface {ifname}')
+
+ if test_conf['type'] == 'user-defined' and 'test_script' not in test_conf:
+ raise ConfigError(f'Missing user-defined script for health test on interface {ifname}')
else:
- for rule, rule_config in lb['rule'].items():
- if 'inbound_interface' not in rule_config:
- raise ConfigError(f'rule {rule} inbound-interface must be specified!')
- if {'failover', 'exclude'} <= set(rule_config):
- raise ConfigError(f'rule {rule} failover cannot be configured with exclude!')
- if {'limit', 'exclude'} <= set(rule_config):
- raise ConfigError(f'rule {rule} limit cannot be used with exclude!')
- if 'interface' not in rule_config:
- if 'exclude' not in rule_config:
- Warning(
- f'rule {rule} will be inactive because no (outbound) interfaces have been defined for this rule'
- )
- for direction in {'source', 'destination'}:
- if direction in rule_config:
- if 'protocol' in rule_config and 'port' in rule_config[
- direction]:
- if rule_config['protocol'] not in {'tcp', 'udp'}:
- raise ConfigError('ports can only be specified when protocol is "tcp" or "udp"')
+ raise ConfigError('Interface health tests must be configured')
+ if 'rule' in lb:
+ for rule_id, rule_conf in lb['rule'].items():
+ if 'interface' not in rule_conf and 'exclude' not in rule_conf:
+ raise ConfigError(f'Interface or exclude not specified on load-balancing wan rule {rule_id}')
-def generate(lb):
- if not lb:
- # Delete /run/load-balance/wlb.conf
- if os.path.isfile(load_balancing_conf_file):
- os.unlink(load_balancing_conf_file)
- # Delete old directories
- if os.path.isdir(load_balancing_dir):
- rmtree(load_balancing_dir, ignore_errors=True)
- if os.path.exists('/var/run/load-balance/wlb.out'):
- os.unlink('/var/run/load-balance/wlb.out')
+ if 'failover' in rule_conf and 'exclude' in rule_conf:
+ raise ConfigError(f'Failover cannot be configured with exclude on load-balancing wan rule {rule_id}')
- return None
+ if 'limit' in rule_conf:
+ if 'exclude' in rule_conf:
+ raise ConfigError(f'Limit cannot be configured with exclude on load-balancing wan rule {rule_id}')
- # Create load-balance dir
- if not os.path.isdir(load_balancing_dir):
- os.mkdir(load_balancing_dir)
+ if 'rate' in rule_conf['limit'] and 'period' not in rule_conf['limit']:
+ raise ConfigError(f'Missing "limit period" on load-balancing wan rule {rule_id}')
- render(load_balancing_conf_file, 'load-balancing/wlb.conf.j2', lb)
+ if 'period' in rule_conf['limit'] and 'rate' not in rule_conf['limit']:
+ raise ConfigError(f'Missing "limit rate" on load-balancing wan rule {rule_id}')
- return None
+ for direction in ['source', 'destination']:
+ if direction in rule_conf:
+ if 'port' in rule_conf[direction]:
+ if 'protocol' not in rule_conf:
+ raise ConfigError(f'Protocol required to specify port on load-balancing wan rule {rule_id}')
+
+ if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
+ raise ConfigError(f'Protocol must be tcp, udp or tcp_udp to specify port on load-balancing wan rule {rule_id}')
+def generate(lb):
+ return None
def apply(lb):
if not lb:
- try:
- cmd(f'systemctl stop {systemd_service}')
- except Exception as e:
- print(f"Error message: {e}")
-
+ cmd(f'sudo systemctl stop {service}')
else:
- cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1')
- cmd(f'systemctl restart {systemd_service}')
+ cmd(f'sudo systemctl restart {service}')
call_dependents()
- return None
-
-
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py
index 1174b1238..c64c59af7 100755
--- a/src/conf_mode/service_snmp.py
+++ b/src/conf_mode/service_snmp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2024 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -22,6 +22,7 @@ from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_vrf
+from vyos.defaults import systemd_services
from vyos.snmpv3_hashgen import plaintext_to_md5
from vyos.snmpv3_hashgen import plaintext_to_sha1
from vyos.snmpv3_hashgen import random
@@ -43,7 +44,7 @@ config_file_access = r'/usr/share/snmp/snmpd.conf'
config_file_user = r'/var/lib/snmp/snmpd.conf'
default_script_dir = r'/config/user-data/'
systemd_override = r'/run/systemd/system/snmpd.service.d/override.conf'
-systemd_service = 'snmpd.service'
+systemd_service = systemd_services['snmpd']
def get_config(config=None):
if config:
@@ -146,6 +147,9 @@ def verify(snmp):
return None
if 'user' in snmp['v3']:
+ if 'engineid' not in snmp['v3']:
+ raise ConfigError(f'EngineID must be configured for SNMPv3!')
+
for user, user_config in snmp['v3']['user'].items():
if 'group' not in user_config:
raise ConfigError(f'Group membership required for user "{user}"!')
diff --git a/src/conf_mode/system_host-name.py b/src/conf_mode/system_host-name.py
index 3f245f166..fef034d1c 100755
--- a/src/conf_mode/system_host-name.py
+++ b/src/conf_mode/system_host-name.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2024 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -23,6 +23,7 @@ import vyos.hostsd_client
from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import leaf_node_changed
+from vyos.defaults import systemd_services
from vyos.ifconfig import Section
from vyos.template import is_ip
from vyos.utils.process import cmd
@@ -174,11 +175,13 @@ def apply(config):
# Restart services that use the hostname
if hostname_new != hostname_old:
- call("systemctl restart rsyslog.service")
+ tmp = systemd_services['rsyslog']
+ call(f'systemctl restart {tmp}')
# If SNMP is running, restart it too
if process_named_running('snmpd') and config['snmpd_restart_reqired']:
- call('systemctl restart snmpd.service')
+ tmp = systemd_services['snmpd']
+ call(f'systemctl restart {tmp}')
return None
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index e2832cde6..064a1aa91 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -86,7 +86,7 @@ def verify(options):
if 'source_address' in config:
if not is_addr_assigned(config['source_address']):
- raise ConfigError('No interface with give address specified!')
+ raise ConfigError('No interface with given address specified!')
if 'ssh_client' in options:
config = options['ssh_client']
diff --git a/src/conf_mode/system_sflow.py b/src/conf_mode/system_sflow.py
index 41119b494..a22dac36f 100755
--- a/src/conf_mode/system_sflow.py
+++ b/src/conf_mode/system_sflow.py
@@ -54,7 +54,7 @@ def verify(sflow):
# Check if configured sflow agent-address exist in the system
if 'agent_address' in sflow:
tmp = sflow['agent_address']
- if not is_addr_assigned(tmp):
+ if not is_addr_assigned(tmp, include_vrf=True):
raise ConfigError(
f'Configured "sflow agent-address {tmp}" does not exist in the system!'
)
diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py
index eb2f02eb3..414bd4b6b 100755
--- a/src/conf_mode/system_syslog.py
+++ b/src/conf_mode/system_syslog.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2024 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -20,17 +20,22 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
+from vyos.defaults import systemd_services
+from vyos.utils.network import is_addr_assigned
from vyos.utils.process import call
from vyos.template import render
+from vyos.template import is_ipv4
+from vyos.template import is_ipv6
from vyos import ConfigError
from vyos import airbag
airbag.enable()
-rsyslog_conf = '/etc/rsyslog.d/00-vyos.conf'
+rsyslog_conf = '/run/rsyslog/rsyslog.conf'
logrotate_conf = '/etc/logrotate.d/vyos-rsyslog'
-systemd_override = r'/run/systemd/system/rsyslog.service.d/override.conf'
+
+systemd_socket = 'syslog.socket'
+systemd_service = systemd_services['rsyslog']
def get_config(config=None):
if config:
@@ -46,23 +51,17 @@ def get_config(config=None):
syslog.update({ 'logrotate' : logrotate_conf })
- tmp = is_node_changed(conf, base + ['vrf'])
- if tmp: syslog.update({'restart_required': {}})
-
syslog = conf.merge_defaults(syslog, recursive=True)
- if syslog.from_defaults(['global']):
- del syslog['global']
-
- if (
- 'global' in syslog
- and 'preserve_fqdn' in syslog['global']
- and conf.exists(['system', 'host-name'])
- and conf.exists(['system', 'domain-name'])
- ):
- hostname = conf.return_value(['system', 'host-name'])
- domain = conf.return_value(['system', 'domain-name'])
- fqdn = f'{hostname}.{domain}'
- syslog['global']['local_host_name'] = fqdn
+ if syslog.from_defaults(['local']):
+ del syslog['local']
+
+ if 'preserve_fqdn' in syslog:
+ if conf.exists(['system', 'host-name']):
+ tmp = conf.return_value(['system', 'host-name'])
+ syslog['preserve_fqdn']['host_name'] = tmp
+ if conf.exists(['system', 'domain-name']):
+ tmp = conf.return_value(['system', 'domain-name'])
+ syslog['preserve_fqdn']['domain_name'] = tmp
return syslog
@@ -70,13 +69,33 @@ def verify(syslog):
if not syslog:
return None
- if 'host' in syslog:
- for host, host_options in syslog['host'].items():
- if 'protocol' in host_options and host_options['protocol'] == 'udp':
- if 'format' in host_options and 'octet_counted' in host_options['format']:
- Warning(f'Syslog UDP transport for "{host}" should not use octet-counted format!')
-
- verify_vrf(syslog)
+ if 'preserve_fqdn' in syslog:
+ if 'host_name' not in syslog['preserve_fqdn']:
+ Warning('No "system host-name" defined - cannot set syslog FQDN!')
+ if 'domain_name' not in syslog['preserve_fqdn']:
+ Warning('No "system domain-name" defined - cannot set syslog FQDN!')
+
+ if 'remote' in syslog:
+ for remote, remote_options in syslog['remote'].items():
+ if 'protocol' in remote_options and remote_options['protocol'] == 'udp':
+ if 'format' in remote_options and 'octet_counted' in remote_options['format']:
+ Warning(f'Syslog UDP transport for "{remote}" should not use octet-counted format!')
+
+ if 'vrf' in remote_options:
+ verify_vrf(remote_options)
+
+ if 'source_address' in remote_options:
+ vrf = None
+ if 'vrf' in remote_options:
+ vrf = remote_options['vrf']
+ if not is_addr_assigned(remote_options['source_address'], vrf):
+ raise ConfigError('No interface with given address specified!')
+
+ source_address = remote_options['source_address']
+ if ((is_ipv4(remote) and is_ipv6(source_address)) or
+ (is_ipv6(remote) and is_ipv4(source_address))):
+ raise ConfigError(f'Source-address "{source_address}" does not match '\
+ f'address-family of remote "{remote}"!')
def generate(syslog):
if not syslog:
@@ -88,26 +107,15 @@ def generate(syslog):
return None
render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog)
- render(systemd_override, 'rsyslog/override.conf.j2', syslog)
render(logrotate_conf, 'rsyslog/logrotate.j2', syslog)
-
- # Reload systemd manager configuration
- call('systemctl daemon-reload')
return None
def apply(syslog):
- systemd_socket = 'syslog.socket'
- systemd_service = 'syslog.service'
if not syslog:
call(f'systemctl stop {systemd_service} {systemd_socket}')
return None
- # we need to restart the service if e.g. the VRF name changed
- systemd_action = 'reload-or-restart'
- if 'restart_required' in syslog:
- systemd_action = 'restart'
-
- call(f'systemctl {systemd_action} {systemd_service}')
+ call(f'systemctl reload-or-restart {systemd_service}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 25604d2a2..71a503e61 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -156,6 +156,8 @@ def get_config(config=None):
_, vti = get_interface_dict(conf, ['interfaces', 'vti'], vti_interface)
ipsec['vti_interface_dicts'][vti_interface] = vti
+ ipsec['vpp_ipsec_exists'] = conf.exists(['vpp', 'settings', 'ipsec'])
+
return ipsec
def get_dhcp_address(iface):
@@ -484,6 +486,17 @@ def verify(ipsec):
else:
raise ConfigError(f"Missing ike-group on site-to-site peer {peer}")
+ # verify encryption algorithm compatibility for IKE with VPP
+ if ipsec['vpp_ipsec_exists']:
+ ike_group = ipsec['ike_group'][peer_conf['ike_group']]
+ for proposal, proposal_config in ike_group.get('proposal', {}).items():
+ algs = ['gmac', 'serpent', 'twofish']
+ if any(alg in proposal_config['encryption'] for alg in algs):
+ raise ConfigError(
+ f'Encryption algorithm {proposal_config["encryption"]} cannot be used '
+ f'for IKE proposal {proposal} for site-to-site peer {peer} with VPP'
+ )
+
if 'authentication' not in peer_conf or 'mode' not in peer_conf['authentication']:
raise ConfigError(f"Missing authentication on site-to-site peer {peer}")
@@ -562,7 +575,7 @@ def verify(ipsec):
esp_group_name = tunnel_conf['esp_group'] if 'esp_group' in tunnel_conf else peer_conf['default_esp_group']
- if esp_group_name not in ipsec['esp_group']:
+ if esp_group_name not in ipsec.get('esp_group'):
raise ConfigError(f"Invalid esp-group on tunnel {tunnel} for site-to-site peer {peer}")
esp_group = ipsec['esp_group'][esp_group_name]
@@ -574,6 +587,18 @@ def verify(ipsec):
if ('local' in tunnel_conf and 'prefix' in tunnel_conf['local']) or ('remote' in tunnel_conf and 'prefix' in tunnel_conf['remote']):
raise ConfigError(f"Local/remote prefix cannot be used with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}")
+ # verify ESP encryption algorithm compatibility with VPP
+ # because Marvel plugin for VPP doesn't support all algorithms that Strongswan does
+ if ipsec['vpp_ipsec_exists']:
+ for proposal, proposal_config in esp_group.get('proposal', {}).items():
+ algs = ['aes128', 'aes192', 'aes256', 'aes128gcm128', 'aes192gcm128', 'aes256gcm128']
+ if proposal_config['encryption'] not in algs:
+ raise ConfigError(
+ f'Encryption algorithm {proposal_config["encryption"]} cannot be used '
+ f'for ESP proposal {proposal} on tunnel {tunnel} for site-to-site peer {peer} with VPP'
+ )
+
+
def cleanup_pki_files():
for path in [CERT_PATH, CA_PATH, CRL_PATH, KEY_PATH, PUBKEY_PATH]:
if not os.path.exists(path):
diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client
index 55d15a163..4cc824afd 100755
--- a/src/etc/netplug/vyos-netplug-dhcp-client
+++ b/src/etc/netplug/vyos-netplug-dhcp-client
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023-2025 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
@@ -19,44 +19,39 @@ import sys
from time import sleep
-from vyos.configquery import ConfigTreeQuery
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.commit import commit_in_progress
-from vyos.utils.process import call
from vyos import airbag
+
airbag.enable()
if len(sys.argv) < 3:
- airbag.noteworthy("Must specify both interface and link status!")
+ airbag.noteworthy('Must specify both interface and link status!')
sys.exit(1)
if not boot_configuration_complete():
- airbag.noteworthy("System bootup not yet finished...")
+ airbag.noteworthy('System bootup not yet finished...')
sys.exit(1)
+interface = sys.argv[1]
+# helper scripts should only work on physical interfaces not on individual
+# sub-interfaces. Moving e.g. a VLAN interface in/out a VRF will also trigger
+# this script which should be prohibited - bail out early
+if '.' in interface:
+ sys.exit(0)
+
while commit_in_progress():
sleep(1)
-interface = sys.argv[1]
in_out = sys.argv[2]
-config = ConfigTreeQuery()
+config = Config()
interface_path = ['interfaces'] + Section.get_config_path(interface).split()
-
-for _, interface_config in config.get_config_dict(interface_path).items():
- # Bail out early if we do not have an IP address configured
- if 'address' not in interface_config:
- continue
- # Bail out early if interface ist administrative down
- if 'disable' in interface_config:
- continue
- systemd_action = 'start'
- if in_out == 'out':
- systemd_action = 'stop'
- # Start/Stop DHCP service
- if 'dhcp' in interface_config['address']:
- call(f'systemctl {systemd_action} dhclient@{interface}.service')
- # Start/Stop DHCPv6 service
- if 'dhcpv6' in interface_config['address']:
- call(f'systemctl {systemd_action} dhcp6c@{interface}.service')
+_, interface_config = get_interface_dict(
+ config, interface_path[:-1], ifname=interface, with_pki=True
+)
+Interface(interface).update(interface_config)
diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-wlb b/src/etc/ppp/ip-up.d/99-vyos-pppoe-wlb
new file mode 100755
index 000000000..fff258afa
--- /dev/null
+++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-wlb
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This is a Python hook script which is invoked whenever a PPPoE session goes
+# "ip-up". It will call into our vyos.ifconfig library and will then execute
+# common tasks for the PPPoE interface. The reason we have to "hook" this is
+# that we can not create a pppoeX interface in advance in linux and then connect
+# pppd to this already existing interface.
+
+import os
+import signal
+
+from sys import argv
+from sys import exit
+
+from vyos.defaults import directories
+
+# When the ppp link comes up, this script is called with the following
+# parameters
+# $1 the interface name used by pppd (e.g. ppp3)
+# $2 the tty device name
+# $3 the tty device speed
+# $4 the local IP address for the interface
+# $5 the remote IP address
+# $6 the parameter specified by the 'ipparam' option to pppd
+
+if (len(argv) < 7):
+ exit(1)
+
+wlb_pid_file = '/run/wlb_daemon.pid'
+
+interface = argv[6]
+nexthop = argv[5]
+
+if not os.path.exists(directories['ppp_nexthop_dir']):
+ os.mkdir(directories['ppp_nexthop_dir'])
+
+nexthop_file = os.path.join(directories['ppp_nexthop_dir'], interface)
+
+with open(nexthop_file, 'w') as f:
+ f.write(nexthop)
+
+# Trigger WLB daemon update
+if os.path.exists(wlb_pid_file):
+ with open(wlb_pid_file, 'r') as f:
+ pid = int(f.read())
+
+ os.kill(pid, signal.SIGUSR2)
diff --git a/src/etc/rsyslog.conf b/src/etc/rsyslog.conf
deleted file mode 100644
index b3f41acb6..000000000
--- a/src/etc/rsyslog.conf
+++ /dev/null
@@ -1,67 +0,0 @@
-#################
-#### MODULES ####
-#################
-
-$ModLoad imuxsock # provides support for local system logging
-$ModLoad imklog # provides kernel logging support (previously done by rklogd)
-#$ModLoad immark # provides --MARK-- message capability
-
-$OmitLocalLogging off
-$SystemLogSocketName /run/systemd/journal/syslog
-
-$KLogPath /proc/kmsg
-
-###########################
-#### GLOBAL DIRECTIVES ####
-###########################
-
-# Use traditional timestamp format.
-# To enable high precision timestamps, comment out the following line.
-# A modern-style logfile format similar to TraditionalFileFormat, buth with high-precision timestamps and timezone information
-#$ActionFileDefaultTemplate RSYSLOG_FileFormat
-# The "old style" default log file format with low-precision timestamps
-$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
-
-# Filter duplicated messages
-$RepeatedMsgReduction on
-
-#
-# Set the default permissions for all log files.
-#
-$FileOwner root
-$FileGroup adm
-$FileCreateMode 0640
-$DirCreateMode 0755
-$Umask 0022
-
-#
-# Stop excessive logging of sudo
-#
-:msg, contains, " pam_unix(sudo:session): session opened for user root(uid=0) by" stop
-:msg, contains, "pam_unix(sudo:session): session closed for user root" stop
-
-#
-# Include all config files in /etc/rsyslog.d/
-#
-$IncludeConfig /etc/rsyslog.d/*.conf
-
-# The lines below cause all listed daemons/processes to be logged into
-# /var/log/auth.log, then drops the message so it does not also go to the
-# regular syslog so that messages are not duplicated
-
-$outchannel auth_log,/var/log/auth.log
-if $programname == 'CRON' or
- $programname == 'sudo' or
- $programname == 'su'
- then :omfile:$auth_log
-
-if $programname == 'CRON' or
- $programname == 'sudo' or
- $programname == 'su'
- then stop
-
-###############
-#### RULES ####
-###############
-# Emergencies are sent to everybody logged in.
-*.emerg :omusrmsg:* \ No newline at end of file
diff --git a/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf b/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf
index 682e5bbce..4a04892c0 100644
--- a/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf
+++ b/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf
@@ -5,3 +5,5 @@ After=vyos-router.service
[Service]
ExecStart=
ExecStart=/usr/sbin/kea-dhcp4 -c /run/kea/kea-dhcp4.conf
+ExecStartPost=!/usr/bin/python3 /usr/libexec/vyos/system/sync-dhcp-lease-to-hosts.py --inet
+Restart=on-failure
diff --git a/src/etc/systemd/system/rsyslog.service.d/override.conf b/src/etc/systemd/system/rsyslog.service.d/override.conf
new file mode 100644
index 000000000..665b994d9
--- /dev/null
+++ b/src/etc/systemd/system/rsyslog.service.d/override.conf
@@ -0,0 +1,10 @@
+[Unit]
+StartLimitIntervalSec=0
+
+[Service]
+ExecStart=
+ExecStart=/usr/sbin/rsyslogd -n -iNONE -f /run/rsyslog/rsyslog.conf
+Restart=always
+RestartPreventExitStatus=
+RestartSec=10
+RuntimeDirectoryPreserve=yes
diff --git a/src/helpers/vyos-load-balancer.py b/src/helpers/vyos-load-balancer.py
new file mode 100755
index 000000000..30329fd5c
--- /dev/null
+++ b/src/helpers/vyos-load-balancer.py
@@ -0,0 +1,312 @@
+#!/usr/bin/python3
+
+# Copyright 2024-2025 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/>.
+
+import json
+import os
+import signal
+import sys
+import time
+
+from vyos.config import Config
+from vyos.template import render
+from vyos.utils.commit import commit_in_progress
+from vyos.utils.network import get_interface_address
+from vyos.utils.process import rc_cmd
+from vyos.utils.process import run
+from vyos.xml_ref import get_defaults
+from vyos.wanloadbalance import health_ping_host
+from vyos.wanloadbalance import health_ping_host_ttl
+from vyos.wanloadbalance import parse_dhcp_nexthop
+from vyos.wanloadbalance import parse_ppp_nexthop
+
+nftables_wlb_conf = '/run/nftables_wlb.conf'
+wlb_status_file = '/run/wlb_status.json'
+wlb_pid_file = '/run/wlb_daemon.pid'
+sleep_interval = 5 # Main loop sleep interval
+
+def health_check(ifname, conf, state, test_defaults):
+ # Run health tests for interface
+
+ if get_ipv4_address(ifname) is None:
+ return False
+
+ if 'test' not in conf:
+ resp_time = test_defaults['resp-time']
+ target = conf['nexthop']
+
+ if target == 'dhcp':
+ target = state['dhcp_nexthop']
+
+ if not target:
+ return False
+
+ return health_ping_host(target, ifname, wait_time=resp_time)
+
+ for test_id, test_conf in conf['test'].items():
+ check_type = test_conf['type']
+
+ if check_type == 'ping':
+ resp_time = test_conf['resp_time']
+ target = test_conf['target']
+ if not health_ping_host(target, ifname, wait_time=resp_time):
+ return False
+ elif check_type == 'ttl':
+ target = test_conf['target']
+ ttl_limit = test_conf['ttl_limit']
+ if not health_ping_host_ttl(target, ifname, ttl_limit=ttl_limit):
+ return False
+ elif check_type == 'user-defined':
+ script = test_conf['test_script']
+ rc = run(script)
+ if rc != 0:
+ return False
+
+ return True
+
+def on_state_change(lb, ifname, state):
+ # Run hook on state change
+ if 'hook' in lb:
+ script_path = os.path.join('/config/scripts/', lb['hook'])
+ env = {
+ 'WLB_INTERFACE_NAME': ifname,
+ 'WLB_INTERFACE_STATE': 'ACTIVE' if state else 'FAILED'
+ }
+
+ code = run(script_path, env=env)
+ if code != 0:
+ print('WLB hook returned non-zero error code')
+
+ print(f'INFO: State change: {ifname} -> {state}')
+
+def get_ipv4_address(ifname):
+ # Get primary ipv4 address on interface (for source nat)
+ addr_json = get_interface_address(ifname)
+ if addr_json and 'addr_info' in addr_json and len(addr_json['addr_info']) > 0:
+ for addr_info in addr_json['addr_info']:
+ if addr_info['family'] == 'inet':
+ if 'local' in addr_info:
+ return addr_json['addr_info'][0]['local']
+ return None
+
+def dynamic_nexthop_update(lb, ifname):
+ # Update on DHCP/PPP address/nexthop changes
+ # Return True if nftables needs to be updated - IP change
+
+ if 'dhcp_nexthop' in lb['health_state'][ifname]:
+ if ifname[:5] == 'pppoe':
+ dhcp_nexthop_addr = parse_ppp_nexthop(ifname)
+ else:
+ dhcp_nexthop_addr = parse_dhcp_nexthop(ifname)
+
+ table_num = lb['health_state'][ifname]['table_number']
+
+ if dhcp_nexthop_addr and lb['health_state'][ifname]['dhcp_nexthop'] != dhcp_nexthop_addr:
+ lb['health_state'][ifname]['dhcp_nexthop'] = dhcp_nexthop_addr
+ run(f'ip route replace table {table_num} default dev {ifname} via {dhcp_nexthop_addr}')
+
+ if_addr = get_ipv4_address(ifname)
+ if if_addr and if_addr != lb['health_state'][ifname]['if_addr']:
+ lb['health_state'][ifname]['if_addr'] = if_addr
+ return True
+
+ return False
+
+def nftables_update(lb):
+ # Atomically reload nftables table from template
+ if not os.path.exists(nftables_wlb_conf):
+ lb['first_install'] = True
+ elif 'first_install' in lb:
+ del lb['first_install']
+
+ render(nftables_wlb_conf, 'load-balancing/nftables-wlb.j2', lb)
+
+ rc, out = rc_cmd(f'nft -f {nftables_wlb_conf}')
+
+ if rc != 0:
+ print('ERROR: Failed to apply WLB nftables config')
+ print('Output:', out)
+ return False
+
+ return True
+
+def cleanup(lb):
+ if 'interface_health' in lb:
+ index = 1
+ for ifname, health_conf in lb['interface_health'].items():
+ table_num = lb['mark_offset'] + index
+ run(f'ip route del table {table_num} default')
+ run(f'ip rule del fwmark {hex(table_num)} table {table_num}')
+ index += 1
+
+ run(f'nft delete table ip vyos_wanloadbalance')
+
+def get_config():
+ conf = Config()
+ base = ['load-balancing', 'wan']
+ lb = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, with_recursive_defaults=True)
+
+ lb['test_defaults'] = get_defaults(base + ['interface-health', 'A', 'test', 'B'], get_first_key=True)
+
+ return lb
+
+if __name__ == '__main__':
+ while commit_in_progress():
+ print("Notice: Waiting for commit to complete...")
+ time.sleep(1)
+
+ lb = get_config()
+
+ lb['health_state'] = {}
+ lb['mark_offset'] = 0xc8
+
+ # Create state dicts, interface address and nexthop, install routes and ip rules
+ if 'interface_health' in lb:
+ index = 1
+ for ifname, health_conf in lb['interface_health'].items():
+ table_num = lb['mark_offset'] + index
+ addr = get_ipv4_address(ifname)
+ lb['health_state'][ifname] = {
+ 'if_addr': addr,
+ 'failure_count': 0,
+ 'success_count': 0,
+ 'last_success': 0,
+ 'last_failure': 0,
+ 'state': addr is not None,
+ 'state_changed': False,
+ 'table_number': table_num,
+ 'mark': hex(table_num)
+ }
+
+ if health_conf['nexthop'] == 'dhcp':
+ lb['health_state'][ifname]['dhcp_nexthop'] = None
+
+ dynamic_nexthop_update(lb, ifname)
+ else:
+ run(f'ip route replace table {table_num} default dev {ifname} via {health_conf["nexthop"]}')
+
+ run(f'ip rule add fwmark {hex(table_num)} table {table_num}')
+
+ index += 1
+
+ nftables_update(lb)
+
+ run('ip route flush cache')
+
+ if 'flush_connections' in lb:
+ run('conntrack --delete')
+ run('conntrack -F expect')
+
+ with open(wlb_status_file, 'w') as f:
+ f.write(json.dumps(lb['health_state']))
+
+ # Signal handler SIGUSR2 -> dhcpcd update
+ def handle_sigusr2(signum, frame):
+ for ifname, health_conf in lb['interface_health'].items():
+ if 'nexthop' in health_conf and health_conf['nexthop'] == 'dhcp':
+ retval = dynamic_nexthop_update(lb, ifname)
+
+ if retval:
+ nftables_update(lb)
+
+ # Signal handler SIGTERM -> exit
+ def handle_sigterm(signum, frame):
+ if os.path.exists(wlb_status_file):
+ os.unlink(wlb_status_file)
+
+ if os.path.exists(wlb_pid_file):
+ os.unlink(wlb_pid_file)
+
+ if os.path.exists(nftables_wlb_conf):
+ os.unlink(nftables_wlb_conf)
+
+ cleanup(lb)
+ sys.exit(0)
+
+ signal.signal(signal.SIGUSR2, handle_sigusr2)
+ signal.signal(signal.SIGINT, handle_sigterm)
+ signal.signal(signal.SIGTERM, handle_sigterm)
+
+ with open(wlb_pid_file, 'w') as f:
+ f.write(str(os.getpid()))
+
+ # Main loop
+
+ try:
+ while True:
+ ip_change = False
+
+ if 'interface_health' in lb:
+ for ifname, health_conf in lb['interface_health'].items():
+ state = lb['health_state'][ifname]
+
+ result = health_check(ifname, health_conf, state=state, test_defaults=lb['test_defaults'])
+
+ state_changed = result != state['state']
+ state['state_changed'] = False
+
+ if result:
+ state['failure_count'] = 0
+ state['success_count'] += 1
+ state['last_success'] = time.time()
+ if state_changed and state['success_count'] >= int(health_conf['success_count']):
+ state['state'] = True
+ state['state_changed'] = True
+ elif not result:
+ state['failure_count'] += 1
+ state['success_count'] = 0
+ state['last_failure'] = time.time()
+ if state_changed and state['failure_count'] >= int(health_conf['failure_count']):
+ state['state'] = False
+ state['state_changed'] = True
+
+ if state['state_changed']:
+ state['if_addr'] = get_ipv4_address(ifname)
+ on_state_change(lb, ifname, state['state'])
+
+ if dynamic_nexthop_update(lb, ifname):
+ ip_change = True
+
+ if any(state['state_changed'] for ifname, state in lb['health_state'].items()):
+ if not nftables_update(lb):
+ break
+
+ run('ip route flush cache')
+
+ if 'flush_connections' in lb:
+ run('conntrack --delete')
+ run('conntrack -F expect')
+
+ with open(wlb_status_file, 'w') as f:
+ f.write(json.dumps(lb['health_state']))
+ elif ip_change:
+ nftables_update(lb)
+
+ time.sleep(sleep_interval)
+ except Exception as e:
+ print('WLB ERROR:', e)
+
+ if os.path.exists(wlb_status_file):
+ os.unlink(wlb_status_file)
+
+ if os.path.exists(wlb_pid_file):
+ os.unlink(wlb_pid_file)
+
+ if os.path.exists(nftables_wlb_conf):
+ os.unlink(nftables_wlb_conf)
+
+ cleanup(lb)
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 00136309b..ab3cc42cb 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -461,6 +461,7 @@ start ()
# As VyOS does not execute commands that are not present in the CLI we call
# the script by hand to have a single source for the login banner and MOTD
+ ${vyos_conf_scripts_dir}/system_syslog.py || log_failure_msg "could not reset syslog"
${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console"
${vyos_conf_scripts_dir}/system_login_banner.py || log_failure_msg "could not reset motd and issue files"
${vyos_conf_scripts_dir}/system_option.py || log_failure_msg "could not reset system option files"
diff --git a/src/migration-scripts/bgp/5-to-6 b/src/migration-scripts/bgp/5-to-6
new file mode 100644
index 000000000..e6fea6574
--- /dev/null
+++ b/src/migration-scripts/bgp/5-to-6
@@ -0,0 +1,39 @@
+# Copyright 2025 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/>.
+
+# T7163: migrate "address-family ipv4|6-unicast redistribute table" from a multi
+# leafNode to a tagNode. This is needed to support per table definition of a
+# route-map and/or metric
+
+from vyos.configtree import ConfigTree
+
+def migrate(config: ConfigTree) -> None:
+ bgp_base = ['protocols', 'bgp']
+ if not config.exists(bgp_base):
+ return
+
+ for address_family in ['ipv4-unicast', 'ipv6-unicast']:
+ # there is no non-main routing table beeing redistributed under this addres family
+ # bail out early and continue with next AFI
+ table_path = bgp_base + ['address-family', address_family, 'redistribute', 'table']
+ if not config.exists(table_path):
+ continue
+
+ tables = config.return_values(table_path)
+ config.delete(table_path)
+
+ for table in tables:
+ config.set(table_path + [table])
+ config.set_tag(table_path)
diff --git a/src/migration-scripts/lldp/2-to-3 b/src/migration-scripts/lldp/2-to-3
new file mode 100644
index 000000000..93090756c
--- /dev/null
+++ b/src/migration-scripts/lldp/2-to-3
@@ -0,0 +1,31 @@
+# Copyright 2025 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/>.
+
+# T7165: Migrate LLDP interface disable to 'mode disable'
+
+from vyos.configtree import ConfigTree
+
+base = ['service', 'lldp']
+
+def migrate(config: ConfigTree) -> None:
+ interface_base = base + ['interface']
+ if not config.exists(interface_base):
+ # Nothing to do
+ return
+
+ for interface in config.list_nodes(interface_base):
+ if config.exists(interface_base + [interface, 'disable']):
+ config.delete(interface_base + [interface, 'disable'])
+ config.set(interface_base + [interface, 'mode'], value='disable')
diff --git a/src/migration-scripts/policy/8-to-9 b/src/migration-scripts/policy/8-to-9
new file mode 100644
index 000000000..355e48e00
--- /dev/null
+++ b/src/migration-scripts/policy/8-to-9
@@ -0,0 +1,49 @@
+# Copyright (C) 2025 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/>.
+
+# T7116: Remove unsupported "internet" community following FRR removal
+# From
+ # set policy route-map <name> rule <ord> set community [add | replace] internet
+ # set policy community-list <name> rule <ord> regex internet
+# To
+ # set policy route-map <name> rule <ord> set community [add | replace] 0:0
+ # set policy community-list <name> rule <ord> regex _0:0_
+
+# NOTE: In FRR expanded community-lists, without the '_' delimiters, a regex of
+# "0:0" will match "65000:0" as well as "0:0". This doesn't line up with what
+# we want when replacing "internet".
+
+from vyos.configtree import ConfigTree
+
+rm_base = ['policy', 'route-map']
+cl_base = ['policy', 'community-list']
+
+def migrate(config: ConfigTree) -> None:
+ if config.exists(rm_base):
+ for policy_name in config.list_nodes(rm_base):
+ for rule_ord in config.list_nodes(rm_base + [policy_name, 'rule'], path_must_exist=False):
+ tmp_path = rm_base + [policy_name, 'rule', rule_ord, 'set', 'community']
+ if config.exists(tmp_path + ['add']) and config.return_value(tmp_path + ['add']) == 'internet':
+ config.set(tmp_path + ['add'], '0:0')
+ if config.exists(tmp_path + ['replace']) and config.return_value(tmp_path + ['replace']) == 'internet':
+ config.set(tmp_path + ['replace'], '0:0')
+
+ if config.exists(cl_base):
+ for policy_name in config.list_nodes(cl_base):
+ for rule_ord in config.list_nodes(cl_base + [policy_name, 'rule'], path_must_exist=False):
+ tmp_path = cl_base + [policy_name, 'rule', rule_ord, 'regex']
+ if config.exists(tmp_path) and config.return_value(tmp_path) == 'internet':
+ config.set(tmp_path, '_0:0_')
+
diff --git a/src/migration-scripts/system/28-to-29 b/src/migration-scripts/system/28-to-29
new file mode 100644
index 000000000..ccf7056c4
--- /dev/null
+++ b/src/migration-scripts/system/28-to-29
@@ -0,0 +1,71 @@
+# Copyright 2025 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/>.
+
+# T6989:
+# - remove syslog arbitrary file logging
+# - remove syslog user console logging
+# - move "global preserve-fqdn" one CLI level up
+# - rename "host" to "remote"
+
+from vyos.configtree import ConfigTree
+
+base = ['system', 'syslog']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ return
+ # Drop support for custom file logging
+ if config.exists(base + ['file']):
+ config.delete(base + ['file'])
+
+ # Drop support for logging to a user tty
+ # This should be dynamically added via an op-mode command like "terminal monitor"
+ if config.exists(base + ['user']):
+ config.delete(base + ['user'])
+
+ # Move "global preserve-fqdn" one CLI level up, as it relates to all
+ # logging targets (console, global and remote)
+ preserve_fqdn_base = base + ['global', 'preserve-fqdn']
+ if config.exists(preserve_fqdn_base):
+ config.delete(preserve_fqdn_base)
+ config.set(base + ['preserve-fqdn'])
+
+ # Move "global marker" one CLI level up, as it relates to all
+ # logging targets (console, global and remote)
+ marker_base = base + ['global', 'marker']
+ if config.exists(marker_base):
+ config.copy(marker_base, base + ['marker'])
+ config.delete(marker_base)
+
+ # Rename "global" -> "local" as this describes what is logged locally
+ # on the router to a file on the filesystem
+ if config.exists(base + ['global']):
+ config.rename(base + ['global'], 'local')
+
+ vrf = ''
+ if config.exists(base + ['vrf']):
+ vrf = config.return_value(base + ['vrf'])
+ config.delete(base + ['vrf'])
+
+ # Rename host x.x.x.x -> remote x.x.x.x
+ if config.exists(base + ['host']):
+ config.set(base + ['remote'])
+ config.set_tag(base + ['remote'])
+ for remote in config.list_nodes(base + ['host']):
+ config.copy(base + ['host', remote], base + ['remote', remote])
+ config.set_tag(base + ['remote'])
+ if vrf:
+ config.set(base + ['remote', remote, 'vrf'], value=vrf)
+ config.delete(base + ['host'])
diff --git a/src/migration-scripts/wanloadbalance/3-to-4 b/src/migration-scripts/wanloadbalance/3-to-4
new file mode 100644
index 000000000..e49f46a5b
--- /dev/null
+++ b/src/migration-scripts/wanloadbalance/3-to-4
@@ -0,0 +1,33 @@
+# Copyright 2025 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
+
+base = ['load-balancing', 'wan']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ # Nothing to do
+ return
+
+ if config.exists(base + ['rule']):
+ for rule in config.list_nodes(base + ['rule']):
+ rule_base = base + ['rule', rule]
+
+ if config.exists(rule_base + ['inbound-interface']):
+ ifname = config.return_value(rule_base + ['inbound-interface'])
+
+ if ifname.endswith('+'):
+ config.set(rule_base + ['inbound-interface'], value=ifname.replace('+', '*'))
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index 8eed2c6cd..725bfc75b 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -205,7 +205,7 @@ def _get_raw_server_pool_statistics(config, family='inet', pool=None):
return stats
-def _get_formatted_server_pool_statistics(pool_data, family='inet'):
+def _get_formatted_server_pool_statistics(pool_data):
data_entries = []
for entry in pool_data:
pool = entry.get('pool')
@@ -235,7 +235,7 @@ def _get_raw_server_static_mappings(config, family='inet', pool=None, sorted=Non
return mappings
-def _get_formatted_server_static_mappings(raw_data, family='inet'):
+def _get_formatted_server_static_mappings(raw_data):
data_entries = []
for entry in raw_data:
@@ -245,10 +245,8 @@ def _get_formatted_server_static_mappings(raw_data, family='inet'):
ip_addr = entry.get('ip', 'N/A')
mac_addr = entry.get('mac', 'N/A')
duid = entry.get('duid', 'N/A')
- description = entry.get('description', 'N/A')
- data_entries.append(
- [pool, subnet, hostname, ip_addr, mac_addr, duid, description]
- )
+ desc = entry.get('description', 'N/A')
+ data_entries.append([pool, subnet, hostname, ip_addr, mac_addr, duid, desc])
headers = [
'Pool',
@@ -327,7 +325,7 @@ def show_server_pool_statistics(
if raw:
return pool_data
else:
- return _get_formatted_server_pool_statistics(pool_data, family=family)
+ return _get_formatted_server_pool_statistics(pool_data)
@_verify_server
@@ -408,7 +406,7 @@ def show_server_static_mappings(
if raw:
return static_mappings
else:
- return _get_formatted_server_static_mappings(static_mappings, family=family)
+ return _get_formatted_server_static_mappings(static_mappings)
def _lease_valid(inet, address):
@@ -482,7 +480,7 @@ def _get_raw_client_leases(family='inet', interface=None):
return lease_data
-def _get_formatted_client_leases(lease_data, family):
+def _get_formatted_client_leases(lease_data):
from time import localtime
from time import strftime
@@ -534,7 +532,7 @@ def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[
if raw:
return lease_data
else:
- return _get_formatted_client_leases(lease_data, family=family)
+ return _get_formatted_client_leases(lease_data)
@_verify_client
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index 1da112673..609b0b347 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023-2025 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This file is part of VyOS.
#
@@ -46,7 +46,12 @@ MSG_ERR_NOT_LIVE: str = 'The system is already installed. Please use "add system
MSG_ERR_LIVE: str = 'The system is in live-boot mode. Please use "install image" instead.'
MSG_ERR_NO_DISK: str = 'No suitable disk was found. There must be at least one disk of 2GB or greater size.'
MSG_ERR_IMPROPER_IMAGE: str = 'Missing sha256sum.txt.\nEither this image is corrupted, or of era 1.2.x (md5sum) and would downgrade image tools;\ndisallowed in either case.'
-MSG_ERR_ARCHITECTURE_MISMATCH: str = 'Upgrading to a different image architecture will break your system.'
+MSG_ERR_INCOMPATIBLE_IMAGE: str = 'Image compatibility check failed, aborting installation.'
+MSG_ERR_ARCHITECTURE_MISMATCH: str = 'The current architecture is "{0}", the new image is for "{1}". Upgrading to a different image architecture will break your system.'
+MSG_ERR_FLAVOR_MISMATCH: str = 'The current image flavor is "{0}", the new image is "{1}". Upgrading to a non-matching flavor can have unpredictable consequences.'
+MSG_ERR_MISSING_ARCHITECTURE: str = 'The new image version data does not specify architecture, cannot check compatibility (is it a legacy release image?)'
+MSG_ERR_MISSING_FLAVOR: str = 'The new image version data does not specify flavor, cannot check compatibility (is it a legacy release image?)'
+MSG_ERR_CORRUPT_CURRENT_IMAGE: str = 'Version data in the current image is malformed: missing flavor and/or architecture fields. Upgrade compatibility cannot be checked.'
MSG_INFO_INSTALL_WELCOME: str = 'Welcome to VyOS installation!\nThis command will install VyOS to your permanent storage.'
MSG_INFO_INSTALL_EXIT: str = 'Exiting from VyOS installation'
MSG_INFO_INSTALL_SUCCESS: str = 'The image installed successfully; please reboot now.'
@@ -79,7 +84,6 @@ MSG_WARN_ROOT_SIZE_TOOSMALL: str = 'The size is too small. Try again'
MSG_WARN_IMAGE_NAME_WRONG: str = 'The suggested name is unsupported!\n'\
'It must be between 1 and 64 characters long and contains only the next characters: .+-_ a-z A-Z 0-9'
MSG_WARN_PASSWORD_CONFIRM: str = 'The entered values did not match. Try again'
-MSG_WARN_FLAVOR_MISMATCH: str = 'The running image flavor is "{0}". The new image flavor is "{1}".\n' \
'Installing a different image flavor may cause functionality degradation or break your system.\n' \
'Do you want to continue with installation?'
CONST_MIN_DISK_SIZE: int = 2147483648 # 2 GB
@@ -95,7 +99,7 @@ DIR_ISO_MOUNT: str = f'{DIR_INSTALLATION}/iso_src'
DIR_DST_ROOT: str = f'{DIR_INSTALLATION}/disk_dst'
DIR_KERNEL_SRC: str = '/boot/'
FILE_ROOTFS_SRC: str = '/usr/lib/live/mount/medium/live/filesystem.squashfs'
-ISO_DOWNLOAD_PATH: str = '/tmp/vyos_installation.iso'
+ISO_DOWNLOAD_PATH: str = ''
external_download_script = '/usr/libexec/vyos/simple-download.py'
external_latest_image_url_script = '/usr/libexec/vyos/latest-image-url.py'
@@ -548,6 +552,11 @@ def image_fetch(image_path: str, vrf: str = None,
Returns:
Path: a path to a local file
"""
+ import os.path
+ from uuid import uuid4
+
+ global ISO_DOWNLOAD_PATH
+
# Latest version gets url from configured "system update-check url"
if image_path == 'latest':
command = external_latest_image_url_script
@@ -564,6 +573,7 @@ def image_fetch(image_path: str, vrf: str = None,
# check a type of path
if urlparse(image_path).scheme:
# download an image
+ ISO_DOWNLOAD_PATH = os.path.join(os.path.expanduser("~"), '{0}.iso'.format(uuid4()))
download_file(ISO_DOWNLOAD_PATH, image_path, vrf,
username, password,
progressbar=True, check_space=True)
@@ -701,30 +711,48 @@ def is_raid_install(install_object: Union[disk.DiskDetails, raid.RaidDetails]) -
return False
-def validate_compatibility(iso_path: str) -> None:
+def validate_compatibility(iso_path: str, force: bool = False) -> None:
"""Check architecture and flavor compatibility with the running image
Args:
iso_path (str): a path to the mounted ISO image
"""
- old_data = get_version_data()
- old_flavor = old_data.get('flavor', '')
- old_architecture = old_data.get('architecture') or cmd('dpkg --print-architecture')
+ current_data = get_version_data()
+ current_flavor = current_data.get('flavor')
+ current_architecture = current_data.get('architecture') or cmd('dpkg --print-architecture')
new_data = get_version_data(f'{iso_path}/version.json')
- new_flavor = new_data.get('flavor', '')
- new_architecture = new_data.get('architecture', '')
+ new_flavor = new_data.get('flavor')
+ new_architecture = new_data.get('architecture')
- if not old_architecture == new_architecture:
- print(MSG_ERR_ARCHITECTURE_MISMATCH)
+ if not current_flavor or not current_architecture:
+ # This may only happen if someone modified the version file.
+ # Unlikely but not impossible.
+ print(MSG_ERR_CORRUPT_CURRENT_IMAGE)
cleanup()
exit(MSG_INFO_INSTALL_EXIT)
- if not old_flavor == new_flavor:
- if not ask_yes_no(MSG_WARN_FLAVOR_MISMATCH.format(old_flavor, new_flavor), default=False):
- cleanup()
- exit(MSG_INFO_INSTALL_EXIT)
+ success = True
+ if current_architecture != new_architecture:
+ success = False
+ if not new_architecture:
+ print(MSG_ERR_MISSING_ARCHITECTURE)
+ else:
+ print(MSG_ERR_ARCHITECTURE_MISMATCH.format(current_architecture, new_architecture))
+
+ if current_flavor != new_flavor:
+ if not force:
+ success = False
+ if not new_flavor:
+ print(MSG_ERR_MISSING_FLAVOR)
+ else:
+ print(MSG_ERR_FLAVOR_MISMATCH.format(current_flavor, new_flavor))
+
+ if not success:
+ print(MSG_ERR_INCOMPATIBLE_IMAGE)
+ cleanup()
+ exit(MSG_INFO_INSTALL_EXIT)
def install_image() -> None:
"""Install an image to a disk
@@ -893,7 +921,7 @@ def install_image() -> None:
@compat.grub_cfg_update
def add_image(image_path: str, vrf: str = None, username: str = '',
- password: str = '', no_prompt: bool = False) -> None:
+ password: str = '', no_prompt: bool = False, force: bool = False) -> None:
"""Add a new image
Args:
@@ -910,7 +938,7 @@ def add_image(image_path: str, vrf: str = None, username: str = '',
disk.partition_mount(iso_path, DIR_ISO_MOUNT, 'iso9660')
print('Validating image compatibility')
- validate_compatibility(DIR_ISO_MOUNT)
+ validate_compatibility(DIR_ISO_MOUNT, force=force)
# check sums
print('Validating image checksums')
@@ -1031,6 +1059,9 @@ def parse_arguments() -> Namespace:
parser.add_argument('--image-path',
help='a path (HTTP or local file) to an image that needs to be installed'
)
+ parser.add_argument('--force', action='store_true',
+ help='Ignore flavor compatibility requirements.'
+ )
# parser.add_argument('--image_new_name', help='a new name for image')
args: Namespace = parser.parse_args()
# Validate arguments
@@ -1047,7 +1078,8 @@ if __name__ == '__main__':
install_image()
if args.action == 'add':
add_image(args.image_path, args.vrf,
- args.username, args.password, args.no_prompt)
+ args.username, args.password,
+ args.no_prompt, args.force)
exit()
diff --git a/src/op_mode/load-balancing_wan.py b/src/op_mode/load-balancing_wan.py
new file mode 100755
index 000000000..9fa473802
--- /dev/null
+++ b/src/op_mode/load-balancing_wan.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import re
+import sys
+
+from datetime import datetime
+
+from vyos.config import Config
+from vyos.utils.process import cmd
+
+import vyos.opmode
+
+wlb_status_file = '/run/wlb_status.json'
+
+status_format = '''Interface: {ifname}
+Status: {status}
+Last Status Change: {last_change}
+Last Interface Success: {last_success}
+Last Interface Failure: {last_failure}
+Interface Failures: {failures}
+'''
+
+def _verify(func):
+ """Decorator checks if WLB config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = Config()
+ if not config.exists(['load-balancing', 'wan']):
+ unconf_message = 'WAN load-balancing is not configured'
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+ return _wrapper
+
+def _get_raw_data():
+ with open(wlb_status_file, 'r') as f:
+ data = json.loads(f.read())
+ if not data:
+ return {}
+ return data
+
+def _get_formatted_output(raw_data):
+ for ifname, if_data in raw_data.items():
+ latest_change = if_data['last_success'] if if_data['last_success'] > if_data['last_failure'] else if_data['last_failure']
+
+ change_dt = datetime.fromtimestamp(latest_change) if latest_change > 0 else None
+ success_dt = datetime.fromtimestamp(if_data['last_success']) if if_data['last_success'] > 0 else None
+ failure_dt = datetime.fromtimestamp(if_data['last_failure']) if if_data['last_failure'] > 0 else None
+ now = datetime.utcnow()
+
+ fmt_data = {
+ 'ifname': ifname,
+ 'status': "active" if if_data['state'] else "failed",
+ 'last_change': change_dt.strftime("%Y-%m-%d %H:%M:%S") if change_dt else 'N/A',
+ 'last_success': str(now - success_dt) if success_dt else 'N/A',
+ 'last_failure': str(now - failure_dt) if failure_dt else 'N/A',
+ 'failures': if_data['failure_count']
+ }
+ print(status_format.format(**fmt_data))
+
+@_verify
+def show_summary(raw: bool):
+ data = _get_raw_data()
+
+ if raw:
+ return data
+ else:
+ return _get_formatted_output(data)
+
+@_verify
+def show_connection(raw: bool):
+ res = cmd('sudo conntrack -L -n')
+ lines = res.split("\n")
+ filtered_lines = [line for line in lines if re.search(r' mark=[1-9]', line)]
+
+ if raw:
+ return filtered_lines
+
+ for line in lines:
+ print(line)
+
+@_verify
+def show_status(raw: bool):
+ res = cmd('sudo nft list chain ip vyos_wanloadbalance wlb_mangle_prerouting')
+ lines = res.split("\n")
+ filtered_lines = [line.replace("\t", "") for line in lines[3:-2] if 'meta mark set' not in line]
+
+ if raw:
+ return filtered_lines
+
+ for line in filtered_lines:
+ print(line)
+
+if __name__ == "__main__":
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/restart.py b/src/op_mode/restart.py
index 3b0031f34..efa835485 100755
--- a/src/op_mode/restart.py
+++ b/src/op_mode/restart.py
@@ -53,6 +53,10 @@ service_map = {
'systemd_service': 'strongswan',
'path': ['vpn', 'ipsec'],
},
+ 'load-balancing_wan': {
+ 'systemd_service': 'vyos-wan-load-balance',
+ 'path': ['load-balancing', 'wan'],
+ },
'mdns_repeater': {
'systemd_service': 'avahi-daemon',
'path': ['service', 'mdns', 'repeater'],
@@ -86,6 +90,7 @@ services = typing.Literal[
'haproxy',
'igmp_proxy',
'ipsec',
+ 'load-balancing_wan',
'mdns_repeater',
'router_advert',
'snmp',
diff --git a/src/services/api/rest/models.py b/src/services/api/rest/models.py
index 27d9fb5ee..dda50010f 100644
--- a/src/services/api/rest/models.py
+++ b/src/services/api/rest/models.py
@@ -293,6 +293,13 @@ class TracerouteModel(ApiModel):
}
+class InfoQueryParams(BaseModel):
+ model_config = {"extra": "forbid"}
+
+ version: bool = True
+ hostname: bool = True
+
+
class Success(BaseModel):
success: bool
data: Union[str, bool, Dict]
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index b161fe6ba..28acccd2c 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -28,6 +28,7 @@ import traceback
import importlib.util
import io
from contextlib import redirect_stdout
+from enum import Enum
import zmq
@@ -60,11 +61,14 @@ SOCKET_PATH = 'ipc:///run/vyos-configd.sock'
MAX_MSG_SIZE = 65535
PAD_MSG_SIZE = 6
+
# Response error codes
-R_SUCCESS = 1
-R_ERROR_COMMIT = 2
-R_ERROR_DAEMON = 4
-R_PASS = 8
+class Response(Enum):
+ SUCCESS = 1
+ ERROR_COMMIT = 2
+ ERROR_DAEMON = 4
+ PASS = 8
+
vyos_conf_scripts_dir = directories['conf_mode']
configd_include_file = os.path.join(directories['data'], 'configd-include.json')
@@ -73,12 +77,15 @@ configd_env_unset_file = os.path.join(directories['data'], 'vyos-configd-env-uns
# sourced on entering config session
configd_env_file = '/etc/default/vyos-configd-env'
+
def key_name_from_file_name(f):
return os.path.splitext(f)[0]
+
def module_name_from_key(k):
return k.replace('-', '_')
+
def path_from_file_name(f):
return os.path.join(vyos_conf_scripts_dir, f)
@@ -126,7 +133,7 @@ def write_stdout_log(file_name, msg):
f.write(msg)
-def run_script(script_name, config, args) -> tuple[int, str]:
+def run_script(script_name, config, args) -> tuple[Response, str]:
# pylint: disable=broad-exception-caught
script = conf_mode_scripts[script_name]
@@ -139,13 +146,13 @@ def run_script(script_name, config, args) -> tuple[int, str]:
script.apply(c)
except ConfigError as e:
logger.error(e)
- return R_ERROR_COMMIT, str(e)
+ return Response.ERROR_COMMIT, str(e)
except Exception:
tb = traceback.format_exc()
logger.error(tb)
- return R_ERROR_COMMIT, tb
+ return Response.ERROR_COMMIT, tb
- return R_SUCCESS, ''
+ return Response.SUCCESS, ''
def initialization(socket):
@@ -195,8 +202,9 @@ def initialization(socket):
os.environ['VYATTA_CHANGES_ONLY_DIR'] = changes_only_dir_string
try:
- configsource = ConfigSourceString(running_config_text=active_string,
- session_config_text=session_string)
+ configsource = ConfigSourceString(
+ running_config_text=active_string, session_config_text=session_string
+ )
except ConfigSourceError as e:
logger.debug(e)
return None
@@ -214,11 +222,11 @@ def initialization(socket):
return config
-def process_node_data(config, data, _last: bool = False) -> tuple[int, str]:
+def process_node_data(config, data, _last: bool = False) -> tuple[Response, str]:
if not config:
out = 'Empty config'
logger.critical(out)
- return R_ERROR_DAEMON, out
+ return Response.ERROR_DAEMON, out
script_name = None
os.environ['VYOS_TAGNODE_VALUE'] = ''
@@ -234,7 +242,7 @@ def process_node_data(config, data, _last: bool = False) -> tuple[int, str]:
if not script_name:
out = 'Missing script_name'
logger.critical(out)
- return R_ERROR_DAEMON, out
+ return Response.ERROR_DAEMON, out
if res.group(3):
args = res.group(3).split()
args.insert(0, f'{script_name}.py')
@@ -246,7 +254,7 @@ def process_node_data(config, data, _last: bool = False) -> tuple[int, str]:
scripts_called.append(script_record)
if script_name not in include_set:
- return R_PASS, ''
+ return Response.PASS, ''
with redirect_stdout(io.StringIO()) as o:
result, err_out = run_script(script_name, config, args)
@@ -259,13 +267,15 @@ def process_node_data(config, data, _last: bool = False) -> tuple[int, str]:
def send_result(sock, err, msg):
+ err_no = err.value
+ err_name = err.name
msg = msg if msg else ''
msg_size = min(MAX_MSG_SIZE, len(msg))
- err_rep = err.to_bytes(1)
+ err_rep = err_no.to_bytes(1)
msg_size_rep = f'{msg_size:#0{PAD_MSG_SIZE}x}'
- logger.debug(f'Sending reply: error_code {err} with output')
+ logger.debug(f'Sending reply: {err_name} with output')
sock.send_multipart([err_rep, msg_size_rep.encode(), msg.encode()])
write_stdout_log(script_stdout_log, msg)
@@ -331,7 +341,7 @@ if __name__ == '__main__':
scripts_called = getattr(config, 'scripts_called', [])
logger.debug(f'scripts_called: {scripts_called}')
- if res == R_SUCCESS:
+ if res == Response.SUCCESS:
tmp = get_frrender_dict(config)
if frr.generate(tmp):
# only apply a new FRR configuration if anything changed
diff --git a/src/services/vyos-domain-resolver b/src/services/vyos-domain-resolver
index fe0f40a07..48c6b86d8 100755
--- a/src/services/vyos-domain-resolver
+++ b/src/services/vyos-domain-resolver
@@ -65,13 +65,15 @@ def get_config(conf, node):
node_config = dict_merge(default_values, node_config)
- global timeout, cache
+ if node == base_firewall and 'global_options' in node_config:
+ global_config = node_config['global_options']
+ global timeout, cache
- if 'resolver_interval' in node_config:
- timeout = int(node_config['resolver_interval'])
+ if 'resolver_interval' in global_config:
+ timeout = int(global_config['resolver_interval'])
- if 'resolver_cache' in node_config:
- cache = True
+ if 'resolver_cache' in global_config:
+ cache = True
fqdn_config_parse(node_config, node[0])
@@ -177,39 +179,40 @@ def update_fqdn(config, node):
def update_interfaces(config, node):
if node == 'interfaces':
wg_interfaces = dict_search_args(config, 'wireguard')
+ if wg_interfaces:
+
+ peer_public_keys = {}
+ # for each wireguard interfaces
+ for interface, wireguard in wg_interfaces.items():
+ peer_public_keys[interface] = []
+ for peer, peer_config in wireguard['peer'].items():
+ # check peer if peer host-name or address is set
+ if 'host_name' in peer_config or 'address' in peer_config:
+ # check latest handshake
+ peer_public_keys[interface].append(
+ peer_config['public_key']
+ )
+
+ now_time = time.time()
+ for (interface, check_peer_public_keys) in peer_public_keys.items():
+ if len(check_peer_public_keys) == 0:
+ continue
- peer_public_keys = {}
- # for each wireguard interfaces
- for interface, wireguard in wg_interfaces.items():
- peer_public_keys[interface] = []
- for peer, peer_config in wireguard['peer'].items():
- # check peer if peer host-name or address is set
- if 'host_name' in peer_config or 'address' in peer_config:
- # check latest handshake
- peer_public_keys[interface].append(
- peer_config['public_key']
- )
-
- now_time = time.time()
- for (interface, check_peer_public_keys) in peer_public_keys.items():
- if len(check_peer_public_keys) == 0:
- continue
-
- intf = WireGuardIf(interface, create=False, debug=False)
- handshakes = intf.operational.get_latest_handshakes()
-
- # WireGuard performs a handshake every WIREGUARD_REKEY_AFTER_TIME
- # if data is being transmitted between the peers. If no data is
- # transmitted, the handshake will not be initiated unless new
- # data begins to flow. Each handshake generates a new session
- # key, and the key is rotated at least every 120 seconds or
- # upon data transmission after a prolonged silence.
- for public_key, handshake_time in handshakes.items():
- if public_key in check_peer_public_keys and (
- handshake_time == 0
- or (now_time - handshake_time > 3*WIREGUARD_REKEY_AFTER_TIME)
- ):
- intf.operational.reset_peer(public_key=public_key)
+ intf = WireGuardIf(interface, create=False, debug=False)
+ handshakes = intf.operational.get_latest_handshakes()
+
+ # WireGuard performs a handshake every WIREGUARD_REKEY_AFTER_TIME
+ # if data is being transmitted between the peers. If no data is
+ # transmitted, the handshake will not be initiated unless new
+ # data begins to flow. Each handshake generates a new session
+ # key, and the key is rotated at least every 120 seconds or
+ # upon data transmission after a prolonged silence.
+ for public_key, handshake_time in handshakes.items():
+ if public_key in check_peer_public_keys and (
+ handshake_time == 0
+ or (now_time - handshake_time > 3*WIREGUARD_REKEY_AFTER_TIME)
+ ):
+ intf.operational.reset_peer(public_key=public_key)
if __name__ == '__main__':
logger.info('VyOS domain resolver')
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 558561182..be3dd5051 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -20,18 +20,22 @@ import grp
import json
import logging
import signal
+import traceback
from time import sleep
+from typing import Annotated
-from fastapi import FastAPI
+from fastapi import FastAPI, Query
from fastapi.exceptions import RequestValidationError
from uvicorn import Config as UvicornConfig
from uvicorn import Server as UvicornServer
from vyos.configsession import ConfigSession
from vyos.defaults import api_config_state
+from vyos.utils.file import read_file
+from vyos.version import get_version
from api.session import SessionState
-from api.rest.models import error
+from api.rest.models import error, InfoQueryParams, success
CFG_GROUP = 'vyattacfg'
@@ -57,11 +61,49 @@ app = FastAPI(debug=True,
title="VyOS API",
version="0.1.0")
+
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(_request, exc):
return error(400, str(exc.errors()[0]))
+@app.get('/info')
+def info(q: Annotated[InfoQueryParams, Query()]):
+ show_version = q.version
+ show_hostname = q.hostname
+
+ prelogin_file = r'/etc/issue'
+ hostname_file = r'/etc/hostname'
+ default = 'Welcome to VyOS'
+
+ try:
+ res = {
+ 'banner': '',
+ 'hostname': '',
+ 'version': ''
+ }
+ if show_version:
+ res.update(version=get_version())
+
+ if show_hostname:
+ try:
+ hostname = read_file(hostname_file)
+ except Exception:
+ hostname = 'vyos'
+ res.update(hostname=hostname)
+
+ banner = read_file(prelogin_file, defaultonfailure=default)
+ if banner == f'{default} - \\n \\l':
+ banner = banner.partition(default)[1]
+
+ res.update(banner=banner)
+ except Exception:
+ LOG.critical(traceback.format_exc())
+ return error(500, 'An internal error occured. Check the logs for details.')
+
+ return success(res)
+
+
###
# Modify uvicorn to allow reloading server within the configsession
###
diff --git a/src/system/sync-dhcp-lease-to-hosts.py b/src/system/sync-dhcp-lease-to-hosts.py
new file mode 100755
index 000000000..5c8b18faf
--- /dev/null
+++ b/src/system/sync-dhcp-lease-to-hosts.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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 argparse
+import logging
+
+import vyos.opmode
+import vyos.hostsd_client
+
+from vyos.configquery import ConfigTreeQuery
+
+from vyos.kea import kea_get_active_config
+from vyos.kea import kea_get_dhcp_pools
+from vyos.kea import kea_get_server_leases
+
+# Configure logging
+logger = logging.getLogger(__name__)
+# set stream as output
+logs_handler = logging.StreamHandler()
+logger.addHandler(logs_handler)
+
+
+def _get_all_server_leases(inet_suffix='4') -> list:
+ mappings = []
+ try:
+ active_config = kea_get_active_config(inet_suffix)
+ except Exception:
+ raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration')
+
+ try:
+ pools = kea_get_dhcp_pools(active_config, inet_suffix)
+ mappings = kea_get_server_leases(
+ active_config, inet_suffix, pools, state=[], origin=None
+ )
+ except Exception:
+ raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server leases')
+
+ return mappings
+
+
+if __name__ == '__main__':
+ # Parse command arguments
+ parser = argparse.ArgumentParser()
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument('--inet', action='store_true', help='Use IPv4 DHCP leases')
+ group.add_argument('--inet6', action='store_true', help='Use IPv6 DHCP leases')
+ args = parser.parse_args()
+
+ inet_suffix = '4' if args.inet else '6'
+ service_suffix = '' if args.inet else 'v6'
+
+ if inet_suffix == '6':
+ raise vyos.opmode.UnsupportedOperation(
+ 'Syncing IPv6 DHCP leases are not supported yet'
+ )
+
+ # Load configuration
+ config = ConfigTreeQuery()
+
+ # Check if DHCP server is configured
+ # Using warning instead of error since this check may fail during first-time
+ # DHCP server setup when the service is not yet configured in the config tree.
+ # This happens when called from systemd's ExecStartPost the first time.
+ if not config.exists(f'service dhcp{service_suffix}-server'):
+ logger.warning(f'DHCP{service_suffix} server is not configured')
+
+ # Check if hostfile-update is enabled
+ if not config.exists(f'service dhcp{service_suffix}-server hostfile-update'):
+ logger.debug(
+ f'Hostfile update is disabled for DHCP{service_suffix} server, skipping hosts update'
+ )
+ exit(0)
+
+ lease_data = _get_all_server_leases(inet_suffix)
+
+ try:
+ hc = vyos.hostsd_client.Client()
+
+ for mapping in lease_data:
+ ip_addr = mapping.get('ip')
+ mac_addr = mapping.get('mac')
+ name = mapping.get('hostname')
+ name = name if name else f'host-{mac_addr.replace(":", "-")}'
+ domain = mapping.get('domain')
+ fqdn = f'{name}.{domain}' if domain else name
+ hc.add_hosts(
+ {
+ f'dhcp-server-{ip_addr}': {
+ fqdn: {'address': [ip_addr], 'aliases': []}
+ }
+ }
+ )
+
+ hc.apply()
+
+ logger.debug('Hosts store updated successfully')
+
+ except vyos.hostsd_client.VyOSHostsdError as e:
+ raise vyos.opmode.InternalError(str(e))
diff --git a/src/systemd/vyos-wan-load-balance.service b/src/systemd/vyos-wan-load-balance.service
index 7d62a2ff6..a59f2c3ae 100644
--- a/src/systemd/vyos-wan-load-balance.service
+++ b/src/systemd/vyos-wan-load-balance.service
@@ -1,15 +1,11 @@
[Unit]
-Description=VyOS WAN load-balancing service
+Description=VyOS WAN Load Balancer
After=vyos-router.service
[Service]
-ExecStart=/opt/vyatta/sbin/wan_lb -f /run/load-balance/wlb.conf -d -i /var/run/vyatta/wlb.pid
-ExecReload=/bin/kill -s SIGTERM $MAINPID && sleep 5 && /opt/vyatta/sbin/wan_lb -f /run/load-balance/wlb.conf -d -i /var/run/vyatta/wlb.pid
-ExecStop=/bin/kill -s SIGTERM $MAINPID
-PIDFile=/var/run/vyatta/wlb.pid
-KillMode=process
-Restart=on-failure
-RestartSec=5s
+Type=simple
+Restart=always
+ExecStart=/usr/bin/python3 /usr/libexec/vyos/vyos-load-balancer.py
[Install]
WantedBy=multi-user.target
diff --git a/src/validators/ethernet-interface b/src/validators/ethernet-interface
new file mode 100644
index 000000000..2bf92812e
--- /dev/null
+++ b/src/validators/ethernet-interface
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+if ! [[ "$1" =~ ^(lan|eth|eno|ens|enp|enx)[0-9]+$ ]]; then
+ echo "Error: $1 is not an ethernet interface"
+ exit 1
+fi
+
+if ! [ -d "/sys/class/net/$1" ]; then
+ echo "Error: $1 interface does not exist in the system"
+ exit 1
+fi
+
+exit 0