summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile11
-rw-r--r--data/templates/accel-ppp/config_shaper_radius.j23
-rw-r--r--data/templates/accel-ppp/ipoe.config.j28
-rw-r--r--data/templates/accel-ppp/sstp.config.j21
-rw-r--r--data/templates/container/registries.conf.j22
-rw-r--r--data/templates/container/storage.conf.j25
-rw-r--r--data/templates/dns-forwarding/recursor.conf.j25
-rw-r--r--data/templates/firewall/nftables-defines.j266
-rw-r--r--data/templates/firewall/nftables-geoip-update.j233
-rw-r--r--data/templates/firewall/nftables-policy.j27
-rw-r--r--data/templates/firewall/nftables.j234
-rw-r--r--data/templates/frr/daemons.frr.tmpl54
-rw-r--r--data/templates/frr/eigrpd.frr.j221
-rw-r--r--data/templates/frr/policy.frr.j234
-rw-r--r--data/templates/frr/ripd.frr.j29
-rw-r--r--data/templates/frr/staticd.frr.j24
-rw-r--r--data/templates/https/nginx.default.j22
-rw-r--r--data/templates/ipsec/swanctl/remote_access.j23
-rw-r--r--data/templates/monitoring/override.conf.j22
-rw-r--r--data/templates/monitoring/telegraf.j225
-rw-r--r--data/templates/ntp/ntpd.conf.j213
-rw-r--r--data/templates/pmacct/uacctd.conf.j232
-rw-r--r--data/templates/router-advert/radvd.conf.j23
-rw-r--r--data/templates/sla/owamp-override.conf.j216
-rw-r--r--data/templates/sla/owamp-server.conf.j220
-rw-r--r--data/templates/sla/twamp-override.conf.j216
-rw-r--r--data/templates/sla/twamp-server.conf.j218
-rw-r--r--data/templates/ssh/sshguard_config.j227
-rw-r--r--data/templates/ssh/sshguard_whitelist.j27
-rw-r--r--data/templates/syslog/rsyslog.conf.j24
-rw-r--r--data/templates/zone_policy/nftables.j212
-rw-r--r--debian/control7
-rw-r--r--debian/vyos-1x-smoketest.install1
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/container.xml.in2
-rw-r--r--interface-definitions/dhcp-server.xml.in50
-rw-r--r--interface-definitions/dhcpv6-server.xml.in4
-rw-r--r--interface-definitions/dns-domain-name.xml.in2
-rw-r--r--interface-definitions/dns-dynamic.xml.in22
-rw-r--r--interface-definitions/dns-forwarding.xml.in46
-rw-r--r--interface-definitions/firewall.xml.in132
-rw-r--r--interface-definitions/flow-accounting-conf.xml.in4
-rw-r--r--interface-definitions/igmp-proxy.xml.in2
-rw-r--r--interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i14
-rw-r--r--interface-definitions/include/auth-local-users.xml.i68
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i10
-rw-r--r--interface-definitions/include/bgp/remote-as.xml.i2
-rw-r--r--interface-definitions/include/eigrp/protocol-common-config.xml.i121
-rw-r--r--interface-definitions/include/firewall/action.xml.i2
-rw-r--r--interface-definitions/include/firewall/common-rule.xml.i27
-rw-r--r--interface-definitions/include/firewall/geoip.xml.i28
-rw-r--r--interface-definitions/include/firewall/name-default-log.xml.i2
-rw-r--r--interface-definitions/include/firewall/rule-log-level.xml.i45
-rw-r--r--interface-definitions/include/firewall/source-destination-group.xml.i8
-rw-r--r--interface-definitions/include/generic-interface-multi.xml.i2
-rw-r--r--interface-definitions/include/generic-interface.xml.i2
-rw-r--r--interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i2
-rw-r--r--interface-definitions/include/interface/address-ipv4-ipv6.xml.i2
-rw-r--r--interface-definitions/include/interface/dhcpv6-options.xml.i4
-rw-r--r--interface-definitions/include/interface/enable-directed-broadcast.xml.i8
-rw-r--r--interface-definitions/include/interface/ipv4-options.xml.i1
-rw-r--r--interface-definitions/include/ipsec/local-address.xml.i1
-rw-r--r--interface-definitions/include/monitoring/url.xml.i15
-rw-r--r--interface-definitions/include/ospf/protocol-common-config.xml.i6
-rw-r--r--interface-definitions/include/pki/ca-certificate-multi.xml.i15
-rw-r--r--interface-definitions/include/policy/action.xml.i2
-rw-r--r--interface-definitions/include/policy/route-rule-action.xml.i2
-rw-r--r--interface-definitions/include/rip/access-list.xml.i (renamed from interface-definitions/include/rip/rip-access-list.xml.i)2
-rw-r--r--interface-definitions/include/rip/access-list6.xml.i (renamed from interface-definitions/include/rip/rip-access-list6.xml.i)2
-rw-r--r--interface-definitions/include/rip/default-information.xml.i (renamed from interface-definitions/include/rip/rip-default-information.xml.i)2
-rw-r--r--interface-definitions/include/rip/default-metric.xml.i (renamed from interface-definitions/include/rip/rip-default-metric.xml.i)2
-rw-r--r--interface-definitions/include/rip/interface.xml.i (renamed from interface-definitions/include/rip/rip-interface.xml.i)2
-rw-r--r--interface-definitions/include/rip/prefix-list.xml.i (renamed from interface-definitions/include/rip/rip-prefix-list.xml.i)2
-rw-r--r--interface-definitions/include/rip/prefix-list6.xml.i (renamed from interface-definitions/include/rip/rip-prefix-list6.xml.i)2
-rw-r--r--interface-definitions/include/rip/redistribute.xml.i (renamed from interface-definitions/include/rip/rip-redistribute.xml.i)2
-rw-r--r--interface-definitions/include/rip/timers.xml.i (renamed from interface-definitions/include/rip/rip-timers.xml.i)2
-rw-r--r--interface-definitions/include/rip/version.xml.i18
-rw-r--r--interface-definitions/include/version/monitoring-version.xml.i3
-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/interfaces-bonding.xml.in17
-rw-r--r--interface-definitions/interfaces-bridge.xml.in10
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in13
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in2
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-vti.xml.in16
-rw-r--r--interface-definitions/interfaces-wireless.xml.in4
-rw-r--r--interface-definitions/ntp.xml.in1
-rw-r--r--interface-definitions/pki.xml.in1
-rw-r--r--interface-definitions/policy-local-route.xml.in4
-rw-r--r--interface-definitions/policy.xml.in170
-rw-r--r--interface-definitions/protocols-eigrp.xml.in17
-rw-r--r--interface-definitions/protocols-mpls.xml.in2
-rw-r--r--interface-definitions/protocols-nhrp.xml.in12
-rw-r--r--interface-definitions/protocols-rip.xml.in46
-rw-r--r--interface-definitions/protocols-ripng.xml.in26
-rw-r--r--interface-definitions/protocols-static.xml.in2
-rw-r--r--interface-definitions/service-conntrack-sync.xml.in (renamed from interface-definitions/service_conntrack-sync.xml.in)3
-rw-r--r--interface-definitions/service-console-server.xml.in (renamed from interface-definitions/service_console-server.xml.in)0
-rw-r--r--interface-definitions/service-event-handler.xml.in70
-rw-r--r--interface-definitions/service-ipoe-server.xml.in (renamed from interface-definitions/service_ipoe-server.xml.in)5
-rw-r--r--interface-definitions/service-mdns-repeater.xml.in (renamed from interface-definitions/service_mdns-repeater.xml.in)0
-rw-r--r--interface-definitions/service-monitoring-telegraf.xml.in (renamed from interface-definitions/service_monitoring_telegraf.xml.in)153
-rw-r--r--interface-definitions/service-pppoe-server.xml.in (renamed from interface-definitions/service_pppoe-server.xml.in)0
-rw-r--r--interface-definitions/service-router-advert.xml.in (renamed from interface-definitions/service_router-advert.xml.in)19
-rw-r--r--interface-definitions/service-sla.xml.in36
-rw-r--r--interface-definitions/service-upnp.xml.in (renamed from interface-definitions/service_upnp.xml.in)0
-rw-r--r--interface-definitions/service-webproxy.xml.in (renamed from interface-definitions/service_webproxy.xml.in)6
-rw-r--r--interface-definitions/ssh.xml.in72
-rw-r--r--interface-definitions/system-acceleration-qat.xml.in (renamed from interface-definitions/intel_qat.xml.in)0
-rw-r--r--interface-definitions/system-frr.xml.in77
-rw-r--r--interface-definitions/system-ip.xml.in6
-rw-r--r--interface-definitions/system-lcd.xml.in2
-rw-r--r--interface-definitions/system-syslog.xml.in25
-rw-r--r--interface-definitions/tftp-server.xml.in2
-rw-r--r--interface-definitions/vpn-ipsec.xml.in (renamed from interface-definitions/vpn_ipsec.xml.in)46
-rw-r--r--interface-definitions/vpn-l2tp.xml.in (renamed from interface-definitions/vpn_l2tp.xml.in)0
-rw-r--r--interface-definitions/vpn-openconnect.xml.in (renamed from interface-definitions/vpn_openconnect.xml.in)76
-rw-r--r--interface-definitions/vpn-pptp.xml.in (renamed from interface-definitions/vpn_pptp.xml.in)0
-rw-r--r--interface-definitions/vpn-sstp.xml.in (renamed from interface-definitions/vpn_sstp.xml.in)4
-rw-r--r--interface-definitions/vrf.xml.in27
-rw-r--r--interface-definitions/zone-policy.xml.in1
-rw-r--r--op-mode-definitions/clear-dhcp-server-lease.xml.in20
-rw-r--r--op-mode-definitions/container.xml.in (renamed from op-mode-definitions/containers.xml.in)12
-rw-r--r--op-mode-definitions/dns-forwarding.xml.in43
-rw-r--r--op-mode-definitions/force-wamp.xml.in25
-rw-r--r--op-mode-definitions/geoip.xml.in13
-rw-r--r--op-mode-definitions/include/bgp/reset-bgp-afi-common.xml.i20
-rw-r--r--op-mode-definitions/include/bgp/reset-bgp-neighbor-options.xml.i48
-rw-r--r--op-mode-definitions/include/bgp/reset-bgp-peer-group-vrf.xml.i14
-rw-r--r--op-mode-definitions/include/bgp/reset-bgp-peer-group.xml.i14
-rw-r--r--op-mode-definitions/ipv6-route.xml.in30
-rw-r--r--op-mode-definitions/monitor-log.xml.in26
-rw-r--r--op-mode-definitions/openconnect.xml.in47
-rw-r--r--op-mode-definitions/pki.xml.in135
-rw-r--r--op-mode-definitions/reset-bgp.xml.in258
-rw-r--r--op-mode-definitions/reset-ip-bgp.xml.in154
-rw-r--r--op-mode-definitions/reset-ipv6-bgp.xml.in62
-rw-r--r--op-mode-definitions/show-arp.xml.in4
-rw-r--r--op-mode-definitions/show-conntrack.xml.in27
-rw-r--r--op-mode-definitions/show-ip.xml.in24
-rw-r--r--op-mode-definitions/show-ipv6.xml.in2
-rw-r--r--op-mode-definitions/show-log.xml.in12
-rw-r--r--op-mode-definitions/show-system.xml.in4
-rw-r--r--op-mode-definitions/webproxy.xml.in4
-rw-r--r--python/vyos/configdict.py43
-rw-r--r--python/vyos/configsession.py4
-rw-r--r--python/vyos/cpu.py102
-rw-r--r--python/vyos/firewall.py234
-rw-r--r--python/vyos/frr.py2
-rw-r--r--python/vyos/ifconfig/bond.py75
-rw-r--r--python/vyos/ifconfig/bridge.py21
-rw-r--r--[-rwxr-xr-x]python/vyos/ifconfig/interface.py19
-rw-r--r--python/vyos/ifconfig/vti.py6
-rw-r--r--python/vyos/migrator.py5
-rw-r--r--python/vyos/pki.py63
-rw-r--r--python/vyos/template.py27
-rw-r--r--python/vyos/util.py21
-rwxr-xr-xscripts/build-command-templates10
-rwxr-xr-xsmoketest/bin/vyos-configtest-pki100
-rw-r--r--smoketest/configs.no-load/bgp-small-as (renamed from smoketest/configs/bgp-small-as)0
-rw-r--r--smoketest/configs.no-load/pki-ipsec (renamed from smoketest/configs/pki-ipsec)0
-rw-r--r--smoketest/configs.no-load/vrf-bgp (renamed from smoketest/configs/vrf-bgp)0
-rw-r--r--smoketest/configs/basic-vyos4
-rw-r--r--smoketest/configs/bgp-dmvpn-spoke2
-rw-r--r--smoketest/configs/bgp-small-internet-exchange8
-rw-r--r--smoketest/configs/dialup-router-complex21
-rw-r--r--smoketest/configs/dialup-router-medium-vpn5
-rw-r--r--smoketest/configs/isis-small1
-rw-r--r--smoketest/configs/vrf-basic1
-rw-r--r--smoketest/configs/vrf-ospf1
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py7
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py189
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bonding.py67
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py105
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vti.py34
-rwxr-xr-xsmoketest/scripts/cli/test_load_balancning_wan.py257
-rwxr-xr-xsmoketest/scripts/cli/test_pki.py57
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py129
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py106
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py15
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rip.py66
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_forwarding.py48
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_telegraf.py8
-rwxr-xr-xsmoketest/scripts/cli/test_service_router-advert.py14
-rwxr-xr-xsmoketest/scripts/cli/test_service_ssh.py49
-rwxr-xr-xsmoketest/scripts/cli/test_system_flow-accounting.py94
-rwxr-xr-xsmoketest/scripts/cli/test_system_frr.py146
-rwxr-xr-xsmoketest/scripts/cli/test_system_ip.py13
-rwxr-xr-xsmoketest/scripts/cli/test_system_ntp.py17
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_sstp.py16
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py24
-rwxr-xr-xsrc/completion/list_bgp_neighbors.sh13
-rwxr-xr-xsrc/completion/list_openconnect_users.py36
-rwxr-xr-xsrc/conf_mode/conntrack_sync.py5
-rwxr-xr-xsrc/conf_mode/container.py38
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py9
-rwxr-xr-xsrc/conf_mode/firewall-interface.py11
-rwxr-xr-xsrc/conf_mode/firewall.py215
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py16
-rwxr-xr-xsrc/conf_mode/high-availability.py8
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py16
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py6
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py43
-rwxr-xr-xsrc/conf_mode/ntp.py20
-rwxr-xr-xsrc/conf_mode/pki.py129
-rwxr-xr-xsrc/conf_mode/policy-route-interface.py12
-rwxr-xr-xsrc/conf_mode/policy-route.py72
-rwxr-xr-xsrc/conf_mode/policy.py10
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py14
-rwxr-xr-xsrc/conf_mode/protocols_eigrp.py123
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py4
-rwxr-xr-xsrc/conf_mode/protocols_rip.py2
-rwxr-xr-xsrc/conf_mode/service_event_handler.py91
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py14
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py53
-rwxr-xr-xsrc/conf_mode/service_router-advert.py34
-rwxr-xr-xsrc/conf_mode/service_sla.py113
-rwxr-xr-xsrc/conf_mode/snmp.py10
-rwxr-xr-xsrc/conf_mode/ssh.py19
-rwxr-xr-xsrc/conf_mode/system-ip.py5
-rwxr-xr-xsrc/conf_mode/system-syslog.py2
-rwxr-xr-xsrc/conf_mode/system_frr.py91
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py8
-rwxr-xr-xsrc/conf_mode/vrf.py20
-rwxr-xr-xsrc/conf_mode/zone_policy.py2
-rw-r--r--src/etc/cron.d/vyos-geoip1
-rwxr-xr-xsrc/etc/cron.hourly/vyos-logrotate-hourly4
-rw-r--r--src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper2
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf6
-rw-r--r--src/etc/systemd/system/logrotate.timer.d/10-override.conf2
-rwxr-xr-xsrc/helpers/geoip-update.py44
-rwxr-xr-xsrc/helpers/vyos-domain-group-resolve.py60
-rwxr-xr-xsrc/migration-scripts/firewall/6-to-75
-rwxr-xr-xsrc/migration-scripts/interfaces/24-to-2537
-rwxr-xr-xsrc/migration-scripts/ipsec/5-to-62
-rwxr-xr-xsrc/migration-scripts/monitoring/0-to-171
-rwxr-xr-xsrc/migration-scripts/policy/2-to-358
-rwxr-xr-xsrc/migration-scripts/system/23-to-244
-rwxr-xr-xsrc/migration-scripts/system/24-to-2552
-rwxr-xr-xsrc/migration-scripts/vrf/0-to-110
-rwxr-xr-xsrc/op_mode/clear_dhcp_lease.py78
-rwxr-xr-xsrc/op_mode/connect_disconnect.py4
-rwxr-xr-xsrc/op_mode/conntrack_sync.py4
-rwxr-xr-xsrc/op_mode/containers_op.py80
-rwxr-xr-xsrc/op_mode/firewall.py2
-rwxr-xr-xsrc/op_mode/flow_accounting_op.py7
-rwxr-xr-xsrc/op_mode/generate_ssh_server_key.py5
-rwxr-xr-xsrc/op_mode/openconnect-control.py9
-rwxr-xr-xsrc/op_mode/pki.py201
-rwxr-xr-xsrc/op_mode/reset_openvpn.py4
-rwxr-xr-xsrc/op_mode/restart_dhcp_relay.py7
-rwxr-xr-xsrc/op_mode/show_conntrack.py84
-rwxr-xr-xsrc/op_mode/show_nat_translations.py16
-rwxr-xr-xsrc/op_mode/show_neigh.py162
-rwxr-xr-xsrc/op_mode/show_openconnect_otp.py109
-rwxr-xr-xsrc/op_mode/show_uptime.py17
-rwxr-xr-xsrc/op_mode/vtysh_wrapper.sh5
-rwxr-xr-xsrc/services/vyos-http-api-server37
-rwxr-xr-xsrc/system/vyos-event-handler.py162
-rw-r--r--src/systemd/dhclient@.service3
-rw-r--r--src/systemd/vyos-domain-group-resolve.service11
-rw-r--r--src/systemd/vyos-event-handler.service11
263 files changed, 6875 insertions, 1176 deletions
diff --git a/Makefile b/Makefile
index 2333eebed..cc9bc136e 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,7 @@ SHIM_DIR := src/shim
XDP_DIR := src/xdp
LIBS := -lzmq
CFLAGS :=
+BUILD_ARCH := $(shell dpkg-architecture -q DEB_BUILD_ARCH)
J2LINT := $(shell command -v j2lint 2> /dev/null)
@@ -37,10 +38,20 @@ interface_definitions: $(config_xml_obj)
rm -rf $(TMPL_DIR)/qos
rm -rf $(TMPL_DIR)/interfaces/input
+ # T2472 - EIGRP support
+ rm -rf $(TMPL_DIR)/protocols/eigrp
+ # T2773 - EIGRP support for VRF
+ rm -rf $(TMPL_DIR)/vrf/name/node.tag/protocols/eigrp
+
# XXX: test if there are empty node.def files - this is not allowed as these
# could mask help strings or mandatory priority statements
find $(TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1'
+ifeq ($(BUILD_ARCH),arm64)
+ # There is currently no telegraf support in VyOS for ARM64, remove CLI definitions
+ rm -rf $(TMPL_DIR)/service/monitoring/telegraf
+endif
+
.PHONY: op_mode_definitions
.ONESHELL:
op_mode_definitions: $(op_xml_obj)
diff --git a/data/templates/accel-ppp/config_shaper_radius.j2 b/data/templates/accel-ppp/config_shaper_radius.j2
index c256647e4..942cdf132 100644
--- a/data/templates/accel-ppp/config_shaper_radius.j2
+++ b/data/templates/accel-ppp/config_shaper_radius.j2
@@ -6,5 +6,8 @@ attr={{ authentication.radius.rate_limit.attribute }}
{% if authentication.radius.rate_limit.vendor is vyos_defined %}
vendor={{ authentication.radius.rate_limit.vendor }}
{% endif %}
+{% if authentication.radius.rate_limit.multiplier is vyos_defined %}
+rate-multiplier={{ authentication.radius.rate_limit.multiplier }}
+{% endif %}
{% endif %}
{% endif %}
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index 3c0d47b27..6df12db2c 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -128,10 +128,16 @@ bind={{ radius_source_address }}
{% if radius_dynamic_author %}
dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }}
{% endif %}
-{% if radius_shaper_attr %}
+
+{% if radius_shaper_enable %}
[shaper]
verbose=1
+{% if radius_shaper_attr %}
attr={{ radius_shaper_attr }}
+{% endif %}
+{% if radius_shaper_multiplier %}
+rate-multiplier={{ radius_shaper_multiplier }}
+{% endif %}
{% if radius_shaper_vendor %}
vendor={{ radius_shaper_vendor }}
{% endif %}
diff --git a/data/templates/accel-ppp/sstp.config.j2 b/data/templates/accel-ppp/sstp.config.j2
index 5c6f19306..7ee28dd21 100644
--- a/data/templates/accel-ppp/sstp.config.j2
+++ b/data/templates/accel-ppp/sstp.config.j2
@@ -28,6 +28,7 @@ disable
[sstp]
verbose=1
ifname=sstp%d
+port={{ port }}
accept=ssl
ssl-ca-file=/run/accel-pppd/sstp-ca.pem
ssl-pemfile=/run/accel-pppd/sstp-cert.pem
diff --git a/data/templates/container/registries.conf.j2 b/data/templates/container/registries.conf.j2
index 6a3be58d0..2e86466a1 100644
--- a/data/templates/container/registries.conf.j2
+++ b/data/templates/container/registries.conf.j2
@@ -1,4 +1,4 @@
-### Autogenerated by /usr/libexec/vyos/conf_mode/container.py ###
+### Autogenerated by container.py ###
# For more information on this configuration file, see containers-registries.conf(5).
#
diff --git a/data/templates/container/storage.conf.j2 b/data/templates/container/storage.conf.j2
index 97e1a9d0c..665f9bf95 100644
--- a/data/templates/container/storage.conf.j2
+++ b/data/templates/container/storage.conf.j2
@@ -1,5 +1,4 @@
-### Autogenerated by /usr/libexec/vyos/conf_mode/container.py ###
-
+### Autogenerated by container.py ###
[storage]
driver = "vfs"
- graphroot = "/config/containers/storage"
+ graphroot = "/usr/lib/live/mount/persistence/container/storage"
diff --git a/data/templates/dns-forwarding/recursor.conf.j2 b/data/templates/dns-forwarding/recursor.conf.j2
index c1950e1bc..ce1b676d1 100644
--- a/data/templates/dns-forwarding/recursor.conf.j2
+++ b/data/templates/dns-forwarding/recursor.conf.j2
@@ -32,6 +32,11 @@ local-address={{ listen_address | join(',') }}
# dnssec
dnssec={{ dnssec }}
+{% if dns64_prefix is vyos_defined %}
+# dns64-prefix
+dns64-prefix={{ dns64_prefix }}
+{% endif %}
+
# serve rfc1918 records
serve-rfc1918={{ 'no' if no_serve_rfc1918 is vyos_defined else 'yes' }}
diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2
index 4fa92f2e3..97fc123d5 100644
--- a/data/templates/firewall/nftables-defines.j2
+++ b/data/templates/firewall/nftables-defines.j2
@@ -1,32 +1,76 @@
+{% macro groups(group, is_ipv6) %}
{% if group is vyos_defined %}
-{% if group.address_group is vyos_defined %}
+{% set ip_type = 'ipv6_addr' if is_ipv6 else 'ipv4_addr' %}
+{% if group.address_group is vyos_defined and not is_ipv6 %}
{% for group_name, group_conf in group.address_group.items() %}
-define A_{{ group_name }} = { {{ group_conf.address | join(",") }} }
+{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
+ set A_{{ group_name }} {
+ type {{ ip_type }}
+ flags interval
+{% if group_conf.address is vyos_defined or includes %}
+ elements = { {{ group_conf.address | nft_nested_group(includes, group.address_group, 'address') | join(",") }} }
+{% endif %}
+ }
{% endfor %}
{% endif %}
-{% if group.ipv6_address_group is vyos_defined %}
+{% if group.ipv6_address_group is vyos_defined and is_ipv6 %}
{% for group_name, group_conf in group.ipv6_address_group.items() %}
-define A6_{{ group_name }} = { {{ group_conf.address | join(",") }} }
+{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
+ set A6_{{ group_name }} {
+ type {{ ip_type }}
+ flags interval
+{% if group_conf.address is vyos_defined or includes %}
+ elements = { {{ group_conf.address | nft_nested_group(includes, group.ipv6_address_group, 'address') | join(",") }} }
+{% endif %}
+ }
{% endfor %}
{% endif %}
{% if group.mac_group is vyos_defined %}
{% for group_name, group_conf in group.mac_group.items() %}
-define M_{{ group_name }} = { {{ group_conf.mac_address | join(",") }} }
+{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
+ set M_{{ group_name }} {
+ type ether_addr
+{% if group_conf.mac_address is vyos_defined or includes %}
+ elements = { {{ group_conf.mac_address | nft_nested_group(includes, group.mac_group, 'mac_address') | join(",") }} }
+{% endif %}
+ }
{% endfor %}
{% endif %}
-{% if group.network_group is vyos_defined %}
+{% if group.network_group is vyos_defined and not is_ipv6 %}
{% for group_name, group_conf in group.network_group.items() %}
-define N_{{ group_name }} = { {{ group_conf.network | join(",") }} }
+{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
+ set N_{{ group_name }} {
+ type {{ ip_type }}
+ flags interval
+{% if group_conf.network is vyos_defined or includes %}
+ elements = { {{ group_conf.network | nft_nested_group(includes, group.network_group, 'network') | join(",") }} }
+{% endif %}
+ }
{% endfor %}
{% endif %}
-{% if group.ipv6_network_group is vyos_defined %}
+{% if group.ipv6_network_group is vyos_defined and is_ipv6 %}
{% for group_name, group_conf in group.ipv6_network_group.items() %}
-define N6_{{ group_name }} = { {{ group_conf.network | join(",") }} }
+{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
+ set N6_{{ group_name }} {
+ type {{ ip_type }}
+ flags interval
+{% if group_conf.network is vyos_defined or includes %}
+ elements = { {{ group_conf.network | nft_nested_group(includes, group.ipv6_network_group, 'network') | join(",") }} }
+{% endif %}
+ }
{% endfor %}
{% endif %}
{% if group.port_group is vyos_defined %}
{% for group_name, group_conf in group.port_group.items() %}
-define P_{{ group_name }} = { {{ group_conf.port | join(",") }} }
+{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
+ set P_{{ group_name }} {
+ type inet_service
+ flags interval
+{% if group_conf.port is vyos_defined or includes %}
+ elements = { {{ group_conf.port | nft_nested_group(includes, group.port_group, 'port') | join(",") }} }
+{% endif %}
+ }
{% endfor %}
{% endif %}
-{% endif %} \ No newline at end of file
+{% endif %}
+{% endmacro %}
diff --git a/data/templates/firewall/nftables-geoip-update.j2 b/data/templates/firewall/nftables-geoip-update.j2
new file mode 100644
index 000000000..f9e61a274
--- /dev/null
+++ b/data/templates/firewall/nftables-geoip-update.j2
@@ -0,0 +1,33 @@
+#!/usr/sbin/nft -f
+
+{% if ipv4_sets is vyos_defined %}
+{% for setname, ip_list in ipv4_sets.items() %}
+flush set ip filter {{ setname }}
+{% endfor %}
+
+table ip filter {
+{% for setname, ip_list in ipv4_sets.items() %}
+ set {{ setname }} {
+ type ipv4_addr
+ flags interval
+ elements = { {{ ','.join(ip_list) }} }
+ }
+{% endfor %}
+}
+{% endif %}
+
+{% if ipv6_sets is vyos_defined %}
+{% for setname, ip_list in ipv6_sets.items() %}
+flush set ip6 filter {{ setname }}
+{% endfor %}
+
+table ip6 filter {
+{% for setname, ip_list in ipv6_sets.items() %}
+ set {{ setname }} {
+ type ipv6_addr
+ flags interval
+ elements = { {{ ','.join(ip_list) }} }
+ }
+{% endfor %}
+}
+{% endif %}
diff --git a/data/templates/firewall/nftables-policy.j2 b/data/templates/firewall/nftables-policy.j2
index 0154c9f7e..281525407 100644
--- a/data/templates/firewall/nftables-policy.j2
+++ b/data/templates/firewall/nftables-policy.j2
@@ -1,13 +1,13 @@
#!/usr/sbin/nft -f
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
+
{% if cleanup_commands is vyos_defined %}
{% for command in cleanup_commands %}
{{ command }}
{% endfor %}
{% endif %}
-include "/run/nftables_defines.conf"
-
table ip mangle {
{% if first_install is vyos_defined %}
chain VYOS_PBR_PREROUTING {
@@ -29,6 +29,8 @@ table ip mangle {
}
{% endfor %}
{% endif %}
+
+{{ group_tmpl.groups(firewall_group, False) }}
}
table ip6 mangle {
@@ -52,4 +54,5 @@ table ip6 mangle {
}
{% endfor %}
{% endif %}
+{{ group_tmpl.groups(firewall_group, True) }}
}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index fac3fad03..b91fed615 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -1,13 +1,13 @@
#!/usr/sbin/nft -f
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
+
{% if cleanup_commands is vyos_defined %}
{% for command in cleanup_commands %}
{{ command }}
{% endfor %}
{% endif %}
-include "/run/nftables_defines.conf"
-
table ip filter {
{% if first_install is vyos_defined %}
chain VYOS_FW_FORWARD {
@@ -45,6 +45,14 @@ table ip filter {
{{ conf | nft_default_rule(name_text) }}
}
{% endfor %}
+{% if group is vyos_defined and group.domain_group is vyos_defined %}
+{% for name, name_config in group.domain_group.items() %}
+ set D_{{ name }} {
+ type ipv4_addr
+ flags interval
+ }
+{% endfor %}
+{% endif %}
{% for set_name in ns.sets %}
set RECENT_{{ set_name }} {
type ipv4_addr
@@ -52,7 +60,18 @@ table ip filter {
flags dynamic
}
{% endfor %}
+{% if geoip_updated.name is vyos_defined %}
+{% for setname in geoip_updated.name %}
+ set {{ setname }} {
+ type ipv4_addr
+ flags interval
+ }
+{% endfor %}
+{% endif %}
{% endif %}
+
+{{ group_tmpl.groups(group, False) }}
+
{% if state_policy is vyos_defined %}
chain VYOS_STATE_POLICY {
{% if state_policy.established is vyos_defined %}
@@ -113,7 +132,18 @@ table ip6 filter {
flags dynamic
}
{% endfor %}
+{% if geoip_updated.ipv6_name is vyos_defined %}
+{% for setname in geoip_updated.ipv6_name %}
+ set {{ setname }} {
+ type ipv6_addr
+ flags interval
+ }
+{% endfor %}
+{% endif %}
{% endif %}
+
+{{ group_tmpl.groups(group, True) }}
+
{% if state_policy is vyos_defined %}
chain VYOS_STATE_POLICY6 {
{% if state_policy.established is vyos_defined %}
diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
new file mode 100644
index 000000000..df98e74d6
--- /dev/null
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -0,0 +1,54 @@
+zebra=yes
+bgpd=yes
+ospfd=yes
+ospf6d=yes
+ripd=yes
+ripngd=yes
+isisd=yes
+pimd=no
+ldpd=yes
+nhrpd=no
+eigrpd=yes
+babeld=no
+sharpd=no
+pbrd=no
+bfdd=yes
+staticd=yes
+
+vtysh_enable=yes
+zebra_options=" -s 90000000 --daemon -A 127.0.0.1
+{%- if irdp is defined %} -M irdp{% endif -%}
+{%- if snmp is defined and snmp.zebra is defined %} -M snmp{% endif -%}
+"
+bgpd_options=" --daemon -A 127.0.0.1
+{%- if bmp is defined %} -M bmp{% endif -%}
+{%- if snmp is defined and snmp.bgpd is defined %} -M snmp{% endif -%}
+"
+ospfd_options=" --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ospfd is defined %} -M snmp{% endif -%}
+"
+ospf6d_options=" --daemon -A ::1
+{%- if snmp is defined and snmp.ospf6d is defined %} -M snmp{% endif -%}
+"
+ripd_options=" --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ripd is defined %} -M snmp{% endif -%}
+"
+ripngd_options=" --daemon -A ::1"
+isisd_options=" --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.isisd is defined %} -M snmp{% endif -%}
+"
+pimd_options=" --daemon -A 127.0.0.1"
+ldpd_options=" --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ldpd is defined %} -M snmp{% endif -%}
+"
+nhrpd_options=" --daemon -A 127.0.0.1"
+eigrpd_options=" --daemon -A 127.0.0.1"
+babeld_options=" --daemon -A 127.0.0.1"
+sharpd_options=" --daemon -A 127.0.0.1"
+pbrd_options=" --daemon -A 127.0.0.1"
+staticd_options=" --daemon -A 127.0.0.1"
+bfdd_options=" --daemon -A 127.0.0.1"
+
+watchfrr_enable=no
+valgrind_enable=no
+
diff --git a/data/templates/frr/eigrpd.frr.j2 b/data/templates/frr/eigrpd.frr.j2
new file mode 100644
index 000000000..67f8a3ad1
--- /dev/null
+++ b/data/templates/frr/eigrpd.frr.j2
@@ -0,0 +1,21 @@
+!
+router eigrp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
+{% if maximum_paths is vyos_defined %}
+maximum-paths {{ maximum_paths }}
+{% endif %}
+{% if metric.weights is vyos_defined %}
+metric weights {{ metric.weights }}
+{% endif %}
+{% if network is vyos_defined %}
+{% for net in network %}
+network {{ net }}
+{% endfor %}
+{% endif %}
+{% if redistribute is vyos_defined %}
+{% for protocol in redistribute %}
+redistribute {{ protocol }}
+{% endfor %}
+{% endif %}
+{% if variance is vyos_defined %}
+variance {{ variance }}
+{% endif %} \ No newline at end of file
diff --git a/data/templates/frr/policy.frr.j2 b/data/templates/frr/policy.frr.j2
index f0a64cb89..33df17770 100644
--- a/data/templates/frr/policy.frr.j2
+++ b/data/templates/frr/policy.frr.j2
@@ -185,12 +185,24 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
{% if rule_config.match.ip.address.prefix_list is vyos_defined %}
match ip address prefix-list {{ rule_config.match.ip.address.prefix_list }}
{% endif %}
+{% if rule_config.match.ip.address.prefix_len is vyos_defined %}
+ match ip address prefix-len {{ rule_config.match.ip.address.prefix_len }}
+{% endif %}
{% if rule_config.match.ip.nexthop.access_list is vyos_defined %}
match ip next-hop {{ rule_config.match.ip.nexthop.access_list }}
{% endif %}
+{% if rule_config.match.ip.nexthop.address is vyos_defined %}
+ match ip next-hop address {{ rule_config.match.ip.nexthop.address }}
+{% endif %}
+{% if rule_config.match.ip.nexthop.prefix_len is vyos_defined %}
+ match ip next-hop prefix-len {{ rule_config.match.ip.nexthop.prefix_len }}
+{% endif %}
{% if rule_config.match.ip.nexthop.prefix_list is vyos_defined %}
match ip next-hop prefix-list {{ rule_config.match.ip.nexthop.prefix_list }}
{% endif %}
+{% if rule_config.match.ip.nexthop.type is vyos_defined %}
+ match ip next-hop type {{ rule_config.match.ip.nexthop.type }}
+{% endif %}
{% if rule_config.match.ip.route_source.access_list is vyos_defined %}
match ip route-source {{ rule_config.match.ip.route_source.access_list }}
{% endif %}
@@ -203,8 +215,20 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
{% if rule_config.match.ipv6.address.prefix_list is vyos_defined %}
match ipv6 address prefix-list {{ rule_config.match.ipv6.address.prefix_list }}
{% endif %}
-{% if rule_config.match.ipv6.nexthop is vyos_defined %}
- match ipv6 next-hop address {{ rule_config.match.ipv6.nexthop }}
+{% if rule_config.match.ipv6.address.prefix_len is vyos_defined %}
+ match ipv6 address prefix-len {{ rule_config.match.ipv6.address.prefix_len }}
+{% endif %}
+{% if rule_config.match.ipv6.nexthop.address is vyos_defined %}
+ match ipv6 next-hop address {{ rule_config.match.ipv6.nexthop.address }}
+{% endif %}
+{% if rule_config.match.ipv6.nexthop.access_list is vyos_defined %}
+ match ipv6 next-hop {{ rule_config.match.ipv6.nexthop.access_list }}
+{% endif %}
+{% if rule_config.match.ipv6.nexthop.prefix_list is vyos_defined %}
+ match ipv6 next-hop prefix-list {{ rule_config.match.ipv6.nexthop.prefix_list }}
+{% endif %}
+{% if rule_config.match.ipv6.nexthop.type is vyos_defined %}
+ match ipv6 next-hop type {{ rule_config.match.ipv6.nexthop.type }}
{% endif %}
{% if rule_config.match.large_community.large_community_list is vyos_defined %}
match large-community {{ rule_config.match.large_community.large_community_list }}
@@ -259,6 +283,12 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
{% if rule_config.set.distance is vyos_defined %}
set distance {{ rule_config.set.distance }}
{% endif %}
+{% if rule_config.set.evpn.gateway.ipv4 is vyos_defined %}
+ set evpn gateway-ip ipv4 {{ rule_config.set.evpn.gateway.ipv4 }}
+{% endif %}
+{% if rule_config.set.evpn.gateway.ipv6 is vyos_defined %}
+ set evpn gateway-ip ipv6 {{ rule_config.set.evpn.gateway.ipv6 }}
+{% endif %}
{% if rule_config.set.extcommunity.bandwidth is vyos_defined %}
set extcommunity bandwidth {{ rule_config.set.extcommunity.bandwidth }}
{% endif %}
diff --git a/data/templates/frr/ripd.frr.j2 b/data/templates/frr/ripd.frr.j2
index df35150ca..e9e484cc2 100644
--- a/data/templates/frr/ripd.frr.j2
+++ b/data/templates/frr/ripd.frr.j2
@@ -32,6 +32,12 @@ interface {{ iface }}
{% if iface_config.split_horizon.poison_reverse is vyos_defined %}
ip rip split-horizon poisoned-reverse
{% endif %}
+{% if iface_config.receive.version is vyos_defined %}
+ ip rip receive version {{ iface_config.receive.version }}
+{% endif %}
+{% if iface_config.send.version is vyos_defined %}
+ ip rip send version {{ iface_config.send.version }}
+{% endif %}
exit
!
{% endfor %}
@@ -84,6 +90,9 @@ router rip
{% endif %}
{% endif %}
{% include 'frr/rip_ripng.frr.j2' %}
+{% if version is vyos_defined %}
+ version {{ version }}
+{% endif %}
exit
!
{% if route_map is vyos_defined %}
diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2
index 589f03c2c..55c05ceb7 100644
--- a/data/templates/frr/staticd.frr.j2
+++ b/data/templates/frr/staticd.frr.j2
@@ -17,7 +17,7 @@ vrf {{ vrf }}
{% endif %}
{# IPv4 default routes from DHCP interfaces #}
{% if dhcp is vyos_defined %}
-{% for interface, interface_config in dhcp.items() %}
+{% for interface, interface_config in dhcp.items() if interface_config.dhcp_options.no_default_route is not vyos_defined %}
{% set next_hop = interface | get_dhcp_router %}
{% if next_hop is vyos_defined %}
{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.dhcp_options.default_route_distance if interface_config.dhcp_options.default_route_distance is vyos_defined }}
@@ -26,7 +26,7 @@ vrf {{ vrf }}
{% endif %}
{# IPv4 default routes from PPPoE interfaces #}
{% if pppoe is vyos_defined %}
-{% for interface, interface_config in pppoe.items() %}
+{% for interface, interface_config in pppoe.items() if interface_config.no_default_route is not vyos_defined %}
{{ ip_prefix }} route 0.0.0.0/0 {{ interface }} tag 210 {{ interface_config.default_route_distance if interface_config.default_route_distance is vyos_defined }}
{% endfor %}
{% endif %}
diff --git a/data/templates/https/nginx.default.j2 b/data/templates/https/nginx.default.j2
index 70e62ae7a..dbb08e187 100644
--- a/data/templates/https/nginx.default.j2
+++ b/data/templates/https/nginx.default.j2
@@ -34,7 +34,7 @@ server {
ssl_protocols TLSv1.2 TLSv1.3;
# proxy settings for HTTP API, if enabled; 503, if not
- location ~ /(retrieve|configure|config-file|image|generate|show|docs|openapi.json|redoc|graphql) {
+ location ~ /(retrieve|configure|config-file|image|generate|show|reset|docs|openapi.json|redoc|graphql) {
{% if server.api %}
{% if server.api.socket %}
proxy_pass http://unix:/run/api.sock;
diff --git a/data/templates/ipsec/swanctl/remote_access.j2 b/data/templates/ipsec/swanctl/remote_access.j2
index 676ad88b3..d2760ec1f 100644
--- a/data/templates/ipsec/swanctl/remote_access.j2
+++ b/data/templates/ipsec/swanctl/remote_access.j2
@@ -18,7 +18,8 @@
{% endif %}
local {
{% if rw_conf.authentication.id is vyos_defined and rw_conf.authentication.use_x509_id is not vyos_defined %}
- id = '{{ rw_conf.authentication.id }}'
+{# please use " quotes - else Apple iOS goes crazy #}
+ id = "{{ rw_conf.authentication.id }}"
{% endif %}
{% if rw_conf.authentication.server_mode == 'x509' %}
auth = pubkey
diff --git a/data/templates/monitoring/override.conf.j2 b/data/templates/monitoring/override.conf.j2
index f8f150791..9f1b4ebec 100644
--- a/data/templates/monitoring/override.conf.j2
+++ b/data/templates/monitoring/override.conf.j2
@@ -2,6 +2,6 @@
After=vyos-router.service
ConditionPathExists=/run/telegraf/vyos-telegraf.conf
[Service]
-Environment=INFLUX_TOKEN={{ authentication.token }}
+Environment=INFLUX_TOKEN={{ influxdb.authentication.token }}
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN
AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
diff --git a/data/templates/monitoring/telegraf.j2 b/data/templates/monitoring/telegraf.j2
index d1a94366b..6b395692b 100644
--- a/data/templates/monitoring/telegraf.j2
+++ b/data/templates/monitoring/telegraf.j2
@@ -14,14 +14,31 @@
logfile = ""
hostname = ""
omit_hostname = false
-{% if influxdb_configured is vyos_defined %}
+{% if azure_data_explorer is vyos_defined %}
+### Azure Data Explorer ###
+[[outputs.azure_data_explorer]]
+ ## The URI property of the Azure Data Explorer resource on Azure
+ endpoint_url = "{{ azure_data_explorer.url }}"
+
+ ## The Azure Data Explorer database that the metrics will be ingested into.
+ ## The plugin will NOT generate this database automatically, it's expected that this database already exists before ingestion.
+ database = "{{ azure_data_explorer.database }}"
+ metrics_grouping_type = "{{ azure_data_explorer.group_metrics }}"
+
+ ## Name of the single table to store all the metrics (Only needed if metrics_grouping_type is "SingleTable").
+{% if azure_data_explorer.table is vyos_defined and azure_data_explorer.group_metrics == 'SingleTable' %}
+ table_name = "{{ azure_data_explorer.table }}"
+{% endif %}
+### End Azure Data Explorer ###
+{% endif %}
+{% if influxdb is vyos_defined %}
### InfluxDB2 ###
[[outputs.influxdb_v2]]
- urls = ["{{ url }}:{{ port }}"]
+ urls = ["{{ influxdb.url }}:{{ influxdb.port }}"]
insecure_skip_verify = true
token = "$INFLUX_TOKEN"
- organization = "{{ authentication.organization }}"
- bucket = "{{ bucket }}"
+ organization = "{{ influxdb.authentication.organization }}"
+ bucket = "{{ influxdb.bucket }}"
### End InfluxDB2 ###
{% endif %}
{% if prometheus_client is vyos_defined %}
diff --git a/data/templates/ntp/ntpd.conf.j2 b/data/templates/ntp/ntpd.conf.j2
index da610051e..8921826fa 100644
--- a/data/templates/ntp/ntpd.conf.j2
+++ b/data/templates/ntp/ntpd.conf.j2
@@ -33,10 +33,17 @@ restrict {{ address | address_from_cidr }} mask {{ address | netmask_from_cidr }
{% endfor %}
{% endif %}
-{% if listen_address %}
+{% if listen_address is vyos_defined or interface is vyos_defined %}
# NTP should listen on configured addresses only
interface ignore wildcard
-{% for address in listen_address %}
+{% if listen_address is vyos_defined %}
+{% for address in listen_address %}
interface listen {{ address }}
-{% endfor %}
+{% endfor %}
+{% endif %}
+{% if interface is vyos_defined %}
+{% for ifname in interface %}
+interface listen {{ ifname }}
+{% endfor %}
+{% endif %}
{% endif %}
diff --git a/data/templates/pmacct/uacctd.conf.j2 b/data/templates/pmacct/uacctd.conf.j2
index ea6247005..a5016691f 100644
--- a/data/templates/pmacct/uacctd.conf.j2
+++ b/data/templates/pmacct/uacctd.conf.j2
@@ -21,12 +21,14 @@ imt_mem_pools_number: 169
{% set plugin = [] %}
{% if netflow.server is vyos_defined %}
{% for server in netflow.server %}
-{% set _ = plugin.append('nfprobe[nf_' ~ server ~ ']') %}
+{% set nf_server_key = 'nf_' ~ server | replace(':', '.') %}
+{% set _ = plugin.append('nfprobe['~ nf_server_key ~ ']') %}
{% endfor %}
{% endif %}
{% if sflow.server is vyos_defined %}
{% for server in sflow.server %}
-{% set _ = plugin.append('sfprobe[sf_' ~ server ~ ']') %}
+{% set sf_server_key = 'sf_' ~ server | replace(':', '.') %}
+{% set _ = plugin.append('sfprobe[' ~ sf_server_key ~ ']') %}
{% endfor %}
{% endif %}
{% if disable_imt is not defined %}
@@ -37,22 +39,24 @@ plugins: {{ plugin | join(',') }}
{% if netflow.server is vyos_defined %}
# NetFlow servers
{% for server, server_config in netflow.server.items() %}
-nfprobe_receiver[nf_{{ server }}]: {{ server }}:{{ server_config.port }}
-nfprobe_version[nf_{{ server }}]: {{ netflow.version }}
+{# # prevent pmacct syntax error when using IPv6 flow collectors #}
+{% set nf_server_key = 'nf_' ~ server | replace(':', '.') %}
+nfprobe_receiver[{{ nf_server_key }}]: {{ server | bracketize_ipv6 }}:{{ server_config.port }}
+nfprobe_version[{{ nf_server_key }}]: {{ netflow.version }}
{% if netflow.engine_id is vyos_defined %}
-nfprobe_engine[nf_{{ server }}]: {{ netflow.engine_id }}
+nfprobe_engine[{{ nf_server_key }}]: {{ netflow.engine_id }}
{% endif %}
{% if netflow.max_flows is vyos_defined %}
-nfprobe_maxflows[nf_{{ server }}]: {{ netflow.max_flows }}
+nfprobe_maxflows[{{ nf_server_key }}]: {{ netflow.max_flows }}
{% endif %}
{% if netflow.sampling_rate is vyos_defined %}
-sampling_rate[nf_{{ server }}]: {{ netflow.sampling_rate }}
+sampling_rate[{{ nf_server_key }}]: {{ netflow.sampling_rate }}
{% endif %}
{% if netflow.source_address is vyos_defined %}
-nfprobe_source_ip[nf_{{ server }}]: {{ netflow.source_address }}
+nfprobe_source_ip[{{ nf_server_key }}]: {{ netflow.source_address }}
{% endif %}
{% if netflow.timeout is vyos_defined %}
-nfprobe_timeouts[nf_{{ server }}]: expint={{ netflow.timeout.expiry_interval }}:general={{ netflow.timeout.flow_generic }}:icmp={{ netflow.timeout.icmp }}:maxlife={{ netflow.timeout.max_active_life }}:tcp.fin={{ netflow.timeout.tcp_fin }}:tcp={{ netflow.timeout.tcp_generic }}:tcp.rst={{ netflow.timeout.tcp_rst }}:udp={{ netflow.timeout.udp }}
+nfprobe_timeouts[{{ nf_server_key }}]: expint={{ netflow.timeout.expiry_interval }}:general={{ netflow.timeout.flow_generic }}:icmp={{ netflow.timeout.icmp }}:maxlife={{ netflow.timeout.max_active_life }}:tcp.fin={{ netflow.timeout.tcp_fin }}:tcp={{ netflow.timeout.tcp_generic }}:tcp.rst={{ netflow.timeout.tcp_rst }}:udp={{ netflow.timeout.udp }}
{% endif %}
{% endfor %}
@@ -61,13 +65,15 @@ nfprobe_timeouts[nf_{{ server }}]: expint={{ netflow.timeout.expiry_interval }}:
{% if sflow.server is vyos_defined %}
# sFlow servers
{% for server, server_config in sflow.server.items() %}
-sfprobe_receiver[sf_{{ server }}]: {{ server }}:{{ server_config.port }}
-sfprobe_agentip[sf_{{ server }}]: {{ sflow.agent_address }}
+{# # prevent pmacct syntax error when using IPv6 flow collectors #}
+{% set sf_server_key = 'sf_' ~ server | replace(':', '.') %}
+sfprobe_receiver[{{ sf_server_key }}]: {{ server | bracketize_ipv6 }}:{{ server_config.port }}
+sfprobe_agentip[{{ sf_server_key }}]: {{ sflow.agent_address }}
{% if sflow.sampling_rate is vyos_defined %}
-sampling_rate[sf_{{ server }}]: {{ sflow.sampling_rate }}
+sampling_rate[{{ sf_server_key }}]: {{ sflow.sampling_rate }}
{% endif %}
{% if sflow.source_address is vyos_defined %}
-sfprobe_source_ip[sf_{{ server }}]: {{ sflow.source_address }}
+sfprobe_source_ip[{{ sf_server_key }}]: {{ sflow.source_address }}
{% endif %}
{% endfor %}
diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2
index 6902dc05a..ed15b32f0 100644
--- a/data/templates/router-advert/radvd.conf.j2
+++ b/data/templates/router-advert/radvd.conf.j2
@@ -55,6 +55,9 @@ interface {{ iface }} {
{% endif %}
{% if iface_config.name_server is vyos_defined %}
RDNSS {{ iface_config.name_server | join(" ") }} {
+{% if iface_config.name_server_lifetime is vyos_defined %}
+ AdvRDNSSLifetime {{ iface_config.name_server_lifetime }};
+{% endif %}
};
{% endif %}
{% if iface_config.dnssl is vyos_defined %}
diff --git a/data/templates/sla/owamp-override.conf.j2 b/data/templates/sla/owamp-override.conf.j2
new file mode 100644
index 000000000..b5ec161d4
--- /dev/null
+++ b/data/templates/sla/owamp-override.conf.j2
@@ -0,0 +1,16 @@
+[Unit]
+Description==OWAMP server
+After=vyos-router.service
+# Only start if there is a configuration file
+ConditionFileNotEmpty=/etc/owamp-server/owamp-server.conf
+
+[Service]
+KillMode=process
+Type=simple
+ExecStart=/usr/sbin/owampd -c /etc/owamp-server -R /var/run
+ExecReload=/bin/kill -HUP $MAINPID
+PIDFile=/run/owamp-server.pid
+LimitNOFILE=4096
+
+[Install]
+WantedBy=multi-user.target
diff --git a/data/templates/sla/owamp-server.conf.j2 b/data/templates/sla/owamp-server.conf.j2
new file mode 100644
index 000000000..6af963e57
--- /dev/null
+++ b/data/templates/sla/owamp-server.conf.j2
@@ -0,0 +1,20 @@
+### Autogenerated by service_twamp-server.py ###
+
+user owamp
+group owamp
+
+verbose
+vardir /var/run
+
+# location for "recv" session files.
+# The "catalog" subdirectory is completely cleaned and recreated each time
+datadir /var/lib/owamp
+
+srcnode :{{ port }}
+
+# This is used to limit testing to a specific port range. The valid values are:
+# 0 (twampd will let the system to pick the port number (ephemeral)
+# low-high (A range. high must be larger than low.)
+testports 8760-9960
+
+diskfudge 3.0
diff --git a/data/templates/sla/twamp-override.conf.j2 b/data/templates/sla/twamp-override.conf.j2
new file mode 100644
index 000000000..34bbd228b
--- /dev/null
+++ b/data/templates/sla/twamp-override.conf.j2
@@ -0,0 +1,16 @@
+[Unit]
+Description==TWAMP server
+After=vyos-router.service
+# Only start if there is a configuration file
+ConditionFileNotEmpty=/etc/twamp-server/twamp-server.conf
+
+[Service]
+KillMode=process
+Type=simple
+ExecStart=/usr/sbin/twampd -c /etc/twamp-server -R /var/run
+ExecReload=/bin/kill -HUP $MAINPID
+PIDFile=/run/twamp-server.pid
+LimitNOFILE=4096
+
+[Install]
+WantedBy=multi-user.target
diff --git a/data/templates/sla/twamp-server.conf.j2 b/data/templates/sla/twamp-server.conf.j2
new file mode 100644
index 000000000..ea5bbb54a
--- /dev/null
+++ b/data/templates/sla/twamp-server.conf.j2
@@ -0,0 +1,18 @@
+### Autogenerated by service_twamp-server.py ###
+
+user twamp
+group twamp
+
+verbose
+vardir /var/run
+
+# location for "recv" session files.
+# The "catalog" subdirectory is completely cleaned and recreated each time
+datadir /var/lib/twamp
+
+srcnode :{{ port }}
+
+# This is used to limit testing to a specific port range. The valid values are:
+# 0 (twampd will let the system to pick the port number (ephemeral)
+# low-high (A range. high must be larger than low.)
+testports 18760-19960
diff --git a/data/templates/ssh/sshguard_config.j2 b/data/templates/ssh/sshguard_config.j2
new file mode 100644
index 000000000..58c6ad48d
--- /dev/null
+++ b/data/templates/ssh/sshguard_config.j2
@@ -0,0 +1,27 @@
+### Autogenerated by ssh.py ###
+
+{% if dynamic_protection is vyos_defined %}
+# Full path to backend executable (required, no default)
+BACKEND="/usr/libexec/sshguard/sshg-fw-nft-sets"
+
+# Shell command that provides logs on standard output. (optional, no default)
+# Example 1: ssh and sendmail from systemd journal:
+LOGREADER="LANG=C journalctl -afb -p info -n1 -t sshd -o cat"
+
+#### OPTIONS ####
+# Block attackers when their cumulative attack score exceeds THRESHOLD.
+# Most attacks have a score of 10. (optional, default 30)
+THRESHOLD={{ dynamic_protection.threshold }}
+
+# Block attackers for initially BLOCK_TIME seconds after exceeding THRESHOLD.
+# Subsequent blocks increase by a factor of 1.5. (optional, default 120)
+BLOCK_TIME={{ dynamic_protection.block_time }}
+
+# Remember potential attackers for up to DETECTION_TIME seconds before
+# resetting their score. (optional, default 1800)
+DETECTION_TIME={{ dynamic_protection.detect_time }}
+
+# IP addresses listed in the WHITELIST_FILE are considered to be
+# friendlies and will never be blocked.
+WHITELIST_FILE=/etc/sshguard/whitelist
+{% endif %}
diff --git a/data/templates/ssh/sshguard_whitelist.j2 b/data/templates/ssh/sshguard_whitelist.j2
new file mode 100644
index 000000000..47a950a2b
--- /dev/null
+++ b/data/templates/ssh/sshguard_whitelist.j2
@@ -0,0 +1,7 @@
+### Autogenerated by ssh.py ###
+
+{% if dynamic_protection.allow_from is vyos_defined %}
+{% for address in dynamic_protection.allow_from %}
+{{ address }}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/syslog/rsyslog.conf.j2 b/data/templates/syslog/rsyslog.conf.j2
index 4445d568b..abe880283 100644
--- a/data/templates/syslog/rsyslog.conf.j2
+++ b/data/templates/syslog/rsyslog.conf.j2
@@ -10,7 +10,11 @@ $MarkMessagePeriod {{ files['global']['marker-interval'] }}
$PreserveFQDN on
{% endif %}
{% for file, file_options in files.items() %}
+{% if file_options['max-size'] is vyos_defined %}
$outchannel {{ file }},{{ file_options['log-file'] }},{{ file_options['max-size'] }},{{ file_options['action-on-max-size'] }}
+{% else %}
+$outchannel {{ file }},{{ file_options['log-file'] }}
+{% endif %}
{{ file_options['selectors'] }} :omfile:${{ file }}
{% endfor %}
{% if console is defined and console is not none %}
diff --git a/data/templates/zone_policy/nftables.j2 b/data/templates/zone_policy/nftables.j2
index e4c4dd7da..fe941f9f8 100644
--- a/data/templates/zone_policy/nftables.j2
+++ b/data/templates/zone_policy/nftables.j2
@@ -16,7 +16,7 @@ table ip filter {
iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }}
iifname { {{ zone[from_zone].interface | join(",") }} } counter return
{% endfor %}
- counter {{ zone_conf.default_action }}
+ {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
}
chain VZONE_{{ zone_name }}_OUT {
oifname lo counter return
@@ -24,7 +24,7 @@ table ip filter {
oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }}
oifname { {{ zone[from_zone].interface | join(",") }} } counter return
{% endfor %}
- counter {{ zone_conf.default_action }}
+ {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
}
{% else %}
chain VZONE_{{ zone_name }} {
@@ -38,7 +38,7 @@ table ip filter {
iifname { {{ zone[from_zone].interface | join(",") }} } counter return
{% endif %}
{% endfor %}
- counter {{ zone_conf.default_action }}
+ {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
}
{% endif %}
{% endfor %}
@@ -53,7 +53,7 @@ table ip6 filter {
iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }}
iifname { {{ zone[from_zone].interface | join(",") }} } counter return
{% endfor %}
- counter {{ zone_conf.default_action }}
+ {{ zone_conf | nft_default_rule('zone6_' + zone_name) }}
}
chain VZONE6_{{ zone_name }}_OUT {
oifname lo counter return
@@ -61,7 +61,7 @@ table ip6 filter {
oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }}
oifname { {{ zone[from_zone].interface | join(",") }} } counter return
{% endfor %}
- counter {{ zone_conf.default_action }}
+ {{ zone_conf | nft_default_rule('zone6_' + zone_name) }}
}
{% else %}
chain VZONE6_{{ zone_name }} {
@@ -75,7 +75,7 @@ table ip6 filter {
iifname { {{ zone[from_zone].interface | join(",") }} } counter return
{% endif %}
{% endfor %}
- counter {{ zone_conf.default_action }}
+ {{ zone_conf | nft_default_rule('zone6_' + zone_name) }}
}
{% endif %}
{% endfor %}
diff --git a/debian/control b/debian/control
index c53e4d3b8..6a6ccf602 100644
--- a/debian/control
+++ b/debian/control
@@ -108,6 +108,8 @@ Depends:
openvpn-auth-ldap,
openvpn-auth-radius,
openvpn-otp,
+ owamp-client,
+ owamp-server,
pciutils,
pdns-recursor,
pmacct (>= 1.6.0),
@@ -117,7 +119,6 @@ Depends:
python3,
python3-certbot-nginx,
python3-cryptography,
- python3-flask,
python3-hurry.filesize,
python3-inotify,
python3-isc-dhcp-leases,
@@ -133,7 +134,6 @@ Depends:
python3-tabulate,
python3-vici (>= 5.7.2),
python3-voluptuous,
- python3-waitress,
python3-xmltodict,
python3-zmq,
qrencode,
@@ -147,6 +147,7 @@ Depends:
squid,
squidclient,
squidguard,
+ sshguard,
ssl-cert,
strongswan (>= 5.9),
strongswan-swanctl (>= 5.9),
@@ -159,6 +160,8 @@ Depends:
tftpd-hpa,
traceroute,
tuned,
+ twamp-client,
+ twamp-server,
udp-broadcast-relay,
uidmap,
usb-modeswitch,
diff --git a/debian/vyos-1x-smoketest.install b/debian/vyos-1x-smoketest.install
index 3739763b9..406fef4be 100644
--- a/debian/vyos-1x-smoketest.install
+++ b/debian/vyos-1x-smoketest.install
@@ -1,4 +1,5 @@
usr/bin/vyos-smoketest
usr/bin/vyos-configtest
+usr/bin/vyos-configtest-pki
usr/libexec/vyos/tests/smoke
usr/libexec/vyos/tests/config
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 493c896eb..edd090993 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -1,4 +1,3 @@
-etc/cron.hourly
etc/dhcp
etc/ipsec.d
etc/logrotate.d
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 85231b50c..51171d881 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="container" owner="${vyos_conf_scripts_dir}/containers.py">
+ <node name="container" owner="${vyos_conf_scripts_dir}/container.py">
<properties>
<help>Container applications</help>
<priority>1280</priority>
diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in
index 60e738e01..6e1592200 100644
--- a/interface-definitions/dhcp-server.xml.in
+++ b/interface-definitions/dhcp-server.xml.in
@@ -67,10 +67,7 @@
</node>
<leafNode name="global-parameters">
<properties>
- <help>Additional global parameters for DHCP server. You must
- use the syntax of dhcpd.conf in this text-field. Using this
- without proper knowledge may result in a crashed DHCP server.
- Check system log to look for errors.</help>
+ <help>Additional global parameters for DHCP server. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
<multi/>
</properties>
</leafNode>
@@ -111,10 +108,7 @@
#include <include/name-server-ipv4.xml.i>
<leafNode name="shared-network-parameters">
<properties>
- <help>Additional shared-network parameters for DHCP server.
- You must use the syntax of dhcpd.conf in this text-field.
- Using this without proper knowledge may result in a crashed
- DHCP server. Check system log to look for errors.</help>
+ <help>Additional shared-network parameters for DHCP server. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
<multi/>
</properties>
</leafNode>
@@ -134,17 +128,38 @@
<leafNode name="bootfile-name">
<properties>
<help>Bootstrap file name</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9./]+</regex>
+ </constraint>
</properties>
</leafNode>
<leafNode name="bootfile-server">
<properties>
- <help>Server (IP address or domain name) from which the initial
- boot file is to be loaded</help>
+ <help>Server from which the initial boot file is to be loaded</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Bootfile server IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>Bootfile server FQDN</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="fqdn"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="bootfile-size">
<properties>
- <help>Bootstrap file size in 512 byte blocks</help>
+ <help>Bootstrap file size</help>
+ <valueHelp>
+ <format>u32:1-16</format>
+ <description>Bootstrap file size in 512 byte blocks</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-16"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="client-prefix-length">
@@ -326,11 +341,7 @@
</leafNode>
<leafNode name="static-mapping-parameters">
<properties>
- <help>Additional static-mapping parameters for DHCP server.
- Will be placed inside the "host" block of the mapping.
- You must use the syntax of dhcpd.conf in this text-field.
- Using this without proper knowledge may result in a crashed
- DHCP server. Check system log to look for errors.</help>
+ <help>Additional static-mapping parameters for DHCP server. Will be placed inside the "host" block of the mapping. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
<multi/>
</properties>
</leafNode>
@@ -338,7 +349,7 @@
</tagNode>
<tagNode name="static-route">
<properties>
- <help>Classless static route destination subnet [REQUIRED]</help>
+ <help>Classless static route destination subnet</help>
<valueHelp>
<format>ipv4net</format>
<description>IPv4 address and prefix length</description>
@@ -364,10 +375,7 @@
</tagNode >
<leafNode name="subnet-parameters">
<properties>
- <help>Additional subnet parameters for DHCP server. You must
- use the syntax of dhcpd.conf in this text-field. Using this
- without proper knowledge may result in a crashed DHCP server.
- Check system log to look for errors.</help>
+ <help>Additional subnet parameters for DHCP server. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
<multi/>
</properties>
</leafNode>
diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in
index 10335b07e..9dff68a24 100644
--- a/interface-definitions/dhcpv6-server.xml.in
+++ b/interface-definitions/dhcpv6-server.xml.in
@@ -32,7 +32,7 @@
</leafNode>
<tagNode name="shared-network-name">
<properties>
- <help>DHCPv6 shared network name [REQUIRED]</help>
+ <help>DHCPv6 shared network name</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
@@ -64,7 +64,7 @@
</node>
<tagNode name="subnet">
<properties>
- <help>IPv6 DHCP subnet for this shared network [REQUIRED]</help>
+ <help>IPv6 DHCP subnet for this shared network</help>
<valueHelp>
<format>ipv6net</format>
<description>IPv6 address and prefix length</description>
diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in
index 0d6418272..70b2fb271 100644
--- a/interface-definitions/dns-domain-name.xml.in
+++ b/interface-definitions/dns-domain-name.xml.in
@@ -91,7 +91,7 @@
</leafNode>
<leafNode name="inet">
<properties>
- <help>IP Address [REQUIRED]</help>
+ <help>IP Address</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in
index 6bc467b76..e41ba7f60 100644
--- a/interface-definitions/dns-dynamic.xml.in
+++ b/interface-definitions/dns-dynamic.xml.in
@@ -14,7 +14,7 @@
<children>
<tagNode name="interface">
<properties>
- <help>Interface to send DDNS updates for [REQUIRED]</help>
+ <help>Interface to send DDNS updates for</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
@@ -27,7 +27,7 @@
<children>
<leafNode name="key">
<properties>
- <help>File containing the secret key shared with remote DNS server [REQUIRED]</help>
+ <help>File containing the secret key shared with remote DNS server</help>
<valueHelp>
<format>filename</format>
<description>File in /config/auth directory</description>
@@ -36,13 +36,13 @@
</leafNode>
<leafNode name="record">
<properties>
- <help>Record to be updated [REQUIRED]</help>
+ <help>Record to be updated</help>
<multi/>
</properties>
</leafNode>
<leafNode name="server">
<properties>
- <help>Server to be updated [REQUIRED]</help>
+ <help>Server to be updated</help>
</properties>
</leafNode>
<leafNode name="ttl">
@@ -60,14 +60,14 @@
</leafNode>
<leafNode name="zone">
<properties>
- <help>Zone to be updated [REQUIRED]</help>
+ <help>Zone to be updated</help>
</properties>
</leafNode>
</children>
</tagNode>
<tagNode name="service">
<properties>
- <help>Service being used for Dynamic DNS [REQUIRED]</help>
+ <help>Service being used for Dynamic DNS</help>
<completionHelp>
<list>afraid changeip cloudflare dnspark dslreports dyndns easydns namecheap noip sitelutions zoneedit</list>
</completionHelp>
@@ -127,23 +127,23 @@
<children>
<leafNode name="host-name">
<properties>
- <help>Hostname registered with DDNS service [REQUIRED]</help>
+ <help>Hostname registered with DDNS service</help>
<multi/>
</properties>
</leafNode>
<leafNode name="login">
<properties>
- <help>Login for DDNS service [REQUIRED]</help>
+ <help>Login for DDNS service</help>
</properties>
</leafNode>
<leafNode name="password">
<properties>
- <help>Password for DDNS service [REQUIRED]</help>
+ <help>Password for DDNS service</help>
</properties>
</leafNode>
<leafNode name="protocol">
<properties>
- <help>ddclient protocol used for DDNS service [REQUIRED FOR CUSTOM]</help>
+ <help>ddclient protocol used for DDNS service</help>
<completionHelp>
<list>changeip cloudflare dnsmadeeasy dnspark dondominio dslreports1 dtdns duckdns dyndns2 easydns freedns freemyip googledomains hammernode1 namecheap nfsn noip sitelutions woima yandex zoneedit1</list>
</completionHelp>
@@ -239,7 +239,7 @@
</leafNode>
<leafNode name="server">
<properties>
- <help>Server to send DDNS update to [REQUIRED FOR CUSTOM]</help>
+ <help>Server to send DDNS update to</help>
<valueHelp>
<format>IPv4</format>
<description>IP address of DDNS server</description>
diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in
index 6ead3e199..3de0dc0eb 100644
--- a/interface-definitions/dns-forwarding.xml.in
+++ b/interface-definitions/dns-forwarding.xml.in
@@ -36,6 +36,18 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="dns64-prefix">
+ <properties>
+ <help>Help to communicate between IPv6-only client and IPv4-only server</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and /96 only prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="dnssec">
<properties>
<help>DNSSEC mode</help>
@@ -133,14 +145,18 @@
<format>@</format>
<description>Root record</description>
</valueHelp>
+ <valueHelp>
+ <format>any</format>
+ <description>Wildcard record (any subdomain)</description>
+ </valueHelp>
<constraint>
- <regex>([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)</regex>
+ <regex>([-_a-zA-Z0-9.]{1,63}|@|any)(?&lt;!\.)</regex>
</constraint>
</properties>
<children>
<leafNode name="address">
<properties>
- <help>IPv4 address [REQUIRED]</help>
+ <help>IPv4 address</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
@@ -166,14 +182,18 @@
<format>@</format>
<description>Root record</description>
</valueHelp>
+ <valueHelp>
+ <format>any</format>
+ <description>Wildcard record (any subdomain)</description>
+ </valueHelp>
<constraint>
- <regex>([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)</regex>
+ <regex>([-_a-zA-Z0-9.]{1,63}|@|any)(?&lt;!\.)</regex>
</constraint>
</properties>
<children>
<leafNode name="address">
<properties>
- <help>IPv6 address [REQUIRED]</help>
+ <help>IPv6 address</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address</description>
@@ -206,7 +226,7 @@
<children>
<leafNode name="target">
<properties>
- <help>Target DNS name [REQUIRED]</help>
+ <help>Target DNS name</help>
<valueHelp>
<format>name.example.com</format>
<description>An absolute DNS name</description>
@@ -238,7 +258,7 @@
<children>
<tagNode name="server">
<properties>
- <help>Mail server [REQUIRED]</help>
+ <help>Mail server</help>
<valueHelp>
<format>name.example.com</format>
<description>An absolute DNS name</description>
@@ -285,7 +305,7 @@
<children>
<leafNode name="target">
<properties>
- <help>Target DNS name [REQUIRED]</help>
+ <help>Target DNS name</help>
<valueHelp>
<format>name.example.com</format>
<description>An absolute DNS name</description>
@@ -317,7 +337,7 @@
<children>
<leafNode name="value">
<properties>
- <help>Record contents [REQUIRED]</help>
+ <help>Record contents</help>
<valueHelp>
<format>text</format>
<description>Record contents</description>
@@ -347,7 +367,7 @@
<children>
<leafNode name="value">
<properties>
- <help>Record contents [REQUIRED]</help>
+ <help>Record contents</help>
<valueHelp>
<format>text</format>
<description>Record contents</description>
@@ -376,7 +396,7 @@
<children>
<tagNode name="entry">
<properties>
- <help>Service entry [REQUIRED]</help>
+ <help>Service entry</help>
<valueHelp>
<format>u32:0-65535</format>
<description>Entry number</description>
@@ -388,7 +408,7 @@
<children>
<leafNode name="hostname">
<properties>
- <help>Server hostname [REQUIRED]</help>
+ <help>Server hostname</help>
<valueHelp>
<format>name.example.com</format>
<description>An absolute DNS name</description>
@@ -400,7 +420,7 @@
</leafNode>
<leafNode name="port">
<properties>
- <help>Port number [REQUIRED]</help>
+ <help>Port number</help>
<valueHelp>
<format>u32:0-65535</format>
<description>TCP/UDP port number</description>
@@ -460,7 +480,7 @@
<children>
<tagNode name="rule">
<properties>
- <help>NAPTR rule [REQUIRED]</help>
+ <help>NAPTR rule</help>
<valueHelp>
<format>u32:0-65535</format>
<description>Rule number</description>
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index ff8d92a24..2e9452dfd 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -97,6 +97,40 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="include">
+ <properties>
+ <help>Include another address-group</help>
+ <completionHelp>
+ <path>firewall group address-group</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/generic-description.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="domain-group">
+ <properties>
+ <help>Firewall domain-group</help>
+ <constraint>
+ <regex>[a-zA-Z_][a-zA-Z0-9][\w\-\.]*</regex>
+ </constraint>
+ <constraintErrorMessage>Name of domain-group can only contain alpha-numeric letters, hyphen, underscores and not start with numeric</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>Domain-group member</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Domain address to match</description>
+ </valueHelp>
+ <constraint>
+ <regex>[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,99}?(\/.*)?</regex>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
#include <include/generic-description.xml.i>
</children>
</tagNode>
@@ -126,6 +160,15 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="include">
+ <properties>
+ <help>Include another ipv6-address-group</help>
+ <completionHelp>
+ <path>firewall group ipv6-address-group</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
#include <include/generic-description.xml.i>
</children>
</tagNode>
@@ -151,6 +194,15 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="include">
+ <properties>
+ <help>Include another ipv6-network-group</help>
+ <completionHelp>
+ <path>firewall group ipv6-network-group</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
<tagNode name="mac-group">
@@ -175,6 +227,15 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="include">
+ <properties>
+ <help>Include another mac-group</help>
+ <completionHelp>
+ <path>firewall group mac-group</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
<tagNode name="network-group">
@@ -199,6 +260,15 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="include">
+ <properties>
+ <help>Include another network-group</help>
+ <completionHelp>
+ <path>firewall group network-group</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
<tagNode name="port-group">
@@ -231,6 +301,15 @@
</constraint>
</properties>
</leafNode>
+ <leafNode name="include">
+ <properties>
+ <help>Include another port-group</help>
+ <completionHelp>
+ <path>firewall group port-group</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
</children>
@@ -287,6 +366,7 @@
</properties>
<children>
#include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/geoip.xml.i>
#include <include/firewall/source-destination-group-ipv6.xml.i>
#include <include/firewall/port.xml.i>
</children>
@@ -297,6 +377,7 @@
</properties>
<children>
#include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/geoip.xml.i>
#include <include/firewall/source-destination-group-ipv6.xml.i>
#include <include/firewall/port.xml.i>
</children>
@@ -473,6 +554,7 @@
</properties>
<children>
#include <include/firewall/address.xml.i>
+ #include <include/firewall/geoip.xml.i>
#include <include/firewall/source-destination-group.xml.i>
#include <include/firewall/port.xml.i>
</children>
@@ -483,6 +565,7 @@
</properties>
<children>
#include <include/firewall/address.xml.i>
+ #include <include/firewall/geoip.xml.i>
#include <include/firewall/source-destination-group.xml.i>
#include <include/firewall/port.xml.i>
</children>
@@ -520,6 +603,49 @@
#include <include/firewall/icmp-type-name.xml.i>
</children>
</node>
+ <node name="ttl">
+ <properties>
+ <help>Time to live limit</help>
+ </properties>
+ <children>
+ <leafNode name="eq">
+ <properties>
+ <help>Value to match a ttl equal to it</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ttl equal to value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="gt">
+ <properties>
+ <help>Value to match a ttl greater than or equal to it</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ttl greater than value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="lt">
+ <properties>
+ <help>Value to match a ttl less than or equal to it</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ttl less than value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
</children>
</tagNode>
</children>
@@ -599,7 +725,7 @@
</properties>
<children>
#include <include/firewall/action-accept-drop-reject.xml.i>
- #include <include/firewall/log.xml.i>
+ #include <include/firewall/rule-log-level.xml.i>
</children>
</node>
<node name="invalid">
@@ -608,7 +734,7 @@
</properties>
<children>
#include <include/firewall/action-accept-drop-reject.xml.i>
- #include <include/firewall/log.xml.i>
+ #include <include/firewall/rule-log-level.xml.i>
</children>
</node>
<node name="related">
@@ -617,7 +743,7 @@
</properties>
<children>
#include <include/firewall/action-accept-drop-reject.xml.i>
- #include <include/firewall/log.xml.i>
+ #include <include/firewall/rule-log-level.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in
index fc59f8ab3..878566b3f 100644
--- a/interface-definitions/flow-accounting-conf.xml.in
+++ b/interface-definitions/flow-accounting-conf.xml.in
@@ -220,7 +220,7 @@
</leafNode>
<tagNode name="server">
<properties>
- <help>Server to export NetFlow [REQUIRED]</help>
+ <help>NetFlow destination server</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 server to export NetFlow</description>
@@ -398,7 +398,7 @@
</leafNode>
<tagNode name="server">
<properties>
- <help>Server to export sFlow [REQUIRED]</help>
+ <help>sFlow destination server</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 server to export sFlow</description>
diff --git a/interface-definitions/igmp-proxy.xml.in b/interface-definitions/igmp-proxy.xml.in
index 8e738fa7f..50cb33a93 100644
--- a/interface-definitions/igmp-proxy.xml.in
+++ b/interface-definitions/igmp-proxy.xml.in
@@ -18,7 +18,7 @@
</leafNode>
<tagNode name="interface">
<properties>
- <help>Interface for IGMP proxy [REQUIRED]</help>
+ <help>Interface for IGMP proxy</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
diff --git a/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i b/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i
index be49fce5a..f44920c3f 100644
--- a/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i
+++ b/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i
@@ -21,6 +21,20 @@
<valueless />
</properties>
</leafNode>
+ <leafNode name="multiplier">
+ <properties>
+ <help>Shaper multiplier</help>
+ <valueHelp>
+ <format>&lt;0.001-1000&gt;</format>
+ <description>Shaper multiplier</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0.001-1000 --float"/>
+ </constraint>
+ <constraintErrorMessage>Multiplier needs to be between 0.001 and 1000</constraintErrorMessage>
+ </properties>
+ <defaultValue>1</defaultValue>
+ </leafNode>
</children>
</node>
<!-- include end -->
diff --git a/interface-definitions/include/auth-local-users.xml.i b/interface-definitions/include/auth-local-users.xml.i
index cb456eecf..9fb507474 100644
--- a/interface-definitions/include/auth-local-users.xml.i
+++ b/interface-definitions/include/auth-local-users.xml.i
@@ -19,74 +19,6 @@
<help>Password used for authentication</help>
</properties>
</leafNode>
- <node name="otp">
- <properties>
- <help>2FA OTP authentication parameters</help>
- </properties>
- <children>
- <leafNode name="key">
- <properties>
- <help>Token Key Secret key for the token algorithm (see RFC 4226)</help>
- <valueHelp>
- <format>txt</format>
- <description>OTP key in hex-encoded format</description>
- </valueHelp>
- <constraint>
- <regex>[a-fA-F0-9]{20,10000}</regex>
- </constraint>
- <constraintErrorMessage>Key name must only include hex characters and be at least 20 characters long</constraintErrorMessage>
- </properties>
- </leafNode>
- <leafNode name="otp-length">
- <properties>
- <help>Number of digits in OTP code</help>
- <valueHelp>
- <format>u32:6-8</format>
- <description>Number of digits in OTP code</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 6-8"/>
- </constraint>
- <constraintErrorMessage>Number of digits in OTP code must be between 6 and 8</constraintErrorMessage>
- </properties>
- <defaultValue>6</defaultValue>
- </leafNode>
- <leafNode name="interval">
- <properties>
- <help>Time tokens interval in seconds</help>
- <valueHelp>
- <format>u32:5-86400</format>
- <description>Time tokens interval in seconds.</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 5-86400"/>
- </constraint>
- <constraintErrorMessage>Time token interval must be between 5 and 86400 seconds</constraintErrorMessage>
- </properties>
- <defaultValue>30</defaultValue>
- </leafNode>
- <leafNode name="token-type">
- <properties>
- <help>Token type</help>
- <valueHelp>
- <format>hotp-time</format>
- <description>Time-based OTP algorithm</description>
- </valueHelp>
- <valueHelp>
- <format>hotp-event</format>
- <description>Event-based OTP algorithm</description>
- </valueHelp>
- <constraint>
- <regex>(hotp-time|hotp-event)</regex>
- </constraint>
- <completionHelp>
- <list>hotp-time hotp-event</list>
- </completionHelp>
- </properties>
- <defaultValue>hotp-time</defaultValue>
- </leafNode>
- </children>
- </node>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index abaff5232..c1b465e43 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -1156,7 +1156,7 @@
<children>
<leafNode name="identifier">
<properties>
- <help>Confederation AS identifier [REQUIRED]</help>
+ <help>Confederation AS identifier</help>
<valueHelp>
<format>u32:1-4294967294</format>
<description>Confederation AS id</description>
@@ -1208,7 +1208,7 @@
<children>
<leafNode name="half-life">
<properties>
- <help>Half-life time for dampening [REQUIRED]</help>
+ <help>Half-life time for dampening</help>
<valueHelp>
<format>u32:1-45</format>
<description>Half-life penalty in minutes</description>
@@ -1220,7 +1220,7 @@
</leafNode>
<leafNode name="max-suppress-time">
<properties>
- <help>Maximum duration to suppress a stable route [REQUIRED]</help>
+ <help>Maximum duration to suppress a stable route</help>
<valueHelp>
<format>u32:1-255</format>
<description>Maximum suppress duration in minutes</description>
@@ -1232,7 +1232,7 @@
</leafNode>
<leafNode name="re-use">
<properties>
- <help>Threshold to start reusing a route [REQUIRED]</help>
+ <help>Threshold to start reusing a route</help>
<valueHelp>
<format>u32:1-20000</format>
<description>Re-use penalty points</description>
@@ -1244,7 +1244,7 @@
</leafNode>
<leafNode name="start-suppress-time">
<properties>
- <help>When to start suppressing a route [REQUIRED]</help>
+ <help>When to start suppressing a route</help>
<valueHelp>
<format>u32:1-20000</format>
<description>Start-suppress penalty points</description>
diff --git a/interface-definitions/include/bgp/remote-as.xml.i b/interface-definitions/include/bgp/remote-as.xml.i
index 58595b3b9..79d3b95a9 100644
--- a/interface-definitions/include/bgp/remote-as.xml.i
+++ b/interface-definitions/include/bgp/remote-as.xml.i
@@ -1,7 +1,7 @@
<!-- include start from bgp/remote-as.xml.i -->
<leafNode name="remote-as">
<properties>
- <help>Neighbor BGP AS number [REQUIRED]</help>
+ <help>Neighbor BGP AS number</help>
<completionHelp>
<list>external internal</list>
</completionHelp>
diff --git a/interface-definitions/include/eigrp/protocol-common-config.xml.i b/interface-definitions/include/eigrp/protocol-common-config.xml.i
new file mode 100644
index 000000000..147277102
--- /dev/null
+++ b/interface-definitions/include/eigrp/protocol-common-config.xml.i
@@ -0,0 +1,121 @@
+<!-- include start from eigrp/protocol-common-config.xml.i -->
+<leafNode name="local-as">
+ <properties>
+ <help>Autonomous System Number (ASN)</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Autonomous System Number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+</leafNode>
+<leafNode name="maximum-paths">
+ <properties>
+ <help>Forward packets over multiple paths</help>
+ <valueHelp>
+ <format>u32:1-32</format>
+ <description>Number of paths</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-32"/>
+ </constraint>
+ </properties>
+</leafNode>
+<node name="metric">
+ <properties>
+ <help>Modify metrics and parameters for advertisement</help>
+ </properties>
+ <children>
+ <leafNode name="weights">
+ <properties>
+ <help>Modify metric coefficients</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>K1</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<leafNode name="network">
+ <properties>
+ <help>Enable routing on an IP network</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>EIGRP network prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="passive-interface">
+ <properties>
+ <help>Suppress routing updates on an interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+</leafNode>
+<leafNode name="redistribute">
+ <properties>
+ <help>Redistribute information from another routing protocol</help>
+ <valueHelp>
+ <format>bgp</format>
+ <description>Border Gateway Protocol (BGP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>connected</format>
+ <description>Connected routes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nhrp</format>
+ <description>Next Hop Resolution Protocol (NHRP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ospf</format>
+ <description>Open Shortest Path First (OSPFv2)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rip</format>
+ <description>Routing Information Protocol (RIP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>static</format>
+ <description>Statically configured routes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>vnc</format>
+ <description>Virtual Network Control (VNC)</description>
+ </valueHelp>
+ <completionHelp>
+ <list>bgp connected nhrp ospf rip static vnc</list>
+ </completionHelp>
+ <constraint>
+ <regex>(bgp|connected|nhrp|ospf|rip|static|vnc)</regex>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+#include <include/route-map.xml.i>
+#include <include/router-id.xml.i>
+<!-- FRR timers not implemented yet -->
+<leafNode name="variance">
+ <properties>
+ <help>Control load balancing variance</help>
+ <valueHelp>
+ <format>u32:1-128</format>
+ <description>Metric variance multiplier</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-128"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i
index 0f60e3c38..512cc23bd 100644
--- a/interface-definitions/include/firewall/action.xml.i
+++ b/interface-definitions/include/firewall/action.xml.i
@@ -1,7 +1,7 @@
<!-- include start from firewall/action.xml.i -->
<leafNode name="action">
<properties>
- <help>Rule action [REQUIRED]</help>
+ <help>Rule action</help>
<completionHelp>
<list>accept reject drop</list>
</completionHelp>
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index cbdfa9dc2..079864122 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -95,6 +95,33 @@
</constraint>
</properties>
</leafNode>
+#include <include/firewall/rule-log-level.xml.i>
+<node name="connection-status">
+ <properties>
+ <help>Connection status</help>
+ </properties>
+ <children>
+ <leafNode name="nat">
+ <properties>
+ <help>NAT connection status</help>
+ <completionHelp>
+ <list>destination source</list>
+ </completionHelp>
+ <valueHelp>
+ <format>destination</format>
+ <description>Match connections that are subject to destination NAT</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source</format>
+ <description>Match connections that are subject to source NAT</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(destination|source)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
<leafNode name="protocol">
<properties>
<help>Protocol to match (protocol name, number, or "all")</help>
diff --git a/interface-definitions/include/firewall/geoip.xml.i b/interface-definitions/include/firewall/geoip.xml.i
new file mode 100644
index 000000000..9fb37a574
--- /dev/null
+++ b/interface-definitions/include/firewall/geoip.xml.i
@@ -0,0 +1,28 @@
+<!-- include start from firewall/geoip.xml.i -->
+<node name="geoip">
+ <properties>
+ <help>GeoIP options - Data provided by DB-IP.com</help>
+ </properties>
+ <children>
+ <leafNode name="country-code">
+ <properties>
+ <help>GeoIP country code</help>
+ <valueHelp>
+ <format>&lt;country&gt;</format>
+ <description>Country code (2 characters)</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bl|bm|bn|bo|bq|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mf|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)$</regex>
+ </constraint>
+ <multi />
+ </properties>
+ </leafNode>
+ <leafNode name="inverse-match">
+ <properties>
+ <help>Inverse match of country-codes</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/name-default-log.xml.i b/interface-definitions/include/firewall/name-default-log.xml.i
index 979395146..1d0ff9497 100644
--- a/interface-definitions/include/firewall/name-default-log.xml.i
+++ b/interface-definitions/include/firewall/name-default-log.xml.i
@@ -5,4 +5,4 @@
<valueless/>
</properties>
</leafNode>
-<!-- include end -->
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/rule-log-level.xml.i b/interface-definitions/include/firewall/rule-log-level.xml.i
new file mode 100644
index 000000000..10c8de5e3
--- /dev/null
+++ b/interface-definitions/include/firewall/rule-log-level.xml.i
@@ -0,0 +1,45 @@
+<!-- include start from firewall/common-rule.xml.i -->
+<leafNode name="log-level">
+ <properties>
+ <help>Set log-level. Log must be enable.</help>
+ <completionHelp>
+ <list>emerg alert crit err warn notice info debug</list>
+ </completionHelp>
+ <valueHelp>
+ <format>emerg</format>
+ <description>Emerg log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>alert</format>
+ <description>Alert log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>crit</format>
+ <description>Critical log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>err</format>
+ <description>Error log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>warn</format>
+ <description>Warning log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>notice</format>
+ <description>Notice log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>info</format>
+ <description>Info log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>debug</format>
+ <description>Debug log level</description>
+ </valueHelp>
+ <constraint>
+ <regex>(emerg|alert|crit|err|warn|notice|info|debug)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/source-destination-group.xml.i b/interface-definitions/include/firewall/source-destination-group.xml.i
index ab11e89e9..6ebee356c 100644
--- a/interface-definitions/include/firewall/source-destination-group.xml.i
+++ b/interface-definitions/include/firewall/source-destination-group.xml.i
@@ -12,6 +12,14 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="domain-group">
+ <properties>
+ <help>Group of domains</help>
+ <completionHelp>
+ <path>firewall group domain-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
#include <include/firewall/mac-group.xml.i>
<leafNode name="network-group">
<properties>
diff --git a/interface-definitions/include/generic-interface-multi.xml.i b/interface-definitions/include/generic-interface-multi.xml.i
index 44e87775c..65aae28ae 100644
--- a/interface-definitions/include/generic-interface-multi.xml.i
+++ b/interface-definitions/include/generic-interface-multi.xml.i
@@ -1,7 +1,7 @@
<!-- include start from generic-interface-multi.xml.i -->
<leafNode name="interface">
<properties>
- <help>Interface Name to use</help>
+ <help>Interface to use</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
diff --git a/interface-definitions/include/generic-interface.xml.i b/interface-definitions/include/generic-interface.xml.i
index 50af718a5..8b4cf1d65 100644
--- a/interface-definitions/include/generic-interface.xml.i
+++ b/interface-definitions/include/generic-interface.xml.i
@@ -1,7 +1,7 @@
<!-- include start from generic-interface.xml.i -->
<leafNode name="interface">
<properties>
- <help>Interface Name to use</help>
+ <help>Interface to use</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
diff --git a/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i b/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i
index b9dd59bea..5057ed9ae 100644
--- a/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i
+++ b/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from address-ipv4-ipv6-dhcp.xml.i -->
+<!-- include start from interface/address-ipv4-ipv6-dhcp.xml.i -->
<leafNode name="address">
<properties>
<help>IP address</help>
diff --git a/interface-definitions/include/interface/address-ipv4-ipv6.xml.i b/interface-definitions/include/interface/address-ipv4-ipv6.xml.i
index 519622050..d689da5aa 100644
--- a/interface-definitions/include/interface/address-ipv4-ipv6.xml.i
+++ b/interface-definitions/include/interface/address-ipv4-ipv6.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from address-ipv4-ipv6.xml.i -->
+<!-- include start from interface/address-ipv4-ipv6.xml.i -->
<leafNode name="address">
<properties>
<help>IP address</help>
diff --git a/interface-definitions/include/interface/dhcpv6-options.xml.i b/interface-definitions/include/interface/dhcpv6-options.xml.i
index 08e4f5e0a..c705af7c2 100644
--- a/interface-definitions/include/interface/dhcpv6-options.xml.i
+++ b/interface-definitions/include/interface/dhcpv6-options.xml.i
@@ -71,11 +71,11 @@
<properties>
<help>Interface site-Level aggregator (SLA)</help>
<valueHelp>
- <format>u32:0-128</format>
+ <format>u32:0-65535</format>
<description>Decimal integer which fits in the length of SLA IDs</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 0-128"/>
+ <validator name="numeric" argument="--range 0-65535"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/enable-directed-broadcast.xml.i b/interface-definitions/include/interface/enable-directed-broadcast.xml.i
new file mode 100644
index 000000000..a87395806
--- /dev/null
+++ b/interface-definitions/include/interface/enable-directed-broadcast.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from interface/enable-directed-broadcast.xml.i -->
+<leafNode name="enable-directed-broadcast">
+ <properties>
+ <help>Enable directed broadcast forwarding on this interface</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/ipv4-options.xml.i b/interface-definitions/include/interface/ipv4-options.xml.i
index bca1229c6..eda77e851 100644
--- a/interface-definitions/include/interface/ipv4-options.xml.i
+++ b/interface-definitions/include/interface/ipv4-options.xml.i
@@ -8,6 +8,7 @@
#include <include/interface/arp-cache-timeout.xml.i>
#include <include/interface/disable-arp-filter.xml.i>
#include <include/interface/disable-forwarding.xml.i>
+ #include <include/interface/enable-directed-broadcast.xml.i>
#include <include/interface/enable-arp-accept.xml.i>
#include <include/interface/enable-arp-announce.xml.i>
#include <include/interface/enable-arp-ignore.xml.i>
diff --git a/interface-definitions/include/ipsec/local-address.xml.i b/interface-definitions/include/ipsec/local-address.xml.i
index dc5653ce7..9d267f3f7 100644
--- a/interface-definitions/include/ipsec/local-address.xml.i
+++ b/interface-definitions/include/ipsec/local-address.xml.i
@@ -4,6 +4,7 @@
<help>IPv4 or IPv6 address of a local interface to use for VPN</help>
<completionHelp>
<list>any</list>
+ <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
</completionHelp>
<valueHelp>
<format>ipv4</format>
diff --git a/interface-definitions/include/monitoring/url.xml.i b/interface-definitions/include/monitoring/url.xml.i
new file mode 100644
index 000000000..fd61c38ea
--- /dev/null
+++ b/interface-definitions/include/monitoring/url.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from monitoring/url.xml.i -->
+<leafNode name="url">
+ <properties>
+ <help>Remote URL</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Remote URL</description>
+ </valueHelp>
+ <constraint>
+ <regex>(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}?(\/.*)?</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i
index c156d5b1c..791bbc0f8 100644
--- a/interface-definitions/include/ospf/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospf/protocol-common-config.xml.i
@@ -16,7 +16,7 @@
<children>
<leafNode name="export">
<properties>
- <help>Filter for outgoing routing update [REQUIRED]</help>
+ <help>Filter for outgoing routing update</help>
<completionHelp>
<list>bgp connected kernel rip static</list>
</completionHelp>
@@ -178,10 +178,10 @@
</leafNode>
<leafNode name="network">
<properties>
- <help>OSPF network [REQUIRED]</help>
+ <help>OSPF network</help>
<valueHelp>
<format>ipv4net</format>
- <description>OSPF network [REQUIRED]</description>
+ <description>OSPF network</description>
</valueHelp>
<constraint>
<validator name="ipv4-prefix"/>
diff --git a/interface-definitions/include/pki/ca-certificate-multi.xml.i b/interface-definitions/include/pki/ca-certificate-multi.xml.i
new file mode 100644
index 000000000..646131b54
--- /dev/null
+++ b/interface-definitions/include/pki/ca-certificate-multi.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from pki/ca-certificate-multi.xml.i -->
+<leafNode name="ca-certificate">
+ <properties>
+ <help>Certificate Authority chain in PKI configuration</help>
+ <completionHelp>
+ <path>pki ca</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of CA in PKI configuration</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/action.xml.i b/interface-definitions/include/policy/action.xml.i
index 0a3dc158a..5aa865523 100644
--- a/interface-definitions/include/policy/action.xml.i
+++ b/interface-definitions/include/policy/action.xml.i
@@ -1,7 +1,7 @@
<!-- include start from policy/action.xml.i -->
<leafNode name="action">
<properties>
- <help>Action to take on entries matching this rule [REQUIRED]</help>
+ <help>Action to take on entries matching this rule</help>
<completionHelp>
<list>permit deny</list>
</completionHelp>
diff --git a/interface-definitions/include/policy/route-rule-action.xml.i b/interface-definitions/include/policy/route-rule-action.xml.i
index 1217055f2..456a21400 100644
--- a/interface-definitions/include/policy/route-rule-action.xml.i
+++ b/interface-definitions/include/policy/route-rule-action.xml.i
@@ -1,7 +1,7 @@
<!-- include start from policy/route-rule-action.xml.i -->
<leafNode name="action">
<properties>
- <help>Rule action [REQUIRED]</help>
+ <help>Rule action</help>
<completionHelp>
<list>drop</list>
</completionHelp>
diff --git a/interface-definitions/include/rip/rip-access-list.xml.i b/interface-definitions/include/rip/access-list.xml.i
index 00ee9b736..8799aa9c3 100644
--- a/interface-definitions/include/rip/rip-access-list.xml.i
+++ b/interface-definitions/include/rip/access-list.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-access-list.xml.i -->
+<!-- include start from rip/access-list.xml.i -->
<node name="access-list">
<properties>
<help>Access-list</help>
diff --git a/interface-definitions/include/rip/rip-access-list6.xml.i b/interface-definitions/include/rip/access-list6.xml.i
index 9e4298bc0..732135253 100644
--- a/interface-definitions/include/rip/rip-access-list6.xml.i
+++ b/interface-definitions/include/rip/access-list6.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-access-list.xml.i -->
+<!-- include start from rip/access-list.xml.i -->
<node name="access-list">
<properties>
<help>Access-list</help>
diff --git a/interface-definitions/include/rip/rip-default-information.xml.i b/interface-definitions/include/rip/default-information.xml.i
index 28c540c26..957fb3a8d 100644
--- a/interface-definitions/include/rip/rip-default-information.xml.i
+++ b/interface-definitions/include/rip/default-information.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-default-information.xml.i -->
+<!-- include start from rip/default-information.xml.i -->
<node name="default-information">
<properties>
<help>Control distribution of default route</help>
diff --git a/interface-definitions/include/rip/rip-default-metric.xml.i b/interface-definitions/include/rip/default-metric.xml.i
index 297af5af8..c0f1f9b61 100644
--- a/interface-definitions/include/rip/rip-default-metric.xml.i
+++ b/interface-definitions/include/rip/default-metric.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-default-metric.xml.i -->
+<!-- include start from rip/default-metric.xml.i -->
<leafNode name="default-metric">
<properties>
<help>Metric of redistributed routes</help>
diff --git a/interface-definitions/include/rip/rip-interface.xml.i b/interface-definitions/include/rip/interface.xml.i
index dd3bddd4f..baeceac1c 100644
--- a/interface-definitions/include/rip/rip-interface.xml.i
+++ b/interface-definitions/include/rip/interface.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-interface.xml.i -->
+<!-- include start from rip/interface.xml.i -->
<tagNode name="interface">
<properties>
<help>Interface name</help>
diff --git a/interface-definitions/include/rip/rip-prefix-list.xml.i b/interface-definitions/include/rip/prefix-list.xml.i
index 2569a2a09..8e806aa35 100644
--- a/interface-definitions/include/rip/rip-prefix-list.xml.i
+++ b/interface-definitions/include/rip/prefix-list.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-prefix-list.xml.i -->
+<!-- include start from rip/prefix-list.xml.i -->
<node name="prefix-list">
<properties>
<help>Prefix-list</help>
diff --git a/interface-definitions/include/rip/rip-prefix-list6.xml.i b/interface-definitions/include/rip/prefix-list6.xml.i
index fcf1499e0..84b6846fe 100644
--- a/interface-definitions/include/rip/rip-prefix-list6.xml.i
+++ b/interface-definitions/include/rip/prefix-list6.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-prefix-list.xml.i -->
+<!-- include start from rip/prefix-list.xml.i -->
<node name="prefix-list">
<properties>
<help>Prefix-list</help>
diff --git a/interface-definitions/include/rip/rip-redistribute.xml.i b/interface-definitions/include/rip/redistribute.xml.i
index d7a79b007..34154a526 100644
--- a/interface-definitions/include/rip/rip-redistribute.xml.i
+++ b/interface-definitions/include/rip/redistribute.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-redistribute.xml.i -->
+<!-- include start from rip/redistribute.xml.i -->
<leafNode name="metric">
<properties>
<help>Metric for redistributed routes</help>
diff --git a/interface-definitions/include/rip/rip-timers.xml.i b/interface-definitions/include/rip/timers.xml.i
index 129d9ed23..771a6700e 100644
--- a/interface-definitions/include/rip/rip-timers.xml.i
+++ b/interface-definitions/include/rip/timers.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-timers.xml.i -->
+<!-- include start from rip/timers.xml.i -->
<node name="timers">
<properties>
<help>RIPng timer values</help>
diff --git a/interface-definitions/include/rip/version.xml.i b/interface-definitions/include/rip/version.xml.i
new file mode 100644
index 000000000..a35350aee
--- /dev/null
+++ b/interface-definitions/include/rip/version.xml.i
@@ -0,0 +1,18 @@
+<!-- include start from rip/version.xml.i -->
+<leafNode name="version">
+ <properties>
+ <help>Limit RIP protocol version</help>
+ <valueHelp>
+ <format>1</format>
+ <description>Allow RIPv1 only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>2</format>
+ <description>Allow RIPv2 only</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/monitoring-version.xml.i b/interface-definitions/include/version/monitoring-version.xml.i
new file mode 100644
index 000000000..6a275a5d8
--- /dev/null
+++ b/interface-definitions/include/version/monitoring-version.xml.i
@@ -0,0 +1,3 @@
+<!-- include start from include/version/monitoring-version.xml.i -->
+<syntaxVersion component='monitoring' version='1'></syntaxVersion>
+<!-- include end -->
diff --git a/interface-definitions/include/version/policy-version.xml.i b/interface-definitions/include/version/policy-version.xml.i
index 6d0c80518..426173a19 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='2'></syntaxVersion>
+<syntaxVersion component='policy' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/system-version.xml.i b/interface-definitions/include/version/system-version.xml.i
index 3cf92001c..b7650c782 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='24'></syntaxVersion>
+<syntaxVersion component='system' version='25'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in
index 96dede723..8b6c6ef62 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -94,6 +94,23 @@
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/mac.xml.i>
+ <leafNode name="mii-mon-interval">
+ <properties>
+ <help>Specifies the MII link monitoring frequency in milliseconds</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>Disable MII link monitoring</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:50-1000</format>
+ <description>MII link monitoring frequency in milliseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-0 --range 50-1000"/>
+ </constraint>
+ </properties>
+ <defaultValue>100</defaultValue>
+ </leafNode>
<leafNode name="min-links">
<properties>
<help>Minimum number of member interfaces required up before enabling bond</help>
diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in
index 60edf3ce2..48ee1efbc 100644
--- a/interface-definitions/interfaces-bridge.xml.in
+++ b/interface-definitions/interfaces-bridge.xml.in
@@ -73,12 +73,18 @@
</leafNode>
<node name="igmp">
<properties>
- <help>Internet Group Management Protocol (IGMP) settings</help>
+ <help>Internet Group Management Protocol (IGMP) and Multicast Listener Discovery (MLD) settings</help>
</properties>
<children>
<leafNode name="querier">
<properties>
- <help>Enable IGMP querier</help>
+ <help>Enable IGMP/MLD querier</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="snooping">
+ <properties>
+ <help>Enable IGMP/MLD snooping</help>
<valueless/>
</properties>
</leafNode>
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index edcf7b37f..6cbd91ff4 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -167,6 +167,7 @@
</leafNode>
</children>
</node>
+ #include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/mirror.xml.i>
<leafNode name="hash">
@@ -304,10 +305,7 @@
</leafNode>
<leafNode name="openvpn-option">
<properties>
- <help>Additional OpenVPN options. You must
- use the syntax of openvpn.conf in this text-field. Using this
- without proper knowledge may result in a crashed OpenVPN server.
- Check system log to look for errors.</help>
+ <help>Additional OpenVPN options. You must use the syntax of openvpn.conf in this text-field. Using this without proper knowledge may result in a crashed OpenVPN server. Check system log to look for errors.</help>
<multi/>
</properties>
</leafNode>
@@ -501,10 +499,7 @@
</leafNode>
<leafNode name="subnet-mask">
<properties>
- <help>Subnet mask pushed to dynamic clients.
- If not set the server subnet mask will be used.
- Only used with topology subnet or device type tap.
- Not used with bridged interfaces.</help>
+ <help>Subnet mask pushed to dynamic clients. If not set the server subnet mask will be used. Only used with topology subnet or device type tap. Not used with bridged interfaces.</help>
<constraint>
<validator name="ipv4-address"/>
</constraint>
@@ -746,7 +741,7 @@
</properties>
</leafNode>
#include <include/pki/certificate.xml.i>
- #include <include/pki/ca-certificate.xml.i>
+ #include <include/pki/ca-certificate-multi.xml.i>
<leafNode name="dh-params">
<properties>
<help>Diffie Hellman parameters (server only)</help>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 664914baa..9674cfc0e 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -4,7 +4,7 @@
<children>
<tagNode name="pppoe" owner="${vyos_conf_scripts_dir}/interfaces-pppoe.py">
<properties>
- <help>Point-to-Point Protocol over Ethernet (PPPoE)</help>
+ <help>Point-to-Point Protocol over Ethernet (PPPoE) Interface</help>
<priority>322</priority>
<constraint>
<regex>pppoe[0-9]+</regex>
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index 6b62f4c61..53e6445fa 100644
--- a/interface-definitions/interfaces-pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in
@@ -4,7 +4,7 @@
<children>
<tagNode name="pseudo-ethernet" owner="${vyos_conf_scripts_dir}/interfaces-pseudo-ethernet.py">
<properties>
- <help>Pseudo Ethernet</help>
+ <help>Pseudo Ethernet Interface (Macvlan)</help>
<priority>321</priority>
<constraint>
<regex>peth[0-9]+</regex>
diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in
index b471c3b92..aa83a04b2 100644
--- a/interface-definitions/interfaces-vti.xml.in
+++ b/interface-definitions/interfaces-vti.xml.in
@@ -4,7 +4,7 @@
<children>
<tagNode name="vti" owner="${vyos_conf_scripts_dir}/interfaces-vti.py">
<properties>
- <help>Virtual Tunnel interface</help>
+ <help>Virtual Tunnel Interface (XFRM)</help>
<priority>381</priority>
<constraint>
<regex>vti[0-9]+</regex>
@@ -16,19 +16,7 @@
</valueHelp>
</properties>
<children>
- <leafNode name="address">
- <properties>
- <help>IP address</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>IPv4 address and prefix length</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-host"/>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
+ #include <include/interface/address-ipv4-ipv6.xml.i>
#include <include/interface/description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/ipv4-options.xml.i>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index eb6107303..daee770a9 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -716,9 +716,7 @@
</leafNode>
<leafNode name="passphrase">
<properties>
- <help>WPA personal shared pass phrase. If you are
- using special characters in the WPA passphrase then single
- quotes are required.</help>
+ <help>WPA personal shared pass phrase. If you are using special characters in the WPA passphrase then single quotes are required.</help>
<valueHelp>
<format>txt</format>
<description>Passphrase of at least 8 but not more than 63 printable characters</description>
diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in
index a518a9def..85636a50f 100644
--- a/interface-definitions/ntp.xml.in
+++ b/interface-definitions/ntp.xml.in
@@ -81,6 +81,7 @@
</leafNode>
</children>
</node>
+ #include <include/generic-interface-multi.xml.i>
#include <include/listen-address.xml.i>
#include <include/interface/vrf.xml.i>
</children>
diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in
index 6d137c2ce..c4fde2c78 100644
--- a/interface-definitions/pki.xml.in
+++ b/interface-definitions/pki.xml.in
@@ -3,6 +3,7 @@
<node name="pki" owner="${vyos_conf_scripts_dir}/pki.py">
<properties>
<help>VyOS PKI configuration</help>
+ <priority>300</priority>
</properties>
<children>
<tagNode name="ca">
diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in
index 573a7963f..d969613b1 100644
--- a/interface-definitions/policy-local-route.xml.in
+++ b/interface-definitions/policy-local-route.xml.in
@@ -146,11 +146,11 @@
<properties>
<help>Source address or prefix</help>
<valueHelp>
- <format>ipv4</format>
+ <format>ipv6</format>
<description>Address to match against</description>
</valueHelp>
<valueHelp>
- <format>ipv4net</format>
+ <format>ipv6net</format>
<description>Prefix to match against</description>
</valueHelp>
<constraint>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 1d5d7dd55..0d0ada591 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -637,6 +637,18 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="prefix-len">
+ <properties>
+ <help>IP prefix-length to match</help>
+ <valueHelp>
+ <format>u32:0-32</format>
+ <description>Prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-32"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<!-- T3304 but it overwrite node nexthop
@@ -655,12 +667,20 @@
<node name="nexthop">
<properties>
<help>IP next-hop of route to match</help>
- <valueHelp>
- <format>ipv4</format>
- <description>Next-hop IPv4 router address</description>
- </valueHelp>
</properties>
<children>
+ <leafNode name="address">
+ <properties>
+ <help>IP address to match</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Nexthop IP address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="access-list">
<properties>
<help>IP access-list to match</help>
@@ -682,6 +702,18 @@
</valueHelp>
</properties>
</leafNode>
+ <leafNode name="prefix-len">
+ <properties>
+ <help>IP prefix-length to match</help>
+ <valueHelp>
+ <format>u32:0-32</format>
+ <description>Prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-32"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="prefix-list">
<properties>
<help>IP prefix-list to match</help>
@@ -690,11 +722,26 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Match type</help>
+ <completionHelp>
+ <list>blackhole</list>
+ </completionHelp>
+ <valueHelp>
+ <format>blackhole</format>
+ <description>Blackhole</description>
+ </valueHelp>
+ <constraint>
+ <regex>(blackhole)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<node name="route-source">
<properties>
- <help>test</help>
+ <help>Match advertising source address of route</help>
</properties>
<children>
<leafNode name="access-list">
@@ -760,8 +807,21 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="prefix-len">
+ <properties>
+ <help>IPv6 prefix-length to match</help>
+ <valueHelp>
+ <format>u32:0-128</format>
+ <description>Prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-128"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
+ <!-- T3976 but it overwrite node nexthop
<leafNode name="nexthop">
<properties>
<help>IPv6 next-hop of route to match</help>
@@ -775,6 +835,62 @@
</properties>
</leafNode>
</children>
+ </node> -->
+ <node name="nexthop">
+ <properties>
+ <help>IPv6 next-hop of route to match</help>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IPv6 address of next-hop</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Nexthop IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="access-list">
+ <properties>
+ <help>IPv6 access-list to match</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>IPV6 access list name</description>
+ </valueHelp>
+ <completionHelp>
+ <path>policy access-list6</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="prefix-list">
+ <properties>
+ <help>IPv6 prefix-list to match</help>
+ <completionHelp>
+ <path>policy prefix-list6</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Match type</help>
+ <completionHelp>
+ <list>blackhole</list>
+ </completionHelp>
+ <valueHelp>
+ <format>blackhole</format>
+ <description>Blackhole</description>
+ </valueHelp>
+ <constraint>
+ <regex>(blackhole)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
</node>
<node name="large-community">
<properties>
@@ -845,8 +961,13 @@
<format>ipv4</format>
<description>Peer IP address</description>
</valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Peer IPv6 address</description>
+ </valueHelp>
<constraint>
<validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
</constraint>
</properties>
</leafNode>
@@ -1070,6 +1191,44 @@
</constraint>
</properties>
</leafNode>
+ <node name="evpn">
+ <properties>
+ <help>Ethernet Virtual Private Network</help>
+ </properties>
+ <children>
+ <node name="gateway">
+ <properties>
+ <help>Set gateway IP for prefix advertisement route</help>
+ </properties>
+ <children>
+ <leafNode name="ipv4">
+ <properties>
+ <help>Set gateway IPv4 address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Gateway IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6">
+ <properties>
+ <help>Set gateway IPv6 address</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Gateway IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="extcommunity">
<properties>
<help>BGP extended community attribute</help>
@@ -1257,6 +1416,7 @@
<description>Metric value</description>
</valueHelp>
<constraint>
+ <validator name="numeric" argument="--relative --"/>
<validator name="numeric" argument="--range 0-4294967295"/>
</constraint>
</properties>
diff --git a/interface-definitions/protocols-eigrp.xml.in b/interface-definitions/protocols-eigrp.xml.in
new file mode 100644
index 000000000..88a881a1e
--- /dev/null
+++ b/interface-definitions/protocols-eigrp.xml.in
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!-- Enhanced Interior Gateway Routing Protocol (EIGRP) configuration -->
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="eigrp" owner="${vyos_conf_scripts_dir}/protocols_eigrp.py">
+ <properties>
+ <help>Enhanced Interior Gateway Routing Protocol (EIGRP)</help>
+ <priority>820</priority>
+ </properties>
+ <children>
+ #include <include/eigrp/protocol-common-config.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in
index be8e30c18..43ca659e9 100644
--- a/interface-definitions/protocols-mpls.xml.in
+++ b/interface-definitions/protocols-mpls.xml.in
@@ -6,7 +6,7 @@
<node name="mpls" owner="${vyos_conf_scripts_dir}/protocols_mpls.py">
<properties>
<help>Multiprotocol Label Switching (MPLS)</help>
- <priority>299</priority>
+ <priority>400</priority>
</properties>
<children>
<node name="ldp">
diff --git a/interface-definitions/protocols-nhrp.xml.in b/interface-definitions/protocols-nhrp.xml.in
index 7de3704ce..d7663c095 100644
--- a/interface-definitions/protocols-nhrp.xml.in
+++ b/interface-definitions/protocols-nhrp.xml.in
@@ -4,13 +4,13 @@
<children>
<node name="nhrp" owner="${vyos_conf_scripts_dir}/protocols_nhrp.py">
<properties>
- <help>NHRP parameters</help>
+ <help>Next Hop Resolution Protocol (NHRP) parameters</help>
<priority>680</priority>
</properties>
<children>
<tagNode name="tunnel">
<properties>
- <help>Tunnel for NHRP [REQUIRED]</help>
+ <help>Tunnel for NHRP</help>
<constraint>
<regex>tun[0-9]+</regex>
</constraint>
@@ -27,6 +27,10 @@
<format>txt</format>
<description>Pass phrase for cisco authentication</description>
</valueHelp>
+ <constraint>
+ <regex>[^[:space:]]{1,8}</regex>
+ </constraint>
+ <constraintErrorMessage>Password should contain up to eight non-whitespace characters</constraintErrorMessage>
</properties>
</leafNode>
<tagNode name="dynamic-map">
@@ -40,7 +44,7 @@
<children>
<leafNode name="nbma-domain-name">
<properties>
- <help>Set HUB fqdn (nbma-address - fqdn) [REQUIRED]</help>
+ <help>Set HUB fqdn (nbma-address - fqdn)</help>
<valueHelp>
<format>&lt;fqdn&gt;</format>
<description>Set the external HUB fqdn</description>
@@ -67,7 +71,7 @@
</leafNode>
<leafNode name="nbma-address">
<properties>
- <help>Set HUB address (nbma-address - external hub address or fqdn) [REQUIRED]</help>
+ <help>Set HUB address (nbma-address - external hub address or fqdn)</help>
</properties>
</leafNode>
<leafNode name="register">
diff --git a/interface-definitions/protocols-rip.xml.in b/interface-definitions/protocols-rip.xml.in
index bbb88aef1..2195b0316 100644
--- a/interface-definitions/protocols-rip.xml.in
+++ b/interface-definitions/protocols-rip.xml.in
@@ -20,14 +20,14 @@
</constraint>
</properties>
</leafNode>
- #include <include/rip/rip-default-information.xml.i>
- #include <include/rip/rip-default-metric.xml.i>
+ #include <include/rip/default-information.xml.i>
+ #include <include/rip/default-metric.xml.i>
<node name="distribute-list">
<properties>
<help>Filter networks in routing updates</help>
</properties>
<children>
- #include <include/rip/rip-access-list.xml.i>
+ #include <include/rip/access-list.xml.i>
<tagNode name="interface">
<properties>
<help>Apply filtering to an interface</help>
@@ -43,14 +43,14 @@
</constraint>
</properties>
<children>
- #include <include/rip/rip-access-list.xml.i>
- #include <include/rip/rip-prefix-list.xml.i>
+ #include <include/rip/access-list.xml.i>
+ #include <include/rip/prefix-list.xml.i>
</children>
</tagNode>
- #include <include/rip/rip-prefix-list.xml.i>
+ #include <include/rip/prefix-list.xml.i>
</children>
</node>
- #include <include/rip/rip-interface.xml.i>
+ #include <include/rip/interface.xml.i>
<tagNode name="interface">
<children>
<node name="authentication">
@@ -98,6 +98,22 @@
<constraintErrorMessage>Password must be 16 characters or less</constraintErrorMessage>
</properties>
</leafNode>
+ </children>
+ </node>
+ <node name="receive">
+ <properties>
+ <help>Advertisement reception</help>
+ </properties>
+ <children>
+ #include <include/rip/version.xml.i>
+ </children>
+ </node>
+ <node name="send">
+ <properties>
+ <help>Advertisement transmission</help>
+ </properties>
+ <children>
+ #include <include/rip/version.xml.i>
</children>
</node>
</children>
@@ -166,7 +182,7 @@
<help>Redistribute BGP routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="connected">
@@ -174,7 +190,7 @@
<help>Redistribute connected routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="isis">
@@ -182,7 +198,7 @@
<help>Redistribute IS-IS routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="kernel">
@@ -190,7 +206,7 @@
<help>Redistribute kernel routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="ospf">
@@ -198,7 +214,7 @@
<help>Redistribute OSPF routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="static">
@@ -206,7 +222,7 @@
<help>Redistribute static routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
</children>
@@ -224,10 +240,12 @@
<multi/>
</properties>
</leafNode>
- #include <include/rip/rip-timers.xml.i>
+ #include <include/rip/timers.xml.i>
#include <include/route-map.xml.i>
+ #include <include/rip/version.xml.i>
</children>
</node>
</children>
</node>
</interfaceDefinition>
+
diff --git a/interface-definitions/protocols-ripng.xml.in b/interface-definitions/protocols-ripng.xml.in
index fe7411e65..d7e4b2514 100644
--- a/interface-definitions/protocols-ripng.xml.in
+++ b/interface-definitions/protocols-ripng.xml.in
@@ -21,14 +21,14 @@
<multi/>
</properties>
</leafNode>
- #include <include/rip/rip-default-information.xml.i>
- #include <include/rip/rip-default-metric.xml.i>
+ #include <include/rip/default-information.xml.i>
+ #include <include/rip/default-metric.xml.i>
<node name="distribute-list">
<properties>
<help>Filter networks in routing updates</help>
</properties>
<children>
- #include <include/rip/rip-access-list6.xml.i>
+ #include <include/rip/access-list6.xml.i>
<tagNode name="interface">
<properties>
<help>Apply filtering to an interface</help>
@@ -44,14 +44,14 @@
</constraint>
</properties>
<children>
- #include <include/rip/rip-access-list6.xml.i>
- #include <include/rip/rip-prefix-list6.xml.i>
+ #include <include/rip/access-list6.xml.i>
+ #include <include/rip/prefix-list6.xml.i>
</children>
</tagNode>
- #include <include/rip/rip-prefix-list6.xml.i>
+ #include <include/rip/prefix-list6.xml.i>
</children>
</node>
- #include <include/rip/rip-interface.xml.i>
+ #include <include/rip/interface.xml.i>
<leafNode name="network">
<properties>
<help>RIPng network</help>
@@ -88,7 +88,7 @@
<help>Redistribute BGP routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="connected">
@@ -96,7 +96,7 @@
<help>Redistribute connected routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="kernel">
@@ -104,7 +104,7 @@
<help>Redistribute kernel routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="ospfv3">
@@ -112,7 +112,7 @@
<help>Redistribute OSPFv3 routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="static">
@@ -120,7 +120,7 @@
<help>Redistribute static routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
</children>
@@ -139,7 +139,7 @@
</properties>
</leafNode>
#include <include/route-map.xml.i>
- #include <include/rip/rip-timers.xml.i>
+ #include <include/rip/timers.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/protocols-static.xml.in b/interface-definitions/protocols-static.xml.in
index 3cc28e296..e89433022 100644
--- a/interface-definitions/protocols-static.xml.in
+++ b/interface-definitions/protocols-static.xml.in
@@ -7,7 +7,7 @@
<children>
<node name="static" owner="${vyos_conf_scripts_dir}/protocols_static.py">
<properties>
- <help>Static route parameters</help>
+ <help>Static Routing</help>
<priority>480</priority>
</properties>
<children>
diff --git a/interface-definitions/service_conntrack-sync.xml.in b/interface-definitions/service-conntrack-sync.xml.in
index 32efa7323..6fa6fc5f9 100644
--- a/interface-definitions/service_conntrack-sync.xml.in
+++ b/interface-definitions/service-conntrack-sync.xml.in
@@ -5,7 +5,8 @@
<node name="conntrack-sync" owner="${vyos_conf_scripts_dir}/conntrack_sync.py">
<properties>
<help>Connection tracking synchronization</help>
- <priority>995</priority>
+ <!-- before VRRP / HA -->
+ <priority>799</priority>
</properties>
<children>
<leafNode name="accept-protocol">
diff --git a/interface-definitions/service_console-server.xml.in b/interface-definitions/service-console-server.xml.in
index e9591ad87..e9591ad87 100644
--- a/interface-definitions/service_console-server.xml.in
+++ b/interface-definitions/service-console-server.xml.in
diff --git a/interface-definitions/service-event-handler.xml.in b/interface-definitions/service-event-handler.xml.in
new file mode 100644
index 000000000..aef6bc1bc
--- /dev/null
+++ b/interface-definitions/service-event-handler.xml.in
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="event-handler" owner="${vyos_conf_scripts_dir}/service_event_handler.py">
+ <properties>
+ <help>Service event handler</help>
+ </properties>
+ <children>
+ <tagNode name="event">
+ <properties>
+ <help>Event handler name</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Logs filter settings</help>
+ </properties>
+ <children>
+ <leafNode name="pattern">
+ <properties>
+ <help>Match pattern (regex)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="syslog-identifier">
+ <properties>
+ <help>Identifier of a process in syslog (string)</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="script">
+ <properties>
+ <help>Event handler script file</help>
+ </properties>
+ <children>
+ <leafNode name="arguments">
+ <properties>
+ <help>Script arguments</help>
+ </properties>
+ </leafNode>
+ <tagNode name="environment">
+ <properties>
+ <help>Script environment arguments</help>
+ </properties>
+ <children>
+ <leafNode name="value">
+ <properties>
+ <help>Environment value</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="path">
+ <properties>
+ <help>Path to the script</help>
+ <constraint>
+ <validator name="script"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in
index e222467b1..cd3aa3638 100644
--- a/interface-definitions/service_ipoe-server.xml.in
+++ b/interface-definitions/service-ipoe-server.xml.in
@@ -213,6 +213,11 @@
</tagNode>
</children>
</tagNode>
+ <node name="radius">
+ <children>
+ #include <include/accel-ppp/radius-additions-rate-limit.xml.i>
+ </children>
+ </node>
#include <include/radius-server-ipv4.xml.i>
#include <include/accel-ppp/radius-additions.xml.i>
</children>
diff --git a/interface-definitions/service_mdns-repeater.xml.in b/interface-definitions/service-mdns-repeater.xml.in
index 9a94f1488..9a94f1488 100644
--- a/interface-definitions/service_mdns-repeater.xml.in
+++ b/interface-definitions/service-mdns-repeater.xml.in
diff --git a/interface-definitions/service_monitoring_telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in
index ff4c8c55f..36f40a539 100644
--- a/interface-definitions/service_monitoring_telegraf.xml.in
+++ b/interface-definitions/service-monitoring-telegraf.xml.in
@@ -13,41 +13,141 @@
<help>Telegraf monitoring</help>
</properties>
<children>
- <node name="authentication">
+ <node name="influxdb">
<properties>
- <help>Authentication parameters</help>
+ <help>Output plugin InfluxDB</help>
</properties>
<children>
- <leafNode name="organization">
+ <node name="authentication">
+ <properties>
+ <help>Authentication parameters</help>
+ </properties>
+ <children>
+ <leafNode name="organization">
+ <properties>
+ <help>Authentication organization for InfluxDB v2</help>
+ <constraint>
+ <regex>[a-zA-Z][1-9a-zA-Z@_\-.]{2,50}</regex>
+ </constraint>
+ <constraintErrorMessage>Organization name must be alphanumeric and can contain hyphens, underscores and at symbol.</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="token">
+ <properties>
+ <help>Authentication token for InfluxDB v2</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Authentication token</description>
+ </valueHelp>
+ <constraint>
+ <regex>[a-zA-Z0-9-_]{86}==</regex>
+ </constraint>
+ <constraintErrorMessage>Token must be 88 characters long and must contain only [a-zA-Z0-9-_] and '==' characters.</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="bucket">
+ <properties>
+ <help>Remote bucket</help>
+ </properties>
+ <defaultValue>main</defaultValue>
+ </leafNode>
+ #include <include/monitoring/url.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>8086</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <node name="azure-data-explorer">
+ <properties>
+ <help>Output plugin Azure Data Explorer</help>
+ </properties>
+ <children>
+ <node name="authentication">
<properties>
- <help>Authentication organization for InfluxDB v2 [REQUIRED]</help>
+ <help>Authentication parameters</help>
+ </properties>
+ <children>
+ <leafNode name="client-id">
+ <properties>
+ <help>Application client id</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Client-id is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="client-secret">
+ <properties>
+ <help>Application client secret</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Client-secret is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="tenant-id">
+ <properties>
+ <help>Set tenant id</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Tenant-id is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="database">
+ <properties>
+ <help>Remote database name</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Remote database name</description>
+ </valueHelp>
<constraint>
- <regex>[a-zA-Z][1-9a-zA-Z@_\-.]{2,50}</regex>
+ <regex>[-_a-zA-Z0-9]+</regex>
</constraint>
- <constraintErrorMessage>Organization name must be alphanumeric and can contain hyphens, underscores and at symbol.</constraintErrorMessage>
+ <constraintErrorMessage>Database is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="token">
+ <leafNode name="group-metrics">
<properties>
- <help>Authentication token for InfluxDB v2 [REQUIRED]</help>
+ <help>Type of metrics grouping when push to Azure Data Explorer</help>
+ <completionHelp>
+ <list>single-table table-per-metric</list>
+ </completionHelp>
+ <valueHelp>
+ <format>single-table</format>
+ <description>Metrics stores in one table</description>
+ </valueHelp>
+ <valueHelp>
+ <format>table-per-metric</format>
+ <description>One table per gorups of metric by the metric name</description>
+ </valueHelp>
+ <constraint>
+ <regex>(single-table|table-per-metric)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>table-per-metric</defaultValue>
+ </leafNode>
+ <leafNode name="table">
+ <properties>
+ <help>Name of the single table [Only if set group-metrics single-table]</help>
<valueHelp>
<format>txt</format>
- <description>Authentication token</description>
+ <description>Table name</description>
</valueHelp>
<constraint>
- <regex>[a-zA-Z0-9-_]{86}==</regex>
+ <regex>[-_a-zA-Z0-9]+</regex>
</constraint>
- <constraintErrorMessage>Token must be 88 characters long and must contain only [a-zA-Z0-9-_] and '==' characters.</constraintErrorMessage>
+ <constraintErrorMessage>Table is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage>
</properties>
</leafNode>
+ #include <include/monitoring/url.xml.i>
</children>
</node>
- <leafNode name="bucket">
- <properties>
- <help>Remote bucket</help>
- </properties>
- <defaultValue>main</defaultValue>
- </leafNode>
<leafNode name="source">
<properties>
<help>Source parameters for monitoring</help>
@@ -193,7 +293,7 @@
</node>
<leafNode name="url">
<properties>
- <help>Remote URL [REQUIRED]</help>
+ <help>Remote URL</help>
<valueHelp>
<format>url</format>
<description>Remote URL to Splunk collector</description>
@@ -206,23 +306,6 @@
</leafNode>
</children>
</node>
- <leafNode name="url">
- <properties>
- <help>Remote URL [REQUIRED]</help>
- <valueHelp>
- <format>url</format>
- <description>Remote URL to InfluxDB v2</description>
- </valueHelp>
- <constraint>
- <regex>(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}?(\/.*)?</regex>
- </constraint>
- <constraintErrorMessage>Incorrect URL format.</constraintErrorMessage>
- </properties>
- </leafNode>
- #include <include/port-number.xml.i>
- <leafNode name="port">
- <defaultValue>8086</defaultValue>
- </leafNode>
</children>
</node>
</children>
diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in
index 50f42849b..50f42849b 100644
--- a/interface-definitions/service_pppoe-server.xml.in
+++ b/interface-definitions/service-pppoe-server.xml.in
diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service-router-advert.xml.in
index bb11e9cd0..258b7b749 100644
--- a/interface-definitions/service_router-advert.xml.in
+++ b/interface-definitions/service-router-advert.xml.in
@@ -10,7 +10,7 @@
<children>
<tagNode name="interface">
<properties>
- <help>Interface to send RA on [REQUIRED]</help>
+ <help>Interface to send RA on</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
@@ -136,6 +136,23 @@
</children>
</node>
#include <include/name-server-ipv6.xml.i>
+ <leafNode name="name-server-lifetime">
+ <properties>
+ <help>Maximum duration how long the RDNSS entries are used</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>Name-servers should no longer be used</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1-7200</format>
+ <description>Maximum interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-7200"/>
+ </constraint>
+ <constraintErrorMessage>Maximum interval must be between 1 and 7200 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
<leafNode name="other-config-flag">
<properties>
<help>Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information</help>
diff --git a/interface-definitions/service-sla.xml.in b/interface-definitions/service-sla.xml.in
new file mode 100644
index 000000000..0c4f8a591
--- /dev/null
+++ b/interface-definitions/service-sla.xml.in
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="sla" owner="${vyos_conf_scripts_dir}/service_sla.py">
+ <properties>
+ <help>Service level agreement (SLA)</help>
+ </properties>
+ <children>
+ <node name="owamp-server">
+ <properties>
+ <help>One-way active measurement protocol (OWAMP) server</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>861</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <node name="twamp-server">
+ <properties>
+ <help>Two-way active measurement protocol (TWAMP) server</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>862</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/service_upnp.xml.in b/interface-definitions/service-upnp.xml.in
index a129b7260..a129b7260 100644
--- a/interface-definitions/service_upnp.xml.in
+++ b/interface-definitions/service-upnp.xml.in
diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service-webproxy.xml.in
index 9a75bc27d..e4609b699 100644
--- a/interface-definitions/service_webproxy.xml.in
+++ b/interface-definitions/service-webproxy.xml.in
@@ -288,7 +288,7 @@
</leafNode>
<tagNode name="listen-address">
<properties>
- <help>IPv4 listen-address for WebProxy [REQUIRED]</help>
+ <help>IPv4 listen-address for WebProxy</help>
<completionHelp>
<script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script>
</completionHelp>
@@ -452,7 +452,7 @@
</leafNode>
<leafNode name="source-group">
<properties>
- <help>Source-group for this rule [REQUIRED]</help>
+ <help>Source-group for this rule</help>
<valueHelp>
<format>group</format>
<description>Source group identifier for this rule</description>
@@ -484,7 +484,7 @@
<description>Name of source group</description>
</valueHelp>
<constraint>
- <regex>[^0-9]</regex>
+ <regex>[^0-9][a-zA-Z_][a-zA-Z0-9][\w\-\.]*</regex>
</constraint>
<constraintErrorMessage>URL-filter source-group cannot start with a number!</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in
index 8edbad110..126183162 100644
--- a/interface-definitions/ssh.xml.in
+++ b/interface-definitions/ssh.xml.in
@@ -61,6 +61,78 @@
<valueless/>
</properties>
</leafNode>
+ <node name="dynamic-protection">
+ <properties>
+ <help>Allow dynamic protection</help>
+ </properties>
+ <children>
+ <leafNode name="block-time">
+ <properties>
+ <help>Block source IP in seconds. Subsequent blocks increase by a factor of 1.5</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Time interval in seconds for blocking</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>120</defaultValue>
+ </leafNode>
+ <leafNode name="detect-time">
+ <properties>
+ <help>Remember source IP in seconds before reset their score</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Time interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>1800</defaultValue>
+ </leafNode>
+ <leafNode name="threshold">
+ <properties>
+ <help>Block source IP when their cumulative attack score exceeds threshold</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Threshold score</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>30</defaultValue>
+ </leafNode>
+ <leafNode name="allow-from">
+ <properties>
+ <help>Always allow inbound connections from these systems</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 address and prefix length</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address to match against</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ <validator name="ip-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="key-exchange">
<properties>
<help>Allowed key exchange (KEX) algorithms</help>
diff --git a/interface-definitions/intel_qat.xml.in b/interface-definitions/system-acceleration-qat.xml.in
index 812484184..812484184 100644
--- a/interface-definitions/intel_qat.xml.in
+++ b/interface-definitions/system-acceleration-qat.xml.in
diff --git a/interface-definitions/system-frr.xml.in b/interface-definitions/system-frr.xml.in
new file mode 100644
index 000000000..9fe23ed75
--- /dev/null
+++ b/interface-definitions/system-frr.xml.in
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="system">
+ <children>
+ <node name="frr" owner="${vyos_conf_scripts_dir}/system_frr.py">
+ <properties>
+ <help>Configure FRR parameters</help>
+ <!-- Before components that use FRR -->
+ <priority>150</priority>
+ </properties>
+ <children>
+ <leafNode name="bmp">
+ <properties>
+ <help>Enable BGP Monitoring Protocol support</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="irdp">
+ <properties>
+ <help>Enable ICMP Router Discovery Protocol support</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="snmp">
+ <properties>
+ <help>Enable SNMP integration for next daemons</help>
+ </properties>
+ <children>
+ <leafNode name="bgpd">
+ <properties>
+ <help>BGP</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="isisd">
+ <properties>
+ <help>IS-IS</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ldpd">
+ <properties>
+ <help>LDP</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ospf6d">
+ <properties>
+ <help>OSPFv3</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ospfd">
+ <properties>
+ <help>OSPFv2</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ripd">
+ <properties>
+ <help>RIP</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="zebra">
+ <properties>
+ <help>Zebra (IP routing manager)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/system-ip.xml.in b/interface-definitions/system-ip.xml.in
index 21d70694b..e00dbf252 100644
--- a/interface-definitions/system-ip.xml.in
+++ b/interface-definitions/system-ip.xml.in
@@ -23,6 +23,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="disable-directed-broadcast">
+ <properties>
+ <help>Disable IPv4 directed broadcast forwarding on all interfaces</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<node name="multipath">
<properties>
<help>IPv4 multipath settings</help>
diff --git a/interface-definitions/system-lcd.xml.in b/interface-definitions/system-lcd.xml.in
index 9b1a15317..0cf4de308 100644
--- a/interface-definitions/system-lcd.xml.in
+++ b/interface-definitions/system-lcd.xml.in
@@ -10,7 +10,7 @@
<children>
<leafNode name="model">
<properties>
- <help>Model of the display attached to this system [REQUIRED]</help>
+ <help>Model of the display attached to this system</help>
<completionHelp>
<list>cfa-533 cfa-631 cfa-633 cfa-635 hd44780 sdec</list>
</completionHelp>
diff --git a/interface-definitions/system-syslog.xml.in b/interface-definitions/system-syslog.xml.in
index 480cb1ca6..90c3de5c1 100644
--- a/interface-definitions/system-syslog.xml.in
+++ b/interface-definitions/system-syslog.xml.in
@@ -390,31 +390,6 @@
<help>Logging to system standard location</help>
</properties>
<children>
- <node name="archive">
- <properties>
- <help>Log file size and rotation characteristics</help>
- </properties>
- <children>
- <leafNode name="file">
- <properties>
- <help>Number of saved files (default is 5)</help>
- <constraint>
- <regex>[0-9]+</regex>
- </constraint>
- <constraintErrorMessage>illegal characters in number of files</constraintErrorMessage>
- </properties>
- </leafNode>
- <leafNode name="size">
- <properties>
- <help>Size of log files (in kbytes, default is 256)</help>
- <constraint>
- <regex>[0-9]+</regex>
- </constraint>
- <constraintErrorMessage>illegal characters in size</constraintErrorMessage>
- </properties>
- </leafNode>
- </children>
- </node>
<tagNode name="facility">
<properties>
<help>Facility for logging</help>
diff --git a/interface-definitions/tftp-server.xml.in b/interface-definitions/tftp-server.xml.in
index 4963eab3c..8ca4da883 100644
--- a/interface-definitions/tftp-server.xml.in
+++ b/interface-definitions/tftp-server.xml.in
@@ -11,7 +11,7 @@
<children>
<leafNode name="directory">
<properties>
- <help>Folder containing files served by TFTP [REQUIRED]</help>
+ <help>Folder containing files served by TFTP</help>
</properties>
</leafNode>
<leafNode name="allow-upload">
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn-ipsec.xml.in
index 555ba689f..d36fbb024 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn-ipsec.xml.in
@@ -19,7 +19,7 @@
</leafNode>
<tagNode name="esp-group">
<properties>
- <help>Encapsulated Security Payload (ESP) group name</help>
+ <help>Encapsulating Security Payload (ESP) group name</help>
</properties>
<children>
<leafNode name="compression">
@@ -44,10 +44,10 @@
</leafNode>
<leafNode name="lifetime">
<properties>
- <help>ESP lifetime</help>
+ <help>Security Association time to expire</help>
<valueHelp>
<format>u32:30-86400</format>
- <description>ESP lifetime in seconds</description>
+ <description>SA lifetime in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 30-86400"/>
@@ -57,10 +57,10 @@
</leafNode>
<leafNode name="life-bytes">
<properties>
- <help>ESP life in bytes</help>
+ <help>Security Association byte count to expire</help>
<valueHelp>
<format>u32:1024-26843545600000</format>
- <description>ESP life in bytes</description>
+ <description>SA life in bytes</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1024-26843545600000"/>
@@ -69,10 +69,10 @@
</leafNode>
<leafNode name="life-packets">
<properties>
- <help>ESP life in packets</help>
+ <help>Security Association packet count to expire</help>
<valueHelp>
<format>u32:1000-26843545600000</format>
- <description>ESP life in packets</description>
+ <description>SA life in packets</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1000-26843545600000"/>
@@ -209,7 +209,7 @@
</leafNode>
<tagNode name="proposal">
<properties>
- <help>ESP group proposal [REQUIRED]</help>
+ <help>ESP group proposal</help>
<valueHelp>
<format>u32:1-65535</format>
<description>ESP group proposal number</description>
@@ -308,13 +308,13 @@
</node>
<leafNode name="ikev2-reauth">
<properties>
- <help>Re-authentication of the remote peer during an IKE re-key - IKEv2 only</help>
+ <help>Re-authentication of the remote peer during an IKE re-key (IKEv2 only)</help>
<completionHelp>
<list>yes no</list>
</completionHelp>
<valueHelp>
<format>yes</format>
- <description>Enable remote host re-authentication during an IKE rekey. Currently broken due to a strongswan bug</description>
+ <description>Enable remote host re-authentication during an IKE rekey (currently broken due to a strongswan bug)</description>
</valueHelp>
<valueHelp>
<format>no</format>
@@ -379,7 +379,7 @@
</leafNode>
<leafNode name="mode">
<properties>
- <help>IKEv1 phase 1 mode selection</help>
+ <help>IKEv1 phase 1 mode</help>
<completionHelp>
<list>main aggressive</list>
</completionHelp>
@@ -530,10 +530,10 @@
<children>
<leafNode name="level">
<properties>
- <help>strongSwan logging Level</help>
+ <help>Global IPsec logging Level</help>
<valueHelp>
<format>0</format>
- <description>Very basic auditing logs e.g. SA up/SA down</description>
+ <description>Very basic auditing logs (e.g., SA up/SA down)</description>
</valueHelp>
<valueHelp>
<format>1</format>
@@ -663,13 +663,13 @@
</node>
<tagNode name="profile">
<properties>
- <help>VPN IPSec profile</help>
+ <help>VPN IPsec profile</help>
</properties>
<children>
#include <include/generic-disable-node.xml.i>
<node name="authentication">
<properties>
- <help>Authentication [REQUIRED]</help>
+ <help>Authentication</help>
</properties>
<children>
<leafNode name="mode">
@@ -689,7 +689,7 @@
</node>
<node name="bind">
<properties>
- <help>DMVPN crypto configuration</help>
+ <help>DMVPN tunnel configuration</help>
</properties>
<children>
<leafNode name="tunnel">
@@ -951,7 +951,7 @@
#include <include/generic-disable-node.xml.i>
<node name="authentication">
<properties>
- <help>Peer authentication [REQUIRED]</help>
+ <help>Peer authentication</help>
</properties>
<children>
#include <include/ipsec/authentication-id.xml.i>
@@ -1010,7 +1010,7 @@
</valueHelp>
<valueHelp>
<format>respond</format>
- <description>Bring the connection up only if traffic is detected</description>
+ <description>Wait for the peer to initiate the connection</description>
</valueHelp>
<valueHelp>
<format>none</format>
@@ -1077,10 +1077,10 @@
#include <include/ipsec/local-address.xml.i>
<tagNode name="tunnel">
<properties>
- <help>Peer tunnel [REQUIRED]</help>
+ <help>Peer tunnel</help>
<valueHelp>
<format>u32</format>
- <description>Peer tunnel [REQUIRED]</description>
+ <description>Peer tunnel</description>
</valueHelp>
</properties>
<children>
@@ -1090,10 +1090,10 @@
#include <include/ip-protocol.xml.i>
<leafNode name="priority">
<properties>
- <help>Priority for IPSec policy (lowest value more preferable)</help>
+ <help>Priority for IPsec policy (lowest value more preferable)</help>
<valueHelp>
<format>u32:1-100</format>
- <description>Priority for IPSec policy (lowest value more preferable)</description>
+ <description>Priority for IPsec policy (lowest value more preferable)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-100"/>
@@ -1144,7 +1144,7 @@
</leafNode>
<node name="vti">
<properties>
- <help>Virtual tunnel interface [REQUIRED]</help>
+ <help>Virtual tunnel interface</help>
</properties>
<children>
<leafNode name="bind">
diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in
index f734283e7..f734283e7 100644
--- a/interface-definitions/vpn_l2tp.xml.in
+++ b/interface-definitions/vpn-l2tp.xml.in
diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in
index 7981c3fa2..21b47125d 100644
--- a/interface-definitions/vpn_openconnect.xml.in
+++ b/interface-definitions/vpn-openconnect.xml.in
@@ -51,6 +51,82 @@
</children>
</node>
#include <include/auth-local-users.xml.i>
+ <node name="local-users">
+ <children>
+ <tagNode name="username">
+ <children>
+ <node name="otp">
+ <properties>
+ <help>2FA OTP authentication parameters</help>
+ </properties>
+ <children>
+ <leafNode name="key">
+ <properties>
+ <help>Token Key Secret key for the token algorithm (see RFC 4226)</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>OTP key in hex-encoded format</description>
+ </valueHelp>
+ <constraint>
+ <regex>[a-fA-F0-9]{20,10000}</regex>
+ </constraint>
+ <constraintErrorMessage>Key name must only include hex characters and be at least 20 characters long</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="otp-length">
+ <properties>
+ <help>Number of digits in OTP code</help>
+ <valueHelp>
+ <format>u32:6-8</format>
+ <description>Number of digits in OTP code</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 6-8"/>
+ </constraint>
+ <constraintErrorMessage>Number of digits in OTP code must be between 6 and 8</constraintErrorMessage>
+ </properties>
+ <defaultValue>6</defaultValue>
+ </leafNode>
+ <leafNode name="interval">
+ <properties>
+ <help>Time tokens interval in seconds</help>
+ <valueHelp>
+ <format>u32:5-86400</format>
+ <description>Time tokens interval in seconds.</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 5-86400"/>
+ </constraint>
+ <constraintErrorMessage>Time token interval must be between 5 and 86400 seconds</constraintErrorMessage>
+ </properties>
+ <defaultValue>30</defaultValue>
+ </leafNode>
+ <leafNode name="token-type">
+ <properties>
+ <help>Token type</help>
+ <valueHelp>
+ <format>hotp-time</format>
+ <description>Time-based OTP algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hotp-event</format>
+ <description>Event-based OTP algorithm</description>
+ </valueHelp>
+ <constraint>
+ <regex>(hotp-time|hotp-event)</regex>
+ </constraint>
+ <completionHelp>
+ <list>hotp-time hotp-event</list>
+ </completionHelp>
+ </properties>
+ <defaultValue>hotp-time</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
#include <include/radius-server-ipv4.xml.i>
<node name="radius">
<children>
diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn-pptp.xml.in
index 28a53acb9..28a53acb9 100644
--- a/interface-definitions/vpn_pptp.xml.in
+++ b/interface-definitions/vpn-pptp.xml.in
diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn-sstp.xml.in
index fe2fea9f8..195d581df 100644
--- a/interface-definitions/vpn_sstp.xml.in
+++ b/interface-definitions/vpn-sstp.xml.in
@@ -37,6 +37,10 @@
</children>
</node>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>443</defaultValue>
+ </leafNode>
<node name="ppp-options">
<properties>
<help>PPP (Point-to-Point Protocol) settings</help>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
index 14c31fa8a..3604b41c8 100644
--- a/interface-definitions/vrf.xml.in
+++ b/interface-definitions/vrf.xml.in
@@ -28,6 +28,22 @@
<children>
#include <include/interface/description.xml.i>
#include <include/interface/disable.xml.i>
+ <node name="ip">
+ <properties>
+ <help>IPv4 routing parameters</help>
+ </properties>
+ <children>
+ #include <include/interface/disable-forwarding.xml.i>
+ </children>
+ </node>
+ <node name="ipv6">
+ <properties>
+ <help>IPv6 routing parameters</help>
+ </properties>
+ <children>
+ #include <include/interface/disable-forwarding.xml.i>
+ </children>
+ </node>
<node name="protocols">
<properties>
<help>Routing protocol parameters</help>
@@ -42,6 +58,15 @@
#include <include/bgp/protocol-common-config.xml.i>
</children>
</node>
+ <node name="eigrp" owner="${vyos_conf_scripts_dir}/protocols_eigrp.py $VAR(../../@)">
+ <properties>
+ <help>Enhanced Interior Gateway Routing Protocol (EIGRP)</help>
+ <priority>821</priority>
+ </properties>
+ <children>
+ #include <include/eigrp/protocol-common-config.xml.i>
+ </children>
+ </node>
<node name="isis" owner="${vyos_conf_scripts_dir}/protocols_isis.py $VAR(../../@)">
<properties>
<help>Intermediate System to Intermediate System (IS-IS)</help>
@@ -71,7 +96,7 @@
</node>
<node name="static" owner="${vyos_conf_scripts_dir}/protocols_static.py $VAR(../../@)">
<properties>
- <help>Static route parameters</help>
+ <help>Static Routing</help>
<priority>481</priority>
</properties>
<children>
diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in
index 8af0dcfb6..dca4c59d1 100644
--- a/interface-definitions/zone-policy.xml.in
+++ b/interface-definitions/zone-policy.xml.in
@@ -19,6 +19,7 @@
</properties>
<children>
#include <include/generic-description.xml.i>
+ #include <include/firewall/name-default-log.xml.i>
<leafNode name="default-action">
<properties>
<help>Default-action for traffic coming into this zone</help>
diff --git a/op-mode-definitions/clear-dhcp-server-lease.xml.in b/op-mode-definitions/clear-dhcp-server-lease.xml.in
new file mode 100644
index 000000000..b1241588c
--- /dev/null
+++ b/op-mode-definitions/clear-dhcp-server-lease.xml.in
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="clear">
+ <children>
+ <node name="dhcp-server">
+ <properties>
+ <help>clear DHCP server lease</help>
+ </properties>
+ <children>
+ <tagNode name="lease">
+ <properties>
+ <help>DHCP server lease</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/clear_dhcp_lease.py --ip $4</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/containers.xml.in b/op-mode-definitions/container.xml.in
index 48501bd84..fa66402dc 100644
--- a/op-mode-definitions/containers.xml.in
+++ b/op-mode-definitions/container.xml.in
@@ -11,7 +11,7 @@
<properties>
<help>Pull a new image for container</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/containers_op.py --pull "${4}"</command>
+ <command>sudo podman image pull "${4}"</command>
</tagNode>
</children>
</node>
@@ -44,7 +44,7 @@
<script>sudo podman image ls -q</script>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/containers_op.py --remove "${4}"</command>
+ <command>sudo podman image rm --force "${4}"</command>
</tagNode>
</children>
</node>
@@ -100,13 +100,13 @@
<properties>
<help>Show containers</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/containers_op.py --all</command>
+ <command>sudo podman ps --all</command>
<children>
<leafNode name="image">
<properties>
<help>Show container image</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/containers_op.py --image</command>
+ <command>sudo podman image ls</command>
</leafNode>
<tagNode name="log">
<properties>
@@ -121,7 +121,7 @@
<properties>
<help>Show available container networks</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/containers_op.py --networks</command>
+ <command>sudo podman network ls</command>
</leafNode>
</children>
</node>
@@ -167,7 +167,7 @@
<path>container name</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/containers_op.py --update "${4}"</command>
+ <command>if cli-shell-api existsActive container name "$4"; then sudo podman pull $(cli-shell-api returnActiveValue container name "$4" image); else echo "Container $4 does not exist"; fi</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/dns-forwarding.xml.in b/op-mode-definitions/dns-forwarding.xml.in
index 6574f2319..baf60efbd 100644
--- a/op-mode-definitions/dns-forwarding.xml.in
+++ b/op-mode-definitions/dns-forwarding.xml.in
@@ -1,5 +1,46 @@
<?xml version="1.0"?>
<interfaceDefinition>
+ <node name="monitor">
+ <children>
+ <node name="log">
+ <children>
+ <node name="dns">
+ <properties>
+ <help>Monitor last lines of Domain Name Service (DNS)</help>
+ </properties>
+ <children>
+ <node name="forwarding">
+ <properties>
+ <help>Monitor last lines of DNS forwarding</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit pdns-recursor.service</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="dns">
+ <properties>
+ <help>Show DNS information</help>
+ </properties>
+ <children>
+ <node name="forwarding">
+ <properties>
+ <help>Show DNS forwarding information</help>
+ </properties>
+ <children>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show DNS forwarding statistics</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/dns_forwarding_statistics.py</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="show">
<children>
<node name="log">
@@ -13,7 +54,7 @@
<properties>
<help>Show log for DNS Forwarding</help>
</properties>
- <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e "pdns_recursor"</command>
+ <command>journalctl --no-hostname --boot --unit pdns-recursor.service</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/force-wamp.xml.in b/op-mode-definitions/force-wamp.xml.in
new file mode 100644
index 000000000..dbb205c6b
--- /dev/null
+++ b/op-mode-definitions/force-wamp.xml.in
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="force">
+ <children>
+ <tagNode name="owping">
+ <properties>
+ <help>IP address of the remote OWAMP server</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>owping $3</command>
+ </tagNode>
+ <tagNode name="twping">
+ <properties>
+ <help>IP address of the remote TWAMP server</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>twping $3</command>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/geoip.xml.in b/op-mode-definitions/geoip.xml.in
new file mode 100644
index 000000000..c1b6e87b9
--- /dev/null
+++ b/op-mode-definitions/geoip.xml.in
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="update">
+ <children>
+ <leafNode name="geoip">
+ <properties>
+ <help>Update GeoIP database and firewall sets</help>
+ </properties>
+ <command>sudo ${vyos_libexec_dir}/geoip-update.py --force</command>
+ </leafNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/include/bgp/reset-bgp-afi-common.xml.i b/op-mode-definitions/include/bgp/reset-bgp-afi-common.xml.i
new file mode 100644
index 000000000..2f88daad3
--- /dev/null
+++ b/op-mode-definitions/include/bgp/reset-bgp-afi-common.xml.i
@@ -0,0 +1,20 @@
+<!-- included start from bgp/reset-bgp-afi-common.xml.i -->
+<node name="external">
+ <properties>
+ <help>Reset all external peers</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+</node>
+<tagNode name="1-4294967295">
+ <properties>
+ <help>Reset peers with the AS number</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+</tagNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/reset-bgp-neighbor-options.xml.i b/op-mode-definitions/include/bgp/reset-bgp-neighbor-options.xml.i
new file mode 100644
index 000000000..d9feee18a
--- /dev/null
+++ b/op-mode-definitions/include/bgp/reset-bgp-neighbor-options.xml.i
@@ -0,0 +1,48 @@
+<!-- included start from bgp/reset-bgp-neighbor-options.xml.i -->
+<node name="in">
+ <properties>
+ <help>Send route-refresh unless using 'soft-reconfiguration inbound'</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ <leafNode name="prefix-filter">
+ <properties>
+ <help>Push out prefix-list ORF and do inbound soft reconfig</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+</node>
+<leafNode name="message-stats">
+ <properties>
+ <help>Reset message statistics</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<leafNode name="out">
+ <properties>
+ <help>Resend all outbound updates</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<node name="soft">
+ <properties>
+ <help>Soft reconfig inbound and outbound updates</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ <node name="in">
+ <properties>
+ <help>Send route-refresh unless using 'soft-reconfiguration inbound'</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </node>
+ <node name="out">
+ <properties>
+ <help>Resend all outbound updates</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </node>
+ </children>
+</node>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/reset-bgp-peer-group-vrf.xml.i b/op-mode-definitions/include/bgp/reset-bgp-peer-group-vrf.xml.i
new file mode 100644
index 000000000..c1a24bae2
--- /dev/null
+++ b/op-mode-definitions/include/bgp/reset-bgp-peer-group-vrf.xml.i
@@ -0,0 +1,14 @@
+<!-- included start from bgp/reset-bgp-peer-group-vrf.xml.i -->
+<tagNode name="peer-group">
+ <properties>
+ <help>Reset all members of peer-group</help>
+ <completionHelp>
+ <path>vrf name ${COMP_WORDS[4]} protocols bgp peer-group</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+</tagNode>
+<!-- included end -->
diff --git a/op-mode-definitions/include/bgp/reset-bgp-peer-group.xml.i b/op-mode-definitions/include/bgp/reset-bgp-peer-group.xml.i
new file mode 100644
index 000000000..c26e47b47
--- /dev/null
+++ b/op-mode-definitions/include/bgp/reset-bgp-peer-group.xml.i
@@ -0,0 +1,14 @@
+<!-- included start from bgp/reset-bgp-peer-group.xml.i -->
+<tagNode name="peer-group">
+ <properties>
+ <help>Reset all members of peer-group</help>
+ <completionHelp>
+ <path>protocols bgp peer-group</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+</tagNode>
+<!-- included end -->
diff --git a/op-mode-definitions/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in
index 5f20444d4..4f8792f9f 100644
--- a/op-mode-definitions/ipv6-route.xml.in
+++ b/op-mode-definitions/ipv6-route.xml.in
@@ -7,7 +7,7 @@
<children>
<node name="ipv6">
<properties>
- <help>Show IPv6 routing information</help>
+ <help>Show IPv6 networking information</help>
</properties>
<children>
<leafNode name="groups">
@@ -16,14 +16,32 @@
</properties>
<command>netstat -gn6</command>
</leafNode>
-
- <leafNode name="neighbors">
+ <node name="neighbors">
<properties>
- <help>Show IPv6 Neighbor Discovery (ND) information</help>
+ <help>Show IPv6 neighbor (NDP) table</help>
</properties>
<command>${vyos_op_scripts_dir}/show_neigh.py --family inet6</command>
- </leafNode>
-
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show IPv6 neighbor table for specified interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -b</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6 --interface "$5"</command>
+ </tagNode>
+ <tagNode name="state">
+ <properties>
+ <help>Show IPv6 neighbors with specified state</help>
+ <completionHelp>
+ <list>reachable stale failed permanent</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6 --state "$5"</command>
+ </tagNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index 6f82ce611..f5e0ede59 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -8,7 +8,7 @@
</properties>
<command>journalctl --no-hostname --follow --boot</command>
<children>
- <node name="colored">
+ <node name="color">
<properties>
<help>Output log in a colored fashion</help>
</properties>
@@ -16,18 +16,18 @@
</node>
<node name="dhcp">
<properties>
- <help>Show log for Dynamic Host Control Protocol (DHCP)</help>
+ <help>Monitor last lines of Dynamic Host Control Protocol (DHCP)</help>
</properties>
<children>
<node name="server">
<properties>
- <help>Show log for DHCP server</help>
+ <help>Monitor last lines of DHCP server</help>
</properties>
<command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server.service</command>
</node>
<node name="client">
<properties>
- <help>Show DHCP client logs</help>
+ <help>Monitor last lines of DHCP client</help>
</properties>
<command>journalctl --no-hostname --follow --boot --unit "dhclient@*.service"</command>
<children>
@@ -46,18 +46,18 @@
</node>
<node name="dhcpv6">
<properties>
- <help>Show log for Dynamic Host Control Protocol IPv6 (DHCPv6)</help>
+ <help>Monitor last lines of Dynamic Host Control Protocol IPv6 (DHCPv6)</help>
</properties>
<children>
<node name="server">
<properties>
- <help>Show log for DHCPv6 server</help>
+ <help>Monitor last lines of DHCPv6 server</help>
</properties>
<command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server6.service</command>
</node>
<node name="client">
<properties>
- <help>Show DHCPv6 client logs</help>
+ <help>Monitor last lines of DHCPv6 client</help>
</properties>
<command>journalctl --no-hostname --follow --boot --unit "dhcp6c@*.service"</command>
<children>
@@ -74,12 +74,24 @@
</node>
</children>
</node>
+ <leafNode name="flow-accounting">
+ <properties>
+ <help>Monitor last lines of flow-accounting log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit uacctd.service</command>
+ </leafNode>
<leafNode name="kernel">
<properties>
<help>Monitor last lines of Linux Kernel log</help>
</properties>
<command>journalctl --no-hostname --boot --follow --dmesg</command>
</leafNode>
+ <leafNode name="nhrp">
+ <properties>
+ <help>Monitor last lines of NHRP log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit opennhrp.service</command>
+ </leafNode>
<node name="pppoe">
<properties>
<help>Monitor last lines of PPPoE log</help>
diff --git a/op-mode-definitions/openconnect.xml.in b/op-mode-definitions/openconnect.xml.in
index 6b0082b4c..9343637c0 100644
--- a/op-mode-definitions/openconnect.xml.in
+++ b/op-mode-definitions/openconnect.xml.in
@@ -13,6 +13,53 @@
</properties>
<command>${vyos_op_scripts_dir}/openconnect-control.py --action="show_sessions"</command>
</leafNode>
+ <tagNode name="user">
+ <properties>
+ <help>Show OpenConnect configured user settings</help>
+ <completionHelp>
+ <script>sudo ${vyos_completion_dir}/list_openconnect_users.py</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="otp">
+ <properties>
+ <help>Show OTP key information</help>
+ </properties>
+ <children>
+ <leafNode name="full">
+ <properties>
+ <help>Show full settings, including QR code and commands for VyOS</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="full"</command>
+ </leafNode>
+ <leafNode name="key-hex">
+ <properties>
+ <help>Show OTP authentication secret in Hex (used in VyOS config)</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="key-hex"</command>
+ </leafNode>
+ <leafNode name="key-b32">
+ <properties>
+ <help>Show OTP authentication secret in Base32 (used in mobile apps)</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="key-b32"</command>
+ </leafNode>
+ <leafNode name="qrcode">
+ <properties>
+ <help>Show OTP authentication QR code</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="qrcode"</command>
+ </leafNode>
+ <leafNode name="uri">
+ <properties>
+ <help>Show OTP authentication otpauth URI</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_openconnect_otp.py --user="$4" --info="uri"</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
</children>
</node>
</children>
diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in
index a1c55dcf4..346febec0 100644
--- a/op-mode-definitions/pki.xml.in
+++ b/op-mode-definitions/pki.xml.in
@@ -349,6 +349,141 @@
</node>
</children>
</node>
+ <node name="import">
+ <properties>
+ <help>Import an object</help>
+ </properties>
+ <children>
+ <node name="pki">
+ <properties>
+ <help>Import file into PKI configuration</help>
+ </properties>
+ <children>
+ <tagNode name="ca">
+ <properties>
+ <help>Import CA certificate into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to CA certificate file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --filename "$6"</command>
+ </tagNode>
+ <tagNode name="key-file">
+ <properties>
+ <help>Path to private key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --key-filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="certificate">
+ <properties>
+ <help>Import certificate into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to certificate file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --filename "$6"</command>
+ </tagNode>
+ <tagNode name="key-file">
+ <properties>
+ <help>Path to private key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --key-filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="crl">
+ <properties>
+ <help>Import certificate revocation list into PKI</help>
+ <completionHelp>
+ <list>&lt;CA name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to CRL file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --crl "$4" --filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="dh">
+ <properties>
+ <help>Import DH parameters into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to DH parameters file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --dh "$4" --filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="key-pair">
+ <properties>
+ <help>Import key pair into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="public-file">
+ <properties>
+ <help>Path to public key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --filename "$6"</command>
+ </tagNode>
+ <tagNode name="private-file">
+ <properties>
+ <help>Path to private key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --key-filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <node name="openvpn">
+ <properties>
+ <help>Import OpenVPN keys into PKI</help>
+ </properties>
+ <children>
+ <tagNode name="shared-secret">
+ <properties>
+ <help>Import OpenVPN shared secret key into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to shared secret key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --openvpn "$5" --filename "$7"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="show">
<children>
<node name="pki">
diff --git a/op-mode-definitions/reset-bgp.xml.in b/op-mode-definitions/reset-bgp.xml.in
new file mode 100644
index 000000000..a1d42d4a3
--- /dev/null
+++ b/op-mode-definitions/reset-bgp.xml.in
@@ -0,0 +1,258 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="reset">
+ <children>
+ <node name="bgp">
+ <properties>
+ <help>Border Gateway Protocol (BGP) information</help>
+ </properties>
+ <children>
+ <leafNode name="all">
+ <properties>
+ <help>Clear all peers</help>
+ </properties>
+ <command>vtysh -c "clear bgp *"</command>
+ </leafNode>
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group.xml.i>
+ <tagNode name="prefix">
+ <properties>
+ <help>Clear bestpath and re-advertise</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x/x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </tagNode>
+ <node name="ipv4">
+ <properties>
+ <help>IPv4 Address Family</help>
+ </properties>
+ <children>
+ <leafNode name="all">
+ <properties>
+ <help>Clear all peers</help>
+ </properties>
+ <command>vtysh -c "clear bgp ipv4 *"</command>
+ </leafNode>
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group.xml.i>
+ </children>
+ </node>
+ <tagNode name="ipv4">
+ <properties>
+ <help>IPv4 neighbor to clear</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv4</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+ </tagNode>
+ <node name="ipv6">
+ <properties>
+ <help>IPv6 Address Family</help>
+ </properties>
+ <children>
+ <leafNode name="all">
+ <properties>
+ <help>Clear all peers</help>
+ </properties>
+ <command>vtysh -c "clear bgp ipv6 *"</command>
+ </leafNode>
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group.xml.i>
+ </children>
+ </node>
+ <tagNode name="ipv6">
+ <properties>
+ <help>IPv6 neighbor to clear</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv6</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+ </tagNode>
+ <node name="l2vpn">
+ <properties>
+ <help>Layer 2 Virtual Private Network Address Family</help>
+ </properties>
+ <children>
+ <node name="evpn">
+ <properties>
+ <help>Ethernet Virtual Private Network</help>
+ </properties>
+ <children>
+ <leafNode name="all">
+ <properties>
+ <help>Clear all peers</help>
+ </properties>
+ <command>vtysh -c "clear bgp l2vpn evpn *"</command>
+ </leafNode>
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group.xml.i>
+ </children>
+ </node>
+ <tagNode name="evpn">
+ <properties>
+ <help>BGP IPv4/IPv6 neighbor to clear</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_bgp_neighbors.sh --both</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <tagNode name="vrf">
+ <properties>
+ <help>Virtual Routing and Forwarding (VRF)</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="node.tag">
+ <properties>
+ <help>IPv4/IPv6 neighbor to clear</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_bgp_neighbors.sh --both --vrf ${COMP_WORDS[3]}</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+ </node>
+ <leafNode name="all">
+ <properties>
+ <help>Clear all peers</help>
+ </properties>
+ <command>vtysh -c "clear bgp vrf $4 *"</command>
+ </leafNode>
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group-vrf.xml.i>
+ <tagNode name="prefix">
+ <properties>
+ <help>Clear bestpath and re-advertise</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x/x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </tagNode>
+ <node name="ipv4">
+ <properties>
+ <help>IPv4 Address Family</help>
+ </properties>
+ <children>
+ <leafNode name="all">
+ <properties>
+ <help>Clear all peers</help>
+ </properties>
+ <command>vtysh -c "clear bgp vrf $4 ipv4 *"</command>
+ </leafNode>
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group-vrf.xml.i>
+ </children>
+ </node>
+ <tagNode name="ipv4">
+ <properties>
+ <help>IPv4 neighbor to clear</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv4 --vrf ${COMP_WORDS[3]}</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+ </tagNode>
+ <node name="ipv6">
+ <properties>
+ <help>IPv6 Address Family</help>
+ </properties>
+ <children>
+ <leafNode name="all">
+ <properties>
+ <help>Clear all peers</help>
+ </properties>
+ <command>vtysh -c "clear bgp vrf $4 ipv6 *"</command>
+ </leafNode>
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group-vrf.xml.i>
+ </children>
+ </node>
+ <tagNode name="ipv6">
+ <properties>
+ <help>IPv6 neighbor to clear</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv6 --vrf ${COMP_WORDS[3]}</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+ </tagNode>
+ <node name="l2vpn">
+ <properties>
+ <help>Layer 2 Virtual Private Network Address Family</help>
+ </properties>
+ <children>
+ <node name="evpn">
+ <properties>
+ <help>Ethernet Virtual Private Network</help>
+ </properties>
+ <children>
+ <leafNode name="all">
+ <properties>
+ <help>Clear all peers</help>
+ </properties>
+ <command>vtysh -c "clear bgp vrf $4 l2vpn evpn *"</command>
+ </leafNode>
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group-vrf.xml.i>
+ </children>
+ </node>
+ <tagNode name="evpn">
+ <properties>
+ <help>BGP IPv4/IPv6 neighbor to clear</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_bgp_neighbors.sh --both --vrf ${COMP_WORDS[3]}</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <tagNode name="bgp">
+ <properties>
+ <help>BGP IPv4/IPv6 neighbor to clear</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_bgp_neighbors.sh --both</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/reset-ip-bgp.xml.in b/op-mode-definitions/reset-ip-bgp.xml.in
index 931a2a9bc..34a4503d9 100644
--- a/op-mode-definitions/reset-ip-bgp.xml.in
+++ b/op-mode-definitions/reset-ip-bgp.xml.in
@@ -6,7 +6,7 @@
<children>
<node name="bgp">
<properties>
- <help>Clear Border Gateway Protocol (BGP) statistics or status</help>
+ <help>Border Gateway Protocol (BGP) information</help>
</properties>
<children>
<leafNode name="all">
@@ -41,159 +41,45 @@
</leafNode>
</children>
</tagNode>
- <node name="external">
+ #include <include/bgp/reset-bgp-afi-common.xml.i>
+ #include <include/bgp/reset-bgp-peer-group.xml.i>
+ <tagNode name="vrf">
<properties>
- <help>Clear all external peers</help>
+ <help>Clear BGP statistics or status for vrf</help>
<completionHelp>
- <list>WORD</list>
+ <path>vrf name</path>
</completionHelp>
</properties>
- <command>vtysh -c "clear bgp ipv4 external"</command>
<children>
- <node name="in">
+ <leafNode name="all">
<properties>
- <help>Send route-refresh unless using 'soft-reconfiguration inbound'</help>
+ <help>Clear all BGP peering sessions for vrf</help>
</properties>
- <command>vtysh -c "clear bgp ipv4 external in"</command>
- <children>
- <leafNode name="prefix-filter">
- <properties>
- <help>Push out prefix-list ORF and do inbound soft reconfig</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 external in prefix-filter"</command>
- </leafNode>
- </children>
- </node>
- <node name="out">
- <properties>
- <help>Resend all outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 external out"</command>
- </node>
- <node name="soft">
- <properties>
- <help>Soft reconfig inbound and outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 external soft"</command>
- <children>
- <node name="in">
- <properties>
- <help>Clear via soft reconfig of inbound update</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 external soft in"</command>
- </node>
- <node name="out">
- <properties>
- <help>Resend all outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 external soft out"</command>
- </node>
- </children>
- </node>
- </children>
- </node>
- <tagNode name="peer-group">
- <properties>
- <help>Clear BGP statistics or status for given peer-group</help>
- <completionHelp>
- <list>WORD</list>
- </completionHelp>
- </properties>
- <command>vtysh -c "clear bgp ipv4 peer-group $5"</command>
- <children>
- <node name="in">
- <properties>
- <help>Send route-refresh unless using 'soft-reconfiguration inbound'</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 peer-group $5 in"</command>
- <children>
- <leafNode name="prefix-filter">
- <properties>
- <help>Push out prefix-list ORF and do inbound soft reconfig</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 peer-group $5 in prefix-filter"</command>
- </leafNode>
- </children>
- </node>
- <node name="out">
- <properties>
- <help>Resend all outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 peer-group $5 out"</command>
- </node>
- <node name="soft">
+ <command>vtysh -c "clear bgp vrf $5 *"</command>
+ </leafNode>
+ <leafNode name="node.tag">
<properties>
- <help>Soft reconfig inbound and outbound updates</help>
+ <help>Clear BGP neighbor IP address</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt;</list>
+ </completionHelp>
</properties>
- <command>vtysh -c "clear bgp ipv4 peer-group $5 soft"</command>
- <children>
- <node name="in">
- <properties>
- <help>Clear via soft reconfig of inbound update</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 peer-group $5 soft in"</command>
- </node>
- <node name="out">
- <properties>
- <help>Resend all outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 peer-group $5 soft out"</command>
- </node>
- </children>
- </node>
+ <command>vtysh -c "clear bgp vrf $5 $6"</command>
+ </leafNode>
</children>
</tagNode>
</children>
</node>
<tagNode name="bgp">
<properties>
- <help>Clear BGP neighbor IP address</help>
+ <help>BGP IPv4/IPv6 neighbor to clear</help>
<completionHelp>
<script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv4</script>
</completionHelp>
</properties>
- <command>vtysh -c "clear bgp ipv4 $4"</command>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
<children>
- <node name="in">
- <properties>
- <help>Send route-refresh unless using 'soft-reconfiguration inbound'</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 $4 in"</command>
- <children>
- <leafNode name="prefix-filter">
- <properties>
- <help>Push out prefix-list ORF and do inbound soft reconfig</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 $4 in prefix-filter"</command>
- </leafNode>
- </children>
- </node>
- <node name="out">
- <properties>
- <help>Resend all outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 $4 out"</command>
- </node>
- <node name="soft">
- <properties>
- <help>Soft reconfig inbound and outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 $4 soft"</command>
- <children>
- <node name="in">
- <properties>
- <help>Clear via soft reconfig of inbound update</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 $4 soft in"</command>
- </node>
- <node name="out">
- <properties>
- <help>Resend all outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv4 $4 soft out"</command>
- </node>
- </children>
- </node>
+ #include <include/bgp/reset-bgp-neighbor-options.xml.i>
</children>
</tagNode>
</children>
diff --git a/op-mode-definitions/reset-ipv6-bgp.xml.in b/op-mode-definitions/reset-ipv6-bgp.xml.in
deleted file mode 100644
index 3c4275331..000000000
--- a/op-mode-definitions/reset-ipv6-bgp.xml.in
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="reset">
- <children>
- <node name="ipv6">
- <children>
- <tagNode name="bgp">
- <properties>
- <help>Clear BGP neighbor IP address</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_bgp_neighbors.sh --ipv6</script>
- </completionHelp>
- </properties>
- <command>vtysh -c "clear bgp ipv6 $4"</command>
- <children>
- <node name="in">
- <properties>
- <help>Send route-refresh unless using 'soft-reconfiguration inbound'</help>
- </properties>
- <command>vtysh -c "clear bgp ipv6 $4 in"</command>
- <children>
- <leafNode name="prefix-filter">
- <properties>
- <help>Push out prefix-list ORF and do inbound soft reconfig</help>
- </properties>
- <command>vtysh -c "clear bgp ipv6 $4 in prefix-filter"</command>
- </leafNode>
- </children>
- </node>
- <node name="out">
- <properties>
- <help>Resend all outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv6 $4 out"</command>
- </node>
- <node name="soft">
- <properties>
- <help>Soft reconfig inbound and outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv6 $4 soft"</command>
- <children>
- <node name="in">
- <properties>
- <help>Clear via soft reconfig of inbound update</help>
- </properties>
- <command>vtysh -c "clear bgp ipv6 $4 soft in"</command>
- </node>
- <node name="out">
- <properties>
- <help>Resend all outbound updates</help>
- </properties>
- <command>vtysh -c "clear bgp ipv6 $4 soft out"</command>
- </node>
- </children>
- </node>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/op-mode-definitions/show-arp.xml.in b/op-mode-definitions/show-arp.xml.in
index 12e7d3aa2..58cc6e45e 100644
--- a/op-mode-definitions/show-arp.xml.in
+++ b/op-mode-definitions/show-arp.xml.in
@@ -6,7 +6,7 @@
<properties>
<help>Show Address Resolution Protocol (ARP) information</help>
</properties>
- <command>/usr/sbin/arp -e -n</command>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet</command>
<children>
<tagNode name="interface">
<properties>
@@ -15,7 +15,7 @@
<script>${vyos_completion_dir}/list_interfaces.py -b</script>
</completionHelp>
</properties>
- <command>/usr/sbin/arp -e -n -i "$4"</command>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$4"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/show-conntrack.xml.in b/op-mode-definitions/show-conntrack.xml.in
new file mode 100644
index 000000000..792623d7d
--- /dev/null
+++ b/op-mode-definitions/show-conntrack.xml.in
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="conntrack">
+ <properties>
+ <help>Show conntrack tables entries</help>
+ </properties>
+ <children>
+ <node name="table">
+ <properties>
+ <help>Show conntrack entries for table</help>
+ </properties>
+ <children>
+ <node name="ipv4">
+ <properties>
+ <help>Show conntrack entries for IPv4 protocol</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/show_conntrack.py</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-ip.xml.in b/op-mode-definitions/show-ip.xml.in
index 91564440d..d342ac192 100644
--- a/op-mode-definitions/show-ip.xml.in
+++ b/op-mode-definitions/show-ip.xml.in
@@ -4,14 +4,34 @@
<children>
<node name="ip">
<properties>
- <help>Show IPv4 routing information</help>
+ <help>Show IPv4 networking information</help>
</properties>
<children>
<node name="neighbors">
<properties>
- <help>Show IPv4 Neighbor Discovery (ND) information</help>
+ <help>Show IPv4 neighbor (ARP) table</help>
</properties>
<command>${vyos_op_scripts_dir}/show_neigh.py --family inet</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show IPv4 neighbor table for specified interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -b</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$5"</command>
+ </tagNode>
+ <tagNode name="state">
+ <properties>
+ <help>Show IPv4 neighbors with specified state</help>
+ <completionHelp>
+ <list>reachable stale failed permanent</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --state "$5"</command>
+ </tagNode>
+ </children>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-ipv6.xml.in b/op-mode-definitions/show-ipv6.xml.in
index a59c8df0c..66bc2485a 100644
--- a/op-mode-definitions/show-ipv6.xml.in
+++ b/op-mode-definitions/show-ipv6.xml.in
@@ -4,7 +4,7 @@
<children>
<node name="ipv6">
<properties>
- <help>Show IPv6 routing information</help>
+ <help>Show IPv6 networking information</help>
</properties>
<children>
<node name="access-list">
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 954369712..76879e5d6 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -139,6 +139,12 @@
</tagNode>
</children>
</node>
+ <leafNode name="flow-accounting">
+ <properties>
+ <help>Show log for flow-accounting</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit uacctd.service</command>
+ </leafNode>
<leafNode name="https">
<properties>
<help>Show log for HTTPs</help>
@@ -195,6 +201,12 @@
</properties>
<command>egrep -i "kernel:.*\[NAT-[A-Z]{3,}-[0-9]+(-MASQ)?\]" $(find /var/log -maxdepth 1 -type f -name messages\* | sort -t. -k2nr)</command>
</leafNode>
+ <leafNode name="nhrp">
+ <properties>
+ <help>Show log for NHRP</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit opennhrp.service</command>
+ </leafNode>
<node name="openvpn">
<properties>
<help>Show log for OpenVPN</help>
diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in
index 0f852164e..68b473bc1 100644
--- a/op-mode-definitions/show-system.xml.in
+++ b/op-mode-definitions/show-system.xml.in
@@ -166,9 +166,9 @@
</leafNode>
<leafNode name="uptime">
<properties>
- <help>Show how long the system has been up</help>
+ <help>Show system uptime and load averages</help>
</properties>
- <command>uptime</command>
+ <command>${vyos_op_scripts_dir}/show_uptime.py</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/webproxy.xml.in b/op-mode-definitions/webproxy.xml.in
index f8ec8fb0a..5ae1577d8 100644
--- a/op-mode-definitions/webproxy.xml.in
+++ b/op-mode-definitions/webproxy.xml.in
@@ -12,7 +12,7 @@
<properties>
<help>Monitor the last lines of the squid access log</help>
</properties>
- <command>if [ -f /var/log/squid3/access.log ]; then sudo tail --follow=name /var/log/squid3/access.log; else echo "WebProxy cache-log does not exist"; fi</command>
+ <command>if [ -f /var/log/squid/access.log ]; then sudo tail --follow=name /var/log/squid/access.log; else echo "WebProxy access-log does not exist"; fi</command>
</node>
<node name="background">
<properties>
@@ -37,7 +37,7 @@
<properties>
<help>Monitor the last lines of the squid cache log</help>
</properties>
- <command>if [ -f /var/log/squid3/cache.log ]; then sudo tail --follow=name /var/log/squid3/cache.log; else echo "WebProxy cache-log does not exist"; fi</command>
+ <command>if [ -f /var/log/squid/cache.log ]; then sudo tail --follow=name /var/log/squid/cache.log; else echo "WebProxy cache-log does not exist"; fi</command>
</node>
</children>
</node>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 04ddc10e9..29d89520c 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -201,11 +201,12 @@ def is_member(conf, interface, intftype=None):
intftype is optional, if not passed it will search all known types
(currently bridge and bonding)
- Returns:
- None -> Interface is not a member
- interface name -> Interface is a member of this interface
- False -> interface type cannot have members
+ Returns: dict
+ empty -> Interface is not a member
+ key -> Interface is a member of this interface
"""
+ from vyos.ifconfig import Section
+
ret_val = {}
intftypes = ['bonding', 'bridge']
@@ -221,9 +222,18 @@ def is_member(conf, interface, intftype=None):
for intf in conf.list_nodes(base):
member = base + [intf, 'member', 'interface', interface]
if conf.exists(member):
- tmp = conf.get_config_dict(member, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
- ret_val.update({intf : tmp})
+ member_type = Section.section(interface)
+ # Check if it's a VLAN (QinQ) interface
+ interface = interface.split('.')
+ if len(interface) == 3:
+ if conf.exists(['interfaces', member_type, interface[0], 'vif-s', interface[1], 'vif-c', interface[2]]):
+ ret_val.update({intf : {}})
+ elif len(interface) == 2:
+ if conf.exists(['interfaces', member_type, interface[0], 'vif', interface[1]]):
+ ret_val.update({intf : {}})
+ else:
+ if conf.exists(['interfaces', member_type, interface[0]]):
+ ret_val.update({intf : {}})
return ret_val
@@ -358,13 +368,14 @@ def get_pppoe_interfaces(conf, vrf=None):
""" Common helper functions to retrieve all interfaces from current CLI
sessions that have DHCP configured. """
pppoe_interfaces = {}
+ conf.set_level([])
for ifname in conf.list_nodes(['interfaces', 'pppoe']):
# always reset config level, as get_interface_dict() will alter it
conf.set_level([])
# we already have a dict representation of the config from get_config_dict(),
# but with the extended information from get_interface_dict() we also
# get the DHCP client default-route-distance default option if not specified.
- ifconfig = get_interface_dict(conf, ['interfaces', 'pppoe'], ifname)
+ _, ifconfig = get_interface_dict(conf, ['interfaces', 'pppoe'], ifname)
options = {}
if 'default_route_distance' in ifconfig:
@@ -455,8 +466,8 @@ def get_interface_dict(config, base, ifname=''):
if bond: dict.update({'is_bond_member' : bond})
# Check if any DHCP options changed which require a client restat
- dhcp = node_changed(config, ['dhcp-options'], recursive=True)
- if dhcp: dict.update({'dhcp_options_changed' : ''})
+ dhcp = is_node_changed(config, base + [ifname, 'dhcp-options'])
+ if dhcp: dict.update({'dhcp_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
@@ -515,8 +526,8 @@ def get_interface_dict(config, base, ifname=''):
if bridge: dict['vif'][vif].update({'is_bridge_member' : bridge})
# Check if any DHCP options changed which require a client restat
- dhcp = node_changed(config, ['vif', vif, 'dhcp-options'], recursive=True)
- if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : ''})
+ dhcp = is_node_changed(config, base + [ifname, 'vif', vif, 'dhcp-options'])
+ if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : {}})
for vif_s, vif_s_config in dict.get('vif_s', {}).items():
# Add subinterface name to dictionary
@@ -554,8 +565,8 @@ def get_interface_dict(config, base, ifname=''):
if bridge: dict['vif_s'][vif_s].update({'is_bridge_member' : bridge})
# Check if any DHCP options changed which require a client restat
- dhcp = node_changed(config, ['vif-s', vif_s, 'dhcp-options'], recursive=True)
- if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : ''})
+ dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'dhcp-options'])
+ if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : {}})
for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
# Add subinterface name to dictionary
@@ -594,8 +605,8 @@ def get_interface_dict(config, base, ifname=''):
{'is_bridge_member' : bridge})
# Check if any DHCP options changed which require a client restat
- dhcp = node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, 'dhcp-options'], recursive=True)
- if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : ''})
+ 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' : {}})
# 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 f28ad09c5..3a60f6d92 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -33,6 +33,7 @@ INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url']
REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del']
GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate']
SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show']
+RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset']
# Default "commit via" string
APP = "vyos-http-api"
@@ -200,3 +201,6 @@ class ConfigSession(object):
out = self.__run_command(SHOW + path)
return out
+ def reset(self, path):
+ out = self.__run_command(RESET + path)
+ return out
diff --git a/python/vyos/cpu.py b/python/vyos/cpu.py
new file mode 100644
index 000000000..a0ef864be
--- /dev/null
+++ b/python/vyos/cpu.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+# Copyright 2022 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/>.
+
+"""
+Retrieves (or at least attempts to retrieve) the total number of real CPU cores
+installed in a Linux system.
+
+The issue of core count is complicated by existence of SMT, e.g. Intel's Hyper Threading.
+GNU nproc returns the number of LOGICAL cores,
+which is 2x of the real cores if SMT is enabled.
+
+The idea is to find all physical CPUs and add up their core counts.
+It has special cases for x86_64 and MAY work correctly on other architectures,
+but nothing is certain.
+"""
+
+import re
+
+
+def _read_cpuinfo():
+ with open('/proc/cpuinfo', 'r') as f:
+ return f.readlines()
+
+def _split_line(l):
+ l = l.strip()
+ parts = re.split(r'\s*:\s*', l)
+ return (parts[0], ":".join(parts[1:]))
+
+def _find_cpus(cpuinfo_lines):
+ # Make a dict because it's more convenient to work with later,
+ # when we need to find physicall distinct CPUs there.
+ cpus = {}
+
+ cpu_number = 0
+
+ for l in cpuinfo_lines:
+ key, value = _split_line(l)
+ if key == 'processor':
+ cpu_number = value
+ cpus[cpu_number] = {}
+ else:
+ cpus[cpu_number][key] = value
+
+ return cpus
+
+def _find_physical_cpus():
+ cpus = _find_cpus(_read_cpuinfo())
+
+ phys_cpus = {}
+
+ for num in cpus:
+ if 'physical id' in cpus[num]:
+ # On at least some architectures, CPUs in different sockets
+ # have different 'physical id' field, e.g. on x86_64.
+ phys_id = cpus[num]['physical id']
+ if phys_id not in phys_cpus:
+ phys_cpus[phys_id] = cpus[num]
+ else:
+ # On other architectures, e.g. on ARM, there's no such field.
+ # We just assume they are different CPUs,
+ # whether single core ones or cores of physical CPUs.
+ phys_cpus[num] = cpu[num]
+
+ return phys_cpus
+
+def get_cpus():
+ """ Returns a list of /proc/cpuinfo entries that belong to different CPUs.
+ """
+ cpus_dict = _find_physical_cpus()
+ return list(cpus_dict.values())
+
+def get_core_count():
+ """ Returns the total number of physical CPU cores
+ (even if Hyper-Threading or another SMT is enabled and has inflated
+ the number of cores in /proc/cpuinfo)
+ """
+ physical_cpus = _find_physical_cpus()
+
+ core_count = 0
+
+ for num in physical_cpus:
+ # Some architectures, e.g. x86_64, include a field for core count.
+ # Since we found unique physical CPU entries, we can sum their core counts.
+ if 'cpu cores' in physical_cpus[num]:
+ core_count += int(physical_cpus[num]['cpu cores'])
+ else:
+ core_count += 1
+
+ return core_count
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index ff8623592..3e2de4c3f 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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,82 @@
# 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 csv
+import gzip
+import os
import re
+from pathlib import Path
+from time import strftime
+
+from vyos.remote import download
+from vyos.template import is_ipv4
+from vyos.template import render
+from vyos.util import call
from vyos.util import cmd
from vyos.util import dict_search_args
+from vyos.util import dict_search_recursive
+from vyos.util import run
+
+
+# Functions for firewall group domain-groups
+def get_ips_domains_dict(list_domains):
+ """
+ Get list of IPv4 addresses by list of domains
+ Ex: get_ips_domains_dict(['ex1.com', 'ex2.com'])
+ {'ex1.com': ['192.0.2.1'], 'ex2.com': ['192.0.2.2', '192.0.2.3']}
+ """
+ from socket import gethostbyname_ex
+ from socket import gaierror
+
+ ip_dict = {}
+ for domain in list_domains:
+ try:
+ _, _, ips = gethostbyname_ex(domain)
+ ip_dict[domain] = ips
+ except gaierror:
+ pass
+
+ return ip_dict
+
+def nft_init_set(group_name, table="filter", family="ip"):
+ """
+ table ip filter {
+ set GROUP_NAME
+ type ipv4_addr
+ flags interval
+ }
+ """
+ return call(f'nft add set ip {table} {group_name} {{ type ipv4_addr\\; flags interval\\; }}')
+
+
+def nft_add_set_elements(group_name, elements, table="filter", family="ip"):
+ """
+ table ip filter {
+ set GROUP_NAME {
+ type ipv4_addr
+ flags interval
+ elements = { 192.0.2.1, 192.0.2.2 }
+ }
+ """
+ elements = ", ".join(elements)
+ return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ')
+
+def nft_flush_set(group_name, table="filter", family="ip"):
+ """
+ Flush elements of nft set
+ """
+ return call(f'nft flush set {family} {table} {group_name}')
+
+def nft_update_set_elements(group_name, elements, table="filter", family="ip"):
+ """
+ Update elements of nft set
+ """
+ flush_set = nft_flush_set(group_name, table="filter", family="ip")
+ nft_add_set = nft_add_set_elements(group_name, elements, table="filter", family="ip")
+ return flush_set, nft_add_set
+
+# END firewall group domain-group (sets)
def find_nftables_rule(table, chain, rule_matches=[]):
# Find rule in table/chain that matches all criteria and return the handle
@@ -49,6 +121,15 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if states:
output.append(f'ct state {{{states}}}')
+ if 'connection_status' in rule_conf and rule_conf['connection_status']:
+ status = rule_conf['connection_status']
+ if status['nat'] == 'destination':
+ nat_status = '{dnat}'
+ output.append(f'ct status {nat_status}')
+ if status['nat'] == 'source':
+ nat_status = '{snat}'
+ output.append(f'ct status {nat_status}')
+
if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
proto = rule_conf['protocol']
operator = ''
@@ -69,6 +150,12 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if suffix[0] == '!':
suffix = f'!= {suffix[1:]}'
output.append(f'{ip_name} {prefix}addr {suffix}')
+
+ if dict_search_args(side_conf, 'geoip', 'country_code'):
+ operator = ''
+ if dict_search_args(side_conf, 'geoip', 'inverse_match') != None:
+ operator = '!='
+ output.append(f'{ip_name} {prefix}addr {operator} @GEOIP_CC_{fw_name}_{rule_id}')
if 'mac_address' in side_conf:
suffix = side_conf["mac_address"]
@@ -108,21 +195,29 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if group_name[0] == '!':
operator = '!='
group_name = group_name[1:]
- output.append(f'{ip_name} {prefix}addr {operator} $A{def_suffix}_{group_name}')
+ output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}')
+ # Generate firewall group domain-group
+ elif 'domain_group' in group:
+ group_name = group['domain_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_name} {prefix}addr {operator} @D_{group_name}')
elif 'network_group' in group:
group_name = group['network_group']
operator = ''
if group_name[0] == '!':
operator = '!='
group_name = group_name[1:]
- output.append(f'{ip_name} {prefix}addr {operator} $N{def_suffix}_{group_name}')
+ output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}')
if 'mac_group' in group:
group_name = group['mac_group']
operator = ''
if group_name[0] == '!':
operator = '!='
group_name = group_name[1:]
- output.append(f'ether {prefix}addr {operator} $M_{group_name}')
+ output.append(f'ether {prefix}addr {operator} @M_{group_name}')
if 'port_group' in group:
proto = rule_conf['protocol']
group_name = group['port_group']
@@ -135,11 +230,16 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
operator = '!='
group_name = group_name[1:]
- output.append(f'{proto} {prefix}port {operator} $P_{group_name}')
+ output.append(f'{proto} {prefix}port {operator} @P_{group_name}')
if 'log' in rule_conf and rule_conf['log'] == 'enable':
action = rule_conf['action'] if 'action' in rule_conf else 'accept'
- output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}] "')
+ output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"')
+
+ if 'log_level' in rule_conf:
+ log_level = rule_conf['log_level']
+ output.append(f'level {log_level}')
+
if 'hop_limit' in rule_conf:
operators = {'eq': '==', 'gt': '>', 'lt': '<'}
@@ -148,6 +248,13 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
value = rule_conf['hop_limit'][op]
output.append(f'ip6 hoplimit {operator} {value}')
+ if 'ttl' in rule_conf:
+ operators = {'eq': '==', 'gt': '>', 'lt': '<'}
+ for op, operator in operators.items():
+ if op in rule_conf['ttl']:
+ value = rule_conf['ttl'][op]
+ output.append(f'ip ttl {operator} {value}')
+
for icmp in ['icmp', 'icmpv6']:
if icmp in rule_conf:
if 'type_name' in rule_conf[icmp]:
@@ -248,3 +355,118 @@ def parse_policy_set(set_conf, def_suffix):
mss = set_conf['tcp_mss']
out.append(f'tcp option maxseg size set {mss}')
return " ".join(out)
+
+# GeoIP
+
+nftables_geoip_conf = '/run/nftables-geoip.conf'
+geoip_database = '/usr/share/vyos-geoip/dbip-country-lite.csv.gz'
+geoip_lock_file = '/run/vyos-geoip.lock'
+
+def geoip_load_data(codes=[]):
+ data = None
+
+ if not os.path.exists(geoip_database):
+ return []
+
+ try:
+ with gzip.open(geoip_database, mode='rt') as csv_fh:
+ reader = csv.reader(csv_fh)
+ out = []
+ for start, end, code in reader:
+ if code.lower() in codes:
+ out.append([start, end, code.lower()])
+ return out
+ except:
+ print('Error: Failed to open GeoIP database')
+ return []
+
+def geoip_download_data():
+ url = 'https://download.db-ip.com/free/dbip-country-lite-{}.csv.gz'.format(strftime("%Y-%m"))
+ try:
+ dirname = os.path.dirname(geoip_database)
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
+
+ download(geoip_database, url)
+ print("Downloaded GeoIP database")
+ return True
+ except:
+ print("Error: Failed to download GeoIP database")
+ return False
+
+class GeoIPLock(object):
+ def __init__(self, file):
+ self.file = file
+
+ def __enter__(self):
+ if os.path.exists(self.file):
+ return False
+
+ Path(self.file).touch()
+ return True
+
+ def __exit__(self, exc_type, exc_value, tb):
+ os.unlink(self.file)
+
+def geoip_update(firewall, force=False):
+ with GeoIPLock(geoip_lock_file) as lock:
+ if not lock:
+ print("Script is already running")
+ return False
+
+ if not firewall:
+ print("Firewall is not configured")
+ return True
+
+ if not os.path.exists(geoip_database):
+ if not geoip_download_data():
+ return False
+ elif force:
+ geoip_download_data()
+
+ ipv4_codes = {}
+ ipv6_codes = {}
+
+ ipv4_sets = {}
+ ipv6_sets = {}
+
+ # Map country codes to set names
+ for codes, path in dict_search_recursive(firewall, 'country_code'):
+ set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
+ if path[0] == 'name':
+ for code in codes:
+ ipv4_codes.setdefault(code, []).append(set_name)
+ elif path[0] == 'ipv6_name':
+ for code in codes:
+ ipv6_codes.setdefault(code, []).append(set_name)
+
+ if not ipv4_codes and not ipv6_codes:
+ if force:
+ print("GeoIP not in use by firewall")
+ return True
+
+ geoip_data = geoip_load_data([*ipv4_codes, *ipv6_codes])
+
+ # Iterate IP blocks to assign to sets
+ for start, end, code in geoip_data:
+ ipv4 = is_ipv4(start)
+ if code in ipv4_codes and ipv4:
+ ip_range = f'{start}-{end}' if start != end else start
+ for setname in ipv4_codes[code]:
+ ipv4_sets.setdefault(setname, []).append(ip_range)
+ if code in ipv6_codes and not ipv4:
+ ip_range = f'{start}-{end}' if start != end else start
+ for setname in ipv6_codes[code]:
+ ipv6_sets.setdefault(setname, []).append(ip_range)
+
+ render(nftables_geoip_conf, 'firewall/nftables-geoip-update.j2', {
+ 'ipv4_sets': ipv4_sets,
+ 'ipv6_sets': ipv6_sets
+ })
+
+ result = run(f'nft -f {nftables_geoip_conf}')
+ if result != 0:
+ print('Error: GeoIP failed to update firewall')
+ return False
+
+ return True
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index cbba19ab7..0ffd5cba9 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -85,7 +85,7 @@ LOG.addHandler(ch2)
_frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',
'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd',
- 'bfdd']
+ 'bfdd', 'eigrpd']
path_vtysh = '/usr/bin/vtysh'
path_frr_reload = '/usr/lib/frr/frr-reload.py'
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index 2b9afe109..98bf6162b 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -179,6 +179,21 @@ class BondIf(Interface):
"""
self.set_interface('bond_lacp_rate', slow_fast)
+ def set_miimon_interval(self, interval):
+ """
+ Specifies the MII link monitoring frequency in milliseconds. This
+ determines how often the link state of each slave is inspected for link
+ failures. A value of zero disables MII link monitoring. A value of 100
+ is a good starting point.
+
+ The default value is 0.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_miimon_interval('100')
+ """
+ return self.set_interface('bond_miimon', interval)
+
def set_arp_interval(self, interval):
"""
Specifies the ARP link monitoring frequency in milliseconds.
@@ -202,16 +217,7 @@ class BondIf(Interface):
>>> from vyos.ifconfig import BondIf
>>> BondIf('bond0').set_arp_interval('100')
"""
- if int(interval) == 0:
- """
- Specifies the MII link monitoring frequency in milliseconds.
- This determines how often the link state of each slave is
- inspected for link failures. A value of zero disables MII
- link monitoring. A value of 100 is a good starting point.
- """
- return self.set_interface('bond_miimon', interval)
- else:
- return self.set_interface('bond_arp_interval', interval)
+ return self.set_interface('bond_arp_interval', interval)
def get_arp_ip_target(self):
"""
@@ -381,26 +387,9 @@ class BondIf(Interface):
if 'shutdown_required' in config:
self.set_admin_state('down')
- # ARP monitor targets need to be synchronized between sysfs and CLI.
- # Unfortunately an address can't be send twice to sysfs as this will
- # result in the following exception: OSError: [Errno 22] Invalid argument.
- #
- # We remove ALL addresses prior to adding new ones, this will remove
- # addresses manually added by the user too - but as we are limited to 16 adresses
- # from the kernel side this looks valid to me. We won't run into an error
- # when a user added manual adresses which would result in having more
- # then 16 adresses in total.
- arp_tgt_addr = list(map(str, self.get_arp_ip_target().split()))
- for addr in arp_tgt_addr:
- self.set_arp_ip_target('-' + addr)
-
- # Add configured ARP target addresses
- value = dict_search('arp_monitor.target', config)
- if isinstance(value, str):
- value = [value]
- if value:
- for addr in value:
- self.set_arp_ip_target('+' + addr)
+ # Specifies the MII link monitoring frequency in milliseconds
+ value = config.get('mii_mon_interval')
+ self.set_miimon_interval(value)
# Bonding transmit hash policy
value = config.get('hash_policy')
@@ -430,6 +419,32 @@ class BondIf(Interface):
if mode == '802.3ad':
self.set_lacp_rate(config.get('lacp_rate'))
+ if mode not in ['802.3ad', 'balance-tlb', 'balance-alb']:
+ tmp = dict_search('arp_monitor.interval', config)
+ value = tmp if (tmp != None) else '0'
+ self.set_arp_interval(value)
+
+ # ARP monitor targets need to be synchronized between sysfs and CLI.
+ # Unfortunately an address can't be send twice to sysfs as this will
+ # result in the following exception: OSError: [Errno 22] Invalid argument.
+ #
+ # We remove ALL addresses prior to adding new ones, this will remove
+ # addresses manually added by the user too - but as we are limited to 16 adresses
+ # from the kernel side this looks valid to me. We won't run into an error
+ # when a user added manual adresses which would result in having more
+ # then 16 adresses in total.
+ arp_tgt_addr = list(map(str, self.get_arp_ip_target().split()))
+ for addr in arp_tgt_addr:
+ self.set_arp_ip_target('-' + addr)
+
+ # Add configured ARP target addresses
+ value = dict_search('arp_monitor.target', config)
+ if isinstance(value, str):
+ value = [value]
+ if value:
+ for addr in value:
+ self.set_arp_ip_target('+' + addr)
+
# Add (enslave) interfaces to bond
value = dict_search('member.interface', config)
for interface in (value or []):
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index ffd9c590f..e4db69c1f 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -90,6 +90,10 @@ class BridgeIf(Interface):
'validate': assert_boolean,
'location': '/sys/class/net/{ifname}/bridge/multicast_querier',
},
+ 'multicast_snooping': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/bridge/multicast_snooping',
+ },
}}
_command_set = {**Interface._command_set, **{
@@ -198,6 +202,18 @@ class BridgeIf(Interface):
"""
self.set_interface('multicast_querier', enable)
+ def set_multicast_snooping(self, enable):
+ """
+ Enable or disable multicast snooping on the bridge.
+
+ Use enable=1 to enable or enable=0 to disable
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').set_multicast_snooping(1)
+ """
+ self.set_interface('multicast_snooping', enable)
+
def add_port(self, interface):
"""
Add physical interface to bridge (member port)
@@ -257,6 +273,11 @@ class BridgeIf(Interface):
value = '1' if 'stp' in config else '0'
self.set_stp(value)
+ # enable or disable multicast snooping
+ tmp = dict_search('igmp.snooping', config)
+ value = '1' if (tmp != None) else '0'
+ self.set_multicast_snooping(value)
+
# enable or disable IGMP querier
tmp = dict_search('igmp.querier', config)
value = '1' if (tmp != None) else '0'
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 22441d1d2..33a7f9a2d 100755..100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -168,6 +168,10 @@ class Interface(Control):
'validate': assert_boolean,
'location': '/proc/sys/net/ipv4/conf/{ifname}/forwarding',
},
+ 'ipv4_directed_broadcast': {
+ 'validate': assert_boolean,
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding',
+ },
'rp_filter': {
'validate': lambda flt: assert_range(flt,0,3),
'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter',
@@ -234,6 +238,9 @@ class Interface(Control):
'ipv4_forwarding': {
'location': '/proc/sys/net/ipv4/conf/{ifname}/forwarding',
},
+ 'ipv4_directed_broadcast': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding',
+ },
'rp_filter': {
'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter',
},
@@ -713,6 +720,13 @@ class Interface(Control):
return None
return self.set_interface('ipv4_forwarding', forwarding)
+ def set_ipv4_directed_broadcast(self, forwarding):
+ """ Configure IPv4 directed broadcast forwarding. """
+ tmp = self.get_interface('ipv4_directed_broadcast')
+ if tmp == forwarding:
+ return None
+ return self.set_interface('ipv4_directed_broadcast', forwarding)
+
def set_ipv4_source_validation(self, value):
"""
Help prevent attacks used by Spoofing IP Addresses. Reverse path
@@ -1498,6 +1512,11 @@ class Interface(Control):
value = '0' if (tmp != None) else '1'
self.set_ipv4_forwarding(value)
+ # IPv4 directed broadcast forwarding
+ tmp = dict_search('ip.enable_directed_broadcast', config)
+ value = '1' if (tmp != None) else '0'
+ self.set_ipv4_directed_broadcast(value)
+
# IPv4 source-validation
tmp = dict_search('ip.source_validation', config)
value = tmp if (tmp != None) else '0'
diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py
index c50cd5ce9..dc99d365a 100644
--- a/python/vyos/ifconfig/vti.py
+++ b/python/vyos/ifconfig/vti.py
@@ -1,4 +1,4 @@
-# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2021-2022 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
@@ -53,3 +53,7 @@ class VTIIf(Interface):
self._cmd(cmd.format(**self.config))
self.set_interface('admin_state', 'down')
+
+ def get_mac(self):
+ """ Get a synthetic MAC address. """
+ return self.get_mac_synthetic()
diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py
index a2e0daabd..c6e3435ca 100644
--- a/python/vyos/migrator.py
+++ b/python/vyos/migrator.py
@@ -105,6 +105,11 @@ class Migrator(object):
sys_keys = list(sys_versions.keys())
sys_keys.sort()
+ # XXX 'bgp' needs to follow 'quagga':
+ if 'bgp' in sys_keys and 'quagga' in sys_keys:
+ sys_keys.insert(sys_keys.index('quagga'),
+ sys_keys.pop(sys_keys.index('bgp')))
+
rev_versions = {}
for key in sys_keys:
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index 0b916eaae..cd15e3878 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -247,7 +247,7 @@ def load_private_key(raw_data, passphrase=None, wrap_tags=True):
if wrap_tags:
raw_data = wrap_private_key(raw_data, passphrase)
- if passphrase:
+ if passphrase is not None:
passphrase = bytes(passphrase, 'utf-8')
try:
@@ -332,6 +332,54 @@ def verify_certificate(cert, ca_cert):
except InvalidSignature:
return False
+def verify_crl(crl, ca_cert):
+ # Verify CRL was signed by specified CA
+ if ca_cert.subject != crl.issuer:
+ return False
+
+ ca_public_key = ca_cert.public_key()
+ try:
+ if isinstance(ca_public_key, rsa.RSAPublicKeyWithSerialization):
+ ca_public_key.verify(
+ crl.signature,
+ crl.tbs_certlist_bytes,
+ padding=padding.PKCS1v15(),
+ algorithm=crl.signature_hash_algorithm)
+ elif isinstance(ca_public_key, dsa.DSAPublicKeyWithSerialization):
+ ca_public_key.verify(
+ crl.signature,
+ crl.tbs_certlist_bytes,
+ algorithm=crl.signature_hash_algorithm)
+ elif isinstance(ca_public_key, ec.EllipticCurvePublicKeyWithSerialization):
+ ca_public_key.verify(
+ crl.signature,
+ crl.tbs_certlist_bytes,
+ signature_algorithm=ec.ECDSA(crl.signature_hash_algorithm))
+ else:
+ return False # We cannot verify it
+ return True
+ except InvalidSignature:
+ return False
+
+def verify_ca_chain(sorted_names, pki_node):
+ if len(sorted_names) == 1: # Single cert, no chain
+ return True
+
+ for name in sorted_names:
+ cert = load_certificate(pki_node[name]['certificate'])
+ verified = False
+ for ca_name in sorted_names:
+ if name == ca_name:
+ continue
+ ca_cert = load_certificate(pki_node[ca_name]['certificate'])
+ if verify_certificate(cert, ca_cert):
+ verified = True
+ break
+ if not verified and name != sorted_names[-1]:
+ # Only permit top-most certificate to fail verify (e.g. signed by public CA not explicitly in chain)
+ return False
+ return True
+
# Certificate chain
def find_parent(cert, ca_certs):
@@ -357,3 +405,16 @@ def find_chain(cert, ca_certs):
chain.append(parent)
return chain
+
+def sort_ca_chain(ca_names, pki_node):
+ def ca_cmp(ca_name1, ca_name2, pki_node):
+ cert1 = load_certificate(pki_node[ca_name1]['certificate'])
+ cert2 = load_certificate(pki_node[ca_name2]['certificate'])
+
+ if verify_certificate(cert1, cert2): # cert1 is child of cert2
+ return -1
+ return 1
+
+ from functools import cmp_to_key
+ return sorted(ca_names, key=cmp_to_key(lambda cert1, cert2: ca_cmp(cert1, cert2, pki_node)))
+
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 132f5ddde..eb7f06480 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -554,7 +554,7 @@ def nft_default_rule(fw_conf, fw_name):
if 'enable_default_log' in fw_conf:
action_suffix = default_action[:1].upper()
- output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}] "')
+ output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"')
output.append(nft_action(default_action))
output.append(f'comment "{fw_name} default-action {default_action}"')
@@ -564,8 +564,9 @@ def nft_default_rule(fw_conf, fw_name):
def nft_state_policy(conf, state, ipv6=False):
out = [f'ct state {state}']
- if 'log' in conf and 'enable' in conf['log']:
- out.append('log')
+ if 'log' in conf:
+ log_level = conf['log']
+ out.append(f'log level {log_level}')
out.append('counter')
@@ -590,6 +591,26 @@ def nft_intra_zone_action(zone_conf, ipv6=False):
return f'jump {name_prefix}{name}'
return 'return'
+@register_filter('nft_nested_group')
+def nft_nested_group(out_list, includes, groups, key):
+ if not vyos_defined(out_list):
+ out_list = []
+
+ def add_includes(name):
+ if key in groups[name]:
+ for item in groups[name][key]:
+ if item in out_list:
+ continue
+ out_list.append(item)
+
+ if 'include' in groups[name]:
+ for name_inc in groups[name]['include']:
+ add_includes(name_inc)
+
+ for name in includes:
+ add_includes(name)
+ return out_list
+
@register_test('vyos_defined')
def vyos_defined(value, test_value=None, var_type=None):
"""
diff --git a/python/vyos/util.py b/python/vyos/util.py
index de55e108b..bee5d7aec 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -197,7 +197,7 @@ def read_file(fname, defaultonfailure=None):
return defaultonfailure
raise e
-def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None):
+def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None, append=False):
"""
Write content of data to given fname, should defaultonfailure be not None,
it is returned on failure to read.
@@ -212,7 +212,7 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=N
try:
""" Write a file to string """
bytes = 0
- with open(fname, 'w') as f:
+ with open(fname, 'w' if not append else 'a') as f:
bytes = f.write(data)
chown(fname, user, group)
chmod(fname, mode)
@@ -757,21 +757,26 @@ def dict_search_args(dict_object, *path):
dict_object = dict_object[item]
return dict_object
-def dict_search_recursive(dict_object, key):
+def dict_search_recursive(dict_object, key, path=[]):
""" Traverse a dictionary recurisvely and return the value of the key
we are looking for.
Thankfully copied from https://stackoverflow.com/a/19871956
+
+ Modified to yield optional path to found keys
"""
if isinstance(dict_object, list):
for i in dict_object:
- for x in dict_search_recursive(i, key):
- yield x
+ new_path = path + [i]
+ for x in dict_search_recursive(i, key, new_path):
+ yield x
elif isinstance(dict_object, dict):
if key in dict_object:
- yield dict_object[key]
- for j in dict_object.values():
- for x in dict_search_recursive(j, key):
+ new_path = path + [key]
+ yield dict_object[key], new_path
+ for k, j in dict_object.items():
+ new_path = path + [k]
+ for x in dict_search_recursive(j, key, new_path):
yield x
def get_bridge_fdb(interface):
diff --git a/scripts/build-command-templates b/scripts/build-command-templates
index 876f5877c..c8ae83d9d 100755
--- a/scripts/build-command-templates
+++ b/scripts/build-command-templates
@@ -27,6 +27,7 @@ import copy
import functools
from lxml import etree as ET
+from textwrap import fill
# Defaults
@@ -130,6 +131,7 @@ def get_properties(p, default=None):
# DNS forwarding for instance has multiple defaults - specified as whitespace separated list
tmp = ', '.join(default.text.split())
help += f' (default: {tmp})'
+ help = fill(help, width=64, subsequent_indent='\t\t\t')
props["help"] = help
except:
pass
@@ -192,12 +194,12 @@ def get_properties(p, default=None):
# so we get to emulate it
comp_exprs = []
for i in lists:
- comp_exprs.append("echo \"{0}\"".format(i.text))
+ comp_exprs.append(f'echo "{i.text}"')
for i in paths:
- comp_exprs.append("/bin/cli-shell-api listNodes {0}".format(i.text))
+ comp_exprs.append(f'/bin/cli-shell-api listNodes {i.text}')
for i in scripts:
- comp_exprs.append("sh -c \"{0}\"".format(i.text))
- comp_help = " && ".join(comp_exprs)
+ comp_exprs.append(f'sh -c "{i.text}"')
+ comp_help = ' && echo " " && '.join(comp_exprs)
props["comp_help"] = comp_help
except:
props["comp_help"] = []
diff --git a/smoketest/bin/vyos-configtest-pki b/smoketest/bin/vyos-configtest-pki
new file mode 100755
index 000000000..2f8af0e61
--- /dev/null
+++ b/smoketest/bin/vyos-configtest-pki
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022, 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/>.
+
+from os import system
+from vyos.pki import create_private_key
+from vyos.pki import create_certificate_request
+from vyos.pki import create_certificate
+from vyos.pki import create_certificate_revocation_list
+from vyos.pki import create_dh_parameters
+from vyos.pki import encode_certificate
+from vyos.pki import encode_dh_parameters
+from vyos.pki import encode_private_key
+
+subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'vyos'}
+ca_subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'vyos CA'}
+subca_subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'vyos SubCA'}
+
+ca_cert = '/config/auth/ovpn_test_ca.pem'
+ca_key = '/config/auth/ovpn_test_ca.key'
+ca_cert_chain = '/config/auth/ovpn_test_chain.pem'
+ca_crl = '/config/auth/ovpn_test_ca.crl'
+subca_cert = '/config/auth/ovpn_test_subca.pem'
+subca_csr = '/tmp/subca.csr'
+subca_key = '/config/auth/ovpn_test_subca.key'
+ssl_cert = '/config/auth/ovpn_test_server.pem'
+ssl_key = '/config/auth/ovpn_test_server.key'
+dh_pem = '/config/auth/ovpn_test_dh.pem'
+s2s_key = '/config/auth/ovpn_test_site2site.key'
+auth_key = '/config/auth/ovpn_test_tls_auth.key'
+
+def create_cert(subject, cert_path, key_path, sign_by=None, sign_by_key=None, ca=False, sub_ca=False):
+ priv_key = create_private_key('rsa', 2048)
+ cert_req = create_certificate_request(subject, priv_key)
+ cert = create_certificate(
+ cert_req,
+ sign_by if sign_by else cert_req,
+ sign_by_key if sign_by_key else priv_key,
+ is_ca=ca, is_sub_ca=sub_ca)
+
+ with open(cert_path, 'w') as f:
+ f.write(encode_certificate(cert))
+
+ with open(key_path, 'w') as f:
+ f.write(encode_private_key(priv_key))
+
+ return cert, priv_key
+
+def create_empty_crl(crl_path, sign_by, sign_by_key):
+ crl = create_certificate_revocation_list(sign_by, sign_by_key, [1])
+
+ with open(crl_path, 'w') as f:
+ f.write(encode_certificate(crl))
+
+ return crl
+
+if __name__ == '__main__':
+ # Create Root CA
+ ca_cert_obj, ca_key_obj = create_cert(ca_subject, ca_cert, ca_key, ca=True)
+
+ # Create Empty CRL
+ create_empty_crl(ca_crl, ca_cert_obj, ca_key_obj)
+
+ # Create Intermediate CA
+ subca_cert_obj, subca_key_obj = create_cert(
+ subca_subject, subca_cert, subca_key,
+ sign_by=ca_cert_obj, sign_by_key=ca_key_obj,
+ ca=True, sub_ca=True)
+
+ # Create Chain
+ with open(ca_cert_chain, 'w') as f:
+ f.write(encode_certificate(subca_cert_obj) + "\n")
+ f.write(encode_certificate(ca_cert_obj) + "\n")
+
+ # Create Server Cert
+ create_cert(subject, ssl_cert, ssl_key, sign_by=subca_cert_obj, sign_by_key=subca_key_obj)
+
+ # Create DH params
+ dh_params = create_dh_parameters()
+
+ with open(dh_pem, 'w') as f:
+ f.write(encode_dh_parameters(dh_params))
+
+ # OpenVPN S2S Key
+ system(f'openvpn --genkey secret {s2s_key}')
+
+ # OpenVPN Auth Key
+ system(f'openvpn --genkey secret {auth_key}')
diff --git a/smoketest/configs/bgp-small-as b/smoketest/configs.no-load/bgp-small-as
index 6b953a3f6..6b953a3f6 100644
--- a/smoketest/configs/bgp-small-as
+++ b/smoketest/configs.no-load/bgp-small-as
diff --git a/smoketest/configs/pki-ipsec b/smoketest/configs.no-load/pki-ipsec
index 6fc239d27..6fc239d27 100644
--- a/smoketest/configs/pki-ipsec
+++ b/smoketest/configs.no-load/pki-ipsec
diff --git a/smoketest/configs/vrf-bgp b/smoketest/configs.no-load/vrf-bgp
index 4ad372a36..4ad372a36 100644
--- a/smoketest/configs/vrf-bgp
+++ b/smoketest/configs.no-load/vrf-bgp
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index e6f89954f..23186b9b8 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -128,6 +128,10 @@ system {
name-server 192.168.0.1
syslog {
global {
+ archive {
+ file 5
+ size 512
+ }
facility all {
level info
}
diff --git a/smoketest/configs/bgp-dmvpn-spoke b/smoketest/configs/bgp-dmvpn-spoke
index 3d7503a9b..39b64b935 100644
--- a/smoketest/configs/bgp-dmvpn-spoke
+++ b/smoketest/configs/bgp-dmvpn-spoke
@@ -32,7 +32,7 @@ interfaces {
nat {
source {
rule 10 {
- log enable
+ log
outbound-interface pppoe1
source {
address 172.17.0.0/16
diff --git a/smoketest/configs/bgp-small-internet-exchange b/smoketest/configs/bgp-small-internet-exchange
index d51f87c4a..c9da8fa77 100644
--- a/smoketest/configs/bgp-small-internet-exchange
+++ b/smoketest/configs/bgp-small-internet-exchange
@@ -269,6 +269,14 @@ policy {
}
}
}
+ rule 31 {
+ action deny
+ match {
+ ipv6 {
+ nexthop 2001:db8::1
+ }
+ }
+ }
rule 40 {
action permit
set {
diff --git a/smoketest/configs/dialup-router-complex b/smoketest/configs/dialup-router-complex
index ac5ff5e99..909e6d17b 100644
--- a/smoketest/configs/dialup-router-complex
+++ b/smoketest/configs/dialup-router-complex
@@ -66,6 +66,27 @@ firewall {
action accept
protocol icmpv6
}
+ rule 15 {
+ action accept
+ icmpv6 {
+ type 1
+ }
+ protocol icmpv6
+ }
+ rule 16 {
+ action accept
+ icmpv6 {
+ type 1/1
+ }
+ protocol icmpv6
+ }
+ rule 17 {
+ action accept
+ icmpv6 {
+ type destination-unreachable
+ }
+ protocol icmpv6
+ }
}
ipv6-name ALLOW-ESTABLISHED-6 {
default-action drop
diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn
index 63d955738..56722d222 100644
--- a/smoketest/configs/dialup-router-medium-vpn
+++ b/smoketest/configs/dialup-router-medium-vpn
@@ -120,8 +120,9 @@ interfaces {
persistent-tunnel
remote-host 192.0.2.10
tls {
- ca-cert-file /config/auth/ovpn_test_ca.pem
+ ca-cert-file /config/auth/ovpn_test_chain.pem
cert-file /config/auth/ovpn_test_server.pem
+ crl-file /config/auth/ovpn_test_ca.crl
key-file /config/auth/ovpn_test_server.key
auth-file /config/auth/ovpn_test_tls_auth.key
}
@@ -152,7 +153,7 @@ interfaces {
remote-host 01.foo.com
remote-port 1194
tls {
- ca-cert-file /config/auth/ovpn_test_ca.pem
+ ca-cert-file /config/auth/ovpn_test_chain.pem
auth-file /config/auth/ovpn_test_tls_auth.key
}
}
diff --git a/smoketest/configs/isis-small b/smoketest/configs/isis-small
index 247ae32b5..5a4201988 100644
--- a/smoketest/configs/isis-small
+++ b/smoketest/configs/isis-small
@@ -74,7 +74,6 @@ system {
encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/
plaintext-password ""
}
- level admin
}
}
ntp {
diff --git a/smoketest/configs/vrf-basic b/smoketest/configs/vrf-basic
index ded33f683..20ac7a92f 100644
--- a/smoketest/configs/vrf-basic
+++ b/smoketest/configs/vrf-basic
@@ -196,7 +196,6 @@ system {
}
}
}
- nt
ntp {
server 0.pool.ntp.org {
}
diff --git a/smoketest/configs/vrf-ospf b/smoketest/configs/vrf-ospf
index 7855e86bf..aae6afb6b 100644
--- a/smoketest/configs/vrf-ospf
+++ b/smoketest/configs/vrf-ospf
@@ -51,7 +51,6 @@ system {
}
}
}
- nt
ntp {
server 0.pool.ntp.org {
}
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 816ba6dcd..55343b893 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -232,6 +232,9 @@ class BasicInterfaceTest:
for interface in self._interfaces:
base = self._base_path + [interface]
+ # just set the interface base without any option - some interfaces
+ # (VTI) do not require any option to be brought up
+ self.cli_set(base)
for option in self._options.get(interface, []):
self.cli_set(base + option.split())
@@ -635,6 +638,7 @@ class BasicInterfaceTest:
self.cli_set(path + ['ip', 'arp-cache-timeout', arp_tmo])
self.cli_set(path + ['ip', 'disable-arp-filter'])
self.cli_set(path + ['ip', 'disable-forwarding'])
+ self.cli_set(path + ['ip', 'enable-directed-broadcast'])
self.cli_set(path + ['ip', 'enable-arp-accept'])
self.cli_set(path + ['ip', 'enable-arp-announce'])
self.cli_set(path + ['ip', 'enable-arp-ignore'])
@@ -671,6 +675,9 @@ class BasicInterfaceTest:
tmp = read_file(f'{proc_base}/forwarding')
self.assertEqual('0', tmp)
+ tmp = read_file(f'{proc_base}/bc_forwarding')
+ self.assertEqual('1', tmp)
+
tmp = read_file(f'{proc_base}/proxy_arp')
self.assertEqual('1', tmp)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 37e6b9e7a..4de90e1ec 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -20,6 +20,7 @@ from glob import glob
from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
from vyos.util import cmd
sysfs_config = {
@@ -56,11 +57,63 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['firewall'])
self.cli_commit()
+ # Verify chains/sets are cleaned up from nftables
+ nftables_search = [
+ ['set M_smoketest_mac'],
+ ['set N_smoketest_network'],
+ ['set P_smoketest_port'],
+ ['set D_smoketest_domain'],
+ ['set RECENT_smoketest_4'],
+ ['chain NAME_smoketest']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip filter', inverse=True)
+
+ def verify_nftables(self, nftables_search, table, inverse=False, args=''):
+ nftables_output = cmd(f'sudo nft {args} list table {table}')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
+ def test_geoip(self):
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'drop'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['ip saddr @GEOIP_CC_smoketest_1', 'drop'],
+ ['ip saddr != @GEOIP_CC_smoketest_2', 'return']
+ ]
+ # -t prevents 1000+ GeoIP elements being returned
+ self.verify_nftables(nftables_search, 'ip filter', args='-t')
+
def test_groups(self):
+ hostmap_path = ['system', 'static-host-mapping', 'host-name']
+ example_org = ['192.0.2.8', '192.0.2.10', '192.0.2.11']
+
+ self.cli_set(hostmap_path + ['example.com', 'inet', '192.0.2.5'])
+ for ips in example_org:
+ self.cli_set(hostmap_path + ['example.org', 'inet', ips])
+
+ self.cli_commit()
+
self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05'])
self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53'])
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123'])
+ 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', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
@@ -68,41 +121,88 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
self.cli_commit()
-
nftables_search = [
['iifname "eth0"', 'jump NAME_smoketest'],
- ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'],
- ['ether saddr { 00:01:02:03:04:05 }', 'return']
+ ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'return'],
+ ['elements = { 172.16.99.0/24 }'],
+ ['elements = { 53, 123 }'],
+ ['ether saddr @M_smoketest_mac', 'return'],
+ ['elements = { 00:01:02:03:04:05 }'],
+ ['set D_smoketest_domain'],
+ ['elements = { 192.0.2.5, 192.0.2.8,'],
+ ['192.0.2.10, 192.0.2.11 }'],
+ ['ip saddr @D_smoketest_domain', 'return']
]
+ self.verify_nftables(nftables_search, 'ip filter')
- nftables_output = cmd('sudo nft list table ip filter')
+ self.cli_delete(['system', 'static-host-mapping'])
+ self.cli_commit()
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched, msg=search)
+ def test_nested_groups(self):
+ self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
+ self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24'])
+ self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'include', 'smoketest_network'])
+ self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53'])
+ self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'port', '123'])
+ self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'include', 'smoketest_port'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+
+ self.cli_commit()
+
+ # Test circular includes
+ self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_delete(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1'])
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump NAME_smoketest'],
+ ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'return'],
+ ['elements = { 172.16.99.0/24, 172.16.101.0/24 }'],
+ ['elements = { 53, 123 }']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip filter')
def test_basic_rules(self):
self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'enable-default-log'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'log', 'enable'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'log-level', 'debug'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'ttl', 'eq', '15'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'reject'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'protocol', 'tcp'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'log', 'enable'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'log-level', 'err'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'ttl', 'gt', '102'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'protocol', 'tcp'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'destination', 'port', '22'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'limit', 'rate', '5/minute'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'log', 'disable'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'drop'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'destination', 'port', '22'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'recent', 'count', '10'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'recent', 'time', 'minute'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
@@ -110,27 +210,25 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['iifname "eth0"', 'jump NAME_smoketest'],
- ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'],
- ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'],
+ ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" level debug', 'ip ttl 15','return'],
+ ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'log prefix "[smoketest-2-R]" level err', 'ip ttl > 102', 'reject'],
['tcp dport { 22 }', 'limit rate 5/minute', 'return'],
- ['smoketest default-action', 'drop']
+ ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'],
+ ['tcp dport { 22 }', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'drop']
]
- nftables_output = cmd('sudo nft list table ip filter')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched, msg=search)
+ self.verify_nftables(nftables_search, 'ip filter')
def test_basic_rules_ipv6(self):
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'enable-default-log'])
+
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'action', 'accept'])
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'source', 'address', '2002::1'])
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'destination', 'address', '2002::1:1'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'log', 'enable'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'log-level', 'crit'])
+
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'action', 'reject'])
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'protocol', 'tcp_udp'])
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'destination', 'port', '8888'])
@@ -141,20 +239,12 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['iifname "eth0"', 'jump NAME6_v6-smoketest'],
- ['saddr 2002::1', 'daddr 2002::1:1', 'return'],
+ ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" level crit', 'return'],
['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'reject'],
- ['smoketest default-action', 'drop']
+ ['smoketest default-action', 'log prefix "[v6-smoketest-default-D]"', 'drop']
]
- nftables_output = cmd('sudo nft list table ip6 filter')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched, msg=search)
+ self.verify_nftables(nftables_search, 'ip6 filter')
def test_state_policy(self):
self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept'])
@@ -173,6 +263,37 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_output = cmd(f'sudo nft list chain {table} {chain}')
self.assertTrue('jump VYOS_STATE_POLICY' in nftables_output)
+ def test_state_and_status_rules(self):
+ self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'state', 'established', 'enable'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'state', 'related', 'enable'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'state', 'invalid', 'enable'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'state', 'new', 'enable'])
+
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'connection-status', 'nat', 'destination'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'new', 'enable'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'established', 'enable'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'connection-status', 'nat', 'source'])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump NAME_smoketest'],
+ ['ct state { established, related }', 'return'],
+ ['ct state { invalid }', 'reject'],
+ ['ct state { new }', 'ct status { dnat }', 'return'],
+ ['ct state { established, new }', 'ct status { snat }', 'return'],
+ ['smoketest default-action', 'drop']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip filter')
+
def test_sysfs(self):
for name, conf in sysfs_config.items():
paths = glob(conf['sysfs'])
diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py
index 237abb487..cd3995ed9 100755
--- a/smoketest/scripts/cli/test_interfaces_bonding.py
+++ b/smoketest/scripts/cli/test_interfaces_bonding.py
@@ -49,7 +49,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
if not '.' in tmp:
cls._members.append(tmp)
- cls._options['bond0'] = []
+ cls._options = {'bond0' : []}
for member in cls._members:
cls._options['bond0'].append(f'member interface {member}')
cls._interfaces = list(cls._options)
@@ -136,7 +136,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
def test_bonding_hash_policy(self):
# Define available bonding hash policies
- hash_policies = ['layer2', 'layer2+3', 'layer2+3', 'encap2+3', 'encap3+4']
+ hash_policies = ['layer2', 'layer2+3', 'encap2+3', 'encap3+4']
for hash_policy in hash_policies:
for interface in self._interfaces:
for option in self._options.get(interface, []):
@@ -151,6 +151,29 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
defined_policy = read_file(f'/sys/class/net/{interface}/bonding/xmit_hash_policy').split()
self.assertEqual(defined_policy[0], hash_policy)
+ def test_bonding_mii_monitoring_interval(self):
+ for interface in self._interfaces:
+ for option in self._options.get(interface, []):
+ self.cli_set(self._base_path + [interface] + option.split())
+
+ self.cli_commit()
+
+ # verify default
+ for interface in self._interfaces:
+ tmp = read_file(f'/sys/class/net/{interface}/bonding/miimon').split()
+ self.assertIn('100', tmp)
+
+ mii_mon = '250'
+ for interface in self._interfaces:
+ self.cli_set(self._base_path + [interface, 'mii-mon-interval', mii_mon])
+
+ self.cli_commit()
+
+ # verify new CLI value
+ for interface in self._interfaces:
+ tmp = read_file(f'/sys/class/net/{interface}/bonding/miimon').split()
+ self.assertIn(mii_mon, tmp)
+
def test_bonding_multi_use_member(self):
# Define available bonding hash policies
for interface in ['bond10', 'bond20']:
@@ -165,6 +188,46 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
+ def test_bonding_source_interface(self):
+ # Re-use member interface that is already a source-interface
+ bond = 'bond99'
+ pppoe = 'pppoe98756'
+ member = next(iter(self._members))
+
+ self.cli_set(self._base_path + [bond, 'member', 'interface', member])
+ self.cli_set(['interfaces', 'pppoe', pppoe, 'source-interface', member])
+
+ # check validate() - can not add interface to bond, it is the source-interface of ...
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_delete(['interfaces', 'pppoe', pppoe])
+ self.cli_commit()
+
+ # verify config
+ slaves = read_file(f'/sys/class/net/{bond}/bonding/slaves').split()
+ self.assertIn(member, slaves)
+
+ def test_bonding_source_bridge_interface(self):
+ # Re-use member interface that is already a source-interface
+ bond = 'bond1097'
+ bridge = 'br6327'
+ member = next(iter(self._members))
+
+ self.cli_set(self._base_path + [bond, 'member', 'interface', member])
+ self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', member])
+
+ # check validate() - can not add interface to bond, it is a member of bridge ...
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_delete(['interfaces', 'bridge', bridge])
+ self.cli_commit()
+
+ # verify config
+ slaves = read_file(f'/sys/class/net/{bond}/bonding/slaves').split()
+ self.assertIn(member, slaves)
+
def test_bonding_uniq_member_description(self):
ethernet_path = ['interfaces', 'ethernet']
for interface in self._interfaces:
diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py
index ca0ead9e8..664dc48bc 100755
--- a/smoketest/scripts/cli/test_interfaces_bridge.py
+++ b/smoketest/scripts/cli/test_interfaces_bridge.py
@@ -86,9 +86,83 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
# validate member interface configuration
for member in self._members:
tmp = get_interface_config(member)
+ # verify member is assigned to the bridge
+ self.assertEqual(interface, tmp['master'])
# Isolated must be enabled as configured above
self.assertTrue(tmp['linkinfo']['info_slave_data']['isolated'])
+ def test_igmp_querier_snooping(self):
+ # Add member interfaces to bridge
+ for interface in self._interfaces:
+ base = self._base_path + [interface]
+
+ # assign members to bridge interface
+ for member in self._members:
+ base_member = base + ['member', 'interface', member]
+ self.cli_set(base_member)
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ # Verify IGMP default configuration
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping')
+ self.assertEqual(tmp, '0')
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier')
+ self.assertEqual(tmp, '0')
+
+ # Enable IGMP snooping
+ for interface in self._interfaces:
+ base = self._base_path + [interface]
+ self.cli_set(base + ['igmp', 'snooping'])
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ # Verify IGMP snooping configuration
+ # Verify IGMP default configuration
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping')
+ self.assertEqual(tmp, '1')
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier')
+ self.assertEqual(tmp, '0')
+
+ # Enable IGMP querieer
+ for interface in self._interfaces:
+ base = self._base_path + [interface]
+ self.cli_set(base + ['igmp', 'querier'])
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ # Verify IGMP snooping & querier configuration
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping')
+ self.assertEqual(tmp, '1')
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier')
+ self.assertEqual(tmp, '1')
+
+ # Disable IGMP
+ for interface in self._interfaces:
+ base = self._base_path + [interface]
+ self.cli_delete(base + ['igmp'])
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ # Verify IGMP snooping & querier configuration
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping')
+ self.assertEqual(tmp, '0')
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier')
+ self.assertEqual(tmp, '0')
+
+ # validate member interface configuration
+ for member in self._members:
+ tmp = get_interface_config(member)
+ # verify member is assigned to the bridge
+ self.assertEqual(interface, tmp['master'])
+
def test_add_remove_bridge_member(self):
# Add member interfaces to bridge and set STP cost/priority
@@ -230,7 +304,7 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_delete(self._base_path + [interface, 'member'])
- def test_bridge_vlan_members(self):
+ def test_bridge_vif_members(self):
# T2945: ensure that VIFs are not dropped from bridge
vifs = ['300', '400']
for interface in self._interfaces:
@@ -255,5 +329,34 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_delete(['interfaces', 'ethernet', member, 'vif', vif])
self.cli_delete(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif}'])
+ def test_bridge_vif_s_vif_c_members(self):
+ # T2945: ensure that VIFs are not dropped from bridge
+ vifs = ['300', '400']
+ vifc = ['301', '401']
+ for interface in self._interfaces:
+ for member in self._members:
+ for vif_s in vifs:
+ for vif_c in vifc:
+ self.cli_set(['interfaces', 'ethernet', member, 'vif-s', vif_s, 'vif-c', vif_c])
+ self.cli_set(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif_s}.{vif_c}'])
+
+ self.cli_commit()
+
+ # Verify config
+ for interface in self._interfaces:
+ for member in self._members:
+ for vif_s in vifs:
+ for vif_c in vifc:
+ # member interface must be assigned to the bridge
+ self.assertTrue(os.path.exists(f'/sys/class/net/{interface}/lower_{member}.{vif_s}.{vif_c}'))
+
+ # delete all members
+ for interface in self._interfaces:
+ for member in self._members:
+ for vif_s in vifs:
+ self.cli_delete(['interfaces', 'ethernet', member, 'vif-s', vif_s])
+ for vif_c in vifc:
+ self.cli_delete(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif_s}.{vif_c}'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_vti.py b/smoketest/scripts/cli/test_interfaces_vti.py
new file mode 100755
index 000000000..9cbf104f0
--- /dev/null
+++ b/smoketest/scripts/cli/test_interfaces_vti.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 unittest
+
+from base_interfaces_test import BasicInterfaceTest
+
+class VTIInterfaceTest(BasicInterfaceTest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls._test_ip = True
+ cls._test_ipv6 = True
+ cls._test_mtu = True
+ cls._base_path = ['interfaces', 'vti']
+ cls._interfaces = ['vti10', 'vti20', 'vti30']
+
+ # call base-classes classmethod
+ super(VTIInterfaceTest, cls).setUpClass()
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_load_balancning_wan.py b/smoketest/scripts/cli/test_load_balancning_wan.py
new file mode 100755
index 000000000..303dece86
--- /dev/null
+++ b/smoketest/scripts/cli/test_load_balancning_wan.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import unittest
+import time
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
+from vyos.ifconfig import Section
+from vyos.util import call
+from vyos.util import cmd
+
+
+base_path = ['load-balancing']
+
+
+def create_netns(name):
+ return call(f'sudo ip netns add {name}')
+
+def create_veth_pair(local='veth0', peer='ceth0'):
+ return call(f'sudo ip link add {local} type veth peer name {peer}')
+
+def move_interface_to_netns(iface, netns_name):
+ return call(f'sudo ip link set {iface} netns {netns_name}')
+
+def rename_interface(iface, new_name):
+ return call(f'sudo ip link set {iface} name {new_name}')
+
+def cmd_in_netns(netns, cmd):
+ return call(f'sudo ip netns exec {netns} {cmd}')
+
+def delete_netns(name):
+ return call(f'sudo ip netns del {name}')
+
+
+class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestLoadBalancingWan, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ def test_table_routes(self):
+
+ ns1 = 'ns201'
+ ns2 = 'ns202'
+ ns3 = 'ns203'
+ iface1 = 'eth201'
+ iface2 = 'eth202'
+ iface3 = 'eth203'
+ container_iface1 = 'ceth0'
+ container_iface2 = 'ceth1'
+ container_iface3 = 'ceth2'
+
+ # Create network namespeces
+ create_netns(ns1)
+ create_netns(ns2)
+ create_netns(ns3)
+ create_veth_pair(iface1, container_iface1)
+ create_veth_pair(iface2, container_iface2)
+ create_veth_pair(iface3, container_iface3)
+ move_interface_to_netns(container_iface1, ns1)
+ move_interface_to_netns(container_iface2, ns2)
+ move_interface_to_netns(container_iface3, ns3)
+ call(f'sudo ip address add 203.0.113.10/24 dev {iface1}')
+ call(f'sudo ip address add 192.0.2.10/24 dev {iface2}')
+ call(f'sudo ip address add 198.51.100.10/24 dev {iface3}')
+ call(f'sudo ip link set dev {iface1} up')
+ call(f'sudo ip link set dev {iface2} up')
+ call(f'sudo ip link set dev {iface3} up')
+ cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0')
+ cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0')
+ cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0')
+ cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0')
+ cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0')
+ cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0')
+ cmd_in_netns(ns1, 'ip link set dev eth0 up')
+ cmd_in_netns(ns2, 'ip link set dev eth0 up')
+ cmd_in_netns(ns3, 'ip link set dev eth0 up')
+
+ # Set load-balancing configuration
+ 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'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1'])
+
+ 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'])
+
+
+ # commit changes
+ self.cli_commit()
+
+ time.sleep(5)
+ # Check default routes in tables 201, 202
+ # Expected values
+ original = 'default via 203.0.113.1 dev eth201'
+ tmp = cmd('sudo ip route show table 201')
+ self.assertEqual(tmp, original)
+
+ original = 'default via 192.0.2.1 dev eth202'
+ tmp = cmd('sudo ip route show table 202')
+ self.assertEqual(tmp, original)
+
+ # Delete veth interfaces and netns
+ for iface in [iface1, iface2]:
+ call(f'sudo ip link del dev {iface}')
+
+ delete_netns(ns1)
+ delete_netns(ns2)
+
+ def test_check_chains(self):
+
+ ns1 = 'nsA'
+ ns2 = 'nsB'
+ ns3 = 'nsC'
+ iface1 = 'veth1'
+ iface2 = 'veth2'
+ iface3 = 'veth3'
+ 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_isp2 = """table ip mangle {
+ chain ISP_veth2 {
+ counter ct mark set 0xca
+ counter meta mark set 0xca
+ counter accept
+ }
+}"""
+ mangle_prerouting = """table ip mangle {
+ chain 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 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 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 {
+ counter jump WANLOADBALANCE
+ return
+ }
+}"""
+
+ # Create network namespeces
+ create_netns(ns1)
+ create_netns(ns2)
+ create_netns(ns3)
+ create_veth_pair(iface1, container_iface1)
+ create_veth_pair(iface2, container_iface2)
+ create_veth_pair(iface3, container_iface3)
+ move_interface_to_netns(container_iface1, ns1)
+ move_interface_to_netns(container_iface2, ns2)
+ move_interface_to_netns(container_iface3, ns3)
+ call(f'sudo ip address add 203.0.113.10/24 dev {iface1}')
+ call(f'sudo ip address add 192.0.2.10/24 dev {iface2}')
+ call(f'sudo ip address add 198.51.100.10/24 dev {iface3}')
+ call(f'sudo ip link set dev {iface1} up')
+ call(f'sudo ip link set dev {iface2} up')
+ call(f'sudo ip link set dev {iface3} up')
+ cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0')
+ cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0')
+ cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0')
+ cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0')
+ cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0')
+ cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0')
+ cmd_in_netns(ns1, 'ip link set dev eth0 up')
+ cmd_in_netns(ns2, 'ip link set dev eth0 up')
+ cmd_in_netns(ns3, 'ip link set dev eth0 up')
+
+ # Set load-balancing configuration
+ 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'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1'])
+ 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()
+
+ time.sleep(5)
+
+ # Check mangle chains
+ tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface1}')
+ self.assertEqual(tmp, mangle_isp1)
+
+ tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface2}')
+ self.assertEqual(tmp, mangle_isp2)
+
+ tmp = cmd(f'sudo nft -s list chain 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')
+ 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]:
+ call(f'sudo ip link del dev {iface}')
+
+ delete_netns(ns1)
+ delete_netns(ns2)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_pki.py b/smoketest/scripts/cli/test_pki.py
index e92123dbc..cba5ffdde 100755
--- a/smoketest/scripts/cli/test_pki.py
+++ b/smoketest/scripts/cli/test_pki.py
@@ -128,6 +128,27 @@ g6a75NnEXo0J6YLAOOxd8fD2/HidhbceCmTF+3msidIzCsBidBkgn6V5TXx2IyMS
xGsJxVHfSKeooUQn6q76sg==
"""
+valid_update_cert = """
+MIICJTCCAcugAwIBAgIUZJqjNmPfVQwePjNFBtB6WI31ThMwCgYIKoZIzj0EAwIw
+VzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv
+bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0yMjA1
+MzExNTE3NDlaFw0yMzA1MzExNTE3NDlaMFcxCzAJBgNVBAYTAkdCMRMwEQYDVQQI
+DApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5T1Mx
+EDAOBgNVBAMMB3Z5b3MuaW8wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQMe0h/
+3CdD8mEgy+klk55QfJ8R3ZycefxCn4abWjzTXz/TuCIxqb4wpRT8DZtIn4NRimFT
+mODYdEDOYxFtZm37o3UwczAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAT
+BgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUqH7KSZpzArpMFuxLXqI8e1QD
+fBkwHwYDVR0jBBgwFoAUqH7KSZpzArpMFuxLXqI8e1QDfBkwCgYIKoZIzj0EAwID
+SAAwRQIhAKofUgRtcUljmbubPF6sqHtn/3TRvuafl8VfPbk3s2bJAiBp3Q1AnU/O
+i7t5FGhCgnv5m8DW2F3LZPCJdW4ELQ3d9A==
+"""
+
+valid_update_private_key = """
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvyODf22w/p7Zgfz9
+dyLIT09LqLOrUN6zbAecfukiiiyhRANCAAQMe0h/3CdD8mEgy+klk55QfJ8R3Zyc
+efxCn4abWjzTXz/TuCIxqb4wpRT8DZtIn4NRimFTmODYdEDOYxFtZm37
+"""
+
class TestPKI(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
@@ -189,5 +210,41 @@ class TestPKI(VyOSUnitTestSHIM.TestCase):
with self.assertRaises(ConfigSessionError):
self.cli_commit()
+ def test_certificate_in_use(self):
+ self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')])
+ self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')])
+ self.cli_commit()
+
+ self.cli_set(['service', 'https', 'certificates', 'certificate', 'smoketest'])
+ self.cli_commit()
+
+ self.cli_delete(base_path + ['certificate', 'smoketest'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_delete(['service', 'https', 'certificates', 'certificate'])
+
+ def test_certificate_https_update(self):
+ self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')])
+ self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')])
+ self.cli_commit()
+
+ self.cli_set(['service', 'https', 'certificates', 'certificate', 'smoketest'])
+ self.cli_commit()
+
+ cert_data = None
+
+ with open('/etc/ssl/certs/smoketest.pem') as f:
+ cert_data = f.read()
+
+ self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_update_cert.replace('\n','')])
+ self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_update_private_key.replace('\n','')])
+ self.cli_commit()
+
+ with open('/etc/ssl/certs/smoketest.pem') as f:
+ self.assertNotEqual(cert_data, f.read())
+
+ self.cli_delete(['service', 'https', 'certificates', 'certificate'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index 3e8dd35ae..3d37d22ae 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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
@@ -711,13 +711,20 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
large_community_list = 'bgp-large-community-123456'
prefix_list = 'foo-pfx-list'
- ipv6_nexthop = 'fe80::1'
+ ipv6_nexthop_address = 'fe80::1'
local_pref = '300'
metric = '50'
peer = '2.3.4.5'
+ peerv6 = '2001:db8::1'
tag = '6542'
goto = '25'
+ ipv4_nexthop_address= '192.0.2.2'
+ ipv4_prefix_len= '18'
+ ipv6_prefix_len= '122'
+ ipv4_nexthop_type= 'blackhole'
+ ipv6_nexthop_type= 'blackhole'
+
test_data = {
'foo-map-bar' : {
'rule' : {
@@ -785,7 +792,11 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
'30' : {
'action' : 'permit',
'match' : {
- 'ipv6-nexthop' : ipv6_nexthop,
+ 'ipv6-nexthop-address' : ipv6_nexthop_address,
+ 'ipv6-nexthop-access-list' : access_list,
+ 'ipv6-nexthop-prefix-list' : prefix_list,
+ 'ipv6-nexthop-type' : ipv6_nexthop_type,
+ 'ipv6-address-pfx-len' : ipv6_prefix_len,
'large-community' : large_community_list,
'local-pref' : local_pref,
'metric': metric,
@@ -793,6 +804,33 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
'peer' : peer,
},
},
+
+ '31' : {
+ 'action' : 'permit',
+ 'match' : {
+ 'peer' : peerv6,
+ },
+ },
+
+ '40' : {
+ 'action' : 'permit',
+ 'match' : {
+ 'ip-nexthop-addr' : ipv4_nexthop_address,
+ 'ip-address-pfx-len' : ipv4_prefix_len,
+ },
+ },
+ '42' : {
+ 'action' : 'deny',
+ 'match' : {
+ 'ip-nexthop-plen' : ipv4_prefix_len,
+ },
+ },
+ '44' : {
+ 'action' : 'permit',
+ 'match' : {
+ 'ip-nexthop-type' : ipv4_nexthop_type,
+ },
+ },
},
},
'complicated-configuration' : {
@@ -849,6 +887,35 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
'evpn-vni' : '1234',
},
},
+ '20' : {
+ 'action' : 'permit',
+ 'set' : {
+ 'evpn-gateway-ipv4' : '192.0.2.99',
+ 'evpn-gateway-ipv6' : '2001:db8:f00::1',
+ },
+ },
+ },
+ },
+ 'relative-metric' : {
+ 'rule' : {
+ '10' : {
+ 'action' : 'permit',
+ 'match' : {
+ 'ip-nexthop-addr' : ipv4_nexthop_address,
+ },
+ 'set' : {
+ 'metric' : '+10',
+ },
+ },
+ '20' : {
+ 'action' : 'permit',
+ 'match' : {
+ 'ip-nexthop-addr' : ipv4_nexthop_address,
+ },
+ 'set' : {
+ 'metric' : '-20',
+ },
+ },
},
},
}
@@ -910,10 +977,18 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'access-list', rule_config['match']['ip-address-acl']])
if 'ip-address-pfx' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'prefix-list', rule_config['match']['ip-address-pfx']])
+ if 'ip-address-pfx-len' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'prefix-len', rule_config['match']['ip-address-pfx-len']])
if 'ip-nexthop-acl' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'access-list', rule_config['match']['ip-nexthop-acl']])
if 'ip-nexthop-pfx' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'prefix-list', rule_config['match']['ip-nexthop-pfx']])
+ if 'ip-nexthop-addr' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'address', rule_config['match']['ip-nexthop-addr']])
+ if 'ip-nexthop-plen' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'prefix-len', rule_config['match']['ip-nexthop-plen']])
+ if 'ip-nexthop-type' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'type', rule_config['match']['ip-nexthop-type']])
if 'ip-route-source-acl' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ip', 'route-source', 'access-list', rule_config['match']['ip-route-source-acl']])
if 'ip-route-source-pfx' in rule_config['match']:
@@ -922,8 +997,16 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'access-list', rule_config['match']['ipv6-address-acl']])
if 'ipv6-address-pfx' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'prefix-list', rule_config['match']['ipv6-address-pfx']])
- if 'ipv6-nexthop' in rule_config['match']:
- self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', rule_config['match']['ipv6-nexthop']])
+ if 'ipv6-address-pfx-len' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'prefix-len', rule_config['match']['ipv6-address-pfx-len']])
+ if 'ipv6-nexthop-address' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'address', rule_config['match']['ipv6-nexthop-address']])
+ if 'ipv6-nexthop-access-list' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'access-list', rule_config['match']['ipv6-nexthop-access-list']])
+ if 'ipv6-nexthop-prefix-list' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'prefix-list', rule_config['match']['ipv6-nexthop-prefix-list']])
+ if 'ipv6-nexthop-type' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'type', rule_config['match']['ipv6-nexthop-type']])
if 'large-community' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'large-community', 'large-community-list', rule_config['match']['large-community']])
if 'local-pref' in rule_config['match']:
@@ -996,6 +1079,10 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['rule', rule, 'set', 'tag', rule_config['set']['tag']])
if 'weight' in rule_config['set']:
self.cli_set(path + ['rule', rule, 'set', 'weight', rule_config['set']['weight']])
+ if 'evpn-gateway-ipv4' in rule_config['set']:
+ self.cli_set(path + ['rule', rule, 'set', 'evpn', 'gateway', 'ipv4', rule_config['set']['evpn-gateway-ipv4']])
+ if 'evpn-gateway-ipv6' in rule_config['set']:
+ self.cli_set(path + ['rule', rule, 'set', 'evpn', 'gateway', 'ipv6', rule_config['set']['evpn-gateway-ipv6']])
self.cli_commit()
@@ -1046,12 +1133,24 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
if 'ip-address-pfx' in rule_config['match']:
tmp = f'match ip address prefix-list {rule_config["match"]["ip-address-pfx"]}'
self.assertIn(tmp, config)
+ if 'ip-address-pfx-len' in rule_config['match']:
+ tmp = f'match ip address prefix-len {rule_config["match"]["ip-address-pfx-len"]}'
+ self.assertIn(tmp, config)
if 'ip-nexthop-acl' in rule_config['match']:
tmp = f'match ip next-hop {rule_config["match"]["ip-nexthop-acl"]}'
self.assertIn(tmp, config)
if 'ip-nexthop-pfx' in rule_config['match']:
tmp = f'match ip next-hop prefix-list {rule_config["match"]["ip-nexthop-pfx"]}'
self.assertIn(tmp, config)
+ if 'ip-nexthop-addr' in rule_config['match']:
+ tmp = f'match ip next-hop address {rule_config["match"]["ip-nexthop-addr"]}'
+ self.assertIn(tmp, config)
+ if 'ip-nexthop-plen' in rule_config['match']:
+ tmp = f'match ip next-hop prefix-len {rule_config["match"]["ip-nexthop-plen"]}'
+ self.assertIn(tmp, config)
+ if 'ip-nexthop-type' in rule_config['match']:
+ tmp = f'match ip next-hop type {rule_config["match"]["ip-nexthop-type"]}'
+ self.assertIn(tmp, config)
if 'ip-route-source-acl' in rule_config['match']:
tmp = f'match ip route-source {rule_config["match"]["ip-route-source-acl"]}'
self.assertIn(tmp, config)
@@ -1064,8 +1163,20 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
if 'ipv6-address-pfx' in rule_config['match']:
tmp = f'match ipv6 address prefix-list {rule_config["match"]["ipv6-address-pfx"]}'
self.assertIn(tmp, config)
- if 'ipv6-nexthop' in rule_config['match']:
- tmp = f'match ipv6 next-hop address {rule_config["match"]["ipv6-nexthop"]}'
+ if 'ipv6-address-pfx-len' in rule_config['match']:
+ tmp = f'match ipv6 address prefix-len {rule_config["match"]["ipv6-address-pfx-len"]}'
+ self.assertIn(tmp, config)
+ if 'ipv6-nexthop-address' in rule_config['match']:
+ tmp = f'match ipv6 next-hop address {rule_config["match"]["ipv6-nexthop-address"]}'
+ self.assertIn(tmp, config)
+ if 'ipv6-nexthop-access-list' in rule_config['match']:
+ tmp = f'match ipv6 next-hop {rule_config["match"]["ipv6-nexthop-access-list"]}'
+ self.assertIn(tmp, config)
+ if 'ipv6-nexthop-prefix-list' in rule_config['match']:
+ tmp = f'match ipv6 next-hop prefix-list {rule_config["match"]["ipv6-nexthop-prefix-list"]}'
+ self.assertIn(tmp, config)
+ if 'ipv6-nexthop-type' in rule_config['match']:
+ tmp = f'match ipv6 next-hop type {rule_config["match"]["ipv6-nexthop-type"]}'
self.assertIn(tmp, config)
if 'large-community' in rule_config['match']:
tmp = f'match large-community {rule_config["match"]["large-community"]}'
@@ -1155,6 +1266,10 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
tmp += 'tag ' + rule_config['set']['tag']
elif 'weight' in rule_config['set']:
tmp += 'weight ' + rule_config['set']['weight']
+ elif 'vpn-gateway-ipv4' in rule_config['set']:
+ tmp += 'evpn gateway ipv4 ' + rule_config['set']['vpn-gateway-ipv4']
+ elif 'vpn-gateway-ipv6' in rule_config['set']:
+ tmp += 'evpn gateway ipv6 ' + rule_config['set']['vpn-gateway-ipv6']
self.assertIn(tmp, config)
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index 9035f0832..534cfb082 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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,44 +23,88 @@ from vyos.util import cmd
mark = '100'
table_mark_offset = 0x7fffffff
table_id = '101'
+interface = 'eth0'
+interface_ip = '172.16.10.1/24'
class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '172.16.10.1/24'])
- self.cli_set(['protocols', 'static', 'table', '101', 'route', '0.0.0.0/0', 'interface', 'eth0'])
+ @classmethod
+ def setUpClass(cls):
+ super(TestPolicyRoute, cls).setUpClass()
+
+ cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', interface_ip])
+ cls.cli_set(cls, ['protocols', 'static', 'table', table_id, 'route', '0.0.0.0/0', 'interface', interface])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', interface_ip])
+ cls.cli_delete(cls, ['protocols', 'static', 'table', table_id])
+
+ super(TestPolicyRoute, cls).tearDownClass()
def tearDown(self):
- self.cli_delete(['interfaces', 'ethernet', 'eth0'])
- self.cli_delete(['protocols', 'static'])
+ self.cli_delete(['interfaces', 'ethernet', interface, 'policy'])
self.cli_delete(['policy', 'route'])
self.cli_delete(['policy', 'route6'])
self.cli_commit()
+ nftables_search = [
+ ['set N_smoketest_network'],
+ ['set N_smoketest_network1'],
+ ['chain VYOS_PBR_smoketest']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip filter', inverse=True)
+
+ def verify_nftables(self, nftables_search, table, inverse=False):
+ nftables_output = cmd(f'sudo nft list table {table}')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
+ def test_pbr_group(self):
+ self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
+ self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24'])
+ self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'include', 'smoketest_network'])
+
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'group', 'network-group', 'smoketest_network1'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark])
+
+ self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ [f'iifname "{interface}"','jump VYOS_PBR_smoketest'],
+ ['ip daddr @N_smoketest_network1', 'ip saddr @N_smoketest_network'],
+ ]
+
+ self.verify_nftables(nftables_search, 'ip mangle')
+
+ self.cli_delete(['firewall'])
+
def test_pbr_mark(self):
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark])
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest'])
+ self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest'])
self.cli_commit()
mark_hex = "{0:#010x}".format(int(mark))
nftables_search = [
- ['iifname "eth0"', 'jump VYOS_PBR_smoketest'],
+ [f'iifname "{interface}"','jump VYOS_PBR_smoketest'],
['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'meta mark set ' + mark_hex],
]
- nftables_output = cmd('sudo nft list table ip mangle')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched)
+ self.verify_nftables(nftables_search, 'ip mangle')
def test_pbr_table(self):
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp'])
@@ -72,8 +116,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888'])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'table', table_id])
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest'])
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route6', 'smoketest6'])
+ self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest'])
+ self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6'])
self.cli_commit()
@@ -82,36 +126,20 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
# IPv4
nftables_search = [
- ['iifname "eth0"', 'jump VYOS_PBR_smoketest'],
+ [f'iifname "{interface}"', 'jump VYOS_PBR_smoketest'],
['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'meta mark set ' + mark_hex]
]
- nftables_output = cmd('sudo nft list table ip mangle')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched)
+ self.verify_nftables(nftables_search, 'ip mangle')
# IPv6
nftables6_search = [
- ['iifname "eth0"', 'jump VYOS_PBR6_smoketest'],
+ [f'iifname "{interface}"', 'jump VYOS_PBR6_smoketest'],
['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex]
]
- nftables6_output = cmd('sudo nft list table ip6 mangle')
-
- for search in nftables6_search:
- matched = False
- for line in nftables6_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched)
+ self.verify_nftables(nftables6_search, 'ip6 mangle')
# IP rule fwmark -> table
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 6f92457b2..9c0c93779 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -887,6 +887,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
remote_asn = str(int(ASN) + 150)
neighbor = '192.0.2.1'
peer_group = 'bar'
+ interface = 'eth0'
self.cli_set(base_path + ['local-as', ASN])
self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn])
@@ -898,6 +899,20 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_delete(base_path + ['neighbor', neighbor, 'remote-as'])
+ # re-test with interface based peer-group
+ self.cli_set(base_path + ['neighbor', interface, 'interface', 'peer-group', peer_group])
+ self.cli_set(base_path + ['neighbor', interface, 'interface', 'remote-as', 'external'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['neighbor', interface, 'interface', 'remote-as'])
+
+ # re-test with interface based v6only peer-group
+ self.cli_set(base_path + ['neighbor', interface, 'interface', 'v6only', 'peer-group', peer_group])
+ self.cli_set(base_path + ['neighbor', interface, 'interface', 'v6only', 'remote-as', 'external'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['neighbor', interface, 'interface', 'v6only', 'remote-as'])
+
self.cli_commit()
frrconfig = self.getFRRconfig(f'router bgp {ASN}')
diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py
index 80d4e79f9..11385adb5 100755
--- a/smoketest/scripts/cli/test_protocols_rip.py
+++ b/smoketest/scripts/cli/test_protocols_rip.py
@@ -31,26 +31,38 @@ route_map = 'FooBar123'
base_path = ['protocols', 'rip']
class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self.cli_set(['policy', 'access-list', acl_in, 'rule', '10', 'action', 'permit'])
- self.cli_set(['policy', 'access-list', acl_in, 'rule', '10', 'source', 'any'])
- self.cli_set(['policy', 'access-list', acl_in, 'rule', '10', 'destination', 'any'])
- self.cli_set(['policy', 'access-list', acl_out, 'rule', '20', 'action', 'deny'])
- self.cli_set(['policy', 'access-list', acl_out, 'rule', '20', 'source', 'any'])
- self.cli_set(['policy', 'access-list', acl_out, 'rule', '20', 'destination', 'any'])
- self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '100', 'action', 'permit'])
- self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '100', 'prefix', '192.0.2.0/24'])
- self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '200', 'action', 'deny'])
- self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '200', 'prefix', '192.0.2.0/24'])
- self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
+ @classmethod
+ def setUpClass(cls):
+ super(TestProtocolsRIP, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ cls.cli_set(cls, ['policy', 'access-list', acl_in, 'rule', '10', 'action', 'permit'])
+ cls.cli_set(cls, ['policy', 'access-list', acl_in, 'rule', '10', 'source', 'any'])
+ cls.cli_set(cls, ['policy', 'access-list', acl_in, 'rule', '10', 'destination', 'any'])
+ cls.cli_set(cls, ['policy', 'access-list', acl_out, 'rule', '20', 'action', 'deny'])
+ cls.cli_set(cls, ['policy', 'access-list', acl_out, 'rule', '20', 'source', 'any'])
+ cls.cli_set(cls, ['policy', 'access-list', acl_out, 'rule', '20', 'destination', 'any'])
+ cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '100', 'action', 'permit'])
+ cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '100', 'prefix', '192.0.2.0/24'])
+ cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '200', 'action', 'deny'])
+ cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '200', 'prefix', '192.0.2.0/24'])
+ cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, ['policy', 'access-list', acl_in])
+ cls.cli_delete(cls, ['policy', 'access-list', acl_out])
+ cls.cli_delete(cls, ['policy', 'prefix-list', prefix_list_in])
+ cls.cli_delete(cls, ['policy', 'prefix-list', prefix_list_out])
+ cls.cli_delete(cls, ['policy', 'route-map', route_map])
+
+ super(TestProtocolsRIP, cls).tearDownClass()
def tearDown(self):
self.cli_delete(base_path)
- self.cli_delete(['policy', 'access-list', acl_in])
- self.cli_delete(['policy', 'access-list', acl_out])
- self.cli_delete(['policy', 'prefix-list', prefix_list_in])
- self.cli_delete(['policy', 'prefix-list', prefix_list_out])
- self.cli_delete(['policy', 'route-map', route_map])
self.cli_commit()
# Check for running process
@@ -146,5 +158,25 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase):
frrconfig = self.getFRRconfig(zebra_route_map)
self.assertNotIn(zebra_route_map, frrconfig)
+ def test_rip_03_version(self):
+ rx_version = '1'
+ tx_version = '2'
+ interface = 'eth0'
+
+ self.cli_set(base_path + ['version', tx_version])
+ self.cli_set(base_path + ['interface', interface, 'send', 'version', tx_version])
+ self.cli_set(base_path + ['interface', interface, 'receive', 'version', rx_version])
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify FRR configuration
+ frrconfig = self.getFRRconfig('router rip')
+ self.assertIn(f'version {tx_version}', frrconfig)
+
+ frrconfig = self.getFRRconfig(f'interface {interface}')
+ self.assertIn(f' ip rip receive version {rx_version}', frrconfig)
+ self.assertIn(f' ip rip send version {tx_version}', frrconfig)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
index 5929f8cba..65b676451 100755
--- a/smoketest/scripts/cli/test_service_dns_forwarding.py
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2021 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 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
@@ -39,7 +39,18 @@ def get_config_value(key, file=CONFIG_FILE):
return tmp[0]
class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestServicePowerDNS, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
def tearDown(self):
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
# Delete DNS forwarding configuration
self.cli_delete(base_path)
self.cli_commit()
@@ -100,9 +111,6 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
tmp = get_config_value('serve-rfc1918')
self.assertEqual(tmp, 'yes')
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
def test_dnssec(self):
# DNSSEC option testing
@@ -121,9 +129,6 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
tmp = get_config_value('dnssec')
self.assertEqual(tmp, option)
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
def test_external_nameserver(self):
# Externe Domain Name Servers (DNS) addresses
@@ -147,9 +152,6 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
tmp = get_config_value('export-etc-hosts')
self.assertEqual(tmp, 'yes')
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
def test_domain_forwarding(self):
for network in allow_from:
self.cli_set(base_path + ['allow-from', network])
@@ -186,9 +188,6 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
if domain == domains[1]:
self.assertIn(f'addNTA("{domain}", "static")', hosts_conf)
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
def test_no_rfc1918_forwarding(self):
for network in allow_from:
self.cli_set(base_path + ['allow-from', network])
@@ -204,9 +203,26 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
tmp = get_config_value('serve-rfc1918')
self.assertEqual(tmp, 'no')
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_dns64(self):
+ dns_prefix = '64:ff9b::/96'
+
+ for network in allow_from:
+ self.cli_set(base_path + ['allow-from', network])
+ for address in listen_adress:
+ self.cli_set(base_path + ['listen-address', address])
+
+ # Check dns64-prefix - must be prefix /96
+ self.cli_set(base_path + ['dns64-prefix', '2001:db8:aabb::/64'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + ['dns64-prefix', dns_prefix])
+
+ # commit changes
+ self.cli_commit()
+
+ # verify dns64-prefix configuration
+ tmp = get_config_value('dns64-prefix')
+ self.assertEqual(tmp, dns_prefix)
if __name__ == '__main__':
unittest.main(verbosity=2)
-
diff --git a/smoketest/scripts/cli/test_service_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py
index 09937513e..f9d875e83 100755
--- a/smoketest/scripts/cli/test_service_monitoring_telegraf.py
+++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py
@@ -39,10 +39,10 @@ class TestMonitoringTelegraf(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
def test_01_basic_config(self):
- self.cli_set(base_path + ['authentication', 'organization', org])
- self.cli_set(base_path + ['authentication', 'token', token])
- self.cli_set(base_path + ['port', port])
- self.cli_set(base_path + ['url', url])
+ self.cli_set(base_path + ['influxdb', 'authentication', 'organization', org])
+ self.cli_set(base_path + ['influxdb', 'authentication', 'token', token])
+ self.cli_set(base_path + ['influxdb', 'port', port])
+ self.cli_set(base_path + ['influxdb', 'url', url])
# commit changes
self.cli_commit()
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index 4875fb5d1..1168c05cd 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -17,6 +17,7 @@
import re
import unittest
+from vyos.configsession import ConfigSessionError
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.util import read_file
@@ -93,6 +94,7 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):
def test_dns(self):
nameserver = ['2001:db8::1', '2001:db8::2']
dnssl = ['vyos.net', 'vyos.io']
+ ns_lifetime = '599'
self.cli_set(base_path + ['prefix', '::/64', 'valid-lifetime', 'infinity'])
self.cli_set(base_path + ['other-config-flag'])
@@ -102,6 +104,14 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):
for sl in dnssl:
self.cli_set(base_path + ['dnssl', sl])
+ self.cli_set(base_path + ['name-server-lifetime', ns_lifetime])
+ # The value, if not 0, must be at least interval max (defaults to 600).
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ ns_lifetime = '600'
+ self.cli_set(base_path + ['name-server-lifetime', ns_lifetime])
+
# commit changes
self.cli_commit()
@@ -110,8 +120,12 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):
tmp = 'RDNSS ' + ' '.join(nameserver) + ' {'
self.assertIn(tmp, config)
+ tmp = f'AdvRDNSSLifetime {ns_lifetime};'
+ self.assertIn(tmp, config)
+
tmp = 'DNSSL ' + ' '.join(dnssl) + ' {'
self.assertIn(tmp, config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py
index 77ad5bc0d..0b029dd00 100755
--- a/smoketest/scripts/cli/test_service_ssh.py
+++ b/smoketest/scripts/cli/test_service_ssh.py
@@ -213,5 +213,54 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase):
usernames = [x[0] for x in getpwall()]
self.assertNotIn(test_user, usernames)
+ def test_ssh_dynamic_protection(self):
+ # check sshguard service
+
+ SSHGUARD_CONFIG = '/etc/sshguard/sshguard.conf'
+ SSHGUARD_WHITELIST = '/etc/sshguard/whitelist'
+ SSHGUARD_PROCESS = 'sshguard'
+ block_time = '123'
+ detect_time = '1804'
+ port = '22'
+ threshold = '10'
+ allow_list = ['192.0.2.0/24', '2001:db8::/48']
+
+ self.cli_set(base_path + ['dynamic-protection', 'block-time', block_time])
+ self.cli_set(base_path + ['dynamic-protection', 'detect-time', detect_time])
+ self.cli_set(base_path + ['dynamic-protection', 'threshold', threshold])
+ for allow in allow_list:
+ self.cli_set(base_path + ['dynamic-protection', 'allow-from', allow])
+
+ # commit changes
+ self.cli_commit()
+
+ # Check configured port
+ tmp = get_config_value('Port')
+ self.assertIn(port, tmp)
+
+ # Check sshgurad service
+ self.assertTrue(process_named_running(SSHGUARD_PROCESS))
+
+ sshguard_lines = [
+ f'THRESHOLD={threshold}',
+ f'BLOCK_TIME={block_time}',
+ f'DETECTION_TIME={detect_time}'
+ ]
+
+ tmp_sshguard_conf = read_file(SSHGUARD_CONFIG)
+ for line in sshguard_lines:
+ self.assertIn(line, tmp_sshguard_conf)
+
+ tmp_whitelist_conf = read_file(SSHGUARD_WHITELIST)
+ for allow in allow_list:
+ self.assertIn(allow, tmp_whitelist_conf)
+
+ # Delete service ssh dynamic-protection
+ # but not service ssh itself
+ self.cli_delete(base_path + ['dynamic-protection'])
+ self.cli_commit()
+
+ self.assertFalse(process_named_running(SSHGUARD_PROCESS))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py
index 5a73ebc7d..a6eef3fb6 100755
--- a/smoketest/scripts/cli/test_system_flow-accounting.py
+++ b/smoketest/scripts/cli/test_system_flow-accounting.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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,6 +20,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
+from vyos.template import bracketize_ipv6
+from vyos.template import is_ipv6
from vyos.util import cmd
from vyos.util import process_named_running
from vyos.util import read_file
@@ -103,14 +105,12 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
agent_address = '192.0.2.2'
sflow_server = {
- '1.2.3.4' : {
- },
- '5.6.7.8' : {
- 'port' : '6000'
- }
+ '1.2.3.4' : { },
+ '5.6.7.8' : { 'port' : '6000' },
}
self.cli_set(['interfaces', 'dummy', dummy_if, 'address', agent_address + '/32'])
+ self.cli_set(['interfaces', 'dummy', dummy_if, 'address', source_address + '/32'])
self.cli_set(base_path + ['disable-imt'])
# You need to configure at least one interface for flow-accounting
@@ -155,6 +155,54 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['interfaces', 'dummy', dummy_if])
+ def test_sflow_ipv6(self):
+ sampling_rate = '100'
+ sflow_server = {
+ '2001:db8::1' : { },
+ '2001:db8::2' : { 'port' : '6000' },
+ }
+
+ self.cli_set(base_path + ['disable-imt'])
+
+ # You need to configure at least one interface for flow-accounting
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ for interface in Section.interfaces('ethernet'):
+ self.cli_set(base_path + ['interface', interface])
+
+
+ # You need to configure at least one sFlow or NetFlow protocol, or not
+ # set "disable-imt" for flow-accounting
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(base_path + ['sflow', 'sampling-rate', sampling_rate])
+ for server, server_config in sflow_server.items():
+ self.cli_set(base_path + ['sflow', 'server', server])
+ if 'port' in server_config:
+ self.cli_set(base_path + ['sflow', 'server', server, 'port', server_config['port']])
+
+ # commit changes
+ self.cli_commit()
+
+ uacctd = read_file(uacctd_conf)
+
+ # when 'disable-imt' is not configured on the CLI it must be present
+ self.assertNotIn(f'imt_path: /tmp/uacctd.pipe', uacctd)
+ self.assertNotIn(f'imt_mem_pools_number: 169', uacctd)
+ self.assertNotIn(f'plugins: memory', uacctd)
+
+ for server, server_config in sflow_server.items():
+ tmp_srv = server
+ if is_ipv6(tmp_srv):
+ tmp_srv = tmp_srv.replace(':', '.')
+
+ if 'port' in server_config:
+ self.assertIn(f'sfprobe_receiver[sf_{tmp_srv}]: {bracketize_ipv6(server)}', uacctd)
+ else:
+ self.assertIn(f'sfprobe_receiver[sf_{tmp_srv}]: {bracketize_ipv6(server)}:6343', uacctd)
+ self.assertIn(f'sampling_rate[sf_{tmp_srv}]: {sampling_rate}', uacctd)
+
def test_netflow(self):
engine_id = '33'
max_flows = '667'
@@ -173,14 +221,13 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
tmo_udp = '10'
netflow_server = {
- '11.22.33.44' : {
- },
- '55.66.77.88' : {
- 'port' : '6000'
- }
+ '11.22.33.44' : { },
+ '55.66.77.88' : { 'port' : '6000' },
+ '2001:db8::1' : { },
}
self.cli_set(['interfaces', 'dummy', dummy_if, 'address', agent_address + '/32'])
+ self.cli_set(['interfaces', 'dummy', dummy_if, 'address', source_address + '/32'])
for interface in Section.interfaces('ethernet'):
self.cli_set(base_path + ['interface', interface])
@@ -217,23 +264,30 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
tmp = []
for server, server_config in netflow_server.items():
- tmp.append(f'nfprobe[nf_{server}]')
+ tmp_srv = server
+ if is_ipv6(tmp_srv):
+ tmp_srv = tmp_srv.replace(':', '.')
+ tmp.append(f'nfprobe[nf_{tmp_srv}]')
tmp.append('memory')
self.assertIn('plugins: ' + ','.join(tmp), uacctd)
for server, server_config in netflow_server.items():
- self.assertIn(f'nfprobe_engine[nf_{server}]: {engine_id}', uacctd)
- self.assertIn(f'nfprobe_maxflows[nf_{server}]: {max_flows}', uacctd)
- self.assertIn(f'sampling_rate[nf_{server}]: {sampling_rate}', uacctd)
- self.assertIn(f'nfprobe_source_ip[nf_{server}]: {source_address}', uacctd)
- self.assertIn(f'nfprobe_version[nf_{server}]: {version}', uacctd)
+ tmp_srv = server
+ if is_ipv6(tmp_srv):
+ tmp_srv = tmp_srv.replace(':', '.')
+
+ self.assertIn(f'nfprobe_engine[nf_{tmp_srv}]: {engine_id}', uacctd)
+ self.assertIn(f'nfprobe_maxflows[nf_{tmp_srv}]: {max_flows}', uacctd)
+ self.assertIn(f'sampling_rate[nf_{tmp_srv}]: {sampling_rate}', uacctd)
+ self.assertIn(f'nfprobe_source_ip[nf_{tmp_srv}]: {source_address}', uacctd)
+ self.assertIn(f'nfprobe_version[nf_{tmp_srv}]: {version}', uacctd)
if 'port' in server_config:
- self.assertIn(f'nfprobe_receiver[nf_{server}]: {server}', uacctd)
+ self.assertIn(f'nfprobe_receiver[nf_{tmp_srv}]: {bracketize_ipv6(server)}', uacctd)
else:
- self.assertIn(f'nfprobe_receiver[nf_{server}]: {server}:2055', uacctd)
+ self.assertIn(f'nfprobe_receiver[nf_{tmp_srv}]: {bracketize_ipv6(server)}:2055', uacctd)
- self.assertIn(f'nfprobe_timeouts[nf_{server}]: expint={tmo_expiry}:general={tmo_flow}:icmp={tmo_icmp}:maxlife={tmo_max}:tcp.fin={tmo_tcp_fin}:tcp={tmo_tcp_generic}:tcp.rst={tmo_tcp_rst}:udp={tmo_udp}', uacctd)
+ self.assertIn(f'nfprobe_timeouts[nf_{tmp_srv}]: expint={tmo_expiry}:general={tmo_flow}:icmp={tmo_icmp}:maxlife={tmo_max}:tcp.fin={tmo_tcp_fin}:tcp={tmo_tcp_generic}:tcp.rst={tmo_tcp_rst}:udp={tmo_udp}', uacctd)
self.cli_delete(['interfaces', 'dummy', dummy_if])
diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py
new file mode 100755
index 000000000..331133ed4
--- /dev/null
+++ b/smoketest/scripts/cli/test_system_frr.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-2020 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 re
+import unittest
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.util import read_file
+
+config_file = '/etc/frr/daemons'
+base_path = ['system', 'frr']
+
+
+def daemons_config_parse(daemons_config):
+ # create regex for parsing daemons options
+ regex_daemon_config = re.compile(
+ r'^(?P<daemon_name>\w+)_options="(?P<daemon_options>.*)"$', re.M)
+ # create empty dict for config
+ daemons_config_dict = {}
+ # fill dictionary with actual config
+ for daemon in regex_daemon_config.finditer(daemons_config):
+ daemon_name = daemon.group('daemon_name')
+ daemon_options = daemon.group('daemon_options')
+ daemons_config_dict[daemon_name] = daemon_options
+
+ # return daemons config
+ return (daemons_config_dict)
+
+
+class TestSystemFRR(VyOSUnitTestSHIM.TestCase):
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ def test_frr_snmp_multipledaemons(self):
+ # test SNMP integration for multiple daemons
+ test_daemon_names = ['ospfd', 'bgpd']
+ for test_daemon_name in test_daemon_names:
+ self.cli_set(base_path + ['snmp', test_daemon_name])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex for matching SNMP integration
+ regex_snmp = re.compile(r'^.* -M snmp.*$')
+ for (daemon_name, daemon_options) in daemons_config_dict.items():
+ snmp_enabled = regex_snmp.match(daemon_options)
+ if daemon_name in test_daemon_names:
+ self.assertTrue(snmp_enabled)
+ else:
+ self.assertFalse(snmp_enabled)
+
+ def test_frr_snmp_addandremove(self):
+ # test enabling and disabling of SNMP integration
+ test_daemon_names = ['ospfd', 'bgpd']
+ for test_daemon_name in test_daemon_names:
+ self.cli_set(base_path + ['snmp', test_daemon_name])
+ self.cli_commit()
+
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex for matching SNMP integration
+ regex_snmp = re.compile(r'^.* -M snmp.*$')
+ for test_daemon_name in test_daemon_names:
+ snmp_enabled = regex_snmp.match(
+ daemons_config_dict[test_daemon_name])
+ self.assertFalse(snmp_enabled)
+
+ def test_frr_snmp_empty(self):
+ # test empty config section
+ self.cli_set(base_path + ['snmp'])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex for matching SNMP integration
+ regex_snmp = re.compile(r'^.* -M snmp.*$')
+ for daemon_options in daemons_config_dict.values():
+ snmp_enabled = regex_snmp.match(daemon_options)
+ self.assertFalse(snmp_enabled)
+
+ def test_frr_bmp(self):
+ # test BMP
+ self.cli_set(base_path + ['bmp'])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex
+ regex_bmp = re.compile(r'^.* -M bmp.*$')
+ bmp_enabled = regex_bmp.match(daemons_config_dict['bgpd'])
+ self.assertTrue(bmp_enabled)
+
+ def test_frr_irdp(self):
+ # test IRDP
+ self.cli_set(base_path + ['irdp'])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex
+ regex_irdp = re.compile(r'^.* -M irdp.*$')
+ irdp_enabled = regex_irdp.match(daemons_config_dict['zebra'])
+ self.assertTrue(irdp_enabled)
+
+ def test_frr_bmpandsnmp(self):
+ # test empty config section
+ self.cli_set(base_path + ['bmp'])
+ self.cli_set(base_path + ['snmp', 'bgpd'])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex
+ regex_snmp = re.compile(r'^.* -M bmp.*$')
+ regex_snmp = re.compile(r'^.* -M snmp.*$')
+ bmp_enabled = regex_snmp.match(daemons_config_dict['bgpd'])
+ snmp_enabled = regex_snmp.match(daemons_config_dict['bgpd'])
+ self.assertTrue(bmp_enabled)
+ self.assertTrue(snmp_enabled)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2, failfast=True)
diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py
index 83df9d99e..f71ef5b3f 100755
--- a/smoketest/scripts/cli/test_system_ip.py
+++ b/smoketest/scripts/cli/test_system_ip.py
@@ -28,7 +28,7 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase):
def test_system_ip_forwarding(self):
# Test if IPv4 forwarding can be disabled globally, default is '1'
- # which means forwearding enabled
+ # which means forwarding enabled
all_forwarding = '/proc/sys/net/ipv4/conf/all/forwarding'
self.assertEqual(read_file(all_forwarding), '1')
@@ -37,6 +37,17 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase):
self.assertEqual(read_file(all_forwarding), '0')
+ def test_system_ip_directed_broadcast_forwarding(self):
+ # Test if IPv4 directed broadcast forwarding can be disabled globally,
+ # default is '1' which means forwarding enabled
+ bc_forwarding = '/proc/sys/net/ipv4/conf/all/bc_forwarding'
+ self.assertEqual(read_file(bc_forwarding), '1')
+
+ self.cli_set(base_path + ['disable-directed-broadcast'])
+ self.cli_commit()
+
+ self.assertEqual(read_file(bc_forwarding), '0')
+
def test_system_ip_multipath(self):
# Test IPv4 multipathing options, options default to off -> '0'
use_neigh = '/proc/sys/net/ipv4/fib_multipath_use_neigh'
diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py
index e2821687c..a0806acf0 100755
--- a/smoketest/scripts/cli/test_system_ntp.py
+++ b/smoketest/scripts/cli/test_system_ntp.py
@@ -108,5 +108,22 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
for listen in listen_address:
self.assertIn(f'interface listen {listen}', config)
+ def test_03_ntp_interface(self):
+ interfaces = ['eth0', 'eth1']
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface])
+
+ servers = ['time1.vyos.net', 'time2.vyos.net']
+ for server in servers:
+ self.cli_set(base_path + ['server', server])
+
+ self.cli_commit()
+
+ # Check generated client address configuration
+ config = read_file(NTP_CONF)
+ self.assertIn('interface ignore wildcard', config)
+ for interface in interfaces:
+ self.assertIn(f'interface listen {interface}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_sstp.py b/smoketest/scripts/cli/test_vpn_sstp.py
index 24673278b..f58920b5b 100755
--- a/smoketest/scripts/cli/test_vpn_sstp.py
+++ b/smoketest/scripts/cli/test_vpn_sstp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,7 +17,8 @@
import unittest
from base_accel_ppp_test import BasicAccelPPPTest
-from vyos.util import cmd
+from vyos.util import read_file
+
pki_path = ['pki']
cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0='
@@ -40,6 +41,7 @@ class TestVPNSSTPServer(BasicAccelPPPTest.TestCase):
self.cli_set(pki_path + ['ca', 'sstp', 'certificate', cert_data])
self.cli_set(pki_path + ['certificate', 'sstp', 'certificate', cert_data])
self.cli_set(pki_path + ['certificate', 'sstp', 'private', 'key', key_data])
+
# SSL is mandatory
self.set(['ssl', 'ca-certificate', 'sstp'])
self.set(['ssl', 'certificate', 'sstp'])
@@ -47,5 +49,15 @@ class TestVPNSSTPServer(BasicAccelPPPTest.TestCase):
super().basic_config()
+ def test_accel_local_authentication(self):
+ # Change default port
+ port = '8443'
+ self.set(['port', port])
+
+ super().test_accel_local_authentication()
+
+ config = read_file(self._config_file)
+ self.assertIn(f'port={port}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index ff18f7261..176c095fb 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -127,6 +127,9 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for vrf in vrfs:
# Ensure VRF was created
self.assertIn(vrf, interfaces())
+ # Verify IP forwarding is 1 (enabled)
+ self.assertEqual(read_file(f'/proc/sys/net/ipv4/conf/{vrf}/forwarding'), '1')
+ self.assertEqual(read_file(f'/proc/sys/net/ipv6/conf/{vrf}/forwarding'), '1')
# Test for proper loopback IP assignment
for addr in loopbacks:
self.assertTrue(is_intf_addr_assigned(vrf, addr))
@@ -267,5 +270,26 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['interfaces', 'dummy', interface])
self.cli_commit()
+ def test_vrf_disable_forwarding(self):
+ table = '2000'
+ for vrf in vrfs:
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', table])
+ self.cli_set(base + ['ip', 'disable-forwarding'])
+ self.cli_set(base + ['ipv6', 'disable-forwarding'])
+ table = str(int(table) + 1)
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify VRF configuration
+ loopbacks = ['127.0.0.1', '::1']
+ for vrf in vrfs:
+ # Ensure VRF was created
+ self.assertIn(vrf, interfaces())
+ # Verify IP forwarding is 0 (disabled)
+ self.assertEqual(read_file(f'/proc/sys/net/ipv4/conf/{vrf}/forwarding'), '0')
+ self.assertEqual(read_file(f'/proc/sys/net/ipv6/conf/{vrf}/forwarding'), '0')
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/completion/list_bgp_neighbors.sh b/src/completion/list_bgp_neighbors.sh
index f74f102ef..869a7ab0a 100755
--- a/src/completion/list_bgp_neighbors.sh
+++ b/src/completion/list_bgp_neighbors.sh
@@ -1,5 +1,5 @@
#!/bin/sh
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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
@@ -18,19 +18,21 @@
ipv4=0
ipv6=0
+vrf=""
while [[ "$#" -gt 0 ]]; do
case $1 in
-4|--ipv4) ipv4=1 ;;
-6|--ipv6) ipv6=1 ;;
-b|--both) ipv4=1; ipv6=1 ;;
+ --vrf) vrf="vrf name $2"; shift ;;
*) echo "Unknown parameter passed: $1" ;;
esac
shift
done
declare -a vals
-eval "vals=($(cli-shell-api listActiveNodes protocols bgp neighbor))"
+eval "vals=($(cli-shell-api listActiveNodes $vrf protocols bgp neighbor))"
if [ $ipv4 -eq 1 ] && [ $ipv6 -eq 1 ]; then
echo -n '<x.x.x.x>' '<h:h:h:h:h:h:h:h>' ${vals[@]}
@@ -54,9 +56,10 @@ elif [ $ipv6 -eq 1 ] ; then
done
else
echo "Usage:"
- echo "-4|--ipv4 list only IPv4 peers"
- echo "-6|--ipv6 list only IPv6 peers"
- echo "--both list both IP4 and IPv6 peers"
+ echo "-4|--ipv4 list only IPv4 peers"
+ echo "-6|--ipv6 list only IPv6 peers"
+ echo "--both list both IP4 and IPv6 peers"
+ echo "--vrf <name> apply command to given VRF (optional)"
echo ""
exit 1
fi
diff --git a/src/completion/list_openconnect_users.py b/src/completion/list_openconnect_users.py
new file mode 100755
index 000000000..a266fd893
--- /dev/null
+++ b/src/completion/list_openconnect_users.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-2022 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/>.
+
+from vyos.config import Config
+from vyos.util import dict_search
+
+def get_user_from_ocserv():
+ config = Config()
+ base = ['vpn', 'openconnect', 'authentication', 'local-users', 'username']
+ openconnect = config.get_config_dict(base, effective=True, key_mangling=('-', '_'))
+ users = []
+ try:
+ for user in (dict_search('username', openconnect) or []):
+ users.append(user)
+ except:
+ pass
+ return users
+
+if __name__ == "__main__":
+ users = []
+ users = get_user_from_ocserv()
+ print(" ".join(users))
+
diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py
index 311e01529..c4b2bb488 100755
--- a/src/conf_mode/conntrack_sync.py
+++ b/src/conf_mode/conntrack_sync.py
@@ -116,6 +116,7 @@ def generate(conntrack):
return None
def apply(conntrack):
+ systemd_service = 'conntrackd.service'
if not conntrack:
# Failover mechanism daemon should be indicated that it no longer needs
# to execute conntrackd actions on transition. This is only required
@@ -123,7 +124,7 @@ def apply(conntrack):
if process_named_running('conntrackd'):
resync_vrrp()
- call('systemctl stop conntrackd.service')
+ call(f'systemctl stop {systemd_service}')
return None
# Failover mechanism daemon should be indicated that it needs to execute
@@ -132,7 +133,7 @@ def apply(conntrack):
if not process_named_running('conntrackd'):
resync_vrrp()
- call('systemctl restart conntrackd.service')
+ call(f'systemctl reload-or-restart {systemd_service}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 7e1dc5911..ac3dc536b 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -15,13 +15,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import json
from ipaddress import ip_address
from ipaddress import ip_network
from time import sleep
from json import dumps as json_write
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
@@ -90,10 +90,10 @@ def get_config(config=None):
container['name'][name] = dict_merge(default_values, container['name'][name])
# Delete container network, delete containers
- tmp = node_changed(conf, base + ['container', 'network'])
+ tmp = node_changed(conf, base + ['network'])
if tmp: container.update({'network_remove' : tmp})
- tmp = node_changed(conf, base + ['container', 'name'])
+ tmp = node_changed(conf, base + ['name'])
if tmp: container.update({'container_remove' : tmp})
return container
@@ -110,15 +110,21 @@ def verify(container):
if 'image' not in container_config:
raise ConfigError(f'Container image for "{name}" is mandatory!')
- # verify container image exists locally
- image = container_config['image']
-
# Check if requested container image exists locally. If it does not
- # exist locally - inform the user.
+ # exist locally - inform the user. This is required as there is a
+ # shared container image storage accross all VyOS images. A user can
+ # delete a container image from the system, boot into another version
+ # of VyOS and then it would fail to boot. This is to prevent any
+ # configuration error when container images are deleted from the
+ # global storage. A per image local storage would be a super waste
+ # of diskspace as there will be a full copy (up tu several GB/image)
+ # on upgrade. This is the "cheapest" and fastest solution in terms
+ # of image upgrade and deletion.
+ image = container_config['image']
if run(f'podman image exists {image}') != 0:
- raise ConfigError(f'Image "{image}" used in contianer "{name}" does not exist '\
- f'locally.\nPlease use "add container image {image}" to add it '\
- 'to the system!')
+ Warning(f'Image "{image}" used in contianer "{name}" does not exist '\
+ f'locally. Please use "add container image {image}" to add it '\
+ f'to the system! Container "{name}" will not be started!')
if 'network' in container_config:
if len(container_config['network']) > 1:
@@ -126,7 +132,7 @@ def verify(container):
# Check if the specified container network exists
network_name = list(container_config['network'])[0]
- if network_name not in container['network']:
+ if network_name not in container.get('network', {}):
raise ConfigError(f'Container network "{network_name}" does not exist!')
if 'address' in container_config['network'][network_name]:
@@ -264,12 +270,13 @@ def apply(container):
# Option "--force" allows to delete containers with any status
if 'container_remove' in container:
for name in container['container_remove']:
- call(f'podman stop {name}')
+ call(f'podman stop --time 3 {name}')
call(f'podman rm --force {name}')
# Delete old networks if needed
if 'network_remove' in container:
for network in container['network_remove']:
+ call(f'podman network rm {network}')
tmp = f'/etc/cni/net.d/{network}.conflist'
if os.path.exists(tmp):
os.unlink(tmp)
@@ -279,11 +286,16 @@ def apply(container):
for name, container_config in container['name'].items():
image = container_config['image']
+ if run(f'podman image exists {image}') != 0:
+ # container image does not exist locally - user already got
+ # informed by a WARNING in verfiy() - bail out early
+ continue
+
if 'disable' in container_config:
# check if there is a container by that name running
tmp = _cmd('podman ps -a --format "{{.Names}}"')
if name in tmp:
- _cmd(f'podman stop {name}')
+ _cmd(f'podman stop --time 3 {name}')
_cmd(f'podman rm --force {name}')
continue
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index f1c2d1f43..d0d87d73e 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -98,6 +98,9 @@ def get_config(config=None):
dns['authoritative_zone_errors'].append('{}.{}: at least one address is required'.format(subnode, node))
continue
+ if subnode == 'any':
+ subnode = '*'
+
for address in rdata['address']:
zone['records'].append({
'name': subnode,
@@ -263,6 +266,12 @@ def verify(dns):
if 'server' not in dns['domain'][domain]:
raise ConfigError(f'No server configured for domain {domain}!')
+ if 'dns64_prefix' in dns:
+ dns_prefix = dns['dns64_prefix'].split('/')[1]
+ # RFC 6147 requires prefix /96
+ if int(dns_prefix) != 96:
+ raise ConfigError('DNS 6to4 prefix must be of length /96')
+
if ('authoritative_zone_errors' in dns) and dns['authoritative_zone_errors']:
for error in dns['authoritative_zone_errors']:
print(error)
diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py
index 9a5d278e9..ab1c69259 100755
--- a/src/conf_mode/firewall-interface.py
+++ b/src/conf_mode/firewall-interface.py
@@ -64,6 +64,11 @@ def get_config(config=None):
return if_firewall
+def verify_chain(table, chain):
+ # Verify firewall applied
+ code = run(f'nft list chain {table} {chain}')
+ return code == 0
+
def verify(if_firewall):
# bail out early - looks like removal from running config
if not if_firewall:
@@ -80,6 +85,9 @@ def verify(if_firewall):
if name not in if_firewall['firewall']['name']:
raise ConfigError(f'Invalid firewall name "{name}"')
+ if not verify_chain('ip filter', f'{NAME_PREFIX}{name}'):
+ raise ConfigError('Firewall did not apply')
+
if 'ipv6_name' in if_firewall[direction]:
name = if_firewall[direction]['ipv6_name']
@@ -89,6 +97,9 @@ def verify(if_firewall):
if name not in if_firewall['firewall']['ipv6_name']:
raise ConfigError(f'Invalid firewall ipv6-name "{name}"')
+ if not verify_chain('ip6 filter', f'{NAME6_PREFIX}{name}'):
+ raise ConfigError('Firewall did not apply')
+
return None
def generate(if_firewall):
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 6924bf555..07eca722f 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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
@@ -26,9 +26,17 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
+from vyos.firewall import geoip_update
+from vyos.firewall import get_ips_domains_dict
+from vyos.firewall import nft_add_set_elements
+from vyos.firewall import nft_flush_set
+from vyos.firewall import nft_init_set
+from vyos.firewall import nft_update_set_elements
from vyos.template import render
+from vyos.util import call
from vyos.util import cmd
from vyos.util import dict_search_args
+from vyos.util import dict_search_recursive
from vyos.util import process_named_running
from vyos.util import run
from vyos.xml import defaults
@@ -79,10 +87,26 @@ nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
valid_groups = [
'address_group',
+ 'domain_group',
'network_group',
'port_group'
]
+nested_group_types = [
+ 'address_group', 'network_group', 'mac_group',
+ 'port_group', 'ipv6_address_group', 'ipv6_network_group'
+]
+
+group_set_prefix = {
+ 'A_': 'address_group',
+ 'A6_': 'ipv6_address_group',
+ 'D_': 'domain_group',
+ 'M_': 'mac_group',
+ 'N_': 'network_group',
+ 'N6_': 'ipv6_network_group',
+ 'P_': 'port_group'
+}
+
snmp_change_type = {
'unknown': 0,
'add': 1,
@@ -138,6 +162,40 @@ def get_firewall_zones(conf):
return {'name': used_v4, 'ipv6_name': used_v6}
+def geoip_updated(conf, firewall):
+ diff = get_config_diff(conf)
+ node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True)
+
+ out = {
+ 'name': [],
+ 'ipv6_name': [],
+ 'deleted_name': [],
+ 'deleted_ipv6_name': []
+ }
+ updated = False
+
+ for key, path in dict_search_recursive(firewall, 'geoip'):
+ set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
+ if path[0] == 'name':
+ out['name'].append(set_name)
+ elif path[0] == 'ipv6_name':
+ out['ipv6_name'].append(set_name)
+ updated = True
+
+ if 'delete' in node_diff:
+ for key, path in dict_search_recursive(node_diff['delete'], 'geoip'):
+ set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
+ if path[0] == 'name':
+ out['deleted_name'].append(set_name)
+ elif path[0] == 'ipv6-name':
+ out['deleted_ipv6_name'].append(set_name)
+ updated = True
+
+ if updated:
+ return out
+
+ return False
+
def get_config(config=None):
if config:
conf = config
@@ -162,6 +220,8 @@ def get_config(config=None):
key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
+ firewall['geoip_updated'] = geoip_updated(conf, firewall)
+
return firewall
def verify_rule(firewall, rule_conf, ipv6):
@@ -207,6 +267,16 @@ def verify_rule(firewall, rule_conf, ipv6):
if side in rule_conf:
side_conf = rule_conf[side]
+ if dict_search_args(side_conf, 'geoip', 'country_code'):
+ if 'address' in side_conf:
+ raise ConfigError('Address and GeoIP cannot both be defined')
+
+ if dict_search_args(side_conf, 'group', 'address_group'):
+ raise ConfigError('Address-group and GeoIP cannot both be defined')
+
+ if dict_search_args(side_conf, 'group', 'network_group'):
+ raise ConfigError('Network-group and GeoIP cannot both be defined')
+
if 'group' in side_conf:
if {'address_group', 'network_group'} <= set(side_conf['group']):
raise ConfigError('Only one address-group or network-group can be specified')
@@ -235,11 +305,34 @@ def verify_rule(firewall, rule_conf, ipv6):
if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group')
+def verify_nested_group(group_name, group, groups, seen):
+ if 'include' not in group:
+ return
+
+ for g in group['include']:
+ if g not in groups:
+ raise ConfigError(f'Nested group "{g}" does not exist')
+
+ if g in seen:
+ raise ConfigError(f'Group "{group_name}" has a circular reference')
+
+ seen.append(g)
+
+ if 'include' in groups[g]:
+ verify_nested_group(g, groups[g], groups, seen)
+
def verify(firewall):
if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
if not firewall['trap_targets']:
raise ConfigError(f'Firewall config-trap enabled but "service snmp trap-target" is not defined')
+ if 'group' in firewall:
+ for group_type in nested_group_types:
+ if group_type in firewall['group']:
+ groups = firewall['group'][group_type]
+ for group_name, group in groups.items():
+ verify_nested_group(group_name, group, groups, [])
+
for name in ['name', 'ipv6_name']:
if name in firewall:
for name_id, name_conf in firewall[name].items():
@@ -271,55 +364,75 @@ def verify(firewall):
return None
-def cleanup_rule(table, jump_chain):
- commands = []
- chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains
- for chain in chains:
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- for line in results:
- if f'jump {jump_chain}' in line:
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- commands.append(f'delete rule {table} {chain} handle {handle_search[1]}')
- return commands
-
def cleanup_commands(firewall):
commands = []
- commands_end = []
+ commands_chains = []
+ commands_sets = []
for table in ['ip filter', 'ip6 filter']:
+ name_node = 'name' if table == 'ip filter' else 'ipv6_name'
+ chain_prefix = NAME_PREFIX if table == 'ip filter' else NAME6_PREFIX
state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6'
- json_str = cmd(f'nft -j list table {table}')
+ iface_chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains
+
+ geoip_list = []
+ if firewall['geoip_updated']:
+ geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name'
+ geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or []
+
+ json_str = cmd(f'nft -t -j list table {table}')
obj = loads(json_str)
+
if 'nftables' not in obj:
continue
+
for item in obj['nftables']:
if 'chain' in item:
chain = item['chain']['name']
- if chain in ['VYOS_STATE_POLICY', 'VYOS_STATE_POLICY6']:
- if 'state_policy' not in firewall:
- commands.append(f'delete chain {table} {chain}')
- else:
- commands.append(f'flush chain {table} {chain}')
- elif chain not in preserve_chains and not chain.startswith("VZONE"):
- if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- else:
- commands += cleanup_rule(table, chain)
- commands.append(f'delete chain {table} {chain}')
- elif 'rule' in item:
+ if chain in preserve_chains or chain.startswith("VZONE"):
+ continue
+
+ if chain == state_chain:
+ command = 'delete' if 'state_policy' not in firewall else 'flush'
+ commands_chains.append(f'{command} chain {table} {chain}')
+ elif dict_search_args(firewall, name_node, chain.replace(chain_prefix, "", 1)) != None:
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands_chains.append(f'delete chain {table} {chain}')
+
+ if 'rule' in item:
rule = item['rule']
- if rule['chain'] in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL', 'VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']:
- if 'expr' in rule and any([True for expr in rule['expr'] if dict_search_args(expr, 'jump', 'target') == state_chain]):
- if 'state_policy' not in firewall:
- chain = rule['chain']
- handle = rule['handle']
+ chain = rule['chain']
+ handle = rule['handle']
+
+ if chain in iface_chains:
+ target, _ = next(dict_search_recursive(rule['expr'], 'target'))
+
+ if target == state_chain and 'state_policy' not in firewall:
+ commands.append(f'delete rule {table} {chain} handle {handle}')
+
+ if target.startswith(chain_prefix):
+ if dict_search_args(firewall, name_node, target.replace(chain_prefix, "", 1)) == None:
commands.append(f'delete rule {table} {chain} handle {handle}')
- elif 'set' in item:
+
+ if 'set' in item:
set_name = item['set']['name']
- commands_end.append(f'delete set {table} {set_name}')
- return commands + commands_end
+
+ if set_name.startswith('GEOIP_CC_') and set_name in geoip_list:
+ commands_sets.append(f'delete set {table} {set_name}')
+ continue
+
+ if set_name.startswith("RECENT_"):
+ commands_sets.append(f'delete set {table} {set_name}')
+ continue
+
+ for prefix, group_type in group_set_prefix.items():
+ if set_name.startswith(prefix):
+ group_name = set_name.replace(prefix, "", 1)
+ if dict_search_args(firewall, 'group', group_type, group_name) != None:
+ commands_sets.append(f'flush set {table} {set_name}')
+ else:
+ commands_sets.append(f'delete set {table} {set_name}')
+ return commands + commands_chains + commands_sets
def generate(firewall):
if not os.path.exists(nftables_conf):
@@ -328,7 +441,6 @@ def generate(firewall):
firewall['cleanup_commands'] = cleanup_commands(firewall)
render(nftables_conf, 'firewall/nftables.j2', firewall)
- render(nftables_defines_conf, 'firewall/nftables-defines.j2', firewall)
return None
def apply_sysfs(firewall):
@@ -408,6 +520,27 @@ def apply(firewall):
if install_result == 1:
raise ConfigError('Failed to apply firewall')
+ # set fireall group domain-group xxx
+ if 'group' in firewall:
+ if 'domain_group' in firewall['group']:
+ # T970 Enable a resolver (systemd daemon) that checks
+ # domain-group addresses and update entries for domains by timeout
+ # If router loaded without internet connection or for synchronization
+ call('systemctl restart vyos-domain-group-resolve.service')
+ for group, group_config in firewall['group']['domain_group'].items():
+ domains = []
+ if group_config.get('address') is not None:
+ for address in group_config.get('address'):
+ domains.append(address)
+ # Add elements to domain-group, try to resolve domain => ip
+ # and add elements to nft set
+ ip_dict = get_ips_domains_dict(domains)
+ elements = sum(ip_dict.values(), [])
+ nft_init_set(f'D_{group}')
+ nft_add_set_elements(f'D_{group}', elements)
+ else:
+ call('systemctl stop vyos-domain-group-resolve.service')
+
if 'state_policy' in firewall and not state_policy_rule_exists():
for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']:
cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY')
@@ -420,6 +553,12 @@ def apply(firewall):
if firewall['policy_resync']:
resync_policy_route()
+ if firewall['geoip_updated']:
+ # Call helper script to Update set contents
+ if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']:
+ print('Updating GeoIP. Please wait...')
+ geoip_update(firewall)
+
post_apply_trap(firewall)
return None
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 7f7a98b04..7750c1247 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -22,6 +22,7 @@ import ipaddress
from ipaddress import ip_address
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.ifconfig import Section
@@ -109,6 +110,9 @@ def _nftables_config(configured_ifaces, direction, length=None):
iface_prefix = "o" if direction == "egress" else "i"
rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {length} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"'
nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}')
+ # Also add IPv6 ingres logging
+ if nftables_table == nftables_nflog_table:
+ nftable_commands.append(f'nft insert rule ip6 {nftables_table} {nftables_chain} {rule_definition}')
# change nftables
for command in nftable_commands:
@@ -172,7 +176,7 @@ def verify(flow_config):
if interface not in Section.interfaces():
# Changed from error to warning to allow adding dynamic interfaces
# and interface templates
- print(f'Warning: Interface "{interface}" is not presented in the system')
+ Warning(f'Interface "{interface}" is not presented in the system')
# check sFlow configuration
if 'sflow' in flow_config:
@@ -200,7 +204,13 @@ def verify(flow_config):
if 'agent_address' in flow_config['sflow']:
tmp = flow_config['sflow']['agent_address']
if not is_addr_assigned(tmp):
- print(f'Warning: Configured "sflow agent-address {tmp}" does not exist in the system!')
+ raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!')
+
+ # Check if configured netflow source-address exist in the system
+ if 'source_address' in flow_config['sflow']:
+ if not is_addr_assigned(flow_config['sflow']['source_address']):
+ tmp = flow_config['sflow']['source_address']
+ raise ConfigError(f'Configured "sflow source-address {tmp}" does not exist on the system!')
# check NetFlow configuration
if 'netflow' in flow_config:
@@ -212,7 +222,7 @@ def verify(flow_config):
if 'source_address' in flow_config['netflow']:
if not is_addr_assigned(flow_config['netflow']['source_address']):
tmp = flow_config['netflow']['source_address']
- print(f'Warning: Configured "netflow source-address {tmp}" does not exist on the system!')
+ raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!')
# Check if engine-id compatible with selected protocol version
if 'engine_id' in flow_config['netflow']:
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index f939f9469..e14050dd3 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -28,7 +28,6 @@ from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.util import call
-from vyos.util import is_systemd_service_running
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -161,12 +160,7 @@ def apply(ha):
call(f'systemctl stop {service_name}')
return None
- # XXX: T3944 - reload keepalived configuration if service is already running
- # to not cause any service disruption when applying changes.
- if is_systemd_service_running(service_name):
- call(f'systemctl reload {service_name}')
- else:
- call(f'systemctl restart {service_name}')
+ call(f'systemctl reload-or-restart {service_name}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index 4167594e3..82ce1b41a 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -109,20 +109,22 @@ def get_config(config=None):
for interface, interface_config in bond['member']['interface'].items():
# Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
- if tmp: interface_config.update({'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')
- if tmp and bond['ifname'] not in tmp:
- interface_config.update({'is_bond_member' : tmp})
+ for tmp in is_member(conf, interface, 'bonding'):
+ if bond['ifname'] == tmp:
+ continue
+ 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.update({'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.update({'has_address' : ''})
+ if tmp: bond['member']['interface'][interface].update({'has_address' : {}})
return bond
@@ -167,11 +169,11 @@ def verify(bond):
raise ConfigError(error_msg + 'it does not exist!')
if 'is_bridge_member' in interface_config:
- tmp = next(iter(interface_config['is_bridge_member']))
+ tmp = interface_config['is_bridge_member']
raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!')
if 'is_bond_member' in interface_config:
- tmp = next(iter(interface_config['is_bond_member']))
+ tmp = interface_config['is_bond_member']
raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!')
if 'is_source_interface' in interface_config:
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 38ae727c1..9f8eb31c6 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -118,11 +118,11 @@ def verify(bridge):
raise ConfigError('Loopback interface "lo" can not be added to a bridge')
if 'is_bridge_member' in interface_config:
- tmp = next(iter(interface_config['is_bridge_member']))
+ tmp = interface_config['is_bridge_member']
raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!')
if 'is_bond_member' in interface_config:
- tmp = next(iter(interface_config['is_bond_member']))
+ tmp = interface_config['is_bond_member']
raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!')
if 'is_source_interface' in interface_config:
@@ -134,7 +134,7 @@ def verify(bridge):
if 'enable_vlan' in bridge:
if 'has_vlan' in interface_config:
- raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!')
+ raise ConfigError(error_msg + 'it has VLAN subinterface(s) assigned!')
if 'wlan' in interface:
raise ConfigError(error_msg + 'VLAN aware cannot be set!')
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 4750ca3e8..280a62b9a 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -39,6 +39,8 @@ from vyos.configverify import verify_mirror_redirect
from vyos.ifconfig import VTunIf
from vyos.pki import load_dh_parameters
from vyos.pki import load_private_key
+from vyos.pki import sort_ca_chain
+from vyos.pki import verify_ca_chain
from vyos.pki import wrap_certificate
from vyos.pki import wrap_crl
from vyos.pki import wrap_dh_parameters
@@ -148,8 +150,14 @@ def verify_pki(openvpn):
if 'ca_certificate' not in tls:
raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}')
- if tls['ca_certificate'] not in pki['ca']:
- raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+ for ca_name in tls['ca_certificate']:
+ if ca_name not in pki['ca']:
+ raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+
+ if len(tls['ca_certificate']) > 1:
+ sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca'])
+ if not verify_ca_chain(sorted_chain, pki['ca']):
+ raise ConfigError(f'CA certificates are not a valid chain')
if mode != 'client' and 'auth_key' not in tls:
if 'certificate' not in tls:
@@ -516,21 +524,28 @@ def generate_pki_files(openvpn):
if tls:
if 'ca_certificate' in tls:
- cert_name = tls['ca_certificate']
- pki_ca = pki['ca'][cert_name]
+ cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
+ crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
- if 'certificate' in pki_ca:
- cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
- write_file(cert_path, wrap_certificate(pki_ca['certificate']),
- user=user, group=group, mode=0o600)
+ if os.path.exists(cert_path):
+ os.unlink(cert_path)
+
+ if os.path.exists(crl_path):
+ os.unlink(crl_path)
+
+ for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']):
+ pki_ca = pki['ca'][cert_name]
+
+ if 'certificate' in pki_ca:
+ write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n",
+ user=user, group=group, mode=0o600, append=True)
- if 'crl' in pki_ca:
- for crl in pki_ca['crl']:
- crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
- write_file(crl_path, wrap_crl(crl), user=user, group=group,
- mode=0o600)
+ if 'crl' in pki_ca:
+ for crl in pki_ca['crl']:
+ write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group,
+ mode=0o600, append=True)
- openvpn['tls']['crl'] = True
+ openvpn['tls']['crl'] = True
if 'certificate' in tls:
cert_name = tls['certificate']
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index 0d6ec9ace..5490a794d 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -18,9 +18,11 @@ import os
from vyos.config import Config
from vyos.configverify import verify_vrf
-from vyos import ConfigError
+from vyos.configverify import verify_interface_exists
from vyos.util import call
+from vyos.util import get_interface_config
from vyos.template import render
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -49,6 +51,20 @@ def verify(ntp):
raise ConfigError('NTP server not configured')
verify_vrf(ntp)
+
+ if 'interface' in ntp:
+ # If ntpd should listen on a given interface, ensure it exists
+ for interface in ntp['interface']:
+ verify_interface_exists(interface)
+
+ # If we run in a VRF, our interface must belong to this VRF, too
+ if 'vrf' in ntp:
+ tmp = get_interface_config(interface)
+ vrf_name = ntp['vrf']
+ if 'master' not in tmp or tmp['master'] != vrf_name:
+ raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\
+ f'does not belong to this VRF!')
+
return None
def generate(ntp):
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index efa3578b4..29ed7b1b7 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -29,12 +29,60 @@ from vyos.pki import load_private_key
from vyos.pki import load_crl
from vyos.pki import load_dh_parameters
from vyos.util import ask_input
+from vyos.util import call
+from vyos.util import dict_search_args
from vyos.util import dict_search_recursive
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
+# keys to recursively search for under specified path, script to call if update required
+sync_search = [
+ {
+ 'keys': ['certificate'],
+ 'path': ['service', 'https'],
+ 'script': '/usr/libexec/vyos/conf_mode/https.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate'],
+ 'path': ['interfaces', 'ethernet'],
+ 'script': '/usr/libexec/vyos/conf_mode/interfaces-ethernet.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate', 'dh_params', 'shared_secret_key', 'auth_key', 'crypt_key'],
+ 'path': ['interfaces', 'openvpn'],
+ 'script': '/usr/libexec/vyos/conf_mode/interfaces-openvpn.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'],
+ 'path': ['vpn', 'ipsec'],
+ 'script': '/usr/libexec/vyos/conf_mode/vpn_ipsec.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate'],
+ 'path': ['vpn', 'openconnect'],
+ 'script': '/usr/libexec/vyos/conf_mode/vpn_openconnect.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate'],
+ 'path': ['vpn', 'sstp'],
+ 'script': '/usr/libexec/vyos/conf_mode/vpn_sstp.py'
+ }
+]
+
+# key from other config nodes -> key in pki['changed'] and pki
+sync_translate = {
+ 'certificate': 'certificate',
+ 'ca_certificate': 'ca',
+ 'dh_params': 'dh',
+ 'local_key': 'key_pair',
+ 'remote_key': 'key_pair',
+ 'shared_secret_key': 'openvpn',
+ 'auth_key': 'openvpn',
+ 'crypt_key': 'openvpn'
+}
+
def get_config(config=None):
if config:
conf = config
@@ -47,12 +95,21 @@ def get_config(config=None):
no_tag_node_value_mangle=True)
pki['changed'] = {}
- tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_'))
+ tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_'), recursive=True)
if tmp: pki['changed'].update({'ca' : tmp})
- tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_'))
+ tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_'), recursive=True)
if tmp: pki['changed'].update({'certificate' : tmp})
+ tmp = node_changed(conf, base + ['dh'], key_mangling=('-', '_'), recursive=True)
+ if tmp: pki['changed'].update({'dh' : tmp})
+
+ tmp = node_changed(conf, base + ['key-pair'], key_mangling=('-', '_'), recursive=True)
+ if tmp: pki['changed'].update({'key_pair' : tmp})
+
+ tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], key_mangling=('-', '_'), recursive=True)
+ if tmp: pki['changed'].update({'openvpn' : tmp})
+
# We only merge on the defaults of there is a configuration at all
if conf.exists(base):
default_values = defaults(base)
@@ -164,17 +221,30 @@ def verify(pki):
if 'changed' in pki:
# if the list is getting longer, we can move to a dict() and also embed the
# search key as value from line 173 or 176
- for cert_type in ['ca', 'certificate']:
- if not cert_type in pki['changed']:
- continue
- for certificate in pki['changed'][cert_type]:
- if cert_type not in pki or certificate not in pki['changed'][cert_type]:
- if cert_type == 'ca':
- if certificate in dict_search_recursive(pki['system'], 'ca_certificate'):
- raise ConfigError(f'CA certificate "{certificate}" is still in use!')
- elif cert_type == 'certificate':
- if certificate in dict_search_recursive(pki['system'], 'certificate'):
- raise ConfigError(f'Certificate "{certificate}" is still in use!')
+ for search in sync_search:
+ for key in search['keys']:
+ changed_key = sync_translate[key]
+
+ if changed_key not in pki['changed']:
+ continue
+
+ for item_name in pki['changed'][changed_key]:
+ node_present = False
+ if changed_key == 'openvpn':
+ node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
+ else:
+ node_present = dict_search_args(pki, changed_key, item_name)
+
+ if not node_present:
+ search_dict = dict_search_args(pki['system'], *search['path'])
+
+ if not search_dict:
+ continue
+
+ for found_name, found_path in dict_search_recursive(search_dict, key):
+ if found_name == item_name:
+ path_str = " ".join(search['path'] + found_path)
+ raise ConfigError(f'PKI object "{item_name}" still in use by "{path_str}"')
return None
@@ -188,7 +258,38 @@ def apply(pki):
if not pki:
return None
- # XXX: restart services if the content of a certificate changes
+ if 'changed' in pki:
+ for search in sync_search:
+ for key in search['keys']:
+ changed_key = sync_translate[key]
+
+ if changed_key not in pki['changed']:
+ continue
+
+ for item_name in pki['changed'][changed_key]:
+ node_present = False
+ if changed_key == 'openvpn':
+ node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
+ else:
+ node_present = dict_search_args(pki, changed_key, item_name)
+
+ if node_present:
+ search_dict = dict_search_args(pki['system'], *search['path'])
+
+ if not search_dict:
+ continue
+
+ for found_name, found_path in dict_search_recursive(search_dict, key):
+ if found_name == item_name:
+ path_str = ' '.join(search['path'] + found_path)
+ print(f'pki: Updating config: {path_str} {found_name}')
+
+ script = search['script']
+ if found_path[0] == 'interfaces':
+ ifname = found_path[2]
+ call(f'VYOS_TAGNODE_VALUE={ifname} {script}')
+ else:
+ call(script)
return None
diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py
index 1108aebe6..58c5fd93d 100755
--- a/src/conf_mode/policy-route-interface.py
+++ b/src/conf_mode/policy-route-interface.py
@@ -24,6 +24,7 @@ from vyos.config import Config
from vyos.ifconfig import Section
from vyos.template import render
from vyos.util import cmd
+from vyos.util import run
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -47,6 +48,11 @@ def get_config(config=None):
return if_policy
+def verify_chain(table, chain):
+ # Verify policy route applied
+ code = run(f'nft list chain {table} {chain}')
+ return code == 0
+
def verify(if_policy):
# bail out early - looks like removal from running config
if not if_policy:
@@ -62,6 +68,12 @@ def verify(if_policy):
if route_name not in if_policy['policy'][route]:
raise ConfigError(f'Invalid policy route name "{name}"')
+ nft_prefix = 'VYOS_PBR6_' if route == 'route6' else 'VYOS_PBR_'
+ nft_table = 'ip6 mangle' if route == 'route6' else 'ip mangle'
+
+ if not verify_chain(nft_table, nft_prefix + route_name):
+ raise ConfigError('Policy route did not apply')
+
return None
def generate(if_policy):
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index 5de341beb..9fddbd2c6 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -25,6 +25,7 @@ from vyos.config import Config
from vyos.template import render
from vyos.util import cmd
from vyos.util import dict_search_args
+from vyos.util import dict_search_recursive
from vyos.util import run
from vyos import ConfigError
from vyos import airbag
@@ -33,6 +34,9 @@ airbag.enable()
mark_offset = 0x7FFFFFFF
nftables_conf = '/run/nftables_policy.conf'
+ROUTE_PREFIX = 'VYOS_PBR_'
+ROUTE6_PREFIX = 'VYOS_PBR6_'
+
preserve_chains = [
'VYOS_PBR_PREROUTING',
'VYOS_PBR_POSTROUTING',
@@ -46,6 +50,16 @@ valid_groups = [
'port_group'
]
+group_set_prefix = {
+ 'A_': 'address_group',
+ 'A6_': 'ipv6_address_group',
+# 'D_': 'domain_group',
+ 'M_': 'mac_group',
+ 'N_': 'network_group',
+ 'N6_': 'ipv6_network_group',
+ 'P_': 'port_group'
+}
+
def get_policy_interfaces(conf):
out = {}
interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
@@ -166,37 +180,55 @@ def verify(policy):
return None
-def cleanup_rule(table, jump_chain):
- commands = []
- results = cmd(f'nft -a list table {table}').split("\n")
- for line in results:
- if f'jump {jump_chain}' in line:
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- commands.append(f'delete rule {table} {chain} handle {handle_search[1]}')
- return commands
-
def cleanup_commands(policy):
commands = []
+ commands_chains = []
+ commands_sets = []
for table in ['ip mangle', 'ip6 mangle']:
- json_str = cmd(f'nft -j list table {table}')
+ route_node = 'route' if table == 'ip mangle' else 'route6'
+ chain_prefix = ROUTE_PREFIX if table == 'ip mangle' else ROUTE6_PREFIX
+
+ json_str = cmd(f'nft -t -j list table {table}')
obj = loads(json_str)
if 'nftables' not in obj:
continue
for item in obj['nftables']:
if 'chain' in item:
chain = item['chain']['name']
- if not chain.startswith("VYOS_PBR"):
+ if chain in preserve_chains or not chain.startswith("VYOS_PBR"):
continue
+
+ if dict_search_args(policy, route_node, chain.replace(chain_prefix, "", 1)) != None:
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands_chains.append(f'delete chain {table} {chain}')
+
+ if 'rule' in item:
+ rule = item['rule']
+ chain = rule['chain']
+ handle = rule['handle']
+
if chain not in preserve_chains:
- if table == 'ip mangle' and dict_search_args(policy, 'route', chain.replace("VYOS_PBR_", "", 1)):
- commands.append(f'flush chain {table} {chain}')
- elif table == 'ip6 mangle' and dict_search_args(policy, 'route6', chain.replace("VYOS_PBR6_", "", 1)):
- commands.append(f'flush chain {table} {chain}')
- else:
- commands += cleanup_rule(table, chain)
- commands.append(f'delete chain {table} {chain}')
- return commands
+ continue
+
+ target, _ = next(dict_search_recursive(rule['expr'], 'target'))
+
+ if target.startswith(chain_prefix):
+ if dict_search_args(policy, route_node, target.replace(chain_prefix, "", 1)) == None:
+ commands.append(f'delete rule {table} {chain} handle {handle}')
+
+ if 'set' in item:
+ set_name = item['set']['name']
+
+ for prefix, group_type in group_set_prefix.items():
+ if set_name.startswith(prefix):
+ group_name = set_name.replace(prefix, "", 1)
+ if dict_search_args(policy, 'firewall_group', group_type, group_name) != None:
+ commands_sets.append(f'flush set {table} {set_name}')
+ else:
+ commands_sets.append(f'delete set {table} {set_name}')
+
+ return commands + commands_chains + commands_sets
def generate(policy):
if not os.path.exists(nftables_conf):
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index ef6008140..3008a20e0 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -150,6 +150,16 @@ def verify(policy):
tmp = dict_search('match.ipv6.address.prefix_list', rule_config)
if tmp and tmp not in policy.get('prefix_list6', []):
raise ConfigError(f'prefix-list6 {tmp} does not exist!')
+
+ # Specified access_list6 in nexthop must exist
+ tmp = dict_search('match.ipv6.nexthop.access_list', rule_config)
+ if tmp and tmp not in policy.get('access_list6', []):
+ raise ConfigError(f'access_list6 {tmp} does not exist!')
+
+ # Specified prefix-list6 in nexthop must exist
+ tmp = dict_search('match.ipv6.nexthop.prefix_list', rule_config)
+ if tmp and tmp not in policy.get('prefix_list6', []):
+ raise ConfigError(f'prefix-list6 {tmp} does not exist!')
# When routing protocols are active some use prefix-lists, route-maps etc.
# to apply the systems routing policy to the learned or redistributed routes.
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index a9173ab87..01f14df61 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -19,6 +19,7 @@ import os
from sys import exit
from sys import argv
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_prefix_list
@@ -169,6 +170,16 @@ def verify(bgp):
peer_group = peer_config['peer_group']
if 'remote_as' in peer_config and 'remote_as' in bgp['peer_group'][peer_group]:
raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!')
+ if 'interface' in peer_config:
+ if 'peer_group' in peer_config['interface']:
+ peer_group = peer_config['interface']['peer_group']
+ if 'remote_as' in peer_config['interface'] and 'remote_as' in bgp['peer_group'][peer_group]:
+ raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!')
+ if 'v6only' in peer_config['interface']:
+ if 'peer_group' in peer_config['interface']['v6only']:
+ peer_group = peer_config['interface']['v6only']['peer_group']
+ if 'remote_as' in peer_config['interface']['v6only'] and 'remote_as' in bgp['peer_group'][peer_group]:
+ raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!')
# Only checks for ipv4 and ipv6 neighbors
# Check if neighbor address is assigned as system interface address
@@ -188,6 +199,9 @@ def verify(bgp):
if 'source_interface' in peer_config['interface']:
raise ConfigError(f'"source-interface" option not allowed for neighbor "{peer}"')
+ if 'address_family' not in peer_config:
+ Warning(f'BGP neighbor "{peer}" requires address-family!')
+
for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec',
'ipv6_unicast', 'ipv6_multicast', 'ipv6_labeled_unicast', 'ipv6_flowspec',
'l2vpn_evpn']:
diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py
new file mode 100755
index 000000000..c1a1a45e1
--- /dev/null
+++ b/src/conf_mode/protocols_eigrp.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+from sys import exit
+from sys import argv
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'eigrp']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'eigrp'] or base_path
+ eigrp = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ # Assign the name of our VRF context. This MUST be done before the return
+ # statement below, else on deletion we will delete the default instance
+ # instead of the VRF instance.
+ if vrf: eigrp.update({'vrf' : vrf})
+
+ if not conf.exists(base):
+ eigrp.update({'deleted' : ''})
+ if not vrf:
+ # We are running in the default VRF context, thus we can not delete
+ # our main EIGRP instance if there are dependent EIGRP VRF instances.
+ eigrp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return eigrp
+
+ # We also need some additional information from the config, prefix-lists
+ # and route-maps for instance. They will be used in verify().
+ #
+ # XXX: one MUST always call this without the key_mangling() option! See
+ # vyos.configverify.verify_common_route_maps() for more information.
+ tmp = conf.get_config_dict(['policy'])
+ # Merge policy dict into "regular" config dict
+ eigrp = dict_merge(tmp, eigrp)
+
+ import pprint
+ pprint.pprint(eigrp)
+ return eigrp
+
+def verify(eigrp):
+ pass
+
+def generate(eigrp):
+ if not eigrp or 'deleted' in eigrp:
+ return None
+
+ eigrp['protocol'] = 'eigrp' # required for frr/vrf.route-map.frr.j2
+ eigrp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', eigrp)
+ eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp)
+
+def apply(eigrp):
+ eigrp_daemon = 'eigrpd'
+ zebra_daemon = 'zebra'
+
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ # The route-map used for the FIB (zebra) is part of the zebra daemon
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(r'(\s+)?ip protocol eigrp route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
+ if 'frr_zebra_config' in eigrp:
+ frr_cfg.add_before(frr.default_add_before, eigrp['frr_zebra_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
+
+ # Generate empty helper string which can be ammended to FRR commands, it
+ # will be either empty (default VRF) or contain the "vrf <name" statement
+ vrf = ''
+ if 'vrf' in eigrp:
+ vrf = ' vrf ' + eigrp['vrf']
+
+ frr_cfg.load_configuration(eigrp_daemon)
+ frr_cfg.modify_section(f'^router eigrp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True)
+ if 'frr_eigrpd_config' in eigrp:
+ frr_cfg.add_before(frr.default_add_before, eigrp['frr_eigrpd_config'])
+ frr_cfg.commit_configuration(eigrp_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index 92b335085..b247ce2ab 100755
--- a/src/conf_mode/protocols_nhrp.py
+++ b/src/conf_mode/protocols_nhrp.py
@@ -104,8 +104,8 @@ def apply(nhrp):
if rule_handle:
remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle)
- action = 'reload-or-restart' if nhrp and 'tunnel' in nhrp else 'stop'
- run(f'systemctl {action} opennhrp')
+ action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
+ run(f'systemctl {action} opennhrp.service')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py
index a76c1ce76..c78d90396 100755
--- a/src/conf_mode/protocols_rip.py
+++ b/src/conf_mode/protocols_rip.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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
diff --git a/src/conf_mode/service_event_handler.py b/src/conf_mode/service_event_handler.py
new file mode 100755
index 000000000..5440d1056
--- /dev/null
+++ b/src/conf_mode/service_event_handler.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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
+from pathlib import Path
+
+from vyos.config import Config
+from vyos.util import call, dict_search
+from vyos import ConfigError
+from vyos import airbag
+
+airbag.enable()
+
+service_name = 'vyos-event-handler'
+service_conf = Path(f'/run/{service_name}.conf')
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['service', 'event-handler', 'event']
+ config = conf.get_config_dict(base,
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return config
+
+
+def verify(config):
+ # bail out early - looks like removal from running config
+ if not config:
+ return None
+
+ for name, event_config in config.items():
+ if not dict_search('filter.pattern', event_config) or not dict_search(
+ 'script.path', event_config):
+ raise ConfigError(
+ 'Event-handler: both pattern and script path items are mandatory'
+ )
+
+ if dict_search('script.environment.message', event_config):
+ raise ConfigError(
+ 'Event-handler: "message" environment variable is reserved for log message text'
+ )
+
+
+def generate(config):
+ if not config:
+ # Remove old config and return
+ service_conf.unlink(missing_ok=True)
+ return None
+
+ # Write configuration file
+ conf_json = json.dumps(config, indent=4)
+ service_conf.write_text(conf_json)
+
+ return None
+
+
+def apply(config):
+ if config:
+ call(f'systemctl restart {service_name}.service')
+ else:
+ call(f'systemctl stop {service_name}.service')
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 559d1bcd5..61f484129 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -53,6 +53,8 @@ default_config_data = {
'radius_nas_ip': '',
'radius_source_address': '',
'radius_shaper_attr': '',
+ 'radius_shaper_enable': False,
+ 'radius_shaper_multiplier': '',
'radius_shaper_vendor': '',
'radius_dynamic_author': '',
'thread_cnt': get_half_cpus()
@@ -196,6 +198,18 @@ def get_config(config=None):
if conf.exists(['nas-ip-address']):
ipoe['radius_nas_ip'] = conf.return_value(['nas-ip-address'])
+ if conf.exists(['rate-limit', 'attribute']):
+ ipoe['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute'])
+
+ if conf.exists(['rate-limit', 'enable']):
+ ipoe['radius_shaper_enable'] = True
+
+ if conf.exists(['rate-limit', 'multiplier']):
+ ipoe['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier'])
+
+ if conf.exists(['rate-limit', 'vendor']):
+ ipoe['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor'])
+
if conf.exists(['source-address']):
ipoe['radius_source_address'] = conf.return_value(['source-address'])
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 102a87318..62f5e1ddf 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -99,15 +99,31 @@ def get_config(config=None):
monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False)
monitoring['nft_chains'] = get_nft_filter_chains()
- if 'authentication' in monitoring or \
- 'url' in monitoring:
- monitoring['influxdb_configured'] = True
+ # Redefine azure group-metrics 'single-table' and 'table-per-metric'
+ if 'azure_data_explorer' in monitoring:
+ if 'single-table' in monitoring['azure_data_explorer']['group_metrics']:
+ monitoring['azure_data_explorer']['group_metrics'] = 'SingleTable'
+ else:
+ monitoring['azure_data_explorer']['group_metrics'] = 'TablePerMetric'
+ # Set azure env
+ if 'authentication' in monitoring['azure_data_explorer']:
+ auth_config = monitoring['azure_data_explorer']['authentication']
+ if {'client_id', 'client_secret', 'tenant_id'} <= set(auth_config):
+ os.environ['AZURE_CLIENT_ID'] = auth_config['client_id']
+ os.environ['AZURE_CLIENT_SECRET'] = auth_config['client_secret']
+ os.environ['AZURE_TENANT_ID'] = auth_config['tenant_id']
# Ignore default XML values if config doesn't exists
# Delete key from dict
+ if not conf.exists(base + ['influxdb']):
+ del monitoring['influxdb']
+
if not conf.exists(base + ['prometheus-client']):
del monitoring['prometheus_client']
+ if not conf.exists(base + ['azure-data-explorer']):
+ del monitoring['azure_data_explorer']
+
return monitoring
def verify(monitoring):
@@ -115,15 +131,34 @@ def verify(monitoring):
if not monitoring:
return None
- if 'influxdb_configured' in monitoring:
- if 'authentication' not in monitoring or \
- 'organization' not in monitoring['authentication'] or \
- 'token' not in monitoring['authentication']:
- raise ConfigError(f'Authentication "organization and token" are mandatory!')
+ # Verify influxdb
+ if 'influxdb' in monitoring:
+ if 'authentication' not in monitoring['influxdb'] or \
+ 'organization' not in monitoring['influxdb']['authentication'] or \
+ 'token' not in monitoring['influxdb']['authentication']:
+ raise ConfigError(f'influxdb authentication "organization and token" are mandatory!')
+
+ if 'url' not in monitoring['influxdb']:
+ raise ConfigError(f'Monitoring influxdb "url" is mandatory!')
- if 'url' not in monitoring:
+ # Verify azure-data-explorer
+ if 'azure_data_explorer' in monitoring:
+ if 'authentication' not in monitoring['azure_data_explorer'] or \
+ 'client_id' not in monitoring['azure_data_explorer']['authentication'] or \
+ 'client_secret' not in monitoring['azure_data_explorer']['authentication'] or \
+ 'tenant_id' not in monitoring['azure_data_explorer']['authentication']:
+ raise ConfigError(f'Authentication "client-id, client-secret and tenant-id" are mandatory!')
+
+ if 'database' not in monitoring['azure_data_explorer']:
+ raise ConfigError(f'Monitoring "database" is mandatory!')
+
+ if 'url' not in monitoring['azure_data_explorer']:
raise ConfigError(f'Monitoring "url" is mandatory!')
+ if monitoring['azure_data_explorer']['group_metrics'] == 'SingleTable' and \
+ 'table' not in monitoring['azure_data_explorer']:
+ raise ConfigError(f'Monitoring "table" name for single-table mode is mandatory!')
+
# Verify Splunk
if 'splunk' in monitoring:
if 'authentication' not in monitoring['splunk'] or \
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index 71b758399..ff7caaa84 100755
--- a/src/conf_mode/service_router-advert.py
+++ b/src/conf_mode/service_router-advert.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,7 +17,7 @@
import os
from sys import exit
-
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
@@ -79,22 +79,35 @@ def verify(rtradv):
if 'interface' not in rtradv:
return None
- for interface in rtradv['interface']:
- interface = rtradv['interface'][interface]
+ for interface, interface_config in rtradv['interface'].items():
if 'prefix' in interface:
- for prefix in interface['prefix']:
- prefix = interface['prefix'][prefix]
- valid_lifetime = prefix['valid_lifetime']
+ for prefix, prefix_config in interface_config['prefix'].items():
+ valid_lifetime = prefix_config['valid_lifetime']
if valid_lifetime == 'infinity':
valid_lifetime = 4294967295
- preferred_lifetime = prefix['preferred_lifetime']
+ preferred_lifetime = prefix_config['preferred_lifetime']
if preferred_lifetime == 'infinity':
preferred_lifetime = 4294967295
if not (int(valid_lifetime) > int(preferred_lifetime)):
raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime')
+ if 'name_server_lifetime' in interface_config:
+ # man page states:
+ # The maximum duration how long the RDNSS entries are used for name
+ # resolution. A value of 0 means the nameserver must no longer be
+ # used. The value, if not 0, must be at least MaxRtrAdvInterval. To
+ # ensure stale RDNSS info gets removed in a timely fashion, this
+ # should not be greater than 2*MaxRtrAdvInterval.
+ lifetime = int(interface_config['name_server_lifetime'])
+ interval_max = int(interface_config['interval']['max'])
+ if lifetime > 0:
+ if lifetime < int(interval_max):
+ raise ConfigError(f'RDNSS lifetime must be at least "{interval_max}" seconds!')
+ if lifetime > 2* interval_max:
+ Warning(f'RDNSS lifetime should not exceed "{2 * interval_max}" which is two times "interval max"!')
+
return None
def generate(rtradv):
@@ -105,15 +118,16 @@ def generate(rtradv):
return None
def apply(rtradv):
+ systemd_service = 'radvd.service'
if not rtradv:
# bail out early - looks like removal from running config
- call('systemctl stop radvd.service')
+ call(f'systemctl stop {systemd_service}')
if os.path.exists(config_file):
os.unlink(config_file)
return None
- call('systemctl restart radvd.service')
+ call(f'systemctl reload-or-restart {systemd_service}')
return None
diff --git a/src/conf_mode/service_sla.py b/src/conf_mode/service_sla.py
new file mode 100755
index 000000000..e7c3ca59c
--- /dev/null
+++ b/src/conf_mode/service_sla.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render
+from vyos.util import call
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+
+owamp_config_dir = '/etc/owamp-server'
+owamp_config_file = f'{owamp_config_dir}/owamp-server.conf'
+systemd_override_owamp = r'/etc/systemd/system/owamp-server.d/20-override.conf'
+
+twamp_config_dir = '/etc/twamp-server'
+twamp_config_file = f'{twamp_config_dir}/twamp-server.conf'
+systemd_override_twamp = r'/etc/systemd/system/twamp-server.d/20-override.conf'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'sla']
+ if not conf.exists(base):
+ return None
+
+ sla = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ sla = dict_merge(default_values, sla)
+
+ # Ignore default XML values if config doesn't exists
+ # Delete key from dict
+ if not conf.exists(base + ['owamp-server']):
+ del sla['owamp_server']
+ if not conf.exists(base + ['twamp-server']):
+ del sla['twamp_server']
+
+ return sla
+
+def verify(sla):
+ if not sla:
+ return None
+
+def generate(sla):
+ if not sla:
+ return None
+
+ render(owamp_config_file, 'sla/owamp-server.conf.j2', sla)
+ render(systemd_override_owamp, 'sla/owamp-override.conf.j2', sla)
+
+ render(twamp_config_file, 'sla/twamp-server.conf.j2', sla)
+ render(systemd_override_twamp, 'sla/twamp-override.conf.j2', sla)
+
+ return None
+
+def apply(sla):
+ owamp_service = 'owamp-server.service'
+ twamp_service = 'twamp-server.service'
+
+ call('systemctl daemon-reload')
+
+ if not sla or 'owamp_server' not in sla:
+ call(f'systemctl stop {owamp_service}')
+
+ if os.path.exists(owamp_config_file):
+ os.unlink(owamp_config_file)
+
+ if not sla or 'twamp_server' not in sla:
+ call(f'systemctl stop {twamp_service}')
+ if os.path.exists(twamp_config_file):
+ os.unlink(twamp_config_file)
+
+ if sla and 'owamp_server' in sla:
+ call(f'systemctl reload-or-restart {owamp_service}')
+
+ if sla and 'twamp_server' in sla:
+ call(f'systemctl reload-or-restart {twamp_service}')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index ae060580d..5cd24db32 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -293,7 +293,15 @@ def apply(snmp):
call(f'systemctl restart {systemd_service}')
# Enable AgentX in FRR
- call('vtysh -c "configure terminal" -c "agentx" >/dev/null')
+ # This should be done for each daemon individually because common command
+ # works only if all the daemons started with SNMP support
+ frr_daemons_list = [
+ 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'ripngd', 'isisd', 'ldpd', 'zebra'
+ ]
+ for frr_daemon in frr_daemons_list:
+ call(
+ f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null'
+ )
return None
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 487e8c229..28669694b 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -33,6 +33,9 @@ airbag.enable()
config_file = r'/run/sshd/sshd_config'
systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf'
+sshguard_config_file = '/etc/sshguard/sshguard.conf'
+sshguard_whitelist = '/etc/sshguard/whitelist'
+
key_rsa = '/etc/ssh/ssh_host_rsa_key'
key_dsa = '/etc/ssh/ssh_host_dsa_key'
key_ed25519 = '/etc/ssh/ssh_host_ed25519_key'
@@ -54,6 +57,11 @@ def get_config(config=None):
# pass config file path - used in override template
ssh['config_file'] = config_file
+ # Ignore default XML values if config doesn't exists
+ # Delete key from dict
+ if not conf.exists(base + ['dynamic-protection']):
+ del ssh['dynamic_protection']
+
return ssh
def verify(ssh):
@@ -86,6 +94,10 @@ def generate(ssh):
render(config_file, 'ssh/sshd_config.j2', ssh)
render(systemd_override, 'ssh/override.conf.j2', ssh)
+
+ if 'dynamic_protection' in ssh:
+ render(sshguard_config_file, 'ssh/sshguard_config.j2', ssh)
+ render(sshguard_whitelist, 'ssh/sshguard_whitelist.j2', ssh)
# Reload systemd manager configuration
call('systemctl daemon-reload')
@@ -95,7 +107,12 @@ def apply(ssh):
if not ssh:
# SSH access is removed in the commit
call('systemctl stop ssh.service')
+ call('systemctl stop sshguard.service')
return None
+ if 'dynamic_protection' not in ssh:
+ call('systemctl stop sshguard.service')
+ else:
+ call('systemctl restart sshguard.service')
call('systemctl restart ssh.service')
return None
diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py
index 05fc3a97a..0c5063ed3 100755
--- a/src/conf_mode/system-ip.py
+++ b/src/conf_mode/system-ip.py
@@ -64,6 +64,11 @@ def apply(opt):
value = '0' if (tmp != None) else '1'
write_file('/proc/sys/net/ipv4/conf/all/forwarding', value)
+ # enable/disable IPv4 directed broadcast forwarding
+ tmp = dict_search('disable_directed_broadcast', opt)
+ value = '0' if (tmp != None) else '1'
+ write_file('/proc/sys/net/ipv4/conf/all/bc_forwarding', value)
+
# configure multipath
tmp = dict_search('multipath.ignore_unreachable_nexthops', opt)
value = '1' if (tmp != None) else '0'
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index a9d3bbe31..20132456c 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -52,8 +52,6 @@ def get_config(config=None):
{
'global': {
'log-file': '/var/log/messages',
- 'max-size': 262144,
- 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog',
'selectors': '*.notice;local7.debug',
'max-files': '5',
'preserver_fqdn': False
diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py
new file mode 100755
index 000000000..1af0055f6
--- /dev/null
+++ b/src/conf_mode/system_frr.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+
+from pathlib import Path
+from sys import exit
+
+from vyos import ConfigError
+from vyos import airbag
+from vyos.config import Config
+from vyos.logger import syslog
+from vyos.template import render_to_string
+from vyos.util import read_file, write_file, run
+airbag.enable()
+
+# path to daemons config and config status files
+config_file = '/etc/frr/daemons'
+vyos_status_file = '/tmp/vyos-config-status'
+# path to watchfrr for FRR control
+watchfrr = '/usr/lib/frr/watchfrr.sh'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['system', 'frr']
+ frr_config = conf.get_config_dict(base, get_first_key=True)
+
+ return frr_config
+
+
+def verify(frr_config):
+ # Nothing to verify here
+ pass
+
+
+def generate(frr_config):
+ # read daemons config file
+ daemons_config_current = read_file(config_file)
+ # generate new config file
+ daemons_config_new = render_to_string('frr/daemons.frr.tmpl', frr_config)
+ # update configuration file if this is necessary
+ if daemons_config_new != daemons_config_current:
+ syslog.warning('FRR daemons configuration file need to be changed')
+ write_file(config_file, daemons_config_new)
+ frr_config['config_file_changed'] = True
+
+
+def apply(frr_config):
+ # check if this is initial commit during boot or intiated by CLI
+ # if the file exists, this must be CLI commit
+ commit_type_cli = Path(vyos_status_file).exists()
+ # display warning to user
+ if commit_type_cli and frr_config.get('config_file_changed'):
+ # Since FRR restart is not safe thing, better to give
+ # control over this to users
+ print('''
+ You need to reboot a router (preferred) or restart FRR
+ to apply changes in modules settings
+ ''')
+ # restart FRR automatically. DUring the initial boot this should be
+ # safe in most cases
+ if not commit_type_cli and frr_config.get('config_file_changed'):
+ syslog.warning('Restarting FRR to apply changes in modules')
+ run(f'{watchfrr} restart')
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index db53463cf..23e5162ba 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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,6 +20,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
+from vyos.configdict import dict_merge
from vyos.configverify import verify_accel_ppp_base_service
from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
@@ -50,10 +51,10 @@ def get_config(config=None):
# retrieve common dictionary keys
sstp = get_accel_dict(conf, base, sstp_chap_secrets)
-
if sstp:
sstp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
return sstp
@@ -121,7 +122,6 @@ def generate(sstp):
ca_cert_name = sstp['ssl']['ca_certificate']
pki_ca = sstp['pki']['ca'][ca_cert_name]
-
write_file(cert_file_path, wrap_certificate(pki_cert['certificate']))
write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
write_file(ca_cert_file_path, wrap_certificate(pki_ca['certificate']))
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index f2d041083..1b4156895 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -83,7 +83,8 @@ def get_config(config=None):
conf = Config()
base = ['vrf']
- vrf = conf.get_config_dict(base, get_first_key=True)
+ vrf = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True, get_first_key=True)
# determine which VRF has been removed
for name in node_changed(conf, base + ['name']):
@@ -112,8 +113,14 @@ def verify(vrf):
f'static routes installed!')
if 'name' in vrf:
+ reserved_names = ["add", "all", "broadcast", "default", "delete", "dev", "get", "inet", "mtu", "link", "type",
+ "vrf"]
table_ids = []
for name, config in vrf['name'].items():
+ # Reserved VRF names
+ if name in reserved_names:
+ raise ConfigError(f'VRF name "{name}" is reserved and connot be used!')
+
# table id is mandatory
if 'table' not in config:
raise ConfigError(f'VRF "{name}" table id is mandatory!')
@@ -152,7 +159,7 @@ def apply(vrf):
# set the default VRF global behaviour
bind_all = '0'
- if 'bind-to-all' in vrf:
+ if 'bind_to_all' in vrf:
bind_all = '1'
sysctl_write('net.ipv4.tcp_l3mdev_accept', bind_all)
sysctl_write('net.ipv4.udp_l3mdev_accept', bind_all)
@@ -222,6 +229,15 @@ def apply(vrf):
# add VRF description if available
vrf_if.set_alias(config.get('description', ''))
+ # Enable/Disable IPv4 forwarding
+ tmp = dict_search('ip.disable_forwarding', config)
+ value = '0' if (tmp != None) else '1'
+ vrf_if.set_ipv4_forwarding(value)
+ # Enable/Disable IPv6 forwarding
+ tmp = dict_search('ipv6.disable_forwarding', config)
+ value = '0' if (tmp != None) else '1'
+ vrf_if.set_ipv6_forwarding(value)
+
# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as
diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py
index 070a4deea..a52c52706 100755
--- a/src/conf_mode/zone_policy.py
+++ b/src/conf_mode/zone_policy.py
@@ -155,7 +155,7 @@ def get_local_from(zone_policy, local_zone_name):
def cleanup_commands():
commands = []
for table in ['ip filter', 'ip6 filter']:
- json_str = cmd(f'nft -j list table {table}')
+ json_str = cmd(f'nft -t -j list table {table}')
obj = loads(json_str)
if 'nftables' not in obj:
continue
diff --git a/src/etc/cron.d/vyos-geoip b/src/etc/cron.d/vyos-geoip
new file mode 100644
index 000000000..9bb38a850
--- /dev/null
+++ b/src/etc/cron.d/vyos-geoip
@@ -0,0 +1 @@
+30 4 * * 1 root sg vyattacfg "/usr/libexec/vyos/geoip-update.py --force" >/tmp/geoip-update.log 2>&1
diff --git a/src/etc/cron.hourly/vyos-logrotate-hourly b/src/etc/cron.hourly/vyos-logrotate-hourly
deleted file mode 100755
index f4f56a9c2..000000000
--- a/src/etc/cron.hourly/vyos-logrotate-hourly
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-test -x /usr/sbin/logrotate || exit 0
-/usr/sbin/logrotate /etc/logrotate.conf
diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
index 74a7e83bf..5d879471d 100644
--- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
+++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
@@ -26,7 +26,7 @@ function iptovtysh () {
local VTYSH_GATEWAY=""
local VTYSH_DEV=""
local VTYSH_TAG="210"
- local VTYSH_DISTANCE=""
+ local VTYSH_DISTANCE=$IF_METRIC
# convert default route to 0.0.0.0/0
if [ "$4" == "default" ] ; then
VTYSH_NETADDR="0.0.0.0/0"
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index e03d3a29c..4feb7e09a 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -27,6 +27,12 @@ net.ipv4.conf.all.arp_announce=2
# Enable packet forwarding for IPv4
net.ipv4.ip_forward=1
+# Enable directed broadcast forwarding feature described in rfc1812#section-5.3.5.2 and rfc2644.
+# Note that setting the 'all' entry to 1 doesn't enable directed broadcast forwarding on all interfaces.
+# To enable directed broadcast forwarding on an interface, both the 'all' entry and the input interface entry should be set to 1.
+net.ipv4.conf.all.bc_forwarding=1
+net.ipv4.conf.default.bc_forwarding=0
+
# if a primary address is removed from an interface promote the
# secondary address if available
net.ipv4.conf.all.promote_secondaries=1
diff --git a/src/etc/systemd/system/logrotate.timer.d/10-override.conf b/src/etc/systemd/system/logrotate.timer.d/10-override.conf
new file mode 100644
index 000000000..f50c2b082
--- /dev/null
+++ b/src/etc/systemd/system/logrotate.timer.d/10-override.conf
@@ -0,0 +1,2 @@
+[Timer]
+OnCalendar=hourly
diff --git a/src/helpers/geoip-update.py b/src/helpers/geoip-update.py
new file mode 100755
index 000000000..34accf2cc
--- /dev/null
+++ b/src/helpers/geoip-update.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 sys
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.firewall import geoip_update
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = ConfigTreeQuery()
+ base = ['firewall']
+
+ if not conf.exists(base):
+ return None
+
+ return conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--force", help="Force update", action="store_true")
+ args = parser.parse_args()
+
+ firewall = get_config()
+
+ if not geoip_update(firewall, force=args.force):
+ sys.exit(1)
diff --git a/src/helpers/vyos-domain-group-resolve.py b/src/helpers/vyos-domain-group-resolve.py
new file mode 100755
index 000000000..6b677670b
--- /dev/null
+++ b/src/helpers/vyos-domain-group-resolve.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 time
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.firewall import get_ips_domains_dict
+from vyos.firewall import nft_add_set_elements
+from vyos.firewall import nft_flush_set
+from vyos.firewall import nft_init_set
+from vyos.firewall import nft_update_set_elements
+from vyos.util import call
+
+
+base = ['firewall', 'group', 'domain-group']
+check_required = True
+# count_failed = 0
+# Timeout in sec between checks
+timeout = 300
+
+domain_state = {}
+
+if __name__ == '__main__':
+
+ while check_required:
+ config = ConfigTreeQuery()
+ if config.exists(base):
+ domain_groups = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ for set_name, domain_config in domain_groups.items():
+ list_domains = domain_config['address']
+ elements = []
+ ip_dict = get_ips_domains_dict(list_domains)
+
+ for domain in list_domains:
+ # Resolution succeeded, update domain state
+ if domain in ip_dict:
+ domain_state[domain] = ip_dict[domain]
+ elements += ip_dict[domain]
+ # Resolution failed, use previous domain state
+ elif domain in domain_state:
+ elements += domain_state[domain]
+
+ # Resolve successful
+ if elements:
+ nft_update_set_elements(f'D_{set_name}', elements)
+ time.sleep(timeout)
diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7
index 5f4cff90d..626d6849f 100755
--- a/src/migration-scripts/firewall/6-to-7
+++ b/src/migration-scripts/firewall/6-to-7
@@ -194,11 +194,12 @@ if config.exists(base + ['ipv6-name']):
if config.exists(rule_icmp + ['type']):
tmp = config.return_value(rule_icmp + ['type'])
- type_code_match = re.match(r'^(\d+)/(\d+)$', tmp)
+ type_code_match = re.match(r'^(\d+)(?:/(\d+))?$', tmp)
if type_code_match:
config.set(rule_icmp + ['type'], value=type_code_match[1])
- config.set(rule_icmp + ['code'], value=type_code_match[2])
+ if type_code_match[2]:
+ config.set(rule_icmp + ['code'], value=type_code_match[2])
elif tmp in icmpv6_remove:
config.delete(rule_icmp + ['type'])
elif tmp in icmpv6_translations:
diff --git a/src/migration-scripts/interfaces/24-to-25 b/src/migration-scripts/interfaces/24-to-25
index 93ce9215f..4095f2a3e 100755
--- a/src/migration-scripts/interfaces/24-to-25
+++ b/src/migration-scripts/interfaces/24-to-25
@@ -20,6 +20,7 @@
import os
import sys
from vyos.configtree import ConfigTree
+from vyos.pki import CERT_BEGIN
from vyos.pki import load_certificate
from vyos.pki import load_crl
from vyos.pki import load_dh_parameters
@@ -27,6 +28,7 @@ from vyos.pki import load_private_key
from vyos.pki import encode_certificate
from vyos.pki import encode_dh_parameters
from vyos.pki import encode_private_key
+from vyos.pki import verify_crl
from vyos.util import run
def wrapped_pem_to_config_value(pem):
@@ -129,6 +131,8 @@ if config.exists(base):
config.delete(base + [interface, 'tls', 'crypt-file'])
+ ca_certs = {}
+
if config.exists(x509_base + ['ca-cert-file']):
if not config.exists(pki_base + ['ca']):
config.set(pki_base + ['ca'])
@@ -136,20 +140,27 @@ if config.exists(base):
cert_file = config.return_value(x509_base + ['ca-cert-file'])
cert_path = os.path.join(AUTH_DIR, cert_file)
- cert = None
if os.path.isfile(cert_path):
if not os.access(cert_path, os.R_OK):
run(f'sudo chmod 644 {cert_path}')
with open(cert_path, 'r') as f:
- cert_data = f.read()
- cert = load_certificate(cert_data, wrap_tags=False)
-
- if cert:
- cert_pem = encode_certificate(cert)
- config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
- config.set(x509_base + ['ca-certificate'], value=pki_name)
+ certs_str = f.read()
+ certs_data = certs_str.split(CERT_BEGIN)
+ index = 1
+ for cert_data in certs_data[1:]:
+ cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False)
+
+ if cert:
+ ca_certs[f'{pki_name}_{index}'] = cert
+ cert_pem = encode_certificate(cert)
+ config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False)
+ else:
+ print(f'Failed to migrate CA certificate on openvpn interface {interface}')
+
+ index += 1
else:
print(f'Failed to migrate CA certificate on openvpn interface {interface}')
@@ -163,6 +174,7 @@ if config.exists(base):
crl_file = config.return_value(x509_base + ['crl-file'])
crl_path = os.path.join(AUTH_DIR, crl_file)
crl = None
+ crl_ca_name = None
if os.path.isfile(crl_path):
if not os.access(crl_path, os.R_OK):
@@ -172,9 +184,14 @@ if config.exists(base):
crl_data = f.read()
crl = load_crl(crl_data, wrap_tags=False)
- if crl:
+ for ca_name, ca_cert in ca_certs.items():
+ if verify_crl(crl, ca_cert):
+ crl_ca_name = ca_name
+ break
+
+ if crl and crl_ca_name:
crl_pem = encode_certificate(crl)
- config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
+ config.set(pki_base + ['ca', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
else:
print(f'Failed to migrate CRL on openvpn interface {interface}')
diff --git a/src/migration-scripts/ipsec/5-to-6 b/src/migration-scripts/ipsec/5-to-6
index e9adee01b..3a8b3926d 100755
--- a/src/migration-scripts/ipsec/5-to-6
+++ b/src/migration-scripts/ipsec/5-to-6
@@ -78,7 +78,7 @@ if config.exists(log_mode):
base_interfaces = base + ['ipsec-interfaces', 'interface']
if config.exists(base_interfaces):
config.copy(base_interfaces, base + ['interface'])
- config.delete(base_interfaces)
+ config.delete(base + ['ipsec-interfaces'])
# Remove deprecated "auto-update" option
tmp = base + ['auto-update']
diff --git a/src/migration-scripts/monitoring/0-to-1 b/src/migration-scripts/monitoring/0-to-1
new file mode 100755
index 000000000..803cdb49c
--- /dev/null
+++ b/src/migration-scripts/monitoring/0-to-1
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+# T3417: migrate IS-IS tagNode to node as we can only have one IS-IS process
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'monitoring', 'telegraf']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['authentication', 'organization']):
+ tmp = config.return_value(base + ['authentication', 'organization'])
+ config.delete(base + ['authentication', 'organization'])
+ config.set(base + ['influxdb', 'authentication', 'organization'], value=tmp)
+
+if config.exists(base + ['authentication', 'token']):
+ tmp = config.return_value(base + ['authentication', 'token'])
+ config.delete(base + ['authentication', 'token'])
+ config.set(base + ['influxdb', 'authentication', 'token'], value=tmp)
+
+if config.exists(base + ['bucket']):
+ tmp = config.return_value(base + ['bucket'])
+ config.delete(base + ['bucket'])
+ config.set(base + ['influxdb', 'bucket'], value=tmp)
+
+if config.exists(base + ['port']):
+ tmp = config.return_value(base + ['port'])
+ config.delete(base + ['port'])
+ config.set(base + ['influxdb', 'port'], value=tmp)
+
+if config.exists(base + ['url']):
+ tmp = config.return_value(base + ['url'])
+ config.delete(base + ['url'])
+ config.set(base + ['influxdb', 'url'], value=tmp)
+
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/policy/2-to-3 b/src/migration-scripts/policy/2-to-3
new file mode 100755
index 000000000..84cb1ff4a
--- /dev/null
+++ b/src/migration-scripts/policy/2-to-3
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+# T3976: change cli
+# from: set policy route-map FOO rule 10 match ipv6 nexthop 'h:h:h:h:h:h:h:h'
+# to: set policy route-map FOO rule 10 match ipv6 nexthop address 'h:h:h:h:h:h:h:h'
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['policy', 'route-map']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+for route_map in config.list_nodes(base):
+ if not config.exists(base + [route_map, 'rule']):
+ continue
+ for rule in config.list_nodes(base + [route_map, 'rule']):
+ base_rule = base + [route_map, 'rule', rule]
+
+ if config.exists(base_rule + ['match', 'ipv6', 'nexthop']):
+ tmp = config.return_value(base_rule + ['match', 'ipv6', 'nexthop'])
+ config.delete(base_rule + ['match', 'ipv6', 'nexthop'])
+ config.set(base_rule + ['match', 'ipv6', 'nexthop', 'address'], value=tmp)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1) \ No newline at end of file
diff --git a/src/migration-scripts/system/23-to-24 b/src/migration-scripts/system/23-to-24
index 5ea71d51a..97fe82462 100755
--- a/src/migration-scripts/system/23-to-24
+++ b/src/migration-scripts/system/23-to-24
@@ -20,6 +20,7 @@ from ipaddress import ip_interface
from ipaddress import ip_address
from sys import exit, argv
from vyos.configtree import ConfigTree
+from vyos.template import is_ipv4
if (len(argv) < 1):
print("Must specify file name!")
@@ -37,6 +38,9 @@ def fixup_cli(config, path, interface):
if config.exists(path + ['address']):
for address in config.return_values(path + ['address']):
tmp = ip_interface(address)
+ # ARP is only available for IPv4 ;-)
+ if not is_ipv4(tmp):
+ continue
if ip_address(host) in tmp.network.hosts():
mac = config.return_value(tmp_base + [host, 'hwaddr'])
iface_path = ['protocols', 'static', 'arp', 'interface']
diff --git a/src/migration-scripts/system/24-to-25 b/src/migration-scripts/system/24-to-25
new file mode 100755
index 000000000..c2f70689d
--- /dev/null
+++ b/src/migration-scripts/system/24-to-25
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+#
+# Migrate system syslog global archive to system logs logrotate messages
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['system', 'syslog', 'global', 'archive']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ exit(0)
+
+if config.exists(base + ['file']):
+ tmp = config.return_value(base + ['file'])
+ config.set(['system', 'logs', 'logrotate', 'messages', 'rotate'], value=tmp)
+
+if config.exists(base + ['size']):
+ tmp = config.return_value(base + ['size'])
+ tmp = max(round(int(tmp) / 1024), 1) # kb -> mb
+ config.set(['system', 'logs', 'logrotate', 'messages', 'max-size'], value=tmp)
+
+config.delete(base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/vrf/0-to-1 b/src/migration-scripts/vrf/0-to-1
index 2b41ef3c7..5df751113 100755
--- a/src/migration-scripts/vrf/0-to-1
+++ b/src/migration-scripts/vrf/0-to-1
@@ -114,6 +114,16 @@ for vrf in config.list_nodes(base):
if config.exists(vrf_path):
config.rename(vrf_path, 'vrf')
+ next_hop = route_path + [route, 'interface']
+ if config.exists(next_hop):
+ for interface in config.list_nodes(next_hop):
+ interface_path = next_hop + [interface, 'next-hop-interface']
+ if config.exists(interface_path):
+ config.rename(interface_path, 'interface')
+ vrf_path = next_hop + [interface, 'next-hop-vrf']
+ if config.exists(vrf_path):
+ config.rename(vrf_path, 'vrf')
+
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
diff --git a/src/op_mode/clear_dhcp_lease.py b/src/op_mode/clear_dhcp_lease.py
new file mode 100755
index 000000000..250dbcce1
--- /dev/null
+++ b/src/op_mode/clear_dhcp_lease.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+import argparse
+import re
+
+from isc_dhcp_leases import Lease
+from isc_dhcp_leases import IscDhcpLeases
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import ask_yes_no
+from vyos.util import call
+from vyos.util import commit_in_progress
+
+
+config = ConfigTreeQuery()
+base = ['service', 'dhcp-server']
+lease_file = '/config/dhcpd.leases'
+
+
+def del_lease_ip(address):
+ """
+ Read lease_file and write data to this file
+ without specific section "lease ip"
+ Delete section "lease x.x.x.x { x;x;x; }"
+ """
+ with open(lease_file, encoding='utf-8') as f:
+ data = f.read().rstrip()
+ lease_config_ip = '{(?P<config>[\s\S]+?)\n}'
+ pattern = rf"lease {address} {lease_config_ip}"
+ # Delete lease for ip block
+ data = re.sub(pattern, '', data)
+
+ # Write new data to original lease_file
+ with open(lease_file, 'w', encoding='utf-8') as f:
+ f.write(data)
+
+def is_ip_in_leases(address):
+ """
+ Return True if address found in the lease file
+ """
+ leases = IscDhcpLeases(lease_file)
+ lease_ips = []
+ for lease in leases.get():
+ lease_ips.append(lease.ip)
+ if address not in lease_ips:
+ print(f'Address "{address}" not found in "{lease_file}"')
+ return False
+ return True
+
+
+if not config.exists(base):
+ print('DHCP-server not configured!')
+ exit(0)
+
+if config.exists(base + ['failover']):
+ print('Lease cannot be reset in failover mode!')
+ exit(0)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--ip', help='IPv4 address', action='store', required=True)
+
+ args = parser.parse_args()
+ address = args.ip
+
+ if not is_ip_in_leases(address):
+ exit(1)
+
+ if commit_in_progress():
+ print('Cannot clear DHCP lease while a commit is in progress')
+ exit(1)
+
+ if not ask_yes_no(f'This will restart DHCP server.\nContinue?'):
+ exit(1)
+ else:
+ del_lease_ip(address)
+ call('systemctl restart isc-dhcp-server.service')
diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py
index ffc574362..936c20bcb 100755
--- a/src/op_mode/connect_disconnect.py
+++ b/src/op_mode/connect_disconnect.py
@@ -20,6 +20,7 @@ import argparse
from psutil import process_iter
from vyos.util import call
+from vyos.util import commit_in_progress
from vyos.util import DEVNULL
from vyos.util import is_wwan_connected
@@ -87,6 +88,9 @@ def main():
args = parser.parse_args()
if args.connect:
+ if commit_in_progress():
+ print('Cannot connect while a commit is in progress')
+ exit(1)
connect(args.connect)
elif args.disconnect:
disconnect(args.disconnect)
diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py
index e45c38f07..54ecd6d0e 100755
--- a/src/op_mode/conntrack_sync.py
+++ b/src/op_mode/conntrack_sync.py
@@ -22,6 +22,7 @@ from argparse import ArgumentParser
from vyos.configquery import CliShellApiConfigQuery
from vyos.configquery import ConfigTreeQuery
from vyos.util import call
+from vyos.util import commit_in_progress
from vyos.util import cmd
from vyos.util import run
from vyos.template import render_to_string
@@ -86,6 +87,9 @@ if __name__ == '__main__':
if args.restart:
is_configured()
+ if commit_in_progress():
+ print('Cannot restart conntrackd while a commit is in progress')
+ exit(1)
syslog.syslog('Restarting conntrack sync service...')
cmd('systemctl restart conntrackd.service')
diff --git a/src/op_mode/containers_op.py b/src/op_mode/containers_op.py
deleted file mode 100755
index c55a48b3c..000000000
--- a/src/op_mode/containers_op.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import argparse
-
-from getpass import getuser
-from vyos.configquery import ConfigTreeQuery
-from vyos.base import Warning
-from vyos.util import cmd
-from subprocess import STDOUT
-
-parser = argparse.ArgumentParser()
-parser.add_argument("-a", "--all", action="store_true", help="Show all containers")
-parser.add_argument("-i", "--image", action="store_true", help="Show container images")
-parser.add_argument("-n", "--networks", action="store_true", help="Show container images")
-parser.add_argument("-p", "--pull", action="store", help="Pull image for container")
-parser.add_argument("-d", "--remove", action="store", help="Delete container image")
-parser.add_argument("-u", "--update", action="store", help="Update given container image")
-
-config = ConfigTreeQuery()
-base = ['container']
-
-if getuser() != 'root':
- raise OSError('This functions needs to be run as root to return correct results!')
-
-if __name__ == '__main__':
- args = parser.parse_args()
-
- if args.all:
- print(cmd('podman ps --all'))
- elif args.image:
- print(cmd('podman image ls'))
- elif args.networks:
- print(cmd('podman network ls'))
-
- elif args.pull:
- image = args.pull
- registry_config = '/etc/containers/registries.conf'
- if not os.path.exists(registry_config):
- Warning('No container registry configured. Please use full URL when '\
- 'adding an image. E.g. prefix with docker.io/image-name.')
- try:
- print(os.system(f'podman image pull {image}'))
- except Exception as e:
- print(f'Unable to download image "{image}". {e}')
-
- elif args.remove:
- image = args.remove
- try:
- print(os.system(f'podman image rm {image}'))
- except FileNotFoundError as e:
- print(f'Unable to delete image "{image}". {e}')
-
- elif args.update:
- tmp = config.get_config_dict(base + ['name', args.update],
- key_mangling=('-', '_'), get_first_key=True)
- try:
- image = tmp['image']
- print(cmd(f'podman image pull {image}'))
- except Exception as e:
- print(f'Unable to download image "{image}". {e}')
- else:
- parser.print_help()
- exit(1)
-
- exit(0)
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 3146fc357..0aea17b3a 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -270,7 +270,7 @@ def show_firewall_group(name=None):
references = find_references(group_type, group_name)
row = [group_name, group_type, '\n'.join(references) or 'N/A']
if 'address' in group_conf:
- row.append("\n".join(sorted(group_conf['address'], key=ipaddress.ip_address)))
+ row.append("\n".join(sorted(group_conf['address'])))
elif 'network' in group_conf:
row.append("\n".join(sorted(group_conf['network'], key=ipaddress.ip_network)))
elif 'mac_address' in group_conf:
diff --git a/src/op_mode/flow_accounting_op.py b/src/op_mode/flow_accounting_op.py
index 6586cbceb..514143cd7 100755
--- a/src/op_mode/flow_accounting_op.py
+++ b/src/op_mode/flow_accounting_op.py
@@ -22,7 +22,9 @@ import ipaddress
import os.path
from tabulate import tabulate
from json import loads
-from vyos.util import cmd, run
+from vyos.util import cmd
+from vyos.util import commit_in_progress
+from vyos.util import run
from vyos.logger import syslog
# some default values
@@ -224,6 +226,9 @@ if not _uacctd_running():
# restart pmacct daemon
if cmd_args.action == 'restart':
+ if commit_in_progress():
+ print('Cannot restart flow-accounting while a commit is in progress')
+ exit(1)
# run command to restart flow-accounting
cmd('systemctl restart uacctd.service',
message='Failed to restart flow-accounting')
diff --git a/src/op_mode/generate_ssh_server_key.py b/src/op_mode/generate_ssh_server_key.py
index cbc9ef973..43e94048d 100755
--- a/src/op_mode/generate_ssh_server_key.py
+++ b/src/op_mode/generate_ssh_server_key.py
@@ -17,10 +17,15 @@
from sys import exit
from vyos.util import ask_yes_no
from vyos.util import cmd
+from vyos.util import commit_in_progress
if not ask_yes_no('Do you really want to remove the existing SSH host keys?'):
exit(0)
+if commit_in_progress():
+ print('Cannot restart SSH while a commit is in progress')
+ exit(1)
+
cmd('rm -v /etc/ssh/ssh_host_*')
cmd('dpkg-reconfigure openssh-server')
cmd('systemctl restart ssh.service')
diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py
index c3cd25186..a128cc011 100755
--- a/src/op_mode/openconnect-control.py
+++ b/src/op_mode/openconnect-control.py
@@ -19,7 +19,10 @@ import argparse
import json
from vyos.config import Config
-from vyos.util import popen, run, DEVNULL
+from vyos.util import commit_in_progress
+from vyos.util import popen
+from vyos.util import run
+from vyos.util import DEVNULL
from tabulate import tabulate
occtl = '/usr/bin/occtl'
@@ -57,6 +60,10 @@ def main():
# Check is Openconnect server configured
is_ocserv_configured()
+ if commit_in_progress():
+ print('Cannot restart openconnect while a commit is in progress')
+ exit(1)
+
if args.action == "restart":
run("sudo systemctl restart ocserv.service")
sys.exit(0)
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index bc7813052..1e78c3a03 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -17,6 +17,7 @@
import argparse
import ipaddress
import os
+import re
import sys
import tabulate
@@ -30,7 +31,8 @@ from vyos.pki import encode_certificate, encode_public_key, encode_private_key,
from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list
from vyos.pki import create_private_key
from vyos.pki import create_dh_parameters
-from vyos.pki import load_certificate, load_certificate_request, load_private_key, load_crl
+from vyos.pki import load_certificate, load_certificate_request, load_private_key
+from vyos.pki import load_crl, load_dh_parameters, load_public_key
from vyos.pki import verify_certificate
from vyos.xml import defaults
from vyos.util import ask_input, ask_yes_no
@@ -183,13 +185,13 @@ def install_ssh_key(name, public_key, private_key, passphrase=None):
])
print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
-def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None):
+def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None, prompt=True):
# Show/install conf commands for key-pair
config_paths = []
if public_key:
- install_public_key = ask_yes_no('Do you want to install the public key?', default=True)
+ install_public_key = not prompt or ask_yes_no('Do you want to install the public key?', default=True)
public_key_pem = encode_public_key(public_key)
if install_public_key:
@@ -200,7 +202,7 @@ def install_keypair(name, key_type, private_key=None, public_key=None, passphras
print(public_key_pem)
if private_key:
- install_private_key = ask_yes_no('Do you want to install the private key?', default=True)
+ install_private_key = not prompt or ask_yes_no('Do you want to install the private key?', default=True)
private_key_pem = encode_private_key(private_key, passphrase=passphrase)
if install_private_key:
@@ -214,6 +216,13 @@ def install_keypair(name, key_type, private_key=None, public_key=None, passphras
install_into_config(conf, config_paths)
+def install_openvpn_key(name, key_data, key_version='1'):
+ config_paths = [
+ f"pki openvpn shared-secret {name} key '{key_data}'",
+ f"pki openvpn shared-secret {name} version '{key_version}'"
+ ]
+ install_into_config(conf, config_paths)
+
def install_wireguard_key(interface, private_key, public_key):
# Show conf commands for installing wireguard key pairs
from vyos.ifconfig import Section
@@ -640,15 +649,11 @@ def generate_openvpn_key(name, install=False, file=False):
key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings
key_version = '1'
- import re
version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', result) # Future-proofing (hopefully)
if version_search:
key_version = version_search[1]
- base = f"set pki openvpn shared-secret {name}"
- print("Configure mode commands to install OpenVPN key:")
- print(f"{base} key '{key_data}'")
- print(f"{base} version '{key_version}'")
+ install_openvpn_key(name, key_data, key_version)
if file:
write_file(f'{name}.key', result)
@@ -670,6 +675,167 @@ def generate_wireguard_psk(interface=None, peer=None, install=False):
else:
print(f'Pre-shared key: {psk}')
+# Import functions
+def import_ca_certificate(name, path=None, key_path=None):
+ if path:
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ cert = None
+
+ with open(path) as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if not cert:
+ print(f'Invalid certificate: {path}')
+ return
+
+ install_certificate(name, cert, is_ca=True)
+
+ if key_path:
+ if not os.path.exists(key_path):
+ print(f'File not found: {key_path}')
+ return
+
+ key = None
+ passphrase = ask_input('Enter private key passphrase: ') or None
+
+ with open(key_path) as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False)
+
+ if not key:
+ print(f'Invalid private key or passphrase: {path}')
+ return
+
+ install_certificate(name, private_key=key, is_ca=True)
+
+def import_certificate(name, path=None, key_path=None):
+ if path:
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ cert = None
+
+ with open(path) as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if not cert:
+ print(f'Invalid certificate: {path}')
+ return
+
+ install_certificate(name, cert, is_ca=False)
+
+ if key_path:
+ if not os.path.exists(key_path):
+ print(f'File not found: {key_path}')
+ return
+
+ key = None
+ passphrase = ask_input('Enter private key passphrase: ') or None
+
+ with open(key_path) as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False)
+
+ if not key:
+ print(f'Invalid private key or passphrase: {path}')
+ return
+
+ install_certificate(name, private_key=key, is_ca=False)
+
+def import_crl(name, path):
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ crl = None
+
+ with open(path) as f:
+ crl_data = f.read()
+ crl = load_crl(crl_data, wrap_tags=False)
+
+ if not crl:
+ print(f'Invalid certificate: {path}')
+ return
+
+ install_crl(name, crl)
+
+def import_dh_parameters(name, path):
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ dh = None
+
+ with open(path) as f:
+ dh_data = f.read()
+ dh = load_dh_parameters(dh_data, wrap_tags=False)
+
+ if not dh:
+ print(f'Invalid DH parameters: {path}')
+ return
+
+ install_dh_parameters(name, dh)
+
+def import_keypair(name, path=None, key_path=None):
+ if path:
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ key = None
+
+ with open(path) as f:
+ key_data = f.read()
+ key = load_public_key(key_data, wrap_tags=False)
+
+ if not key:
+ print(f'Invalid public key: {path}')
+ return
+
+ install_keypair(name, None, public_key=key, prompt=False)
+
+ if key_path:
+ if not os.path.exists(key_path):
+ print(f'File not found: {key_path}')
+ return
+
+ key = None
+ passphrase = ask_input('Enter private key passphrase: ') or None
+
+ with open(key_path) as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False)
+
+ if not key:
+ print(f'Invalid private key or passphrase: {path}')
+ return
+
+ install_keypair(name, None, private_key=key, prompt=False)
+
+def import_openvpn_secret(name, path):
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ key_data = None
+ key_version = '1'
+
+ with open(path) as f:
+ key_lines = f.read().split("\n")
+ key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings
+
+ version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', key_lines[0]) # Future-proofing (hopefully)
+ if version_search:
+ key_version = version_search[1]
+
+ install_openvpn_key(name, key_data, key_version)
+
# Show functions
def show_certificate_authority(name=None):
headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent']
@@ -799,6 +965,9 @@ if __name__ == '__main__':
parser.add_argument('--file', help='Write generated keys into specified filename', action='store_true')
parser.add_argument('--install', help='Install generated keys into running-config', action='store_true')
+ parser.add_argument('--filename', help='Write certificate into specified filename', action='store')
+ parser.add_argument('--key-filename', help='Write key into specified filename', action='store')
+
args = parser.parse_args()
try:
@@ -840,7 +1009,19 @@ if __name__ == '__main__':
generate_wireguard_key(args.interface, install=args.install)
if args.psk:
generate_wireguard_psk(args.interface, peer=args.peer, install=args.install)
-
+ elif args.action == 'import':
+ if args.ca:
+ import_ca_certificate(args.ca, path=args.filename, key_path=args.key_filename)
+ elif args.certificate:
+ import_certificate(args.certificate, path=args.filename, key_path=args.key_filename)
+ elif args.crl:
+ import_crl(args.crl, args.filename)
+ elif args.dh:
+ import_dh_parameters(args.dh, args.filename)
+ elif args.keypair:
+ import_keypair(args.keypair, path=args.filename, key_path=args.key_filename)
+ elif args.openvpn:
+ import_openvpn_secret(args.openvpn, args.filename)
elif args.action == 'show':
if args.ca:
ca_name = None if args.ca == 'all' else args.ca
diff --git a/src/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py
index dbd3eb4d1..efbf65083 100755
--- a/src/op_mode/reset_openvpn.py
+++ b/src/op_mode/reset_openvpn.py
@@ -17,6 +17,7 @@
import os
from sys import argv, exit
from vyos.util import call
+from vyos.util import commit_in_progress
if __name__ == '__main__':
if (len(argv) < 1):
@@ -25,6 +26,9 @@ if __name__ == '__main__':
interface = argv[1]
if os.path.isfile(f'/run/openvpn/{interface}.conf'):
+ if commit_in_progress():
+ print('Cannot restart OpenVPN while a commit is in progress')
+ exit(1)
call(f'systemctl restart openvpn@{interface}.service')
else:
print(f'OpenVPN interface "{interface}" does not exist!')
diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py
index af4fb2d15..db5a48970 100755
--- a/src/op_mode/restart_dhcp_relay.py
+++ b/src/op_mode/restart_dhcp_relay.py
@@ -24,6 +24,7 @@ import os
import vyos.config
from vyos.util import call
+from vyos.util import commit_in_progress
parser = argparse.ArgumentParser()
@@ -39,6 +40,9 @@ if __name__ == '__main__':
if not c.exists_effective('service dhcp-relay'):
print("DHCP relay service not configured")
else:
+ if commit_in_progress():
+ print('Cannot restart DHCP relay while a commit is in progress')
+ exit(1)
call('systemctl restart isc-dhcp-server.service')
sys.exit(0)
@@ -47,6 +51,9 @@ if __name__ == '__main__':
if not c.exists_effective('service dhcpv6-relay'):
print("DHCPv6 relay service not configured")
else:
+ if commit_in_progress():
+ print('Cannot restart DHCPv6 relay while commit is in progress')
+ exit(1)
call('systemctl restart isc-dhcp-server6.service')
sys.exit(0)
diff --git a/src/op_mode/show_conntrack.py b/src/op_mode/show_conntrack.py
new file mode 100755
index 000000000..4eb160d97
--- /dev/null
+++ b/src/op_mode/show_conntrack.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 xmltodict
+
+from tabulate import tabulate
+from vyos.util import cmd
+
+
+def _get_raw_data():
+ """
+ Get conntrack XML output
+ """
+ return cmd(f'sudo conntrack --dump --output xml')
+
+
+def _xml_to_dict(xml):
+ """
+ Convert XML to dictionary
+ Return: dictionary
+ """
+ parse = xmltodict.parse(xml)
+ # If only one conntrack entry we must change dict
+ if 'meta' in parse['conntrack']['flow']:
+ return dict(conntrack={'flow': [parse['conntrack']['flow']]})
+ return parse
+
+
+def _get_formatted_output(xml):
+ """
+ :param xml:
+ :return: formatted output
+ """
+ data_entries = []
+ dict_data = _xml_to_dict(xml)
+ for entry in dict_data['conntrack']['flow']:
+ src, dst, sport, dport, proto = {}, {}, {}, {}, {}
+ for meta in entry['meta']:
+ direction = meta['@direction']
+ if direction in ['original']:
+ if 'layer3' in meta:
+ src = meta['layer3']['src']
+ dst = meta['layer3']['dst']
+ if 'layer4' in meta:
+ if meta.get('layer4').get('sport'):
+ sport = meta['layer4']['sport']
+ if meta.get('layer4').get('dport'):
+ dport = meta['layer4']['dport']
+ proto = meta['layer4']['@protoname']
+ if direction == 'independent':
+ conn_id = meta['id']
+ timeout = meta['timeout']
+ src = f'{src}:{sport}' if sport else src
+ dst = f'{dst}:{dport}' if dport else dst
+ state = meta['state'] if 'state' in meta else ''
+ data_entries.append([conn_id, src, dst, proto, state, timeout])
+ headers = ["Connection id", "Source", "Destination", "Protocol", "State", "Timeout"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return output
+
+
+def show(raw: bool):
+ conntrack_data = _get_raw_data()
+ if raw:
+ return conntrack_data
+ else:
+ return _get_formatted_output(conntrack_data)
+
+
+if __name__ == '__main__':
+ print(show(raw=False))
diff --git a/src/op_mode/show_nat_translations.py b/src/op_mode/show_nat_translations.py
index 25091e9fc..508845e23 100755
--- a/src/op_mode/show_nat_translations.py
+++ b/src/op_mode/show_nat_translations.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 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
@@ -83,11 +83,23 @@ def pipe():
return xml
+def xml_to_dict(xml):
+ """
+ Convert XML to dictionary
+ Return: dictionary
+ """
+ parse = xmltodict.parse(xml)
+ # If only one NAT entry we must change dict T4499
+ if 'meta' in parse['conntrack']['flow']:
+ return dict(conntrack={'flow': [parse['conntrack']['flow']]})
+ return parse
+
+
def process(data, stats, protocol, pipe, verbose, flowtype=''):
if not data:
return
- parsed = xmltodict.parse(data)
+ parsed = xml_to_dict(data)
print(headers(verbose, pipe))
diff --git a/src/op_mode/show_neigh.py b/src/op_mode/show_neigh.py
index 94e745493..d874bd544 100755
--- a/src/op_mode/show_neigh.py
+++ b/src/op_mode/show_neigh.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2022 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,83 +14,89 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#ip -j -f inet neigh list | jq
-#[
- #{
- #"dst": "192.168.101.8",
- #"dev": "enp0s25",
- #"lladdr": "78:d2:94:72:77:7e",
- #"state": [
- #"STALE"
- #]
- #},
- #{
- #"dst": "192.168.101.185",
- #"dev": "enp0s25",
- #"lladdr": "34:46:ec:76:f8:9b",
- #"state": [
- #"STALE"
- #]
- #},
- #{
- #"dst": "192.168.101.225",
- #"dev": "enp0s25",
- #"lladdr": "c2:cb:fa:bf:a0:35",
- #"state": [
- #"STALE"
- #]
- #},
- #{
- #"dst": "192.168.101.1",
- #"dev": "enp0s25",
- #"lladdr": "00:98:2b:f8:3f:11",
- #"state": [
- #"REACHABLE"
- #]
- #},
- #{
- #"dst": "192.168.101.181",
- #"dev": "enp0s25",
- #"lladdr": "d8:9b:3b:d5:88:22",
- #"state": [
- #"STALE"
- #]
- #}
-#]
+# Sample output of `ip --json neigh list`:
+#
+# [
+# {
+# "dst": "192.168.1.1",
+# "dev": "eth0", # Missing if `dev ...` option is used
+# "lladdr": "00:aa:bb:cc:dd:ee", # May be missing for failed entries
+# "state": [
+# "REACHABLE"
+# ]
+# },
+# ]
import sys
-import argparse
-import json
-from vyos.util import cmd
-
-def main():
- #parese args
- parser = argparse.ArgumentParser()
- parser.add_argument('--family', help='Protocol family', required=True)
- args = parser.parse_args()
-
- neigh_raw_json = cmd(f'ip -j -f {args.family} neigh list')
- neigh_raw_json = neigh_raw_json.lower()
- neigh_json = json.loads(neigh_raw_json)
-
- format_neigh = '%-50s %-10s %-20s %s'
- print(format_neigh % ("IP Address", "Device", "State", "LLADDR"))
- print(format_neigh % ("----------", "------", "-----", "------"))
-
- if neigh_json is not None:
- for neigh_item in neigh_json:
- dev = neigh_item['dev']
- dst = neigh_item['dst']
- lladdr = neigh_item['lladdr'] if 'lladdr' in neigh_item else ''
- state = neigh_item['state']
-
- i = 0
- for state_item in state:
- if i == 0:
- print(format_neigh % (dst, dev, state_item, lladdr))
- else:
- print(format_neigh % ('', '', state_item, ''))
- i+=1
-
+
+
+def get_raw_data(family, device=None, state=None):
+ from json import loads
+ from vyos.util import cmd
+
+ if device:
+ device = f"dev {device}"
+ else:
+ device = ""
+
+ if state:
+ state = f"nud {state}"
+ else:
+ state = ""
+
+ neigh_cmd = f"ip --family {family} --json neighbor list {device} {state}"
+
+ data = loads(cmd(neigh_cmd))
+
+ return data
+
+def get_formatted_output(family, device=None, state=None):
+ from tabulate import tabulate
+
+ def entry_to_list(e, intf=None):
+ dst = e["dst"]
+
+ # State is always a list in the iproute2 output
+ state = ", ".join(e["state"])
+
+ # Link layer address is absent from e.g. FAILED entries
+ if "lladdr" in e:
+ lladdr = e["lladdr"]
+ else:
+ lladdr = None
+
+ # Device field is absent from outputs of `ip neigh list dev ...`
+ if "dev" in e:
+ dev = e["dev"]
+ elif device:
+ dev = device
+ else:
+ raise ValueError("interface is not defined")
+
+ return [dst, dev, lladdr, state]
+
+ neighs = get_raw_data(family, device=device, state=state)
+ neighs = map(entry_to_list, neighs)
+
+ headers = ["Address", "Interface", "Link layer address", "State"]
+ return tabulate(neighs, headers)
+
if __name__ == '__main__':
- main()
+ from argparse import ArgumentParser
+
+ parser = ArgumentParser()
+ parser.add_argument("-f", "--family", type=str, default="inet", help="Address family")
+ parser.add_argument("-i", "--interface", type=str, help="Network interface")
+ parser.add_argument("-s", "--state", type=str, help="Neighbor table entry state")
+
+ args = parser.parse_args()
+
+ if args.state:
+ if args.state not in ["reachable", "failed", "stale", "permanent"]:
+ raise ValueError(f"""Incorrect state "{args.state}"! Must be one of: reachable, stale, failed, permanent""")
+
+ try:
+ print(get_formatted_output(args.family, device=args.interface, state=args.state))
+ except ValueError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/show_openconnect_otp.py b/src/op_mode/show_openconnect_otp.py
new file mode 100755
index 000000000..ae532ccc9
--- /dev/null
+++ b/src/op_mode/show_openconnect_otp.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+
+# Copyright 2017, 2022 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 argparse
+import os
+
+from vyos.config import Config
+from vyos.xml import defaults
+from vyos.configdict import dict_merge
+from vyos.util import popen
+from base64 import b32encode
+
+otp_file = '/run/ocserv/users.oath'
+
+def check_uname_otp(username):
+ """
+ Check if "username" exists and have an OTP key
+ """
+ config = Config()
+ base_key = ['vpn', 'openconnect', 'authentication', 'local-users', 'username', username, 'otp', 'key']
+ if not config.exists(base_key):
+ return None
+ return True
+
+def get_otp_ocserv(username):
+ config = Config()
+ base = ['vpn', 'openconnect']
+ if not config.exists(base):
+ return None
+ ocserv = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ ocserv = dict_merge(default_values, ocserv)
+ # workaround a "know limitation" - https://phabricator.vyos.net/T2665
+ del ocserv['authentication']['local_users']['username']['otp']
+ if not ocserv["authentication"]["local_users"]["username"]:
+ return None
+ default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
+ for user, params in ocserv['authentication']['local_users']['username'].items():
+ # Not every configuration requires OTP settings
+ if ocserv['authentication']['local_users']['username'][user].get('otp'):
+ ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
+ result = ocserv['authentication']['local_users']['username'][username]
+ return result
+
+def display_otp_ocserv(username, params, info):
+ hostname = os.uname()[1]
+ key_hex = params['otp']['key']
+ otp_length = params['otp']['otp_length']
+ interval = params['otp']['interval']
+ token_type = params['otp']['token_type']
+ if token_type == 'hotp-time':
+ token_type_acrn = 'totp'
+ key_base32 = b32encode(bytes.fromhex(key_hex)).decode()
+ otp_url = ''.join(["otpauth://",token_type_acrn,"/",username,"@",hostname,"?secret=",key_base32,"&digits=",otp_length,"&period=",interval])
+ qrcode,err = popen('qrencode -t ansiutf8', input=otp_url)
+
+ if info == 'full':
+ print("# You can share it with the user, he just needs to scan the QR in his OTP app")
+ print("# username: ", username)
+ print("# OTP KEY: ", key_base32)
+ print("# OTP URL: ", otp_url)
+ print(qrcode)
+ print('# To add this OTP key to configuration, run the following commands:')
+ print(f"set vpn openconnect authentication local-users username {username} otp key '{key_hex}'")
+ if interval != "30":
+ print(f"set vpn openconnect authentication local-users username {username} otp interval '{interval}'")
+ if otp_length != "6":
+ print(f"set vpn openconnect authentication local-users username {username} otp otp-length '{otp_length}'")
+ elif info == 'key-hex':
+ print("# OTP key in hexadecimal: ")
+ print(key_hex)
+ elif info == 'key-b32':
+ print("# OTP key in Base32: ")
+ print(key_base32)
+ elif info == 'qrcode':
+ print(f"# QR code for OpenConnect user '{username}'")
+ print(qrcode)
+ elif info == 'uri':
+ print(f"# URI for OpenConnect user '{username}'")
+ print(otp_url)
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(add_help=False, description='Show OTP authentication information for selected user')
+ parser.add_argument('--user', action="store", type=str, default='', help='Username')
+ parser.add_argument('--info', action="store", type=str, default='full', help='Wich information to display')
+
+ args = parser.parse_args()
+ check_otp = check_uname_otp(args.user)
+ if check_otp:
+ user_otp_params = get_otp_ocserv(args.user)
+ display_otp_ocserv(args.user, user_otp_params, args.info)
+ else:
+ print(f'There is no such user ("{args.user}") with an OTP key configured')
diff --git a/src/op_mode/show_uptime.py b/src/op_mode/show_uptime.py
index 1b5e33fa9..b70c60cf8 100755
--- a/src/op_mode/show_uptime.py
+++ b/src/op_mode/show_uptime.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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 as
@@ -26,14 +26,17 @@ def get_uptime_seconds():
def get_load_averages():
from re import search
from vyos.util import cmd
+ from vyos.cpu import get_core_count
data = cmd("uptime")
matches = search(r"load average:\s*(?P<one>[0-9\.]+)\s*,\s*(?P<five>[0-9\.]+)\s*,\s*(?P<fifteen>[0-9\.]+)\s*", data)
+ core_count = get_core_count()
+
res = {}
- res[1] = float(matches["one"])
- res[5] = float(matches["five"])
- res[15] = float(matches["fifteen"])
+ res[1] = float(matches["one"]) / core_count
+ res[5] = float(matches["five"]) / core_count
+ res[15] = float(matches["fifteen"]) / core_count
return res
@@ -53,9 +56,9 @@ def get_formatted_output():
out = "Uptime: {}\n\n".format(data["uptime"])
avgs = data["load_average"]
out += "Load averages:\n"
- out += "1 minute: {:.02f}%\n".format(avgs[1]*100)
- out += "5 minutes: {:.02f}%\n".format(avgs[5]*100)
- out += "15 minutes: {:.02f}%\n".format(avgs[15]*100)
+ out += "1 minute: {:.01f}%\n".format(avgs[1]*100)
+ out += "5 minutes: {:.01f}%\n".format(avgs[5]*100)
+ out += "15 minutes: {:.01f}%\n".format(avgs[15]*100)
return out
diff --git a/src/op_mode/vtysh_wrapper.sh b/src/op_mode/vtysh_wrapper.sh
index 09980e14f..25d09ce77 100755
--- a/src/op_mode/vtysh_wrapper.sh
+++ b/src/op_mode/vtysh_wrapper.sh
@@ -1,5 +1,6 @@
#!/bin/sh
declare -a tmp
-# FRR uses ospf6 where we use ospfv3, thus alter the command
-tmp=$(echo $@ | sed -e "s/ospfv3/ospf6/")
+# FRR uses ospf6 where we use ospfv3, and we use reset over clear for BGP,
+# thus alter the commands
+tmp=$(echo $@ | sed -e "s/ospfv3/ospf6/" | sed -e "s/^reset bgp/clear bgp/" | sed -e "s/^reset ip bgp/clear ip bgp/")
vtysh -c "$tmp"
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index c1b595412..e9b904ba8 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -201,6 +201,20 @@ class ShowModel(ApiModel):
}
}
+class ResetModel(ApiModel):
+ op: StrictStr
+ path: List[StrictStr]
+
+ class Config:
+ schema_extra = {
+ "example": {
+ "key": "id_key",
+ "op": "reset",
+ "path": ["op", "mode", "path"],
+ }
+ }
+
+
class Success(BaseModel):
success: bool
data: Union[str, bool, Dict]
@@ -372,7 +386,7 @@ class MultipartRoute(APIRoute):
return error(400, "Malformed command \"{0}\": \"value\" field must be a string".format(json.dumps(request.offending_command)))
if request.ERR_PATH_NOT_LIST_OF_STR:
return error(400, "Malformed command \"{0}\": \"path\" field must be a list of strings".format(json.dumps(request.offending_command)))
- if endpoint in ('/retrieve','/generate','/show'):
+ if endpoint in ('/retrieve','/generate','/show','/reset'):
if request.ERR_NO_OP or request.ERR_NO_PATH:
return error(400, "Missing required field. \"op\" and \"path\" fields are required")
if endpoint in ('/config-file', '/image'):
@@ -607,6 +621,27 @@ def show_op(data: ShowModel):
return success(res)
+@app.post('/reset')
+def reset_op(data: ResetModel):
+ session = app.state.vyos_session
+
+ op = data.op
+ path = data.path
+
+ try:
+ if op == 'reset':
+ res = session.reset(path)
+ else:
+ return error(400, "\"{0}\" is not a valid operation".format(op))
+ except ConfigSessionError as e:
+ return error(400, str(e))
+ except Exception as e:
+ logger.critical(traceback.format_exc())
+ return error(500, "An internal error occured. Check the logs for details.")
+
+ return success(res)
+
+
###
# GraphQL integration
###
diff --git a/src/system/vyos-event-handler.py b/src/system/vyos-event-handler.py
new file mode 100755
index 000000000..1c85380bc
--- /dev/null
+++ b/src/system/vyos-event-handler.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 json
+import re
+import select
+from copy import deepcopy
+from os import getpid, environ
+from pathlib import Path
+from signal import signal, SIGTERM, SIGINT
+from sys import exit
+from systemd import journal
+
+from vyos.util import run, dict_search
+
+# Identify this script
+my_pid = getpid()
+my_name = Path(__file__).stem
+
+
+# handle termination signal
+def handle_signal(signal_type, frame):
+ if signal_type == SIGTERM:
+ journal.send('Received SIGTERM signal, stopping normally',
+ SYSLOG_IDENTIFIER=my_name)
+ if signal_type == SIGINT:
+ journal.send('Received SIGINT signal, stopping normally',
+ SYSLOG_IDENTIFIER=my_name)
+ exit(0)
+
+
+# Class for analyzing and process messages
+class Analyzer:
+ # Initialize settings
+ def __init__(self, config: dict) -> None:
+ self.config = {}
+ # Prepare compiled regex objects
+ for event_id, event_config in config.items():
+ script = dict_search('script.path', event_config)
+ # Check for arguments
+ if dict_search('script.arguments', event_config):
+ script_arguments = dict_search('script.arguments', event_config)
+ script = f'{script} {script_arguments}'
+ # Prepare environment
+ environment = deepcopy(environ)
+ # Check for additional environment options
+ if dict_search('script.environment', event_config):
+ for env_variable, env_value in dict_search(
+ 'script.environment', event_config).items():
+ environment[env_variable] = env_value.get('value')
+ # Create final config dictionary
+ pattern_raw = event_config['filter']['pattern']
+ pattern_compiled = re.compile(
+ rf'{event_config["filter"]["pattern"]}')
+ pattern_config = {
+ pattern_compiled: {
+ 'pattern_raw':
+ pattern_raw,
+ 'syslog_id':
+ dict_search('filter.syslog-identifier', event_config),
+ 'pattern_script': {
+ 'path': script,
+ 'environment': environment
+ }
+ }
+ }
+ self.config.update(pattern_config)
+
+ # Execute script safely
+ def script_run(self, pattern: str, script_path: str,
+ script_env: dict) -> None:
+ try:
+ run(script_path, env=script_env)
+ journal.send(
+ f'Pattern found: "{pattern}", script executed: "{script_path}"',
+ SYSLOG_IDENTIFIER=my_name)
+ except Exception as err:
+ journal.send(
+ f'Pattern found: "{pattern}", failed to execute script "{script_path}": {err}',
+ SYSLOG_IDENTIFIER=my_name)
+
+ # Analyze a message
+ def process_message(self, message: dict) -> None:
+ for pattern_compiled, pattern_config in self.config.items():
+ # Check if syslog id is presented in config and matches
+ syslog_id = pattern_config.get('syslog_id')
+ if syslog_id and message['SYSLOG_IDENTIFIER'] != syslog_id:
+ continue
+ if pattern_compiled.fullmatch(message['MESSAGE']):
+ # Add message to environment variables
+ pattern_config['pattern_script']['environment'][
+ 'message'] = message['MESSAGE']
+ # Run script
+ self.script_run(
+ pattern=pattern_config['pattern_raw'],
+ script_path=pattern_config['pattern_script']['path'],
+ script_env=pattern_config['pattern_script']['environment'])
+
+
+if __name__ == '__main__':
+ # Parse command arguments and get config
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-c',
+ '--config',
+ action='store',
+ help='Path to even-handler configuration',
+ required=True,
+ type=Path)
+
+ args = parser.parse_args()
+ try:
+ config_path = Path(args.config)
+ config = json.loads(config_path.read_text())
+ # Create an object for analazyng messages
+ analyzer = Analyzer(config)
+ except Exception as err:
+ print(
+ f'Configuration file "{config_path}" does not exist or malformed: {err}'
+ )
+ exit(1)
+
+ # Prepare for proper exitting
+ signal(SIGTERM, handle_signal)
+ signal(SIGINT, handle_signal)
+
+ # Set up journal connection
+ data = journal.Reader()
+ data.seek_tail()
+ data.get_previous()
+ p = select.poll()
+ p.register(data, data.get_events())
+
+ journal.send(f'Started with configuration: {config}',
+ SYSLOG_IDENTIFIER=my_name)
+
+ while p.poll():
+ if data.process() != journal.APPEND:
+ continue
+ for entry in data:
+ message = entry['MESSAGE']
+ pid = entry['_PID']
+ # Skip empty messages and messages from this process
+ if message and pid != my_pid:
+ try:
+ analyzer.process_message(entry)
+ except Exception as err:
+ journal.send(f'Unable to process message: {err}',
+ SYSLOG_IDENTIFIER=my_name)
diff --git a/src/systemd/dhclient@.service b/src/systemd/dhclient@.service
index 2ced1038a..23cd4cfc3 100644
--- a/src/systemd/dhclient@.service
+++ b/src/systemd/dhclient@.service
@@ -13,6 +13,9 @@ PIDFile=/var/lib/dhcp/dhclient_%i.pid
ExecStart=/sbin/dhclient -4 $DHCLIENT_OPTS
ExecStop=/sbin/dhclient -4 $DHCLIENT_OPTS -r
Restart=always
+TimeoutStopSec=20
+SendSIGKILL=true
+FinalKillSignal=SIGABRT
[Install]
WantedBy=multi-user.target
diff --git a/src/systemd/vyos-domain-group-resolve.service b/src/systemd/vyos-domain-group-resolve.service
new file mode 100644
index 000000000..29628fddb
--- /dev/null
+++ b/src/systemd/vyos-domain-group-resolve.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=VyOS firewall domain-group resolver
+After=vyos-router.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/python3 /usr/libexec/vyos/vyos-domain-group-resolve.py
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/systemd/vyos-event-handler.service b/src/systemd/vyos-event-handler.service
new file mode 100644
index 000000000..6afe4f95b
--- /dev/null
+++ b/src/systemd/vyos-event-handler.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=VyOS event handler
+After=network.target vyos-router.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/python3 /usr/libexec/vyos/system/vyos-event-handler.py --config /run/vyos-event-handler.conf
+
+[Install]
+WantedBy=multi-user.target