summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/reviewers.yml30
-rw-r--r--data/op-mode-standardized.json4
-rw-r--r--data/templates/firewall/nftables-geoip-update.j28
-rw-r--r--data/templates/firewall/nftables-nat66.j241
-rw-r--r--data/templates/firewall/nftables-policy.j22
-rw-r--r--data/templates/firewall/nftables-static-nat.j2115
-rw-r--r--data/templates/firewall/nftables-zone.j272
-rw-r--r--data/templates/firewall/nftables.j2222
-rw-r--r--data/templates/firewall/upnpd.conf.j215
-rw-r--r--data/templates/frr/bgpd.frr.j26
-rw-r--r--data/templates/frr/isisd.frr.j224
-rw-r--r--data/templates/high-availability/keepalived.conf.j24
-rw-r--r--data/templates/ipsec/swanctl/profile.j24
-rw-r--r--data/templates/login/pam_radius_auth.conf.j23
-rw-r--r--data/templates/monitoring/override.conf.j27
-rw-r--r--data/templates/nhrp/nftables.conf.j217
-rw-r--r--data/templates/ocserv/ocserv_config.j252
-rw-r--r--data/templates/telegraf/override.conf.j216
-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--data/templates/zone_policy/nftables.j2113
-rw-r--r--data/vyos-firewall-init.conf162
-rw-r--r--debian/control2
-rw-r--r--interface-definitions/firewall.xml.in279
-rw-r--r--interface-definitions/include/accel-ppp/radius-additions.xml.i13
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i13
-rw-r--r--interface-definitions/include/firewall/default-action.xml.i (renamed from interface-definitions/include/firewall/name-default-action.xml.i)3
-rw-r--r--interface-definitions/include/firewall/dscp.xml.i36
-rw-r--r--interface-definitions/include/firewall/enable-default-log.xml.i8
-rw-r--r--interface-definitions/include/firewall/eq.xml.i14
-rw-r--r--interface-definitions/include/firewall/gt.xml.i14
-rw-r--r--interface-definitions/include/firewall/hop-limit.xml.i12
-rw-r--r--interface-definitions/include/firewall/lt.xml.i14
-rw-r--r--interface-definitions/include/firewall/name-default-log.xml.i8
-rw-r--r--interface-definitions/include/firewall/name.xml.i18
-rw-r--r--interface-definitions/include/firewall/packet-length.xml.i36
-rw-r--r--interface-definitions/include/firewall/tcp-flags.xml.i16
-rw-r--r--interface-definitions/include/firewall/ttl.xml.i12
-rw-r--r--interface-definitions/include/inbound-interface.xml.i11
-rw-r--r--interface-definitions/include/interface/interface-firewall-vif-c.xml.i79
-rw-r--r--interface-definitions/include/interface/interface-firewall-vif.xml.i79
-rw-r--r--interface-definitions/include/interface/interface-firewall.xml.i79
-rw-r--r--interface-definitions/include/interface/vif-s.xml.i2
-rw-r--r--interface-definitions/include/interface/vif.xml.i1
-rw-r--r--interface-definitions/include/ipv4-address-prefix.xml.i19
-rw-r--r--interface-definitions/include/nat/protocol.xml.i34
-rw-r--r--interface-definitions/include/radius-nas-ip-address.xml.i14
-rw-r--r--interface-definitions/include/version/firewall-version.xml.i2
-rw-r--r--interface-definitions/interfaces-bonding.xml.in1
-rw-r--r--interface-definitions/interfaces-bridge.xml.in1
-rw-r--r--interface-definitions/interfaces-dummy.xml.in1
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in7
-rw-r--r--interface-definitions/interfaces-geneve.xml.in1
-rw-r--r--interface-definitions/interfaces-input.xml.in1
-rw-r--r--interface-definitions/interfaces-l2tpv3.xml.in1
-rw-r--r--interface-definitions/interfaces-macsec.xml.in1
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in1
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in1
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in1
-rw-r--r--interface-definitions/interfaces-tunnel.xml.in1
-rw-r--r--interface-definitions/interfaces-vti.xml.in1
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in1
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in1
-rw-r--r--interface-definitions/interfaces-wireless.xml.in1
-rw-r--r--interface-definitions/interfaces-wwan.xml.in1
-rw-r--r--interface-definitions/nat.xml.in53
-rw-r--r--interface-definitions/nat66.xml.in8
-rw-r--r--interface-definitions/policy-local-route.xml.in2
-rw-r--r--interface-definitions/policy-route.xml.in10
-rw-r--r--interface-definitions/policy.xml.in2
-rw-r--r--interface-definitions/protocols-rpki.xml.in6
-rw-r--r--interface-definitions/service-monitoring-telegraf.xml.in39
-rw-r--r--interface-definitions/service-pppoe-server.xml.in2
-rw-r--r--interface-definitions/service-upnp.xml.in19
-rw-r--r--interface-definitions/system-conntrack.xml.in8
-rw-r--r--interface-definitions/system-option.xml.in14
-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.in52
-rw-r--r--interface-definitions/zone-policy.xml.in148
-rw-r--r--op-mode-definitions/container.xml.in2
-rw-r--r--op-mode-definitions/dns-forwarding.xml.in20
-rw-r--r--op-mode-definitions/monitor-log.xml.in37
-rw-r--r--op-mode-definitions/nat.xml.in10
-rw-r--r--op-mode-definitions/nat66.xml.in8
-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--op-mode-definitions/show-system.xml.in6
-rw-r--r--op-mode-definitions/vpn-ipsec.xml.in2
-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.py40
-rw-r--r--python/vyos/ifconfig/bond.py12
-rw-r--r--python/vyos/ifconfig/bridge.py20
-rw-r--r--python/vyos/ifconfig/ethernet.py30
-rw-r--r--python/vyos/ifconfig/pppoe.py37
-rw-r--r--python/vyos/ifconfig/section.py12
-rw-r--r--python/vyos/opmode.py2
-rw-r--r--python/vyos/template.py4
-rw-r--r--python/vyos/util.py57
-rw-r--r--smoketest/configs/vpn-openconnect-sstp (renamed from smoketest/configs/pki-misc)10
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py281
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py137
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py38
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py48
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py82
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py13
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py47
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_nhrp.py106
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_forwarding.py2
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_telegraf.py2
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py2
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py79
-rwxr-xr-xsmoketest/scripts/cli/test_zone_policy.py69
-rwxr-xr-xsrc/conf_mode/arp.py2
-rwxr-xr-xsrc/conf_mode/firewall-interface.py186
-rwxr-xr-xsrc/conf_mode/firewall.py296
-rwxr-xr-xsrc/conf_mode/high-availability.py21
-rwxr-xr-xsrc/conf_mode/https.py29
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py56
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py23
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py8
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py3
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py35
-rwxr-xr-xsrc/conf_mode/nat.py24
-rwxr-xr-xsrc/conf_mode/ntp.py21
-rwxr-xr-xsrc/conf_mode/policy-route.py4
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py32
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py82
-rwxr-xr-xsrc/conf_mode/service_upnp.py19
-rwxr-xr-xsrc/conf_mode/ssh.py23
-rwxr-xr-xsrc/conf_mode/system_console.py27
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py8
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py25
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py8
-rwxr-xr-xsrc/conf_mode/zone_policy.py213
-rwxr-xr-xsrc/etc/opennhrp/opennhrp-script.py326
-rw-r--r--src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf11
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/show_firewall_input_filter.py6
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/show_interfaces_input_filter.py16
-rwxr-xr-xsrc/migration-scripts/firewall/7-to-898
-rwxr-xr-xsrc/op_mode/bridge.py8
-rwxr-xr-xsrc/op_mode/conntrack.py23
-rwxr-xr-xsrc/op_mode/ipsec.py116
-rwxr-xr-xsrc/op_mode/nat.py136
-rwxr-xr-xsrc/op_mode/openconnect-control.py5
-rwxr-xr-xsrc/op_mode/openconnect.py81
-rwxr-xr-xsrc/op_mode/restart_dhcp_relay.py4
-rwxr-xr-x[-rw-r--r--]src/op_mode/route.py0
-rwxr-xr-xsrc/op_mode/show_nat66_rules.py102
-rwxr-xr-xsrc/op_mode/storage.py60
-rwxr-xr-xsrc/op_mode/uptime.py (renamed from src/op_mode/show_uptime.py)36
-rw-r--r--src/services/api/graphql/bindings.py3
-rw-r--r--src/services/api/graphql/graphql/errors.py8
-rw-r--r--src/services/api/graphql/graphql/mutations.py18
-rw-r--r--src/services/api/graphql/graphql/queries.py18
-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)4
-rw-r--r--src/services/api/graphql/session/errors/op_mode_errors.py13
-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)15
-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.py48
-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/range56
176 files changed, 3390 insertions, 2354 deletions
diff --git a/.github/reviewers.yml b/.github/reviewers.yml
index 9ef3ec961..8463681fc 100644
--- a/.github/reviewers.yml
+++ b/.github/reviewers.yml
@@ -1,34 +1,8 @@
---
-python/**:
- - c-po
- - dmbaturin
- - jestabro
-
-interface-definitions/**:
- - c-po
- - DmitriyEshenko
- - dmbaturin
- - jestabro
- - sever-sever
- - zdc
-
-op-mode-definitions/**:
- - c-po
- - DmitriyEshenko
+"**/*":
- dmbaturin
- - jestabro
- - sever-sever
+ - UnicronNL
- zdc
-
-src/**:
- - c-po
- - DmitriyEshenko
- - dmbaturin
- jestabro
- sever-sever
- - zdc
-
-.github/**:
- c-po
- - dmbaturin
- - UnicronNL
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index b5e9308c5..2d6f6da41 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -6,7 +6,11 @@
"memory.py",
"nat.py",
"neighbor.py",
+"openconnect.py",
"route.py",
+"ipsec.py",
+"storage.py",
+"uptime.py",
"version.py",
"vrf.py"
]
diff --git a/data/templates/firewall/nftables-geoip-update.j2 b/data/templates/firewall/nftables-geoip-update.j2
index f9e61a274..832ccc3e9 100644
--- a/data/templates/firewall/nftables-geoip-update.j2
+++ b/data/templates/firewall/nftables-geoip-update.j2
@@ -2,10 +2,10 @@
{% if ipv4_sets is vyos_defined %}
{% for setname, ip_list in ipv4_sets.items() %}
-flush set ip filter {{ setname }}
+flush set ip vyos_filter {{ setname }}
{% endfor %}
-table ip filter {
+table ip vyos_filter {
{% for setname, ip_list in ipv4_sets.items() %}
set {{ setname }} {
type ipv4_addr
@@ -18,10 +18,10 @@ table ip filter {
{% if ipv6_sets is vyos_defined %}
{% for setname, ip_list in ipv6_sets.items() %}
-flush set ip6 filter {{ setname }}
+flush set ip6 vyos_filter {{ setname }}
{% endfor %}
-table ip6 filter {
+table ip6 vyos_filter {
{% for setname, ip_list in ipv6_sets.items() %}
set {{ setname }} {
type ipv6_addr
diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2
index 2fe04b4ff..28714c7a7 100644
--- a/data/templates/firewall/nftables-nat66.j2
+++ b/data/templates/firewall/nftables-nat66.j2
@@ -7,6 +7,17 @@
{% 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 %}
+{# Port #}
+{% if config.source.port is vyos_defined and config.source.port.startswith('!') %}
+{% set src_port = 'sport != { ' ~ config.source.port.replace('!','') ~ ' }' %}
+{% else %}
+{% set src_port = 'sport { ' ~ config.source.port ~ ' }' if config.source.port is vyos_defined %}
+{% endif %}
+{% if config.destination.port is vyos_defined and config.destination.port.startswith('!') %}
+{% set dst_port = 'dport != { ' ~ config.destination.port.replace('!','') ~ ' }' %}
+{% else %}
+{% set dst_port = 'dport { ' ~ config.destination.port ~ ' }' if config.destination.port is vyos_defined %}
+{% endif %}
{% if chain is vyos_defined('PREROUTING') %}
{% set comment = 'DST-NAT66-' ~ rule %}
{% set base_log = '[NAT66-DST-' ~ rule %}
@@ -36,6 +47,14 @@
{% endif %}
{% set interface = ' oifname "' ~ config.outbound_interface ~ '"' if config.outbound_interface is vyos_defined else '' %}
{% endif %}
+{% set trns_port = ':' ~ config.translation.port if config.translation.port is vyos_defined %}
+{# protocol has a default value thus it is always present #}
+{% if config.protocol is vyos_defined('tcp_udp') %}
+{% set protocol = 'tcp' %}
+{% set comment = comment ~ ' tcp_udp' %}
+{% else %}
+{% set protocol = config.protocol %}
+{% endif %}
{% if config.log is vyos_defined %}
{% if config.translation.address is vyos_defined('masquerade') %}
{% set log = base_log ~ '-MASQ]' %}
@@ -43,6 +62,11 @@
{% set log = base_log ~ ']' %}
{% endif %}
{% endif %}
+{% if config.exclude is vyos_defined %}
+{# rule has been marked as 'exclude' thus we simply return here #}
+{% set trns_addr = 'return' %}
+{% set trns_port = '' %}
+{% endif %}
{% set output = 'add rule ip6 nat ' ~ chain ~ interface %}
{# Count packets #}
{% set output = output ~ ' counter' %}
@@ -54,12 +78,18 @@
{% if src_prefix is vyos_defined %}
{% set output = output ~ ' ' ~ src_prefix %}
{% endif %}
+{% if dst_port is vyos_defined %}
+{% set output = output ~ ' ' ~ protocol ~ ' ' ~ dst_port %}
+{% 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 src_port is vyos_defined %}
+{% set output = output ~ ' ' ~ protocol ~ ' ' ~ src_port %}
+{% endif %}
{% if dest_address is vyos_defined %}
{% set output = output ~ ' ' ~ dest_address %}
{% endif %}
@@ -70,11 +100,22 @@
{% if trns_address is vyos_defined %}
{% set output = output ~ ' ' ~ trns_address %}
{% endif %}
+{% if trns_port is vyos_defined %}
+{# Do not add a whitespace here, translation port must be directly added after IP address #}
+{# e.g. 2001:db8::1:3389 #}
+{% set output = output ~ trns_port %}
+{% endif %}
{% if comment is vyos_defined %}
{% set output = output ~ ' comment "' ~ comment ~ '"' %}
{% endif %}
{{ log_output if log_output is vyos_defined }}
{{ output }}
+{# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #}
+{% if config.protocol is vyos_defined('tcp_udp') %}
+{# Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #}
+{{ log_output | replace('tcp ', 'udp ') if log_output is vyos_defined }}
+{{ output | replace('tcp ', 'udp ') }}
+{% endif %}
{% endmacro %}
# Start with clean NAT table
diff --git a/data/templates/firewall/nftables-policy.j2 b/data/templates/firewall/nftables-policy.j2
index 281525407..40118930b 100644
--- a/data/templates/firewall/nftables-policy.j2
+++ b/data/templates/firewall/nftables-policy.j2
@@ -25,7 +25,6 @@ table ip mangle {
{{ rule_conf | nft_rule(route_text, rule_id, 'ip') }}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(route_text) }}
}
{% endfor %}
{% endif %}
@@ -50,7 +49,6 @@ table ip6 mangle {
{{ rule_conf | nft_rule(route_text, rule_id, 'ip6') }}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(route_text) }}
}
{% endfor %}
{% endif %}
diff --git a/data/templates/firewall/nftables-static-nat.j2 b/data/templates/firewall/nftables-static-nat.j2
new file mode 100644
index 000000000..d3c43858f
--- /dev/null
+++ b/data/templates/firewall/nftables-static-nat.j2
@@ -0,0 +1,115 @@
+#!/usr/sbin/nft -f
+
+{% macro nat_rule(rule, config, chain) %}
+{% set comment = '' %}
+{% set base_log = '' %}
+
+{% if chain is vyos_defined('PREROUTING') %}
+{% set comment = 'STATIC-NAT-' ~ rule %}
+{% set base_log = '[NAT-DST-' ~ rule %}
+{% set interface = ' iifname "' ~ config.inbound_interface ~ '"' if config.inbound_interface is vyos_defined and config.inbound_interface is not vyos_defined('any') else '' %}
+{% if config.translation.address is vyos_defined %}
+{# support 1:1 network translation #}
+{% if config.translation.address | is_ip_network %}
+{% set trns_addr = 'dnat ip prefix to ip daddr map { ' ~ config.destination.address ~ ' : ' ~ config.translation.address ~ ' }' %}
+{# we can now clear out the dst_addr part as it's already covered in aboves map #}
+{% else %}
+{% set dst_addr = 'ip daddr ' ~ config.destination.address if config.destination.address is vyos_defined %}
+{% set trns_addr = 'dnat to ' ~ config.translation.address %}
+{% endif %}
+{% endif %}
+{% elif chain is vyos_defined('POSTROUTING') %}
+{% set comment = 'STATIC-NAT-' ~ rule %}
+{% set base_log = '[NAT-SRC-' ~ rule %}
+{% set interface = ' oifname "' ~ config.inbound_interface ~ '"' if config.inbound_interface is vyos_defined and config.inbound_interface is not vyos_defined('any') else '' %}
+{% if config.translation.address is vyos_defined %}
+{# support 1:1 network translation #}
+{% if config.translation.address | is_ip_network %}
+{% set trns_addr = 'snat ip prefix to ip saddr map { ' ~ config.translation.address ~ ' : ' ~ config.destination.address ~ ' }' %}
+{# we can now clear out the src_addr part as it's already covered in aboves map #}
+{% else %}
+{% set src_addr = 'ip saddr ' ~ config.translation.address if config.translation.address is vyos_defined %}
+{% set trns_addr = 'snat to ' ~ config.destination.address %}
+{% endif %}
+{% endif %}
+{% endif %}
+
+{% if config.exclude is vyos_defined %}
+{# rule has been marked as 'exclude' thus we simply return here #}
+{% set trns_addr = 'return' %}
+{% set trns_port = '' %}
+{% endif %}
+
+{% if config.translation.options is vyos_defined %}
+{% if config.translation.options.address_mapping is vyos_defined('persistent') %}
+{% set trns_opts_addr = 'persistent' %}
+{% endif %}
+{% if config.translation.options.port_mapping is vyos_defined('random') %}
+{% set trns_opts_port = 'random' %}
+{% elif config.translation.options.port_mapping is vyos_defined('fully-random') %}
+{% set trns_opts_port = 'fully-random' %}
+{% endif %}
+{% endif %}
+
+{% if trns_opts_addr is vyos_defined and trns_opts_port is vyos_defined %}
+{% set trns_opts = trns_opts_addr ~ ',' ~ trns_opts_port %}
+{% elif trns_opts_addr is vyos_defined %}
+{% set trns_opts = trns_opts_addr %}
+{% elif trns_opts_port is vyos_defined %}
+{% set trns_opts = trns_opts_port %}
+{% endif %}
+
+{% set output = 'add rule ip vyos_static_nat ' ~ chain ~ interface %}
+
+{% if dst_addr is vyos_defined %}
+{% set output = output ~ ' ' ~ dst_addr %}
+{% endif %}
+{% if src_addr is vyos_defined %}
+{% set output = output ~ ' ' ~ src_addr %}
+{% endif %}
+
+{# Count packets #}
+{% set output = output ~ ' counter' %}
+{# Special handling of log option, we must repeat the entire rule before the #}
+{# NAT translation options are added, this is essential #}
+{% if log is vyos_defined %}
+{% set log_output = output ~ ' log prefix "' ~ log ~ '" comment "' ~ comment ~ '"' %}
+{% endif %}
+{% if trns_addr is vyos_defined %}
+{% set output = output ~ ' ' ~ trns_addr %}
+{% endif %}
+
+{% if trns_opts is vyos_defined %}
+{% set output = output ~ ' ' ~ trns_opts %}
+{% endif %}
+{% if comment is vyos_defined %}
+{% set output = output ~ ' comment "' ~ comment ~ '"' %}
+{% endif %}
+{{ log_output if log_output is vyos_defined }}
+{{ output }}
+{% endmacro %}
+
+# Start with clean STATIC NAT chains
+flush chain ip vyos_static_nat PREROUTING
+flush chain ip vyos_static_nat POSTROUTING
+
+{# NAT if enabled - add targets to nftables #}
+
+#
+# Destination NAT rules build up here
+#
+add rule ip vyos_static_nat PREROUTING counter jump VYOS_PRE_DNAT_HOOK
+{% if static.rule is vyos_defined %}
+{% for rule, config in static.rule.items() if config.disable is not vyos_defined %}
+{{ nat_rule(rule, config, 'PREROUTING') }}
+{% endfor %}
+{% endif %}
+#
+# Source NAT rules build up here
+#
+add rule ip vyos_static_nat POSTROUTING counter jump VYOS_PRE_SNAT_HOOK
+{% if static.rule is vyos_defined %}
+{% for rule, config in static.rule.items() if config.disable is not vyos_defined %}
+{{ nat_rule(rule, config, 'POSTROUTING') }}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2
new file mode 100644
index 000000000..919881e19
--- /dev/null
+++ b/data/templates/firewall/nftables-zone.j2
@@ -0,0 +1,72 @@
+
+{% macro zone_chains(zone, state_policy=False, ipv6=False) %}
+{% set fw_name = 'ipv6_name' if ipv6 else 'name' %}
+{% set suffix = '6' if ipv6 else '' %}
+ chain VYOS_ZONE_FORWARD {
+ type filter hook forward priority 1; policy accept;
+{% if state_policy %}
+ jump VYOS_STATE_POLICY{{ suffix }}
+{% endif %}
+{% for zone_name, zone_conf in zone.items() %}
+{% if 'local_zone' not in zone_conf %}
+ oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
+{% endif %}
+{% endfor %}
+ }
+ chain VYOS_ZONE_LOCAL {
+ type filter hook input priority 1; policy accept;
+{% if state_policy %}
+ jump VYOS_STATE_POLICY{{ suffix }}
+{% endif %}
+{% for zone_name, zone_conf in zone.items() %}
+{% if 'local_zone' in zone_conf %}
+ counter jump VZONE_{{ zone_name }}_IN
+{% endif %}
+{% endfor %}
+ }
+ chain VYOS_ZONE_OUTPUT {
+ type filter hook output priority 1; policy accept;
+{% if state_policy %}
+ jump VYOS_STATE_POLICY{{ suffix }}
+{% endif %}
+{% for zone_name, zone_conf in zone.items() %}
+{% if 'local_zone' in zone_conf %}
+ counter jump VZONE_{{ zone_name }}_OUT
+{% endif %}
+{% endfor %}
+ }
+{% for zone_name, zone_conf in zone.items() %}
+{% if zone_conf.local_zone is vyos_defined %}
+ chain VZONE_{{ zone_name }}_IN {
+ iifname lo counter return
+{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endfor %}
+ {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
+ }
+ chain VZONE_{{ zone_name }}_OUT {
+ oifname lo counter return
+{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall[fw_name] is vyos_defined %}
+ oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
+ oifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endfor %}
+ {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
+ }
+{% else %}
+ chain VZONE_{{ zone_name }} {
+ iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
+{% if zone_conf.intra_zone_filtering is vyos_defined %}
+ iifname { {{ zone_conf.interface | join(",") }} } counter return
+{% endif %}
+{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
+{% if zone[from_zone].local_zone is not defined %}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endif %}
+{% endfor %}
+ {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
+ }
+{% endif %}
+{% endfor %}
+{% endmacro %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index b91fed615..c0780dad5 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -1,25 +1,48 @@
#!/usr/sbin/nft -f
{% import 'firewall/nftables-defines.j2' as group_tmpl %}
+{% import 'firewall/nftables-zone.j2' as zone_tmpl %}
-{% if cleanup_commands is vyos_defined %}
-{% for command in cleanup_commands %}
-{{ command }}
-{% endfor %}
+{% if first_install is not vyos_defined %}
+delete table ip vyos_filter
{% endif %}
-
-table ip filter {
-{% if first_install is vyos_defined %}
+table ip vyos_filter {
chain VYOS_FW_FORWARD {
type filter hook forward priority 0; policy accept;
+{% if state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
+{% if interface is vyos_defined %}
+{% for ifname, ifconf in interface.items() %}
+{% if ifconf.in is vyos_defined and ifconf.in.name is vyos_defined %}
+ iifname {{ ifname }} counter jump NAME_{{ ifconf.in.name }}
+{% endif %}
+{% if ifconf.out is vyos_defined and ifconf.out.name is vyos_defined %}
+ oifname {{ ifname }} counter jump NAME_{{ ifconf.out.name }}
+{% endif %}
+{% endfor %}
+{% endif %}
jump VYOS_POST_FW
}
chain VYOS_FW_LOCAL {
type filter hook input priority 0; policy accept;
+{% if state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
+{% if interface is vyos_defined %}
+{% for ifname, ifconf in interface.items() %}
+{% if ifconf.local is vyos_defined and ifconf.local.name is vyos_defined %}
+ iifname {{ ifname }} counter jump NAME_{{ ifconf.local.name }}
+{% endif %}
+{% endfor %}
+{% endif %}
jump VYOS_POST_FW
}
chain VYOS_FW_OUTPUT {
type filter hook output priority 0; policy accept;
+{% if state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
jump VYOS_POST_FW
}
chain VYOS_POST_FW {
@@ -29,7 +52,6 @@ table ip filter {
type filter hook prerouting priority -450; policy accept;
ip frag-off & 0x3fff != 0 meta mark set 0xffff1 return
}
-{% endif %}
{% if name is vyos_defined %}
{% set ns = namespace(sets=[]) %}
{% for name_text, conf in name.items() %}
@@ -72,6 +94,10 @@ table ip filter {
{{ group_tmpl.groups(group, False) }}
+{% if zone is vyos_defined %}
+{{ zone_tmpl.zone_chains(zone, state_policy is vyos_defined, False) }}
+{% endif %}
+
{% if state_policy is vyos_defined %}
chain VYOS_STATE_POLICY {
{% if state_policy.established is vyos_defined %}
@@ -88,18 +114,46 @@ table ip filter {
{% endif %}
}
-table ip6 filter {
-{% if first_install is vyos_defined %}
+{% if first_install is not vyos_defined %}
+delete table ip6 vyos_filter
+{% endif %}
+table ip6 vyos_filter {
chain VYOS_FW6_FORWARD {
type filter hook forward priority 0; policy accept;
+{% if state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY6
+{% endif %}
+{% if interface is vyos_defined %}
+{% for ifname, ifconf in interface.items() %}
+{% if ifconf.in is vyos_defined and ifconf.in.ipv6_name is vyos_defined %}
+ iifname {{ ifname }} counter jump NAME6_{{ ifconf.in.ipv6_name }}
+{% endif %}
+{% if ifconf.out is vyos_defined and ifconf.out.ipv6_name is vyos_defined %}
+ oifname {{ ifname }} counter jump NAME6_{{ ifconf.out.ipv6_name }}
+{% endif %}
+{% endfor %}
+{% endif %}
jump VYOS_POST_FW6
}
chain VYOS_FW6_LOCAL {
type filter hook input priority 0; policy accept;
+{% if state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY6
+{% endif %}
+{% if interface is vyos_defined %}
+{% for ifname, ifconf in interface.items() %}
+{% if ifconf.local is vyos_defined and ifconf.local.ipv6_name is vyos_defined %}
+ iifname {{ ifname }} counter jump NAME6_{{ ifconf.local.ipv6_name }}
+{% endif %}
+{% endfor %}
+{% endif %}
jump VYOS_POST_FW6
}
chain VYOS_FW6_OUTPUT {
type filter hook output priority 0; policy accept;
+{% if state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY6
+{% endif %}
jump VYOS_POST_FW6
}
chain VYOS_POST_FW6 {
@@ -109,7 +163,6 @@ table ip6 filter {
type filter hook prerouting priority -450; policy accept;
exthdr frag exists meta mark set 0xffff1 return
}
-{% endif %}
{% if ipv6_name is vyos_defined %}
{% set ns = namespace(sets=[]) %}
{% for name_text, conf in ipv6_name.items() %}
@@ -144,6 +197,10 @@ table ip6 filter {
{{ group_tmpl.groups(group, True) }}
+{% if zone is vyos_defined %}
+{{ zone_tmpl.zone_chains(zone, state_policy is vyos_defined, True) }}
+{% endif %}
+
{% if state_policy is vyos_defined %}
chain VYOS_STATE_POLICY6 {
{% if state_policy.established is vyos_defined %}
@@ -159,146 +216,3 @@ table ip6 filter {
}
{% endif %}
}
-
-{% if first_install is vyos_defined %}
-table ip nat {
- chain PREROUTING {
- type nat hook prerouting priority -100; policy accept;
- counter jump VYOS_PRE_DNAT_HOOK
- }
-
- chain POSTROUTING {
- type nat hook postrouting priority 100; policy accept;
- counter jump VYOS_PRE_SNAT_HOOK
- }
-
- chain VYOS_PRE_DNAT_HOOK {
- return
- }
-
- chain VYOS_PRE_SNAT_HOOK {
- return
- }
-}
-
-table ip6 nat {
- chain PREROUTING {
- type nat hook prerouting priority -100; policy accept;
- counter jump VYOS_DNPT_HOOK
- }
-
- chain POSTROUTING {
- type nat hook postrouting priority 100; policy accept;
- counter jump VYOS_SNPT_HOOK
- }
-
- chain VYOS_DNPT_HOOK {
- return
- }
-
- chain VYOS_SNPT_HOOK {
- return
- }
-}
-
-table inet mangle {
- chain FORWARD {
- type filter hook forward priority -150; policy accept;
- }
-}
-
-table raw {
- chain VYOS_TCP_MSS {
- type filter hook forward priority -300; policy accept;
- }
-
- chain PREROUTING {
- type filter hook prerouting priority -200; policy accept;
- counter jump VYOS_CT_IGNORE
- counter jump VYOS_CT_TIMEOUT
- counter jump VYOS_CT_PREROUTING_HOOK
- counter jump FW_CONNTRACK
- notrack
- }
-
- chain OUTPUT {
- type filter hook output priority -200; policy accept;
- counter jump VYOS_CT_IGNORE
- counter jump VYOS_CT_TIMEOUT
- counter jump VYOS_CT_OUTPUT_HOOK
- counter jump FW_CONNTRACK
- notrack
- }
-
- ct helper rpc_tcp {
- type "rpc" protocol tcp;
- }
-
- ct helper rpc_udp {
- type "rpc" protocol udp;
- }
-
- ct helper tns_tcp {
- type "tns" protocol tcp;
- }
-
- chain VYOS_CT_HELPER {
- ct helper set "rpc_tcp" tcp dport {111} return
- ct helper set "rpc_udp" udp dport {111} return
- ct helper set "tns_tcp" tcp dport {1521,1525,1536} return
- return
- }
-
- chain VYOS_CT_IGNORE {
- return
- }
-
- chain VYOS_CT_TIMEOUT {
- return
- }
-
- chain VYOS_CT_PREROUTING_HOOK {
- return
- }
-
- chain VYOS_CT_OUTPUT_HOOK {
- return
- }
-
- chain FW_CONNTRACK {
- accept
- }
-}
-
-table ip6 raw {
- chain VYOS_TCP_MSS {
- type filter hook forward priority -300; policy accept;
- }
-
- chain PREROUTING {
- type filter hook prerouting priority -300; policy accept;
- counter jump VYOS_CT_PREROUTING_HOOK
- counter jump FW_CONNTRACK
- notrack
- }
-
- chain OUTPUT {
- type filter hook output priority -300; policy accept;
- counter jump VYOS_CT_OUTPUT_HOOK
- counter jump FW_CONNTRACK
- notrack
- }
-
- chain VYOS_CT_PREROUTING_HOOK {
- return
- }
-
- chain VYOS_CT_OUTPUT_HOOK {
- return
- }
-
- chain FW_CONNTRACK {
- accept
- }
-}
-{% 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 2ab7c8596..bf4be23ff 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 %}
@@ -458,6 +461,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if parameters.bestpath.med is vyos_defined %}
bgp bestpath med {{ 'confed' if parameters.bestpath.med.confed is vyos_defined }} {{ 'missing-as-worst' if parameters.bestpath.med.missing_as_worst is vyos_defined }}
{% endif %}
+{% if parameters.bestpath.peer_type is vyos_defined %}
+ bgp bestpath peer-type {{ 'multipath-relax' if parameters.bestpath.peer_type.multipath_relax is vyos_defined }}
+{% endif %}
{% if parameters.cluster_id is vyos_defined %}
bgp cluster-id {{ parameters.cluster_id }}
{% endif %}
diff --git a/data/templates/frr/isisd.frr.j2 b/data/templates/frr/isisd.frr.j2
index 8e95348bc..709484c98 100644
--- a/data/templates/frr/isisd.frr.j2
+++ b/data/templates/frr/isisd.frr.j2
@@ -124,23 +124,23 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }}
{% for prefix, prefix_config in segment_routing.prefix.items() %}
{% if prefix_config.absolute is vyos_defined %}
{% if prefix_config.absolute.value is vyos_defined %}
- segment-routing prefix {{ prefixes }} absolute {{ prefix_config.absolute.value }}
+ segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }}
{% if prefix_config.absolute.explicit_null is vyos_defined %}
- segment-routing prefix {{ prefixes }} absolute {{ prefix_config.absolute.value }} explicit-null
+ segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }} explicit-null
{% endif %}
{% if prefix_config.absolute.no_php_flag is vyos_defined %}
- segment-routing prefix {{ prefixes }} absolute {{ prefix_config.absolute.value }} no-php-flag
+ segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }} no-php-flag
{% endif %}
{% endif %}
-{% if prefix_config.index is vyos_defined %}
-{% if prefix_config.index.value is vyos_defined %}
- segment-routing prefix {{ prefixes }} index {{ prefix_config.index.value }}
-{% if prefix_config.index.explicit_null is vyos_defined %}
- segment-routing prefix {{ prefixes }} index {{ prefix_config.index.value }} explicit-null
-{% endif %}
-{% if prefix_config.index.no_php_flag is vyos_defined %}
- segment-routing prefix {{ prefixes }} index {{ prefix_config.index.value }} no-php-flag
-{% endif %}
+{% endif %}
+{% if prefix_config.index is vyos_defined %}
+{% if prefix_config.index.value is vyos_defined %}
+ segment-routing prefix {{ prefix }} index {{ prefix_config.index.value }}
+{% if prefix_config.index.explicit_null is vyos_defined %}
+ segment-routing prefix {{ prefix }} index {{ prefix_config.index.value }} explicit-null
+{% endif %}
+{% if prefix_config.index.no_php_flag is vyos_defined %}
+ segment-routing prefix {{ prefix }} index {{ prefix_config.index.value }} no-php-flag
{% endif %}
{% endif %}
{% endif %}
diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2
index 6684dbc2c..706e1c5ae 100644
--- a/data/templates/high-availability/keepalived.conf.j2
+++ b/data/templates/high-availability/keepalived.conf.j2
@@ -47,10 +47,10 @@ vrrp_instance {{ name }} {
{% endif %}
{% endif %}
{% if group_config.rfc3768_compatibility is vyos_defined and group_config.peer_address is vyos_defined %}
- use_vmac {{ group_config.interface }}v{{ group_config.vrid }}
+ use_vmac {{ group_config.interface }}v{{ group_config.vrid }}v{{ '4' if group_config['address'] | first | is_ipv4 else '6' }}
vmac_xmit_base
{% elif group_config.rfc3768_compatibility is vyos_defined %}
- use_vmac {{ group_config.interface }}v{{ group_config.vrid }}
+ use_vmac {{ group_config.interface }}v{{ group_config.vrid }}v{{ '4' if group_config['address'] | first | is_ipv4 else '6' }}
{% endif %}
{% if group_config.authentication is vyos_defined %}
authentication {
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/login/pam_radius_auth.conf.j2 b/data/templates/login/pam_radius_auth.conf.j2
index 1105b60e5..c61154753 100644
--- a/data/templates/login/pam_radius_auth.conf.j2
+++ b/data/templates/login/pam_radius_auth.conf.j2
@@ -16,7 +16,7 @@
{% if radius.server is vyos_defined %}
# server[:port] shared_secret timeout source_ip
{# .items() returns a tuple of two elements: key and value. 1 relates to the 2nd element i.e. the value and .priority relates to the key from the internal dict #}
-{% for server, options in radius.server.items() | sort(attribute='1.priority') if not options.disabled %}
+{% for server, options in radius.server.items() | sort(attribute='1.priority') if not 'disable' in options %}
{# RADIUS IPv6 servers must be specified in [] notation #}
{% if server | is_ipv4 %}
{{ server }}:{{ options.port }} {{ "%-25s" | format(options.key) }} {{ "%-10s" | format(options.timeout) }} {{ source_address.ipv4 if source_address.ipv4 is vyos_defined }}
@@ -33,4 +33,3 @@ mapped_priv_user radius_priv_user
vrf-name {{ radius.vrf }}
{% endif %}
{% endif %}
-
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/nhrp/nftables.conf.j2 b/data/templates/nhrp/nftables.conf.j2
new file mode 100644
index 000000000..a0d1f6d4c
--- /dev/null
+++ b/data/templates/nhrp/nftables.conf.j2
@@ -0,0 +1,17 @@
+#!/usr/sbin/nft -f
+
+{% if first_install is not vyos_defined %}
+delete table ip vyos_nhrp_filter
+{% endif %}
+table ip vyos_nhrp_filter {
+ chain VYOS_NHRP_OUTPUT {
+ type filter hook output priority 10; policy accept;
+{% if tunnel is vyos_defined %}
+{% for tun, tunnel_conf in tunnel.items() %}
+{% if if_tunnel[tun].source_address is vyos_defined %}
+ ip protocol gre ip saddr {{ if_tunnel[tun].source_address }} ip daddr 224.0.0.0/4 counter drop comment "VYOS_NHRP_{{ tun }}"
+{% endif %}
+{% endfor %}
+{% endif %}
+ }
+}
diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2
index 8418a2185..3194354e6 100644
--- a/data/templates/ocserv/ocserv_config.j2
+++ b/data/templates/ocserv/ocserv_config.j2
@@ -1,5 +1,9 @@
### generated by vpn_openconnect.py ###
+{% if listen_address is vyos_defined %}
+listen-host = {{ listen_address }}
+{% endif %}
+
tcp-port = {{ listen_ports.tcp }}
udp-port = {{ listen_ports.udp }}
@@ -7,7 +11,7 @@ run-as-user = nobody
run-as-group = daemon
{% if "radius" in authentication.mode %}
-auth = "radius [config=/run/ocserv/radiusclient.conf]"
+auth = "radius [config=/run/ocserv/radiusclient.conf{{ ',groupconfig=true' if authentication.radius.groupconfig is vyos_defined else '' }}]"
{% elif "local" in authentication.mode %}
{% if authentication.mode.local == "password-otp" %}
auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]"
@@ -56,36 +60,46 @@ 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 %}
+{% endfor %}
+{% endif %}
+{% if network_settings.tunnel_all_dns is vyos_defined %}
+{% if "yes" in network_settings.tunnel_all_dns %}
+tunnel-all-dns = true
+{% else %}
+tunnel-all-dns = false
{% endif %}
+{% 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 %}
+
+{% if authentication.group is vyos_defined %}
+# Group settings
+{% for grp in authentication.group %}
+select-group = {{ grp }}
+{% endfor %}
+{% endif %} \ No newline at end of file
diff --git a/data/templates/telegraf/override.conf.j2 b/data/templates/telegraf/override.conf.j2
new file mode 100644
index 000000000..7e3e4aaf5
--- /dev/null
+++ b/data/templates/telegraf/override.conf.j2
@@ -0,0 +1,16 @@
+{% 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
+Restart=always
+RestartSec=10
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/data/templates/zone_policy/nftables.j2 b/data/templates/zone_policy/nftables.j2
deleted file mode 100644
index fe941f9f8..000000000
--- a/data/templates/zone_policy/nftables.j2
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/usr/sbin/nft -f
-
-{% if cleanup_commands is vyos_defined %}
-{% for command in cleanup_commands %}
-{{ command }}
-{% endfor %}
-{% endif %}
-
-{% if zone is vyos_defined %}
-table ip filter {
-{% for zone_name, zone_conf in zone.items() if zone_conf.ipv4 %}
-{% if zone_conf.local_zone is vyos_defined %}
- chain VZONE_{{ zone_name }}_IN {
- iifname lo counter return
-{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is vyos_defined %}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endfor %}
- {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
- }
- chain VZONE_{{ zone_name }}_OUT {
- oifname lo counter return
-{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.name is vyos_defined %}
- oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }}
- oifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endfor %}
- {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
- }
-{% else %}
- chain VZONE_{{ zone_name }} {
- iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=False) }}
-{% if zone_conf.intra_zone_filtering is vyos_defined %}
- iifname { {{ zone_conf.interface | join(",") }} } counter return
-{% endif %}
-{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is vyos_defined %}
-{% if zone[from_zone].local_zone is not defined %}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endif %}
-{% endfor %}
- {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
- }
-{% endif %}
-{% endfor %}
-}
-
-table ip6 filter {
-{% for zone_name, zone_conf in zone.items() if zone_conf.ipv6 %}
-{% if zone_conf.local_zone is vyos_defined %}
- chain VZONE6_{{ zone_name }}_IN {
- iifname lo counter return
-{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is vyos_defined %}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endfor %}
- {{ zone_conf | nft_default_rule('zone6_' + zone_name) }}
- }
- chain VZONE6_{{ zone_name }}_OUT {
- oifname lo counter return
-{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.ipv6_name is vyos_defined %}
- oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }}
- oifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endfor %}
- {{ zone_conf | nft_default_rule('zone6_' + zone_name) }}
- }
-{% else %}
- chain VZONE6_{{ zone_name }} {
- iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=True) }}
-{% if zone_conf.intra_zone_filtering is vyos_defined %}
- iifname { {{ zone_conf.interface | join(",") }} } counter return
-{% endif %}
-{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is vyos_defined %}
-{% if zone[from_zone].local_zone is not defined %}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter return
-{% endif %}
-{% endfor %}
- {{ zone_conf | nft_default_rule('zone6_' + zone_name) }}
- }
-{% endif %}
-{% endfor %}
-}
-
-{% for zone_name, zone_conf in zone.items() %}
-{% if zone_conf.ipv4 %}
-{% if 'local_zone' in zone_conf %}
-insert rule ip filter VYOS_FW_LOCAL counter jump VZONE_{{ zone_name }}_IN
-insert rule ip filter VYOS_FW_OUTPUT counter jump VZONE_{{ zone_name }}_OUT
-{% else %}
-insert rule ip filter VYOS_FW_FORWARD oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
-{% endif %}
-{% endif %}
-{% if zone_conf.ipv6 %}
-{% if 'local_zone' in zone_conf %}
-insert rule ip6 filter VYOS_FW6_LOCAL counter jump VZONE6_{{ zone_name }}_IN
-insert rule ip6 filter VYOS_FW6_OUTPUT counter jump VZONE6_{{ zone_name }}_OUT
-{% else %}
-insert rule ip6 filter VYOS_FW6_FORWARD oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE6_{{ zone_name }}
-{% endif %}
-{% endif %}
-{% endfor %}
-
-{# Ensure that state-policy rule is first in the chain #}
-{% if firewall.state_policy is vyos_defined %}
-{% for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'] %}
-insert rule ip filter {{ chain }} jump VYOS_STATE_POLICY
-{% endfor %}
-{% for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] %}
-insert rule ip6 filter {{ chain }} jump VYOS_STATE_POLICY6
-{% endfor %}
-{% endif %}
-
-{% endif %}
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
new file mode 100644
index 000000000..cd815148e
--- /dev/null
+++ b/data/vyos-firewall-init.conf
@@ -0,0 +1,162 @@
+#!/usr/sbin/nft -f
+
+table ip vyos_static_nat {
+ chain PREROUTING {
+ type nat hook prerouting priority -100; policy accept;
+ counter jump VYOS_PRE_DNAT_HOOK
+ }
+
+ chain POSTROUTING {
+ type nat hook postrouting priority 100; policy accept;
+ counter jump VYOS_PRE_SNAT_HOOK
+ }
+
+ chain VYOS_PRE_DNAT_HOOK {
+ return
+ }
+
+ chain VYOS_PRE_SNAT_HOOK {
+ return
+ }
+}
+
+table ip nat {
+ chain PREROUTING {
+ type nat hook prerouting priority -100; policy accept;
+ counter jump VYOS_PRE_DNAT_HOOK
+ }
+
+ chain POSTROUTING {
+ type nat hook postrouting priority 100; policy accept;
+ counter jump VYOS_PRE_SNAT_HOOK
+ }
+
+ chain VYOS_PRE_DNAT_HOOK {
+ return
+ }
+
+ chain VYOS_PRE_SNAT_HOOK {
+ return
+ }
+}
+
+table ip6 nat {
+ chain PREROUTING {
+ type nat hook prerouting priority -100; policy accept;
+ counter jump VYOS_DNPT_HOOK
+ }
+
+ chain POSTROUTING {
+ type nat hook postrouting priority 100; policy accept;
+ counter jump VYOS_SNPT_HOOK
+ }
+
+ chain VYOS_DNPT_HOOK {
+ return
+ }
+
+ chain VYOS_SNPT_HOOK {
+ return
+ }
+}
+
+table inet mangle {
+ chain FORWARD {
+ type filter hook forward priority -150; policy accept;
+ }
+}
+
+table raw {
+ chain VYOS_TCP_MSS {
+ type filter hook forward priority -300; policy accept;
+ }
+
+ chain PREROUTING {
+ type filter hook prerouting priority -200; policy accept;
+ counter jump VYOS_CT_IGNORE
+ counter jump VYOS_CT_TIMEOUT
+ counter jump VYOS_CT_PREROUTING_HOOK
+ counter jump FW_CONNTRACK
+ notrack
+ }
+
+ chain OUTPUT {
+ type filter hook output priority -200; policy accept;
+ counter jump VYOS_CT_IGNORE
+ counter jump VYOS_CT_TIMEOUT
+ counter jump VYOS_CT_OUTPUT_HOOK
+ counter jump FW_CONNTRACK
+ notrack
+ }
+
+ ct helper rpc_tcp {
+ type "rpc" protocol tcp;
+ }
+
+ ct helper rpc_udp {
+ type "rpc" protocol udp;
+ }
+
+ ct helper tns_tcp {
+ type "tns" protocol tcp;
+ }
+
+ chain VYOS_CT_HELPER {
+ ct helper set "rpc_tcp" tcp dport {111} return
+ ct helper set "rpc_udp" udp dport {111} return
+ ct helper set "tns_tcp" tcp dport {1521,1525,1536} return
+ return
+ }
+
+ chain VYOS_CT_IGNORE {
+ return
+ }
+
+ chain VYOS_CT_TIMEOUT {
+ return
+ }
+
+ chain VYOS_CT_PREROUTING_HOOK {
+ return
+ }
+
+ chain VYOS_CT_OUTPUT_HOOK {
+ return
+ }
+
+ chain FW_CONNTRACK {
+ accept
+ }
+}
+
+table ip6 raw {
+ chain VYOS_TCP_MSS {
+ type filter hook forward priority -300; policy accept;
+ }
+
+ chain PREROUTING {
+ type filter hook prerouting priority -300; policy accept;
+ counter jump VYOS_CT_PREROUTING_HOOK
+ counter jump FW_CONNTRACK
+ notrack
+ }
+
+ chain OUTPUT {
+ type filter hook output priority -300; policy accept;
+ counter jump VYOS_CT_OUTPUT_HOOK
+ counter jump FW_CONNTRACK
+ notrack
+ }
+
+ chain VYOS_CT_PREROUTING_HOOK {
+ return
+ }
+
+ chain VYOS_CT_OUTPUT_HOOK {
+ return
+ }
+
+ chain FW_CONNTRACK {
+ accept
+ }
+}
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/firewall.xml.in b/interface-definitions/firewall.xml.in
index 2e9452dfd..d39dddc77 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -314,6 +314,40 @@
</tagNode>
</children>
</node>
+ <tagNode name="interface">
+ <properties>
+ <help>Interface name</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="in">
+ <properties>
+ <help>Forwarded packets on inbound interface</help>
+ </properties>
+ <children>
+ #include <include/firewall/name.xml.i>
+ </children>
+ </node>
+ <node name="out">
+ <properties>
+ <help>Forwarded packets on outbound interface</help>
+ </properties>
+ <children>
+ #include <include/firewall/name.xml.i>
+ </children>
+ </node>
+ <node name="local">
+ <properties>
+ <help>Packets destined for this router</help>
+ </properties>
+ <children>
+ #include <include/firewall/name.xml.i>
+ </children>
+ </node>
+ </children>
+ </tagNode>
<leafNode name="ip-src-route">
<properties>
<help>Policy for handling IPv4 packets with source route option</help>
@@ -342,8 +376,8 @@
</constraint>
</properties>
<children>
- #include <include/firewall/name-default-action.xml.i>
- #include <include/firewall/name-default-log.xml.i>
+ #include <include/firewall/default-action.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
@@ -383,49 +417,9 @@
</children>
</node>
#include <include/firewall/common-rule.xml.i>
- <node name="hop-limit">
- <properties>
- <help>Hop Limit</help>
- </properties>
- <children>
- <leafNode name="eq">
- <properties>
- <help>Value to match a hop limit equal to it</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>Hop limit equal to value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="gt">
- <properties>
- <help>Value to match a hop limit greater than or equal to it</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>Hop limit greater than value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="lt">
- <properties>
- <help>Value to match a hop limit less than or equal to it</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>Hop limit less than value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
+ #include <include/firewall/dscp.xml.i>
+ #include <include/firewall/packet-length.xml.i>
+ #include <include/firewall/hop-limit.xml.i>
<node name="icmpv6">
<properties>
<help>ICMPv6 type and code information</help>
@@ -433,7 +427,7 @@
<children>
<leafNode name="code">
<properties>
- <help>ICMPv6 code (0-255)</help>
+ <help>ICMPv6 code</help>
<valueHelp>
<format>u32:0-255</format>
<description>ICMPv6 code (0-255)</description>
@@ -445,7 +439,7 @@
</leafNode>
<leafNode name="type">
<properties>
- <help>ICMPv6 type (0-255)</help>
+ <help>ICMPv6 type</help>
<valueHelp>
<format>u32:0-255</format>
<description>ICMPv6 type (0-255)</description>
@@ -530,8 +524,8 @@
</constraint>
</properties>
<children>
- #include <include/firewall/name-default-action.xml.i>
- #include <include/firewall/name-default-log.xml.i>
+ #include <include/firewall/default-action.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
@@ -571,6 +565,8 @@
</children>
</node>
#include <include/firewall/common-rule.xml.i>
+ #include <include/firewall/dscp.xml.i>
+ #include <include/firewall/packet-length.xml.i>
<node name="icmp">
<properties>
<help>ICMP type and code information</help>
@@ -578,7 +574,7 @@
<children>
<leafNode name="code">
<properties>
- <help>ICMP code (0-255)</help>
+ <help>ICMP code</help>
<valueHelp>
<format>u32:0-255</format>
<description>ICMP code (0-255)</description>
@@ -590,7 +586,7 @@
</leafNode>
<leafNode name="type">
<properties>
- <help>ICMP type (0-255)</help>
+ <help>ICMP type</help>
<valueHelp>
<format>u32:0-255</format>
<description>ICMP type (0-255)</description>
@@ -603,49 +599,7 @@
#include <include/firewall/icmp-type-name.xml.i>
</children>
</node>
- <node name="ttl">
- <properties>
- <help>Time to live limit</help>
- </properties>
- <children>
- <leafNode name="eq">
- <properties>
- <help>Value to match a ttl equal to it</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ttl equal to value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="gt">
- <properties>
- <help>Value to match a ttl greater than or equal to it</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ttl greater than value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="lt">
- <properties>
- <help>Value to match a ttl less than or equal to it</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ttl less than value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
+ #include <include/firewall/ttl.xml.i>
</children>
</tagNode>
</children>
@@ -788,6 +742,143 @@
</properties>
<defaultValue>disable</defaultValue>
</leafNode>
+ <tagNode name="zone">
+ <properties>
+ <help>Zone-policy</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Zone name</description>
+ </valueHelp>
+ <constraint>
+ <regex>[a-zA-Z0-9][\w\-\.]*</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
+ <leafNode name="default-action">
+ <properties>
+ <help>Default-action for traffic coming into this zone</help>
+ <completionHelp>
+ <list>drop reject</list>
+ </completionHelp>
+ <valueHelp>
+ <format>drop</format>
+ <description>Drop silently</description>
+ </valueHelp>
+ <valueHelp>
+ <format>reject</format>
+ <description>Drop and notify source</description>
+ </valueHelp>
+ <constraint>
+ <regex>(drop|reject)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>drop</defaultValue>
+ </leafNode>
+ <tagNode name="from">
+ <properties>
+ <help>Zone from which to filter traffic</help>
+ <completionHelp>
+ <path>zone-policy zone</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="firewall">
+ <properties>
+ <help>Firewall options</help>
+ </properties>
+ <children>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>IPv6 firewall ruleset</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="name">
+ <properties>
+ <help>IPv4 firewall ruleset</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ <leafNode name="interface">
+ <properties>
+ <help>Interface associated with zone</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface associated with zone</description>
+ </valueHelp>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <node name="intra-zone-filtering">
+ <properties>
+ <help>Intra-zone filtering</help>
+ </properties>
+ <children>
+ <leafNode name="action">
+ <properties>
+ <help>Action for intra-zone traffic</help>
+ <completionHelp>
+ <list>accept drop</list>
+ </completionHelp>
+ <valueHelp>
+ <format>accept</format>
+ <description>Accept traffic</description>
+ </valueHelp>
+ <valueHelp>
+ <format>drop</format>
+ <description>Drop silently</description>
+ </valueHelp>
+ <constraint>
+ <regex>(accept|drop)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="firewall">
+ <properties>
+ <help>Use the specified firewall chain</help>
+ </properties>
+ <children>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>IPv6 firewall ruleset</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="name">
+ <properties>
+ <help>IPv4 firewall ruleset</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <leafNode name="local-zone">
+ <properties>
+ <help>Zone to be local-zone</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
</children>
</node>
</interfaceDefinition>
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 d2bcea62a..70176144d 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -1135,6 +1135,19 @@
</leafNode>
</children>
</node>
+ <node name="peer-type">
+ <properties>
+ <help>Peer type</help>
+ </properties>
+ <children>
+ <leafNode name="multipath-relax">
+ <properties>
+ <help>Allow load sharing across routes learned from different peer types</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
<leafNode name="cluster-id">
diff --git a/interface-definitions/include/firewall/name-default-action.xml.i b/interface-definitions/include/firewall/default-action.xml.i
index 512b0296f..92a2fcaaf 100644
--- a/interface-definitions/include/firewall/name-default-action.xml.i
+++ b/interface-definitions/include/firewall/default-action.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from firewall/name-default-action.xml.i -->
+<!-- include start from firewall/default-action.xml.i -->
<leafNode name="default-action">
<properties>
<help>Default-action for rule-set</help>
@@ -21,5 +21,6 @@
<regex>(drop|reject|accept)</regex>
</constraint>
</properties>
+ <defaultValue>drop</defaultValue>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/firewall/dscp.xml.i b/interface-definitions/include/firewall/dscp.xml.i
new file mode 100644
index 000000000..796bab548
--- /dev/null
+++ b/interface-definitions/include/firewall/dscp.xml.i
@@ -0,0 +1,36 @@
+<!-- include start from firewall/dscp.xml.i -->
+<leafNode name="dscp">
+ <properties>
+ <help>DSCP value</help>
+ <valueHelp>
+ <format>u32:0-63</format>
+ <description>DSCP value to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;start-end&gt;</format>
+ <description>DSCP range to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-63"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="dscp-exclude">
+ <properties>
+ <help>DSCP value not to match</help>
+ <valueHelp>
+ <format>u32:0-63</format>
+ <description>DSCP value not to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;start-end&gt;</format>
+ <description>DSCP range not to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-63"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/enable-default-log.xml.i b/interface-definitions/include/firewall/enable-default-log.xml.i
new file mode 100644
index 000000000..0efd8341b
--- /dev/null
+++ b/interface-definitions/include/firewall/enable-default-log.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from firewall/enable-default-log.xml.i -->
+<leafNode name="enable-default-log">
+ <properties>
+ <help>Log packets hitting default-action</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/eq.xml.i b/interface-definitions/include/firewall/eq.xml.i
new file mode 100644
index 000000000..e1b4f37a2
--- /dev/null
+++ b/interface-definitions/include/firewall/eq.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from firewall/eq.xml.i -->
+<leafNode name="eq">
+ <properties>
+ <help>Match on equal value</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>Equal to value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/gt.xml.i b/interface-definitions/include/firewall/gt.xml.i
new file mode 100644
index 000000000..c879171ee
--- /dev/null
+++ b/interface-definitions/include/firewall/gt.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from firewall/gt.xml.i -->
+<leafNode name="gt">
+ <properties>
+ <help>Match on greater then value</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>Greater then value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/hop-limit.xml.i b/interface-definitions/include/firewall/hop-limit.xml.i
new file mode 100644
index 000000000..d375dc985
--- /dev/null
+++ b/interface-definitions/include/firewall/hop-limit.xml.i
@@ -0,0 +1,12 @@
+<!-- include start from firewall/hop-limit.xml.i -->
+<node name="hop-limit">
+ <properties>
+ <help>Hop limit</help>
+ </properties>
+ <children>
+ #include <include/firewall/eq.xml.i>
+ #include <include/firewall/gt.xml.i>
+ #include <include/firewall/lt.xml.i>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/lt.xml.i b/interface-definitions/include/firewall/lt.xml.i
new file mode 100644
index 000000000..77894d3ce
--- /dev/null
+++ b/interface-definitions/include/firewall/lt.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from firewall/lt.xml.i -->
+<leafNode name="lt">
+ <properties>
+ <help>Match on less then value</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>Less then value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/name-default-log.xml.i b/interface-definitions/include/firewall/name-default-log.xml.i
deleted file mode 100644
index 1d0ff9497..000000000
--- a/interface-definitions/include/firewall/name-default-log.xml.i
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- include start from firewall/name-default-log.xml.i -->
-<leafNode name="enable-default-log">
- <properties>
- <help>Option to log packets hitting default-action</help>
- <valueless/>
- </properties>
-</leafNode>
-<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/name.xml.i b/interface-definitions/include/firewall/name.xml.i
new file mode 100644
index 000000000..231b9b144
--- /dev/null
+++ b/interface-definitions/include/firewall/name.xml.i
@@ -0,0 +1,18 @@
+<!-- include start from firewall/name.xml.i -->
+<leafNode name="name">
+ <properties>
+ <help>Local IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<leafNode name="ipv6-name">
+ <properties>
+ <help>Local IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include end from firewall/name.xml.i --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/packet-length.xml.i b/interface-definitions/include/firewall/packet-length.xml.i
new file mode 100644
index 000000000..91f08314a
--- /dev/null
+++ b/interface-definitions/include/firewall/packet-length.xml.i
@@ -0,0 +1,36 @@
+<!-- include start from firewall/packet-length.xml.i -->
+<leafNode name="packet-length">
+ <properties>
+ <help>Payload size in bytes, including header and data to match</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Packet length to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;start-end&gt;</format>
+ <description>Packet length range to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="packet-length-exclude">
+ <properties>
+ <help>Payload size in bytes, including header and data not to match</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Packet length not to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;start-end&gt;</format>
+ <description>Packet length range not to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i
index b99896687..fc0da3135 100644
--- a/interface-definitions/include/firewall/tcp-flags.xml.i
+++ b/interface-definitions/include/firewall/tcp-flags.xml.i
@@ -114,6 +114,22 @@
</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"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<!-- include end -->
diff --git a/interface-definitions/include/firewall/ttl.xml.i b/interface-definitions/include/firewall/ttl.xml.i
new file mode 100644
index 000000000..9c782a9a5
--- /dev/null
+++ b/interface-definitions/include/firewall/ttl.xml.i
@@ -0,0 +1,12 @@
+<!-- include start from firewall/ttl.xml.i -->
+<node name="ttl">
+ <properties>
+ <help>Time to live limit</help>
+ </properties>
+ <children>
+ #include <include/firewall/eq.xml.i>
+ #include <include/firewall/gt.xml.i>
+ #include <include/firewall/lt.xml.i>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/inbound-interface.xml.i b/interface-definitions/include/inbound-interface.xml.i
new file mode 100644
index 000000000..3289bbf8f
--- /dev/null
+++ b/interface-definitions/include/inbound-interface.xml.i
@@ -0,0 +1,11 @@
+<!-- include start from inbound-interface.xml.i -->
+<leafNode name="inbound-interface">
+ <properties>
+ <help>Inbound interface of NAT traffic</help>
+ <completionHelp>
+ <list>any</list>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-firewall-vif-c.xml.i b/interface-definitions/include/interface/interface-firewall-vif-c.xml.i
deleted file mode 100644
index 1bc235fcb..000000000
--- a/interface-definitions/include/interface/interface-firewall-vif-c.xml.i
+++ /dev/null
@@ -1,79 +0,0 @@
-<!-- include start from interface/interface-firewall-vif-c.xml.i -->
-<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../../../@).$VAR(../../@).$VAR(../@)">
- <properties>
- <priority>615</priority>
- <help>Firewall options</help>
- </properties>
- <children>
- <node name="in">
- <properties>
- <help>forwarded packets on inbound interface</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Inbound IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Inbound IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- <node name="out">
- <properties>
- <help>forwarded packets on outbound interface</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Outbound IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Outbound IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- <node name="local">
- <properties>
- <help>packets destined for this router</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Local IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Local IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
-</node>
-<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-firewall-vif.xml.i b/interface-definitions/include/interface/interface-firewall-vif.xml.i
deleted file mode 100644
index a37ac5c4a..000000000
--- a/interface-definitions/include/interface/interface-firewall-vif.xml.i
+++ /dev/null
@@ -1,79 +0,0 @@
-<!-- include start from interface/interface-firewall-vif.xml.i -->
-<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../../@).$VAR(../@)">
- <properties>
- <priority>615</priority>
- <help>Firewall options</help>
- </properties>
- <children>
- <node name="in">
- <properties>
- <help>forwarded packets on inbound interface</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Inbound IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Inbound IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- <node name="out">
- <properties>
- <help>forwarded packets on outbound interface</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Outbound IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Outbound IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- <node name="local">
- <properties>
- <help>packets destined for this router</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Local IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Local IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
-</node>
-<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-firewall.xml.i b/interface-definitions/include/interface/interface-firewall.xml.i
deleted file mode 100644
index b3f20c3bf..000000000
--- a/interface-definitions/include/interface/interface-firewall.xml.i
+++ /dev/null
@@ -1,79 +0,0 @@
-<!-- include start from interface/interface-firewall.xml.i -->
-<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../@)">
- <properties>
- <priority>615</priority>
- <help>Firewall options</help>
- </properties>
- <children>
- <node name="in">
- <properties>
- <help>forwarded packets on inbound interface</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Inbound IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Inbound IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- <node name="out">
- <properties>
- <help>forwarded packets on outbound interface</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Outbound IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Outbound IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- <node name="local">
- <properties>
- <help>packets destined for this router</help>
- </properties>
- <children>
- <leafNode name="name">
- <properties>
- <help>Local IPv4 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="ipv6-name">
- <properties>
- <help>Local IPv6 firewall ruleset name for interface</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
-</node>
-<!-- include end -->
diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i
index c1af9f9e3..916349ade 100644
--- a/interface-definitions/include/interface/vif-s.xml.i
+++ b/interface-definitions/include/interface/vif-s.xml.i
@@ -18,7 +18,6 @@
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-firewall-vif.xml.i>
#include <include/interface/interface-policy-vif.xml.i>
<leafNode name="protocol">
<properties>
@@ -68,7 +67,6 @@
#include <include/interface/mtu-68-16000.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vrf.xml.i>
- #include <include/interface/interface-firewall-vif-c.xml.i>
#include <include/interface/interface-policy-vif-c.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i
index 57ef8d64c..73a8c98ff 100644
--- a/interface-definitions/include/interface/vif.xml.i
+++ b/interface-definitions/include/interface/vif.xml.i
@@ -18,7 +18,6 @@
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-firewall-vif.xml.i>
#include <include/interface/interface-policy-vif.xml.i>
<leafNode name="egress-qos">
<properties>
diff --git a/interface-definitions/include/ipv4-address-prefix.xml.i b/interface-definitions/include/ipv4-address-prefix.xml.i
new file mode 100644
index 000000000..f5be6f1fe
--- /dev/null
+++ b/interface-definitions/include/ipv4-address-prefix.xml.i
@@ -0,0 +1,19 @@
+<!-- include start from ipv4-address-prefix.xml.i -->
+<leafNode name="address">
+ <properties>
+ <help>IP address, prefix</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 prefix to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/nat/protocol.xml.i b/interface-definitions/include/nat/protocol.xml.i
new file mode 100644
index 000000000..54e7ff00d
--- /dev/null
+++ b/interface-definitions/include/nat/protocol.xml.i
@@ -0,0 +1,34 @@
+<!-- include start from nat/protocol.xml.i -->
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
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/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i
index 059a89f24..065925319 100644
--- a/interface-definitions/include/version/firewall-version.xml.i
+++ b/interface-definitions/include/version/firewall-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/firewall-version.xml.i -->
-<syntaxVersion component='firewall' version='7'></syntaxVersion>
+<syntaxVersion component='firewall' version='8'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in
index 8b6c6ef62..41e4a68a8 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -56,7 +56,6 @@
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
#include <include/interface/mirror.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<leafNode name="hash-policy">
<properties>
diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in
index 48ee1efbc..1e11cd4c6 100644
--- a/interface-definitions/interfaces-bridge.xml.in
+++ b/interface-definitions/interfaces-bridge.xml.in
@@ -41,7 +41,6 @@
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<leafNode name="forwarding-delay">
<properties>
diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in
index 01438de31..fb36741f7 100644
--- a/interface-definitions/interfaces-dummy.xml.in
+++ b/interface-definitions/interfaces-dummy.xml.in
@@ -19,7 +19,6 @@
#include <include/interface/address-ipv4-ipv6.xml.i>
#include <include/interface/description.xml.i>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<node name="ip">
<properties>
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index c821f04b2..77f130e1c 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -31,7 +31,6 @@
</leafNode>
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<leafNode name="duplex">
<properties>
@@ -94,6 +93,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="rfs">
+ <properties>
+ <help>Enable Receive Flow Steering</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="sg">
<properties>
<help>Enable Scatter-Gather</help>
diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in
index 6e8a8fee2..b959c787d 100644
--- a/interface-definitions/interfaces-geneve.xml.in
+++ b/interface-definitions/interfaces-geneve.xml.in
@@ -23,7 +23,6 @@
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1450-16000.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<node name="parameters">
<properties>
diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in
index 2164bfa4e..d01c760f8 100644
--- a/interface-definitions/interfaces-input.xml.in
+++ b/interface-definitions/interfaces-input.xml.in
@@ -19,7 +19,6 @@
<children>
#include <include/interface/description.xml.i>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
#include <include/interface/redirect.xml.i>
</children>
diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in
index 6a85064cd..bde68dd5a 100644
--- a/interface-definitions/interfaces-l2tpv3.xml.in
+++ b/interface-definitions/interfaces-l2tpv3.xml.in
@@ -32,7 +32,6 @@
<defaultValue>5000</defaultValue>
</leafNode>
#include <include/interface/disable.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<leafNode name="encapsulation">
<properties>
diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in
index adb48813f..5c9f4cd76 100644
--- a/interface-definitions/interfaces-macsec.xml.in
+++ b/interface-definitions/interfaces-macsec.xml.in
@@ -21,7 +21,6 @@
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
#include <include/interface/mirror.xml.i>
<node name="security">
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index 6cbd91ff4..3876e31da 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -34,7 +34,6 @@
</children>
</node>
#include <include/interface/description.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<leafNode name="device-type">
<properties>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 9674cfc0e..84f76a7ee 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -19,7 +19,6 @@
#include <include/pppoe-access-concentrator.xml.i>
#include <include/interface/authentication.xml.i>
#include <include/interface/dial-on-demand.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
#include <include/interface/no-default-route.xml.i>
#include <include/interface/default-route-distance.xml.i>
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index 53e6445fa..4eb9bf111 100644
--- a/interface-definitions/interfaces-pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in
@@ -28,7 +28,6 @@
#include <include/source-interface-ethernet.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mirror.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<leafNode name="mode">
<properties>
diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index 98ff878ba..fe49d337a 100644
--- a/interface-definitions/interfaces-tunnel.xml.in
+++ b/interface-definitions/interfaces-tunnel.xml.in
@@ -29,7 +29,6 @@
#include <include/source-address-ipv4-ipv6.xml.i>
#include <include/interface/tunnel-remote.xml.i>
#include <include/source-interface.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<leafNode name="6rd-prefix">
<properties>
diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in
index aa83a04b2..eeaea0dc3 100644
--- a/interface-definitions/interfaces-vti.xml.in
+++ b/interface-definitions/interfaces-vti.xml.in
@@ -25,7 +25,6 @@
#include <include/interface/mirror.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vrf.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index faa3dd5e0..4902ff36d 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -54,7 +54,6 @@
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1200-16000.xml.i>
#include <include/interface/mirror.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<leafNode name="mtu">
<defaultValue>1450</defaultValue>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index 4a1b4ac68..23f50d146 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -21,7 +21,6 @@
#include <include/interface/disable.xml.i>
#include <include/port-number.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
#include <include/interface/mirror.xml.i>
<leafNode name="mtu">
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index daee770a9..9e7fc29bc 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -20,7 +20,6 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
<node name="capabilities">
<properties>
diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in
index 3071e6091..b0b8367dc 100644
--- a/interface-definitions/interfaces-wwan.xml.in
+++ b/interface-definitions/interfaces-wwan.xml.in
@@ -39,7 +39,6 @@
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/dial-on-demand.xml.i>
- #include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vrf.xml.i>
diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in
index 9295b631f..501ff05d3 100644
--- a/interface-definitions/nat.xml.in
+++ b/interface-definitions/nat.xml.in
@@ -14,15 +14,7 @@
#include <include/nat-rule.xml.i>
<tagNode name="rule">
<children>
- <leafNode name="inbound-interface">
- <properties>
- <help>Inbound interface of NAT traffic</help>
- <completionHelp>
- <list>any</list>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
- </completionHelp>
- </properties>
- </leafNode>
+ #include <include/inbound-interface.xml.i>
<node name="translation">
<properties>
<help>Inside NAT IP (destination NAT only)</help>
@@ -65,6 +57,17 @@
<children>
#include <include/nat-rule.xml.i>
<tagNode name="rule">
+ <properties>
+ <help>Rule number for NAT</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of NAT rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
<children>
#include <include/nat-interface.xml.i>
<node name="translation">
@@ -110,6 +113,38 @@
</tagNode>
</children>
</node>
+ <node name="static">
+ <properties>
+ <help>Static NAT (one-to-one)</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Rule number for NAT</help>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ <node name="destination">
+ <properties>
+ <help>NAT destination parameters</help>
+ </properties>
+ <children>
+ #include <include/ipv4-address-prefix.xml.i>
+ </children>
+ </node>
+ #include <include/inbound-interface.xml.i>
+ <node name="translation">
+ <properties>
+ <help>Translation address or prefix</help>
+ </properties>
+ <children>
+ #include <include/ipv4-address-prefix.xml.i>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
</children>
</node>
</interfaceDefinition>
diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in
index bde1a6f8d..dab4543e0 100644
--- a/interface-definitions/nat66.xml.in
+++ b/interface-definitions/nat66.xml.in
@@ -50,6 +50,7 @@
</completionHelp>
</properties>
</leafNode>
+ #include <include/nat/protocol.xml.i>
<node name="destination">
<properties>
<help>IPv6 destination prefix options</help>
@@ -72,6 +73,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-port.xml.i>
</children>
</node>
<node name="source">
@@ -96,6 +98,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-port.xml.i>
</children>
</node>
<node name="translation">
@@ -128,6 +131,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-translation-port.xml.i>
</children>
</node>
</children>
@@ -179,6 +183,7 @@
</completionHelp>
</properties>
</leafNode>
+ #include <include/nat/protocol.xml.i>
<node name="destination">
<properties>
<help>IPv6 destination prefix options</help>
@@ -211,6 +216,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-port.xml.i>
</children>
</node>
<node name="source">
@@ -245,6 +251,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-port.xml.i>
</children>
</node>
<node name="translation">
@@ -269,6 +276,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-translation-port.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in
index d969613b1..8619e839e 100644
--- a/interface-definitions/policy-local-route.xml.in
+++ b/interface-definitions/policy-local-route.xml.in
@@ -6,6 +6,7 @@
<node name="local-route" owner="${vyos_conf_scripts_dir}/policy-local-route.py">
<properties>
<help>IPv4 policy route of local traffic</help>
+ <priority>500</priority>
</properties>
<children>
<tagNode name="rule">
@@ -96,6 +97,7 @@
<node name="local-route6" owner="${vyos_conf_scripts_dir}/policy-local-route.py">
<properties>
<help>IPv6 policy route of local traffic</help>
+ <priority>500</priority>
</properties>
<children>
<tagNode name="rule">
diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in
index a10c9b08f..f480f3bd5 100644
--- a/interface-definitions/policy-route.xml.in
+++ b/interface-definitions/policy-route.xml.in
@@ -12,7 +12,7 @@
</properties>
<children>
#include <include/generic-description.xml.i>
- #include <include/firewall/name-default-log.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
<tagNode name="rule">
<properties>
<help>Policy rule number</help>
@@ -47,6 +47,9 @@
</children>
</node>
#include <include/policy/route-common-rule-ipv6.xml.i>
+ #include <include/firewall/dscp.xml.i>
+ #include <include/firewall/packet-length.xml.i>
+ #include <include/firewall/hop-limit.xml.i>
</children>
</tagNode>
</children>
@@ -61,7 +64,7 @@
</properties>
<children>
#include <include/generic-description.xml.i>
- #include <include/firewall/name-default-log.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
<tagNode name="rule">
<properties>
<help>Policy rule number</help>
@@ -96,6 +99,9 @@
</children>
</node>
#include <include/policy/route-common-rule.xml.i>
+ #include <include/firewall/dscp.xml.i>
+ #include <include/firewall/packet-length.xml.i>
+ #include <include/firewall/ttl.xml.i>
</children>
</tagNode>
</children>
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/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in
index 68762ff9a..4535d3990 100644
--- a/interface-definitions/protocols-rpki.xml.in
+++ b/interface-definitions/protocols-rpki.xml.in
@@ -12,15 +12,15 @@
<help>RPKI cache server address</help>
<valueHelp>
<format>ipv4</format>
- <description>IP address of NTP server</description>
+ <description>IP address of RPKI server</description>
</valueHelp>
<valueHelp>
<format>ipv6</format>
- <description>IPv6 address of NTP server</description>
+ <description>IPv6 address of RPKI server</description>
</valueHelp>
<valueHelp>
<format>hostname</format>
- <description>Fully qualified domain name of NTP server</description>
+ <description>Fully qualified domain name of RPKI server</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
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-pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in
index 50f42849b..c88f9e950 100644
--- a/interface-definitions/service-pppoe-server.xml.in
+++ b/interface-definitions/service-pppoe-server.xml.in
@@ -90,7 +90,7 @@
<description>VLAN monitor range for the automatic creation of vlans (e.g. 1-4094)</description>
</valueHelp>
<constraint>
- <validator name="range" argument="--min=1 --max=4094"/>
+ <validator name="numeric" argument="--range 1-4094"/>
</constraint>
<multi/>
</properties>
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-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in
index 14f12b569..5810a97c6 100644
--- a/interface-definitions/system-conntrack.xml.in
+++ b/interface-definitions/system-conntrack.xml.in
@@ -259,13 +259,13 @@
</leafNode>
<leafNode name="max-retrans">
<properties>
- <help>TCP maximum retransmit attempts</help>
+ <help>Maximum number of packets that can be retransmitted without received an ACK</help>
<valueHelp>
- <format>u32:1-2147483647</format>
- <description>Generic connection timeout in seconds</description>
+ <format>u32:1-255</format>
+ <description>Number of packets to be retransmitted</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-2147483647"/>
+ <validator name="numeric" argument="--range 1-255"/>
</constraint>
</properties>
<defaultValue>3</defaultValue>
diff --git a/interface-definitions/system-option.xml.in b/interface-definitions/system-option.xml.in
index 8cd25799b..a9fed81fe 100644
--- a/interface-definitions/system-option.xml.in
+++ b/interface-definitions/system-option.xml.in
@@ -36,7 +36,7 @@
<properties>
<help>System keyboard layout, type ISO2</help>
<completionHelp>
- <list>us fr de fi no dk dvorak</list>
+ <list>us fr de es fi jp106 no dk dvorak</list>
</completionHelp>
<valueHelp>
<format>us</format>
@@ -51,10 +51,18 @@
<description>Germany</description>
</valueHelp>
<valueHelp>
+ <format>es</format>
+ <description>Spain</description>
+ </valueHelp>
+ <valueHelp>
<format>fi</format>
<description>Finland</description>
</valueHelp>
<valueHelp>
+ <format>jp106</format>
+ <description>Japan</description>
+ </valueHelp>
+ <valueHelp>
<format>no</format>
<description>Norway</description>
</valueHelp>
@@ -66,6 +74,10 @@
<format>dvorak</format>
<description>Dvorak</description>
</valueHelp>
+ <constraint>
+ <regex>(us|fr|de|es|fi|jp106|no|dk|dvorak)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid keyboard layout</constraintErrorMessage>
</properties>
<defaultValue>us</defaultValue>
</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..bc7f78e79 100644
--- a/interface-definitions/vpn-openconnect.xml.in
+++ b/interface-definitions/vpn-openconnect.xml.in
@@ -50,6 +50,16 @@
</leafNode>
</children>
</node>
+ <leafNode name="group">
+ <properties>
+ <help>Group that a client is allowed to select (from a list). Maps to RADIUS Class attribute.</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Group string. The group may be followed by a user-friendly name in brackets: group1[First Group]</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+ </leafNode>
#include <include/auth-local-users.xml.i>
<node name="local-users">
<children>
@@ -144,10 +154,19 @@
</properties>
<defaultValue>2</defaultValue>
</leafNode>
+ <leafNode name="groupconfig">
+ <properties>
+ <help>If the groupconfig option is set, then config-per-user will be overriden, and all configuration will be read from radius.</help>
+ </properties>
+ </leafNode>
</children>
</node>
</children>
</node>
+ #include <include/listen-address-ipv4.xml.i>
+ <leafNode name="listen-address">
+ <defaultValue>0.0.0.0</defaultValue>
+ </leafNode>
<node name="listen-ports">
<properties>
<help>Specify custom ports to use for client connections</help>
@@ -265,6 +284,39 @@
</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>
+ <leafNode name="tunnel-all-dns">
+ <properties>
+ <help>If the tunnel-all-dns option is set to yes, tunnel all DNS queries via the VPN. This is the default when a default route is set.</help>
+ <completionHelp>
+ <list>yes no</list>
+ </completionHelp>
+ <valueHelp>
+ <format>yes</format>
+ <description>Enable tunneling of all DNS traffic</description>
+ </valueHelp>
+ <valueHelp>
+ <format>no</format>
+ <description>Disable tunneling of all DNS traffic</description>
+ </valueHelp>
+ <constraint>
+ <regex>(yes|no)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>no</defaultValue>
+ </leafNode>
</children>
</node>
</children>
diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in
deleted file mode 100644
index dca4c59d1..000000000
--- a/interface-definitions/zone-policy.xml.in
+++ /dev/null
@@ -1,148 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="zone-policy" owner="${vyos_conf_scripts_dir}/zone_policy.py">
- <properties>
- <help>Configure zone-policy</help>
- <priority>250</priority>
- </properties>
- <children>
- <tagNode name="zone">
- <properties>
- <help>Zone name</help>
- <valueHelp>
- <format>txt</format>
- <description>Zone name</description>
- </valueHelp>
- <constraint>
- <regex>[a-zA-Z0-9][\w\-\.]*</regex>
- </constraint>
- </properties>
- <children>
- #include <include/generic-description.xml.i>
- #include <include/firewall/name-default-log.xml.i>
- <leafNode name="default-action">
- <properties>
- <help>Default-action for traffic coming into this zone</help>
- <completionHelp>
- <list>drop reject</list>
- </completionHelp>
- <valueHelp>
- <format>drop</format>
- <description>Drop silently</description>
- </valueHelp>
- <valueHelp>
- <format>reject</format>
- <description>Drop and notify source</description>
- </valueHelp>
- <constraint>
- <regex>(drop|reject)</regex>
- </constraint>
- </properties>
- <defaultValue>drop</defaultValue>
- </leafNode>
- <tagNode name="from">
- <properties>
- <help>Zone from which to filter traffic</help>
- <completionHelp>
- <path>zone-policy zone</path>
- </completionHelp>
- </properties>
- <children>
- <node name="firewall">
- <properties>
- <help>Firewall options</help>
- </properties>
- <children>
- <leafNode name="ipv6-name">
- <properties>
- <help>IPv6 firewall ruleset</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="name">
- <properties>
- <help>IPv4 firewall ruleset</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
- </tagNode>
- <leafNode name="interface">
- <properties>
- <help>Interface associated with zone</help>
- <valueHelp>
- <format>txt</format>
- <description>Interface associated with zone</description>
- </valueHelp>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces.py</script>
- </completionHelp>
- <multi/>
- </properties>
- </leafNode>
- <node name="intra-zone-filtering">
- <properties>
- <help>Intra-zone filtering</help>
- </properties>
- <children>
- <leafNode name="action">
- <properties>
- <help>Action for intra-zone traffic</help>
- <completionHelp>
- <list>accept drop</list>
- </completionHelp>
- <valueHelp>
- <format>accept</format>
- <description>Accept traffic</description>
- </valueHelp>
- <valueHelp>
- <format>drop</format>
- <description>Drop silently</description>
- </valueHelp>
- <constraint>
- <regex>(accept|drop)</regex>
- </constraint>
- </properties>
- </leafNode>
- <node name="firewall">
- <properties>
- <help>Use the specified firewall chain</help>
- </properties>
- <children>
- <leafNode name="ipv6-name">
- <properties>
- <help>IPv6 firewall ruleset</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="name">
- <properties>
- <help>IPv4 firewall ruleset</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
- </node>
- <leafNode name="local-zone">
- <properties>
- <help>Zone to be local-zone</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </node>
-</interfaceDefinition>
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/dns-forwarding.xml.in b/op-mode-definitions/dns-forwarding.xml.in
index 5dea5b91b..c8ca117be 100644
--- a/op-mode-definitions/dns-forwarding.xml.in
+++ b/op-mode-definitions/dns-forwarding.xml.in
@@ -19,26 +19,6 @@
</node>
</children>
</node>
- <node name="dns">
- <properties>
- <help>Show DNS information</help>
- </properties>
- <children>
- <node name="forwarding">
- <properties>
- <help>Show DNS forwarding information</help>
- </properties>
- <children>
- <leafNode name="statistics">
- <properties>
- <help>Show DNS forwarding statistics</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/dns_forwarding_statistics.py</command>
- </leafNode>
- </children>
- </node>
- </children>
- </node>
</children>
</node>
<node name="show">
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..ce0544390 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>
@@ -45,7 +45,7 @@
<command>${vyos_op_scripts_dir}/show_nat_translations.py --type=source --verbose</command>
</node>
</children>
- <command>${vyos_op_scripts_dir}/show_nat_translations.py --type=source</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet</command>
</node>
</children>
</node>
@@ -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>
@@ -87,7 +87,7 @@
<command>${vyos_op_scripts_dir}/show_nat_translations.py --type=destination --verbose</command>
</node>
</children>
- <command>${vyos_op_scripts_dir}/show_nat_translations.py --type=destination</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in
index 1ec46eb11..25aa04d59 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>
@@ -45,7 +45,7 @@
<command>${vyos_op_scripts_dir}/show_nat66_translations.py --type=source --verbose</command>
</node>
</children>
- <command>${vyos_op_scripts_dir}/show_nat66_translations.py --type=source</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6</command>
</node>
</children>
</node>
@@ -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>
@@ -87,7 +87,7 @@
<command>${vyos_op_scripts_dir}/show_nat66_translations.py --type=destination --verbose</command>
</node>
</children>
- <command>${vyos_op_scripts_dir}/show_nat66_translations.py --type=destination</command>
+ <command>${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6</command>
</node>
</children>
</node>
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/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in
index 60ed28b6f..bd32992aa 100644
--- a/op-mode-definitions/show-system.xml.in
+++ b/op-mode-definitions/show-system.xml.in
@@ -142,7 +142,7 @@
<properties>
<help>Show summary of system processes</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_uptime.py</command>
+ <command>${vyos_op_scripts_dir}/uptime.py show</command>
</leafNode>
<leafNode name="tree">
<properties>
@@ -162,13 +162,13 @@
<properties>
<help>Show filesystem usage</help>
</properties>
- <command>df -h -x squashfs</command>
+ <command>${vyos_op_scripts_dir}/storage.py show</command>
</leafNode>
<leafNode name="uptime">
<properties>
<help>Show system uptime and load averages</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_uptime.py</command>
+ <command>${vyos_op_scripts_dir}/uptime.py show</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in
index a98cf8ff2..8c9e76651 100644
--- a/op-mode-definitions/vpn-ipsec.xml.in
+++ b/op-mode-definitions/vpn-ipsec.xml.in
@@ -187,7 +187,7 @@
<command>if pgrep charon >/dev/null ; then sudo /usr/sbin/ipsec statusall ; else echo "IPSec process not running" ; fi</command>
</node>
</children>
- <command>if pgrep charon >/dev/null ; then sudo ${vyos_op_scripts_dir}/show_ipsec_sa.py ; else echo "IPSec process not running" ; fi</command>
+ <command>if pgrep charon >/dev/null ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa ; else echo "IPSec process not running" ; fi</command>
</node>
<node name="state">
<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..b56caef71 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -52,9 +52,9 @@ def get_ips_domains_dict(list_domains):
return ip_dict
-def nft_init_set(group_name, table="filter", family="ip"):
+def nft_init_set(group_name, table="vyos_filter", family="ip"):
"""
- table ip filter {
+ table ip vyos_filter {
set GROUP_NAME
type ipv4_addr
flags interval
@@ -63,9 +63,9 @@ def nft_init_set(group_name, table="filter", family="ip"):
return call(f'nft add set ip {table} {group_name} {{ type ipv4_addr\\; flags interval\\; }}')
-def nft_add_set_elements(group_name, elements, table="filter", family="ip"):
+def nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip"):
"""
- table ip filter {
+ table ip vyos_filter {
set GROUP_NAME {
type ipv4_addr
flags interval
@@ -75,18 +75,18 @@ def nft_add_set_elements(group_name, elements, table="filter", family="ip"):
elements = ", ".join(elements)
return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ')
-def nft_flush_set(group_name, table="filter", family="ip"):
+def nft_flush_set(group_name, table="vyos_filter", family="ip"):
"""
Flush elements of nft set
"""
return call(f'nft flush set {family} {table} {group_name}')
-def nft_update_set_elements(group_name, elements, table="filter", family="ip"):
+def nft_update_set_elements(group_name, elements, table="vyos_filter", family="ip"):
"""
Update elements of nft set
"""
- flush_set = nft_flush_set(group_name, table="filter", family="ip")
- nft_add_set = nft_add_set_elements(group_name, elements, table="filter", family="ip")
+ flush_set = nft_flush_set(group_name, table="vyos_filter", family="ip")
+ nft_add_set = nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip")
return flush_set, nft_add_set
# END firewall group domain-group (sets)
@@ -150,7 +150,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if suffix[0] == '!':
suffix = f'!= {suffix[1:]}'
output.append(f'{ip_name} {prefix}addr {suffix}')
-
+
if dict_search_args(side_conf, 'geoip', 'country_code'):
operator = ''
if dict_search_args(side_conf, 'geoip', 'inverse_match') != None:
@@ -265,6 +265,23 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'type' in rule_conf[icmp]:
output.append(icmp + ' type ' + rule_conf[icmp]['type'])
+
+ if 'packet_length' in rule_conf:
+ lengths_str = ','.join(rule_conf['packet_length'])
+ output.append(f'ip{def_suffix} length {{{lengths_str}}}')
+
+ if 'packet_length_exclude' in rule_conf:
+ negated_lengths_str = ','.join(rule_conf['packet_length_exclude'])
+ output.append(f'ip{def_suffix} length != {{{negated_lengths_str}}}')
+
+ if 'dscp' in rule_conf:
+ dscp_str = ','.join(rule_conf['dscp'])
+ output.append(f'ip{def_suffix} dscp {{{dscp_str}}}')
+
+ if 'dscp_exclude' in rule_conf:
+ negated_dscp_str = ','.join(rule_conf['dscp_exclude'])
+ output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}')
+
if 'ipsec' in rule_conf:
if 'match_ipsec' in rule_conf['ipsec']:
output.append('meta ipsec == 1')
@@ -297,6 +314,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/bond.py b/python/vyos/ifconfig/bond.py
index 98bf6162b..0edd17055 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -405,10 +405,12 @@ class BondIf(Interface):
# Remove ALL bond member interfaces
for interface in self.get_slaves():
self.del_port(interface)
- # Removing an interface from a bond will always place the underlaying
- # physical interface in admin-down state! If physical interface is
- # not disabled, re-enable it.
- if not dict_search(f'member.interface_remove.{interface}.disable', config):
+
+ # Restore correct interface status based on config
+ if dict_search(f'member.interface.{interface}.disable', config) is not None or \
+ dict_search(f'member.interface_remove.{interface}.disable', config) is not None:
+ Interface(interface).set_admin_state('down')
+ else:
Interface(interface).set_admin_state('up')
# Bonding policy/mode - default value, always present
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/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 1280fc238..32e667038 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -16,6 +16,7 @@
import os
import re
+from glob import glob
from vyos.ethtool import Ethtool
from vyos.ifconfig.interface import Interface
from vyos.util import run
@@ -74,6 +75,10 @@ class EthernetIf(Interface):
'convert': lambda cpus: cpus if cpus else '0',
'location': '/sys/class/net/{ifname}/queues/rx-0/rps_cpus',
},
+ 'rfs': {
+ 'convert': lambda num: num if num else '0',
+ 'location': '/proc/sys/net/core/rps_sock_flow_entries',
+ },
}}
def __init__(self, ifname, **kargs):
@@ -236,7 +241,7 @@ class EthernetIf(Interface):
enabled, fixed = self.ethtool.get_large_receive_offload()
if enabled != state:
if not fixed:
- return self.set_interface('gro', 'on' if state else 'off')
+ return self.set_interface('lro', 'on' if state else 'off')
else:
print('Adapter does not support changing large-receive-offload settings!')
return False
@@ -258,6 +263,20 @@ class EthernetIf(Interface):
# send bitmask representation as hex string without leading '0x'
return self.set_interface('rps', rps_cpus)
+ def set_rfs(self, state):
+ rfs_flow = 0
+ global_rfs_flow = 0
+ queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))
+ if state:
+ global_rfs_flow = 32768
+ rfs_flow = int(global_rfs_flow/queues)
+
+ self.set_interface('rfs', global_rfs_flow)
+ for i in range(0, queues):
+ self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', rfs_flow)
+
+ return True
+
def set_sg(self, state):
"""
Enable Scatter-Gather support. State can be either True or False.
@@ -273,7 +292,7 @@ class EthernetIf(Interface):
enabled, fixed = self.ethtool.get_scatter_gather()
if enabled != state:
if not fixed:
- return self.set_interface('gro', 'on' if state else 'off')
+ return self.set_interface('sg', 'on' if state else 'off')
else:
print('Adapter does not support changing scatter-gather settings!')
return False
@@ -293,7 +312,7 @@ class EthernetIf(Interface):
enabled, fixed = self.ethtool.get_tcp_segmentation_offload()
if enabled != state:
if not fixed:
- return self.set_interface('gro', 'on' if state else 'off')
+ return self.set_interface('tso', 'on' if state else 'off')
else:
print('Adapter does not support changing tcp-segmentation-offload settings!')
return False
@@ -342,6 +361,9 @@ class EthernetIf(Interface):
# RPS - Receive Packet Steering
self.set_rps(dict_search('offload.rps', config) != None)
+ # RFS - Receive Flow Steering
+ self.set_rfs(dict_search('offload.rfs', config) != None)
+
# scatter-gather option
self.set_sg(dict_search('offload.sg', config) != None)
@@ -359,5 +381,5 @@ class EthernetIf(Interface):
for rx_tx, size in config['ring_buffer'].items():
self.set_ring_buffer(rx_tx, size)
- # call base class first
+ # call base class last
super().update(config)
diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py
index 63ffc8069..437fe0cae 100644
--- a/python/vyos/ifconfig/pppoe.py
+++ b/python/vyos/ifconfig/pppoe.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -14,6 +14,7 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
from vyos.ifconfig.interface import Interface
+from vyos.validate import assert_range
from vyos.util import get_interface_config
@Interface.register
@@ -27,6 +28,21 @@ class PPPoEIf(Interface):
},
}
+ _sysfs_get = {
+ **Interface._sysfs_get,**{
+ 'accept_ra_defrtr': {
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra_defrtr',
+ }
+ }
+ }
+
+ _sysfs_set = {**Interface._sysfs_set, **{
+ 'accept_ra_defrtr': {
+ 'validate': lambda value: assert_range(value, 0, 2),
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra_defrtr',
+ },
+ }}
+
def _remove_routes(self, vrf=None):
# Always delete default routes when interface is removed
vrf_cmd = ''
@@ -70,6 +86,21 @@ class PPPoEIf(Interface):
""" Get a synthetic MAC address. """
return self.get_mac_synthetic()
+ def set_accept_ra_defrtr(self, enable):
+ """
+ Learn default router in Router Advertisement.
+ 1: enabled
+ 0: disable
+
+ Example:
+ >>> from vyos.ifconfig import PPPoEIf
+ >>> PPPoEIf('pppoe1').set_accept_ra_defrtr(0)
+ """
+ tmp = self.get_interface('accept_ra_defrtr')
+ if tmp == enable:
+ return None
+ self.set_interface('accept_ra_defrtr', enable)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -107,6 +138,10 @@ class PPPoEIf(Interface):
tmp = config['vrf']
vrf = f'-c "vrf {tmp}"'
+ # learn default router in Router Advertisement.
+ tmp = '0' if 'no_default_route' in config else '1'
+ self.set_accept_ra_defrtr(tmp)
+
if 'no_default_route' not in config:
# Set default route(s) pointing to PPPoE interface
distance = config['default_route_distance']
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 628f7b3a2..7e3545c87 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -105,6 +105,8 @@ def run(module):
subparser = subparsers.add_parser(function_name, help=functions[function_name].__doc__)
type_hints = typing.get_type_hints(functions[function_name])
+ if 'return' in type_hints:
+ del type_hints['return']
for opt in type_hints:
th = type_hints[opt]
diff --git a/python/vyos/template.py b/python/vyos/template.py
index eb7f06480..9804308c1 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -550,7 +550,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'):
@register_filter('nft_default_rule')
def nft_default_rule(fw_conf, fw_name):
output = ['counter']
- default_action = fw_conf.get('default_action', 'accept')
+ default_action = fw_conf['default_action']
if 'enable_default_log' in fw_conf:
action_suffix = default_action[:1].upper()
diff --git a/python/vyos/util.py b/python/vyos/util.py
index b86b1949c..461df9a6e 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -471,6 +471,35 @@ def process_named_running(name):
return p.pid
return None
+def is_list_equal(first: list, second: list) -> bool:
+ """ Check if 2 lists are equal and list not empty """
+ if len(first) != len(second) or len(first) == 0:
+ return False
+ return sorted(first) == sorted(second)
+
+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
@@ -800,6 +829,32 @@ def dict_search_recursive(dict_object, key, path=[]):
for x in dict_search_recursive(j, key, new_path):
yield x
+def convert_data(data):
+ """Convert multiple types of data to types usable in CLI
+
+ Args:
+ data (str | bytes | list | OrderedDict): input data
+
+ Returns:
+ str | list | dict: converted data
+ """
+ from collections import OrderedDict
+
+ if isinstance(data, str):
+ return data
+ if isinstance(data, bytes):
+ return data.decode()
+ if isinstance(data, list):
+ list_tmp = []
+ for item in data:
+ list_tmp.append(convert_data(item))
+ return list_tmp
+ if isinstance(data, OrderedDict):
+ dict_tmp = {}
+ for key, value in data.items():
+ dict_tmp[key] = convert_data(value)
+ return dict_tmp
+
def get_bridge_fdb(interface):
""" Returns the forwarding database entries for a given interface """
if not os.path.exists(f'/sys/class/net/{interface}'):
diff --git a/smoketest/configs/pki-misc b/smoketest/configs/vpn-openconnect-sstp
index c90226a2a..59a26f501 100644
--- a/smoketest/configs/pki-misc
+++ b/smoketest/configs/vpn-openconnect-sstp
@@ -3,15 +3,6 @@ interfaces {
address 192.168.150.1/24
}
}
-service {
- https {
- certificates {
- system-generated-certificate {
- lifetime 365
- }
- }
- }
-}
system {
config-management {
commit-revisions 100
@@ -84,6 +75,7 @@ vpn {
subnet 192.168.170.0/24
}
gateway-address 192.168.150.1
+ port 8443
ssl {
ca-cert-file /config/auth/ovpn_test_ca.pem
cert-file /config/auth/ovpn_test_server.pem
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 4de90e1ec..8e4aac788 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -45,15 +45,11 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
# out the current configuration :)
cls.cli_delete(cls, ['firewall'])
- cls.cli_set(cls, ['interfaces', 'ethernet', 'eth0', 'address', '172.16.10.1/24'])
-
@classmethod
def tearDownClass(cls):
- cls.cli_delete(cls, ['interfaces', 'ethernet', 'eth0', 'address', '172.16.10.1/24'])
super(TestFirewall, cls).tearDownClass()
def tearDown(self):
- self.cli_delete(['interfaces', 'ethernet', 'eth0', 'firewall'])
self.cli_delete(['firewall'])
self.cli_commit()
@@ -67,7 +63,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['chain NAME_smoketest']
]
- self.verify_nftables(nftables_search, 'ip filter', inverse=True)
+ self.verify_nftables(nftables_search, 'ip vyos_filter', inverse=True)
def verify_nftables(self, nftables_search, table, inverse=False, args=''):
nftables_output = cmd(f'sudo nft {args} list table {table}')
@@ -95,8 +91,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ip saddr @GEOIP_CC_smoketest_1', 'drop'],
['ip saddr != @GEOIP_CC_smoketest_2', 'return']
]
+
# -t prevents 1000+ GeoIP elements being returned
- self.verify_nftables(nftables_search, 'ip filter', args='-t')
+ self.verify_nftables(nftables_search, 'ip vyos_filter', args='-t')
def test_groups(self):
hostmap_path = ['system', 'static-host-mapping', 'host-name']
@@ -114,6 +111,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123'])
self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com'])
self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org'])
+
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
@@ -124,7 +122,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain'])
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest'])
self.cli_commit()
nftables_search = [
@@ -139,7 +137,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['192.0.2.10, 192.0.2.11 }'],
['ip saddr @D_smoketest_domain', 'return']
]
- self.verify_nftables(nftables_search, 'ip filter')
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
self.cli_delete(['system', 'static-host-mapping'])
self.cli_commit()
@@ -156,7 +154,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest'])
self.cli_commit()
@@ -174,77 +172,155 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['elements = { 53, 123 }']
]
- self.verify_nftables(nftables_search, 'ip filter')
-
- def test_basic_rules(self):
- self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
- self.cli_set(['firewall', 'name', 'smoketest', 'enable-default-log'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'log', 'enable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'log-level', 'debug'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'ttl', 'eq', '15'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'reject'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'protocol', 'tcp'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'log', 'enable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'log-level', 'err'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'ttl', 'gt', '102'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'protocol', 'tcp'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'destination', 'port', '22'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'limit', 'rate', '5/minute'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'log', 'disable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'drop'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'protocol', 'tcp'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'destination', 'port', '22'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'recent', 'count', '10'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'recent', 'time', 'minute'])
-
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
+
+ def test_ipv4_basic_rules(self):
+ name = 'smoketest'
+ interface = 'eth0'
+ mss_range = '501-1460'
+
+ self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'name', name, 'enable-default-log'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'log', 'enable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'log-level', 'debug'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'ttl', 'eq', '15'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'destination', 'port', '8888'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'log', 'enable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'log-level', 'err'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'ttl', 'gt', '102'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'destination', 'port', '22'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'limit', 'rate', '5/minute'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'log', 'disable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'action', 'drop'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'destination', 'port', '22'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'recent', 'count', '10'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'recent', 'time', 'minute'])
+ self.cli_set(['firewall', 'name', name, 'rule', '5', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '5', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'flags', 'syn'])
+ self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'mss', mss_range])
+
+ self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
self.cli_commit()
nftables_search = [
- ['iifname "eth0"', 'jump NAME_smoketest'],
- ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" level debug', 'ip ttl 15','return'],
- ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'log prefix "[smoketest-2-R]" level err', 'ip ttl > 102', 'reject'],
- ['tcp dport { 22 }', 'limit rate 5/minute', 'return'],
+ [f'iifname "{interface}"', f'jump NAME_{name}'],
+ ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" level debug', 'ip ttl 15', 'return'],
+ ['tcp flags syn / syn,ack', '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'],
+ ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}'],
]
- self.verify_nftables(nftables_search, 'ip filter')
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
- def test_basic_rules_ipv6(self):
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'default-action', 'drop'])
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'enable-default-log'])
+ def test_ipv4_advanced(self):
+ name = 'smoketest-adv'
+ interface = 'eth0'
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'source', 'address', '2002::1'])
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'destination', 'address', '2002::1:1'])
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'log', 'enable'])
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'log-level', 'crit'])
+ self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'name', name, 'enable-default-log'])
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'action', 'reject'])
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'protocol', 'tcp_udp'])
- self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'destination', 'port', '8888'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '64'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '512'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '1024'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '17'])
+ self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '52'])
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'ipv6-name', 'v6-smoketest'])
+ self.cli_set(['firewall', 'name', name, 'rule', '7', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length', '1-30000'])
+ self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535'])
+ self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp', '3-11'])
+ self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp-exclude', '21-25'])
+
+ self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
self.cli_commit()
nftables_search = [
- ['iifname "eth0"', 'jump NAME6_v6-smoketest'],
+ [f'iifname "{interface}"', f'jump NAME_{name}'],
+ ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', 'return'],
+ ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'return'],
+ [f'log prefix "[{name}-default-D]"', 'drop']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
+
+ def test_ipv6_basic_rules(self):
+ name = 'v6-smoketest'
+ interface = 'eth0'
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'enable-default-log'])
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'source', 'address', '2002::1'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address', '2002::1:1'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log', 'enable'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log-level', 'crit'])
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'destination', 'port', '8888'])
+
+ self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])
+
+ self.cli_commit()
+
+ nftables_search = [
+ [f'iifname "{interface}"', f'jump NAME6_{name}'],
['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" level crit', 'return'],
- ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'reject'],
- ['smoketest default-action', 'log prefix "[v6-smoketest-default-D]"', 'drop']
+ ['meta l4proto { tcp, udp }', 'th dport 8888', 'reject'],
+ ['smoketest default-action', f'log prefix "[{name}-default-D]"', 'drop']
]
- self.verify_nftables(nftables_search, 'ip6 filter')
+ self.verify_nftables(nftables_search, 'ip6 vyos_filter')
+
+ def test_ipv6_advanced(self):
+ name = 'v6-smoketest-adv'
+ interface = 'eth0'
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'enable-default-log'])
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '65'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '513'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '1025'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'dscp', '18'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'dscp', '53'])
+
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'packet-length', '1-1999'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'packet-length-exclude', '60000-65535'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp', '4-14'])
+ self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp-exclude', '31-35'])
+
+ self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])
+
+ self.cli_commit()
+
+ nftables_search = [
+ [f'iifname "{interface}"', f'jump NAME6_{name}'],
+ ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'return'],
+ ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'return'],
+ [f'log prefix "[{name}-default-D]"', 'drop']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 vyos_filter')
def test_state_policy(self):
self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept'])
@@ -254,45 +330,48 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
chains = {
- 'ip filter': ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'],
- 'ip6 filter': ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
+ 'ip vyos_filter': ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'],
+ 'ip6 vyos_filter': ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
}
- for table in ['ip filter', 'ip6 filter']:
+ for table in ['ip vyos_filter', 'ip6 vyos_filter']:
for chain in chains[table]:
nftables_output = cmd(f'sudo nft list chain {table} {chain}')
self.assertTrue('jump VYOS_STATE_POLICY' in nftables_output)
- def test_state_and_status_rules(self):
- self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'state', 'established', 'enable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'state', 'related', 'enable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'reject'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'state', 'invalid', 'enable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'state', 'new', 'enable'])
+ def test_ipv4_state_and_status_rules(self):
+ name = 'smoketest-state'
+ interface = 'eth0'
+
+ self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'state', 'established', 'enable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '1', 'state', 'related', 'enable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'name', name, 'rule', '2', 'state', 'invalid', 'enable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'state', 'new', 'enable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'connection-status', 'nat', 'destination'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'new', 'enable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'established', 'enable'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'connection-status', 'nat', 'source'])
+ self.cli_set(['firewall', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'state', 'new', 'enable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'state', 'established', 'enable'])
+ self.cli_set(['firewall', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source'])
- self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
self.cli_commit()
nftables_search = [
- ['iifname "eth0"', 'jump NAME_smoketest'],
+ [f'iifname "{interface}"', f'jump NAME_{name}'],
['ct state { established, related }', 'return'],
- ['ct state { invalid }', 'reject'],
- ['ct state { new }', 'ct status { dnat }', 'return'],
- ['ct state { established, new }', 'ct status { snat }', 'return'],
- ['smoketest default-action', 'drop']
+ ['ct state invalid', 'reject'],
+ ['ct state new', 'ct status dnat', 'return'],
+ ['ct state { established, new }', 'ct status snat', 'return'],
+ ['drop', f'comment "{name} default-action drop"']
]
- self.verify_nftables(nftables_search, 'ip filter')
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
def test_sysfs(self):
for name, conf in sysfs_config.items():
@@ -311,5 +390,35 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
with open(path, 'r') as f:
self.assertNotEqual(f.read().strip(), conf['default'], msg=path)
+ def test_zone_basic(self):
+ self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone'])
+ self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['chain VZONE_smoketest-eth0'],
+ ['chain VZONE_smoketest-local_IN'],
+ ['chain VZONE_smoketest-local_OUT'],
+ ['oifname "eth0"', 'jump VZONE_smoketest-eth0'],
+ ['jump VZONE_smoketest-local_IN'],
+ ['jump VZONE_smoketest-local_OUT'],
+ ['iifname "eth0"', 'jump NAME_smoketest'],
+ ['oifname "eth0"', 'jump NAME_smoketest']
+ ]
+
+ nftables_output = cmd('sudo nft list table ip vyos_filter')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(matched)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
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_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index 05d2ae5f5..5049bd5b0 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -17,6 +17,7 @@
import os
import re
import unittest
+from glob import glob
from netifaces import AF_INET
from netifaces import AF_INET6
@@ -185,6 +186,43 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
self.assertEqual(f'{cpus:x}', f'{rps_cpus:x}')
+ def test_offloading_rfs(self):
+ global_rfs_flow = 32768
+ rfs_flow = global_rfs_flow
+
+ for interface in self._interfaces:
+ self.cli_set(self._base_path + [interface, 'offload', 'rfs'])
+
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*'))
+ rfs_flow = int(global_rfs_flow/queues)
+ for i in range(0, queues):
+ tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt')
+ self.assertEqual(int(tmp), rfs_flow)
+
+ tmp = read_file(f'/proc/sys/net/core/rps_sock_flow_entries')
+ self.assertEqual(int(tmp), global_rfs_flow)
+
+
+ # delete configuration of RFS and check all values returned to default "0"
+ for interface in self._interfaces:
+ self.cli_delete(self._base_path + [interface, 'offload', 'rfs'])
+
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*'))
+ rfs_flow = int(global_rfs_flow/queues)
+ for i in range(0, queues):
+ tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt')
+ self.assertEqual(int(tmp), 0)
+
+ tmp = read_file(f'/proc/sys/net/core/rps_sock_flow_entries')
+ self.assertEqual(int(tmp), 0)
+
+
def test_non_existing_interface(self):
unknonw_interface = self._base_path + ['eth667']
self.cli_set(unknonw_interface)
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index 4b5625569..537b094a4 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -131,6 +131,30 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 nat')
+ def test_destination_nat66_protocol(self):
+ translation_address = '2001:db8:1111::1'
+ source_prefix = '2001:db8:2222::/64'
+ dport = '4545'
+ sport = '8080'
+ tport = '5555'
+ proto = 'tcp'
+ self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dport])
+ self.cli_set(dst_path + ['rule', '1', 'source', 'address', source_prefix])
+ self.cli_set(dst_path + ['rule', '1', 'source', 'port', sport])
+ self.cli_set(dst_path + ['rule', '1', 'protocol', proto])
+ self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_address])
+ self.cli_set(dst_path + ['rule', '1', 'translation', 'port', tport])
+
+ # check validate() - outbound-interface must be defined
+ self.cli_commit()
+
+ nftables_search = [
+ ['iifname "eth1"', 'tcp dport 4545', 'ip6 saddr 2001:db8:2222::/64', 'tcp sport 8080', 'dnat to 2001:db8:1111::1:5555']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 nat')
+
def test_destination_nat66_prefix(self):
destination_prefix = 'fc00::/64'
translation_prefix = 'fc01::/64'
@@ -176,6 +200,30 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
self.cli_commit()
+ def test_source_nat66_protocol(self):
+ translation_address = '2001:db8:1111::1'
+ source_prefix = '2001:db8:2222::/64'
+ dport = '9999'
+ sport = '8080'
+ tport = '80'
+ proto = 'tcp'
+ self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '1', 'destination', 'port', dport])
+ self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix])
+ self.cli_set(src_path + ['rule', '1', 'source', 'port', sport])
+ self.cli_set(src_path + ['rule', '1', 'protocol', proto])
+ self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_address])
+ self.cli_set(src_path + ['rule', '1', 'translation', 'port', tport])
+
+ # check validate() - outbound-interface must be defined
+ self.cli_commit()
+
+ nftables_search = [
+ ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64', 'tcp dport 9999', 'tcp sport 8080', 'snat to 2001:db8:1111::1:80']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 nat')
+
def test_nat66_no_rules(self):
# T3206: deleting all rules but keep the direction 'destination' or
# 'source' resulteds in KeyError: 'rule'.
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index 534cfb082..046e385bb 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -53,7 +53,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
['chain VYOS_PBR_smoketest']
]
- self.verify_nftables(nftables_search, 'ip filter', inverse=True)
+ self.verify_nftables(nftables_search, 'ip mangle', inverse=True)
def verify_nftables(self, nftables_search, table, inverse=False):
nftables_output = cmd(f'sudo nft list table {table}')
@@ -127,7 +127,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
nftables_search = [
[f'iifname "{interface}"', 'jump VYOS_PBR_smoketest'],
- ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'meta mark set ' + mark_hex]
+ ['tcp flags syn / syn,ack', 'tcp dport 8888', 'meta mark set ' + mark_hex]
]
self.verify_nftables(nftables_search, 'ip mangle')
@@ -136,7 +136,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
nftables6_search = [
[f'iifname "{interface}"', 'jump VYOS_PBR6_smoketest'],
- ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex]
+ ['meta l4proto { tcp, udp }', 'th dport 8888', 'meta mark set ' + mark_hex]
]
self.verify_nftables(nftables6_search, 'ip6 mangle')
@@ -158,5 +158,81 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.assertTrue(matched)
+ def test_pbr_matching_criteria(self):
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'udp'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'action', 'drop'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'protocol', 'tcp'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'set', 'table', table_id])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'source', 'address', '198.51.100.0/24'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'protocol', 'tcp'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'destination', 'port', '22'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'state', 'new', 'enable'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'ttl', 'gt', '2'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'set', 'table', table_id])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'protocol', 'icmp'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'icmp', 'type-name', 'echo-request'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '128'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '1024-2048'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'log', 'enable'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'set', 'table', table_id])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'dscp', '41'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'dscp', '57-59'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'set', 'table', table_id])
+
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'udp'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'action', 'drop'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'protocol', 'tcp'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'tcp', 'flags', 'syn'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'set', 'table', table_id])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'source', 'address', '2001:db8::0/64'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'protocol', 'tcp'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'destination', 'port', '22'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'state', 'new', 'enable'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'hop-limit', 'gt', '2'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'set', 'table', table_id])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'protocol', 'icmpv6'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'icmpv6', 'type', 'echo-request'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '128'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '1024-2048'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'log', 'enable'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'set', 'table', table_id])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '61'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '14-19'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'set', 'table', table_id])
+
+ self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest'])
+ self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6'])
+
+ self.cli_commit()
+
+ mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id))
+
+ # IPv4
+ nftables_search = [
+ [f'iifname "{interface}"', 'jump VYOS_PBR_smoketest'],
+ ['meta l4proto udp', 'drop'],
+ ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex],
+ ['ct state new', 'tcp dport 22', 'ip saddr 198.51.100.0/24', 'ip ttl > 2', 'meta mark set ' + mark_hex],
+ ['meta l4proto icmp', 'log prefix "[smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta mark set ' + mark_hex],
+ ['ip dscp { 0x29, 0x39-0x3b }', 'meta mark set ' + mark_hex]
+ ]
+
+ self.verify_nftables(nftables_search, 'ip mangle')
+
+ # IPv6
+ nftables6_search = [
+ [f'iifname "{interface}"', 'jump VYOS_PBR6_smoketest'],
+ ['meta l4proto udp', 'drop'],
+ ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex],
+ ['ct state new', 'tcp dport 22', 'ip6 saddr 2001:db8::/64', 'ip6 hoplimit > 2', 'meta mark set ' + mark_hex],
+ ['meta l4proto ipv6-icmp', 'log prefix "[smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta mark set ' + mark_hex],
+ ['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex]
+ ]
+
+ self.verify_nftables(nftables6_search, 'ip6 mangle')
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index cefaad64a..d2dad8c1a 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -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',
@@ -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'
@@ -282,6 +287,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['parameters', 'bestpath', 'as-path', 'multipath-relax'])
self.cli_set(base_path + ['parameters', 'bestpath', 'bandwidth', 'default-weight-for-missing'])
self.cli_set(base_path + ['parameters', 'bestpath', 'compare-routerid'])
+ self.cli_set(base_path + ['parameters', 'bestpath', 'peer-type', 'multipath-relax'])
self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer])
self.cli_set(base_path + ['parameters', 'fast-convergence'])
@@ -313,6 +319,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' bgp bestpath as-path multipath-relax', frrconfig)
self.assertIn(f' bgp bestpath bandwidth default-weight-for-missing', frrconfig)
self.assertIn(f' bgp bestpath compare-routerid', frrconfig)
+ self.assertIn(f' bgp bestpath peer-type multipath-relax', frrconfig)
self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig)
self.assertIn(f' bgp reject-as-sets', frrconfig)
self.assertIn(f' bgp shutdown', frrconfig)
@@ -400,6 +407,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 +497,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:
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index ee4be0b37..e4bb9e1f8 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -262,5 +262,52 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' isis bfd', tmp)
self.assertIn(f' isis bfd profile {bfd_profile}', tmp)
+ def test_isis_07_segment_routing_configuration(self):
+ global_block_low = "1000"
+ global_block_high = "1999"
+ local_block_low = "2000"
+ local_block_high = "2999"
+ interface = 'lo'
+ maximum_stack_size = '5'
+ prefix_one = '192.168.0.1/32'
+ prefix_two = '192.168.0.2/32'
+ prefix_three = '192.168.0.3/32'
+ prefix_four = '192.168.0.4/32'
+ prefix_one_value = '1'
+ prefix_two_value = '2'
+ prefix_three_value = '60000'
+ prefix_four_value = '65000'
+
+ self.cli_set(base_path + ['net', net])
+ self.cli_set(base_path + ['interface', interface])
+ self.cli_set(base_path + ['segment-routing', 'enable'])
+ self.cli_set(base_path + ['segment-routing', 'maximum-label-depth', maximum_stack_size])
+ self.cli_set(base_path + ['segment-routing', 'global-block', 'low-label-value', global_block_low])
+ self.cli_set(base_path + ['segment-routing', 'global-block', 'high-label-value', global_block_high])
+ self.cli_set(base_path + ['segment-routing', 'local-block', 'low-label-value', local_block_low])
+ self.cli_set(base_path + ['segment-routing', 'local-block', 'high-label-value', local_block_high])
+ self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'value', prefix_one_value])
+ self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'explicit-null'])
+ self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'value', prefix_two_value])
+ self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'no-php-flag'])
+ self.cli_set(base_path + ['segment-routing', 'prefix', prefix_three, 'absolute', 'value', prefix_three_value])
+ self.cli_set(base_path + ['segment-routing', 'prefix', prefix_three, 'absolute', 'explicit-null'])
+ self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'value', prefix_four_value])
+ self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'no-php-flag'])
+
+ # Commit all changes
+ self.cli_commit()
+
+ # Verify all changes
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' segment-routing on', tmp)
+ self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', tmp)
+ self.assertIn(f' segment-routing node-msd {maximum_stack_size}', tmp)
+ self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', tmp)
+ self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', tmp)
+ self.assertIn(f' segment-routing prefix {prefix_three} absolute {prefix_three_value} explicit-null', tmp)
+ self.assertIn(f' segment-routing prefix {prefix_four} absolute {prefix_four_value} no-php-flag', tmp)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py
index 40b19fec7..9a00b86fc 100755
--- a/smoketest/scripts/cli/test_protocols_nhrp.py
+++ b/smoketest/scripts/cli/test_protocols_nhrp.py
@@ -26,65 +26,81 @@ nhrp_path = ['protocols', 'nhrp']
vpn_path = ['vpn', 'ipsec']
class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestProtocolsNHRP, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, nhrp_path)
+ cls.cli_delete(cls, tunnel_path)
+
def tearDown(self):
self.cli_delete(nhrp_path)
self.cli_delete(tunnel_path)
self.cli_commit()
def test_config(self):
- self.cli_delete(nhrp_path)
- self.cli_delete(tunnel_path)
+ tunnel_if = "tun100"
+ tunnel_source = "192.0.2.1"
+ tunnel_encapsulation = "gre"
+ esp_group = "ESP-HUB"
+ ike_group = "IKE-HUB"
+ nhrp_secret = "vyos123"
+ nhrp_profile = "NHRPVPN"
+ ipsec_secret = "secret"
# Tunnel
- self.cli_set(tunnel_path + ["tun100", "address", "172.16.253.134/29"])
- self.cli_set(tunnel_path + ["tun100", "encapsulation", "gre"])
- self.cli_set(tunnel_path + ["tun100", "source-address", "192.0.2.1"])
- self.cli_set(tunnel_path + ["tun100", "multicast", "enable"])
- self.cli_set(tunnel_path + ["tun100", "parameters", "ip", "key", "1"])
+ self.cli_set(tunnel_path + [tunnel_if, "address", "172.16.253.134/29"])
+ self.cli_set(tunnel_path + [tunnel_if, "encapsulation", tunnel_encapsulation])
+ self.cli_set(tunnel_path + [tunnel_if, "source-address", tunnel_source])
+ self.cli_set(tunnel_path + [tunnel_if, "multicast", "enable"])
+ self.cli_set(tunnel_path + [tunnel_if, "parameters", "ip", "key", "1"])
# NHRP
- self.cli_set(nhrp_path + ["tunnel", "tun100", "cisco-authentication", "secret"])
- self.cli_set(nhrp_path + ["tunnel", "tun100", "holding-time", "300"])
- self.cli_set(nhrp_path + ["tunnel", "tun100", "multicast", "dynamic"])
- self.cli_set(nhrp_path + ["tunnel", "tun100", "redirect"])
- self.cli_set(nhrp_path + ["tunnel", "tun100", "shortcut"])
+ self.cli_set(nhrp_path + ["tunnel", tunnel_if, "cisco-authentication", nhrp_secret])
+ self.cli_set(nhrp_path + ["tunnel", tunnel_if, "holding-time", "300"])
+ self.cli_set(nhrp_path + ["tunnel", tunnel_if, "multicast", "dynamic"])
+ self.cli_set(nhrp_path + ["tunnel", tunnel_if, "redirect"])
+ self.cli_set(nhrp_path + ["tunnel", tunnel_if, "shortcut"])
# IKE/ESP Groups
- self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "compression", "disable"])
- self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "lifetime", "1800"])
- self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "mode", "transport"])
- self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "pfs", "dh-group2"])
- self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "1", "encryption", "aes256"])
- self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "1", "hash", "sha1"])
- self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "2", "encryption", "3des"])
- self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "2", "hash", "md5"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "ikev2-reauth", "no"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "key-exchange", "ikev1"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "lifetime", "3600"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "dh-group", "2"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "encryption", "aes256"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "hash", "sha1"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "dh-group", "2"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "encryption", "aes128"])
- self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "hash", "sha1"])
+ self.cli_set(vpn_path + ["esp-group", esp_group, "compression", "disable"])
+ self.cli_set(vpn_path + ["esp-group", esp_group, "lifetime", "1800"])
+ self.cli_set(vpn_path + ["esp-group", esp_group, "mode", "transport"])
+ self.cli_set(vpn_path + ["esp-group", esp_group, "pfs", "dh-group2"])
+ self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "1", "encryption", "aes256"])
+ self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "1", "hash", "sha1"])
+ self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "2", "encryption", "3des"])
+ self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "2", "hash", "md5"])
+
+ self.cli_set(vpn_path + ["ike-group", ike_group, "ikev2-reauth", "no"])
+ self.cli_set(vpn_path + ["ike-group", ike_group, "key-exchange", "ikev1"])
+ self.cli_set(vpn_path + ["ike-group", ike_group, "lifetime", "3600"])
+ self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "dh-group", "2"])
+ self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "encryption", "aes256"])
+ self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "hash", "sha1"])
+ self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "dh-group", "2"])
+ self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "encryption", "aes128"])
+ self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "hash", "sha1"])
# Profile - Not doing full DMVPN checks here, just want to verify the profile name in the output
self.cli_set(vpn_path + ["interface", "eth0"])
- self.cli_set(vpn_path + ["profile", "NHRPVPN", "authentication", "mode", "pre-shared-secret"])
- self.cli_set(vpn_path + ["profile", "NHRPVPN", "authentication", "pre-shared-secret", "secret"])
- self.cli_set(vpn_path + ["profile", "NHRPVPN", "bind", "tunnel", "tun100"])
- self.cli_set(vpn_path + ["profile", "NHRPVPN", "esp-group", "ESP-HUB"])
- self.cli_set(vpn_path + ["profile", "NHRPVPN", "ike-group", "IKE-HUB"])
+ self.cli_set(vpn_path + ["profile", nhrp_profile, "authentication", "mode", "pre-shared-secret"])
+ self.cli_set(vpn_path + ["profile", nhrp_profile, "authentication", "pre-shared-secret", ipsec_secret])
+ self.cli_set(vpn_path + ["profile", nhrp_profile, "bind", "tunnel", tunnel_if])
+ self.cli_set(vpn_path + ["profile", nhrp_profile, "esp-group", esp_group])
+ self.cli_set(vpn_path + ["profile", nhrp_profile, "ike-group", ike_group])
self.cli_commit()
opennhrp_lines = [
- 'interface tun100 #hub NHRPVPN',
- 'cisco-authentication secret',
- 'holding-time 300',
- 'shortcut',
- 'multicast dynamic',
- 'redirect'
+ f'interface {tunnel_if} #hub {nhrp_profile}',
+ f'cisco-authentication {nhrp_secret}',
+ f'holding-time 300',
+ f'shortcut',
+ f'multicast dynamic',
+ f'redirect'
]
tmp_opennhrp_conf = read_file('/run/opennhrp/opennhrp.conf')
@@ -93,13 +109,13 @@ class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase):
self.assertIn(line, tmp_opennhrp_conf)
firewall_matches = [
- 'ip protocol gre',
- 'ip saddr 192.0.2.1',
- 'ip daddr 224.0.0.0/4',
- 'comment "VYOS_NHRP_tun100"'
+ f'ip protocol {tunnel_encapsulation}',
+ f'ip saddr {tunnel_source}',
+ f'ip daddr 224.0.0.0/4',
+ f'comment "VYOS_NHRP_{tunnel_if}"'
]
- self.assertTrue(find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', firewall_matches) is not None)
+ self.assertTrue(find_nftables_rule('ip vyos_nhrp_filter', 'VYOS_NHRP_OUTPUT', firewall_matches) is not None)
self.assertTrue(process_named_running('opennhrp'))
if __name__ == '__main__':
diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
index 65b676451..fe2682d50 100755
--- a/smoketest/scripts/cli/test_service_dns_forwarding.py
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -26,7 +26,7 @@ from vyos.util import process_named_running
CONFIG_FILE = '/run/powerdns/recursor.conf'
FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf'
HOSTSD_FILE = '/run/powerdns/recursor.vyos-hostsd.conf.lua'
-PROCESS_NAME= 'pdns-r/worker'
+PROCESS_NAME= 'pdns_recursor'
base_path = ['service', 'dns', 'forwarding']
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_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index 95c2a6c55..fd16146b1 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -59,7 +59,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
},
'net.netfilter.nf_conntrack_tcp_max_retrans' :{
'cli' : ['tcp', 'max-retrans'],
- 'test_value' : '1024',
+ 'test_value' : '128',
'default_value' : '3',
},
'net.netfilter.nf_conntrack_icmp_timeout' :{
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/smoketest/scripts/cli/test_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py
deleted file mode 100755
index 2c580e2f1..000000000
--- a/smoketest/scripts/cli/test_zone_policy.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import unittest
-
-from base_vyostest_shim import VyOSUnitTestSHIM
-
-from vyos.util import cmd
-
-class TestZonePolicy(VyOSUnitTestSHIM.TestCase):
- @classmethod
- def setUpClass(cls):
- super(TestZonePolicy, cls).setUpClass()
- cls.cli_set(cls, ['firewall', 'name', 'smoketest', 'default-action', 'drop'])
-
- @classmethod
- def tearDownClass(cls):
- cls.cli_delete(cls, ['firewall'])
- super(TestZonePolicy, cls).tearDownClass()
-
- def tearDown(self):
- self.cli_delete(['zone-policy'])
- self.cli_commit()
-
- def test_basic_zone(self):
- self.cli_set(['zone-policy', 'zone', 'smoketest-eth0', 'interface', 'eth0'])
- self.cli_set(['zone-policy', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest'])
- self.cli_set(['zone-policy', 'zone', 'smoketest-local', 'local-zone'])
- self.cli_set(['zone-policy', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest'])
-
- self.cli_commit()
-
- nftables_search = [
- ['chain VZONE_smoketest-eth0'],
- ['chain VZONE_smoketest-local_IN'],
- ['chain VZONE_smoketest-local_OUT'],
- ['oifname { "eth0" }', 'jump VZONE_smoketest-eth0'],
- ['jump VZONE_smoketest-local_IN'],
- ['jump VZONE_smoketest-local_OUT'],
- ['iifname { "eth0" }', 'jump NAME_smoketest'],
- ['oifname { "eth0" }', 'jump NAME_smoketest']
- ]
-
- nftables_output = cmd('sudo nft list table ip filter')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched)
-
-
-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/firewall-interface.py b/src/conf_mode/firewall-interface.py
deleted file mode 100755
index ab1c69259..000000000
--- a/src/conf_mode/firewall-interface.py
+++ /dev/null
@@ -1,186 +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 os
-import re
-
-from sys import argv
-from sys import exit
-
-from vyos.config import Config
-from vyos.configdict import leaf_node_changed
-from vyos.ifconfig import Section
-from vyos.template import render
-from vyos.util import cmd
-from vyos.util import dict_search_args
-from vyos.util import run
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-NAME_PREFIX = 'NAME_'
-NAME6_PREFIX = 'NAME6_'
-
-NFT_CHAINS = {
- 'in': 'VYOS_FW_FORWARD',
- 'out': 'VYOS_FW_FORWARD',
- 'local': 'VYOS_FW_LOCAL'
-}
-NFT6_CHAINS = {
- 'in': 'VYOS_FW6_FORWARD',
- 'out': 'VYOS_FW6_FORWARD',
- 'local': 'VYOS_FW6_LOCAL'
-}
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
-
- ifname = argv[1]
- ifpath = Section.get_config_path(ifname)
- if_firewall_path = f'interfaces {ifpath} firewall'
-
- if_firewall = conf.get_config_dict(if_firewall_path, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if_firewall['ifname'] = ifname
- if_firewall['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- return if_firewall
-
-def verify_chain(table, chain):
- # Verify firewall applied
- code = run(f'nft list chain {table} {chain}')
- return code == 0
-
-def verify(if_firewall):
- # bail out early - looks like removal from running config
- if not if_firewall:
- return None
-
- for direction in ['in', 'out', 'local']:
- if direction in if_firewall:
- if 'name' in if_firewall[direction]:
- name = if_firewall[direction]['name']
-
- if 'name' not in if_firewall['firewall']:
- raise ConfigError('Firewall name not configured')
-
- if name not in if_firewall['firewall']['name']:
- raise ConfigError(f'Invalid firewall name "{name}"')
-
- if not verify_chain('ip filter', f'{NAME_PREFIX}{name}'):
- raise ConfigError('Firewall did not apply')
-
- if 'ipv6_name' in if_firewall[direction]:
- name = if_firewall[direction]['ipv6_name']
-
- if 'ipv6_name' not in if_firewall['firewall']:
- raise ConfigError('Firewall ipv6-name not configured')
-
- if name not in if_firewall['firewall']['ipv6_name']:
- raise ConfigError(f'Invalid firewall ipv6-name "{name}"')
-
- if not verify_chain('ip6 filter', f'{NAME6_PREFIX}{name}'):
- raise ConfigError('Firewall did not apply')
-
- return None
-
-def generate(if_firewall):
- return None
-
-def cleanup_rule(table, chain, prefix, ifname, new_name=None):
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- retval = None
- for line in results:
- if f'{prefix}ifname "{ifname}"' in line:
- if new_name and f'jump {new_name}' in line:
- # new_name is used to clear rules for any previously referenced chains
- # returns true when rule exists and doesn't need to be created
- retval = True
- continue
-
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- run(f'nft delete rule {table} {chain} handle {handle_search[1]}')
- return retval
-
-def state_policy_handle(table, chain):
- # Find any state-policy rule to ensure interface rules are only inserted afterwards
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- for line in results:
- if 'jump VYOS_STATE_POLICY' in line:
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- return handle_search[1]
- return None
-
-def apply(if_firewall):
- ifname = if_firewall['ifname']
-
- for direction in ['in', 'out', 'local']:
- chain = NFT_CHAINS[direction]
- ipv6_chain = NFT6_CHAINS[direction]
- if_prefix = 'i' if direction in ['in', 'local'] else 'o'
-
- name = dict_search_args(if_firewall, direction, 'name')
- if name:
- rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, f'{NAME_PREFIX}{name}')
-
- if not rule_exists:
- rule_action = 'insert'
- rule_prefix = ''
-
- handle = state_policy_handle('ip filter', chain)
- if handle:
- rule_action = 'add'
- rule_prefix = f'position {handle}'
-
- run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME_PREFIX}{name}')
- else:
- cleanup_rule('ip filter', chain, if_prefix, ifname)
-
- ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
- if ipv6_name:
- rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, f'{NAME6_PREFIX}{ipv6_name}')
-
- if not rule_exists:
- rule_action = 'insert'
- rule_prefix = ''
-
- handle = state_policy_handle('ip6 filter', ipv6_chain)
- if handle:
- rule_action = 'add'
- rule_prefix = f'position {handle}'
-
- run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME6_PREFIX}{ipv6_name}')
- else:
- cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname)
-
- return None
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 07eca722f..eeb57bd30 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -26,6 +26,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
+# from vyos.configverify import verify_interface_exists
from vyos.firewall import geoip_update
from vyos.firewall import get_ips_domains_dict
from vyos.firewall import nft_add_set_elements
@@ -38,7 +39,7 @@ from vyos.util import cmd
from vyos.util import dict_search_args
from vyos.util import dict_search_recursive
from vyos.util import process_named_running
-from vyos.util import run
+from vyos.util import rc_cmd
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -47,7 +48,6 @@ airbag.enable()
policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py'
nftables_conf = '/run/nftables.conf'
-nftables_defines_conf = '/run/nftables_defines.conf'
sysfs_config = {
'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'},
@@ -63,28 +63,6 @@ sysfs_config = {
'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'}
}
-NAME_PREFIX = 'NAME_'
-NAME6_PREFIX = 'NAME6_'
-
-preserve_chains = [
- 'INPUT',
- 'FORWARD',
- 'OUTPUT',
- 'VYOS_FW_FORWARD',
- 'VYOS_FW_LOCAL',
- 'VYOS_FW_OUTPUT',
- 'VYOS_POST_FW',
- 'VYOS_FRAG_MARK',
- 'VYOS_FW6_FORWARD',
- 'VYOS_FW6_LOCAL',
- 'VYOS_FW6_OUTPUT',
- 'VYOS_POST_FW6',
- 'VYOS_FRAG6_MARK'
-]
-
-nft_iface_chains = ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']
-nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
-
valid_groups = [
'address_group',
'domain_group',
@@ -97,16 +75,6 @@ nested_group_types = [
'port_group', 'ipv6_address_group', 'ipv6_network_group'
]
-group_set_prefix = {
- 'A_': 'address_group',
- 'A6_': 'ipv6_address_group',
- 'D_': 'domain_group',
- 'M_': 'mac_group',
- 'N_': 'network_group',
- 'N6_': 'ipv6_network_group',
- 'P_': 'port_group'
-}
-
snmp_change_type = {
'unknown': 0,
'add': 1,
@@ -117,51 +85,6 @@ snmp_event_source = 1
snmp_trap_mib = 'VYATTA-TRAP-MIB'
snmp_trap_name = 'mgmtEventTrap'
-def get_firewall_interfaces(conf):
- out = {}
- interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
- def find_interfaces(iftype_conf, output={}, prefix=''):
- for ifname, if_conf in iftype_conf.items():
- if 'firewall' in if_conf:
- output[prefix + ifname] = if_conf['firewall']
- for vif in ['vif', 'vif_s', 'vif_c']:
- if vif in if_conf:
- output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.'))
- return output
- for iftype, iftype_conf in interfaces.items():
- out.update(find_interfaces(iftype_conf))
- return out
-
-def get_firewall_zones(conf):
- used_v4 = []
- used_v6 = []
- zone_policy = conf.get_config_dict(['zone-policy'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if 'zone' in zone_policy:
- for zone, zone_conf in zone_policy['zone'].items():
- if 'from' in zone_conf:
- for from_zone, from_conf in zone_conf['from'].items():
- name = dict_search_args(from_conf, 'firewall', 'name')
- if name:
- used_v4.append(name)
-
- ipv6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
- if ipv6_name:
- used_v6.append(ipv6_name)
-
- if 'intra_zone_filtering' in zone_conf:
- name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'name')
- if name:
- used_v4.append(name)
-
- ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name')
- if ipv6_name:
- used_v6.append(ipv6_name)
-
- return {'name': used_v4, 'ipv6_name': used_v6}
-
def geoip_updated(conf, firewall):
diff = get_config_diff(conf)
node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True)
@@ -206,12 +129,40 @@ def get_config(config=None):
firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
+ # We have gathered the dict representation of the CLI, but there are
+ # default options which we need to update into the dictionary retrived.
+ # XXX: T2665: we currently have no nice way for defaults under tag
+ # nodes, thus we load the defaults "by hand"
default_values = defaults(base)
+ for tmp in ['name', 'ipv6_name']:
+ if tmp in default_values:
+ del default_values[tmp]
+
+ if 'zone' in default_values:
+ del default_values['zone']
+
firewall = dict_merge(default_values, firewall)
+ # Merge in defaults for IPv4 ruleset
+ if 'name' in firewall:
+ default_values = defaults(base + ['name'])
+ for name in firewall['name']:
+ firewall['name'][name] = dict_merge(default_values,
+ firewall['name'][name])
+
+ # Merge in defaults for IPv6 ruleset
+ if 'ipv6_name' in firewall:
+ default_values = defaults(base + ['ipv6-name'])
+ for ipv6_name in firewall['ipv6_name']:
+ firewall['ipv6_name'][ipv6_name] = dict_merge(default_values,
+ firewall['ipv6_name'][ipv6_name])
+
+ if 'zone' in firewall:
+ default_values = defaults(base + ['zone'])
+ for zone in firewall['zone']:
+ firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone])
+
firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
- firewall['interfaces'] = get_firewall_interfaces(conf)
- firewall['zone_policy'] = get_firewall_zones(conf)
if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
diff = get_config_diff(conf)
@@ -315,7 +266,7 @@ def verify_nested_group(group_name, group, groups, seen):
if g in seen:
raise ConfigError(f'Group "{group_name}" has a circular reference')
-
+
seen.append(g)
if 'include' in groups[g]:
@@ -336,109 +287,99 @@ def verify(firewall):
for name in ['name', 'ipv6_name']:
if name in firewall:
for name_id, name_conf in firewall[name].items():
- if name_id in preserve_chains:
- raise ConfigError(f'Firewall name "{name_id}" is reserved for VyOS')
-
- if name_id.startswith("VZONE"):
- raise ConfigError(f'Firewall name "{name_id}" uses reserved prefix')
-
if 'rule' in name_conf:
for rule_id, rule_conf in name_conf['rule'].items():
verify_rule(firewall, rule_conf, name == 'ipv6_name')
- for ifname, if_firewall in firewall['interfaces'].items():
- for direction in ['in', 'out', 'local']:
- name = dict_search_args(if_firewall, direction, 'name')
- ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
+ if 'interface' in firewall:
+ for ifname, if_firewall in firewall['interface'].items():
+ # verify ifname needs to be disabled, dynamic devices come up later
+ # verify_interface_exists(ifname)
- if name and dict_search_args(firewall, 'name', name) == None:
- raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}')
+ for direction in ['in', 'out', 'local']:
+ name = dict_search_args(if_firewall, direction, 'name')
+ ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
- if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None:
- raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}')
+ if name and dict_search_args(firewall, 'name', name) == None:
+ raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}')
- for fw_name, used_names in firewall['zone_policy'].items():
- for name in used_names:
- if dict_search_args(firewall, fw_name, name) == None:
- raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy')
+ if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None:
+ raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}')
- return None
+ local_zone = False
+ zone_interfaces = []
-def cleanup_commands(firewall):
- commands = []
- commands_chains = []
- commands_sets = []
- for table in ['ip filter', 'ip6 filter']:
- name_node = 'name' if table == 'ip filter' else 'ipv6_name'
- chain_prefix = NAME_PREFIX if table == 'ip filter' else NAME6_PREFIX
- state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6'
- iface_chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains
-
- geoip_list = []
- if firewall['geoip_updated']:
- geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name'
- geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or []
-
- json_str = cmd(f'nft -t -j list table {table}')
- obj = loads(json_str)
-
- if 'nftables' not in obj:
- continue
-
- for item in obj['nftables']:
- if 'chain' in item:
- chain = item['chain']['name']
- if chain in preserve_chains or chain.startswith("VZONE"):
- continue
+ if 'zone' in firewall:
+ for zone, zone_conf in firewall['zone'].items():
+ if 'local_zone' not in zone_conf and 'interface' not in zone_conf:
+ raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone')
- if chain == state_chain:
- command = 'delete' if 'state_policy' not in firewall else 'flush'
- commands_chains.append(f'{command} chain {table} {chain}')
- elif dict_search_args(firewall, name_node, chain.replace(chain_prefix, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- else:
- commands_chains.append(f'delete chain {table} {chain}')
+ if 'local_zone' in zone_conf:
+ if local_zone:
+ raise ConfigError('There cannot be multiple local zones')
+ if 'interface' in zone_conf:
+ raise ConfigError('Local zone cannot have interfaces assigned')
+ if 'intra_zone_filtering' in zone_conf:
+ raise ConfigError('Local zone cannot use intra-zone-filtering')
+ local_zone = True
- if 'rule' in item:
- rule = item['rule']
- chain = rule['chain']
- handle = rule['handle']
+ if 'interface' in zone_conf:
+ found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces]
- if chain in iface_chains:
- target, _ = next(dict_search_recursive(rule['expr'], 'target'))
+ if found_duplicates:
+ raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
- if target == state_chain and 'state_policy' not in firewall:
- commands.append(f'delete rule {table} {chain} handle {handle}')
+ zone_interfaces += zone_conf['interface']
- if target.startswith(chain_prefix):
- if dict_search_args(firewall, name_node, target.replace(chain_prefix, "", 1)) == None:
- commands.append(f'delete rule {table} {chain} handle {handle}')
+ if 'intra_zone_filtering' in zone_conf:
+ intra_zone = zone_conf['intra_zone_filtering']
- if 'set' in item:
- set_name = item['set']['name']
+ if len(intra_zone) > 1:
+ raise ConfigError('Only one intra-zone-filtering action must be specified')
- if set_name.startswith('GEOIP_CC_') and set_name in geoip_list:
- commands_sets.append(f'delete set {table} {set_name}')
- continue
-
- if set_name.startswith("RECENT_"):
- commands_sets.append(f'delete set {table} {set_name}')
- continue
+ if 'firewall' in intra_zone:
+ v4_name = dict_search_args(intra_zone, 'firewall', 'name')
+ if v4_name and not dict_search_args(firewall, 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+
+ v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name')
+ if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+
+ if not v4_name and not v6_name:
+ raise ConfigError('No firewall names specified for intra-zone-filtering')
+
+ if 'from' in zone_conf:
+ for from_zone, from_conf in zone_conf['from'].items():
+ if from_zone not in firewall['zone']:
+ raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"')
+
+ v4_name = dict_search_args(from_conf, 'firewall', 'name')
+ if v4_name and not dict_search_args(firewall, 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+
+ v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
+ if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
- for prefix, group_type in group_set_prefix.items():
- if set_name.startswith(prefix):
- group_name = set_name.replace(prefix, "", 1)
- if dict_search_args(firewall, 'group', group_type, group_name) != None:
- commands_sets.append(f'flush set {table} {set_name}')
- else:
- commands_sets.append(f'delete set {table} {set_name}')
- return commands + commands_chains + commands_sets
+ return None
def generate(firewall):
if not os.path.exists(nftables_conf):
firewall['first_install'] = True
- else:
- firewall['cleanup_commands'] = cleanup_commands(firewall)
+
+ if 'zone' in firewall:
+ for local_zone, local_zone_conf in firewall['zone'].items():
+ if 'local_zone' not in local_zone_conf:
+ continue
+
+ local_zone_conf['from_local'] = {}
+
+ for zone, zone_conf in firewall['zone'].items():
+ if zone == local_zone or 'from' not in zone_conf:
+ continue
+ if local_zone in zone_conf['from']:
+ local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]
render(nftables_conf, 'firewall/nftables.j2', firewall)
return None
@@ -499,28 +440,18 @@ def post_apply_trap(firewall):
cmd(base_cmd + ' '.join(objects))
-def state_policy_rule_exists():
- # Determine if state policy rules already exist in nft
- search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD')
- return 'VYOS_STATE_POLICY' in search_str
-
def resync_policy_route():
# Update policy route as firewall groups were updated
- tmp = run(policy_route_conf_script)
+ tmp, out = rc_cmd(policy_route_conf_script)
if tmp > 0:
- Warning('Failed to re-apply policy route configuration!')
+ Warning(f'Failed to re-apply policy route configuration! {out}')
def apply(firewall):
- if 'first_install' in firewall:
- run('nfct helper add rpc inet tcp')
- run('nfct helper add rpc inet udp')
- run('nfct helper add tns inet tcp')
-
- install_result = run(f'nft -f {nftables_conf}')
+ install_result, output = rc_cmd(f'nft -f {nftables_conf}')
if install_result == 1:
- raise ConfigError('Failed to apply firewall')
+ raise ConfigError(f'Failed to apply firewall: {output}')
- # set fireall group domain-group xxx
+ # set firewall group domain-group xxx
if 'group' in firewall:
if 'domain_group' in firewall['group']:
# T970 Enable a resolver (systemd daemon) that checks
@@ -541,13 +472,6 @@ def apply(firewall):
else:
call('systemctl stop vyos-domain-group-resolve.service')
- if 'state_policy' in firewall and not state_policy_rule_exists():
- for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']:
- cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY')
-
- for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']:
- cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6')
-
apply_sysfs(firewall)
if firewall['policy_resync']:
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index e14050dd3..8a959dc79 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -88,15 +88,12 @@ def verify(ha):
if not {'password', 'type'} <= set(group_config['authentication']):
raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"')
- # We can not use a VRID once per interface
+ # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
+ # We also need to make sure VRID is not used twice on the same interface with the
+ # same address family.
+
interface = group_config['interface']
vrid = group_config['vrid']
- tmp = {'interface': interface, 'vrid': vrid}
- if tmp in used_vrid_if:
- raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface}"!')
- used_vrid_if.append(tmp)
-
- # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
# XXX: filter on map object is destructive, so we force it to list.
# Additionally, filter objects always evaluate to True, empty or not,
@@ -109,6 +106,11 @@ def verify(ha):
raise ConfigError(f'VRRP group "{group}" mixes IPv4 and IPv6 virtual addresses, this is not allowed.\n' \
'Create individual groups for IPv4 and IPv6!')
if vaddrs4:
+ tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv4'}
+ if tmp in used_vrid_if:
+ raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface} with address family IPv4"!')
+ used_vrid_if.append(tmp)
+
if 'hello_source_address' in group_config:
if is_ipv6(group_config['hello_source_address']):
raise ConfigError(f'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!')
@@ -118,6 +120,11 @@ def verify(ha):
raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!')
if vaddrs6:
+ tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv6'}
+ if tmp in used_vrid_if:
+ raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface} with address family IPv6"!')
+ used_vrid_if.append(tmp)
+
if 'hello_source_address' in group_config:
if is_ipv4(group_config['hello_source_address']):
raise ConfigError(f'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!')
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-bonding.py b/src/conf_mode/interfaces-bonding.py
index 7e146f446..21cf204fc 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.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
@@ -73,63 +73,83 @@ def get_config(config=None):
# To make our own life easier transfor the list of member interfaces
# into a dictionary - we will use this to add additional information
- # later on for wach member
+ # later on for each member
if 'member' in bond and 'interface' in bond['member']:
- # convert list if member interfaces to a dictionary
- bond['member']['interface'] = dict.fromkeys(
- bond['member']['interface'], {})
+ # convert list of member interfaces to a dictionary
+ bond['member']['interface'] = {k: {} for k in bond['member']['interface']}
if 'mode' in bond:
bond['mode'] = get_bond_mode(bond['mode'])
tmp = leaf_node_changed(conf, base + [ifname, 'mode'])
- if tmp: bond.update({'shutdown_required': {}})
+ if tmp: bond['shutdown_required'] = {}
tmp = leaf_node_changed(conf, base + [ifname, 'lacp-rate'])
- if tmp: bond.update({'shutdown_required': {}})
+ if tmp: bond['shutdown_required'] = {}
# determine which members have been removed
interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface'])
+
+ # Reset config level to interfaces
+ old_level = conf.get_level()
+ conf.set_level(['interfaces'])
+
if interfaces_removed:
- bond.update({'shutdown_required': {}})
+ bond['shutdown_required'] = {}
if 'member' not in bond:
- bond.update({'member': {}})
+ bond['member'] = {}
tmp = {}
for interface in interfaces_removed:
section = Section.section(interface) # this will be 'ethernet' for 'eth0'
- if conf.exists(['insterfaces', section, interface, 'disable']):
- tmp.update({interface : {'disable': ''}})
+ if conf.exists([section, interface, 'disable']):
+ tmp[interface] = {'disable': ''}
else:
- tmp.update({interface : {}})
+ tmp[interface] = {}
# also present the interfaces to be removed from the bond as dictionary
- bond['member'].update({'interface_remove': tmp})
+ bond['member']['interface_remove'] = tmp
+
+ # Restore existing config level
+ conf.set_level(old_level)
if dict_search('member.interface', bond):
for interface, interface_config in bond['member']['interface'].items():
+ # Check if member interface is a new member
+ if not conf.exists_effective(['member', 'interface', interface]):
+ bond['shutdown_required'] = {}
+
+ # Check if member interface is disabled
+ conf.set_level(['interfaces'])
+
+ section = Section.section(interface) # this will be 'ethernet' for 'eth0'
+ if conf.exists([section, interface, 'disable']):
+ interface_config['disable'] = ''
+
+ conf.set_level(old_level)
+
# Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
- if tmp: bond['member']['interface'][interface].update({'is_bridge_member' : tmp})
+ if tmp: interface_config['is_bridge_member'] = tmp
# Check if member interface is already member of a bond
tmp = is_member(conf, interface, 'bonding')
for tmp in is_member(conf, interface, 'bonding'):
if bond['ifname'] == tmp:
continue
- bond['member']['interface'][interface].update({'is_bond_member' : tmp})
+ interface_config['is_bond_member'] = tmp
# Check if member interface is used as source-interface on another interface
tmp = is_source_interface(conf, interface)
- if tmp: bond['member']['interface'][interface].update({'is_source_interface' : tmp})
+ if tmp: interface_config['is_source_interface'] = tmp
# bond members must not have an assigned address
tmp = has_address_configured(conf, interface)
- if tmp: bond['member']['interface'][interface].update({'has_address' : {}})
+ if tmp: interface_config['has_address'] = {}
# bond members must not have a VRF attached
tmp = has_vrf_configured(conf, interface)
- if tmp: bond['member']['interface'][interface].update({'has_vrf' : {}})
+ if tmp: interface_config['has_vrf'] = {}
return bond
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-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index ef745d737..8155f36c2 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -55,6 +55,7 @@ from vyos.util import chown
from vyos.util import cmd
from vyos.util import dict_search
from vyos.util import dict_search_args
+from vyos.util import is_list_equal
from vyos.util import makedir
from vyos.util import read_file
from vyos.util import write_file
@@ -274,7 +275,7 @@ def verify(openvpn):
elif v6remAddr and not v6loAddr:
raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"')
- if (v4loAddr == v4remAddr) or (v6remAddr == v4remAddr):
+ if is_list_equal(v4loAddr, v4remAddr) or is_list_equal(v6loAddr, v6remAddr):
raise ConfigError('"local-address" and "remote-address" cannot be the same')
if dict_search('local_host', openvpn) in dict_search('local_address', openvpn):
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index f26a50a0e..4c65bc0b6 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
@@ -15,10 +15,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sys import exit
+from netifaces import interfaces
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.configdict import leaf_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
@@ -48,9 +51,16 @@ def get_config(config=None):
mode = is_node_changed(conf, ['mode'])
if mode: peth.update({'shutdown_required' : {}})
+ if leaf_node_changed(conf, base + [ifname, 'mode']):
+ peth.update({'rebuild_required': {}})
+
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):
@@ -72,21 +82,18 @@ def generate(peth):
return None
def apply(peth):
- if 'deleted' in peth:
- # delete interface
- MACVLANIf(peth['ifname']).remove()
- return None
+ # Check if the MACVLAN interface already exists
+ if 'rebuild_required' in peth or 'deleted' in peth:
+ if peth['ifname'] in interfaces():
+ p = MACVLANIf(peth['ifname'])
+ # MACVLAN is always needs to be recreated,
+ # thus we can simply always delete it first.
+ p.remove()
- # Check if MACVLAN interface already exists. Parameters like the underlaying
- # source-interface device or mode can not be changed on the fly and the
- # interface needs to be recreated from the bottom.
- if 'mode_old' in peth:
- MACVLANIf(peth['ifname']).remove()
+ if 'deleted' not in peth:
+ p = MACVLANIf(**peth)
+ p.update(peth)
- # It is safe to "re-create" the interface always, there is a sanity check
- # that the interface will only be create if its non existent
- p = MACVLANIf(**peth)
- p.update(peth)
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 85819a77e..e75418ba5 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -44,7 +44,8 @@ if LooseVersion(kernel_version()) > LooseVersion('5.1'):
else:
k_mod = ['nft_nat', 'nft_chain_nat_ipv4']
-nftables_nat_config = '/tmp/vyos-nat-rules.nft'
+nftables_nat_config = '/run/nftables_nat.conf'
+nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
def get_handler(json, chain, target):
""" Get nftable rule handler number of given chain/target combination.
@@ -88,7 +89,7 @@ def get_config(config=None):
# T2665: we must add the tagNode defaults individually until this is
# moved to the base class
- for direction in ['source', 'destination']:
+ for direction in ['source', 'destination', 'static']:
if direction in nat:
default_values = defaults(base + [direction, 'rule'])
for rule in dict_search(f'{direction}.rule', nat) or []:
@@ -178,24 +179,35 @@ def verify(nat):
# common rule verification
verify_rule(config, err_msg)
+ if dict_search('static.rule', nat):
+ for rule, config in dict_search('static.rule', nat).items():
+ err_msg = f'Static NAT configuration error in rule {rule}:'
+
+ if 'inbound_interface' not in config:
+ raise ConfigError(f'{err_msg}\n' \
+ 'inbound-interface not specified')
+
+ # common rule verification
+ verify_rule(config, err_msg)
+
return None
def generate(nat):
render(nftables_nat_config, 'firewall/nftables-nat.j2', nat)
+ render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat)
# dry-run newly generated configuration
tmp = run(f'nft -c -f {nftables_nat_config}')
if tmp > 0:
- if os.path.exists(nftables_nat_config):
- os.unlink(nftables_nat_config)
raise ConfigError('Configuration file errors encountered!')
+ tmp = run(f'nft -c -f {nftables_nat_config}')
+
return None
def apply(nat):
cmd(f'nft -f {nftables_nat_config}')
- if os.path.isfile(nftables_nat_config):
- os.unlink(nftables_nat_config)
+ cmd(f'nft -f {nftables_static_nat_conf}')
return 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/policy-route.py b/src/conf_mode/policy-route.py
index 9fddbd2c6..00539b9c7 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -92,7 +92,7 @@ def get_config(config=None):
return policy
-def verify_rule(policy, name, rule_conf, ipv6):
+def verify_rule(policy, name, rule_conf, ipv6, rule_id):
icmp = 'icmp' if not ipv6 else 'icmpv6'
if icmp in rule_conf:
icmp_defined = False
@@ -166,7 +166,7 @@ def verify(policy):
for name, pol_conf in policy[route].items():
if 'rule' in pol_conf:
for rule_id, rule_conf in pol_conf['rule'].items():
- verify_rule(policy, name, rule_conf, ipv6)
+ verify_rule(policy, name, rule_conf, ipv6, rule_id)
for ifname, if_policy in policy['interfaces'].items():
name = dict_search_args(if_policy, 'route')
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index b247ce2ab..d28ced4fd 100755
--- a/src/conf_mode/protocols_nhrp.py
+++ b/src/conf_mode/protocols_nhrp.py
@@ -14,10 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
+
from vyos.config import Config
from vyos.configdict import node_changed
-from vyos.firewall import find_nftables_rule
-from vyos.firewall import remove_nftables_rule
from vyos.template import render
from vyos.util import process_named_running
from vyos.util import run
@@ -26,6 +26,7 @@ from vyos import airbag
airbag.enable()
opennhrp_conf = '/run/opennhrp/opennhrp.conf'
+nhrp_nftables_conf = '/run/nftables_nhrp.conf'
def get_config(config=None):
if config:
@@ -84,28 +85,23 @@ def verify(nhrp):
return None
def generate(nhrp):
+ if not os.path.exists(nhrp_nftables_conf):
+ nhrp['first_install'] = True
+
render(opennhrp_conf, 'nhrp/opennhrp.conf.j2', nhrp)
+ render(nhrp_nftables_conf, 'nhrp/nftables.conf.j2', nhrp)
return None
def apply(nhrp):
- if 'tunnel' in nhrp:
- for tunnel, tunnel_conf in nhrp['tunnel'].items():
- if 'source_address' in nhrp['if_tunnel'][tunnel]:
- comment = f'VYOS_NHRP_{tunnel}'
- source_address = nhrp['if_tunnel'][tunnel]['source_address']
-
- rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', ['ip protocol gre', f'ip saddr {source_address}', 'ip daddr 224.0.0.0/4'])
- if not rule_handle:
- run(f'sudo nft insert rule ip filter VYOS_FW_OUTPUT ip protocol gre ip saddr {source_address} ip daddr 224.0.0.0/4 counter drop comment "{comment}"')
-
- for tunnel in nhrp['del_tunnels']:
- comment = f'VYOS_NHRP_{tunnel}'
- rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', [f'comment "{comment}"'])
- if rule_handle:
- remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle)
+ nft_rc = run(f'nft -f {nhrp_nftables_conf}')
+ if nft_rc != 0:
+ raise ConfigError('Failed to apply NHRP tunnel firewall rules')
action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
- run(f'systemctl {action} opennhrp.service')
+ service_rc = run(f'systemctl {action} opennhrp.service')
+ if service_rc != 0:
+ raise ConfigError(f'Failed to {action} the NHRP service')
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 62f5e1ddf..427cb6911 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,40 +34,15 @@ 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'
-
-
-def get_interfaces(type='', vlan=True):
- """
- Get interfaces
- get_interfaces()
- ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
-
- get_interfaces("dummy")
- ['dum0']
- """
- interfaces = []
- ifaces = Section.interfaces(type)
- for iface in ifaces:
- if vlan == False and '.' in iface:
- continue
- interfaces.append(iface)
-
- return interfaces
+systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf'
def get_nft_filter_chains():
- """
- Get nft chains for table filter
- """
- nft = cmd('nft --json list table ip filter')
+ """ Get nft chains for table filter """
+ nft = cmd('nft --json list table ip vyos_filter')
nft = json.loads(nft)
chain_list = []
@@ -76,9 +53,7 @@ def get_nft_filter_chains():
return chain_list
-
def get_config(config=None):
-
if config:
conf = config
else:
@@ -87,8 +62,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.
@@ -96,7 +75,7 @@ def get_config(config=None):
monitoring = dict_merge(default_values, monitoring)
monitoring['custom_scripts_dir'] = custom_scripts_dir
- monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False)
+ monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False)
monitoring['nft_chains'] = get_nft_filter_chains()
# Redefine azure group-metrics 'single-table' and 'table-per-metric'
@@ -131,6 +110,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 +154,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 +171,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/system_console.py b/src/conf_mode/system_console.py
index 86985d765..e922edc4e 100755
--- a/src/conf_mode/system_console.py
+++ b/src/conf_mode/system_console.py
@@ -16,6 +16,7 @@
import os
import re
+from pathlib import Path
from vyos.config import Config
from vyos.configdict import dict_merge
@@ -68,18 +69,15 @@ def verify(console):
# amount of connected devices. We will resolve the fixed device name
# to its dynamic device file - and create a new dict entry for it.
by_bus_device = f'{by_bus_dir}/{device}'
- if os.path.isdir(by_bus_dir) and os.path.exists(by_bus_device):
- device = os.path.basename(os.readlink(by_bus_device))
-
- # If the device name still starts with usbXXX no matching tty was found
- # and it can not be used as a serial interface
- if device.startswith('usb'):
- raise ConfigError(f'Device {device} does not support beeing used as tty')
+ # If the device name still starts with usbXXX no matching tty was found
+ # and it can not be used as a serial interface
+ if not os.path.isdir(by_bus_dir) or not os.path.exists(by_bus_device):
+ raise ConfigError(f'Device {device} does not support beeing used as tty')
return None
def generate(console):
- base_dir = '/etc/systemd/system'
+ base_dir = '/run/systemd/system'
# Remove all serial-getty configuration files in advance
for root, dirs, files in os.walk(base_dir):
for basename in files:
@@ -90,7 +88,8 @@ def generate(console):
if not console or 'device' not in console:
return None
- for device, device_config in console['device'].items():
+ # replace keys in the config for ttyUSB items to use them in `apply()` later
+ for device in console['device'].copy():
if device.startswith('usb'):
# It is much easiert to work with the native ttyUSBn name when using
# getty, but that name may change across reboots - depending on the
@@ -98,9 +97,17 @@ def generate(console):
# to its dynamic device file - and create a new dict entry for it.
by_bus_device = f'{by_bus_dir}/{device}'
if os.path.isdir(by_bus_dir) and os.path.exists(by_bus_device):
- device = os.path.basename(os.readlink(by_bus_device))
+ device_updated = os.path.basename(os.readlink(by_bus_device))
+
+ # replace keys in the config to use them in `apply()` later
+ console['device'][device_updated] = console['device'][device]
+ del console['device'][device]
+ else:
+ raise ConfigError(f'Device {device} does not support beeing used as tty')
+ for device, device_config in console['device'].items():
config_file = base_dir + f'/serial-getty@{device}.service'
+ Path(f'{base_dir}/getty.target.wants').mkdir(exist_ok=True)
getty_wants_symlink = base_dir + f'/getty.target.wants/serial-getty@{device}.service'
render(config_file, 'getty/serial-getty.service.j2', device_config)
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index c0fe3ae5d..c9061366d 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -604,13 +604,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 a3e774678..c050b796b 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -25,6 +25,7 @@ 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
@@ -57,15 +58,16 @@ def get_config():
default_values = defaults(base)
ocserv = dict_merge(default_values, ocserv)
- # workaround a "know limitation" - https://phabricator.vyos.net/T2665
- del ocserv['authentication']['local_users']['username']['otp']
- if not ocserv["authentication"]["local_users"]["username"]:
- raise ConfigError('openconnect mode local required at least one user')
- default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
- for user, params in ocserv['authentication']['local_users']['username'].items():
- # Not every configuration requires OTP settings
- if ocserv['authentication']['local_users']['username'][user].get('otp'):
- ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
+ if "local" in ocserv["authentication"]["mode"]:
+ # workaround a "know limitation" - https://phabricator.vyos.net/T2665
+ del ocserv['authentication']['local_users']['username']['otp']
+ if not ocserv["authentication"]["local_users"]["username"]:
+ raise ConfigError('openconnect mode local required at least one user')
+ default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
+ for user, params in ocserv['authentication']['local_users']['username'].items():
+ # Not every configuration requires OTP settings
+ if ocserv['authentication']['local_users']['username'][user].get('otp'):
+ ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
if ocserv:
ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
@@ -77,9 +79,12 @@ 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:
+ if check_port_availability(ocserv['listen_address'], 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/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py
deleted file mode 100755
index a52c52706..000000000
--- a/src/conf_mode/zone_policy.py
+++ /dev/null
@@ -1,213 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-
-from json import loads
-from sys import exit
-
-from vyos.config import Config
-from vyos.configdict import dict_merge
-from vyos.template import render
-from vyos.util import cmd
-from vyos.util import dict_search_args
-from vyos.util import run
-from vyos.xml import defaults
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-nftables_conf = '/run/nftables_zone.conf'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- base = ['zone-policy']
- zone_policy = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- zone_policy['firewall'] = conf.get_config_dict(['firewall'],
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if 'zone' in zone_policy:
- # 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 + ['zone'])
- for zone in zone_policy['zone']:
- zone_policy['zone'][zone] = dict_merge(default_values,
- zone_policy['zone'][zone])
-
- return zone_policy
-
-def verify(zone_policy):
- # bail out early - looks like removal from running config
- if not zone_policy:
- return None
-
- local_zone = False
- interfaces = []
-
- if 'zone' in zone_policy:
- for zone, zone_conf in zone_policy['zone'].items():
- if 'local_zone' not in zone_conf and 'interface' not in zone_conf:
- raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone')
-
- if 'local_zone' in zone_conf:
- if local_zone:
- raise ConfigError('There cannot be multiple local zones')
- if 'interface' in zone_conf:
- raise ConfigError('Local zone cannot have interfaces assigned')
- if 'intra_zone_filtering' in zone_conf:
- raise ConfigError('Local zone cannot use intra-zone-filtering')
- local_zone = True
-
- if 'interface' in zone_conf:
- found_duplicates = [intf for intf in zone_conf['interface'] if intf in interfaces]
-
- if found_duplicates:
- raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
-
- interfaces += zone_conf['interface']
-
- if 'intra_zone_filtering' in zone_conf:
- intra_zone = zone_conf['intra_zone_filtering']
-
- if len(intra_zone) > 1:
- raise ConfigError('Only one intra-zone-filtering action must be specified')
-
- if 'firewall' in intra_zone:
- v4_name = dict_search_args(intra_zone, 'firewall', 'name')
- if v4_name and not dict_search_args(zone_policy, 'firewall', 'name', v4_name):
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6-name')
- if v6_name and not dict_search_args(zone_policy, 'firewall', 'ipv6-name', v6_name):
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
-
- if not v4_name and not v6_name:
- raise ConfigError('No firewall names specified for intra-zone-filtering')
-
- if 'from' in zone_conf:
- for from_zone, from_conf in zone_conf['from'].items():
- if from_zone not in zone_policy['zone']:
- raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"')
-
- v4_name = dict_search_args(from_conf, 'firewall', 'name')
- if v4_name:
- if 'name' not in zone_policy['firewall']:
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- if not dict_search_args(zone_policy, 'firewall', 'name', v4_name):
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- v6_name = dict_search_args(from_conf, 'firewall', 'v6_name')
- if v6_name:
- if 'ipv6_name' not in zone_policy['firewall']:
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
-
- if not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name):
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
-
- return None
-
-def has_ipv4_fw(zone_conf):
- if 'from' not in zone_conf:
- return False
- zone_from = zone_conf['from']
- return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'name')])
-
-def has_ipv6_fw(zone_conf):
- if 'from' not in zone_conf:
- return False
- zone_from = zone_conf['from']
- return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'ipv6_name')])
-
-def get_local_from(zone_policy, local_zone_name):
- # Get all zone firewall names from the local zone
- out = {}
- for zone, zone_conf in zone_policy['zone'].items():
- if zone == local_zone_name:
- continue
- if 'from' not in zone_conf:
- continue
- if local_zone_name in zone_conf['from']:
- out[zone] = zone_conf['from'][local_zone_name]
- return out
-
-def cleanup_commands():
- commands = []
- for table in ['ip filter', 'ip6 filter']:
- json_str = cmd(f'nft -t -j list table {table}')
- obj = loads(json_str)
- if 'nftables' not in obj:
- continue
- for item in obj['nftables']:
- if 'rule' in item:
- chain = item['rule']['chain']
- handle = item['rule']['handle']
- if 'expr' not in item['rule']:
- continue
- for expr in item['rule']['expr']:
- target = dict_search_args(expr, 'jump', 'target')
- if not target:
- continue
- if target.startswith("VZONE") or target.startswith("VYOS_STATE_POLICY"):
- commands.append(f'delete rule {table} {chain} handle {handle}')
- for item in obj['nftables']:
- if 'chain' in item:
- if item['chain']['name'].startswith("VZONE"):
- chain = item['chain']['name']
- commands.append(f'delete chain {table} {chain}')
- return commands
-
-def generate(zone_policy):
- data = zone_policy or {}
-
- if os.path.exists(nftables_conf): # Check to see if we've run before
- data['cleanup_commands'] = cleanup_commands()
-
- if 'zone' in data:
- for zone, zone_conf in data['zone'].items():
- zone_conf['ipv4'] = has_ipv4_fw(zone_conf)
- zone_conf['ipv6'] = has_ipv6_fw(zone_conf)
-
- if 'local_zone' in zone_conf:
- zone_conf['from_local'] = get_local_from(data, zone)
-
- render(nftables_conf, 'zone_policy/nftables.j2', data)
- return None
-
-def apply(zone_policy):
- install_result = run(f'nft -f {nftables_conf}')
- if install_result != 0:
- raise ConfigError('Failed to apply zone-policy')
-
- return None
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py
index 8274e6564..bf25a7331 100755
--- a/src/etc/opennhrp/opennhrp-script.py
+++ b/src/etc/opennhrp/opennhrp-script.py
@@ -18,44 +18,126 @@ 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_generator = session.terminate(
+ {'ike-id': ikeid, 'timeout': '-1'})
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
+ 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 +158,222 @@ 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_generator = 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)
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
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 +386,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/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
index bf4bfd05d..cbc2bfe6b 100755
--- a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
+++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
@@ -11,7 +11,7 @@ def get_nft_filter_chains():
"""
Get list of nft chains for table filter
"""
- nft = cmd('/usr/sbin/nft --json list table ip filter')
+ nft = cmd('/usr/sbin/nft --json list table ip vyos_filter')
nft = json.loads(nft)
chain_list = []
@@ -27,7 +27,7 @@ def get_nftables_details(name):
"""
Get dict, counters packets and bytes for chain
"""
- command = f'/usr/sbin/nft list chain ip filter {name}'
+ command = f'/usr/sbin/nft list chain ip vyos_filter {name}'
try:
results = cmd(command)
except:
@@ -60,7 +60,7 @@ def get_nft_telegraf(name):
Get data for telegraf in influxDB format
"""
for rule, rule_config in get_nftables_details(name).items():
- print(f'nftables,table=filter,chain={name},'
+ print(f'nftables,table=vyos_filter,chain={name},'
f'ruleid={rule} '
f'pkts={rule_config["packets"]}i,'
f'bytes={rule_config["bytes"]}i '
diff --git a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
index 0c7474156..6f14d6a8e 100755
--- a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
+++ b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
@@ -5,20 +5,6 @@ from vyos.ifconfig import Interface
import time
-def get_interfaces(type='', vlan=True):
- """
- Get interfaces:
- ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
- """
- interfaces = []
- ifaces = Section.interfaces(type)
- for iface in ifaces:
- if vlan == False and '.' in iface:
- continue
- interfaces.append(iface)
-
- return interfaces
-
def get_interface_addresses(iface, link_local_v6=False):
"""
Get IP and IPv6 addresses from interface in one string
@@ -77,7 +63,7 @@ def get_interface_oper_state(iface):
return oper_state
-interfaces = get_interfaces()
+interfaces = Section.interfaces('')
for iface in interfaces:
print(f'show_interfaces,interface={iface} '
diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8
new file mode 100755
index 000000000..ce527acf5
--- /dev/null
+++ b/src/migration-scripts/firewall/7-to-8
@@ -0,0 +1,98 @@
+#!/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/>.
+
+# T2199: Migrate interface firewall nodes to firewall interfaces <ifname> <direction> name/ipv6-name <name>
+# T2199: Migrate zone-policy to firewall node
+
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['firewall']
+zone_base = ['zone-policy']
+config = ConfigTree(config_file)
+
+if not config.exists(base) and not config.exists(zone_base):
+ # Nothing to do
+ exit(0)
+
+def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None):
+ if_path = ['interfaces', iftype, ifname]
+ ifname_full = ifname
+
+ if vif:
+ if_path += ['vif', vif]
+ ifname_full = f'{ifname}.{vif}'
+ elif vifs:
+ if_path += ['vif-s', vifs]
+ ifname_full = f'{ifname}.{vifs}'
+ if vifc:
+ if_path += ['vif-c', vifc]
+ ifname_full = f'{ifname}.{vifs}.{vifc}'
+
+ if not config.exists(if_path + ['firewall']):
+ return
+
+ if not config.exists(['firewall', 'interface']):
+ config.set(['firewall', 'interface'])
+ config.set_tag(['firewall', 'interface'])
+
+ config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full])
+ config.delete(if_path + ['firewall'])
+
+for iftype in config.list_nodes(['interfaces']):
+ for ifname in config.list_nodes(['interfaces', iftype]):
+ migrate_interface(config, iftype, ifname)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif']):
+ for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']):
+ migrate_interface(config, iftype, ifname, vif=vif)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif-s']):
+ for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']):
+ migrate_interface(config, iftype, ifname, vifs=vifs)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
+ for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
+ migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc)
+
+if config.exists(zone_base + ['zone']):
+ config.set(['firewall', 'zone'])
+ config.set_tag(['firewall', 'zone'])
+
+ for zone in config.list_nodes(zone_base + ['zone']):
+ config.copy(zone_base + ['zone', zone], ['firewall', 'zone', zone])
+ config.delete(zone_base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py
index fe8dadd70..5a821a287 100755
--- a/src/op_mode/bridge.py
+++ b/src/op_mode/bridge.py
@@ -22,7 +22,7 @@ import typing
from sys import exit
from tabulate import tabulate
-from vyos.util import cmd
+from vyos.util import cmd, rc_cmd
from vyos.util import dict_search
import vyos.opmode
@@ -57,7 +57,11 @@ def _get_raw_data_fdb(bridge):
"""Get MAC-address for the bridge brX
:returns list
"""
- json_data = cmd(f'sudo bridge --json fdb show br {bridge}')
+ code, json_data = rc_cmd(f'sudo bridge --json fdb show br {bridge}')
+ # From iproute2 fdb.c, fdb_show() will only exit(-1) in case of
+ # non-existent bridge device; raise error.
+ if code == 255:
+ raise vyos.opmode.UnconfiguredSubsystem(f"no such bridge device {bridge}")
data_dict = json.loads(json_data)
return data_dict
diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py
index 036226418..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,6 +126,14 @@ 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__])
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index 49c8e6142..a4d1b4cb1 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -16,13 +16,122 @@
import re
import sys
+
+from collections import OrderedDict
+from hurry import filesize
+from re import split as re_split
+from tabulate import tabulate
+
from vyos.util import call
+from vyos.util import convert_data
+from vyos.util import seconds_to_human
+
import vyos.opmode
SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+def _convert(text):
+ return int(text) if text.isdigit() else text.lower()
+
+
+def _alphanum_key(key):
+ return [_convert(c) for c in re_split('([0-9]+)', str(key))]
+
+
+def _get_vici_sas():
+ from vici import Session as vici_session
+
+ session = vici_session()
+ sas = list(session.list_sas())
+ return sas
+
+
+def _get_raw_data_sas():
+ get_sas = _get_vici_sas()
+ sas = convert_data(get_sas)
+ return sas
+
+
+def _get_formatted_output_sas(sas):
+ sa_data = []
+ for sa in sas:
+ for parent_sa in sa.values():
+ # create an item for each child-sa
+ for child_sa in parent_sa.get('child-sas', {}).values():
+ # prepare a list for output data
+ sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A'
+
+ # collect raw data
+ sa_name = child_sa.get('name')
+ sa_state = child_sa.get('state')
+ sa_uptime = child_sa.get('install-time')
+ sa_bytes_in = child_sa.get('bytes-in')
+ sa_bytes_out = child_sa.get('bytes-out')
+ sa_packets_in = child_sa.get('packets-in')
+ sa_packets_out = child_sa.get('packets-out')
+ sa_remote_addr = parent_sa.get('remote-host')
+ sa_remote_id = parent_sa.get('remote-id')
+ sa_proposal_encr_alg = child_sa.get('encr-alg')
+ sa_proposal_integ_alg = child_sa.get('integ-alg')
+ sa_proposal_encr_keysize = child_sa.get('encr-keysize')
+ sa_proposal_dh_group = child_sa.get('dh-group')
+
+ # format data to display
+ if sa_name:
+ sa_out_name = sa_name
+ if sa_state:
+ if sa_state == 'INSTALLED':
+ sa_out_state = 'up'
+ else:
+ sa_out_state = 'down'
+ if sa_uptime:
+ sa_out_uptime = seconds_to_human(sa_uptime)
+ if sa_bytes_in and sa_bytes_out:
+ bytes_in = filesize.size(int(sa_bytes_in))
+ bytes_out = filesize.size(int(sa_bytes_out))
+ sa_out_bytes = f'{bytes_in}/{bytes_out}'
+ if sa_packets_in and sa_packets_out:
+ packets_in = filesize.size(int(sa_packets_in),
+ system=filesize.si)
+ packets_out = filesize.size(int(sa_packets_out),
+ system=filesize.si)
+ packets_str = f'{packets_in}/{packets_out}'
+ sa_out_packets = re.sub(r'B', r'', packets_str)
+ if sa_remote_addr:
+ sa_out_remote_addr = sa_remote_addr
+ if sa_remote_id:
+ sa_out_remote_id = sa_remote_id
+ # format proposal
+ if sa_proposal_encr_alg:
+ sa_out_proposal = sa_proposal_encr_alg
+ if sa_proposal_encr_keysize:
+ sa_proposal_encr_keysize_str = sa_proposal_encr_keysize
+ sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}'
+ if sa_proposal_integ_alg:
+ sa_proposal_integ_alg_str = sa_proposal_integ_alg
+ sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}'
+ if sa_proposal_dh_group:
+ sa_proposal_dh_group_str = sa_proposal_dh_group
+ sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}'
+
+ # add a new item to output data
+ sa_data.append([
+ sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes,
+ sa_out_packets, sa_out_remote_addr, sa_out_remote_id,
+ sa_out_proposal
+ ])
+
+ headers = [
+ "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out",
+ "Remote address", "Remote ID", "Proposal"
+ ]
+ sa_data = sorted(sa_data, key=_alphanum_key)
+ output = tabulate(sa_data, headers)
+ return output
+
+
def get_peer_connections(peer, tunnel, return_all = False):
peer = peer.replace(':', '-')
search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*'
@@ -61,6 +170,13 @@ def reset_peer(peer: str, tunnel:str):
print('Peer reset result: ' + ('success' if result else 'failed'))
+def show_sa(raw: bool):
+ sa_data = _get_raw_data_sas()
+ if raw:
+ return sa_data
+ return _get_formatted_output_sas(sa_data)
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index 12fc4c782..1339d5b92 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -17,6 +17,7 @@
import jmespath
import json
import sys
+import xmltodict
from sys import exit
from tabulate import tabulate
@@ -27,7 +28,30 @@ from vyos.util import dict_search
import vyos.opmode
-def _get_json_data(direction):
+def _get_xml_translation(direction, family):
+ """
+ Get conntrack XML output --src-nat|--dst-nat
+ """
+ if direction == 'source':
+ opt = '--src-nat'
+ if direction == 'destination':
+ opt = '--dst-nat'
+ return cmd(f'sudo conntrack --dump --family {family} {opt} --output xml')
+
+
+def _xml_to_dict(xml):
+ """
+ Convert XML to dictionary
+ Return: dictionary
+ """
+ parse = xmltodict.parse(xml, attr_prefix='')
+ # If only one conntrack entry we must change dict
+ if 'meta' in parse['conntrack']['flow']:
+ return dict(conntrack={'flow': [parse['conntrack']['flow']]})
+ return parse
+
+
+def _get_json_data(direction, family):
"""
Get NAT format JSON
"""
@@ -35,14 +59,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 +76,28 @@ def _get_raw_data_rules(direction):
return rules
-def _get_formatted_output_rules(data, direction):
+def _get_raw_translation(direction, family):
+ """
+ Return: dictionary
+ """
+ xml = _get_xml_translation(direction, family)
+ if len(xml) == 0:
+ output = {'conntrack':
+ {
+ 'error': True,
+ 'reason': 'entries not found'
+ }
+ }
+ return output
+ return _xml_to_dict(xml)
+
+
+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 +112,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 +141,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,22 +220,83 @@ def _get_formatted_output_statistics(data, direction):
return output
-def show_rules(raw: bool, direction: str):
- nat_rules = _get_raw_data_rules(direction)
+def _get_formatted_translation(dict_data, nat_direction, family):
+ data_entries = []
+ if 'error' in dict_data['conntrack']:
+ return 'Entries not found'
+ for entry in dict_data['conntrack']['flow']:
+ orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {}
+ reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {}
+ proto = {}
+ for meta in entry['meta']:
+ direction = meta['direction']
+ if direction in ['original']:
+ if 'layer3' in meta:
+ orig_src = meta['layer3']['src']
+ orig_dst = meta['layer3']['dst']
+ if 'layer4' in meta:
+ if meta.get('layer4').get('sport'):
+ orig_sport = meta['layer4']['sport']
+ if meta.get('layer4').get('dport'):
+ orig_dport = meta['layer4']['dport']
+ proto = meta['layer4']['protoname']
+ if direction in ['reply']:
+ if 'layer3' in meta:
+ reply_src = meta['layer3']['src']
+ reply_dst = meta['layer3']['dst']
+ if 'layer4' in meta:
+ if meta.get('layer4').get('sport'):
+ reply_sport = meta['layer4']['sport']
+ if meta.get('layer4').get('dport'):
+ reply_dport = meta['layer4']['dport']
+ proto = meta['layer4']['protoname']
+ if direction == 'independent':
+ conn_id = meta['id']
+ timeout = meta['timeout']
+ orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src
+ orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst
+ reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src
+ reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst
+ state = meta['state'] if 'state' in meta else ''
+ mark = meta['mark']
+ zone = meta['zone'] if 'zone' in meta else ''
+ if nat_direction == 'source':
+ data_entries.append(
+ [orig_src, reply_dst, proto, timeout, mark, zone])
+ elif nat_direction == 'destination':
+ data_entries.append(
+ [orig_dst, reply_src, proto, timeout, mark, zone])
+
+ headers = ["Pre-NAT", "Post-NAT", "Proto", "Timeout", "Mark", "Zone"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return output
+
+
+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:
return _get_formatted_output_statistics(nat_statistics, direction)
+def show_translations(raw: bool, direction: str, family: str):
+ family = 'ipv6' if family == 'inet6' else 'ipv4'
+ nat_translation = _get_raw_translation(direction, family)
+ if raw:
+ return nat_translation
+ else:
+ return _get_formatted_translation(nat_translation, direction, family)
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
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 e1eee5bbf..e1eee5bbf 100644..100755
--- a/src/op_mode/route.py
+++ b/src/op_mode/route.py
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/storage.py b/src/op_mode/storage.py
new file mode 100755
index 000000000..75964c493
--- /dev/null
+++ b/src/op_mode/storage.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import sys
+
+import vyos.opmode
+from vyos.util import cmd
+
+
+def _get_system_storage(only_persistent=False):
+ if not only_persistent:
+ cmd_str = 'df -h -x squashf'
+ else:
+ cmd_str = 'df -h -t ext4 --output=source,size,used,avail,pcent'
+
+ res = cmd(cmd_str)
+
+ return res
+
+def _get_raw_data():
+ out = _get_system_storage(only_persistent=True)
+ lines = out.splitlines()
+ lists = [l.split() for l in lines]
+ res = {lists[0][i]: lists[1][i] for i in range(len(lists[0]))}
+
+ return res
+
+def _get_formatted_output():
+ return _get_system_storage()
+
+def show(raw: bool):
+ if raw:
+ return _get_raw_data()
+
+ return _get_formatted_output()
+
+
+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/show_uptime.py b/src/op_mode/uptime.py
index b70c60cf8..2ebe6783b 100755
--- a/src/op_mode/show_uptime.py
+++ b/src/op_mode/uptime.py
@@ -14,7 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-def get_uptime_seconds():
+import sys
+
+import vyos.opmode
+
+def _get_uptime_seconds():
from re import search
from vyos.util import read_file
@@ -23,7 +27,7 @@ def get_uptime_seconds():
return int(float(seconds))
-def get_load_averages():
+def _get_load_averages():
from re import search
from vyos.util import cmd
from vyos.cpu import get_core_count
@@ -40,19 +44,17 @@ def get_load_averages():
return res
-def get_raw_data():
+def _get_raw_data():
from vyos.util import seconds_to_human
res = {}
- res["uptime_seconds"] = get_uptime_seconds()
- res["uptime"] = seconds_to_human(get_uptime_seconds())
- res["load_average"] = get_load_averages()
+ res["uptime_seconds"] = _get_uptime_seconds()
+ res["uptime"] = seconds_to_human(_get_uptime_seconds())
+ res["load_average"] = _get_load_averages()
return res
-def get_formatted_output():
- data = get_raw_data()
-
+def _get_formatted_output(data):
out = "Uptime: {}\n\n".format(data["uptime"])
avgs = data["load_average"]
out += "Load averages:\n"
@@ -62,5 +64,19 @@ def get_formatted_output():
return out
+def show(raw: bool):
+ uptime_data = _get_raw_data()
+
+ if raw:
+ return uptime_data
+ else:
+ return _get_formatted_output(uptime_data)
+
if __name__ == '__main__':
- print(get_formatted_output())
+ 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/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py
index 049d59de7..0b1260912 100644
--- a/src/services/api/graphql/bindings.py
+++ b/src/services/api/graphql/bindings.py
@@ -17,6 +17,7 @@ import vyos.defaults
from . graphql.queries import query
from . graphql.mutations import mutation
from . graphql.directives import directives_dict
+from . graphql.errors import op_mode_error
from . utils.schema_from_op_mode import generate_op_mode_definitions
from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers
@@ -27,6 +28,6 @@ def generate_schema():
type_defs = load_schema_from_path(api_schema_dir)
- schema = make_executable_schema(type_defs, query, mutation, snake_case_fallback_resolvers, directives=directives_dict)
+ schema = make_executable_schema(type_defs, query, op_mode_error, mutation, snake_case_fallback_resolvers, directives=directives_dict)
return schema
diff --git a/src/services/api/graphql/graphql/errors.py b/src/services/api/graphql/graphql/errors.py
new file mode 100644
index 000000000..1066300e0
--- /dev/null
+++ b/src/services/api/graphql/graphql/errors.py
@@ -0,0 +1,8 @@
+
+from ariadne import InterfaceType
+
+op_mode_error = InterfaceType("OpModeError")
+
+@op_mode_error.type_resolver
+def resolve_op_mode_error(obj, *_):
+ return obj['name']
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index 3e89fb239..5ccc9b0b6 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -21,7 +21,9 @@ 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
+from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code
+from vyos.opmode import Error as OpModeError
mutation = ObjectType("Mutation")
@@ -71,7 +73,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
@@ -86,10 +88,20 @@ def make_mutation_resolver(mutation_name, class_name, session_func):
"success": True,
"data": data
}
+ except OpModeError as e:
+ typename = type(e).__name__
+ msg = str(e)
+ return {
+ "success": False,
+ "errore": ['op_mode_error'],
+ "op_mode_error": {"name": f"{typename}",
+ "message": msg if msg else op_mode_err_msg.get(typename, "Unknown"),
+ "vyos_code": op_mode_err_code.get(typename, 9999)}
+ }
except Exception as error:
return {
"success": False,
- "errors": [str(error)]
+ "errors": [repr(error)]
}
return func_impl
diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py
index f6544709e..b46914dcc 100644
--- a/src/services/api/graphql/graphql/queries.py
+++ b/src/services/api/graphql/graphql/queries.py
@@ -21,7 +21,9 @@ 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
+from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code
+from vyos.opmode import Error as OpModeError
query = ObjectType("Query")
@@ -71,7 +73,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
@@ -86,10 +88,20 @@ def make_query_resolver(query_name, class_name, session_func):
"success": True,
"data": data
}
+ except OpModeError as e:
+ typename = type(e).__name__
+ msg = str(e)
+ return {
+ "success": False,
+ "errors": ['op_mode_error'],
+ "op_mode_error": {"name": f"{typename}",
+ "message": msg if msg else op_mode_err_msg.get(typename, "Unknown"),
+ "vyos_code": op_mode_err_code.get(typename, 9999)}
+ }
except Exception as error:
return {
"success": False,
- "errors": [str(error)]
+ "errors": [repr(error)]
}
return func_impl
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..3c1a3d45b 100755
--- a/src/services/api/graphql/recipes/queries/system_status.py
+++ b/src/services/api/graphql/session/composite/system_status.py
@@ -30,8 +30,8 @@ def get_system_version() -> dict:
return show_version.show(raw=True, funny=False)
def get_system_uptime() -> dict:
- show_uptime = load_op_mode_as_module('show_uptime.py')
- return show_uptime.get_raw_data()
+ show_uptime = load_op_mode_as_module('uptime.py')
+ return show_uptime._get_raw_data()
def get_system_ram_usage() -> dict:
show_ram = load_op_mode_as_module('memory.py')
diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py
new file mode 100644
index 000000000..7ba75455d
--- /dev/null
+++ b/src/services/api/graphql/session/errors/op_mode_errors.py
@@ -0,0 +1,13 @@
+
+
+op_mode_err_msg = {
+ "UnconfiguredSubsystem": "subsystem is not configured or not running",
+ "DataUnavailable": "data currently unavailable",
+ "PermissionDenied": "client does not have permission"
+}
+
+op_mode_err_code = {
+ "UnconfiguredSubsystem": 2000,
+ "DataUnavailable": 2001,
+ "PermissionDenied": 1003
+}
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..93e1c328e 100644
--- a/src/services/api/graphql/recipes/session.py
+++ b/src/services/api/graphql/session/session.py
@@ -22,6 +22,7 @@ from vyos.config import Config
from vyos.configtree import ConfigTree
from vyos.defaults import directories
from vyos.template import render
+from vyos.opmode import Error as OpModeError
from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name
@@ -149,7 +150,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
@@ -177,10 +178,10 @@ class Session:
mod = load_op_mode_as_module(f'{scriptname}')
func = getattr(mod, func_name)
- if len(list(data)) > 0:
+ try:
res = func(True, **data)
- else:
- res = func(True)
+ except OpModeError as e:
+ raise e
return res
@@ -199,9 +200,9 @@ class Session:
mod = load_op_mode_as_module(f'{scriptname}')
func = getattr(mod, func_name)
- if len(list(data)) > 0:
+ try:
res = func(**data)
- else:
- res = func()
+ except OpModeError as e:
+ raise e
return res
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..379d15250 100755
--- a/src/services/api/graphql/utils/schema_from_op_mode.py
+++ b/src/services/api/graphql/utils/schema_from_op_mode.py
@@ -21,17 +21,21 @@
import os
import json
import typing
-from inspect import signature, getmembers, isfunction
+from inspect import signature, getmembers, isfunction, isclass, getmro
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']
DATA_DIR = directories['data']
op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json')
+op_mode_error_schema = 'op_mode_error.graphql'
schema_data: dict = {'schema_name': '',
'schema_fields': []}
@@ -50,6 +54,7 @@ type {{ schema_name }} {
type {{ schema_name }}Result {
data: {{ schema_name }}
+ op_mode_error: OpModeError
success: Boolean!
errors: [String]
}
@@ -73,6 +78,7 @@ type {{ schema_name }} {
type {{ schema_name }}Result {
data: {{ schema_name }}
+ op_mode_error: OpModeError
success: Boolean!
errors: [String]
}
@@ -82,6 +88,21 @@ extend type Mutation {
}
"""
+error_template = """
+interface OpModeError {
+ name: String!
+ message: String!
+ vyos_code: Int!
+}
+{% for name in error_names %}
+type {{ name }} implements OpModeError {
+ name: String!
+ message: String!
+ vyos_code: Int!
+}
+{%- endfor %}
+"""
+
def _snake_to_pascal_case(name: str) -> str:
res = ''.join(map(str.title, name.split('_')))
return res
@@ -133,7 +154,30 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str:
return res
+def create_error_schema():
+ from vyos import opmode
+
+ e = Exception
+ err_types = getmembers(opmode, isclass)
+ err_types = [k for k in err_types if issubclass(k[1], e)]
+ # drop base class, to be replaced by interface type. Find the class
+ # programmatically, in case the base class name changes.
+ for i in range(len(err_types)):
+ if err_types[i][1] in getmro(err_types[i-1][1]):
+ del err_types[i]
+ break
+ err_names = [k[0] for k in err_types]
+ error_data = {'error_names': err_names}
+ j2_template = Template(error_template)
+ res = j2_template.render(error_data)
+
+ return res
+
def generate_op_mode_definitions():
+ out = create_error_schema()
+ with open(f'{SCHEMA_PATH}/{op_mode_error_schema}', 'w') as f:
+ f.write(out)
+
with open(op_mode_include_file) as f:
op_mode_files = json.load(f)
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/range b/src/validators/range
deleted file mode 100755
index d4c25f3c4..000000000
--- a/src/validators/range
+++ /dev/null
@@ -1,56 +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 re
-import sys
-import argparse
-
-class MalformedRange(Exception):
- pass
-
-def validate_range(value, min=None, max=None):
- try:
- lower, upper = re.match(r'^(\d+)-(\d+)$', value).groups()
-
- lower, upper = int(lower), int(upper)
-
- if int(lower) > int(upper):
- raise MalformedRange("the lower bound exceeds the upper bound".format(value))
-
- if min is not None:
- if lower < min:
- raise MalformedRange("the lower bound must not be less than {}".format(min))
-
- if max is not None:
- if upper > max:
- raise MalformedRange("the upper bound must not be greater than {}".format(max))
-
- except (AttributeError, ValueError):
- raise MalformedRange("range syntax error")
-
-parser = argparse.ArgumentParser(description='Range validator.')
-parser.add_argument('--min', type=int, action='store')
-parser.add_argument('--max', type=int, action='store')
-parser.add_argument('value', action='store')
-
-if __name__ == '__main__':
- args = parser.parse_args()
-
- try:
- validate_range(args.value, min=args.min, max=args.max)
- except MalformedRange as e:
- print("Incorrect range '{}': {}".format(args.value, e))
- sys.exit(1)