summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/firewall/nftables-zone.j269
-rw-r--r--data/templates/firewall/nftables.j238
-rw-r--r--data/templates/ndppd/ndppd.conf.j28
-rw-r--r--interface-definitions/firewall.xml.in142
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i1
-rw-r--r--interface-definitions/include/firewall/inbound-interface-no-group.xml.i34
-rw-r--r--interface-definitions/include/firewall/ipv4-hook-forward.xml.i1
-rw-r--r--interface-definitions/include/firewall/ipv4-hook-input.xml.i1
-rw-r--r--interface-definitions/include/firewall/ipv4-hook-output.xml.i1
-rw-r--r--interface-definitions/include/firewall/ipv6-hook-forward.xml.i1
-rw-r--r--interface-definitions/include/firewall/ipv6-hook-input.xml.i1
-rw-r--r--interface-definitions/include/firewall/ipv6-hook-output.xml.i1
-rw-r--r--interface-definitions/include/firewall/match-interface.xml.i4
-rw-r--r--interface-definitions/include/firewall/outbound-interface-no-group.xml.i34
-rw-r--r--interface-definitions/include/version/cluster-version.xml.i2
-rw-r--r--interface-definitions/include/version/interfaces-version.xml.i2
-rw-r--r--interface-definitions/include/version/nat-version.xml.i2
-rw-r--r--interface-definitions/include/version/nat66-version.xml.i2
-rw-r--r--interface-definitions/interfaces-bridge.xml.in3
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in20
-rw-r--r--interface-definitions/nat.xml.in4
-rw-r--r--interface-definitions/nat66.xml.in19
-rw-r--r--op-mode-definitions/monitor-log.xml.in12
-rw-r--r--op-mode-definitions/nat66.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-wireless.xml.in10
-rw-r--r--op-mode-definitions/show-log.xml.in12
-rw-r--r--op-mode-definitions/show-ssh.xml.in10
-rw-r--r--python/vyos/configdep.py2
-rw-r--r--python/vyos/configdict.py14
-rw-r--r--python/vyos/firewall.py12
-rw-r--r--python/vyos/ifconfig/vxlan.py25
-rw-r--r--python/vyos/nat.py34
-rw-r--r--python/vyos/qos/trafficshaper.py9
-rw-r--r--python/vyos/template.py3
-rwxr-xr-xscripts/import-conf-mode-commands255
-rw-r--r--smoketest/config-tests/dialup-router-medium-vpn32
-rw-r--r--smoketest/configs/bgp-evpn-l2vpn-leaf1
-rw-r--r--smoketest/configs/cluster-basic62
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py80
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vxlan.py43
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py28
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py22
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py6
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py57
-rwxr-xr-xsrc/conf_mode/firewall.py74
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py33
-rwxr-xr-xsrc/conf_mode/load-balancing-haproxy.py14
-rwxr-xr-xsrc/conf_mode/nat.py14
-rwxr-xr-xsrc/conf_mode/nat66.py22
-rwxr-xr-xsrc/migration-scripts/cluster/1-to-2193
-rwxr-xr-xsrc/migration-scripts/firewall/10-to-11185
-rwxr-xr-xsrc/migration-scripts/firewall/11-to-1275
-rwxr-xr-xsrc/migration-scripts/interfaces/31-to-3251
-rwxr-xr-xsrc/migration-scripts/nat/5-to-662
-rwxr-xr-xsrc/migration-scripts/nat/6-to-767
-rwxr-xr-xsrc/migration-scripts/nat66/1-to-263
-rwxr-xr-xsrc/op_mode/firewall.py147
-rwxr-xr-xsrc/op_mode/generate_tech-support_archive.py4
-rwxr-xr-xsrc/op_mode/interfaces_wireless.py186
-rwxr-xr-xsrc/op_mode/lldp.py5
-rw-r--r--src/op_mode/show-ssh-fingerprints.py49
-rwxr-xr-xsrc/op_mode/show_wireless.py149
-rwxr-xr-xsrc/op_mode/ssh.py100
-rwxr-xr-xsrc/system/uacctd_stop.py7
64 files changed, 1772 insertions, 855 deletions
diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2
new file mode 100644
index 000000000..1e9351f97
--- /dev/null
+++ b/data/templates/firewall/nftables-zone.j2
@@ -0,0 +1,69 @@
+
+{% macro zone_chains(zone, 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;
+{% 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;
+{% 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;
+{% 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
+{% if zone_conf.from is vyos_defined %}
+{% 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 %}
+{% endif %}
+ {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
+ }
+ chain VZONE_{{ zone_name }}_OUT {
+ oifname lo counter return
+{% if zone_conf.from_local is vyos_defined %}
+{% 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 %}
+{% endif %}
+ {{ 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 %}
+{% if zone_conf.from is vyos_defined %}
+{% 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 %}
+{% endif %}
+ {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
+ }
+{% endif %}
+{% endfor %}
+{% endmacro %} \ No newline at end of file
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 75800ee3d..e24a9655d 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -3,6 +3,7 @@
{% import 'firewall/nftables-defines.j2' as group_tmpl %}
{% import 'firewall/nftables-bridge.j2' as bridge_tmpl %}
{% import 'firewall/nftables-offload.j2' as offload_tmpl %}
+{% import 'firewall/nftables-zone.j2' as zone_tmpl %}
flush chain raw vyos_global_rpfilter
flush chain ip6 raw vyos_global_rpfilter
@@ -43,9 +44,8 @@ table ip vyos_filter {
{% set ns = namespace(sets=[]) %}
{% if ipv4.forward is vyos_defined %}
{% for prior, conf in ipv4.forward.items() %}
-{% set def_action = conf.default_action %}
chain VYOS_FORWARD_{{ prior }} {
- type filter hook forward priority {{ prior }}; policy {{ def_action }};
+ type filter hook forward priority {{ prior }}; policy accept;
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
{{ rule_conf | nft_rule('FWD', prior, rule_id) }}
@@ -54,15 +54,15 @@ table ip vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
+ {{ conf | nft_default_rule('FWD-filter') }}
}
{% endfor %}
{% endif %}
{% if ipv4.input is vyos_defined %}
{% for prior, conf in ipv4.input.items() %}
-{% set def_action = conf.default_action %}
chain VYOS_INPUT_{{ prior }} {
- type filter hook input priority {{ prior }}; policy {{ def_action }};
+ type filter hook input priority {{ prior }}; policy accept;
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
{{ rule_conf | nft_rule('INP',prior, rule_id) }}
@@ -71,15 +71,15 @@ table ip vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
+ {{ conf | nft_default_rule('INP-filter') }}
}
{% endfor %}
{% endif %}
{% if ipv4.output is vyos_defined %}
{% for prior, conf in ipv4.output.items() %}
-{% set def_action = conf.default_action %}
chain VYOS_OUTPUT_{{ prior }} {
- type filter hook output priority {{ prior }}; policy {{ def_action }};
+ type filter hook output priority {{ prior }}; policy accept;
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
{{ rule_conf | nft_rule('OUT', prior, rule_id) }}
@@ -88,6 +88,7 @@ table ip vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
+ {{ conf | nft_default_rule('OUT-filter') }}
}
{% endfor %}
{% endif %}
@@ -97,9 +98,8 @@ table ip vyos_filter {
}
{% if ipv4.prerouting is vyos_defined %}
{% for prior, conf in ipv4.prerouting.items() %}
-{% set def_action = conf.default_action %}
chain VYOS_PREROUTING_{{ prior }} {
- type filter hook prerouting priority {{ prior }}; policy {{ def_action }};
+ type filter hook prerouting priority {{ prior }}; policy accept;
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
{{ rule_conf | nft_rule('PRE', prior, rule_id) }}
@@ -108,7 +108,7 @@ table ip vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(prior) }}
+ {{ conf | nft_default_rule('PRE-filter') }}
}
{% endfor %}
{% endif %}
@@ -152,6 +152,10 @@ table ip vyos_filter {
{% endif %}
{% endif %}
{{ group_tmpl.groups(group, False, True) }}
+
+{% if zone is vyos_defined %}
+{{ zone_tmpl.zone_chains(zone, False) }}
+{% endif %}
}
{% if first_install is not vyos_defined %}
@@ -168,9 +172,8 @@ table ip6 vyos_filter {
{% set ns = namespace(sets=[]) %}
{% if ipv6.forward is vyos_defined %}
{% for prior, conf in ipv6.forward.items() %}
-{% set def_action = conf.default_action %}
chain VYOS_IPV6_FORWARD_{{ prior }} {
- type filter hook forward priority {{ prior }}; policy {{ def_action }};
+ type filter hook forward priority {{ prior }}; policy accept;
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
{{ rule_conf | nft_rule('FWD', prior, rule_id ,'ip6') }}
@@ -179,15 +182,15 @@ table ip6 vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
+ {{ conf | nft_default_rule('FWD-filter', ipv6=True) }}
}
{% endfor %}
{% endif %}
{% if ipv6.input is vyos_defined %}
{% for prior, conf in ipv6.input.items() %}
-{% set def_action = conf.default_action %}
chain VYOS_IPV6_INPUT_{{ prior }} {
- type filter hook input priority {{ prior }}; policy {{ def_action }};
+ type filter hook input priority {{ prior }}; policy accept;
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
{{ rule_conf | nft_rule('INP', prior, rule_id ,'ip6') }}
@@ -196,15 +199,15 @@ table ip6 vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
+ {{ conf | nft_default_rule('INP-filter', ipv6=True) }}
}
{% endfor %}
{% endif %}
{% if ipv6.output is vyos_defined %}
{% for prior, conf in ipv6.output.items() %}
-{% set def_action = conf.default_action %}
chain VYOS_IPV6_OUTPUT_{{ prior }} {
- type filter hook output priority {{ prior }}; policy {{ def_action }};
+ type filter hook output priority {{ prior }}; policy accept;
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
{{ rule_conf | nft_rule('OUT', prior, rule_id ,'ip6') }}
@@ -213,6 +216,7 @@ table ip6 vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
+ {{ conf | nft_default_rule('OUT-filter', ipv6=True) }}
}
{% endfor %}
{% endif %}
@@ -261,6 +265,9 @@ table ip6 vyos_filter {
{% endif %}
{% endif %}
{{ group_tmpl.groups(group, True, True) }}
+{% if zone is vyos_defined %}
+{{ zone_tmpl.zone_chains(zone, True) }}
+{% endif %}
}
## Bridge Firewall
@@ -270,4 +277,5 @@ delete table bridge vyos_filter
table bridge vyos_filter {
{{ bridge_tmpl.bridge(bridge) }}
{{ group_tmpl.groups(group, False, False) }}
+
}
diff --git a/data/templates/ndppd/ndppd.conf.j2 b/data/templates/ndppd/ndppd.conf.j2
index 120fa0a64..1297f36be 100644
--- a/data/templates/ndppd/ndppd.conf.j2
+++ b/data/templates/ndppd/ndppd.conf.j2
@@ -17,12 +17,12 @@
{% set global = namespace(ndppd_interfaces = [],ndppd_prefixs = []) %}
{% if source.rule is vyos_defined %}
{% for rule, config in source.rule.items() if config.disable is not defined %}
-{% if config.outbound_interface is vyos_defined %}
-{% if config.outbound_interface not in global.ndppd_interfaces %}
-{% set global.ndppd_interfaces = global.ndppd_interfaces + [config.outbound_interface] %}
+{% if config.outbound_interface.name is vyos_defined %}
+{% if config.outbound_interface.name not in global.ndppd_interfaces %}
+{% set global.ndppd_interfaces = global.ndppd_interfaces + [config.outbound_interface.name] %}
{% endif %}
{% if config.translation.address is vyos_defined and config.translation.address | is_ip_network %}
-{% set global.ndppd_prefixs = global.ndppd_prefixs + [{'interface':config.outbound_interface,'rule':config.translation.address}] %}
+{% set global.ndppd_prefixs = global.ndppd_prefixs + [{'interface':config.outbound_interface.name,'rule':config.translation.address}] %}
{% endif %}
{% endif %}
{% endfor %}
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 81e6b89ea..0bb14a1b3 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -355,6 +355,148 @@
#include <include/firewall/ipv6-custom-name.xml.i>
</children>
</node>
+ <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 ipv4 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>
+ <valueHelp>
+ <format>vrf</format>
+ <description>VRF associated with zone</description>
+ </valueHelp>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ <path>vrf name</path>
+ </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 ipv4 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/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index 504385b53..3d9333639 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -343,6 +343,7 @@
#include <include/route-map.xml.i>
</children>
</tagNode>
+ #include <include/bgp/afi-maximum-paths.xml.i>
</children>
</node>
<node name="ipv4-flowspec">
diff --git a/interface-definitions/include/firewall/inbound-interface-no-group.xml.i b/interface-definitions/include/firewall/inbound-interface-no-group.xml.i
new file mode 100644
index 000000000..bcd4c9570
--- /dev/null
+++ b/interface-definitions/include/firewall/inbound-interface-no-group.xml.i
@@ -0,0 +1,34 @@
+<!-- include start from firewall/inbound-interface-no-group.xml.i -->
+<node name="inbound-interface">
+ <properties>
+ <help>Match inbound-interface</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Match interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ <path>vrf name</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>txt*</format>
+ <description>Interface name with wildcard</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!txt</format>
+ <description>Inverted interface name to match</description>
+ </valueHelp>
+ <constraint>
+ <regex>(\!?)(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|(\!?)lo</regex>
+ <validator name="vrf-name"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv4-hook-forward.xml.i b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i
index 70c0adb77..100f1c3d9 100644
--- a/interface-definitions/include/firewall/ipv4-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i
@@ -10,6 +10,7 @@
</properties>
<children>
#include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
diff --git a/interface-definitions/include/firewall/ipv4-hook-input.xml.i b/interface-definitions/include/firewall/ipv4-hook-input.xml.i
index 32b0ec94f..22546640b 100644
--- a/interface-definitions/include/firewall/ipv4-hook-input.xml.i
+++ b/interface-definitions/include/firewall/ipv4-hook-input.xml.i
@@ -10,6 +10,7 @@
</properties>
<children>
#include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
diff --git a/interface-definitions/include/firewall/ipv4-hook-output.xml.i b/interface-definitions/include/firewall/ipv4-hook-output.xml.i
index d50d1e93b..80c30cdeb 100644
--- a/interface-definitions/include/firewall/ipv4-hook-output.xml.i
+++ b/interface-definitions/include/firewall/ipv4-hook-output.xml.i
@@ -10,6 +10,7 @@
</properties>
<children>
#include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
diff --git a/interface-definitions/include/firewall/ipv6-hook-forward.xml.i b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i
index d83827161..fb38267eb 100644
--- a/interface-definitions/include/firewall/ipv6-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i
@@ -10,6 +10,7 @@
</properties>
<children>
#include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
diff --git a/interface-definitions/include/firewall/ipv6-hook-input.xml.i b/interface-definitions/include/firewall/ipv6-hook-input.xml.i
index e34958f28..49d4493cc 100644
--- a/interface-definitions/include/firewall/ipv6-hook-input.xml.i
+++ b/interface-definitions/include/firewall/ipv6-hook-input.xml.i
@@ -10,6 +10,7 @@
</properties>
<children>
#include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
diff --git a/interface-definitions/include/firewall/ipv6-hook-output.xml.i b/interface-definitions/include/firewall/ipv6-hook-output.xml.i
index eb4ea7ac3..452b9027f 100644
--- a/interface-definitions/include/firewall/ipv6-hook-output.xml.i
+++ b/interface-definitions/include/firewall/ipv6-hook-output.xml.i
@@ -10,6 +10,7 @@
</properties>
<children>
#include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
diff --git a/interface-definitions/include/firewall/match-interface.xml.i b/interface-definitions/include/firewall/match-interface.xml.i
index 1cd9f8c4a..5da6f51fb 100644
--- a/interface-definitions/include/firewall/match-interface.xml.i
+++ b/interface-definitions/include/firewall/match-interface.xml.i
@@ -1,5 +1,5 @@
<!-- include start from firewall/match-interface.xml.i -->
-<leafNode name="interface-name">
+<leafNode name="name">
<properties>
<help>Match interface</help>
<completionHelp>
@@ -24,7 +24,7 @@
</constraint>
</properties>
</leafNode>
-<leafNode name="interface-group">
+<leafNode name="group">
<properties>
<help>Match interface-group</help>
<completionHelp>
diff --git a/interface-definitions/include/firewall/outbound-interface-no-group.xml.i b/interface-definitions/include/firewall/outbound-interface-no-group.xml.i
new file mode 100644
index 000000000..e3bace42d
--- /dev/null
+++ b/interface-definitions/include/firewall/outbound-interface-no-group.xml.i
@@ -0,0 +1,34 @@
+<!-- include start from firewall/outbound-interface-no-group.xml.i -->
+<node name="outbound-interface">
+ <properties>
+ <help>Match outbound-interface</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Match interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ <path>vrf name</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>txt*</format>
+ <description>Interface name with wildcard</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!txt</format>
+ <description>Inverted interface name to match</description>
+ </valueHelp>
+ <constraint>
+ <regex>(\!?)(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|(\!?)lo</regex>
+ <validator name="vrf-name"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/version/cluster-version.xml.i b/interface-definitions/include/version/cluster-version.xml.i
index 621996df4..402fe36c5 100644
--- a/interface-definitions/include/version/cluster-version.xml.i
+++ b/interface-definitions/include/version/cluster-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/cluster-version.xml.i -->
-<syntaxVersion component='cluster' version='1'></syntaxVersion>
+<syntaxVersion component='cluster' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i
index 76c5d3c05..854e60f4e 100644
--- a/interface-definitions/include/version/interfaces-version.xml.i
+++ b/interface-definitions/include/version/interfaces-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/interfaces-version.xml.i -->
-<syntaxVersion component='interfaces' version='31'></syntaxVersion>
+<syntaxVersion component='interfaces' version='32'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/nat-version.xml.i b/interface-definitions/include/version/nat-version.xml.i
index 027216a07..656da6e14 100644
--- a/interface-definitions/include/version/nat-version.xml.i
+++ b/interface-definitions/include/version/nat-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/nat-version.xml.i -->
-<syntaxVersion component='nat' version='5'></syntaxVersion>
+<syntaxVersion component='nat' version='7'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/nat66-version.xml.i b/interface-definitions/include/version/nat66-version.xml.i
index 7b7123dcc..478ca080f 100644
--- a/interface-definitions/include/version/nat66-version.xml.i
+++ b/interface-definitions/include/version/nat66-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/nat66-version.xml.i -->
-<syntaxVersion component='nat66' version='1'></syntaxVersion>
+<syntaxVersion component='nat66' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in
index fcfb8686c..db3762065 100644
--- a/interface-definitions/interfaces-bridge.xml.in
+++ b/interface-definitions/interfaces-bridge.xml.in
@@ -123,6 +123,9 @@
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces --bridgeable</script>
</completionHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
</properties>
<children>
<leafNode name="native-vlan">
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index b246d9a09..f20743a65 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -19,12 +19,6 @@
#include <include/interface/address-ipv4-ipv6.xml.i>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
- <leafNode name="external">
- <properties>
- <help>Use external control plane</help>
- <valueless/>
- </properties>
- </leafNode>
<leafNode name="gpe">
<properties>
<help>Enable Generic Protocol extension (VXLAN-GPE)</help>
@@ -83,17 +77,29 @@
#include <include/interface/parameters-flowlabel.xml.i>
</children>
</node>
+ <leafNode name="external">
+ <properties>
+ <help>Use external control plane</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="nolearning">
<properties>
<help>Do not add unknown addresses into forwarding database</help>
<valueless/>
</properties>
</leafNode>
+ <leafNode name="neighbor-suppress">
+ <properties>
+ <help>Enable neighbor discovery (ARP and ND) suppression</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</node>
#include <include/port-number.xml.i>
<leafNode name="port">
- <defaultValue>8472</defaultValue>
+ <defaultValue>4789</defaultValue>
</leafNode>
#include <include/source-address-ipv4-ipv6.xml.i>
#include <include/source-interface.xml.i>
diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in
index a06ceefb6..0a639bd80 100644
--- a/interface-definitions/nat.xml.in
+++ b/interface-definitions/nat.xml.in
@@ -14,7 +14,7 @@
#include <include/nat-rule.xml.i>
<tagNode name="rule">
<children>
- #include <include/inbound-interface.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
<node name="translation">
<properties>
<help>Inside NAT IP (destination NAT only)</help>
@@ -77,7 +77,7 @@
<constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
- #include <include/nat-interface.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
<node name="translation">
<properties>
<help>Outside NAT IP (source NAT only)</help>
diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in
index 7a8970bdf..a657535ba 100644
--- a/interface-definitions/nat66.xml.in
+++ b/interface-definitions/nat66.xml.in
@@ -38,14 +38,7 @@
<valueless/>
</properties>
</leafNode>
- <leafNode name="outbound-interface">
- <properties>
- <help>Outbound interface of NAT66 traffic</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- </properties>
- </leafNode>
+ #include <include/firewall/outbound-interface-no-group.xml.i>
#include <include/nat/protocol.xml.i>
<node name="destination">
<properties>
@@ -166,15 +159,7 @@
<valueless/>
</properties>
</leafNode>
- <leafNode name="inbound-interface">
- <properties>
- <help>Inbound interface of NAT66 traffic</help>
- <completionHelp>
- <list>any</list>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- </properties>
- </leafNode>
+ #include <include/firewall/inbound-interface-no-group.xml.i>
#include <include/nat/protocol.xml.i>
<node name="destination">
<properties>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index 52b5b85d4..ee066b39b 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -274,12 +274,20 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit snmpd.service</command>
</leafNode>
- <leafNode name="ssh">
+ <node name="ssh">
<properties>
<help>Monitor last lines of Secure Shell log</help>
</properties>
<command>journalctl --no-hostname --boot --follow --unit ssh.service</command>
- </leafNode>
+ <children>
+ <node name="dynamic-protection">
+ <properties>
+ <help>Monitor last lines of SSH guard log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit sshguard.service</command>
+ </node>
+ </children>
+ </node>
<leafNode name="vpn">
<properties>
<help>Monitor last lines of ALL Virtual Private Network services</help>
diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in
index 6a8a39000..4df20d847 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}/nat.py show_rules --direction source --family inet6</command>
+ <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet6</command>
</node>
<node name="statistics">
<properties>
@@ -39,7 +39,7 @@
<command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6 --address "$6"</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6</command>
+ <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6</command>
</node>
</children>
</node>
@@ -52,7 +52,7 @@
<properties>
<help>Show configured destination NAT66 rules</help>
</properties>
- <command>${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet6</command>
+ <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet6</command>
</node>
<node name="statistics">
<properties>
@@ -75,7 +75,7 @@
<command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6 --address "$6"</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6</command>
+ <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-wireless.xml.in b/op-mode-definitions/show-interfaces-wireless.xml.in
index 27c0f43db..09c9a7895 100644
--- a/op-mode-definitions/show-interfaces-wireless.xml.in
+++ b/op-mode-definitions/show-interfaces-wireless.xml.in
@@ -20,7 +20,7 @@
<properties>
<help>Show wireless interface configuration</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_wireless.py --brief</command>
+ <command>${vyos_op_scripts_dir}/interfaces_wireless.py show_info</command>
</leafNode>
</children>
</node>
@@ -35,15 +35,15 @@
<children>
<leafNode name="brief">
<properties>
- <help>Show summary of the specified wireless interface information</help>
+ <help>Show brief summary of the specified wireless interface</help>
</properties>
<command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-name="$4" --intf-type=wireless</command>
</leafNode>
<node name="scan">
<properties>
- <help>Show summary of the specified wireless interface information</help>
+ <help>Scan for networks via specified wireless interface</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/show_wireless.py --scan "$4"</command>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces_wireless.py show_scan --intf-name="$4"</command>
<children>
<leafNode name="detail">
<properties>
@@ -57,7 +57,7 @@
<properties>
<help>Show specified Wireless interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_wireless.py --stations "$4"</command>
+ <command>${vyos_op_scripts_dir}/interfaces_wireless.py show_stations --intf-name="$4"</command>
</leafNode>
<tagNode name="vif">
<properties>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index a2a210543..39d69c64e 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -618,12 +618,20 @@
</properties>
<command>journalctl --no-hostname --boot --unit snmpd.service</command>
</leafNode>
- <leafNode name="ssh">
+ <node name="ssh">
<properties>
<help>Show log for Secure Shell (SSH)</help>
</properties>
<command>journalctl --no-hostname --boot --unit ssh.service</command>
- </leafNode>
+ <children>
+ <node name="dynamic-protection">
+ <properties>
+ <help>Show SSH guard log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit sshguard.service</command>
+ </node>
+ </children>
+ </node>
<tagNode name="tail">
<properties>
<help>Show last n changes to messages</help>
diff --git a/op-mode-definitions/show-ssh.xml.in b/op-mode-definitions/show-ssh.xml.in
index dc6e0d02e..ca8e669b3 100644
--- a/op-mode-definitions/show-ssh.xml.in
+++ b/op-mode-definitions/show-ssh.xml.in
@@ -7,17 +7,23 @@
<help>Show SSH server information</help>
</properties>
<children>
+ <node name="dynamic-protection">
+ <properties>
+ <help>Show SSH server dynamic-protection blocked attackers</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/ssh.py show_dynamic_protection</command>
+ </node>
<node name="fingerprints">
<properties>
<help>Show SSH server public key fingerprints</help>
</properties>
- <command>${vyos_op_scripts_dir}/show-ssh-fingerprints.py</command>
+ <command>${vyos_op_scripts_dir}/ssh.py show_fingerprints</command>
<children>
<node name="ascii">
<properties>
<help>Show visual ASCII art representation of the public key</help>
</properties>
- <command>${vyos_op_scripts_dir}/show-ssh-fingerprints.py --ascii</command>
+ <command>${vyos_op_scripts_dir}/ssh.py show_fingerprints --ascii</command>
</node>
</children>
</node>
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py
index 05d9a3fa3..8a28811eb 100644
--- a/python/vyos/configdep.py
+++ b/python/vyos/configdep.py
@@ -43,7 +43,7 @@ def canon_name_of_path(path: str) -> str:
return canon_name(script)
def caller_name() -> str:
- return stack()[-1].filename
+ return stack()[2].filename
def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict:
res = {}
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 71a06b625..075ffe466 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -258,10 +258,10 @@ def has_address_configured(conf, intf):
old_level = conf.get_level()
conf.set_level([])
- intfpath = 'interfaces ' + Section.get_config_path(intf)
- if ( conf.exists(f'{intfpath} address') or
- conf.exists(f'{intfpath} ipv6 address autoconf') or
- conf.exists(f'{intfpath} ipv6 address eui64') ):
+ intfpath = ['interfaces', Section.get_config_path(intf)]
+ if (conf.exists([intfpath, 'address']) or
+ conf.exists([intfpath, 'ipv6', 'address', 'autoconf']) or
+ conf.exists([intfpath, 'ipv6', 'address', 'eui64'])):
ret = True
conf.set_level(old_level)
@@ -279,8 +279,7 @@ def has_vrf_configured(conf, intf):
old_level = conf.get_level()
conf.set_level([])
- tmp = ['interfaces', Section.get_config_path(intf), 'vrf']
- if conf.exists(tmp):
+ if conf.exists(['interfaces', Section.get_config_path(intf), 'vrf']):
ret = True
conf.set_level(old_level)
@@ -298,8 +297,7 @@ def has_vlan_subinterface_configured(conf, intf):
ret = False
intfpath = ['interfaces', Section.section(intf), intf]
- if ( conf.exists(intfpath + ['vif']) or
- conf.exists(intfpath + ['vif-s'])):
+ if (conf.exists(intfpath + ['vif']) or conf.exists(intfpath + ['vif-s'])):
ret = True
return ret
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index c07ed1adf..dc5787595 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -275,14 +275,14 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if 'inbound_interface' in rule_conf:
operator = ''
- if 'interface_name' in rule_conf['inbound_interface']:
- iiface = rule_conf['inbound_interface']['interface_name']
+ if 'name' in rule_conf['inbound_interface']:
+ iiface = rule_conf['inbound_interface']['name']
if iiface[0] == '!':
operator = '!='
iiface = iiface[1:]
output.append(f'iifname {operator} {{{iiface}}}')
else:
- iiface = rule_conf['inbound_interface']['interface_group']
+ iiface = rule_conf['inbound_interface']['group']
if iiface[0] == '!':
operator = '!='
iiface = iiface[1:]
@@ -290,14 +290,14 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if 'outbound_interface' in rule_conf:
operator = ''
- if 'interface_name' in rule_conf['outbound_interface']:
- oiface = rule_conf['outbound_interface']['interface_name']
+ if 'name' in rule_conf['outbound_interface']:
+ oiface = rule_conf['outbound_interface']['name']
if oiface[0] == '!':
operator = '!='
oiface = oiface[1:]
output.append(f'oifname {operator} {{{oiface}}}')
else:
- oiface = rule_conf['outbound_interface']['interface_group']
+ oiface = rule_conf['outbound_interface']['group']
if oiface[0] == '!':
operator = '!='
oiface = oiface[1:]
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 1fe5db7cd..8c5a0220e 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -56,6 +56,10 @@ class VXLANIf(Interface):
}
_command_set = {**Interface._command_set, **{
+ 'neigh_suppress': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': 'bridge link set dev {ifname} neigh_suppress {value} learning off',
+ },
'vlan_tunnel': {
'validate': lambda v: assert_list(v, ['on', 'off']),
'shellcmd': 'bridge link set dev {ifname} vlan_tunnel {value}',
@@ -68,8 +72,8 @@ class VXLANIf(Interface):
# - https://man7.org/linux/man-pages/man8/ip-link.8.html
mapping = {
'group' : 'group',
- 'external' : 'external',
'gpe' : 'gpe',
+ 'parameters.external' : 'external',
'parameters.ip.df' : 'df',
'parameters.ip.tos' : 'tos',
'parameters.ip.ttl' : 'ttl',
@@ -113,6 +117,19 @@ class VXLANIf(Interface):
'port {port} dev {ifname}'
self._cmd(cmd.format(**self.config))
+ def set_neigh_suppress(self, state):
+ """
+ Controls whether neigh discovery (arp and nd) proxy and suppression
+ is enabled on the port. By default this flag is off.
+ """
+
+ # Determine current OS Kernel neigh_suppress setting - only adjust when needed
+ tmp = get_interface_config(self.ifname)
+ cur_state = 'on' if dict_search(f'linkinfo.info_slave_data.neigh_suppress', tmp) == True else 'off'
+ new_state = 'on' if state else 'off'
+ if cur_state != new_state:
+ self.set_interface('neigh_suppress', state)
+
def set_vlan_vni_mapping(self, state):
"""
Controls whether vlan to tunnel mapping is enabled on the port.
@@ -163,3 +180,9 @@ class VXLANIf(Interface):
# Enable/Disable VLAN tunnel mapping
# This is only possible after the interface was assigned to the bridge
self.set_vlan_vni_mapping(dict_search('vlan_to_vni', config) != None)
+
+ # Enable/Disable neighbor suppression and learning, there is no need to
+ # explicitly "disable" it, as VXLAN interface will be recreated if anything
+ # under "parameters" changes.
+ if dict_search('parameters.neighbor_suppress', config) != None:
+ self.set_neigh_suppress('on')
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 9cbc2b96e..392d38772 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -32,14 +32,34 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
translation_str = ''
if 'inbound_interface' in rule_conf:
- ifname = rule_conf['inbound_interface']
- if ifname != 'any':
- output.append(f'iifname "{ifname}"')
+ operator = ''
+ if 'name' in rule_conf['inbound_interface']:
+ iiface = rule_conf['inbound_interface']['name']
+ if iiface[0] == '!':
+ operator = '!='
+ iiface = iiface[1:]
+ output.append(f'iifname {operator} {{{iiface}}}')
+ else:
+ iiface = rule_conf['inbound_interface']['group']
+ if iiface[0] == '!':
+ operator = '!='
+ iiface = iiface[1:]
+ output.append(f'iifname {operator} @I_{iiface}')
if 'outbound_interface' in rule_conf:
- ifname = rule_conf['outbound_interface']
- if ifname != 'any':
- output.append(f'oifname "{ifname}"')
+ operator = ''
+ if 'name' in rule_conf['outbound_interface']:
+ oiface = rule_conf['outbound_interface']['name']
+ if oiface[0] == '!':
+ operator = '!='
+ oiface = oiface[1:]
+ output.append(f'oifname {operator} {{{oiface}}}')
+ else:
+ oiface = rule_conf['outbound_interface']['group']
+ if oiface[0] == '!':
+ operator = '!='
+ oiface = oiface[1:]
+ output.append(f'oifname {operator} @I_{oiface}')
if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
protocol = rule_conf['protocol']
@@ -150,7 +170,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
operator = ''
if addr_prefix[:1] == '!':
operator = '!='
- addr_prefix = addr[1:]
+ addr_prefix = addr_prefix[1:]
output.append(f'ip6 {prefix}addr {operator} {addr_prefix}')
port = dict_search_args(side_conf, 'port')
diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py
index c63c7cf39..0d5f9a8a1 100644
--- a/python/vyos/qos/trafficshaper.py
+++ b/python/vyos/qos/trafficshaper.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2022-2023 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
@@ -89,6 +89,10 @@ class TrafficShaper(QoSBase):
if 'priority' in cls_config:
priority = cls_config['priority']
tmp += f' prio {priority}'
+
+ if 'ceiling' in cls_config:
+ f_ceil = self._rate_convert(cls_config['ceiling'])
+ tmp += f' ceil {f_ceil}'
self._cmd(tmp)
tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq'
@@ -102,6 +106,9 @@ class TrafficShaper(QoSBase):
if 'priority' in config['default']:
priority = config['default']['priority']
tmp += f' prio {priority}'
+ if 'ceiling' in config['default']:
+ f_ceil = self._rate_convert(config['default']['ceiling'])
+ tmp += f' ceil {f_ceil}'
self._cmd(tmp)
tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq'
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 3be486cc4..c778d0de8 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -582,10 +582,11 @@ def nft_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name='ip'):
def nft_default_rule(fw_conf, fw_name, ipv6=False):
output = ['counter']
default_action = fw_conf['default_action']
+ family = 'ipv6' if ipv6 else 'ipv4'
if 'enable_default_log' in fw_conf:
action_suffix = default_action[:1].upper()
- output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"')
+ output.append(f'log prefix "[{family}-{fw_name[:19]}-default-{action_suffix}]"')
#output.append(nft_action(default_action))
output.append(f'{default_action}')
diff --git a/scripts/import-conf-mode-commands b/scripts/import-conf-mode-commands
deleted file mode 100755
index 996b31c9c..000000000
--- a/scripts/import-conf-mode-commands
+++ /dev/null
@@ -1,255 +0,0 @@
-#!/usr/bin/env python3
-#
-# build-command-template: converts old style commands definitions to XML
-#
-# Copyright (C) 2019 VyOS maintainers <maintainers@vyos.net>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
-# USA
-
-
-import os
-import re
-import sys
-
-from lxml import etree
-
-
-# Node types
-NODE = 0
-LEAF_NODE = 1
-TAG_NODE = 2
-
-def parse_command_data(t):
- regs = {
- 'help': r'\bhelp:(.*)(?:\n|$)',
- 'priority': r'\bpriority:(.*)(?:\n|$)',
- 'type': r'\btype:(.*)(?:\n|$)',
- 'syntax_expression_var': r'\bsyntax:expression: \$VAR\(\@\) in (.*)'
- }
-
- data = {'multi': False, 'help': ""}
-
- for r in regs:
- try:
- data[r] = re.search(regs[r], t).group(1).strip()
- except:
- data[r] = None
-
- # val_help is special: there can be multiple instances
- val_help_strings = re.findall(r'\bval_help:(.*)(?:\n|$)', t)
- val_help = []
- for v in val_help_strings:
- try:
- fmt, msg = re.match(r'\s*(.*)\s*;\s*(.*)\s*(?:\n|$)', v).groups()
- except:
- fmt = "<text>"
- msg = v
- val_help.append((fmt, msg))
- data['val_help'] = val_help
-
- # multi is on/off
- if re.match(r'\bmulti:', t):
- data['multi'] = True
-
- return(data)
-
-def walk(tree, base_path, name):
- path = os.path.join(base_path, name)
-
- contents = os.listdir(path)
-
- # Determine node type and create XML element for the node
- # Tag node dirs will always have 'node.tag' subdir and 'node.def' file
- # Leaf node dirs have nothing but a 'node.def' file
- # Everything that doesn't match either of these patterns is a normal node
- if 'node.tag' in contents:
- print("Creating a tag node from {0}".format(path))
- elem = etree.Element('tagNode')
- node_type = TAG_NODE
- elif contents == ['node.def']:
- print("Creating a leaf node from {0}".format(path))
- elem = etree.Element('leafNode')
- node_type = LEAF_NODE
- else:
- print("Creating a node from {0}".format(path))
- elem = etree.Element('node')
- node_type = NODE
-
- # Read and parse the command definition data (the 'node.def' file)
- with open(os.path.join(path, 'node.def'), 'r') as f:
- node_def = f.read()
- data = parse_command_data(node_def)
-
- # Import the data into the properties element
- props_elem = etree.Element('properties')
-
- if data['priority']:
- # Priority values sometimes come with comments that explain the value choice
- try:
- prio, prio_comment = re.match(r'\s*(\d+)\s*#(.*)', data['priority']).groups()
- except:
- prio = data['priority'].strip()
- prio_comment = None
- prio_elem = etree.Element('priority')
- prio_elem.text = prio
- props_elem.append(prio_elem)
- if prio_comment:
- prio_comment_elem = etree.Comment(prio_comment)
- props_elem.append(prio_comment_elem)
-
- if data['multi']:
- multi_elem = etree.Element('multi')
- props_elem.append(multi_elem)
-
- if data['help']:
- help_elem = etree.Element('help')
- help_elem.text = data['help']
- props_elem.append(help_elem)
-
- # For leaf nodes, absense of a type: tag means they take no values
- # For any other nodes, it doesn't mean anything
- if not data['type'] and (node_type == LEAF_NODE):
- valueless = etree.Element('valueless')
- props_elem.append(valueless)
-
- # There can be only one constraint element in the definition
- # Create it now, we'll modify it in the next two cases, then append
- constraint_elem = etree.Element('constraint')
- has_constraint = False
-
- # Add regexp field for multiple options
- if data['syntax_expression_var']:
- regex = etree.Element('regex')
- constraint_error=etree.Element('constraintErrorMessage')
- values = re.search(r'(.+) ; (.+)', data['syntax_expression_var']).group(1)
- message = re.search(r'(.+) ; (.+)', data['syntax_expression_var']).group(2)
- values = re.findall(r'\"(.+?)\"', values)
- regex.text = '|'.join(values)
- constraint_error.text = re.sub('\".*?VAR.*?\"', '', message)
- constraint_error.text = re.sub(r'[\"|\\]', '', message)
- constraint_elem.append(regex)
- props_elem.append(constraint_elem)
- props_elem.append(constraint_error)
-
- if data['val_help']:
- for vh in data['val_help']:
- vh_elem = etree.Element('valueHelp')
-
- vh_fmt_elem = etree.Element('format')
- # Many commands use special "u32:<start>-<end>" format for ranges
- if re.match(r'u32:', vh[0]):
- vh_fmt = re.match(r'u32:(.*)', vh[0]).group(1).strip()
-
- # If valid range of values is specified in val_help, we can automatically
- # create a constraint for it
- # Extracting it from syntax:expression: would be much more complicated
- vh_validator = etree.Element('validator')
- vh_validator.set("name", "numeric")
- vh_validator.set("argument", "--range {0}".format(vh_fmt))
- constraint_elem.append(vh_validator)
- has_constraint = True
- else:
- vh_fmt = vh[0]
- vh_fmt_elem.text = vh_fmt
-
- vh_help_elem = etree.Element('description')
- vh_help_elem.text = vh[1]
-
- vh_elem.append(vh_fmt_elem)
- vh_elem.append(vh_help_elem)
- props_elem.append(vh_elem)
-
- # Translate the "type:" to the new validator system
- if data['type']:
- t = data['type']
- if t == 'txt':
- # Can't infer anything from the generic "txt" type
- pass
- else:
- validator = etree.Element('validator')
- if t == 'u32':
- validator.set('name', 'numeric')
- validator.set('argument', '--non-negative')
- elif t == 'ipv4':
- validator.set('name', 'ipv4-address')
- elif t == 'ipv4net':
- validator.set('name', 'ipv4-prefix')
- elif t == 'ipv6':
- validator.set('name', 'ipv6-address')
- elif t == 'ipv6net':
- validator.set('name', 'ipv6-prefix')
- elif t == 'macaddr':
- validator.set('name', 'mac-address')
- else:
- print("Warning: unsupported type \'{0}\'".format(t))
- validator = None
-
- if (validator is not None) and (not has_constraint):
- # If has_constraint is true, it means a more specific validator
- # was already extracted from another option
- constraint_elem.append(validator)
- has_constraint = True
-
- if has_constraint:
- props_elem.append(constraint_elem)
-
- elem.append(props_elem)
-
- elem.set("name", name)
-
- if node_type != LEAF_NODE:
- children = etree.Element('children')
-
- # Create the next level dir path,
- # accounting for the "virtual" node.tag subdir for tag nodes
- next_level = path
- if node_type == TAG_NODE:
- next_level = os.path.join(path, 'node.tag')
-
- # Walk the subdirs of the next level
- for d in os.listdir(next_level):
- dp = os.path.join(next_level, d)
- if os.path.isdir(dp):
- walk(children, next_level, d)
-
- elem.append(children)
-
- tree.append(elem)
-
-if __name__ == '__main__':
- if len(sys.argv) < 2:
- print("Usage: {0} <base path>".format(sys.argv[0]))
- sys.exit(1)
- else:
- base_path = sys.argv[1]
-
- root = etree.Element('interfaceDefinition')
- contents = os.listdir(base_path)
- elem = etree.Element('node')
- elem.set('name', os.path.basename(base_path))
- children = etree.Element('children')
-
- for c in contents:
- path = os.path.join(base_path, c)
- if os.path.isdir(path):
- walk(children, base_path, c)
-
- elem.append(children)
- root.append(elem)
-
- xml_data = etree.tostring(root, pretty_print=True).decode()
- with open('output.xml', 'w') as f:
- f.write(xml_data)
diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn
index 37baee0fd..e10adbbc6 100644
--- a/smoketest/config-tests/dialup-router-medium-vpn
+++ b/smoketest/config-tests/dialup-router-medium-vpn
@@ -141,65 +141,65 @@ set interfaces wireguard wg1 peer sam preshared-key 'XpFtzx2Z+nR8pBv9/sSf7I94OkZ
set interfaces wireguard wg1 peer sam public-key 'v5zfKGvH6W/lfDXJ0en96lvKo1gfFxMUWxe02+Fj5BU='
set interfaces wireguard wg1 port '7778'
set nat destination rule 50 destination port '49371'
-set nat destination rule 50 inbound-interface 'pppoe0'
+set nat destination rule 50 inbound-interface name 'pppoe0'
set nat destination rule 50 protocol 'tcp_udp'
set nat destination rule 50 translation address '192.168.0.5'
set nat destination rule 51 destination port '58050-58051'
-set nat destination rule 51 inbound-interface 'pppoe0'
+set nat destination rule 51 inbound-interface name 'pppoe0'
set nat destination rule 51 protocol 'tcp'
set nat destination rule 51 translation address '192.168.0.5'
set nat destination rule 52 destination port '22067-22070'
-set nat destination rule 52 inbound-interface 'pppoe0'
+set nat destination rule 52 inbound-interface name 'pppoe0'
set nat destination rule 52 protocol 'tcp'
set nat destination rule 52 translation address '192.168.0.5'
set nat destination rule 53 destination port '34342'
-set nat destination rule 53 inbound-interface 'pppoe0'
+set nat destination rule 53 inbound-interface name 'pppoe0'
set nat destination rule 53 protocol 'tcp_udp'
set nat destination rule 53 translation address '192.168.0.121'
set nat destination rule 54 destination port '45459'
-set nat destination rule 54 inbound-interface 'pppoe0'
+set nat destination rule 54 inbound-interface name 'pppoe0'
set nat destination rule 54 protocol 'tcp_udp'
set nat destination rule 54 translation address '192.168.0.120'
set nat destination rule 55 destination port '22'
-set nat destination rule 55 inbound-interface 'pppoe0'
+set nat destination rule 55 inbound-interface name 'pppoe0'
set nat destination rule 55 protocol 'tcp'
set nat destination rule 55 translation address '192.168.0.5'
set nat destination rule 56 destination port '8920'
-set nat destination rule 56 inbound-interface 'pppoe0'
+set nat destination rule 56 inbound-interface name 'pppoe0'
set nat destination rule 56 protocol 'tcp'
set nat destination rule 56 translation address '192.168.0.5'
set nat destination rule 60 destination port '80,443'
-set nat destination rule 60 inbound-interface 'pppoe0'
+set nat destination rule 60 inbound-interface name 'pppoe0'
set nat destination rule 60 protocol 'tcp'
set nat destination rule 60 translation address '192.168.0.5'
set nat destination rule 70 destination port '5001'
-set nat destination rule 70 inbound-interface 'pppoe0'
+set nat destination rule 70 inbound-interface name 'pppoe0'
set nat destination rule 70 protocol 'tcp'
set nat destination rule 70 translation address '192.168.0.5'
set nat destination rule 80 destination port '25'
-set nat destination rule 80 inbound-interface 'pppoe0'
+set nat destination rule 80 inbound-interface name 'pppoe0'
set nat destination rule 80 protocol 'tcp'
set nat destination rule 80 translation address '192.168.0.5'
set nat destination rule 90 destination port '8123'
-set nat destination rule 90 inbound-interface 'pppoe0'
+set nat destination rule 90 inbound-interface name 'pppoe0'
set nat destination rule 90 protocol 'tcp'
set nat destination rule 90 translation address '192.168.0.7'
set nat destination rule 91 destination port '1880'
-set nat destination rule 91 inbound-interface 'pppoe0'
+set nat destination rule 91 inbound-interface name 'pppoe0'
set nat destination rule 91 protocol 'tcp'
set nat destination rule 91 translation address '192.168.0.7'
set nat destination rule 500 destination address '!192.168.0.0/24'
set nat destination rule 500 destination port '53'
-set nat destination rule 500 inbound-interface 'eth1'
+set nat destination rule 500 inbound-interface name 'eth1'
set nat destination rule 500 protocol 'tcp_udp'
set nat destination rule 500 source address '!192.168.0.1-192.168.0.5'
set nat destination rule 500 translation address '192.168.0.1'
-set nat source rule 1000 outbound-interface 'pppoe0'
+set nat source rule 1000 outbound-interface name 'pppoe0'
set nat source rule 1000 translation address 'masquerade'
-set nat source rule 2000 outbound-interface 'vtun0'
+set nat source rule 2000 outbound-interface name 'vtun0'
set nat source rule 2000 source address '192.168.0.0/16'
set nat source rule 2000 translation address 'masquerade'
-set nat source rule 3000 outbound-interface 'vtun1'
+set nat source rule 3000 outbound-interface name 'vtun1'
set nat source rule 3000 translation address 'masquerade'
set policy prefix-list user1-routes rule 1 action 'permit'
set policy prefix-list user1-routes rule 1 prefix '192.168.0.0/24'
diff --git a/smoketest/configs/bgp-evpn-l2vpn-leaf b/smoketest/configs/bgp-evpn-l2vpn-leaf
index 020490186..ab46fbb02 100644
--- a/smoketest/configs/bgp-evpn-l2vpn-leaf
+++ b/smoketest/configs/bgp-evpn-l2vpn-leaf
@@ -33,7 +33,6 @@ interfaces {
parameters {
nolearning
}
- port 4789
source-address 172.29.0.1
vni 100
}
diff --git a/smoketest/configs/cluster-basic b/smoketest/configs/cluster-basic
new file mode 100644
index 000000000..1e34999c1
--- /dev/null
+++ b/smoketest/configs/cluster-basic
@@ -0,0 +1,62 @@
+cluster {
+ dead-interval 500
+ group VyOS {
+ auto-failback true
+ primary vyos1
+ secondary vyos2
+ service 192.0.2.10/24/eth1
+ service 192.0.2.20/24
+ }
+ interface eth1
+ keepalive-interval 100
+ monitor-dead-interval 420
+ pre-shared-secret qwerty
+}
+interfaces {
+ ethernet eth0 {
+ duplex auto
+ smp-affinity auto
+ speed auto
+ }
+ ethernet eth1 {
+ address 192.0.2.1/24
+ duplex auto
+ smp-affinity auto
+ speed auto
+ }
+ loopback lo {
+ }
+}
+system {
+ config-management {
+ commit-revisions 100
+ }
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0
+ plaintext-password ""
+ }
+ }
+ }
+ syslog {
+ global {
+ facility all {
+ level info
+ }
+ facility protocols {
+ level debug
+ }
+ }
+ }
+ time-zone Antarctica/South_Pole
+}
+// Warning: Do not remove the following line.
+// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1"
+// Release version: 1.3.3
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 7b4ba11d0..1f9b6e3f0 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -148,7 +148,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'accept'])
- self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'interface-group', '!smoketest_interface'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'group', '!smoketest_interface'])
self.cli_commit()
@@ -226,6 +226,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'ttl', 'gt', '102'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'enable-default-log'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'protocol', 'tcp'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'destination', 'port', '22'])
@@ -243,15 +244,16 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'flags', 'syn'])
self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'mss', mss_range])
self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'packet-type', 'broadcast'])
- self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'inbound-interface', 'interface-name', interface_wc])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'inbound-interface', 'name', interface_wc])
self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'action', 'return'])
self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'protocol', 'gre'])
self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'connection-mark', conn_mark])
- self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'enable-default-log'])
self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'action', 'drop'])
self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'protocol', 'gre'])
- self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'outbound-interface', 'interface-name', interface_inv])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'outbound-interface', 'name', interface_inv])
self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'action', 'return'])
self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'protocol', 'icmp'])
self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'connection-mark', conn_mark])
@@ -262,21 +264,24 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['chain VYOS_FORWARD_filter'],
- ['type filter hook forward priority filter; policy drop;'],
+ ['type filter hook forward priority filter; policy accept;'],
['tcp dport 22', 'limit rate 5/minute', 'accept'],
['tcp dport 22', 'add @RECENT_FWD_filter_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'],
+ ['log prefix "[ipv4-FWD-filter-default-D]"','FWD-filter default-action drop', 'drop'],
['chain VYOS_INPUT_filter'],
['type filter hook input priority filter; policy accept;'],
['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface_wc}"', 'meta pkttype broadcast', 'accept'],
['meta l4proto gre', f'ct mark {mark_hex}', 'return'],
+ ['INP-filter default-action accept', 'accept'],
['chain VYOS_OUTPUT_filter'],
['type filter hook output priority filter; policy accept;'],
['meta l4proto gre', f'oifname != "{interface}"', 'drop'],
['meta l4proto icmp', f'ct mark {mark_hex}', 'return'],
+ ['log prefix "[ipv4-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'],
['chain NAME_smoketest'],
['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'],
['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'],
- ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop']
+ ['log prefix "[ipv4-smoketest-default-D]"','smoketest default-action', 'drop']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -326,16 +331,18 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['chain VYOS_FORWARD_filter'],
- ['type filter hook forward priority filter; policy drop;'],
+ ['type filter hook forward priority filter; policy accept;'],
['ip saddr 198.51.100.1', 'meta mark 0x000003f2', f'jump NAME_{name}'],
+ ['FWD-filter default-action drop', 'drop'],
['chain VYOS_INPUT_filter'],
['type filter hook input priority filter; policy accept;'],
['meta mark != 0x000181cd', 'meta l4proto tcp','queue to 3'],
['meta l4proto udp','queue flags bypass,fanout to 0-15'],
+ ['INP-filter default-action accept', 'accept'],
[f'chain NAME_{name}'],
['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'],
['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'],
- [f'log prefix "[{name}-default-D]"', 'drop']
+ [f'log prefix "[ipv4-{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -411,20 +418,22 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log-options', 'level', 'crit'])
self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-action', 'accept'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'enable-default-log'])
self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'action', 'reject'])
self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'protocol', 'tcp_udp'])
self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'destination', 'port', '8888'])
- self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'inbound-interface', 'interface-name', interface])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'inbound-interface', 'name', interface])
self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'enable-default-log'])
self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'action', 'return'])
self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'protocol', 'gre'])
- self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'outbound-interface', 'interface-name', interface])
+ self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'outbound-interface', 'name', interface])
self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'action', 'accept'])
self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'protocol', 'udp'])
self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'source', 'address', '2002::1:2'])
- self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'inbound-interface', 'interface-name', interface])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'inbound-interface', 'name', interface])
self.cli_commit()
@@ -432,15 +441,18 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['chain VYOS_IPV6_FORWARD_filter'],
['type filter hook forward priority filter; policy accept;'],
['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'],
+ ['log prefix "[ipv6-FWD-filter-default-A]"','FWD-filter default-action accept', 'accept'],
['chain VYOS_IPV6_INPUT_filter'],
['type filter hook input priority filter; policy accept;'],
['meta l4proto udp', 'ip6 saddr 2002::1:2', f'iifname "{interface}"', 'accept'],
+ ['INP-filter default-action accept', 'accept'],
['chain VYOS_IPV6_OUTPUT_filter'],
- ['type filter hook output priority filter; policy drop;'],
+ ['type filter hook output priority filter; policy accept;'],
['meta l4proto gre', f'oifname "{interface}"', 'return'],
+ ['log prefix "[ipv6-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'],
[f'chain NAME6_{name}'],
['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'],
- [f'"{name} default-action drop"', f'log prefix "[{name}-default-D]"', 'drop']
+ [f'"{name} default-action drop"', f'log prefix "[ipv6-{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
@@ -483,7 +495,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ip6 saddr 2001:db8::/64', 'meta mark != 0x000019ff-0x00001e56', f'jump NAME6_{name}'],
[f'chain NAME6_{name}'],
['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'],
- [f'log prefix "[{name}-default-D]"', 'drop']
+ [f'log prefix "[ipv6-{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
@@ -569,7 +581,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'bridge', 'name', name, 'enable-default-log'])
self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept'])
self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'source', 'mac-address', mac_address])
- self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'inbound-interface', 'interface-name', interface_in])
+ self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'inbound-interface', 'name', interface_in])
self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log', 'enable'])
self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log-options', 'level', 'crit'])
@@ -635,6 +647,44 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
with open(path, 'r') as f:
self.assertNotEqual(f.read().strip(), conf['default'], msg=path)
+### Zone
+ def test_zone_basic(self):
+ self.cli_set(['firewall', 'ipv4', '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 VYOS_ZONE_FORWARD'],
+ ['type filter hook forward priority filter + 1'],
+ ['chain VYOS_ZONE_OUTPUT'],
+ ['type filter hook output priority filter + 1'],
+ ['chain VYOS_ZONE_LOCAL'],
+ ['type filter hook input priority filter + 1'],
+ ['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)
+
+
def test_flow_offload(self):
self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0'])
self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware'])
diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py
index e9c9e68fd..3a8b8ab99 100755
--- a/smoketest/scripts/cli/test_interfaces_vxlan.py
+++ b/smoketest/scripts/cli/test_interfaces_vxlan.py
@@ -107,7 +107,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
def test_vxlan_external(self):
interface = 'vxlan0'
source_address = '192.0.2.1'
- self.cli_set(self._base_path + [interface, 'external'])
+ self.cli_set(self._base_path + [interface, 'parameters', 'external'])
self.cli_set(self._base_path + [interface, 'source-address', source_address])
# Both 'VNI' and 'external' can not be specified at the same time.
@@ -150,7 +150,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
'31': '10031',
}
- self.cli_set(self._base_path + [interface, 'external'])
+ self.cli_set(self._base_path + [interface, 'parameters', 'external'])
self.cli_set(self._base_path + [interface, 'source-interface', source_interface])
for vlan, vni in vlan_to_vni.items():
@@ -177,11 +177,50 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
tmp = get_interface_config(interface)
self.assertEqual(tmp['master'], bridge)
+ self.assertFalse(tmp['linkinfo']['info_slave_data']['neigh_suppress'])
tmp = get_vxlan_vlan_tunnels('vxlan0')
self.assertEqual(tmp, list(vlan_to_vni))
self.cli_delete(['interfaces', 'bridge', bridge])
+ def test_vxlan_neighbor_suppress(self):
+ bridge = 'br555'
+ interface = 'vxlan555'
+ source_interface = 'eth0'
+
+ self.cli_set(self._base_path + [interface, 'external'])
+ self.cli_set(self._base_path + [interface, 'source-interface', source_interface])
+
+ self.cli_set(self._base_path + [interface, 'parameters', 'neighbor-suppress'])
+
+ # This must fail as this VXLAN interface is not associated with any bridge
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', interface])
+
+ # commit configuration
+ self.cli_commit()
+
+ self.assertTrue(interface_exists(bridge))
+ self.assertTrue(interface_exists(interface))
+
+ tmp = get_interface_config(interface)
+ self.assertEqual(tmp['master'], bridge)
+ self.assertTrue(tmp['linkinfo']['info_slave_data']['neigh_suppress'])
+ self.assertFalse(tmp['linkinfo']['info_slave_data']['learning'])
+
+ # Remove neighbor suppress configuration and re-test
+ self.cli_delete(self._base_path + [interface, 'parameters', 'neighbor-suppress'])
+ # commit configuration
+ self.cli_commit()
+
+ tmp = get_interface_config(interface)
+ self.assertEqual(tmp['master'], bridge)
+ self.assertFalse(tmp['linkinfo']['info_slave_data']['neigh_suppress'])
+ self.assertTrue(tmp['linkinfo']['info_slave_data']['learning'])
+
+ self.cli_delete(['interfaces', 'bridge', bridge])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 703e5ab28..682fc141d 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -82,12 +82,12 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
# or configured destination address for NAT
if int(rule) < 200:
self.cli_set(src_path + ['rule', rule, 'source', 'address', network])
- self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_100])
+ self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'name', outbound_iface_100])
self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
nftables_search.append([f'saddr {network}', f'oifname "{outbound_iface_100}"', 'masquerade'])
else:
self.cli_set(src_path + ['rule', rule, 'destination', 'address', network])
- self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_200])
+ self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'name', outbound_iface_200])
self.cli_set(src_path + ['rule', rule, 'exclude'])
nftables_search.append([f'daddr {network}', f'oifname "{outbound_iface_200}"', 'return'])
@@ -98,13 +98,15 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
def test_snat_groups(self):
address_group = 'smoketest_addr'
address_group_member = '192.0.2.1'
+ interface_group = 'smoketest_ifaces'
+ interface_group_member = 'bond.99'
rule = '100'
- outbound_iface = 'eth0'
self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member])
+ self.cli_set(['firewall', 'group', 'interface-group', interface_group, 'interface', interface_group_member])
self.cli_set(src_path + ['rule', rule, 'source', 'group', 'address-group', address_group])
- self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface])
+ self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'group', interface_group])
self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
self.cli_commit()
@@ -112,7 +114,7 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
nftables_search = [
[f'set A_{address_group}'],
[f'elements = {{ {address_group_member} }}'],
- [f'ip saddr @A_{address_group}', f'oifname "{outbound_iface}"', 'masquerade']
+ [f'ip saddr @A_{address_group}', f'oifname @I_{interface_group}', 'masquerade']
]
self.verify_nftables(nftables_search, 'ip vyos_nat')
@@ -136,12 +138,12 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
rule_search = [f'dnat to 192.0.2.1:{port}']
if int(rule) < 200:
self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_100])
- self.cli_set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_100])
+ self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'name', inbound_iface_100])
rule_search.append(f'{inbound_proto_100} sport {port}')
rule_search.append(f'iifname "{inbound_iface_100}"')
else:
self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_200])
- self.cli_set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_200])
+ self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'name', inbound_iface_200])
rule_search.append(f'iifname "{inbound_iface_200}"')
nftables_search.append(rule_search)
@@ -167,7 +169,7 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
rule = '1000'
self.cli_set(dst_path + ['rule', rule, 'destination', 'address', '!192.0.2.1'])
self.cli_set(dst_path + ['rule', rule, 'destination', 'port', '53'])
- self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'eth0'])
+ self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'name', 'eth0'])
self.cli_set(dst_path + ['rule', rule, 'protocol', 'tcp_udp'])
self.cli_set(dst_path + ['rule', rule, 'source', 'address', '!192.0.2.1'])
self.cli_set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1'])
@@ -186,7 +188,7 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
def test_dnat_without_translation_address(self):
- self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1'])
self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443'])
self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp'])
self.cli_set(dst_path + ['rule', '1', 'packet-type', 'host'])
@@ -236,13 +238,13 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_set(dst_path + ['rule', '10', 'destination', 'address', dst_addr_1])
self.cli_set(dst_path + ['rule', '10', 'destination', 'port', dest_port])
self.cli_set(dst_path + ['rule', '10', 'protocol', protocol])
- self.cli_set(dst_path + ['rule', '10', 'inbound-interface', ifname])
+ self.cli_set(dst_path + ['rule', '10', 'inbound-interface', 'name', ifname])
self.cli_set(dst_path + ['rule', '10', 'translation', 'redirect', 'port', redirected_port])
self.cli_set(dst_path + ['rule', '20', 'destination', 'address', dst_addr_1])
self.cli_set(dst_path + ['rule', '20', 'destination', 'port', dest_port])
self.cli_set(dst_path + ['rule', '20', 'protocol', protocol])
- self.cli_set(dst_path + ['rule', '20', 'inbound-interface', ifname])
+ self.cli_set(dst_path + ['rule', '20', 'inbound-interface', 'name', ifname])
self.cli_set(dst_path + ['rule', '20', 'translation', 'redirect'])
self.cli_commit()
@@ -266,7 +268,7 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
weight_4 = '65'
dst_port = '443'
- self.cli_set(dst_path + ['rule', '1', 'inbound-interface', ifname])
+ self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', ifname])
self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp'])
self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dst_port])
self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-address'])
@@ -276,7 +278,7 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_1, 'weight', weight_1])
self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_2, 'weight', weight_2])
- self.cli_set(src_path + ['rule', '1', 'outbound-interface', ifname])
+ self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'name', ifname])
self.cli_set(src_path + ['rule', '1', 'load-balance', 'hash', 'random'])
self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_3, 'weight', weight_3])
self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_4, 'weight', weight_4])
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index e062f28a6..0607f6616 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -56,15 +56,15 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
def test_source_nat66(self):
source_prefix = 'fc00::/64'
translation_prefix = 'fc01::/64'
- self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'name', 'eth1'])
self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix])
self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_prefix])
- self.cli_set(src_path + ['rule', '2', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '2', 'outbound-interface', 'name', 'eth1'])
self.cli_set(src_path + ['rule', '2', 'source', 'prefix', source_prefix])
self.cli_set(src_path + ['rule', '2', 'translation', 'address', 'masquerade'])
- self.cli_set(src_path + ['rule', '3', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '3', 'outbound-interface', 'name', 'eth1'])
self.cli_set(src_path + ['rule', '3', 'source', 'prefix', source_prefix])
self.cli_set(src_path + ['rule', '3', 'exclude'])
@@ -81,7 +81,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
def test_source_nat66_address(self):
source_prefix = 'fc00::/64'
translation_address = 'fc00::1'
- self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'name', 'eth1'])
self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix])
self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_address])
@@ -98,11 +98,11 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
destination_address = 'fc00::1'
translation_address = 'fc01::1'
source_address = 'fc02::1'
- self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1'])
self.cli_set(dst_path + ['rule', '1', 'destination', 'address', destination_address])
self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_address])
- self.cli_set(dst_path + ['rule', '2', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '2', 'inbound-interface', 'name', 'eth1'])
self.cli_set(dst_path + ['rule', '2', 'destination', 'address', destination_address])
self.cli_set(dst_path + ['rule', '2', 'source', 'address', source_address])
self.cli_set(dst_path + ['rule', '2', 'exclude'])
@@ -124,7 +124,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
sport = '8080'
tport = '5555'
proto = 'tcp'
- self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', '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])
@@ -144,7 +144,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
def test_destination_nat66_prefix(self):
destination_prefix = 'fc00::/64'
translation_prefix = 'fc01::/64'
- self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1'])
self.cli_set(dst_path + ['rule', '1', 'destination', 'address', destination_prefix])
self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_prefix])
@@ -158,7 +158,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 vyos_nat')
def test_destination_nat66_without_translation_address(self):
- self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1'])
self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443'])
self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp'])
self.cli_set(dst_path + ['rule', '1', 'translation', 'port', '443'])
@@ -180,7 +180,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
# check validate() - outbound-interface must be defined
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'eth0'])
+ self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'name', 'eth0'])
# check validate() - translation address not specified
with self.assertRaises(ConfigSessionError):
@@ -196,7 +196,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
sport = '8080'
tport = '80'
proto = 'tcp'
- self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'name', '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])
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 967958cab..5e3402fa8 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -340,6 +340,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
# AFI maximum path support
self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4])
self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ibgp', max_path_v4ibgp])
+ self.cli_set(base_path + ['address-family', 'ipv4-labeled-unicast', 'maximum-paths', 'ebgp', max_path_v4])
+ self.cli_set(base_path + ['address-family', 'ipv4-labeled-unicast', 'maximum-paths', 'ibgp', max_path_v4ibgp])
self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'maximum-paths', 'ebgp', max_path_v6])
self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'maximum-paths', 'ibgp', max_path_v6ibgp])
@@ -373,6 +375,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config)
self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config)
+ afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast')
+ self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config)
+ self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config)
+
afiv6_config = self.getFRRconfig(' address-family ipv6 unicast')
self.assertIn(f' maximum-paths {max_path_v6}', afiv6_config)
self.assertIn(f' maximum-paths ibgp {max_path_v6ibgp}', afiv6_config)
diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py
index 3743be788..81e7326f8 100755
--- a/smoketest/scripts/cli/test_qos.py
+++ b/smoketest/scripts/cli/test_qos.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 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
@@ -543,5 +543,60 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
dport = int(match_config['dport'])
self.assertEqual(f'{dport:x}', filter['options']['match']['value'])
+
+ def test_11_shaper(self):
+ bandwidth = 250
+ default_bandwidth = 20
+ default_ceil = 30
+ class_bandwidth = 50
+ class_ceil = 80
+ dst_address = '192.0.2.8/32'
+
+ for interface in self._interfaces:
+ shaper_name = f'qos-shaper-{interface}'
+
+ self.cli_set(base_path + ['interface', interface, 'egress', shaper_name])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'bandwidth', f'{bandwidth}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'bandwidth', f'{default_bandwidth}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'ceiling', f'{default_ceil}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'queue-type', 'fair-queue'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'bandwidth', f'{class_bandwidth}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'ceiling', f'{class_ceil}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ip', 'destination', 'address', dst_address])
+
+ bandwidth += 1
+ default_bandwidth += 1
+ default_ceil += 1
+ class_bandwidth += 1
+ class_ceil += 1
+
+ # commit changes
+ self.cli_commit()
+
+ bandwidth = 250
+ default_bandwidth = 20
+ default_ceil = 30
+ class_bandwidth = 50
+ class_ceil = 80
+
+ for interface in self._interfaces:
+ config_entries = (
+ f'root rate {bandwidth}Mbit ceil {bandwidth}Mbit',
+ f'prio 0 rate {class_bandwidth}Mbit ceil {class_ceil}Mbit',
+ f'prio 7 rate {default_bandwidth}Mbit ceil {default_ceil}Mbit'
+ )
+
+ output = cmd(f'tc class show dev {interface}')
+
+ for config_entry in config_entries:
+ self.assertIn(config_entry, output)
+
+ bandwidth += 1
+ default_bandwidth += 1
+ default_ceil += 1
+ class_bandwidth += 1
+ class_ceil += 1
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index f6480ab0a..8028492a7 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -283,8 +283,8 @@ def verify_rule(firewall, rule_conf, ipv6):
for direction in ['inbound_interface','outbound_interface']:
if direction in rule_conf:
- if 'interface_name' in rule_conf[direction] and 'interface_group' in rule_conf[direction]:
- raise ConfigError(f'Cannot specify both interface-group and interface-name for {direction}')
+ if 'name' in rule_conf[direction] and 'group' in rule_conf[direction]:
+ raise ConfigError(f'Cannot specify both interface group and interface name for {direction}')
def verify_nested_group(group_name, group, groups, seen):
if 'include' not in group:
@@ -374,12 +374,82 @@ def verify(firewall):
for rule_id, rule_conf in name_conf['rule'].items():
verify_rule(firewall, rule_conf, True)
+ #### ZONESSSS
+ local_zone = False
+ zone_interfaces = []
+
+ 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 '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 zone_interfaces]
+
+ if found_duplicates:
+ raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
+
+ zone_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(firewall, 'ipv4', '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, 'ipv4', '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')
+
return None
def generate(firewall):
if not os.path.exists(nftables_conf):
firewall['first_install'] = True
+ 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
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 05f68112a..6bf3227d5 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -34,6 +34,7 @@ from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import Interface
from vyos.ifconfig import VXLANIf
from vyos.template import is_ipv6
+from vyos.utils.dict import dict_search
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -53,7 +54,7 @@ def get_config(config=None):
# VXLAN interfaces are picky and require recreation if certain parameters
# change. But a VXLAN interface should - of course - not be re-created if
# it's description or IP address is adjusted. Feels somehow logic doesn't it?
- for cli_option in ['parameters', 'external', 'gpe', 'group', 'port', 'remote',
+ for cli_option in ['parameters', 'gpe', 'group', 'port', 'remote',
'source-address', 'source-interface', 'vni']:
if is_node_changed(conf, base + [ifname, cli_option]):
vxlan.update({'rebuild_required': {}})
@@ -94,17 +95,17 @@ def verify(vxlan):
if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan):
raise ConfigError('Group, remote, source-address or source-interface must be configured')
- if 'vni' not in vxlan and 'external' not in vxlan:
- raise ConfigError(
- 'Must either configure VXLAN "vni" or use "external" CLI option!')
+ if 'vni' not in vxlan and dict_search('parameters.external', vxlan) == None:
+ raise ConfigError('Must either configure VXLAN "vni" or use "external" CLI option!')
- if {'external', 'vni'} <= set(vxlan):
- raise ConfigError('Can not specify both "external" and "VNI"!')
+ if dict_search('parameters.external', vxlan):
+ if 'vni' in vxlan:
+ raise ConfigError('Can not specify both "external" and "VNI"!')
- if {'external', 'other_tunnels'} <= set(vxlan):
- other_tunnels = ', '.join(vxlan['other_tunnels'])
- raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\
- f'CLI option is used. Additional tunnels: {other_tunnels}')
+ if 'other_tunnels' in vxlan:
+ other_tunnels = ', '.join(vxlan['other_tunnels'])
+ raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\
+ f'CLI option is used. Additional tunnels: {other_tunnels}')
if 'gpe' in vxlan and 'external' not in vxlan:
raise ConfigError(f'VXLAN-GPE is only supported when "external" '\
@@ -164,10 +165,22 @@ def verify(vxlan):
raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!')
vnis_used.append(vni)
+ if dict_search('parameters.neighbor_suppress', vxlan):
+ if 'is_bridge_member' not in vxlan:
+ raise ConfigError('Neighbor suppression requires that VXLAN interface '\
+ 'is member of a bridge interface!')
+
verify_mtu_ipv6(vxlan)
verify_address(vxlan)
verify_bond_bridge_member(vxlan)
verify_mirror_redirect(vxlan)
+
+ # We use a defaultValue for port, thus it's always safe to use
+ if vxlan['port'] == '8472':
+ Warning('Starting from VyOS 1.4, the default port for VXLAN '\
+ 'has been changed to 4789. This matches the IANA assigned '\
+ 'standard port number!')
+
return None
def generate(vxlan):
diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py
index 8fe429653..ec4311bb5 100755
--- a/src/conf_mode/load-balancing-haproxy.py
+++ b/src/conf_mode/load-balancing-haproxy.py
@@ -94,8 +94,8 @@ def generate(lb):
if os.path.isfile(file):
os.unlink(file)
# Delete old directories
- #if os.path.isdir(load_balancing_dir):
- # rmtree(load_balancing_dir, ignore_errors=True)
+ if os.path.isdir(load_balancing_dir):
+ rmtree(load_balancing_dir, ignore_errors=True)
return None
@@ -106,15 +106,12 @@ def generate(lb):
# SSL Certificates for frontend
for front, front_config in lb['service'].items():
if 'ssl' in front_config:
- cert_file_path = os.path.join(load_balancing_dir, 'cert.pem')
- cert_key_path = os.path.join(load_balancing_dir, 'cert.pem.key')
- ca_cert_file_path = os.path.join(load_balancing_dir, 'ca.pem')
if 'certificate' in front_config['ssl']:
- #cert_file_path = os.path.join(load_balancing_dir, 'cert.pem')
- #cert_key_path = os.path.join(load_balancing_dir, 'cert.key')
cert_name = front_config['ssl']['certificate']
pki_cert = lb['pki']['certificate'][cert_name]
+ cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
+ cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
with open(cert_file_path, 'w') as f:
f.write(wrap_certificate(pki_cert['certificate']))
@@ -126,6 +123,7 @@ def generate(lb):
if 'ca_certificate' in front_config['ssl']:
ca_name = front_config['ssl']['ca_certificate']
pki_ca_cert = lb['pki']['ca'][ca_name]
+ ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')
with open(ca_cert_file_path, 'w') as f:
f.write(wrap_certificate(pki_ca_cert['certificate']))
@@ -133,11 +131,11 @@ def generate(lb):
# SSL Certificates for backend
for back, back_config in lb['backend'].items():
if 'ssl' in back_config:
- ca_cert_file_path = os.path.join(load_balancing_dir, 'ca.pem')
if 'ca_certificate' in back_config['ssl']:
ca_name = back_config['ssl']['ca_certificate']
pki_ca_cert = lb['pki']['ca'][ca_name]
+ ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')
with open(ca_cert_file_path, 'w') as f:
f.write(wrap_certificate(pki_ca_cert['certificate']))
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 52a7a71fd..44b13d413 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -151,8 +151,11 @@ def verify(nat):
err_msg = f'Source NAT configuration error in rule {rule}:'
if 'outbound_interface' in config:
- if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
- Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']:
+ raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for nat source rule "{rule}"')
+ elif 'name' in config['outbound_interface']:
+ if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces():
+ Warning(f'{err_msg} - interface "{config["outbound_interface"]["name"]}" does not exist on this system')
if not dict_search('translation.address', config) and not dict_search('translation.port', config):
if 'exclude' not in config and 'backend' not in config['load_balance']:
@@ -172,8 +175,11 @@ def verify(nat):
err_msg = f'Destination NAT configuration error in rule {rule}:'
if 'inbound_interface' in config:
- if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']:
+ raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for destination nat rule "{rule}"')
+ elif 'name' in config['inbound_interface']:
+ if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces():
+ Warning(f'{err_msg} - interface "{config["inbound_interface"]["name"]}" does not exist on this system')
if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']:
if 'exclude' not in config and 'backend' not in config['load_balance']:
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 46d796bc8..0ba08aef3 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -62,11 +62,13 @@ def verify(nat):
if dict_search('source.rule', nat):
for rule, config in dict_search('source.rule', nat).items():
err_msg = f'Source NAT66 configuration error in rule {rule}:'
- if 'outbound_interface' not in config:
- raise ConfigError(f'{err_msg} outbound-interface not specified')
- if config['outbound_interface'] not in interfaces():
- raise ConfigError(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ if 'outbound_interface' in config:
+ if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']:
+ raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for nat source rule "{rule}"')
+ elif 'name' in config['outbound_interface']:
+ if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces():
+ Warning(f'{err_msg} - interface "{config["outbound_interface"]["name"]}" does not exist on this system')
addr = dict_search('translation.address', config)
if addr != None:
@@ -85,12 +87,12 @@ def verify(nat):
for rule, config in dict_search('destination.rule', nat).items():
err_msg = f'Destination NAT66 configuration error in rule {rule}:'
- if 'inbound_interface' not in config:
- raise ConfigError(f'{err_msg}\n' \
- 'inbound-interface not specified')
- else:
- if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ if 'inbound_interface' in config:
+ if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']:
+ raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for destination nat rule "{rule}"')
+ elif 'name' in config['inbound_interface']:
+ if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces():
+ Warning(f'{err_msg} - interface "{config["inbound_interface"]["name"]}" does not exist on this system')
return None
diff --git a/src/migration-scripts/cluster/1-to-2 b/src/migration-scripts/cluster/1-to-2
new file mode 100755
index 000000000..a2e589155
--- /dev/null
+++ b/src/migration-scripts/cluster/1-to-2
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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
+
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+ file_name = sys.argv[1]
+
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+
+ if not config.exists(['cluster']):
+ # Cluster is not set -- nothing to do at all
+ sys.exit(0)
+
+ # If at least one cluster group is defined, we have real work to do.
+ # If there are no groups, we remove the top-level cluster node at the end of this script anyway.
+ if config.exists(['cluster', 'group']):
+ # First, gather timer and interface settings to duplicate them in all groups,
+ # since in the old cluster they are global, but in VRRP they are always per-group
+
+ global_interface = None
+ if config.exists(['cluster', 'interface']):
+ global_interface = config.return_value(['cluster', 'interface'])
+ else:
+ # Such configs shouldn't exist in practice because interface is a required option.
+ # But since it's possible to specify interface inside 'service' options,
+ # we may be able to convert such configs nonetheless.
+ print("Warning: incorrect cluster config: interface is not defined.", file=sys.stderr)
+
+ # There are three timers: advertise-interval, dead-interval, and monitor-dead-interval
+ # Only the first one makes sense for the VRRP, we translate it to advertise-interval
+ advertise_interval = None
+ if config.exists(['cluster', 'keepalive-interval']):
+ advertise_interval = config.return_value(['cluster', 'keepalive-interval'])
+
+ if advertise_interval is not None:
+ # Cluster had all timers in milliseconds, so we need to convert them to seconds
+ # And ensure they are not shorter than one second
+ advertise_interval = int(advertise_interval) // 1000
+ if advertise_interval < 1:
+ advertise_interval = 1
+
+ # Cluster had password as a global option, in VRRP it's per-group
+ password = None
+ if config.exists(['cluster', 'pre-shared-secret']):
+ password = config.return_value(['cluster', 'pre-shared-secret'])
+
+ # Set up the stage for converting cluster groups to VRRP groups
+ free_vrids = set(range(1,255))
+ vrrp_base_path = ['high-availability', 'vrrp', 'group']
+ if not config.exists(vrrp_base_path):
+ # If VRRP is not set up, create a node and set it to 'tag node'
+ # Setting it to 'tag' is not mandatory but it's better to be consistent
+ # with configs produced by 'save'
+ config.set(vrrp_base_path)
+ config.set_tag(vrrp_base_path)
+ else:
+ # If there are VRRP groups already, we need to find the set of unused VRID numbers to avoid conflicts
+ existing_vrids = set()
+ for vg in config.list_nodes(vrrp_base_path):
+ existing_vrids.add(int(config.return_value(vrrp_base_path + [vg, 'vrid'])))
+ free_vrids = free_vrids.difference(existing_vrids)
+
+ # Now handle cluster groups
+ groups = config.list_nodes(['cluster', 'group'])
+ for g in groups:
+ base_path = ['cluster', 'group', g]
+ service_names = config.return_values(base_path + ['service'])
+
+ # Cluster used to allow services other than IP addresses, at least nominally
+ # Whether that ever worked is a big question, but we need to consider that,
+ # since configs with custom services are definitely impossible to meaningfully migrate now
+ services = {"ip": [], "other": []}
+ for s in service_names:
+ if re.match(r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2})(/[a-z]+\d+)?$', s):
+ services["ip"].append(s)
+ else:
+ services["other"].append(s)
+
+ if services["other"]:
+ print("Cluster config includes non-IP address services and cannot be migrated", file=sys.stderr)
+ sys.exit(1)
+
+ # Cluster allowed virtual IPs for different interfaces within a single group.
+ # VRRP groups are by definition bound to interfaces, so we cannot migrate such configurations.
+ # Thus we need to find out if all addresses either leave the interface unspecified
+ # (in that case the global 'cluster interface' option is used),
+ # or have the same interface, or have the same interface as the global 'cluster interface'.
+
+ # First, we collect all addresses and check if they have interface specified
+ # If not, we substitute the global interface option
+ # or throw an error if it's not in the config.
+ ips = []
+ for ip in services["ip"]:
+ ip_with_intf = re.match(r'^(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/(?P<intf>[a-z]+\d+)$', ip)
+ if ip_with_intf:
+ ips.append({"ip": ip_with_intf.group("ip"), "interface": ip_with_intf.group("intf")})
+ else:
+ if global_interface is not None:
+ ips.append({"ip": ip, "interface": global_interface})
+ else:
+ print("Error: cluster has groups with IPs without interfaces and 'cluster interface' is not specified.", file=sys.stderr)
+ sys.exit(1)
+
+ # Then we check if all addresses are for the same interface.
+ intfs_set = set(map(lambda i: i["interface"], ips))
+ if len(intfs_set) > 1:
+ print("Error: cluster group has addresses for different interfaces", file=sys.stderr)
+ sys.exit(1)
+
+ # If we got this far, the group is migratable.
+
+ # Extract the interface from the set -- we know there's only a single member.
+ interface = intfs_set.pop()
+
+ addresses = list(map(lambda i: i["ip"], ips))
+ vrrp_path = ['high-availability', 'vrrp', 'group', g]
+
+ # If there's already a VRRP group with exactly the same name,
+ # we probably shouldn't try to make up a unique name, just leave migration to the user...
+ if config.exists(vrrp_path):
+ print("Error: VRRP group with the same name as a cluster group already exists", file=sys.stderr)
+ sys.exit(1)
+
+ config.set(vrrp_path + ['interface'], value=interface)
+ for a in addresses:
+ config.set(vrrp_path + ['virtual-address'], value=a, replace=False)
+
+ # Take the next free VRID and assign it to the group
+ vrid = free_vrids.pop()
+ config.set(vrrp_path + ['vrid'], value=vrid)
+
+ # Convert the monitor option to VRRP ping health check
+ if config.exists(base_path + ['monitor']):
+ monitor_ip = config.return_value(base_path + ['monitor'])
+ config.set(vrrp_path + ['health-check', 'ping'], value=monitor_ip)
+
+ # Convert "auto-failback" to "no-preempt", if necessary
+ if config.exists(base_path + ['auto-failback']):
+ # It's a boolean node that requires "true" or "false"
+ # so if it exists we still need to check its value
+ auto_failback = config.return_value(base_path + ['auto-failback'])
+ if auto_failback == "false":
+ config.set(vrrp_path + ['no-preempt'])
+ else:
+ # It's "true" or we assume it is, which means preemption is desired,
+ # and in VRRP config it's the default
+ pass
+ else:
+ # The old default for that option is false
+ config.set(vrrp_path + ['no-preempt'])
+
+ # Inject settings from the global cluster config that have to be per-group in VRRP
+ if advertise_interval is not None:
+ config.set(vrrp_path + ['advertise-interval'], value=advertise_interval)
+
+ if password is not None:
+ config.set(vrrp_path + ['authentication', 'password'], value=password)
+ config.set(vrrp_path + ['authentication', 'type'], value='plaintext-password')
+
+ # Finally, clean up the old cluster node
+ config.delete(['cluster'])
+
+ 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))
+ sys.exit(1)
diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11
index 716c5a240..b739fb139 100755
--- a/src/migration-scripts/firewall/10-to-11
+++ b/src/migration-scripts/firewall/10-to-11
@@ -181,191 +181,6 @@ if config.exists(base + ['interface']):
config.delete(base + ['interface'])
-
-### Migration of zones:
-### User interface groups
-if config.exists(base + ['zone']):
- inp_ipv4_rule = 101
- inp_ipv6_rule = 101
- fwd_ipv4_rule = 101
- fwd_ipv6_rule = 101
- out_ipv4_rule = 101
- out_ipv6_rule = 101
- local_zone = 'False'
-
- for zone in config.list_nodes(base + ['zone']):
- if config.exists(base + ['zone', zone, 'local-zone']):
- local_zone = 'True'
- # Add default-action== accept for compatibility reasons:
- config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept')
- config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept')
- config.set(base + ['ipv4', 'output', 'filter', 'default-action'], value='accept')
- config.set(base + ['ipv6', 'output', 'filter', 'default-action'], value='accept')
- for from_zone in config.list_nodes(base + ['zone', zone, 'from']):
- group_name = 'IG_' + from_zone
- if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']):
- # ipv4 input ruleset
- target_ipv4_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'name'])
- config.set(base + ['ipv4', 'input', 'filter', 'rule'])
- config.set_tag(base + ['ipv4', 'input', 'filter', 'rule'])
- config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'inbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'action'], value='jump')
- config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'jump-target'], value=target_ipv4_chain)
- inp_ipv4_rule = inp_ipv4_rule + 5
- if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']):
- # ipv6 input ruleset
- target_ipv6_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name'])
- config.set(base + ['ipv6', 'input', 'filter', 'rule'])
- config.set_tag(base + ['ipv6', 'input', 'filter', 'rule'])
- config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'inbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'action'], value='jump')
- config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'jump-target'], value=target_ipv6_chain)
- inp_ipv6_rule = inp_ipv6_rule + 5
-
- # Migrate: set firewall zone <zone> default-action <action>
- # Options: drop or reject. If not specified, is drop
- if config.exists(base + ['zone', zone, 'default-action']):
- local_def_action = config.return_value(base + ['zone', zone, 'default-action'])
- else:
- local_def_action = 'drop'
- config.set(base + ['ipv4', 'input', 'filter', 'rule'])
- config.set_tag(base + ['ipv4', 'input', 'filter', 'rule'])
- config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'action'], value=local_def_action)
- config.set(base + ['ipv6', 'input', 'filter', 'rule'])
- config.set_tag(base + ['ipv6', 'input', 'filter', 'rule'])
- config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'action'], value=local_def_action)
- if config.exists(base + ['zone', zone, 'enable-default-log']):
- config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'log'], value='enable')
- config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'log'], value='enable')
-
- else:
- # It's not a local zone
- group_name = 'IG_' + zone
- # Add default-action== accept for compatibility reasons:
- config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept')
- config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept')
- # intra-filtering migration. By default accept
- intra_zone_ipv4_action = 'accept'
- intra_zone_ipv6_action = 'accept'
-
- if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'action']):
- intra_zone_ipv4_action = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'action'])
- intra_zone_ipv6_action = intra_zone_ipv4_action
- else:
- if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']):
- intra_zone_ipv4_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name'])
- intra_zone_ipv4_action = 'jump'
- if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']):
- intra_zone_ipv6_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name'])
- intra_zone_ipv6_action = 'jump'
- config.set(base + ['ipv4', 'forward', 'filter', 'rule'])
- config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule'])
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'inbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value=intra_zone_ipv4_action)
- config.set(base + ['ipv6', 'forward', 'filter', 'rule'])
- config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule'])
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'inbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value=intra_zone_ipv6_action)
- if intra_zone_ipv4_action == 'jump':
- if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']):
- intra_zone_ipv4_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name'])
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'jump-target'], value=intra_zone_ipv4_target)
- if intra_zone_ipv6_action == 'jump':
- if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']):
- intra_zone_ipv6_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name'])
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'jump-target'], value=intra_zone_ipv6_target)
- fwd_ipv4_rule = fwd_ipv4_rule + 5
- fwd_ipv6_rule = fwd_ipv6_rule + 5
-
- if config.exists(base + ['zone', zone, 'interface']):
- # Create interface group IG_<zone>
- group_name = 'IG_' + zone
- config.set(base + ['group', 'interface-group'], value=group_name)
- config.set_tag(base + ['group', 'interface-group'])
- for iface in config.return_values(base + ['zone', zone, 'interface']):
- config.set(base + ['group', 'interface-group', group_name, 'interface'], value=iface, replace=False)
-
- if config.exists(base + ['zone', zone, 'from']):
- for from_zone in config.list_nodes(base + ['zone', zone, 'from']):
- from_group = 'IG_' + from_zone
- if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']):
- target_ipv4_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'name'])
- if config.exists(base + ['zone', from_zone, 'local-zone']):
- # It's from LOCAL zone -> Output filtering
- config.set(base + ['ipv4', 'output', 'filter', 'rule'])
- config.set_tag(base + ['ipv4', 'output', 'filter', 'rule'])
- config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'action'], value='jump')
- config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'jump-target'], value=target_ipv4_chain)
- out_ipv4_rule = out_ipv4_rule + 5
- else:
- # It's not LOCAL zone -> forward filtering
- config.set(base + ['ipv4', 'forward', 'filter', 'rule'])
- config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule'])
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'inbound-interface', 'interface-group'], value=from_group)
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value='jump')
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'jump-target'], value=target_ipv4_chain)
- fwd_ipv4_rule = fwd_ipv4_rule + 5
- if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']):
- target_ipv6_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name'])
- if config.exists(base + ['zone', from_zone, 'local-zone']):
- # It's from LOCAL zone -> Output filtering
- config.set(base + ['ipv6', 'output', 'filter', 'rule'])
- config.set_tag(base + ['ipv6', 'output', 'filter', 'rule'])
- config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'action'], value='jump')
- config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'jump-target'], value=target_ipv6_chain)
- out_ipv6_rule = out_ipv6_rule + 5
- else:
- # It's not LOCAL zone -> forward filtering
- config.set(base + ['ipv6', 'forward', 'filter', 'rule'])
- config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule'])
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'inbound-interface', 'interface-group'], value=from_group)
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value='jump')
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'jump-target'], value=target_ipv6_chain)
- fwd_ipv6_rule = fwd_ipv6_rule + 5
-
- ## Now need to migrate: set firewall zone <zone> default-action <action> # action=drop if not specified.
- if config.exists(base + ['zone', zone, 'default-action']):
- def_action = config.return_value(base + ['zone', zone, 'default-action'])
- else:
- def_action = 'drop'
- config.set(base + ['ipv4', 'forward', 'filter', 'rule'])
- config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule'])
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value=def_action)
- description = 'zone_' + zone + ' default-action'
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'description'], value=description)
- config.set(base + ['ipv6', 'forward', 'filter', 'rule'])
- config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule'])
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name)
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value=def_action)
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'description'], value=description)
-
- if config.exists(base + ['zone', zone, 'enable-default-log']):
- config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'log'], value='enable')
- config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'log'], value='enable')
- fwd_ipv4_rule = fwd_ipv4_rule + 5
- fwd_ipv6_rule = fwd_ipv6_rule + 5
-
- # Migrate default-action (force to be drop in output chain) if local zone is defined
- if local_zone == 'True':
- # General drop in output change if needed
- config.set(base + ['ipv4', 'output', 'filter', 'rule'])
- config.set_tag(base + ['ipv4', 'output', 'filter', 'rule'])
- config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'action'], value=local_def_action)
- config.set(base + ['ipv6', 'output', 'filter', 'rule'])
- config.set_tag(base + ['ipv6', 'output', 'filter', 'rule'])
- config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'action'], value=local_def_action)
-
- config.delete(base + ['zone'])
-
-###### END migration zones
-
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
diff --git a/src/migration-scripts/firewall/11-to-12 b/src/migration-scripts/firewall/11-to-12
new file mode 100755
index 000000000..51b2fa860
--- /dev/null
+++ b/src/migration-scripts/firewall/11-to-12
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# T5681: Firewall re-writing. Simplify cli when mathcing interface
+# From
+ # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-name <iface>
+ # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-group <iface_group>
+# To
+ # set firewall ... rule <rule> [inbound-interface | outboubd-interface] name <iface>
+ # set firewall ... rule <rule> [inbound-interface | outboubd-interface] group <iface_group>
+
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['firewall']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+## FORT
+## Migration from base chains
+#if config.exists(base + ['interface', iface, direction]):
+for family in ['ipv4', 'ipv6']:
+ if config.exists(base + [family]):
+ for hook in ['forward', 'input', 'output', 'name']:
+ if config.exists(base + [family, hook]):
+ for priority in config.list_nodes(base + [family, hook]):
+ if config.exists(base + [family, hook, priority, 'rule']):
+ for rule in config.list_nodes(base + [family, hook, priority, 'rule']):
+ for direction in ['inbound-interface', 'outbound-interface']:
+ if config.exists(base + [family, hook, priority, 'rule', rule, direction]):
+ if config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']):
+ iface = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-name'])
+ config.set(base + [family, hook, priority, 'rule', rule, direction, 'name'], value=iface)
+ config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-name'])
+ elif config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']):
+ group = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-group'])
+ config.set(base + [family, hook, priority, 'rule', rule, direction, 'group'], value=group)
+ config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-group'])
+
+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) \ No newline at end of file
diff --git a/src/migration-scripts/interfaces/31-to-32 b/src/migration-scripts/interfaces/31-to-32
new file mode 100755
index 000000000..ca3d19320
--- /dev/null
+++ b/src/migration-scripts/interfaces/31-to-32
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+#
+# T5671: change port to IANA assigned default port
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['interfaces', 'vxlan']
+
+config = ConfigTree(config_file)
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+for vxlan in config.list_nodes(base):
+ if config.exists(base + [vxlan, 'external']):
+ config.delete(base + [vxlan, 'external'])
+ config.set(base + [vxlan, 'parameters', 'external'])
+
+ if not config.exists(base + [vxlan, 'port']):
+ config.set(base + [vxlan, 'port'], value='8472')
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/nat/5-to-6 b/src/migration-scripts/nat/5-to-6
new file mode 100755
index 000000000..de3830582
--- /dev/null
+++ b/src/migration-scripts/nat/5-to-6
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# T5643: move from 'set nat [source|destination] rule X [inbound-interface|outbound interface] <iface>'
+# to
+# 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>'
+
+from sys import argv,exit
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['nat']):
+ # Nothing to do
+ exit(0)
+
+for direction in ['source', 'destination']:
+ # If a node doesn't exist, we obviously have nothing to do.
+ if not config.exists(['nat', direction]):
+ continue
+
+ # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist,
+ # but there are no rules under it.
+ if not config.list_nodes(['nat', direction]):
+ continue
+
+ for rule in config.list_nodes(['nat', direction, 'rule']):
+ base = ['nat', direction, 'rule', rule]
+ for iface in ['inbound-interface','outbound-interface']:
+ if config.exists(base + [iface]):
+ tmp = config.return_value(base + [iface])
+ config.delete(base + [iface])
+ config.set(base + [iface, 'interface-name'], value=tmp)
+
+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/migration-scripts/nat/6-to-7 b/src/migration-scripts/nat/6-to-7
new file mode 100755
index 000000000..b5f6328ef
--- /dev/null
+++ b/src/migration-scripts/nat/6-to-7
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# T5681: Firewall re-writing. Simplify cli when mathcing interface
+# From
+# 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>'
+# 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-group <iface_group>'
+# to
+# 'set nat [source|destination] rule X [inbound-interface|outbound interface] name <iface>'
+# 'set nat [source|destination] rule X [inbound-interface|outbound interface] group <iface_group>'
+
+from sys import argv,exit
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['nat']):
+ # Nothing to do
+ exit(0)
+
+for direction in ['source', 'destination']:
+ # If a node doesn't exist, we obviously have nothing to do.
+ if not config.exists(['nat', direction]):
+ continue
+
+ # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist,
+ # but there are no rules under it.
+ if not config.list_nodes(['nat', direction]):
+ continue
+
+ for rule in config.list_nodes(['nat', direction, 'rule']):
+ base = ['nat', direction, 'rule', rule]
+ for iface in ['inbound-interface','outbound-interface']:
+ if config.exists(base + [iface]):
+ if config.exists(base + [iface, 'interface-name']):
+ tmp = config.return_value(base + [iface, 'interface-name'])
+ config.delete(base + [iface, 'interface-name'])
+ config.set(base + [iface, 'name'], value=tmp)
+
+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/migration-scripts/nat66/1-to-2 b/src/migration-scripts/nat66/1-to-2
new file mode 100755
index 000000000..b7d4e3f6b
--- /dev/null
+++ b/src/migration-scripts/nat66/1-to-2
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# T5681: Firewall re-writing. Simplify cli when mathcing interface
+# From
+# 'set nat66 [source|destination] rule X [inbound-interface|outbound interface] <iface>'
+# to
+# 'set nat66 [source|destination] rule X [inbound-interface|outbound interface] name <iface>'
+
+from sys import argv,exit
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+if not config.exists(['nat66']):
+ # Nothing to do
+ exit(0)
+
+for direction in ['source', 'destination']:
+ # If a node doesn't exist, we obviously have nothing to do.
+ if not config.exists(['nat66', direction]):
+ continue
+
+ # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist,
+ # but there are no rules under it.
+ if not config.list_nodes(['nat66', direction]):
+ continue
+
+ for rule in config.list_nodes(['nat66', direction, 'rule']):
+ base = ['nat66', direction, 'rule', rule]
+ for iface in ['inbound-interface','outbound-interface']:
+ if config.exists(base + [iface]):
+ tmp = config.return_value(base + [iface])
+ config.delete(base + [iface])
+ config.set(base + [iface, 'name'], value=tmp)
+
+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/firewall.py b/src/op_mode/firewall.py
index 3434707ec..20f54b9ba 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2023 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
@@ -24,19 +24,28 @@ from vyos.config import Config
from vyos.utils.process import cmd
from vyos.utils.dict import dict_search_args
-def get_config_firewall(conf, family=None, hook=None, priority=None):
- config_path = ['firewall']
- if family:
- config_path += [family]
- if hook:
- config_path += [hook]
- if priority:
- config_path += [priority]
+def get_config_node(conf, node=None, family=None, hook=None, priority=None):
+ if node == 'nat':
+ if family == 'ipv6':
+ config_path = ['nat66']
+ else:
+ config_path = ['nat']
- firewall = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ elif node == 'policy':
+ config_path = ['policy']
+ else:
+ config_path = ['firewall']
+ if family:
+ config_path += [family]
+ if hook:
+ config_path += [hook]
+ if priority:
+ config_path += [priority]
+
+ node_config = conf.get_config_dict(config_path, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
- return firewall
+ return node_config
def get_nftables_details(family, hook, priority):
if family == 'ipv6':
@@ -102,7 +111,15 @@ def output_firewall_name(family, hook, priority, firewall_conf, single_rule_id=N
row.append(rule_details['conditions'])
rows.append(row)
- if 'default_action' in firewall_conf and not single_rule_id:
+ if hook in ['input', 'forward', 'output']:
+ def_action = firewall_conf['default_action'] if 'default_action' in firewall_conf else 'accept'
+ row = ['default', def_action, 'all']
+ rule_details = details['default-action']
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ rows.append(row)
+
+ elif 'default_action' in firewall_conf and not single_rule_id:
row = ['default', firewall_conf['default_action'], 'all']
if 'default-action' in details:
rule_details = details['default-action']
@@ -167,16 +184,16 @@ def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule
dest_addr = 'any'
# Get inbound interface
- iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_name')
+ iiface = dict_search_args(rule_conf, 'inbound_interface', 'name')
if not iiface:
- iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group')
+ iiface = dict_search_args(rule_conf, 'inbound_interface', 'group')
if not iiface:
iiface = 'any'
# Get outbound interface
- oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_name')
+ oiface = dict_search_args(rule_conf, 'outbound_interface', 'name')
if not oiface:
- oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group')
+ oiface = dict_search_args(rule_conf, 'outbound_interface', 'group')
if not oiface:
oiface = 'any'
@@ -198,8 +215,9 @@ def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule
if hook in ['input', 'forward', 'output']:
row = ['default']
- row.append('N/A')
- row.append('N/A')
+ rule_details = details['default-action']
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
if 'default_action' in prior_conf:
row.append(prior_conf['default_action'])
else:
@@ -234,7 +252,7 @@ def show_firewall():
print('Rulesets Information')
conf = Config()
- firewall = get_config_firewall(conf)
+ firewall = get_config_node(conf)
if not firewall:
return
@@ -249,7 +267,7 @@ def show_firewall_family(family):
print(f'Rulesets {family} Information')
conf = Config()
- firewall = get_config_firewall(conf)
+ firewall = get_config_node(conf)
if not firewall or family not in firewall:
return
@@ -262,7 +280,7 @@ def show_firewall_name(family, hook, priority):
print('Ruleset Information')
conf = Config()
- firewall = get_config_firewall(conf, family, hook, priority)
+ firewall = get_config_node(conf, 'firewall', family, hook, priority)
if firewall:
output_firewall_name(family, hook, priority, firewall)
@@ -270,17 +288,20 @@ def show_firewall_rule(family, hook, priority, rule_id):
print('Rule Information')
conf = Config()
- firewall = get_config_firewall(conf, family, hook, priority)
+ firewall = get_config_node(conf, 'firewall', family, hook, priority)
if firewall:
output_firewall_name(family, hook, priority, firewall, rule_id)
def show_firewall_group(name=None):
conf = Config()
- firewall = get_config_firewall(conf)
+ firewall = get_config_node(conf, node='firewall')
if 'group' not in firewall:
return
+ nat = get_config_node(conf, node='nat')
+ policy = get_config_node(conf, node='policy')
+
def find_references(group_type, group_name):
out = []
family = []
@@ -296,6 +317,7 @@ def show_firewall_group(name=None):
family = ['ipv4', 'ipv6']
for item in family:
+ # Look references in firewall
for name_type in ['name', 'ipv6_name', 'forward', 'input', 'output']:
if item in firewall:
if name_type not in firewall[item]:
@@ -308,8 +330,8 @@ def show_firewall_group(name=None):
for rule_id, rule_conf in priority_conf['rule'].items():
source_group = dict_search_args(rule_conf, 'source', 'group', group_type)
dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type)
- in_interface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group')
- out_interface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group')
+ in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group')
+ out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group')
if source_group:
if source_group[0] == "!":
source_group = source_group[1:]
@@ -330,6 +352,76 @@ def show_firewall_group(name=None):
out_interface = out_interface[1:]
if group_name == out_interface:
out.append(f'{item}-{name_type}-{priority}-{rule_id}')
+
+ # Look references in route | route6
+ for name_type in ['route', 'route6']:
+ if name_type not in policy:
+ continue
+ if name_type == 'route' and item == 'ipv6':
+ continue
+ elif name_type == 'route6' and item == 'ipv4':
+ continue
+ else:
+ for policy_name, policy_conf in policy[name_type].items():
+ if 'rule' not in policy_conf:
+ continue
+ for rule_id, rule_conf in policy_conf['rule'].items():
+ source_group = dict_search_args(rule_conf, 'source', 'group', group_type)
+ dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type)
+ in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group')
+ out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group')
+ if source_group:
+ if source_group[0] == "!":
+ source_group = source_group[1:]
+ if group_name == source_group:
+ out.append(f'{name_type}-{policy_name}-{rule_id}')
+ if dest_group:
+ if dest_group[0] == "!":
+ dest_group = dest_group[1:]
+ if group_name == dest_group:
+ out.append(f'{name_type}-{policy_name}-{rule_id}')
+ if in_interface:
+ if in_interface[0] == "!":
+ in_interface = in_interface[1:]
+ if group_name == in_interface:
+ out.append(f'{name_type}-{policy_name}-{rule_id}')
+ if out_interface:
+ if out_interface[0] == "!":
+ out_interface = out_interface[1:]
+ if group_name == out_interface:
+ out.append(f'{name_type}-{policy_name}-{rule_id}')
+
+ ## Look references in nat table
+ for direction in ['source', 'destination']:
+ if direction in nat:
+ if 'rule' not in nat[direction]:
+ continue
+ for rule_id, rule_conf in nat[direction]['rule'].items():
+ source_group = dict_search_args(rule_conf, 'source', 'group', group_type)
+ dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type)
+ in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group')
+ out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group')
+ if source_group:
+ if source_group[0] == "!":
+ source_group = source_group[1:]
+ if group_name == source_group:
+ out.append(f'nat-{direction}-{rule_id}')
+ if dest_group:
+ if dest_group[0] == "!":
+ dest_group = dest_group[1:]
+ if group_name == dest_group:
+ out.append(f'nat-{direction}-{rule_id}')
+ if in_interface:
+ if in_interface[0] == "!":
+ in_interface = in_interface[1:]
+ if group_name == in_interface:
+ out.append(f'nat-{direction}-{rule_id}')
+ if out_interface:
+ if out_interface[0] == "!":
+ out_interface = out_interface[1:]
+ if group_name == out_interface:
+ out.append(f'nat-{direction}-{rule_id}')
+
return out
header = ['Name', 'Type', 'References', 'Members']
@@ -356,6 +448,7 @@ def show_firewall_group(name=None):
row.append('N/D')
rows.append(row)
+
if rows:
print('Firewall Groups\n')
print(tabulate.tabulate(rows, header))
@@ -364,7 +457,7 @@ def show_summary():
print('Ruleset Summary')
conf = Config()
- firewall = get_config_firewall(conf)
+ firewall = get_config_node(conf)
if not firewall:
return
@@ -410,7 +503,7 @@ def show_statistics():
print('Rulesets Statistics')
conf = Config()
- firewall = get_config_firewall(conf)
+ firewall = get_config_node(conf)
if not firewall:
return
diff --git a/src/op_mode/generate_tech-support_archive.py b/src/op_mode/generate_tech-support_archive.py
index 23d81f986..c490b0137 100755
--- a/src/op_mode/generate_tech-support_archive.py
+++ b/src/op_mode/generate_tech-support_archive.py
@@ -100,7 +100,7 @@ if __name__ == '__main__':
location_path = args.path[:-1] if args.path[-1] == '/' else args.path
hostname: str = gethostname()
- time_now: str = datetime.now().isoformat(timespec='seconds')
+ time_now: str = datetime.now().isoformat(timespec='seconds').replace(":", "-")
remote = False
tmp_path = ''
@@ -145,4 +145,4 @@ if __name__ == '__main__':
rmtree(tmp_dir)
finally:
# cleanup
- exit() \ No newline at end of file
+ exit()
diff --git a/src/op_mode/interfaces_wireless.py b/src/op_mode/interfaces_wireless.py
new file mode 100755
index 000000000..dfe50e2cb
--- /dev/null
+++ b/src/op_mode/interfaces_wireless.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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 typing
+import vyos.opmode
+
+from copy import deepcopy
+from tabulate import tabulate
+from vyos.utils.process import popen
+from vyos.configquery import ConfigTreeQuery
+
+def _verify(func):
+ """Decorator checks if Wireless LAN config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ if not config.exists(['interfaces', 'wireless']):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+ return _wrapper
+
+def _get_raw_info_data():
+ output_data = []
+
+ config = ConfigTreeQuery()
+ raw = config.get_config_dict(['interfaces', 'wireless'], effective=True,
+ get_first_key=True, key_mangling=('-', '_'))
+ for interface, interface_config in raw.items():
+ tmp = {'name' : interface}
+
+ if 'type' in interface_config:
+ tmp.update({'type' : interface_config['type']})
+ else:
+ tmp.update({'type' : '-'})
+
+ if 'ssid' in interface_config:
+ tmp.update({'ssid' : interface_config['ssid']})
+ else:
+ tmp.update({'ssid' : '-'})
+
+ if 'channel' in interface_config:
+ tmp.update({'channel' : interface_config['channel']})
+ else:
+ tmp.update({'channel' : '-'})
+
+ output_data.append(tmp)
+
+ return output_data
+
+def _get_formatted_info_output(raw_data):
+ output=[]
+ for ssid in raw_data:
+ output.append([ssid['name'], ssid['type'], ssid['ssid'], ssid['channel']])
+
+ headers = ["Interface", "Type", "SSID", "Channel"]
+ print(tabulate(output, headers, numalign="left"))
+
+def _get_raw_scan_data(intf_name):
+ # XXX: This ignores errors
+ tmp, _ = popen(f'iw dev {intf_name} scan ap-force')
+ networks = []
+ data = {
+ 'ssid': '',
+ 'mac': '',
+ 'channel': '',
+ 'signal': ''
+ }
+ re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})')
+ for line in tmp.splitlines():
+ if line.startswith('BSS '):
+ ssid = deepcopy(data)
+ ssid['mac'] = re.search(re_mac, line).group()
+
+ elif line.lstrip().startswith('SSID: '):
+ # SSID can be " SSID: WLAN-57 6405", thus strip all leading whitespaces
+ ssid['ssid'] = line.lstrip().split(':')[-1].lstrip()
+
+ elif line.lstrip().startswith('signal: '):
+ # Siganl can be " signal: -67.00 dBm", thus strip all leading whitespaces
+ ssid['signal'] = line.lstrip().split(':')[-1].split()[0]
+
+ elif line.lstrip().startswith('DS Parameter set: channel'):
+ # Channel can be " DS Parameter set: channel 6" , thus
+ # strip all leading whitespaces
+ ssid['channel'] = line.lstrip().split(':')[-1].split()[-1]
+ networks.append(ssid)
+ continue
+
+ return networks
+
+def _format_scan_data(raw_data):
+ output=[]
+ for ssid in raw_data:
+ output.append([ssid['mac'], ssid['ssid'], ssid['channel'], ssid['signal']])
+ headers = ["Address", "SSID", "Channel", "Signal (dbm)"]
+ return tabulate(output, headers, numalign="left")
+
+def _get_raw_station_data(intf_name):
+ # XXX: This ignores errors
+ tmp, _ = popen(f'iw dev {intf_name} station dump')
+ clients = []
+ data = {
+ 'mac': '',
+ 'signal': '',
+ 'rx_bytes': '',
+ 'rx_packets': '',
+ 'tx_bytes': '',
+ 'tx_packets': ''
+ }
+ re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})')
+ for line in tmp.splitlines():
+ if line.startswith('Station'):
+ client = deepcopy(data)
+ client['mac'] = re.search(re_mac, line).group()
+
+ elif line.lstrip().startswith('signal avg:'):
+ client['signal'] = line.lstrip().split(':')[-1].lstrip().split()[0]
+
+ elif line.lstrip().startswith('rx bytes:'):
+ client['rx_bytes'] = line.lstrip().split(':')[-1].lstrip()
+
+ elif line.lstrip().startswith('rx packets:'):
+ client['rx_packets'] = line.lstrip().split(':')[-1].lstrip()
+
+ elif line.lstrip().startswith('tx bytes:'):
+ client['tx_bytes'] = line.lstrip().split(':')[-1].lstrip()
+
+ elif line.lstrip().startswith('tx packets:'):
+ client['tx_packets'] = line.lstrip().split(':')[-1].lstrip()
+ clients.append(client)
+ continue
+
+ return clients
+
+def _format_station_data(raw_data):
+ output=[]
+ for ssid in raw_data:
+ output.append([ssid['mac'], ssid['signal'], ssid['rx_bytes'], ssid['rx_packets'], ssid['tx_bytes'], ssid['tx_packets']])
+ headers = ["Station", "Signal", "RX bytes", "RX packets", "TX bytes", "TX packets"]
+ return tabulate(output, headers, numalign="left")
+
+@_verify
+def show_info(raw: bool):
+ info_data = _get_raw_info_data()
+ if raw:
+ return info_data
+ return _get_formatted_info_output(info_data)
+
+def show_scan(raw: bool, intf_name: str):
+ data = _get_raw_scan_data(intf_name)
+ if raw:
+ return data
+ return _format_scan_data(data)
+
+@_verify
+def show_stations(raw: bool, intf_name: str):
+ data = _get_raw_station_data(intf_name)
+ if raw:
+ return data
+ return _format_station_data(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/lldp.py b/src/op_mode/lldp.py
index c287b8fa6..58cfce443 100755
--- a/src/op_mode/lldp.py
+++ b/src/op_mode/lldp.py
@@ -114,7 +114,10 @@ def _get_formatted_output(raw_data):
# Remote software platform
platform = jmespath.search('chassis.[*][0][0].descr', values)
- tmp.append(platform[:37])
+ if platform:
+ tmp.append(platform[:37])
+ else:
+ tmp.append('')
# Remote interface
interface = jmespath.search('port.descr', values)
diff --git a/src/op_mode/show-ssh-fingerprints.py b/src/op_mode/show-ssh-fingerprints.py
deleted file mode 100644
index 913baae46..000000000
--- a/src/op_mode/show-ssh-fingerprints.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-
-import sys
-import glob
-import argparse
-from vyos.utils.process import cmd
-
-# Parse command line
-parser = argparse.ArgumentParser()
-parser.add_argument("--ascii", help="Show visual ASCII art representation of the public key", action="store_true")
-args = parser.parse_args()
-
-# Get list of server public keys
-publickeys = glob.glob("/etc/ssh/*.pub")
-
-if publickeys:
- print("SSH server public key fingerprints:\n", flush=True)
- for keyfile in publickeys:
- if args.ascii:
- try:
- print(cmd("ssh-keygen -l -v -E sha256 -f " + keyfile) + "\n", flush=True)
- # Ignore invalid public keys
- except:
- pass
- else:
- try:
- print(cmd("ssh-keygen -l -E sha256 -f " + keyfile) + "\n", flush=True)
- # Ignore invalid public keys
- except:
- pass
-else:
- print("No SSH server public keys are found.", flush=True)
-
-sys.exit(0)
diff --git a/src/op_mode/show_wireless.py b/src/op_mode/show_wireless.py
deleted file mode 100755
index 340163057..000000000
--- a/src/op_mode/show_wireless.py
+++ /dev/null
@@ -1,149 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019-2023 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import argparse
-import re
-
-from sys import exit
-from copy import deepcopy
-
-from vyos.config import Config
-from vyos.utils.process import popen
-
-parser = argparse.ArgumentParser()
-parser.add_argument("-s", "--scan", help="Scan for Wireless APs on given interface, e.g. 'wlan0'")
-parser.add_argument("-b", "--brief", action="store_true", help="Show wireless configuration")
-parser.add_argument("-c", "--stations", help="Show wireless clients connected on interface, e.g. 'wlan0'")
-
-def show_brief():
- config = Config()
- if len(config.list_effective_nodes('interfaces wireless')) == 0:
- print("No Wireless interfaces configured")
- exit(0)
-
- interfaces = []
- for intf in config.list_effective_nodes('interfaces wireless'):
- config.set_level(f'interfaces wireless {intf}')
- data = { 'name': intf }
- data['type'] = config.return_effective_value('type') or '-'
- data['ssid'] = config.return_effective_value('ssid') or '-'
- data['channel'] = config.return_effective_value('channel') or '-'
- interfaces.append(data)
-
- return interfaces
-
-def ssid_scan(intf):
- # XXX: This ignores errors
- tmp, _ = popen(f'/sbin/iw dev {intf} scan ap-force')
- networks = []
- data = {
- 'ssid': '',
- 'mac': '',
- 'channel': '',
- 'signal': ''
- }
- re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})')
- for line in tmp.splitlines():
- if line.startswith('BSS '):
- ssid = deepcopy(data)
- ssid['mac'] = re.search(re_mac, line).group()
-
- elif line.lstrip().startswith('SSID: '):
- # SSID can be " SSID: WLAN-57 6405", thus strip all leading whitespaces
- ssid['ssid'] = line.lstrip().split(':')[-1].lstrip()
-
- elif line.lstrip().startswith('signal: '):
- # Siganl can be " signal: -67.00 dBm", thus strip all leading whitespaces
- ssid['signal'] = line.lstrip().split(':')[-1].split()[0]
-
- elif line.lstrip().startswith('DS Parameter set: channel'):
- # Channel can be " DS Parameter set: channel 6" , thus
- # strip all leading whitespaces
- ssid['channel'] = line.lstrip().split(':')[-1].split()[-1]
- networks.append(ssid)
- continue
-
- return networks
-
-def show_clients(intf):
- # XXX: This ignores errors
- tmp, _ = popen(f'/sbin/iw dev {intf} station dump')
- clients = []
- data = {
- 'mac': '',
- 'signal': '',
- 'rx_bytes': '',
- 'rx_packets': '',
- 'tx_bytes': '',
- 'tx_packets': ''
- }
- re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})')
- for line in tmp.splitlines():
- if line.startswith('Station'):
- client = deepcopy(data)
- client['mac'] = re.search(re_mac, line).group()
-
- elif line.lstrip().startswith('signal avg:'):
- client['signal'] = line.lstrip().split(':')[-1].lstrip().split()[0]
-
- elif line.lstrip().startswith('rx bytes:'):
- client['rx_bytes'] = line.lstrip().split(':')[-1].lstrip()
-
- elif line.lstrip().startswith('rx packets:'):
- client['rx_packets'] = line.lstrip().split(':')[-1].lstrip()
-
- elif line.lstrip().startswith('tx bytes:'):
- client['tx_bytes'] = line.lstrip().split(':')[-1].lstrip()
-
- elif line.lstrip().startswith('tx packets:'):
- client['tx_packets'] = line.lstrip().split(':')[-1].lstrip()
- clients.append(client)
- continue
-
- return clients
-
-if __name__ == '__main__':
- args = parser.parse_args()
-
- if args.scan:
- print("Address SSID Channel Signal (dbm)")
- for network in ssid_scan(args.scan):
- print("{:<17} {:<32} {:>3} {}".format(network['mac'],
- network['ssid'],
- network['channel'],
- network['signal']))
- exit(0)
-
- elif args.brief:
- print("Interface Type SSID Channel")
- for intf in show_brief():
- print("{:<9} {:<12} {:<32} {:>3}".format(intf['name'],
- intf['type'],
- intf['ssid'],
- intf['channel']))
- exit(0)
-
- elif args.stations:
- print("Station Signal RX: bytes packets TX: bytes packets")
- for client in show_clients(args.stations):
- print("{:<17} {:>3} {:>15} {:>9} {:>15} {:>10} ".format(client['mac'],
- client['signal'], client['rx_bytes'], client['rx_packets'], client['tx_bytes'], client['tx_packets']))
-
- exit(0)
-
- else:
- parser.print_help()
- exit(1)
diff --git a/src/op_mode/ssh.py b/src/op_mode/ssh.py
new file mode 100755
index 000000000..102becc55
--- /dev/null
+++ b/src/op_mode/ssh.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+#
+# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import sys
+import glob
+import vyos.opmode
+from vyos.utils.process import cmd
+from vyos.configquery import ConfigTreeQuery
+from tabulate import tabulate
+
+def show_fingerprints(raw: bool, ascii: bool):
+ config = ConfigTreeQuery()
+ if not config.exists("service ssh"):
+ raise vyos.opmode.UnconfiguredSubsystem("SSH server is not enabled.")
+
+ publickeys = glob.glob("/etc/ssh/*.pub")
+
+ if publickeys:
+ keys = []
+ for keyfile in publickeys:
+ try:
+ if ascii:
+ keydata = cmd("ssh-keygen -l -v -E sha256 -f " + keyfile).splitlines()
+ else:
+ keydata = cmd("ssh-keygen -l -E sha256 -f " + keyfile).splitlines()
+ type = keydata[0].split(None)[-1].strip("()")
+ key_size = keydata[0].split(None)[0]
+ fingerprint = keydata[0].split(None)[1]
+ comment = keydata[0].split(None)[2:-1][0]
+ if ascii:
+ ascii_art = "\n".join(keydata[1:])
+ keys.append({"type": type, "key_size": key_size, "fingerprint": fingerprint, "comment": comment, "ascii_art": ascii_art})
+ else:
+ keys.append({"type": type, "key_size": key_size, "fingerprint": fingerprint, "comment": comment})
+ except:
+ # Ignore invalid public keys
+ pass
+ if raw:
+ return keys
+ else:
+ headers = {"type": "Type", "key_size": "Key Size", "fingerprint": "Fingerprint", "comment": "Comment", "ascii_art": "ASCII Art"}
+ output = "SSH server public key fingerprints:\n\n" + tabulate(keys, headers=headers, tablefmt="simple")
+ return output
+ else:
+ if raw:
+ return []
+ else:
+ return "No SSH server public keys are found."
+
+def show_dynamic_protection(raw: bool):
+ config = ConfigTreeQuery()
+ if not config.exists(['service', 'ssh', 'dynamic-protection']):
+ raise vyos.opmode.UnconfiguredSubsystem("SSH server dynamic-protection is not enabled.")
+
+ attackers = []
+ try:
+ # IPv4
+ attackers = attackers + json.loads(cmd("nft -j list set ip sshguard attackers"))["nftables"][1]["set"]["elem"]
+ except:
+ pass
+ try:
+ # IPv6
+ attackers = attackers + json.loads(cmd("nft -j list set ip6 sshguard attackers"))["nftables"][1]["set"]["elem"]
+ except:
+ pass
+ if attackers:
+ if raw:
+ return attackers
+ else:
+ output = "Blocked attackers:\n" + "\n".join(attackers)
+ return output
+ else:
+ if raw:
+ return []
+ else:
+ return "No blocked attackers."
+
+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/system/uacctd_stop.py b/src/system/uacctd_stop.py
index 7fbac0566..a1b57335b 100755
--- a/src/system/uacctd_stop.py
+++ b/src/system/uacctd_stop.py
@@ -21,7 +21,7 @@
# send some packets to pmacct to wake it up
from argparse import ArgumentParser
-from socket import socket
+from socket import socket, AF_INET, SOCK_DGRAM
from sys import exit
from time import sleep
@@ -42,11 +42,12 @@ def stop_process(pid: int, timeout: int) -> None:
uacctd.terminate()
# create a socket
- trigger = socket()
+ trigger = socket(AF_INET, SOCK_DGRAM)
first_cycle: bool = True
while uacctd.is_running() and timeout:
- trigger.sendto(b'WAKEUP', ('127.0.254.0', 0))
+ print('sending a packet to uacctd...')
+ trigger.sendto(b'WAKEUP', ('127.0.254.0', 1))
# do not sleep during first attempt
if not first_cycle:
sleep(1)