summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/op-mode-standardized.json1
-rw-r--r--data/templates/firewall/nftables-nat66.j216
-rw-r--r--data/templates/firewall/upnpd.conf.j215
-rw-r--r--data/templates/frr/bgpd.frr.j25
-rw-r--r--data/templates/ipsec/swanctl/profile.j24
-rw-r--r--data/templates/macsec/wpa_supplicant.conf.j212
-rw-r--r--data/templates/monitoring/override.conf.j27
-rw-r--r--data/templates/ocserv/ocserv_config.j234
-rw-r--r--data/templates/telegraf/override.conf.j215
-rw-r--r--data/templates/telegraf/syslog_telegraf.j2 (renamed from data/templates/monitoring/syslog_telegraf.j2)0
-rw-r--r--data/templates/telegraf/telegraf.j2 (renamed from data/templates/monitoring/telegraf.j2)0
-rw-r--r--debian/control2
-rw-r--r--interface-definitions/include/accel-ppp/radius-additions.xml.i13
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i2
-rw-r--r--interface-definitions/include/firewall/tcp-flags.xml.i17
-rw-r--r--interface-definitions/include/nat-exclude.xml.i8
-rw-r--r--interface-definitions/include/nat-rule.xml.i7
-rw-r--r--interface-definitions/include/radius-nas-ip-address.xml.i14
-rw-r--r--interface-definitions/include/version/bgp-version.xml.i2
-rw-r--r--interface-definitions/nat66.xml.in75
-rw-r--r--interface-definitions/policy.xml.in2
-rw-r--r--interface-definitions/service-monitoring-telegraf.xml.in39
-rw-r--r--interface-definitions/service-upnp.xml.in19
-rw-r--r--interface-definitions/system-proxy.xml.in2
-rw-r--r--interface-definitions/vpn-l2tp.xml.in1
-rw-r--r--interface-definitions/vpn-openconnect.xml.in13
-rw-r--r--op-mode-definitions/container.xml.in2
-rw-r--r--op-mode-definitions/monitor-log.xml.in37
-rw-r--r--op-mode-definitions/nat.xml.in6
-rw-r--r--op-mode-definitions/nat66.xml.in4
-rw-r--r--op-mode-definitions/openconnect.xml.in2
-rw-r--r--op-mode-definitions/show-conntrack.xml.in6
-rw-r--r--op-mode-definitions/show-log.xml.in6
-rw-r--r--python/vyos/configdict.py11
-rw-r--r--python/vyos/configverify.py6
-rw-r--r--python/vyos/defaults.py2
-rw-r--r--python/vyos/firewall.py5
-rw-r--r--python/vyos/ifconfig/bridge.py20
-rw-r--r--python/vyos/ifconfig/section.py12
-rw-r--r--python/vyos/opmode.py27
-rw-r--r--python/vyos/util.py23
-rw-r--r--smoketest/configs/pki-misc4
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py8
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py137
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_macsec.py49
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py68
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py39
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_telegraf.py2
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py79
-rwxr-xr-xsrc/conf_mode/arp.py2
-rwxr-xr-xsrc/conf_mode/https.py29
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py23
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py8
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py7
-rwxr-xr-xsrc/conf_mode/nat66.py3
-rwxr-xr-xsrc/conf_mode/ntp.py21
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py14
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py60
-rwxr-xr-xsrc/conf_mode/service_upnp.py19
-rwxr-xr-xsrc/conf_mode/ssh.py23
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py8
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py10
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py8
-rwxr-xr-xsrc/etc/opennhrp/opennhrp-script.py315
-rw-r--r--src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf11
-rwxr-xr-xsrc/migration-scripts/bgp/2-to-351
-rwxr-xr-xsrc/op_mode/bridge.py2
-rwxr-xr-xsrc/op_mode/conntrack.py25
-rwxr-xr-xsrc/op_mode/container.py2
-rwxr-xr-xsrc/op_mode/cpu.py2
-rwxr-xr-xsrc/op_mode/dns.py2
-rwxr-xr-xsrc/op_mode/ipsec.py2
-rwxr-xr-xsrc/op_mode/memory.py2
-rwxr-xr-xsrc/op_mode/nat.py37
-rwxr-xr-xsrc/op_mode/neighbor.py2
-rwxr-xr-xsrc/op_mode/openconnect-control.py5
-rwxr-xr-xsrc/op_mode/openconnect.py81
-rwxr-xr-xsrc/op_mode/restart_dhcp_relay.py4
-rw-r--r--src/op_mode/route.py2
-rwxr-xr-xsrc/op_mode/show_nat66_rules.py102
-rwxr-xr-xsrc/op_mode/version.py2
-rwxr-xr-xsrc/op_mode/vrf.py2
-rw-r--r--src/services/api/graphql/graphql/mutations.py4
-rw-r--r--src/services/api/graphql/graphql/queries.py4
-rw-r--r--src/services/api/graphql/session/__init__.py (renamed from src/services/api/graphql/recipes/__init__.py)0
-rwxr-xr-xsrc/services/api/graphql/session/composite/system_status.py (renamed from src/services/api/graphql/recipes/queries/system_status.py)0
-rw-r--r--src/services/api/graphql/session/override/remove_firewall_address_group_members.py (renamed from src/services/api/graphql/recipes/remove_firewall_address_group_members.py)0
-rw-r--r--src/services/api/graphql/session/session.py (renamed from src/services/api/graphql/recipes/session.py)2
-rw-r--r--src/services/api/graphql/session/templates/create_dhcp_server.tmpl (renamed from src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl)0
-rw-r--r--src/services/api/graphql/session/templates/create_firewall_address_group.tmpl (renamed from src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl)0
-rw-r--r--src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl (renamed from src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl)0
-rw-r--r--src/services/api/graphql/session/templates/create_interface_ethernet.tmpl (renamed from src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl)0
-rw-r--r--src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl (renamed from src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl)0
-rw-r--r--src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl (renamed from src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl)0
-rw-r--r--src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl (renamed from src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl)0
-rw-r--r--src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl (renamed from src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl)0
-rwxr-xr-xsrc/services/api/graphql/utils/schema_from_op_mode.py5
-rwxr-xr-xsrc/services/vyos-http-api-server1
-rwxr-xr-xsrc/system/keepalived-fifo.py12
-rw-r--r--src/systemd/telegraf.service (renamed from data/templates/monitoring/systemd_vyos_telegraf_service.j2)3
-rwxr-xr-xsrc/validators/ipv6-address-exclude7
-rwxr-xr-xsrc/validators/ipv6-prefix-exclude7
102 files changed, 1255 insertions, 556 deletions
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index 90ef804f3..db13eeb5a 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -6,6 +6,7 @@
"memory.py",
"nat.py",
"neighbor.py",
+"openconnect.py",
"route.py",
"ipsec.py",
"version.py",
diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2
index 003b138b2..2fe04b4ff 100644
--- a/data/templates/firewall/nftables-nat66.j2
+++ b/data/templates/firewall/nftables-nat66.j2
@@ -3,8 +3,10 @@
{% macro nptv6_rule(rule,config, chain) %}
{% set comment = '' %}
{% set base_log = '' %}
-{% set src_prefix = 'ip6 saddr ' ~ config.source.prefix if config.source.prefix is vyos_defined %}
-{% set dest_address = 'ip6 daddr ' ~ config.destination.address if config.destination.address is vyos_defined %}
+{% set dst_prefix = 'ip6 daddr ' ~ config.destination.prefix.replace('!','!= ') if config.destination.prefix is vyos_defined %}
+{% set src_prefix = 'ip6 saddr ' ~ config.source.prefix.replace('!','!= ') if config.source.prefix is vyos_defined %}
+{% set source_address = 'ip6 saddr ' ~ config.source.address.replace('!','!= ') if config.source.address is vyos_defined %}
+{% set dest_address = 'ip6 daddr ' ~ config.destination.address.replace('!','!= ') if config.destination.address is vyos_defined %}
{% if chain is vyos_defined('PREROUTING') %}
{% set comment = 'DST-NAT66-' ~ rule %}
{% set base_log = '[NAT66-DST-' ~ rule %}
@@ -52,9 +54,19 @@
{% if src_prefix is vyos_defined %}
{% set output = output ~ ' ' ~ src_prefix %}
{% endif %}
+{% if dst_prefix is vyos_defined %}
+{% set output = output ~ ' ' ~ dst_prefix %}
+{% endif %}
+{% if source_address is vyos_defined %}
+{% set output = output ~ ' ' ~ source_address %}
+{% endif %}
{% if dest_address is vyos_defined %}
{% set output = output ~ ' ' ~ dest_address %}
{% endif %}
+{% if config.exclude is vyos_defined %}
+{# rule has been marked as 'exclude' thus we simply return here #}
+{% set trns_address = 'return' %}
+{% endif %}
{% if trns_address is vyos_defined %}
{% set output = output ~ ' ' ~ trns_address %}
{% endif %}
diff --git a/data/templates/firewall/upnpd.conf.j2 b/data/templates/firewall/upnpd.conf.j2
index 27573cbf9..e964fc696 100644
--- a/data/templates/firewall/upnpd.conf.j2
+++ b/data/templates/firewall/upnpd.conf.j2
@@ -71,7 +71,7 @@ min_lifetime={{ pcp_lifetime.min }}
{% if friendly_name is vyos_defined %}
# Name of this service, default is "`uname -s` router"
-friendly_name= {{ friendly_name }}
+friendly_name={{ friendly_name }}
{% endif %}
# Manufacturer name, default is "`uname -s`"
@@ -117,7 +117,10 @@ clean_ruleset_threshold=10
clean_ruleset_interval=600
# Anchor name in pf (default is miniupnpd)
-anchor=VyOS
+# Something wrong with this option "anchor", comment it out
+# vyos@r14# miniupnpd -vv -f /run/upnp/miniupnp.conf
+# invalid option in file /run/upnp/miniupnp.conf line 74 : anchor=VyOS
+#anchor=VyOS
uuid={{ uuid }}
@@ -129,7 +132,7 @@ lease_file=/config/upnp.leases
#serial=12345678
#model_number=1
-{% if rules is vyos_defined %}
+{% if rule is vyos_defined %}
# UPnP permission rules
# (allow|deny) (external port range) IP/mask (internal port range)
# A port range is <min port>-<max port> or <port> if there is only
@@ -142,9 +145,9 @@ lease_file=/config/upnp.leases
# modify the IP ranges to match their own internal networks, and
# also consider implementing network-specific restrictions
# CAUTION: failure to enforce any rules may permit insecure requests to be made!
-{% for rule, config in rules.items() %}
-{% if config.disable is vyos_defined %}
-{{ config.action }} {{ config.external_port_range }} {{ config.ip }} {{ config.internal_port_range }}
+{% for rule, config in rule.items() %}
+{% if config.disable is not vyos_defined %}
+{{ config.action }} {{ config.external_port_range }} {{ config.ip }}{{ '/32' if '/' not in config.ip else '' }} {{ config.internal_port_range }}
{% endif %}
{% endfor %}
{% endif %}
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 7029f39af..808e9dbe7 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -38,6 +38,9 @@
{% if config.disable_capability_negotiation is vyos_defined %}
neighbor {{ neighbor }} dont-capability-negotiate
{% endif %}
+{% if config.disable_connected_check is vyos_defined %}
+ neighbor {{ neighbor }} disable-connected-check
+{% endif %}
{% if config.ebgp_multihop is vyos_defined %}
neighbor {{ neighbor }} ebgp-multihop {{ config.ebgp_multihop }}
{% endif %}
@@ -231,7 +234,7 @@
{% endif %}
{% endmacro %}
!
-router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
+router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if parameters.ebgp_requires_policy is vyos_defined %}
bgp ebgp-requires-policy
{% else %}
diff --git a/data/templates/ipsec/swanctl/profile.j2 b/data/templates/ipsec/swanctl/profile.j2
index d4f417378..8519a84f8 100644
--- a/data/templates/ipsec/swanctl/profile.j2
+++ b/data/templates/ipsec/swanctl/profile.j2
@@ -9,6 +9,10 @@
version = {{ ike.key_exchange[4:] if ike.key_exchange is vyos_defined else "0" }}
rekey_time = {{ ike.lifetime }}s
keyingtries = 0
+{% if ike.dead_peer_detection is vyos_defined %}
+ dpd_timeout = {{ ike.dead_peer_detection.timeout }}
+ dpd_delay = {{ ike.dead_peer_detection.interval }}
+{% endif %}
{% if profile_conf.authentication.mode is vyos_defined('pre-shared-secret') %}
local {
auth = psk
diff --git a/data/templates/macsec/wpa_supplicant.conf.j2 b/data/templates/macsec/wpa_supplicant.conf.j2
index d2529c50d..1f7ba16f4 100644
--- a/data/templates/macsec/wpa_supplicant.conf.j2
+++ b/data/templates/macsec/wpa_supplicant.conf.j2
@@ -47,6 +47,12 @@ network={
# 1: Integrity only
macsec_integ_only={{ '0' if security.encrypt is vyos_defined else '1' }}
+ # macsec_csindex: IEEE 802.1X/MACsec cipher suite
+ # 0 = GCM-AES-128
+ # 1 = GCM-AES-256
+{# security.cipher is a mandatory key #}
+ macsec_csindex={{ '1' if security.cipher is vyos_defined('gcm-aes-256') else '0' }}
+
{% if security.encrypt is vyos_defined %}
# mka_cak, mka_ckn, and mka_priority: IEEE 802.1X/MACsec pre-shared key mode
# This allows to configure MACsec with a pre-shared key using a (CAK,CKN) pair.
@@ -62,12 +68,6 @@ network={
# mka_priority (Priority of MKA Actor) is in 0..255 range with 255 being
# default priority
mka_priority={{ security.mka.priority }}
-
- # macsec_csindex: IEEE 802.1X/MACsec cipher suite
- # 0 = GCM-AES-128
- # 1 = GCM-AES-256
-{# security.cipher is a mandatory key #}
- macsec_csindex={{ '1' if security.cipher is vyos_defined('gcm-aes-256') else '0' }}
{% endif %}
{% if security.replay_window is vyos_defined %}
diff --git a/data/templates/monitoring/override.conf.j2 b/data/templates/monitoring/override.conf.j2
deleted file mode 100644
index 9f1b4ebec..000000000
--- a/data/templates/monitoring/override.conf.j2
+++ /dev/null
@@ -1,7 +0,0 @@
-[Unit]
-After=vyos-router.service
-ConditionPathExists=/run/telegraf/vyos-telegraf.conf
-[Service]
-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/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2
index 8418a2185..e0cad5181 100644
--- a/data/templates/ocserv/ocserv_config.j2
+++ b/data/templates/ocserv/ocserv_config.j2
@@ -56,36 +56,32 @@ ban-reset-time = 300
# The name to use for the tun device
device = sslvpn
-# An alternative way of specifying the network:
-{% if network_settings %}
# DNS settings
-{% if network_settings.name_server is string %}
-dns = {{ network_settings.name_server }}
-{% else %}
-{% for dns in network_settings.name_server %}
+{% if network_settings.name_server is vyos_defined %}
+{% for dns in network_settings.name_server %}
dns = {{ dns }}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
+
# IPv4 network pool
-{% if network_settings.client_ip_settings %}
-{% if network_settings.client_ip_settings.subnet %}
+{% if network_settings.client_ip_settings.subnet is vyos_defined %}
ipv4-network = {{ network_settings.client_ip_settings.subnet }}
-{% endif %}
-{% endif %}
+{% endif %}
+
# IPv6 network pool
-{% if network_settings.client_ipv6_pool %}
-{% if network_settings.client_ipv6_pool.prefix %}
+{% if network_settings.client_ipv6_pool.prefix is vyos_defined %}
ipv6-network = {{ network_settings.client_ipv6_pool.prefix }}
ipv6-subnet-prefix = {{ network_settings.client_ipv6_pool.mask }}
-{% endif %}
-{% endif %}
{% endif %}
-{% if network_settings.push_route is string %}
-route = {{ network_settings.push_route }}
-{% else %}
+{% if network_settings.push_route is vyos_defined %}
{% for route in network_settings.push_route %}
route = {{ route }}
{% endfor %}
{% endif %}
+{% if network_settings.split_dns is vyos_defined %}
+{% for tmp in network_settings.split_dns %}
+split-dns = {{ tmp }}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/telegraf/override.conf.j2 b/data/templates/telegraf/override.conf.j2
new file mode 100644
index 000000000..d30bb19de
--- /dev/null
+++ b/data/templates/telegraf/override.conf.j2
@@ -0,0 +1,15 @@
+{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %}
+[Unit]
+After=
+After=vyos-router.service
+ConditionPathExists=/run/telegraf/telegraf.conf
+
+[Service]
+ExecStart=
+ExecStart={{ vrf_command }}/usr/bin/telegraf --config /run/telegraf/telegraf.conf --config-directory /etc/telegraf/telegraf.d --pidfile /run/telegraf/telegraf.pid
+PIDFile=/run/telegraf/telegraf.pid
+EnvironmentFile=
+Environment=INFLUX_TOKEN={{ influxdb.authentication.token }}
+CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_BPF CAP_DAC_OVERRIDE
+AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
+
diff --git a/data/templates/monitoring/syslog_telegraf.j2 b/data/templates/telegraf/syslog_telegraf.j2
index cdcbd92a4..cdcbd92a4 100644
--- a/data/templates/monitoring/syslog_telegraf.j2
+++ b/data/templates/telegraf/syslog_telegraf.j2
diff --git a/data/templates/monitoring/telegraf.j2 b/data/templates/telegraf/telegraf.j2
index 6b395692b..6b395692b 100644
--- a/data/templates/monitoring/telegraf.j2
+++ b/data/templates/telegraf/telegraf.j2
diff --git a/debian/control b/debian/control
index 6a6ccf602..0db098be6 100644
--- a/debian/control
+++ b/debian/control
@@ -59,7 +59,7 @@ Depends:
frr-rpki-rtrlib,
frr-snmp,
grc,
- hostapd (>= 0.6.8),
+ hostapd,
hvinfo,
igmpproxy,
ipaddrcheck,
diff --git a/interface-definitions/include/accel-ppp/radius-additions.xml.i b/interface-definitions/include/accel-ppp/radius-additions.xml.i
index 441c9dda5..15ff5165f 100644
--- a/interface-definitions/include/accel-ppp/radius-additions.xml.i
+++ b/interface-definitions/include/accel-ppp/radius-additions.xml.i
@@ -89,18 +89,7 @@
<defaultValue>3</defaultValue>
</leafNode>
#include <include/radius-nas-identifier.xml.i>
- <leafNode name="nas-ip-address">
- <properties>
- <help>NAS-IP-Address attribute sent to RADIUS</help>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- <valueHelp>
- <format>ipv4</format>
- <description>NAS-IP-Address attribute</description>
- </valueHelp>
- </properties>
- </leafNode>
+ #include <include/radius-nas-ip-address.xml.i>
<leafNode name="preallocate-vif">
<properties>
<help>Enable attribute NAS-Port-Id in Access-Request</help>
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index c1b465e43..d2bcea62a 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -896,7 +896,7 @@
</tagNode>
</children>
</node>
-<leafNode name="local-as">
+<leafNode name="system-as">
<properties>
<help>Autonomous System Number (ASN)</help>
<valueHelp>
diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i
index b99896687..5a7b5a8d3 100644
--- a/interface-definitions/include/firewall/tcp-flags.xml.i
+++ b/interface-definitions/include/firewall/tcp-flags.xml.i
@@ -114,6 +114,23 @@
</node>
</children>
</node>
+ <leafNode name="mss">
+ <properties>
+ <help>Maximum segment size (MSS)</help>
+ <valueHelp>
+ <format>u32:1-16384</format>
+ <description>Maximum segment size</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;min&gt;-&lt;max&gt;</format>
+ <description>TCP MSS range (use '-' as delimiter)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-16384"/>
+ <validator name="range" argument="--min=1 --max=16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<!-- include end -->
diff --git a/interface-definitions/include/nat-exclude.xml.i b/interface-definitions/include/nat-exclude.xml.i
new file mode 100644
index 000000000..4d53cf844
--- /dev/null
+++ b/interface-definitions/include/nat-exclude.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from nat-exclude.xml.i -->
+<leafNode name="exclude">
+ <properties>
+ <help>Exclude packets matching this rule from NAT</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i
index bdb86ed9b..84941aa6a 100644
--- a/interface-definitions/include/nat-rule.xml.i
+++ b/interface-definitions/include/nat-rule.xml.i
@@ -23,12 +23,7 @@
</children>
</node>
#include <include/generic-disable-node.xml.i>
- <leafNode name="exclude">
- <properties>
- <help>Exclude packets matching this rule from NAT</help>
- <valueless/>
- </properties>
- </leafNode>
+ #include <include/nat-exclude.xml.i>
<leafNode name="log">
<properties>
<help>NAT rule logging</help>
diff --git a/interface-definitions/include/radius-nas-ip-address.xml.i b/interface-definitions/include/radius-nas-ip-address.xml.i
new file mode 100644
index 000000000..8d0a3fd6a
--- /dev/null
+++ b/interface-definitions/include/radius-nas-ip-address.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from radius-nas-ip-address.xml.i -->
+<leafNode name="nas-ip-address">
+ <properties>
+ <help>NAS-IP-Address attribute sent to RADIUS</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>NAS-IP-Address attribute</description>
+ </valueHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/bgp-version.xml.i b/interface-definitions/include/version/bgp-version.xml.i
index 15bc5abd4..ced49e729 100644
--- a/interface-definitions/include/version/bgp-version.xml.i
+++ b/interface-definitions/include/version/bgp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/bgp-version.xml.i -->
-<syntaxVersion component='bgp' version='2'></syntaxVersion>
+<syntaxVersion component='bgp' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in
index b47f653c6..bde1a6f8d 100644
--- a/interface-definitions/nat66.xml.in
+++ b/interface-definitions/nat66.xml.in
@@ -35,6 +35,7 @@
<valueless/>
</properties>
</leafNode>
+ #include <include/nat-exclude.xml.i>
<leafNode name="log">
<properties>
<help>NAT66 rule logging</help>
@@ -49,6 +50,30 @@
</completionHelp>
</properties>
</leafNode>
+ <node name="destination">
+ <properties>
+ <help>IPv6 destination prefix options</help>
+ </properties>
+ <children>
+ <leafNode name="prefix">
+ <properties>
+ <help>IPv6 prefix to be translated</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv6net</format>
+ <description>Match everything except the specified IPv6 prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ <validator name="ipv6-prefix-exclude"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<node name="source">
<properties>
<help>IPv6 source prefix options</help>
@@ -61,8 +86,13 @@
<format>ipv6net</format>
<description>IPv6 prefix</description>
</valueHelp>
+ <valueHelp>
+ <format>!ipv6net</format>
+ <description>Match everything except the specified IPv6 prefix</description>
+ </valueHelp>
<constraint>
<validator name="ipv6-prefix"/>
+ <validator name="ipv6-prefix-exclude"/>
</constraint>
</properties>
</leafNode>
@@ -133,6 +163,7 @@
<valueless/>
</properties>
</leafNode>
+ #include <include/nat-exclude.xml.i>
<leafNode name="log">
<properties>
<help>NAT66 rule logging</help>
@@ -164,9 +195,53 @@
<format>ipv6net</format>
<description>IPv6 prefix</description>
</valueHelp>
+ <valueHelp>
+ <format>!ipv6</format>
+ <description>Match everything except the specified IPv6 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv6net</format>
+ <description>Match everything except the specified IPv6 prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ <validator name="ipv6-prefix"/>
+ <validator name="ipv6-address-exclude"/>
+ <validator name="ipv6-prefix-exclude"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="source">
+ <properties>
+ <help>IPv6 source prefix options</help>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IPv6 address or prefix to be translated</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv6</format>
+ <description>Match everything except the specified IPv6 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv6net</format>
+ <description>Match everything except the specified IPv6 prefix</description>
+ </valueHelp>
<constraint>
<validator name="ipv6-address"/>
<validator name="ipv6-prefix"/>
+ <validator name="ipv6-address-exclude"/>
+ <validator name="ipv6-prefix-exclude"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index cc1de609d..e794c4b90 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -392,7 +392,7 @@
<description>Prefix to match against</description>
</valueHelp>
<constraint>
- <validator name="ip-prefix"/>
+ <validator name="ipv4-prefix"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/service-monitoring-telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in
index 36f40a539..68215dba4 100644
--- a/interface-definitions/service-monitoring-telegraf.xml.in
+++ b/interface-definitions/service-monitoring-telegraf.xml.in
@@ -10,7 +10,7 @@
<children>
<node name="telegraf" owner="${vyos_conf_scripts_dir}/service_monitoring_telegraf.py">
<properties>
- <help>Telegraf monitoring</help>
+ <help>Telegraf metric collector</help>
</properties>
<children>
<node name="influxdb">
@@ -228,27 +228,7 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="listen-address">
- <properties>
- <help>Local IP addresses to listen on</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
- </completionHelp>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address to listen for incoming connections</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address to listen for incoming connections</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
- <validator name="ipv6-link-local"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/listen-address.xml.i>
<leafNode name="metric-version">
<properties>
<help>Metric version control mapping from Telegraf to Prometheus format</help>
@@ -291,21 +271,10 @@
</leafNode>
</children>
</node>
- <leafNode name="url">
- <properties>
- <help>Remote URL</help>
- <valueHelp>
- <format>url</format>
- <description>Remote URL to Splunk collector</description>
- </valueHelp>
- <constraint>
- <regex>^(http(s?):\/\/.*):(\d*)\/?(.*)</regex>
- </constraint>
- <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
- </properties>
- </leafNode>
+ #include <include/monitoring/url.xml.i>
</children>
</node>
+ #include <include/interface/vrf.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in
index a129b7260..ec23d87df 100644
--- a/interface-definitions/service-upnp.xml.in
+++ b/interface-definitions/service-upnp.xml.in
@@ -103,19 +103,19 @@
</valueHelp>
<valueHelp>
<format>ipv4</format>
- <description>IP address to listen for incoming connections</description>
+ <description>IPv4 address to listen for incoming connections</description>
</valueHelp>
<valueHelp>
- <format>ipv4-prefix</format>
- <description>IP prefix to listen for incoming connections</description>
+ <format>ipv4net</format>
+ <description>IPv4 prefix to listen for incoming connections</description>
</valueHelp>
<valueHelp>
<format>ipv6</format>
- <description>IP address to listen for incoming connections</description>
+ <description>IPv6 address to listen for incoming connections</description>
</valueHelp>
<valueHelp>
- <format>ipv6-prefix</format>
- <description>IP prefix to listen for incoming connections</description>
+ <format>ipv6net</format>
+ <description>IPv6 prefix to listen for incoming connections</description>
</valueHelp>
<multi/>
<constraint>
@@ -197,10 +197,15 @@
<help>The IP to which this rule applies (REQUIRE)</help>
<valueHelp>
<format>ipv4</format>
+ <description>The IPv4 address to which this rule applies</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
<description>The IPv4 to which this rule applies</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address" />
+ <validator name="ipv4-address"/>
+ <validator name="ipv4-host"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/system-proxy.xml.in b/interface-definitions/system-proxy.xml.in
index 1c06b347f..8fb6bfae5 100644
--- a/interface-definitions/system-proxy.xml.in
+++ b/interface-definitions/system-proxy.xml.in
@@ -11,7 +11,7 @@
<properties>
<help>Proxy URL</help>
<constraint>
- <regex>http:\/\/[a-z0-9\.]+</regex>
+ <regex>http(s)?:\/\/[a-z0-9-\.]+</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in
index f734283e7..fd70a76dc 100644
--- a/interface-definitions/vpn-l2tp.xml.in
+++ b/interface-definitions/vpn-l2tp.xml.in
@@ -215,6 +215,7 @@
</properties>
</leafNode>
#include <include/radius-nas-identifier.xml.i>
+ #include <include/radius-nas-ip-address.xml.i>
<node name="dae-server">
<properties>
<help>IPv4 address and port to bind Dynamic Authorization Extension server (DM/CoA)</help>
diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in
index 21b47125d..6309863c5 100644
--- a/interface-definitions/vpn-openconnect.xml.in
+++ b/interface-definitions/vpn-openconnect.xml.in
@@ -265,6 +265,19 @@
</children>
</node>
#include <include/name-server-ipv4-ipv6.xml.i>
+ <leafNode name="split-dns">
+ <properties>
+ <help>Domains over which the provided DNS should be used</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Client prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
</children>
</node>
</children>
diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in
index a7048e5ed..97a087ce2 100644
--- a/op-mode-definitions/container.xml.in
+++ b/op-mode-definitions/container.xml.in
@@ -149,7 +149,7 @@
<path>container name</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/container.py restart name="$3"</command>
+ <command>sudo ${vyos_op_scripts_dir}/container.py restart --name="$3"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index 8a02e1f08..975d20465 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -224,6 +224,43 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit ssh.service</command>
</leafNode>
+ <node name="vpn">
+ <properties>
+ <help>Show log for Virtual Private Network (VPN)</help>
+ </properties>
+ <children>
+ <leafNode name="all">
+ <properties>
+ <help>Monitor last lines of ALL VPNs</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit strongswan-starter.service --unit accel-ppp@*.service</command>
+ </leafNode>
+ <leafNode name="ipsec">
+ <properties>
+ <help>Monitor last lines of IPSec</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit strongswan-starter.service</command>
+ </leafNode>
+ <leafNode name="l2tp">
+ <properties>
+ <help>Monitor last lines of L2TP</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit accel-ppp@l2tp.service</command>
+ </leafNode>
+ <leafNode name="pptp">
+ <properties>
+ <help>Monitor last lines of PPTP</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit accel-ppp@pptp.service</command>
+ </leafNode>
+ <leafNode name="sstp">
+ <properties>
+ <help>Monitor last lines of SSTP</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit accel-ppp@sstp.service</command>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in
index 7148c1128..dbc06b930 100644
--- a/op-mode-definitions/nat.xml.in
+++ b/op-mode-definitions/nat.xml.in
@@ -16,13 +16,13 @@
<properties>
<help>Show configured source NAT rules</help>
</properties>
- <command>${vyos_op_scripts_dir}/nat.py show_rules --direction source</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet</command>
</node>
<node name="statistics">
<properties>
<help>Show statistics for configured source NAT rules</help>
</properties>
- <command>${vyos_op_scripts_dir}/nat.py show_statistics --direction source</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet</command>
</node>
<node name="translations">
<properties>
@@ -58,7 +58,7 @@
<properties>
<help>Show configured destination NAT rules</help>
</properties>
- <command>${vyos_op_scripts_dir}/nat.py show_rules --direction destination</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet</command>
</node>
<node name="statistics">
<properties>
diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in
index 1ec46eb11..aba2d6add 100644
--- a/op-mode-definitions/nat66.xml.in
+++ b/op-mode-definitions/nat66.xml.in
@@ -16,7 +16,7 @@
<properties>
<help>Show configured source NAT66 rules</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_nat66_rules.py --source</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet6</command>
</node>
<node name="statistics">
<properties>
@@ -58,7 +58,7 @@
<properties>
<help>Show configured destination NAT66 rules</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_nat66_rules.py --destination</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet6</command>
</node>
<node name="statistics">
<properties>
diff --git a/op-mode-definitions/openconnect.xml.in b/op-mode-definitions/openconnect.xml.in
index 9343637c0..88e1f9f15 100644
--- a/op-mode-definitions/openconnect.xml.in
+++ b/op-mode-definitions/openconnect.xml.in
@@ -11,7 +11,7 @@
<properties>
<help>Show active OpenConnect server sessions</help>
</properties>
- <command>${vyos_op_scripts_dir}/openconnect-control.py --action="show_sessions"</command>
+ <command>${vyos_op_scripts_dir}/openconnect.py show_sessions</command>
</leafNode>
<tagNode name="user">
<properties>
diff --git a/op-mode-definitions/show-conntrack.xml.in b/op-mode-definitions/show-conntrack.xml.in
index 8d921e6a5..4cdcffcdb 100644
--- a/op-mode-definitions/show-conntrack.xml.in
+++ b/op-mode-definitions/show-conntrack.xml.in
@@ -7,6 +7,12 @@
<help>Show conntrack tables entries</help>
</properties>
<children>
+ <node name="statistics">
+ <properties>
+ <help>Show conntrack statistics</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/conntrack.py show_statistics</command>
+ </node>
<node name="table">
<properties>
<help>Show conntrack entries for table</help>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 24a1b5f3e..ebd198215 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -380,19 +380,19 @@
<properties>
<help>Show log for ALL</help>
</properties>
- <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e charon -e accel -e pptpd -e ppp</command>
+ <command>journalctl --no-hostname --boot --unit strongswan-starter.service --unit accel-ppp@*.service</command>
</leafNode>
<leafNode name="ipsec">
<properties>
<help>Show log for IPSec</help>
</properties>
- <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e charon</command>
+ <command>journalctl --no-hostname --boot --unit strongswan-starter.service</command>
</leafNode>
<leafNode name="l2tp">
<properties>
<help>Show log for L2TP</help>
</properties>
- <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e remote-access-aaa-win -e remote-access-zzz-mac -e accel-l2tp -e ppp</command>
+ <command>journalctl --no-hostname --boot --unit accel-ppp@l2tp.service</command>
</leafNode>
<leafNode name="pptp">
<properties>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 8f822a97d..912bc94f2 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -295,11 +295,18 @@ def is_source_interface(conf, interface, intftype=None):
"""
ret_val = None
intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan']
- if intftype not in intftypes + [None]:
+ if not intftype:
+ intftype = intftypes
+
+ if isinstance(intftype, str):
+ intftype = [intftype]
+ elif not isinstance(intftype, list):
+ raise ValueError(f'Interface type "{type(intftype)}" must be either str or list!')
+
+ if not all(x in intftypes for x in intftype):
raise ValueError(f'unknown interface type "{intftype}" or it can not '
'have a source-interface')
- intftype = intftypes if intftype == None else [intftype]
for it in intftype:
base = ['interfaces', it]
for intf in conf.list_nodes(base):
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 2ab3cb408..447ec795c 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -295,6 +295,12 @@ def verify_source_interface(config):
raise ConfigError(f'Invalid source-interface "{src_ifname}". Interface '
f'is already a member of bond "{bond_name}"!')
+ if 'is_source_interface' in config:
+ tmp = config['is_source_interface']
+ src_ifname = config['source_interface']
+ raise ConfigError(f'Can not use source-interface "{src_ifname}", it already ' \
+ f'belongs to interface "{tmp}"!')
+
def verify_dhcpv6(config):
"""
Common helper function used by interface implementations to perform
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 09ae73eac..6894fc4da 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -26,7 +26,7 @@ directories = {
"templates": "/usr/share/vyos/templates/",
"certbot": "/config/auth/letsencrypt",
"api_schema": "/usr/libexec/vyos/services/api/graphql/graphql/schema/",
- "api_templates": "/usr/libexec/vyos/services/api/graphql/recipes/templates/",
+ "api_templates": "/usr/libexec/vyos/services/api/graphql/session/templates/",
"vyos_udev_dir": "/run/udev/vyos"
}
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 3e2de4c3f..663c4394a 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -297,6 +297,11 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if tcp_flags:
output.append(parse_tcp_flags(tcp_flags))
+ # TCP MSS
+ tcp_mss = dict_search_args(rule_conf, 'tcp', 'mss')
+ if tcp_mss:
+ output.append(f'tcp option maxseg size {tcp_mss}')
+
output.append('counter')
if 'set' in rule_conf:
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index 758967fbc..aa818bc5f 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -295,8 +295,24 @@ class BridgeIf(Interface):
self.del_port(member)
# enable/disable Vlan Filter
- vlan_filter = '1' if 'enable_vlan' in config else '0'
- self.set_vlan_filter(vlan_filter)
+ tmp = '1' if 'enable_vlan' in config else '0'
+ self.set_vlan_filter(tmp)
+
+ # add VLAN interfaces to local 'parent' bridge to allow forwarding
+ if 'enable_vlan' in config:
+ for vlan in config.get('vif_remove', {}):
+ # Remove old VLANs from the bridge
+ cmd = f'bridge vlan del dev {self.ifname} vid {vlan} self'
+ self._cmd(cmd)
+
+ for vlan in config.get('vif', {}):
+ cmd = f'bridge vlan add dev {self.ifname} vid {vlan} self'
+ self._cmd(cmd)
+
+ # VLAN of bridge parent interface is always 1. VLAN 1 is the default
+ # VLAN for all unlabeled packets
+ cmd = f'bridge vlan add dev {self.ifname} vid 1 pvid untagged self'
+ self._cmd(cmd)
tmp = dict_search('member.interface', config)
if tmp:
diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py
index 91f667b65..5e98cd510 100644
--- a/python/vyos/ifconfig/section.py
+++ b/python/vyos/ifconfig/section.py
@@ -88,7 +88,7 @@ class Section:
raise ValueError(f'No type found for interface name: {name}')
@classmethod
- def _intf_under_section (cls,section=''):
+ def _intf_under_section (cls,section='',vlan=True):
"""
return a generator with the name of the configured interface
which are under a section
@@ -103,6 +103,9 @@ class Section:
if section and ifsection != section:
continue
+ if vlan == False and '.' in ifname:
+ continue
+
yield ifname
@classmethod
@@ -135,13 +138,14 @@ class Section:
return l
@classmethod
- def interfaces(cls, section=''):
+ def interfaces(cls, section='', vlan=True):
"""
return a list of the name of the configured interface which are under a section
- if no section is provided, then it returns all configured interfaces
+ if no section is provided, then it returns all configured interfaces.
+ If vlan is True, also Vlan subinterfaces will be returned
"""
- return cls._sort_interfaces(cls._intf_under_section(section))
+ return cls._sort_interfaces(cls._intf_under_section(section, vlan))
@classmethod
def _intf_with_feature(cls, feature=''):
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 0af4359c6..628f7b3a2 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -18,6 +18,33 @@ import sys
import typing
+class Error(Exception):
+ """ Any error that makes requested operation impossible to complete
+ for reasons unrelated to the user input or script logic.
+ """
+ pass
+
+class UnconfiguredSubsystem(Error):
+ """ Requested operation is valid, but cannot be completed
+ because corresponding subsystem is not configured and running.
+ """
+ pass
+
+class DataUnavailable(Error):
+ """ Requested operation is valid, but cannot be completed
+ because data for it is not available.
+ This error MAY be treated as temporary because such issues
+ are often caused by transient events such as service restarts.
+ """
+ pass
+
+class PermissionDenied(Error):
+ """ Requested operation is valid, but the caller has no permission
+ to perform it.
+ """
+ pass
+
+
def _is_op_mode_function_name(name):
if re.match(r"^(show|clear|reset|restart)", name):
return True
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 8df9ef7d6..325b630bc 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -471,6 +471,29 @@ def process_named_running(name):
return p.pid
return None
+def is_listen_port_bind_service(port: int, service: str) -> bool:
+ """Check if listen port bound to expected program name
+ :param port: Bind port
+ :param service: Program name
+ :return: bool
+
+ Example:
+ % is_listen_port_bind_service(443, 'nginx')
+ True
+ % is_listen_port_bind_service(443, 'ocservr-main')
+ False
+ """
+ from psutil import net_connections as connections
+ from psutil import Process as process
+ for connection in connections():
+ addr = connection.laddr
+ pid = connection.pid
+ pid_name = process(pid).name()
+ pid_port = addr.port
+ if service == pid_name and port == pid_port:
+ return True
+ return False
+
def seconds_to_human(s, separator=""):
""" Converts number of seconds passed to a human-readable
interval such as 1w4d18h35m59s
diff --git a/smoketest/configs/pki-misc b/smoketest/configs/pki-misc
index c90226a2a..4db795565 100644
--- a/smoketest/configs/pki-misc
+++ b/smoketest/configs/pki-misc
@@ -59,6 +59,10 @@ vpn {
}
mode local
}
+ listen-ports {
+ tcp 4443
+ udp 4443
+ }
network-settings {
client-ip-settings {
subnet 192.168.160.0/24
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 4de90e1ec..684a07681 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -177,6 +177,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip filter')
def test_basic_rules(self):
+ mss_range = '501-1460'
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'])
@@ -203,6 +204,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
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(['firewall', 'name', 'smoketest', 'rule', '5', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '5', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '5', 'tcp', 'flags', 'syn'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '5', 'tcp', 'mss', mss_range])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
@@ -214,7 +219,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['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'],
['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']
+ ['tcp dport { 22 }', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'drop'],
+ [f'tcp flags & syn == syn tcp option maxseg size {mss_range}']
]
self.verify_nftables(nftables_search, 'ip filter')
diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py
index 8f711af20..6d7af78eb 100755
--- a/smoketest/scripts/cli/test_interfaces_bridge.py
+++ b/smoketest/scripts/cli/test_interfaces_bridge.py
@@ -19,6 +19,7 @@ import json
import unittest
from base_interfaces_test import BasicInterfaceTest
+from copy import deepcopy
from glob import glob
from netifaces import interfaces
@@ -224,85 +225,78 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
super().test_vif_8021q_mtu_limits()
def test_bridge_vlan_filter(self):
- def _verify_members() -> None:
- # check member interfaces are added on the bridge
- for interface in self._interfaces:
- bridge_members = []
- for tmp in glob(f'/sys/class/net/{interface}/lower_*'):
- bridge_members.append(os.path.basename(tmp).replace('lower_', ''))
-
- # We can not use assertListEqual() b/c the position of the interface
- # names within the list is not fixed
- self.assertEqual(len(self._members), len(bridge_members))
- for member in self._members:
- self.assertIn(member, bridge_members)
-
- def _check_vlan_filter() -> None:
- for interface in self._interfaces:
- tmp = cmd(f'bridge -j vlan show dev {interface}')
- tmp = json.loads(tmp)
- self.assertIsNotNone(tmp)
-
- for interface_status in tmp:
- ifname = interface_status['ifname']
- for interface in self._members:
- vlan_success = 0;
- if interface == ifname:
- vlans_status = interface_status['vlans']
- for vlan_status in vlans_status:
- vlan_id = vlan_status['vlan']
- flag_num = 0
- if 'flags' in vlan_status:
- flags = vlan_status['flags']
- for flag in flags:
- flag_num = flag_num +1
- if vlan_id == 2:
- if flag_num == 0:
- vlan_success = vlan_success + 1
- else:
- for id in range(4,10):
- if vlan_id == id:
- if flag_num == 0:
- vlan_success = vlan_success + 1
- if vlan_id >= 101:
- if flag_num == 2:
- vlan_success = vlan_success + 1
- self.assertGreaterEqual(vlan_success, 7)
-
- vif_vlan = 2
+ vifs = ['10', '20', '30', '40']
+ native_vlan = '20'
+
# Add member interface to bridge and set VLAN filter
for interface in self._interfaces:
base = self._base_path + [interface]
self.cli_set(base + ['enable-vlan'])
self.cli_set(base + ['address', '192.0.2.1/24'])
- self.cli_set(base + ['vif', str(vif_vlan), 'address', '192.0.3.1/24'])
- self.cli_set(base + ['vif', str(vif_vlan), 'mtu', self._mtu])
- vlan_id = 101
- allowed_vlan = 2
- allowed_vlan_range = '4-9'
- # assign members to bridge interface
+ for vif in vifs:
+ self.cli_set(base + ['vif', vif, 'address', f'192.0.{vif}.1/24'])
+ self.cli_set(base + ['vif', vif, 'mtu', self._mtu])
+
for member in self._members:
base_member = base + ['member', 'interface', member]
- self.cli_set(base_member + ['allowed-vlan', str(allowed_vlan)])
- self.cli_set(base_member + ['allowed-vlan', allowed_vlan_range])
- self.cli_set(base_member + ['native-vlan', str(vlan_id)])
- vlan_id += 1
+ self.cli_set(base_member + ['native-vlan', native_vlan])
+ for vif in vifs:
+ self.cli_set(base_member + ['allowed-vlan', vif])
# commit config
self.cli_commit()
+ def _verify_members(interface, members) -> None:
+ # check member interfaces are added on the bridge
+ bridge_members = []
+ for tmp in glob(f'/sys/class/net/{interface}/lower_*'):
+ bridge_members.append(os.path.basename(tmp).replace('lower_', ''))
+
+ self.assertListEqual(sorted(members), sorted(bridge_members))
+
+ def _check_vlan_filter(interface, vifs) -> None:
+ configured_vlan_ids = []
+
+ bridge_json = cmd(f'bridge -j vlan show dev {interface}')
+ bridge_json = json.loads(bridge_json)
+ self.assertIsNotNone(bridge_json)
+
+ for tmp in bridge_json:
+ self.assertIn('vlans', tmp)
+
+ for vlan in tmp['vlans']:
+ self.assertIn('vlan', vlan)
+ configured_vlan_ids.append(str(vlan['vlan']))
+
+ # Verify native VLAN ID has 'PVID' flag set on individual member ports
+ if not interface.startswith('br') and str(vlan['vlan']) == native_vlan:
+ self.assertIn('flags', vlan)
+ self.assertIn('PVID', vlan['flags'])
+
+ self.assertListEqual(sorted(configured_vlan_ids), sorted(vifs))
+
# Verify correct setting of VLAN filter function
for interface in self._interfaces:
tmp = read_file(f'/sys/class/net/{interface}/bridge/vlan_filtering')
self.assertEqual(tmp, '1')
- # Execute the program to obtain status information and verify proper
- # VLAN filter setup
- _check_vlan_filter()
+ # Obtain status information and verify proper VLAN filter setup.
+ # First check if all members are present, second check if all VLANs
+ # are assigned on the parend bridge interface, third verify all the
+ # VLANs are properly setup on the downstream "member" ports
+ for interface in self._interfaces:
+ # check member interfaces are added on the bridge
+ _verify_members(interface, self._members)
- # check member interfaces are added on the bridge
- _verify_members()
+ # Check if all VLAN ids are properly set up. Bridge interface always
+ # has native VLAN 1
+ tmp = deepcopy(vifs)
+ tmp.append('1')
+ _check_vlan_filter(interface, tmp)
+
+ for member in self._members:
+ _check_vlan_filter(member, vifs)
# change member interface description to trigger config update,
# VLANs must still exist (T4565)
@@ -313,12 +307,22 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
# commit config
self.cli_commit()
- # check member interfaces are added on the bridge
- _verify_members()
+ # Obtain status information and verify proper VLAN filter setup.
+ # First check if all members are present, second check if all VLANs
+ # are assigned on the parend bridge interface, third verify all the
+ # VLANs are properly setup on the downstream "member" ports
+ for interface in self._interfaces:
+ # check member interfaces are added on the bridge
+ _verify_members(interface, self._members)
+
+ # Check if all VLAN ids are properly set up. Bridge interface always
+ # has native VLAN 1
+ tmp = deepcopy(vifs)
+ tmp.append('1')
+ _check_vlan_filter(interface, tmp)
- # Execute the program to obtain status information and verify proper
- # VLAN filter setup
- _check_vlan_filter()
+ for member in self._members:
+ _check_vlan_filter(member, vifs)
# delete all members
for interface in self._interfaces:
@@ -337,7 +341,6 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
for member in self._members:
self.assertNotIn(member, bridge_members)
-
def test_bridge_vif_members(self):
# T2945: ensure that VIFs are not dropped from bridge
vifs = ['300', '400']
diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index ca70e2c18..f141cc6d3 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -28,6 +28,8 @@ from vyos.util import read_file
from vyos.util import get_interface_config
from vyos.util import process_named_running
+PROCESS_NAME = 'wpa_supplicant'
+
def get_config_value(interface, key):
tmp = read_file(f'/run/wpa_supplicant/{interface}.conf')
tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp)
@@ -55,6 +57,10 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
# call base-classes classmethod
super(MACsecInterfaceTest, cls).setUpClass()
+ def tearDown(self):
+ super().tearDown()
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
def test_macsec_encryption(self):
# MACsec can be operating in authentication and encryption mode - both
# using different mandatory settings, lets test encryption as the basic
@@ -96,32 +102,29 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
tmp = get_config_value(src_interface, 'macsec_integ_only')
- self.assertTrue("0" in tmp)
+ self.assertIn("0", tmp)
tmp = get_config_value(src_interface, 'mka_cak')
- self.assertTrue(mak_cak in tmp)
+ self.assertIn(mak_cak, tmp)
tmp = get_config_value(src_interface, 'mka_ckn')
- self.assertTrue(mak_ckn in tmp)
-
- # check that we use the new macsec_csindex option (T4537)
- tmp = get_config_value(src_interface, 'macsec_csindex')
- self.assertTrue("1" in tmp)
+ self.assertIn(mak_ckn, tmp)
# check that the default priority of 255 is programmed
tmp = get_config_value(src_interface, 'mka_priority')
- self.assertTrue("255" in tmp)
+ self.assertIn("255", tmp)
tmp = get_config_value(src_interface, 'macsec_replay_window')
- self.assertTrue(replay_window in tmp)
+ self.assertIn(replay_window, tmp)
tmp = read_file(f'/sys/class/net/{interface}/mtu')
self.assertEqual(tmp, '1460')
- # Check for running process
- self.assertTrue(process_named_running('wpa_supplicant'))
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
def test_macsec_gcm_aes_128(self):
+ src_interface = 'eth0'
interface = 'macsec1'
cipher = 'gcm-aes-128'
self.cli_set(self._base_path + [interface])
@@ -129,7 +132,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
# check validate() - source interface is mandatory
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(self._base_path + [interface, 'source-interface', 'eth0'])
+ self.cli_set(self._base_path + [interface, 'source-interface', src_interface])
# check validate() - cipher is mandatory
with self.assertRaises(ConfigSessionError):
@@ -142,7 +145,15 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
self.assertIn(interface, interfaces())
self.assertEqual(cipher, get_cipher(interface))
+ # check that we use the new macsec_csindex option (T4537)
+ tmp = get_config_value(src_interface, 'macsec_csindex')
+ self.assertIn("0", tmp)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
def test_macsec_gcm_aes_256(self):
+ src_interface = 'eth0'
interface = 'macsec4'
cipher = 'gcm-aes-256'
self.cli_set(self._base_path + [interface])
@@ -150,7 +161,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
# check validate() - source interface is mandatory
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(self._base_path + [interface, 'source-interface', 'eth0'])
+ self.cli_set(self._base_path + [interface, 'source-interface', src_interface])
# check validate() - cipher is mandatory
with self.assertRaises(ConfigSessionError):
@@ -162,6 +173,13 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
self.assertIn(interface, interfaces())
self.assertEqual(cipher, get_cipher(interface))
+ # check that we use the new macsec_csindex option (T4537)
+ tmp = get_config_value(src_interface, 'macsec_csindex')
+ self.assertIn("1", tmp)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
def test_macsec_source_interface(self):
# Ensure source-interface can bot be part of any other bond or bridge
@@ -190,6 +208,9 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
self.assertIn(interface, interfaces())
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
if __name__ == '__main__':
- unittest.main(verbosity=2)
+ unittest.main(verbosity=2, failfast=True)
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index aac6a30f9..4b5625569 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -42,6 +42,17 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ 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_source_nat66(self):
source_prefix = 'fc00::/64'
translation_prefix = 'fc01::/64'
@@ -49,29 +60,23 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix])
self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_prefix])
- # check validate() - outbound-interface must be defined
- self.cli_commit()
+ self.cli_set(src_path + ['rule', '2', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '2', 'source', 'prefix', source_prefix])
+ self.cli_set(src_path + ['rule', '2', 'translation', 'address', 'masquerade'])
- tmp = cmd('sudo nft -j list table ip6 nat')
- data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp))
+ self.cli_set(src_path + ['rule', '3', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '3', 'source', 'prefix', source_prefix])
+ self.cli_set(src_path + ['rule', '3', 'exclude'])
- for idx in range(0, len(data_json)):
- data = data_json[idx]
+ self.cli_commit()
- self.assertEqual(data['chain'], 'POSTROUTING')
- self.assertEqual(data['family'], 'ip6')
- self.assertEqual(data['table'], 'nat')
+ nftables_search = [
+ ['oifname "eth1"', 'ip6 saddr fc00::/64', 'snat prefix to fc01::/64'],
+ ['oifname "eth1"', 'ip6 saddr fc00::/64', 'masquerade'],
+ ['oifname "eth1"', 'ip6 saddr fc00::/64', 'return']
+ ]
- iface = dict_search('match.right', data['expr'][0])
- address = dict_search('match.right.prefix.addr', data['expr'][2])
- mask = dict_search('match.right.prefix.len', data['expr'][2])
- translation_address = dict_search('snat.addr.prefix.addr', data['expr'][3])
- translation_mask = dict_search('snat.addr.prefix.len', data['expr'][3])
-
- self.assertEqual(iface, 'eth1')
- # check for translation address
- self.assertEqual(f'{translation_address}/{translation_mask}', translation_prefix)
- self.assertEqual(f'{address}/{mask}', source_prefix)
+ self.verify_nftables(nftables_search, 'ip6 nat')
def test_source_nat66_address(self):
source_prefix = 'fc00::/64'
@@ -106,28 +111,25 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
def test_destination_nat66(self):
destination_address = 'fc00::1'
translation_address = 'fc01::1'
+ source_address = 'fc02::1'
self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
self.cli_set(dst_path + ['rule', '1', 'destination', 'address', destination_address])
self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_address])
+ self.cli_set(dst_path + ['rule', '2', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '2', 'destination', 'address', destination_address])
+ self.cli_set(dst_path + ['rule', '2', 'source', 'address', source_address])
+ self.cli_set(dst_path + ['rule', '2', 'exclude'])
+
# check validate() - outbound-interface must be defined
self.cli_commit()
- tmp = cmd('sudo nft -j list table ip6 nat')
- data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp))
-
- for idx in range(0, len(data_json)):
- data = data_json[idx]
-
- self.assertEqual(data['chain'], 'PREROUTING')
- self.assertEqual(data['family'], 'ip6')
- self.assertEqual(data['table'], 'nat')
-
- iface = dict_search('match.right', data['expr'][0])
- dnat_addr = dict_search('dnat.addr', data['expr'][3])
+ nftables_search = [
+ ['iifname "eth1"', 'ip6 daddr fc00::1', 'dnat to fc01::1'],
+ ['iifname "eth1"', 'ip6 saddr fc02::1', 'ip6 daddr fc00::1', 'return']
+ ]
- self.assertEqual(dnat_addr, translation_address)
- self.assertEqual(iface, 'eth1')
+ self.verify_nftables(nftables_search, 'ip6 nat')
def test_destination_nat66_prefix(self):
destination_prefix = 'fc00::/64'
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 009dbc803..6196ffe60 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -46,7 +46,7 @@ neighbor_config = {
'shutdown' : '',
'cap_over' : '',
'ttl_security' : '5',
- 'local_as' : '300',
+ 'system_as' : '300',
'route_map_in' : route_map_in,
'route_map_out' : route_map_out,
'no_send_comm_ext' : '',
@@ -87,7 +87,7 @@ neighbor_config = {
'shutdown' : '',
'cap_over' : '',
'ttl_security' : '5',
- 'local_as' : '300',
+ 'system_as' : '300',
'solo' : '',
'route_map_in' : route_map_in,
'route_map_out' : route_map_out,
@@ -105,7 +105,8 @@ neighbor_config = {
'pfx_list_out' : prefix_list_out6,
'no_send_comm_ext' : '',
'peer_group' : 'foo-bar_baz',
- 'graceful_rst_hlp' : ''
+ 'graceful_rst_hlp' : '',
+ 'disable_conn_chk' : '',
},
}
@@ -120,6 +121,7 @@ peer_group_config = {
'shutdown' : '',
'cap_over' : '',
'ttl_security' : '5',
+ 'disable_conn_chk' : '',
},
'bar' : {
'remote_as' : '111',
@@ -131,7 +133,7 @@ peer_group_config = {
'remote_as' : '200',
'shutdown' : '',
'no_cap_nego' : '',
- 'local_as' : '300',
+ 'system_as' : '300',
'pfx_list_in' : prefix_list_in,
'pfx_list_out' : prefix_list_out,
'no_send_comm_ext' : '',
@@ -177,7 +179,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
cls.cli_delete(cls, ['policy'])
def setUp(self):
- self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['system-as', ASN])
def tearDown(self):
self.cli_delete(['vrf'])
@@ -251,6 +253,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' neighbor {peer} graceful-restart-disable', frrconfig)
if 'graceful_rst_hlp' in peer_config:
self.assertIn(f' neighbor {peer} graceful-restart-helper', frrconfig)
+ if 'disable_conn_chk' in peer_config:
+ self.assertIn(f' neighbor {peer} disable-connected-check', frrconfig)
+
def test_bgp_01_simple(self):
router_id = '127.0.0.1'
@@ -266,12 +271,12 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['parameters', 'router-id', router_id])
self.cli_set(base_path + ['parameters', 'log-neighbor-changes'])
- # Local AS number MUST be defined - as this is set in setUp() we remove
+ # System AS number MUST be defined - as this is set in setUp() we remove
# this once for testing of the proper error
- self.cli_delete(base_path + ['local-as'])
+ self.cli_delete(base_path + ['system-as'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['system-as', ASN])
# Default local preference (higher = more preferred, default value is 100)
self.cli_set(base_path + ['parameters', 'default', 'local-pref', local_pref])
@@ -400,6 +405,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'disable'])
if 'graceful_rst_hlp' in peer_config:
self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'restart-helper'])
+ if 'disable_conn_chk' in peer_config:
+ self.cli_set(base_path + ['neighbor', peer, 'disable-connected-check'])
# Conditional advertisement
if 'advertise_map' in peer_config:
@@ -488,6 +495,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'disable'])
if 'graceful_rst_hlp' in config:
self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'restart-helper'])
+ if 'disable_conn_chk' in config:
+ self.cli_set(base_path + ['peer-group', peer_group, 'disable-connected-check'])
# Conditional advertisement
if 'advertise_map' in config:
@@ -760,13 +769,13 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
# templates and Jinja2 FRR template.
table = '1000'
- self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['system-as', ASN])
# testing only one AFI is sufficient as it's generic code
for vrf in vrfs:
vrf_base = ['vrf', 'name', vrf]
self.cli_set(vrf_base + ['table', table])
- self.cli_set(vrf_base + ['protocols', 'bgp', 'local-as', ASN])
+ self.cli_set(vrf_base + ['protocols', 'bgp', 'system-as', ASN])
self.cli_set(vrf_base + ['protocols', 'bgp', 'parameters', 'router-id', router_id])
self.cli_set(vrf_base + ['protocols', 'bgp', 'route-map', route_map_in])
table = str(int(table) + 1000)
@@ -804,7 +813,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
confed_id = str(int(ASN) + 1)
confed_asns = '10 20 30 40'
- self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['system-as', ASN])
self.cli_set(base_path + ['parameters', 'router-id', router_id])
self.cli_set(base_path + ['parameters', 'confederation', 'identifier', confed_id])
for asn in confed_asns.split():
@@ -825,7 +834,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
remote_asn = str(int(ASN) + 10)
interface = 'eth0'
- self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['system-as', ASN])
self.cli_set(base_path + ['neighbor', interface, 'address-family', 'ipv6-unicast'])
self.cli_set(base_path + ['neighbor', interface, 'interface', 'v6only', 'remote-as', remote_asn])
@@ -850,7 +859,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
rt_export = f'{neighbor}:1002 1.2.3.4:567'
rt_import = f'{neighbor}:1003 500:100'
- self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['system-as', ASN])
# testing only one AFI is sufficient as it's generic code
for afi in ['ipv4-unicast', 'ipv6-unicast']:
self.cli_set(base_path + ['address-family', afi, 'export', 'vpn'])
@@ -889,7 +898,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
peer_group = 'bar'
interface = 'eth0'
- self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['system-as', ASN])
self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn])
self.cli_set(base_path + ['neighbor', neighbor, 'peer-group', peer_group])
self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', remote_asn])
@@ -929,7 +938,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
remote_asn = '500'
local_asn = '400'
- self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['system-as', ASN])
self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', ASN])
self.cli_set(base_path + ['neighbor', neighbor, 'local-as', local_asn])
diff --git a/smoketest/scripts/cli/test_service_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py
index 1c8cc9759..c1c4044e6 100755
--- a/smoketest/scripts/cli/test_service_monitoring_telegraf.py
+++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py
@@ -24,7 +24,7 @@ from vyos.util import process_named_running
from vyos.util import read_file
PROCESS_NAME = 'telegraf'
-TELEGRAF_CONF = '/run/telegraf/vyos-telegraf.conf'
+TELEGRAF_CONF = '/run/telegraf/telegraf.conf'
base_path = ['service', 'monitoring', 'telegraf']
org = 'log@in.local'
token = 'GuRJc12tIzfjnYdKRAIYbxdWd2aTpOT9PVYNddzDnFV4HkAcD7u7-kndTFXjGuXzJN6TTxmrvPODB4mnFcseDV=='
diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py
index bda279342..8572d6d66 100755
--- a/smoketest/scripts/cli/test_vpn_openconnect.py
+++ b/smoketest/scripts/cli/test_vpn_openconnect.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
@@ -19,6 +19,7 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.util import process_named_running
+from vyos.util import read_file
OCSERV_CONF = '/run/ocserv/ocserv.conf'
base_path = ['vpn', 'openconnect']
@@ -46,36 +47,84 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx
u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww
"""
-class TestVpnOpenconnect(VyOSUnitTestSHIM.TestCase):
+PROCESS_NAME = 'ocserv-main'
+config_file = '/run/ocserv/ocserv.conf'
+auth_file = '/run/ocserv/ocpasswd'
+otp_file = '/run/ocserv/users.oath'
+
+class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestVPNOpenConnect, 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, pki_path + ['ca', 'openconnect', 'certificate', cert_data.replace('\n','')])
+ cls.cli_set(cls, pki_path + ['certificate', 'openconnect', 'certificate', cert_data.replace('\n','')])
+ cls.cli_set(cls, pki_path + ['certificate', 'openconnect', 'private', 'key', key_data.replace('\n','')])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, pki_path)
+ super(TestVPNOpenConnect, cls).tearDownClass()
+
def tearDown(self):
- # Delete vpn openconnect configuration
- self.cli_delete(pki_path)
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
self.cli_delete(base_path)
self.cli_commit()
- def test_vpn(self):
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_ocserv(self):
user = 'vyos_user'
password = 'vyos_pass'
otp = '37500000026900000000200000000000'
-
- self.cli_delete(pki_path)
- self.cli_delete(base_path)
-
- self.cli_set(pki_path + ['ca', 'openconnect', 'certificate', cert_data.replace('\n','')])
- self.cli_set(pki_path + ['certificate', 'openconnect', 'certificate', cert_data.replace('\n','')])
- self.cli_set(pki_path + ['certificate', 'openconnect', 'private', 'key', key_data.replace('\n','')])
+ v4_subnet = '192.0.2.0/24'
+ v6_prefix = '2001:db8:1000::/64'
+ v6_len = '126'
+ name_server = ['1.2.3.4', '1.2.3.5', '2001:db8::1']
+ split_dns = ['vyos.net', 'vyos.io']
self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'password', password])
self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'otp', 'key', otp])
self.cli_set(base_path + ['authentication', 'mode', 'local', 'password-otp'])
- self.cli_set(base_path + ['network-settings', 'client-ip-settings', 'subnet', '192.0.2.0/24'])
+
+ self.cli_set(base_path + ['network-settings', 'client-ip-settings', 'subnet', v4_subnet])
+ self.cli_set(base_path + ['network-settings', 'client-ipv6-pool', 'prefix', v6_prefix])
+ self.cli_set(base_path + ['network-settings', 'client-ipv6-pool', 'mask', v6_len])
+
+ for ns in name_server:
+ self.cli_set(base_path + ['network-settings', 'name-server', ns])
+ for domain in split_dns:
+ self.cli_set(base_path + ['network-settings', 'split-dns', domain])
+
self.cli_set(base_path + ['ssl', 'ca-certificate', 'openconnect'])
self.cli_set(base_path + ['ssl', 'certificate', 'openconnect'])
self.cli_commit()
- # Check for running process
- self.assertTrue(process_named_running('ocserv-main'))
+ # Verify configuration
+ daemon_config = read_file(config_file)
+
+ # authentication mode local password-otp
+ self.assertIn(f'auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]"', daemon_config)
+ self.assertIn(f'ipv4-network = {v4_subnet}', daemon_config)
+ self.assertIn(f'ipv6-network = {v6_prefix}', daemon_config)
+ self.assertIn(f'ipv6-subnet-prefix = {v6_len}', daemon_config)
+
+ for ns in name_server:
+ self.assertIn(f'dns = {ns}', daemon_config)
+ for domain in split_dns:
+ self.assertIn(f'split-dns = {domain}', daemon_config)
+
+ auth_config = read_file(auth_file)
+ self.assertIn(f'{user}:*:$', auth_config)
+
+ otp_config = read_file(otp_file)
+ self.assertIn(f'HOTP/T30/6 {user} - {otp}', otp_config)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py
index 1cd8f5451..7dc5206e0 100755
--- a/src/conf_mode/arp.py
+++ b/src/conf_mode/arp.py
@@ -61,7 +61,7 @@ def apply(arp):
continue
for address, address_config in interface_config['address'].items():
mac = address_config['mac']
- call(f'ip neigh add {address} lladdr {mac} dev {interface}')
+ call(f'ip neigh replace {address} lladdr {mac} dev {interface}')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index 3057357fc..7cd7ea42e 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.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
@@ -29,6 +29,8 @@ from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.util import call
+from vyos.util import check_port_availability
+from vyos.util import is_listen_port_bind_service
from vyos.util import write_file
from vyos import airbag
@@ -107,6 +109,31 @@ def verify(https):
raise ConfigError("At least one 'virtual-host <id> server-name' "
"matching the 'certbot domain-name' is required.")
+ server_block_list = []
+
+ # organize by vhosts
+ vhost_dict = https.get('virtual-host', {})
+
+ if not vhost_dict:
+ # no specified virtual hosts (server blocks); use default
+ server_block_list.append(default_server_block)
+ else:
+ for vhost in list(vhost_dict):
+ server_block = deepcopy(default_server_block)
+ data = vhost_dict.get(vhost, {})
+ server_block['address'] = data.get('listen-address', '*')
+ server_block['port'] = data.get('listen-port', '443')
+ server_block_list.append(server_block)
+
+ for entry in server_block_list:
+ _address = entry.get('address')
+ _address = '0.0.0.0' if _address == '*' else _address
+ _port = entry.get('port')
+ proto = 'tcp'
+ if check_port_availability(_address, int(_port), proto) is not True and \
+ not is_listen_port_bind_service(int(_port), 'nginx'):
+ raise ConfigError(f'"{proto}" port "{_port}" is used by another service')
+
verify_vrf(https)
return None
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 30e7a2af7..e02841831 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -153,11 +153,20 @@ def verify(ethernet):
return None
def generate(ethernet):
- if 'eapol' in ethernet:
- render(wpa_suppl_conf.format(**ethernet),
- 'ethernet/wpa_supplicant.conf.j2', ethernet)
+ # render real configuration file once
+ wpa_supplicant_conf = wpa_suppl_conf.format(**ethernet)
+
+ if 'deleted' in ethernet:
+ # delete configuration on interface removal
+ if os.path.isfile(wpa_supplicant_conf):
+ os.unlink(wpa_supplicant_conf)
+ return None
+ if 'eapol' in ethernet:
ifname = ethernet['ifname']
+
+ render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', ethernet)
+
cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem')
cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key')
@@ -184,10 +193,6 @@ def generate(ethernet):
write_file(ca_cert_file_path,
'\n'.join(encode_certificate(c) for c in ca_full_chain))
- else:
- # delete configuration on interface removal
- if os.path.isfile(wpa_suppl_conf.format(**ethernet)):
- os.unlink(wpa_suppl_conf.format(**ethernet))
return None
@@ -203,9 +208,9 @@ def apply(ethernet):
else:
e.update(ethernet)
if 'eapol' in ethernet:
- eapol_action='restart'
+ eapol_action='reload-or-restart'
- call(f'systemctl {eapol_action} wpa_supplicant-macsec@{ifname}')
+ call(f'systemctl {eapol_action} wpa_supplicant-wired@{ifname}')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py
index 870049a88..649ea8d50 100755
--- a/src/conf_mode/interfaces-macsec.py
+++ b/src/conf_mode/interfaces-macsec.py
@@ -67,7 +67,7 @@ def get_config(config=None):
macsec.update({'shutdown_required': {}})
if 'source_interface' in macsec:
- tmp = is_source_interface(conf, macsec['source_interface'], 'macsec')
+ tmp = is_source_interface(conf, macsec['source_interface'], ['macsec', 'pseudo-ethernet'])
if tmp and tmp != ifname: macsec.update({'is_source_interface' : tmp})
return macsec
@@ -102,12 +102,6 @@ def verify(macsec):
# gcm-aes-128 requires a 128bit long key - 64 characters (string) = 32byte = 256bit
raise ConfigError('gcm-aes-128 requires a 256bit long key!')
- if 'is_source_interface' in macsec:
- tmp = macsec['is_source_interface']
- src_ifname = macsec['source_interface']
- raise ConfigError(f'Can not use source-interface "{src_ifname}", it already ' \
- f'belongs to interface "{tmp}"!')
-
if 'source_interface' in macsec:
# MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad
# and 802.1q) - we need to check the underlaying MTU if our configured
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index f26a50a0e..20f2b1975 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 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
@@ -19,6 +19,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
+from vyos.configdict import is_source_interface
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
@@ -51,6 +52,10 @@ def get_config(config=None):
if 'source_interface' in peth:
_, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'],
peth['source_interface'])
+ # test if source-interface is maybe already used by another interface
+ tmp = is_source_interface(conf, peth['source_interface'], ['macsec'])
+ if tmp and tmp != ifname: peth.update({'is_source_interface' : tmp})
+
return peth
def verify(peth):
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 0972151a0..f64102d88 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -125,7 +125,8 @@ def verify(nat):
if addr != 'masquerade' and not is_ipv6(addr):
raise ConfigError(f'IPv6 address {addr} is not a valid address')
else:
- raise ConfigError(f'{err_msg} translation address not specified')
+ if 'exclude' not in config:
+ raise ConfigError(f'{err_msg} translation address not specified')
prefix = dict_search('source.prefix', config)
if prefix != None:
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index 5490a794d..0ecb4d736 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -17,6 +17,7 @@
import os
from vyos.config import Config
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_interface_exists
from vyos.util import call
@@ -40,6 +41,10 @@ def get_config(config=None):
ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
ntp['config_file'] = config_file
+
+ tmp = is_node_changed(conf, base + ['vrf'])
+ if tmp: ntp.update({'restart_required': {}})
+
return ntp
def verify(ntp):
@@ -78,19 +83,25 @@ def generate(ntp):
return None
def apply(ntp):
+ systemd_service = 'ntp.service'
+ # Reload systemd manager configuration
+ call('systemctl daemon-reload')
+
if not ntp:
# NTP support is removed in the commit
- call('systemctl stop ntp.service')
+ call(f'systemctl stop {systemd_service}')
if os.path.exists(config_file):
os.unlink(config_file)
if os.path.isfile(systemd_override):
os.unlink(systemd_override)
+ return
- # Reload systemd manager configuration
- call('systemctl daemon-reload')
- if ntp:
- call('systemctl restart ntp.service')
+ # we need to restart the service if e.g. the VRF name changed
+ systemd_action = 'reload-or-restart'
+ if 'restart_required' in ntp:
+ systemd_action = 'restart'
+ call(f'systemctl {systemd_action} {systemd_service}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 7d3687094..87456f00b 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -121,8 +121,8 @@ def verify(bgp):
'dependent VRF instance(s) exist!')
return None
- if 'local_as' not in bgp:
- raise ConfigError('BGP local-as number must be defined!')
+ if 'system_as' not in bgp:
+ raise ConfigError('BGP system-as number must be defined!')
# Common verification for both peer-group and neighbor statements
for neighbor in ['neighbor', 'peer_group']:
@@ -147,8 +147,8 @@ def verify(bgp):
# Neighbor local-as override can not be the same as the local-as
# we use for this BGP instane!
asn = list(peer_config['local_as'].keys())[0]
- if asn == bgp['local_as']:
- raise ConfigError('Cannot have local-as same as BGP AS number')
+ if asn == bgp['system_as']:
+ raise ConfigError('Cannot have local-as same as system-as number')
# Neighbor AS specified for local-as and remote-as can not be the same
if dict_search('remote_as', peer_config) == asn:
@@ -216,7 +216,7 @@ def verify(bgp):
# Local-AS allowed only for EBGP peers
if 'local_as' in peer_config:
remote_as = verify_remote_as(peer_config, bgp)
- if remote_as == bgp['local_as']:
+ if remote_as == bgp['system_as']:
raise ConfigError(f'local-as configured for "{peer}", allowed only for eBGP peers!')
for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec',
@@ -279,12 +279,12 @@ def verify(bgp):
verify_route_map(afi_config['route_map'][tmp], bgp)
if 'route_reflector_client' in afi_config:
- if 'remote_as' in peer_config and peer_config['remote_as'] != 'internal' and peer_config['remote_as'] != bgp['local_as']:
+ if 'remote_as' in peer_config and peer_config['remote_as'] != 'internal' and peer_config['remote_as'] != bgp['system_as']:
raise ConfigError('route-reflector-client only supported for iBGP peers')
else:
if 'peer_group' in peer_config:
peer_group_as = dict_search(f'peer_group.{peer_group}.remote_as', bgp)
- if peer_group_as != None and peer_group_as != 'internal' and peer_group_as != bgp['local_as']:
+ if peer_group_as != None and peer_group_as != 'internal' and peer_group_as != bgp['system_as']:
raise ConfigError('route-reflector-client only supported for iBGP peers')
# Throw an error if a peer group is not configured for allow range
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 62f5e1ddf..18b32edab 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -22,6 +22,8 @@ from shutil import rmtree
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_vrf
from vyos.ifconfig import Section
from vyos.template import render
from vyos.util import call
@@ -32,20 +34,14 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-
-base_dir = '/run/telegraf'
cache_dir = f'/etc/telegraf/.cache'
-config_telegraf = f'{base_dir}/vyos-telegraf.conf'
+config_telegraf = f'/run/telegraf/telegraf.conf'
custom_scripts_dir = '/etc/telegraf/custom_scripts'
syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf'
-systemd_telegraf_service = '/etc/systemd/system/vyos-telegraf.service'
-systemd_telegraf_override_dir = '/etc/systemd/system/vyos-telegraf.service.d'
-systemd_override = f'{systemd_telegraf_override_dir}/10-override.conf'
-
+systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf'
def get_interfaces(type='', vlan=True):
"""
- Get interfaces
get_interfaces()
['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
@@ -62,9 +58,7 @@ def get_interfaces(type='', vlan=True):
return interfaces
def get_nft_filter_chains():
- """
- Get nft chains for table filter
- """
+ """ Get nft chains for table filter """
nft = cmd('nft --json list table ip filter')
nft = json.loads(nft)
chain_list = []
@@ -78,7 +72,6 @@ def get_nft_filter_chains():
def get_config(config=None):
-
if config:
conf = config
else:
@@ -87,8 +80,12 @@ def get_config(config=None):
if not conf.exists(base):
return None
- monitoring = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
+ monitoring = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ tmp = is_node_changed(conf, base + ['vrf'])
+ if tmp: monitoring.update({'restart_required': {}})
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
@@ -131,6 +128,8 @@ def verify(monitoring):
if not monitoring:
return None
+ verify_vrf(monitoring)
+
# Verify influxdb
if 'influxdb' in monitoring:
if 'authentication' not in monitoring['influxdb'] or \
@@ -173,7 +172,7 @@ def verify(monitoring):
def generate(monitoring):
if not monitoring:
# Delete config and systemd files
- config_files = [config_telegraf, systemd_telegraf_service, systemd_override, syslog_telegraf]
+ config_files = [config_telegraf, systemd_override, syslog_telegraf]
for file in config_files:
if os.path.isfile(file):
os.unlink(file)
@@ -190,33 +189,34 @@ def generate(monitoring):
chown(cache_dir, 'telegraf', 'telegraf')
- # Create systemd override dir
- if not os.path.exists(systemd_telegraf_override_dir):
- os.mkdir(systemd_telegraf_override_dir)
-
# Create custome scripts dir
if not os.path.exists(custom_scripts_dir):
os.mkdir(custom_scripts_dir)
# Render telegraf configuration and systemd override
- render(config_telegraf, 'monitoring/telegraf.j2', monitoring)
- render(systemd_telegraf_service, 'monitoring/systemd_vyos_telegraf_service.j2', monitoring)
- render(systemd_override, 'monitoring/override.conf.j2', monitoring, permission=0o640)
- render(syslog_telegraf, 'monitoring/syslog_telegraf.j2', monitoring)
-
- chown(base_dir, 'telegraf', 'telegraf')
+ render(config_telegraf, 'telegraf/telegraf.j2', monitoring, user='telegraf', group='telegraf')
+ render(systemd_override, 'telegraf/override.conf.j2', monitoring)
+ render(syslog_telegraf, 'telegraf/syslog_telegraf.j2', monitoring)
return None
def apply(monitoring):
# Reload systemd manager configuration
+ systemd_service = 'telegraf.service'
call('systemctl daemon-reload')
- if monitoring:
- call('systemctl restart vyos-telegraf.service')
- else:
- call('systemctl stop vyos-telegraf.service')
+ if not monitoring:
+ call(f'systemctl stop {systemd_service}')
+ return
+
+ # we need to restart the service if e.g. the VRF name changed
+ systemd_action = 'reload-or-restart'
+ if 'restart_required' in monitoring:
+ systemd_action = 'restart'
+
+ call(f'systemctl {systemd_action} {systemd_service}')
+
# Telegraf include custom rsyslog config changes
- call('systemctl restart rsyslog')
+ call('systemctl reload-or-restart rsyslog')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py
index 36f3e18a7..c798fd515 100755
--- a/src/conf_mode/service_upnp.py
+++ b/src/conf_mode/service_upnp.py
@@ -24,8 +24,6 @@ from ipaddress import IPv6Network
from vyos.config import Config
from vyos.configdict import dict_merge
-from vyos.configdict import get_interface_dict
-from vyos.configverify import verify_vrf
from vyos.util import call
from vyos.template import render
from vyos.template import is_ipv4
@@ -113,19 +111,28 @@ def verify(upnpd):
listen_dev = []
system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6])
system_addrs = get_all_interface_addr(False, [], [netifaces.AF_INET, netifaces.AF_INET6])
+ if 'listen' not in upnpd:
+ raise ConfigError(f'Listen address or interface is required!')
for listen_if_or_addr in upnpd['listen']:
if listen_if_or_addr not in netifaces.interfaces():
listen_dev.append(listen_if_or_addr)
- if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and (listen_if_or_addr not in netifaces.interfaces()):
+ if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and \
+ (listen_if_or_addr not in netifaces.interfaces()):
if is_ipv4(listen_if_or_addr) and IPv4Network(listen_if_or_addr).is_multicast:
- raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!')
+ raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed'
+ f'to listen on. It is not an interface address nor a multicast address!')
if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast:
- raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!')
+ raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed'
+ f'to listen on. It is not an interface address nor a multicast address!')
system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6])
system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6])
for listen_if_or_addr in upnpd['listen']:
- if listen_if_or_addr not in netifaces.interfaces() and (listen_if_or_addr not in system_listening_dev_addrs_cidr) and (listen_if_or_addr not in system_listening_dev_addrs) and is_ipv6(listen_if_or_addr) and (not IPv6Network(listen_if_or_addr).is_multicast):
+ if listen_if_or_addr not in netifaces.interfaces() and \
+ (listen_if_or_addr not in system_listening_dev_addrs_cidr) and \
+ (listen_if_or_addr not in system_listening_dev_addrs) and \
+ is_ipv6(listen_if_or_addr) and \
+ (not IPv6Network(listen_if_or_addr).is_multicast):
raise ConfigError(f'{listen_if_or_addr} must listen on the interface of the network card')
def generate(upnpd):
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 28669694b..2bbd7142a 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -22,6 +22,7 @@ from syslog import LOG_INFO
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.util import call
from vyos.template import render
@@ -50,6 +51,10 @@ def get_config(config=None):
return None
ssh = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ tmp = is_node_changed(conf, base + ['vrf'])
+ if tmp: ssh.update({'restart_required': {}})
+
# 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)
@@ -104,17 +109,25 @@ def generate(ssh):
return None
def apply(ssh):
+ systemd_service_ssh = 'ssh.service'
+ systemd_service_sshguard = 'sshguard.service'
if not ssh:
# SSH access is removed in the commit
- call('systemctl stop ssh.service')
- call('systemctl stop sshguard.service')
+ call(f'systemctl stop {systemd_service_ssh}')
+ call(f'systemctl stop {systemd_service_sshguard}')
return None
+
if 'dynamic_protection' not in ssh:
- call('systemctl stop sshguard.service')
+ call(f'systemctl stop {systemd_service_sshguard}')
else:
- call('systemctl restart sshguard.service')
+ call(f'systemctl reload-or-restart {systemd_service_sshguard}')
+
+ # we need to restart the service if e.g. the VRF name changed
+ systemd_action = 'reload-or-restart'
+ if 'restart_required' in ssh:
+ systemd_action = 'restart'
- call('systemctl restart ssh.service')
+ call(f'systemctl {systemd_action} {systemd_service_ssh}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index bad9cfbd8..5ca32d23e 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -595,13 +595,11 @@ def wait_for_vici_socket(timeout=5, sleep_interval=0.1):
sleep(sleep_interval)
def apply(ipsec):
+ systemd_service = 'strongswan-starter.service'
if not ipsec:
- call('sudo ipsec stop')
+ call(f'systemctl stop {systemd_service}')
else:
- call('sudo ipsec restart')
- call('sudo ipsec rereadall')
- call('sudo ipsec reload')
-
+ call(f'systemctl reload-or-restart {systemd_service}')
if wait_for_vici_socket():
call('sudo swanctl -q')
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 8e0e30bbf..240546817 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.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
@@ -23,7 +23,9 @@ from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.util import call
+from vyos.util import check_port_availability
from vyos.util import is_systemd_service_running
+from vyos.util import is_listen_port_bind_service
from vyos.util import dict_search
from vyos.xml import defaults
from vyos import ConfigError
@@ -75,6 +77,12 @@ def get_config():
def verify(ocserv):
if ocserv is None:
return None
+ # Check if listen-ports not binded other services
+ # It can be only listen by 'ocserv-main'
+ for proto, port in ocserv.get('listen_ports').items():
+ if check_port_availability('0.0.0.0', int(port), proto) is not True and \
+ not is_listen_port_bind_service(int(port), 'ocserv-main'):
+ raise ConfigError(f'"{proto}" port "{port}" is used by another service')
# Check authentication
if "authentication" in ocserv:
if "mode" in ocserv["authentication"]:
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index 23e5162ba..2949ab290 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -26,7 +26,9 @@ from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.util import call
+from vyos.util import check_port_availability
from vyos.util import dict_search
+from vyos.util import is_listen_port_bind_service
from vyos.util import write_file
from vyos import ConfigError
from vyos import airbag
@@ -62,6 +64,12 @@ def verify(sstp):
if not sstp:
return None
+ port = sstp.get('port')
+ proto = 'tcp'
+ if check_port_availability('0.0.0.0', int(port), proto) is not True and \
+ not is_listen_port_bind_service(int(port), 'accel-pppd'):
+ raise ConfigError(f'"{proto}" port "{port}" is used by another service')
+
verify_accel_ppp_base_service(sstp)
if 'client_ip_pool' not in sstp and 'client_ipv6_pool' not in sstp:
diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py
index 8274e6564..a5293c97e 100755
--- a/src/etc/opennhrp/opennhrp-script.py
+++ b/src/etc/opennhrp/opennhrp-script.py
@@ -18,44 +18,120 @@ import os
import re
import sys
import vici
+
from json import loads
+from pathlib import Path
+from vyos.logger import getLogger
from vyos.util import cmd
from vyos.util import process_named_running
-NHRP_CONFIG = "/run/opennhrp/opennhrp.conf"
+NHRP_CONFIG: str = '/run/opennhrp/opennhrp.conf'
+
+
+def vici_get_ipsec_uniqueid(conn: str, src_nbma: str,
+ dst_nbma: str) -> list[str]:
+ """ Find and return IKE SAs by src nbma and dst nbma
+
+ Args:
+ conn (str): a connection name
+ src_nbma (str): an IP address of NBMA source
+ dst_nbma (str): an IP address of NBMA destination
+
+ Returns:
+ list: a list of IKE connections that match a criteria
+ """
+ if not conn or not src_nbma or not dst_nbma:
+ logger.error(
+ f'Incomplete input data for resolving IKE unique ids: '
+ f'conn: {conn}, src_nbma: {src_nbma}, dst_nbma: {dst_nbma}')
+ return []
+
+ try:
+ logger.info(
+ f'Resolving IKE unique ids for: conn: {conn}, '
+ f'src_nbma: {src_nbma}, dst_nbma: {dst_nbma}')
+ session: vici.Session = vici.Session()
+ list_ikeid: list[str] = []
+ list_sa = session.list_sas({'ike': conn})
+ for sa in list_sa:
+ if sa[conn]['local-host'].decode('ascii') == src_nbma \
+ and sa[conn]['remote-host'].decode('ascii') == dst_nbma:
+ list_ikeid.append(sa[conn]['uniqueid'].decode('ascii'))
+ return list_ikeid
+ except Exception as err:
+ logger.error(f'Unable to find unique ids for IKE: {err}')
+ return []
+
+
+def vici_ike_terminate(list_ikeid: list[str]) -> bool:
+ """Terminating IKE SAs by list of IKE IDs
+
+ Args:
+ list_ikeid (list[str]): a list of IKE ids to terminate
+
+ Returns:
+ bool: result of termination action
+ """
+ if not list:
+ logger.warning('An empty list for termination was provided')
+ return False
+
+ try:
+ session = vici.Session()
+ for ikeid in list_ikeid:
+ logger.info(f'Terminating IKE SA with id {ikeid}')
+ session.terminate({'ike-id': ikeid, 'timeout': '-1'})
+ return True
+ except Exception as err:
+ logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}')
+ return False
+
+def parse_type_ipsec(interface: str) -> tuple[str, str]:
+ """Get DMVPN Type and NHRP Profile from the configuration
-def parse_type_ipsec(interface):
- with open(NHRP_CONFIG, 'r') as f:
- lines = f.readlines()
- match = rf'^interface {interface} #(hub|spoke)(?:\s([\w-]+))?$'
- for line in lines:
- m = re.match(match, line)
- if m:
- return m[1], m[2]
- return None, None
+ Args:
+ interface (str): a name of interface
+
+ Returns:
+ tuple[str, str]: `peer_type` and `profile_name`
+ """
+ if not interface:
+ logger.error('Cannot find peer type - no input provided')
+ return '', ''
+
+ config_file: str = Path(NHRP_CONFIG).read_text()
+ regex: str = rf'^interface {interface} #(?P<peer_type>hub|spoke) ?(?P<profile_name>[^\n]*)$'
+ match = re.search(regex, config_file, re.M)
+ if match:
+ return match.groupdict()['peer_type'], match.groupdict()[
+ 'profile_name']
+ return '', ''
def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None:
"""Add a route to a NBMA peer
Args:
- nmba_src (str): a local IP address
+ nbma_src (str): a local IP address
nbma_dst (str): a remote IP address
mtu (str): a MTU for a route
"""
+ logger.info(f'Adding route from {nbma_src} to {nbma_dst} with MTU {mtu}')
# Find routes to a peer
- route_get_cmd = f'sudo ip -j route get {nbma_dst} from {nbma_src}'
+ route_get_cmd: str = f'sudo ip --json route get {nbma_dst} from {nbma_src}'
try:
route_info_data = loads(cmd(route_get_cmd))
except Exception as err:
- print(f'Unable to find a route to {nbma_dst}: {err}')
+ logger.error(f'Unable to find a route to {nbma_dst}: {err}')
+ return
# Check if an output has an expected format
if not isinstance(route_info_data, list):
- print(f'Garbage returned from the "{route_get_cmd}" command: \
- {route_info_data}')
+ logger.error(
+ f'Garbage returned from the "{route_get_cmd}" '
+ f'command: {route_info_data}')
return
# Add static routes to a peer
@@ -76,104 +152,217 @@ def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None:
try:
cmd(route_add_cmd)
except Exception as err:
- print(f'Unable to add a route using command "{route_add_cmd}": \
- {err}')
+ logger.error(
+ f'Unable to add a route using command "{route_add_cmd}": '
+ f'{err}')
-def vici_initiate(conn, child_sa, src_addr, dest_addr):
- try:
- session = vici.Session()
- logs = session.initiate({
- 'ike': conn,
- 'child': child_sa,
- 'timeout': '-1',
- 'my-host': src_addr,
- 'other-host': dest_addr
- })
- for log in logs:
- message = log['msg'].decode('ascii')
- print('INIT LOG:', message)
- return True
- except:
- return None
+def vici_initiate(conn: str, child_sa: str, src_addr: str,
+ dest_addr: str) -> bool:
+ """Initiate IKE SA connection with specific peer
+ Args:
+ conn (str): an IKE connection name
+ child_sa (str): a child SA profile name
+ src_addr (str): NBMA local address
+ dest_addr (str): NBMA address of a peer
-def vici_terminate(conn, child_sa, src_addr, dest_addr):
+ Returns:
+ bool: a result of initiation command
+ """
+ logger.info(
+ f'Trying to initiate connection. Name: {conn}, child sa: {child_sa}, '
+ f'src_addr: {src_addr}, dst_addr: {dest_addr}')
try:
session = vici.Session()
- logs = session.terminate({
+ session.initiate({
'ike': conn,
'child': child_sa,
'timeout': '-1',
'my-host': src_addr,
'other-host': dest_addr
})
- for log in logs:
- message = log['msg'].decode('ascii')
- print('TERM LOG:', message)
return True
- except:
- return None
+ except Exception as err:
+ logger.error(f'Unable to initiate connection {err}')
+ return False
+
+
+def vici_terminate(conn: str, src_addr: str, dest_addr: str) -> None:
+ """Find and terminate IKE SAs by local NBMA and remote NBMA addresses
+
+ Args:
+ conn (str): IKE connection name
+ src_addr (str): NBMA local address
+ dest_addr (str): NBMA address of a peer
+ """
+ logger.info(
+ f'Terminating IKE connection {conn} between {src_addr} '
+ f'and {dest_addr}')
+ ikeid_list: list[str] = vici_get_ipsec_uniqueid(conn, src_addr, dest_addr)
-def iface_up(interface):
- cmd(f'sudo ip route flush proto 42 dev {interface}')
- cmd(f'sudo ip neigh flush dev {interface}')
+ if not ikeid_list:
+ logger.warning(
+ f'No active sessions found for IKE profile {conn}, '
+ f'local NBMA {src_addr}, remote NBMA {dest_addr}')
+ else:
+ vici_ike_terminate(ikeid_list)
-def peer_up(dmvpn_type, conn):
- # src_addr = os.getenv('NHRP_SRCADDR')
+def iface_up(interface: str) -> None:
+ """Proceed tunnel interface UP event
+
+ Args:
+ interface (str): an interface name
+ """
+ if not interface:
+ logger.warning('No interface name provided for UP event')
+
+ logger.info(f'Turning up interface {interface}')
+ try:
+ cmd(f'sudo ip route flush proto 42 dev {interface}')
+ cmd(f'sudo ip neigh flush dev {interface}')
+ except Exception as err:
+ logger.error(
+ f'Unable to flush route on interface "{interface}": {err}')
+
+
+def peer_up(dmvpn_type: str, conn: str) -> None:
+ """Proceed NHRP peer UP event
+
+ Args:
+ dmvpn_type (str): a type of peer
+ conn (str): an IKE profile name
+ """
+ logger.info(f'Peer UP event for {dmvpn_type} using IKE profile {conn}')
src_nbma = os.getenv('NHRP_SRCNBMA')
- # dest_addr = os.getenv('NHRP_DESTADDR')
dest_nbma = os.getenv('NHRP_DESTNBMA')
dest_mtu = os.getenv('NHRP_DESTMTU')
+ if not src_nbma or not dest_nbma:
+ logger.error(
+ f'Can not get NHRP NBMA addresses: local {src_nbma}, '
+ f'remote {dest_nbma}')
+ return
+
+ logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}')
if dest_mtu:
add_peer_route(src_nbma, dest_nbma, dest_mtu)
-
if conn and dmvpn_type == 'spoke' and process_named_running('charon'):
- vici_terminate(conn, 'dmvpn', src_nbma, dest_nbma)
+ vici_terminate(conn, src_nbma, dest_nbma)
vici_initiate(conn, 'dmvpn', src_nbma, dest_nbma)
-def peer_down(dmvpn_type, conn):
+def peer_down(dmvpn_type: str, conn: str) -> None:
+ """Proceed NHRP peer DOWN event
+
+ Args:
+ dmvpn_type (str): a type of peer
+ conn (str): an IKE profile name
+ """
+ logger.info(f'Peer DOWN event for {dmvpn_type} using IKE profile {conn}')
+
src_nbma = os.getenv('NHRP_SRCNBMA')
dest_nbma = os.getenv('NHRP_DESTNBMA')
+ if not src_nbma or not dest_nbma:
+ logger.error(
+ f'Can not get NHRP NBMA addresses: local {src_nbma}, '
+ f'remote {dest_nbma}')
+ return
+
+ logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}')
if conn and dmvpn_type == 'spoke' and process_named_running('charon'):
- vici_terminate(conn, 'dmvpn', src_nbma, dest_nbma)
+ vici_terminate(conn, src_nbma, dest_nbma)
+ try:
+ cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42')
+ except Exception as err:
+ logger.error(
+ f'Unable to del route from {src_nbma} to {dest_nbma}: {err}')
- cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42')
+def route_up(interface: str) -> None:
+ """Proceed NHRP route UP event
+
+ Args:
+ interface (str): an interface name
+ """
+ logger.info(f'Route UP event for interface {interface}')
-def route_up(interface):
dest_addr = os.getenv('NHRP_DESTADDR')
dest_prefix = os.getenv('NHRP_DESTPREFIX')
next_hop = os.getenv('NHRP_NEXTHOP')
- cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 \
- via {next_hop} dev {interface}')
- cmd('sudo ip route flush cache')
+ if not dest_addr or not dest_prefix or not next_hop:
+ logger.error(
+ f'Can not get route details: dest_addr {dest_addr}, '
+ f'dest_prefix {dest_prefix}, next_hop {next_hop}')
+ return
+
+ logger.info(
+ f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}, '
+ f'next_hop {next_hop}')
+ try:
+ cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 \
+ via {next_hop} dev {interface}')
+ cmd('sudo ip route flush cache')
+ except Exception as err:
+ logger.error(
+ f'Unable replace or flush route to {dest_addr}/{dest_prefix} '
+ f'via {next_hop} dev {interface}: {err}')
+
+
+def route_down(interface: str) -> None:
+ """Proceed NHRP route DOWN event
+
+ Args:
+ interface (str): an interface name
+ """
+ logger.info(f'Route DOWN event for interface {interface}')
-def route_down(interface):
dest_addr = os.getenv('NHRP_DESTADDR')
dest_prefix = os.getenv('NHRP_DESTPREFIX')
- cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42')
- cmd('sudo ip route flush cache')
+ if not dest_addr or not dest_prefix:
+ logger.error(
+ f'Can not get route details: dest_addr {dest_addr}, '
+ f'dest_prefix {dest_prefix}')
+ return
+
+ logger.info(
+ f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}')
+ try:
+ cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42')
+ cmd('sudo ip route flush cache')
+ except Exception as err:
+ logger.error(
+ f'Unable delete or flush route to {dest_addr}/{dest_prefix}: '
+ f'{err}')
if __name__ == '__main__':
+ logger = getLogger('opennhrp-script', syslog=True)
+ logger.debug(
+ f'Running script with arguments: {sys.argv}, '
+ f'environment: {os.environ}')
+
action = sys.argv[1]
interface = os.getenv('NHRP_INTERFACE')
- dmvpn_type, profile_name = parse_type_ipsec(interface)
- dmvpn_conn = None
+ if not interface:
+ logger.error('Can not get NHRP interface name')
+ sys.exit(1)
- if profile_name:
- dmvpn_conn = f'dmvpn-{profile_name}-{interface}'
+ dmvpn_type, profile_name = parse_type_ipsec(interface)
+ if not dmvpn_type:
+ logger.info(f'Interface {interface} is not NHRP tunnel')
+ sys.exit()
+ dmvpn_conn: str = ''
+ if profile_name:
+ dmvpn_conn: str = f'dmvpn-{profile_name}-{interface}'
if action == 'interface-up':
iface_up(interface)
elif action == 'peer-register':
@@ -186,3 +375,5 @@ if __name__ == '__main__':
route_up(interface)
elif action == 'route-down':
route_down(interface)
+
+ sys.exit()
diff --git a/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf b/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf
new file mode 100644
index 000000000..030b89a2b
--- /dev/null
+++ b/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf
@@ -0,0 +1,11 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=
+WorkingDirectory=/run/wpa_supplicant
+PIDFile=/run/wpa_supplicant/%I.pid
+ExecStart=
+ExecStart=/sbin/wpa_supplicant -c/run/wpa_supplicant/%I.conf -Dwired -P/run/wpa_supplicant/%I.pid -i%I
+ExecReload=/bin/kill -HUP $MAINPID
diff --git a/src/migration-scripts/bgp/2-to-3 b/src/migration-scripts/bgp/2-to-3
new file mode 100755
index 000000000..7ced0a3b0
--- /dev/null
+++ b/src/migration-scripts/bgp/2-to-3
@@ -0,0 +1,51 @@
+#!/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/>.
+
+# T4257: Discussion on changing BGP autonomous system number syntax
+
+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()
+
+config = ConfigTree(config_file)
+
+# Check if BGP is even configured. Then check if local-as exists, then add the system-as, then remove the local-as. This is for global configuration.
+if config.exists(['protocols', 'bgp']):
+ if config.exists(['protocols', 'bgp', 'local-as']):
+ config.rename(['protocols', 'bgp', 'local-as'], 'system-as')
+
+# Check if vrf names are configured. Then check if local-as exists inside of a name, then add the system-as, then remove the local-as. This is for vrf configuration.
+if config.exists(['vrf', 'name']):
+ for vrf in config.list_nodes(['vrf', 'name']):
+ if config.exists(['vrf', f'name {vrf}', 'protocols', 'bgp', 'local-as']):
+ config.rename(['vrf', f'name {vrf}', 'protocols', 'bgp', 'local-as'], 'system-as')
+
+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/op_mode/bridge.py b/src/op_mode/bridge.py
index 411aa06d1..fe8dadd70 100755
--- a/src/op_mode/bridge.py
+++ b/src/op_mode/bridge.py
@@ -197,6 +197,6 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py
index 1441d110f..b27aa6060 100755
--- a/src/op_mode/conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -51,6 +51,21 @@ def _get_raw_data(family):
return _xml_to_dict(xml)
+def _get_raw_statistics():
+ entries = []
+ data = cmd('sudo conntrack -S')
+ data = data.replace(' \t', '').split('\n')
+ for entry in data:
+ entries.append(entry.split())
+ return entries
+
+
+def get_formatted_statistics(entries):
+ headers = ["CPU", "Found", "Invalid", "Insert", "Insert fail", "Drop", "Early drop", "Errors", "Search restart"]
+ output = tabulate(entries, headers, numalign="left")
+ return output
+
+
def get_formatted_output(dict_data):
"""
:param xml:
@@ -111,11 +126,19 @@ def show(raw: bool, family: str):
return get_formatted_output(conntrack_data)
+def show_statistics(raw: bool):
+ conntrack_statistics = _get_raw_statistics()
+ if raw:
+ return conntrack_statistics
+ else:
+ return get_formatted_statistics(conntrack_statistics)
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
index 78d42f800..ce466ffc1 100755
--- a/src/op_mode/container.py
+++ b/src/op_mode/container.py
@@ -80,6 +80,6 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/cpu.py b/src/op_mode/cpu.py
index f9c425826..d53663c17 100755
--- a/src/op_mode/cpu.py
+++ b/src/op_mode/cpu.py
@@ -76,7 +76,7 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/dns.py b/src/op_mode/dns.py
index 717652b9b..9e5b1040c 100755
--- a/src/op_mode/dns.py
+++ b/src/op_mode/dns.py
@@ -90,6 +90,6 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index caa1eef51..a4d1b4cb1 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -182,6 +182,6 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/memory.py b/src/op_mode/memory.py
index a3870e498..178544be4 100755
--- a/src/op_mode/memory.py
+++ b/src/op_mode/memory.py
@@ -84,7 +84,7 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index a98fc4227..dec04aa48 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -27,7 +27,7 @@ from vyos.util import dict_search
import vyos.opmode
-def _get_json_data(direction):
+def _get_json_data(direction, family):
"""
Get NAT format JSON
"""
@@ -35,14 +35,15 @@ def _get_json_data(direction):
chain = 'POSTROUTING'
if direction == 'destination':
chain = 'PREROUTING'
- return cmd(f'sudo nft --json list chain ip nat {chain}')
+ family = 'ip6' if family == 'inet6' else 'ip'
+ return cmd(f'sudo nft --json list chain {family} nat {chain}')
-def _get_raw_data_rules(direction):
+def _get_raw_data_rules(direction, family):
"""Get interested rules
:returns dict
"""
- data = _get_json_data(direction)
+ data = _get_json_data(direction, family)
data_dict = json.loads(data)
rules = []
for rule in data_dict['nftables']:
@@ -51,10 +52,12 @@ def _get_raw_data_rules(direction):
return rules
-def _get_formatted_output_rules(data, direction):
+def _get_formatted_output_rules(data, direction, family):
# Add default values before loop
sport, dport, proto = 'any', 'any', 'any'
- saddr, daddr = '0.0.0.0/0', '0.0.0.0/0'
+ saddr = '::/0' if family == 'inet6' else '0.0.0.0/0'
+ daddr = '::/0' if family == 'inet6' else '0.0.0.0/0'
+
data_entries = []
for rule in data:
if 'comment' in rule['rule']:
@@ -69,11 +72,13 @@ def _get_formatted_output_rules(data, direction):
if 'prefix' in match['right'] or 'set' in match['right']:
# Merge dict src/dst l3_l4 parameters
my_dict = {**match['left']['payload'], **match['right']}
+ my_dict['op'] = match['op']
+ op = '!' if my_dict.get('op') == '!=' else ''
proto = my_dict.get('protocol').upper()
if my_dict['field'] == 'saddr':
- saddr = f'{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}'
+ saddr = f'{op}{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}'
elif my_dict['field'] == 'daddr':
- daddr = f'{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}'
+ daddr = f'{op}{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}'
elif my_dict['field'] == 'sport':
# Port range or single port
if jmespath.search('set[*].range', my_dict):
@@ -96,8 +101,8 @@ def _get_formatted_output_rules(data, direction):
if jmespath.search('left.payload.field', match) == 'daddr':
daddr = match.get('right')
else:
- saddr = '0.0.0.0/0'
- daddr = '0.0.0.0/0'
+ saddr = '::/0' if family == 'inet6' else '0.0.0.0/0'
+ daddr = '::/0' if family == 'inet6' else '0.0.0.0/0'
sport = 'any'
dport = 'any'
proto = 'any'
@@ -175,16 +180,16 @@ def _get_formatted_output_statistics(data, direction):
return output
-def show_rules(raw: bool, direction: str):
- nat_rules = _get_raw_data_rules(direction)
+def show_rules(raw: bool, direction: str, family: str):
+ nat_rules = _get_raw_data_rules(direction, family)
if raw:
return nat_rules
else:
- return _get_formatted_output_rules(nat_rules, direction)
+ return _get_formatted_output_rules(nat_rules, direction, family)
-def show_statistics(raw: bool, direction: str):
- nat_statistics = _get_raw_data_rules(direction)
+def show_statistics(raw: bool, direction: str, family: str):
+ nat_statistics = _get_raw_data_rules(direction, family)
if raw:
return nat_statistics
else:
@@ -196,6 +201,6 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/neighbor.py b/src/op_mode/neighbor.py
index d86a372ac..264dbdc72 100755
--- a/src/op_mode/neighbor.py
+++ b/src/op_mode/neighbor.py
@@ -116,7 +116,7 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py
index a128cc011..20c50e779 100755
--- a/src/op_mode/openconnect-control.py
+++ b/src/op_mode/openconnect-control.py
@@ -19,7 +19,6 @@ import argparse
import json
from vyos.config import Config
-from vyos.util import commit_in_progress
from vyos.util import popen
from vyos.util import run
from vyos.util import DEVNULL
@@ -60,10 +59,6 @@ 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/openconnect.py b/src/op_mode/openconnect.py
new file mode 100755
index 000000000..00992c66a
--- /dev/null
+++ b/src/op_mode/openconnect.py
@@ -0,0 +1,81 @@
+#!/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 sys
+import json
+
+from tabulate import tabulate
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import rc_cmd
+
+import vyos.opmode
+
+
+occtl = '/usr/bin/occtl'
+occtl_socket = '/run/ocserv/occtl.socket'
+
+
+def _get_raw_data_sessions():
+ rc, out = rc_cmd(f'sudo {occtl} --json --socket-file {occtl_socket} show users')
+ if rc != 0:
+ output = {'openconnect':
+ {
+ 'configured': False,
+ 'return_code': rc,
+ 'reason': out
+ }
+ }
+ return output
+
+ sessions = json.loads(out)
+ return sessions
+
+
+def _get_formatted_sessions(data):
+ headers = ["Interface", "Username", "IP", "Remote IP", "RX", "TX", "State", "Uptime"]
+ ses_list = []
+ for ses in data:
+ ses_list.append([
+ ses["Device"], ses["Username"], ses["IPv4"], ses["Remote IP"],
+ ses["_RX"], ses["_TX"], ses["State"], ses["_Connected at"]
+ ])
+ if len(ses_list) > 0:
+ output = tabulate(ses_list, headers)
+ else:
+ output = 'No active openconnect sessions'
+ return output
+
+
+def show_sessions(raw: bool):
+ config = ConfigTreeQuery()
+ if not config.exists('vpn openconnect') and not raw:
+ print('Openconnect is not configured')
+ exit(0)
+
+ openconnect_data = _get_raw_data_sessions()
+ if raw:
+ return openconnect_data
+ return _get_formatted_sessions(openconnect_data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py
index db5a48970..9203c009f 100755
--- a/src/op_mode/restart_dhcp_relay.py
+++ b/src/op_mode/restart_dhcp_relay.py
@@ -43,7 +43,7 @@ if __name__ == '__main__':
if commit_in_progress():
print('Cannot restart DHCP relay while a commit is in progress')
exit(1)
- call('systemctl restart isc-dhcp-server.service')
+ call('systemctl restart isc-dhcp-relay.service')
sys.exit(0)
elif args.ipv6:
@@ -54,7 +54,7 @@ if __name__ == '__main__':
if commit_in_progress():
print('Cannot restart DHCPv6 relay while commit is in progress')
exit(1)
- call('systemctl restart isc-dhcp-server6.service')
+ call('systemctl restart isc-dhcp-relay6.service')
sys.exit(0)
else:
diff --git a/src/op_mode/route.py b/src/op_mode/route.py
index 3bb06adac..e1eee5bbf 100644
--- a/src/op_mode/route.py
+++ b/src/op_mode/route.py
@@ -92,7 +92,7 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/show_nat66_rules.py b/src/op_mode/show_nat66_rules.py
deleted file mode 100755
index 967ec9d37..000000000
--- a/src/op_mode/show_nat66_rules.py
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/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 jmespath
-import json
-
-from argparse import ArgumentParser
-from jinja2 import Template
-from sys import exit
-from vyos.util import cmd
-from vyos.util import dict_search
-
-parser = ArgumentParser()
-group = parser.add_mutually_exclusive_group()
-group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true")
-group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true")
-args = parser.parse_args()
-
-if args.source or args.destination:
- tmp = cmd('sudo nft -j list table ip6 nat')
- tmp = json.loads(tmp)
-
- format_nat66_rule = '{0: <10} {1: <50} {2: <50} {3: <10}'
- print(format_nat66_rule.format("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface"))
- print(format_nat66_rule.format("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------"))
-
- data_json = jmespath.search('nftables[?rule].rule[?chain]', tmp)
- for idx in range(0, len(data_json)):
- data = data_json[idx]
-
- # The following key values must exist
- # When the rule JSON does not have some keys, this is not a rule we can work with
- continue_rule = False
- for key in ['comment', 'chain', 'expr']:
- if key not in data:
- continue_rule = True
- continue
- if continue_rule:
- continue
-
- comment = data['comment']
-
- # Check the annotation to see if the annotation format is created by VYOS
- continue_rule = True
- for comment_prefix in ['SRC-NAT66-', 'DST-NAT66-']:
- if comment_prefix in comment:
- continue_rule = False
- if continue_rule:
- continue
-
- # When log is detected from the second index of expr, then this rule should be ignored
- if 'log' in data['expr'][2]:
- continue
-
- rule = comment.replace('SRC-NAT66-','')
- rule = rule.replace('DST-NAT66-','')
- chain = data['chain']
- if not ((args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING')):
- continue
- interface = dict_search('match.right', data['expr'][0])
- srcdest = dict_search('match.right.prefix.addr', data['expr'][2])
- if srcdest:
- addr_tmp = dict_search('match.right.prefix.len', data['expr'][2])
- if addr_tmp:
- srcdest = srcdest + '/' + str(addr_tmp)
- else:
- srcdest = dict_search('match.right', data['expr'][2])
-
- tran_addr_json = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3])
- if tran_addr_json:
- if isinstance(srcdest_json,str):
- tran_addr = tran_addr_json
-
- if 'prefix' in tran_addr_json:
- addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
- len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
- if addr_tmp:
- tran_addr = addr_tmp + '/' + str(len_tmp)
- else:
- if 'masquerade' in data['expr'][3]:
- tran_addr = 'masquerade'
-
- print(format_nat66_rule.format(rule, srcdest, tran_addr, interface))
-
- exit(0)
-else:
- parser.print_help()
- exit(1)
-
diff --git a/src/op_mode/version.py b/src/op_mode/version.py
index 06208c3e5..ad0293aca 100755
--- a/src/op_mode/version.py
+++ b/src/op_mode/version.py
@@ -78,7 +78,7 @@ if __name__ == '__main__':
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py
index e3d944d90..aeb50fe6e 100755
--- a/src/op_mode/vrf.py
+++ b/src/op_mode/vrf.py
@@ -90,6 +90,6 @@ if __name__ == "__main__":
res = vyos.opmode.run(sys.modules[__name__])
if res:
print(res)
- except ValueError as e:
+ except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index 3e89fb239..c8ae0f516 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -21,7 +21,7 @@ from makefun import with_signature
from .. import state
from .. import key_auth
-from api.graphql.recipes.session import Session
+from api.graphql.session.session import Session
mutation = ObjectType("Mutation")
@@ -71,7 +71,7 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
# one may override the session functions with a local subclass
try:
- mod = import_module(f'api.graphql.recipes.{func_base_name}')
+ mod = import_module(f'api.graphql.session.override.{func_base_name}')
klass = getattr(mod, class_name)
except ImportError:
# otherwise, dynamically generate subclass to invoke subclass
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index f6544709e..921a66274 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -21,7 +21,7 @@ from makefun import with_signature
from .. import state
from .. import key_auth
-from api.graphql.recipes.session import Session
+from api.graphql.session.session import Session
query = ObjectType("Query")
@@ -71,7 +71,7 @@ def make_query_resolver(query_name, class_name, session_func):
# one may override the session functions with a local subclass
try:
- mod = import_module(f'api.graphql.recipes.{func_base_name}')
+ mod = import_module(f'api.graphql.session.override.{func_base_name}')
klass = getattr(mod, class_name)
except ImportError:
# otherwise, dynamically generate subclass to invoke subclass
diff --git a/src/services/api/graphql/recipes/__init__.py b/src/services/api/graphql/session/__init__.py
index e69de29bb..e69de29bb 100644
--- a/src/services/api/graphql/recipes/__init__.py
+++ b/src/services/api/graphql/session/__init__.py
diff --git a/src/services/api/graphql/recipes/queries/system_status.py b/src/services/api/graphql/session/composite/system_status.py
index 8dadcc9f3..8dadcc9f3 100755
--- a/src/services/api/graphql/recipes/queries/system_status.py
+++ b/src/services/api/graphql/session/composite/system_status.py
diff --git a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py b/src/services/api/graphql/session/override/remove_firewall_address_group_members.py
index b91932e14..b91932e14 100644
--- a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py
+++ b/src/services/api/graphql/session/override/remove_firewall_address_group_members.py
diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/session/session.py
index ac185beb7..23bc7154c 100644
--- a/src/services/api/graphql/recipes/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -149,7 +149,7 @@ class Session:
return res
def system_status(self):
- import api.graphql.recipes.queries.system_status as system_status
+ import api.graphql.session.composite.system_status as system_status
session = self._session
data = self._data
diff --git a/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl b/src/services/api/graphql/session/templates/create_dhcp_server.tmpl
index 70de43183..70de43183 100644
--- a/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl
+++ b/src/services/api/graphql/session/templates/create_dhcp_server.tmpl
diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl
index a890d0086..a890d0086 100644
--- a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl
+++ b/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl
diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl b/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl
index e9b660722..e9b660722 100644
--- a/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl
+++ b/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl
diff --git a/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl b/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl
index d9d7ed691..d9d7ed691 100644
--- a/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl
+++ b/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl
diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl
index 458f3e5fc..458f3e5fc 100644
--- a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl
+++ b/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl
diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl
index 0efa0b226..0efa0b226 100644
--- a/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl
+++ b/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl
diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl
index f56c61231..f56c61231 100644
--- a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl
+++ b/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl
diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl
index f98a5517c..f98a5517c 100644
--- a/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl
+++ b/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl
diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py
index d27586747..f990aae52 100755
--- a/src/services/api/graphql/utils/schema_from_op_mode.py
+++ b/src/services/api/graphql/utils/schema_from_op_mode.py
@@ -25,7 +25,10 @@ from inspect import signature, getmembers, isfunction
from jinja2 import Template
from vyos.defaults import directories
-from . util import load_as_module, is_op_mode_function_name, is_show_function_name
+if __package__ is None or __package__ == '':
+ from util import load_as_module, is_op_mode_function_name, is_show_function_name
+else:
+ from . util import load_as_module, is_op_mode_function_name, is_show_function_name
OP_MODE_PATH = directories['op_mode']
SCHEMA_PATH = directories['api_schema']
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index af8837e1e..190f3409d 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -678,6 +678,7 @@ if __name__ == '__main__':
server_config = load_server_config()
except Exception as err:
logger.critical(f"Failed to load the HTTP API server config: {err}")
+ sys.exit(1)
config_session = ConfigSession(os.getpid())
diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py
index a8df232ae..a0fccd1d0 100755
--- a/src/system/keepalived-fifo.py
+++ b/src/system/keepalived-fifo.py
@@ -30,6 +30,7 @@ from vyos.ifconfig.vrrp import VRRP
from vyos.configquery import ConfigTreeQuery
from vyos.util import cmd
from vyos.util import dict_search
+from vyos.util import commit_in_progress
# configure logging
logger = logging.getLogger(__name__)
@@ -63,6 +64,17 @@ class KeepalivedFifo:
# load configuration
def _config_load(self):
+ # For VRRP configuration to be read, the commit must be finished
+ count = 1
+ while commit_in_progress():
+ if ( count <= 40 ):
+ logger.debug(f'commit in progress try: {count}')
+ else:
+ logger.error(f'commit still in progress after {count} continuing anyway')
+ break
+ count += 1
+ time.sleep(0.5)
+
try:
base = ['high-availability', 'vrrp']
conf = ConfigTreeQuery()
diff --git a/data/templates/monitoring/systemd_vyos_telegraf_service.j2 b/src/systemd/telegraf.service
index 234ef5586..553942ac6 100644
--- a/data/templates/monitoring/systemd_vyos_telegraf_service.j2
+++ b/src/systemd/telegraf.service
@@ -5,8 +5,7 @@ After=network.target
[Service]
EnvironmentFile=-/etc/default/telegraf
-User=telegraf
-ExecStart=/usr/bin/telegraf -config /run/telegraf/vyos-telegraf.conf -config-directory /etc/telegraf/telegraf.d $TELEGRAF_OPTS
+ExecStart=/usr/bin/telegraf --config /run/telegraf/vyos-telegraf.conf --config-directory /etc/telegraf/telegraf.d
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartForceExitStatus=SIGPIPE
diff --git a/src/validators/ipv6-address-exclude b/src/validators/ipv6-address-exclude
new file mode 100755
index 000000000..be1d3db25
--- /dev/null
+++ b/src/validators/ipv6-address-exclude
@@ -0,0 +1,7 @@
+#!/bin/sh
+arg="$1"
+if [ "${arg:0:1}" != "!" ]; then
+ exit 1
+fi
+path=$(dirname "$0")
+${path}/ipv6-address "${arg:1}"
diff --git a/src/validators/ipv6-prefix-exclude b/src/validators/ipv6-prefix-exclude
new file mode 100755
index 000000000..6fa4f1d8d
--- /dev/null
+++ b/src/validators/ipv6-prefix-exclude
@@ -0,0 +1,7 @@
+#!/bin/sh
+arg="$1"
+if [ "${arg:0:1}" != "!" ]; then
+ exit 1
+fi
+path=$(dirname "$0")
+${path}/ipv6-prefix "${arg:1}"