summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml20
-rw-r--r--data/config-mode-dependencies/vyos-1x.json (renamed from data/config-mode-dependencies.json)2
-rw-r--r--data/op-mode-standardized.json1
-rw-r--r--data/templates/conntrack/nftables-ct.j254
-rw-r--r--data/templates/dhcp-server/10-override.conf.j22
-rw-r--r--data/templates/firewall/nftables.j225
-rw-r--r--data/templates/high-availability/10-override.conf.j2 (renamed from src/etc/systemd/system/keepalived.service.d/override.conf)4
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j26
-rw-r--r--data/vyos-firewall-init.conf45
-rwxr-xr-xdebian/rules4
-rw-r--r--debian/vyos-1x-smoketest.install1
-rw-r--r--interface-definitions/high-availability.xml.in6
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i32
-rw-r--r--interface-definitions/include/firewall/source-destination-group-ipv4.xml.i41
-rw-r--r--interface-definitions/include/version/conntrack-version.xml.i2
-rw-r--r--interface-definitions/interfaces-virtual-ethernet.xml.in1
-rw-r--r--interface-definitions/system-conntrack.xml.in215
-rw-r--r--op-mode-definitions/show-system.xml.in49
-rw-r--r--op-mode-definitions/vpn-ipsec.xml.in2
-rw-r--r--python/vyos/config_mgmt.py32
-rw-r--r--python/vyos/configdep.py61
-rw-r--r--python/vyos/ifconfig/control.py14
-rw-r--r--python/vyos/ifconfig/interface.py167
-rw-r--r--python/vyos/template.py78
-rw-r--r--python/vyos/utils/network.py97
-rw-r--r--python/vyos/utils/process.py2
-rwxr-xr-xsmoketest/bin/vyos-configtest23
-rw-r--r--smoketest/config-tests/basic-vyos62
-rw-r--r--smoketest/config-tests/dialup-router-medium-vpn321
-rw-r--r--smoketest/configs/basic-vyos12
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py8
-rwxr-xr-xsmoketest/scripts/cli/test_dependency_graph.py54
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py8
-rwxr-xr-xsmoketest/scripts/cli/test_netns.py (renamed from smoketest/scripts/cli/test_interfaces_netns.py)52
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py57
-rwxr-xr-xsrc/conf_mode/conntrack.py91
-rwxr-xr-xsrc/conf_mode/high-availability.py20
-rwxr-xr-xsrc/conf_mode/interfaces-dummy.py4
-rwxr-xr-xsrc/conf_mode/nat.py2
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf4
-rwxr-xr-xsrc/helpers/config_dependency.py58
-rwxr-xr-xsrc/helpers/vyos-domain-resolver.py6
-rwxr-xr-xsrc/helpers/vyos-save-config.py5
-rwxr-xr-xsrc/init/vyos-router3
-rwxr-xr-xsrc/migration-scripts/conntrack/3-to-450
-rwxr-xr-xsrc/migration-scripts/system/13-to-142
-rwxr-xr-xsrc/op_mode/ipsec.py39
-rwxr-xr-xsrc/op_mode/otp.py124
-rw-r--r--src/pam-configs/radius3
-rw-r--r--src/tests/test_dependency_graph.py31
50 files changed, 1611 insertions, 391 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index d77275d38..000000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: Build
-on:
- push:
- branches:
- - current
- pull_request:
- types: [opened, synchronize, reopened]
-jobs:
- sonarcloud:
- name: SonarCloud
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- - name: SonarCloud Scan
- uses: SonarSource/sonarcloud-github-action@master
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/data/config-mode-dependencies.json b/data/config-mode-dependencies/vyos-1x.json
index 91a757c16..08732bd4c 100644
--- a/data/config-mode-dependencies.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -1,5 +1,5 @@
{
- "firewall": {"group_resync": ["nat", "policy-route"]},
+ "firewall": {"group_resync": ["conntrack", "nat", "policy-route"]},
"http_api": {"https": ["https"]},
"pki": {
"ethernet": ["interfaces-ethernet"],
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index 042c466ab..ded934bff 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -16,6 +16,7 @@
"neighbor.py",
"nhrp.py",
"openconnect.py",
+"otp.py",
"openvpn.py",
"reset_vpn.py",
"reverseproxy.py",
diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2
index 16a03fc6e..970869043 100644
--- a/data/templates/conntrack/nftables-ct.j2
+++ b/data/templates/conntrack/nftables-ct.j2
@@ -1,5 +1,7 @@
#!/usr/sbin/nft -f
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
+
{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %}
{% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %}
@@ -10,29 +12,35 @@ flush chain raw {{ nft_ct_timeout_name }}
table raw {
chain {{ nft_ct_ignore_name }} {
-{% if ignore.rule is vyos_defined %}
-{% for rule, rule_config in ignore.rule.items() %}
+{% if ignore.ipv4.rule is vyos_defined %}
+{% for rule, rule_config in ignore.ipv4.rule.items() %}
+ # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }}
+ {{ rule_config | conntrack_ignore_rule(rule, ipv6=False) }}
+{% endfor %}
+{% endif %}
+ return
+ }
+ chain {{ nft_ct_timeout_name }} {
+{% if timeout.custom.rule is vyos_defined %}
+{% for rule, rule_config in timeout.custom.rule.items() %}
+ # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }}
+{% endfor %}
+{% endif %}
+ return
+ }
+
+{{ group_tmpl.groups(firewall_group, False) }}
+}
+
+flush chain ip6 raw {{ nft_ct_ignore_name }}
+flush chain ip6 raw {{ nft_ct_timeout_name }}
+
+table ip6 raw {
+ chain {{ nft_ct_ignore_name }} {
+{% if ignore.ipv6.rule is vyos_defined %}
+{% for rule, rule_config in ignore.ipv6.rule.items() %}
# rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }}
-{% set nft_command = '' %}
-{% if rule_config.inbound_interface is vyos_defined %}
-{% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %}
-{% endif %}
-{% if rule_config.protocol is vyos_defined %}
-{% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %}
-{% endif %}
-{% if rule_config.destination.address is vyos_defined %}
-{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %}
-{% endif %}
-{% if rule_config.destination.port is vyos_defined %}
-{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %}
-{% endif %}
-{% if rule_config.source.address is vyos_defined %}
-{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %}
-{% endif %}
-{% if rule_config.source.port is vyos_defined %}
-{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %}
-{% endif %}
- {{ nft_command }} counter notrack comment ignore-{{ rule }}
+ {{ rule_config | conntrack_ignore_rule(rule, ipv6=True) }}
{% endfor %}
{% endif %}
return
@@ -45,4 +53,6 @@ table raw {
{% endif %}
return
}
+
+{{ group_tmpl.groups(firewall_group, True) }}
}
diff --git a/data/templates/dhcp-server/10-override.conf.j2 b/data/templates/dhcp-server/10-override.conf.j2
index dd5730b90..1504b6808 100644
--- a/data/templates/dhcp-server/10-override.conf.j2
+++ b/data/templates/dhcp-server/10-override.conf.j2
@@ -1,5 +1,5 @@
### Autogenerated by dhcp_server.py ###
-{% set lease_file = '/run/dhcp-server/dhcpd.leases' %}
+{% set lease_file = '/config/dhcpd.leases' %}
[Unit]
Description=ISC DHCP IPv4 server
Documentation=man:dhcpd(8)
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index bb14609b0..87630940b 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -6,29 +6,36 @@
flush chain raw FW_CONNTRACK
flush chain ip6 raw FW_CONNTRACK
+flush chain raw vyos_global_rpfilter
+flush chain ip6 raw vyos_global_rpfilter
+
table raw {
chain FW_CONNTRACK {
{{ ipv4_conntrack_action }}
}
+
+ chain vyos_global_rpfilter {
+{% if global_options.source_validation is vyos_defined('loose') %}
+ fib saddr oif 0 counter drop
+{% elif global_options.source_validation is vyos_defined('strict') %}
+ fib saddr . iif oif 0 counter drop
+{% endif %}
+ return
+ }
}
table ip6 raw {
chain FW_CONNTRACK {
{{ ipv6_conntrack_action }}
}
-}
-{% if first_install is not vyos_defined %}
-delete table inet vyos_global_rpfilter
-{% endif %}
-table inet vyos_global_rpfilter {
- chain PREROUTING {
- type filter hook prerouting priority -300; policy accept;
-{% if global_options.source_validation is vyos_defined('loose') %}
+ chain vyos_global_rpfilter {
+{% if global_options.ipv6_source_validation is vyos_defined('loose') %}
fib saddr oif 0 counter drop
-{% elif global_options.source_validation is vyos_defined('strict') %}
+{% elif global_options.ipv6_source_validation is vyos_defined('strict') %}
fib saddr . iif oif 0 counter drop
{% endif %}
+ return
}
}
diff --git a/src/etc/systemd/system/keepalived.service.d/override.conf b/data/templates/high-availability/10-override.conf.j2
index d91a824b9..d1cb25581 100644
--- a/src/etc/systemd/system/keepalived.service.d/override.conf
+++ b/data/templates/high-availability/10-override.conf.j2
@@ -1,3 +1,5 @@
+### Autogenerated by ${vyos_conf_scripts_dir}/high-availability.py ###
+{% set snmp = '' if vrrp.disable_snmp is vyos_defined else '--snmp' %}
[Unit]
After=vyos-router.service
# Only start if there is our configuration file - remove Debian default
@@ -10,5 +12,5 @@ KillMode=process
Type=simple
# Read configuration variable file if it is present
ExecStart=
-ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp
+ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork {{ snmp }}
PIDFile=/run/keepalived/keepalived.pid
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index f8e1587f8..0a40e1ecf 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -150,13 +150,13 @@ backend {{ back }}
{% endfor %}
{% endif %}
{% if back_config.timeout.check is vyos_defined %}
- timeout check {{ back_config.timeout.check }}
+ timeout check {{ back_config.timeout.check }}s
{% endif %}
{% if back_config.timeout.connect is vyos_defined %}
- timeout connect {{ back_config.timeout.connect }}
+ timeout connect {{ back_config.timeout.connect }}s
{% endif %}
{% if back_config.timeout.server is vyos_defined %}
- timeout server {{ back_config.timeout.server }}
+ timeout server {{ back_config.timeout.server }}s
{% endif %}
{% endfor %}
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 41e7627f5..7e258e6f1 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -19,6 +19,15 @@ table raw {
type filter hook forward priority -300; policy accept;
}
+ chain vyos_global_rpfilter {
+ return
+ }
+
+ chain vyos_rpfilter {
+ type filter hook prerouting priority -300; policy accept;
+ counter jump vyos_global_rpfilter
+ }
+
chain PREROUTING {
type filter hook prerouting priority -300; policy accept;
counter jump VYOS_CT_IGNORE
@@ -82,12 +91,19 @@ table ip6 raw {
type filter hook forward priority -300; policy accept;
}
+ chain vyos_global_rpfilter {
+ return
+ }
+
chain vyos_rpfilter {
type filter hook prerouting priority -300; policy accept;
+ counter jump vyos_global_rpfilter
}
chain PREROUTING {
type filter hook prerouting priority -300; policy accept;
+ counter jump VYOS_CT_IGNORE
+ counter jump VYOS_CT_TIMEOUT
counter jump VYOS_CT_PREROUTING_HOOK
counter jump FW_CONNTRACK
notrack
@@ -95,11 +111,40 @@ table ip6 raw {
chain OUTPUT {
type filter hook output priority -300; policy accept;
+ counter jump VYOS_CT_IGNORE
+ counter jump VYOS_CT_TIMEOUT
counter jump VYOS_CT_OUTPUT_HOOK
counter jump FW_CONNTRACK
notrack
}
+ ct helper rpc_tcp {
+ type "rpc" protocol tcp;
+ }
+
+ ct helper rpc_udp {
+ type "rpc" protocol udp;
+ }
+
+ ct helper tns_tcp {
+ type "tns" protocol tcp;
+ }
+
+ chain VYOS_CT_HELPER {
+ ct helper set "rpc_tcp" tcp dport {111} return
+ ct helper set "rpc_udp" udp dport {111} return
+ ct helper set "tns_tcp" tcp dport {1521,1525,1536} return
+ return
+ }
+
+ chain VYOS_CT_IGNORE {
+ return
+ }
+
+ chain VYOS_CT_TIMEOUT {
+ return
+ }
+
chain VYOS_CT_PREROUTING_HOOK {
return
}
diff --git a/debian/rules b/debian/rules
index e6bbeeafb..9a6ab2996 100755
--- a/debian/rules
+++ b/debian/rules
@@ -117,6 +117,10 @@ override_dh_auto_install:
mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config/
cp -r smoketest/configs/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config
+ # Install smoke test config tests
+ mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config-tests/
+ cp -r smoketest/config-tests/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config-tests
+
# Install system programs
mkdir -p $(DIR)/$(VYOS_BIN_DIR)
cp -r smoketest/bin/* $(DIR)/$(VYOS_BIN_DIR)
diff --git a/debian/vyos-1x-smoketest.install b/debian/vyos-1x-smoketest.install
index 406fef4be..739cb189b 100644
--- a/debian/vyos-1x-smoketest.install
+++ b/debian/vyos-1x-smoketest.install
@@ -3,3 +3,4 @@ usr/bin/vyos-configtest
usr/bin/vyos-configtest-pki
usr/libexec/vyos/tests/smoke
usr/libexec/vyos/tests/config
+usr/libexec/vyos/tests/config-tests
diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in
index 4f55916fa..47a772d04 100644
--- a/interface-definitions/high-availability.xml.in
+++ b/interface-definitions/high-availability.xml.in
@@ -12,6 +12,12 @@
<help>Virtual Router Redundancy Protocol settings</help>
</properties>
<children>
+ <leafNode name="disable-snmp">
+ <properties>
+ <valueless/>
+ <help>Disable SNMP</help>
+ </properties>
+ </leafNode>
<node name="global-parameters">
<properties>
<help>VRRP global parameters</help>
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index a63874cb0..e655cd6ac 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -145,21 +145,21 @@
</leafNode>
<leafNode name="source-validation">
<properties>
- <help>Policy for source validation by reversed path, as specified in RFC3704</help>
+ <help>Policy for IPv4 source validation by reversed path, as specified in RFC3704</help>
<completionHelp>
<list>strict loose disable</list>
</completionHelp>
<valueHelp>
<format>strict</format>
- <description>Enable Strict Reverse Path Forwarding as defined in RFC3704</description>
+ <description>Enable IPv4 Strict Reverse Path Forwarding as defined in RFC3704</description>
</valueHelp>
<valueHelp>
<format>loose</format>
- <description>Enable Loose Reverse Path Forwarding as defined in RFC3704</description>
+ <description>Enable IPv4 Loose Reverse Path Forwarding as defined in RFC3704</description>
</valueHelp>
<valueHelp>
<format>disable</format>
- <description>No source validation</description>
+ <description>No IPv4 source validation</description>
</valueHelp>
<constraint>
<regex>(strict|loose|disable)</regex>
@@ -227,6 +227,30 @@
</properties>
<defaultValue>disable</defaultValue>
</leafNode>
+ <leafNode name="ipv6-source-validation">
+ <properties>
+ <help>Policy for IPv6 source validation by reversed path, as specified in RFC3704</help>
+ <completionHelp>
+ <list>strict loose disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>strict</format>
+ <description>Enable IPv6 Strict Reverse Path Forwarding as defined in RFC3704</description>
+ </valueHelp>
+ <valueHelp>
+ <format>loose</format>
+ <description>Enable IPv6 Loose Reverse Path Forwarding as defined in RFC3704</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>No IPv6 source validation</description>
+ </valueHelp>
+ <constraint>
+ <regex>(strict|loose|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
<leafNode name="ipv6-src-route">
<properties>
<help>Policy for handling IPv6 packets with routing extension header</help>
diff --git a/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i
new file mode 100644
index 000000000..8c34fb933
--- /dev/null
+++ b/interface-definitions/include/firewall/source-destination-group-ipv4.xml.i
@@ -0,0 +1,41 @@
+<!-- include start from firewall/source-destination-group-ipv4.xml.i -->
+<node name="group">
+ <properties>
+ <help>Group</help>
+ </properties>
+ <children>
+ <leafNode name="address-group">
+ <properties>
+ <help>Group of addresses</help>
+ <completionHelp>
+ <path>firewall group address-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="domain-group">
+ <properties>
+ <help>Group of domains</help>
+ <completionHelp>
+ <path>firewall group domain-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="network-group">
+ <properties>
+ <help>Group of networks</help>
+ <completionHelp>
+ <path>firewall group network-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="port-group">
+ <properties>
+ <help>Group of ports</help>
+ <completionHelp>
+ <path>firewall group port-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/version/conntrack-version.xml.i b/interface-definitions/include/version/conntrack-version.xml.i
index 696f76362..c0f632c70 100644
--- a/interface-definitions/include/version/conntrack-version.xml.i
+++ b/interface-definitions/include/version/conntrack-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/conntrack-version.xml.i -->
-<syntaxVersion component='conntrack' version='3'></syntaxVersion>
+<syntaxVersion component='conntrack' version='4'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in
index 1daa764d4..5f205f354 100644
--- a/interface-definitions/interfaces-virtual-ethernet.xml.in
+++ b/interface-definitions/interfaces-virtual-ethernet.xml.in
@@ -21,6 +21,7 @@
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable.xml.i>
+ #include <include/interface/netns.xml.i>
#include <include/interface/vif-s.xml.i>
#include <include/interface/vif.xml.i>
#include <include/interface/vrf.xml.i>
diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in
index 8dad048b8..3abf9bbf0 100644
--- a/interface-definitions/system-conntrack.xml.in
+++ b/interface-definitions/system-conntrack.xml.in
@@ -40,82 +40,177 @@
<help>Customized rules to ignore selective connection tracking</help>
</properties>
<children>
- <tagNode name="rule">
+ <node name="ipv4">
<properties>
- <help>Rule number</help>
- <valueHelp>
- <format>u32:1-999999</format>
- <description>Number of conntrack ignore rule</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-999999"/>
- </constraint>
- <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
+ <help>IPv4 rules</help>
</properties>
<children>
- #include <include/generic-description.xml.i>
- <node name="destination">
+ <tagNode name="rule">
<properties>
- <help>Destination parameters</help>
+ <help>Rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of conntrack ignore rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
- #include <include/nat-address.xml.i>
- #include <include/nat-port.xml.i>
+ #include <include/generic-description.xml.i>
+ <node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/source-destination-group-ipv4.xml.i>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ <leafNode name="inbound-interface">
+ <properties>
+ <help>Interface to ignore connections tracking on</help>
+ <completionHelp>
+ <list>any</list>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ #include <include/ip-protocol.xml.i>
+ <leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/source-destination-group-ipv4.xml.i>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
</children>
- </node>
- <leafNode name="inbound-interface">
- <properties>
- <help>Interface to ignore connections tracking on</help>
- <completionHelp>
- <list>any</list>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- </properties>
- </leafNode>
- #include <include/ip-protocol.xml.i>
- <leafNode name="protocol">
+ </tagNode>
+ </children>
+ </node>
+ <node name="ipv6">
+ <properties>
+ <help>IPv6 rules</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
<properties>
- <help>Protocol to match (protocol name, number, or "all")</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_protocols.sh</script>
- <list>all tcp_udp</list>
- </completionHelp>
- <valueHelp>
- <format>all</format>
- <description>All IP protocols</description>
- </valueHelp>
- <valueHelp>
- <format>tcp_udp</format>
- <description>Both TCP and UDP</description>
- </valueHelp>
- <valueHelp>
- <format>u32:0-255</format>
- <description>IP protocol number</description>
- </valueHelp>
- <valueHelp>
- <format>&lt;protocol&gt;</format>
- <description>IP protocol name</description>
- </valueHelp>
+ <help>Rule number</help>
<valueHelp>
- <format>!&lt;protocol&gt;</format>
- <description>IP protocol name</description>
+ <format>u32:1-999999</format>
+ <description>Number of conntrack ignore rule</description>
</valueHelp>
<constraint>
- <validator name="ip-protocol"/>
+ <validator name="numeric" argument="--range 1-999999"/>
</constraint>
- </properties>
- </leafNode>
- <node name="source">
- <properties>
- <help>Source parameters</help>
+ <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
- #include <include/nat-address.xml.i>
- #include <include/nat-port.xml.i>
+ #include <include/generic-description.xml.i>
+ <node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ <leafNode name="inbound-interface">
+ <properties>
+ <help>Interface to ignore connections tracking on</help>
+ <completionHelp>
+ <list>any</list>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ #include <include/ip-protocol.xml.i>
+ <leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
</children>
- </node>
+ </tagNode>
</children>
- </tagNode>
+ </node>
+
</children>
</node>
<node name="log">
diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in
index 85bfdcdba..116c7460f 100644
--- a/op-mode-definitions/show-system.xml.in
+++ b/op-mode-definitions/show-system.xml.in
@@ -102,6 +102,55 @@
<help>Show user accounts</help>
</properties>
<children>
+ <node name="authentication">
+ <properties>
+ <help>Show user account authentication information</help>
+ </properties>
+ <children>
+ <tagNode name="user">
+ <properties>
+ <help>Show configured user</help>
+ <completionHelp>
+ <path>system login user</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="otp">
+ <properties>
+ <help>Show OTP key information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"</command>
+ <children>
+ <leafNode name="full">
+ <properties>
+ <help>Show full settings, including QR code and commands for VyOS</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"</command>
+ </leafNode>
+ <leafNode name="key-b32">
+ <properties>
+ <help>Show OTP authentication secret in Base32 (used in mobile apps)</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="key-b32"</command>
+ </leafNode>
+ <leafNode name="qrcode">
+ <properties>
+ <help>Show OTP authentication QR code</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="qrcode"</command>
+ </leafNode>
+ <leafNode name="uri">
+ <properties>
+ <help>Show OTP authentication otpauth URI</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="uri"</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
<node name="users">
<properties>
<help>Show user account information</help>
diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in
index c7ba780a3..b551af2be 100644
--- a/op-mode-definitions/vpn-ipsec.xml.in
+++ b/op-mode-definitions/vpn-ipsec.xml.in
@@ -177,7 +177,7 @@
<properties>
<help>Show all the pre-shared key secrets</help>
</properties>
- <command>sudo cat /etc/ipsec.secrets | sed 's/#.*//'</command>
+ <command>${vyos_op_scripts_dir}/ipsec.py show_psk</command>
</node>
<node name="status">
<properties>
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index 0fc72e660..dbf17ade4 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -25,12 +25,14 @@ from datetime import datetime
from textwrap import dedent
from pathlib import Path
from tabulate import tabulate
+from shutil import copy
from vyos.config import Config
from vyos.configtree import ConfigTree, ConfigTreeError, show_diff
from vyos.defaults import directories
from vyos.version import get_full_version_data
from vyos.utils.io import ask_yes_no
+from vyos.utils.boot import boot_configuration_complete
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import rc_cmd
@@ -200,9 +202,9 @@ Proceed ?'''
raise ConfigMgmtError(out)
entry = self._read_tmp_log_entry()
- self._add_log_entry(**entry)
if self._archive_active_config():
+ self._add_log_entry(**entry)
self._update_archive()
msg = 'Reboot timer stopped'
@@ -223,8 +225,6 @@ Proceed ?'''
def rollback(self, rev: int, no_prompt: bool=False) -> Tuple[str,int]:
"""Reboot to config revision 'rev'.
"""
- from shutil import copy
-
msg = ''
if not self._check_revision_number(rev):
@@ -334,10 +334,10 @@ Proceed ?'''
user = self._get_user()
via = 'init'
comment = ''
- self._add_log_entry(user, via, comment)
# add empty init config before boot-config load for revision
# and diff consistency
if self._archive_active_config():
+ self._add_log_entry(user, via, comment)
self._update_archive()
os.umask(mask)
@@ -352,9 +352,8 @@ Proceed ?'''
self._new_log_entry(tmp_file=tmp_log_entry)
return
- self._add_log_entry()
-
if self._archive_active_config():
+ self._add_log_entry()
self._update_archive()
def commit_archive(self):
@@ -475,22 +474,26 @@ Proceed ?'''
conf_file.chmod(0o644)
def _archive_active_config(self) -> bool:
+ save_to_tmp = (boot_configuration_complete() or not
+ os.path.isfile(archive_config_file))
mask = os.umask(0o113)
ext = os.getpid()
- tmp_save = f'/tmp/config.boot.{ext}'
- save_config(tmp_save)
+ cmp_saved = f'/tmp/config.boot.{ext}'
+ if save_to_tmp:
+ save_config(cmp_saved)
+ else:
+ copy(config_file, cmp_saved)
try:
- if cmp(tmp_save, archive_config_file, shallow=False):
- # this will be the case on boot, as well as certain
- # re-initialiation instances after delete/set
- os.unlink(tmp_save)
+ if cmp(cmp_saved, archive_config_file, shallow=False):
+ os.unlink(cmp_saved)
+ os.umask(mask)
return False
except FileNotFoundError:
pass
- rc, out = rc_cmd(f'sudo mv {tmp_save} {archive_config_file}')
+ rc, out = rc_cmd(f'sudo mv {cmp_saved} {archive_config_file}')
os.umask(mask)
if rc != 0:
@@ -522,9 +525,8 @@ Proceed ?'''
return len(l)
def _check_revision_number(self, rev: int) -> bool:
- # exclude init revision:
maxrev = self._get_number_of_revisions()
- if not 0 <= rev < maxrev - 1:
+ if not 0 <= rev < maxrev:
return False
return True
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py
index 7a8559839..05d9a3fa3 100644
--- a/python/vyos/configdep.py
+++ b/python/vyos/configdep.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 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
@@ -17,8 +17,10 @@ import os
import json
import typing
from inspect import stack
+from graphlib import TopologicalSorter, CycleError
from vyos.utils.system import load_as_module
+from vyos.configdict import dict_merge
from vyos.defaults import directories
from vyos.configsource import VyOSError
from vyos import ConfigError
@@ -28,6 +30,9 @@ from vyos import ConfigError
if typing.TYPE_CHECKING:
from vyos.config import Config
+dependency_dir = os.path.join(directories['data'],
+ 'config-mode-dependencies')
+
dependent_func: dict[str, list[typing.Callable]] = {}
def canon_name(name: str) -> str:
@@ -40,12 +45,20 @@ def canon_name_of_path(path: str) -> str:
def caller_name() -> str:
return stack()[-1].filename
-def read_dependency_dict() -> dict:
- path = os.path.join(directories['data'],
- 'config-mode-dependencies.json')
- with open(path) as f:
- d = json.load(f)
- return d
+def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict:
+ res = {}
+ for dep_file in os.listdir(dependency_dir):
+ if not dep_file.endswith('.json'):
+ continue
+ path = os.path.join(dependency_dir, dep_file)
+ with open(path) as f:
+ d = json.load(f)
+ if dep_file == 'vyos-1x.json':
+ res = dict_merge(res, d)
+ else:
+ res = dict_merge(d, res)
+
+ return res
def get_dependency_dict(config: 'Config') -> dict:
if hasattr(config, 'cached_dependency_dict'):
@@ -93,3 +106,37 @@ def call_dependents():
while l:
f = l.pop(0)
f()
+
+def graph_from_dependency_dict(d: dict) -> dict:
+ g = {}
+ for k in list(d):
+ g[k] = set()
+ # add the dependencies for every sub-case; should there be cases
+ # that are mutally exclusive in the future, the graphs will be
+ # distinguished
+ for el in list(d[k]):
+ g[k] |= set(d[k][el])
+
+ return g
+
+def is_acyclic(d: dict) -> bool:
+ g = graph_from_dependency_dict(d)
+ ts = TopologicalSorter(g)
+ try:
+ # get node iterator
+ order = ts.static_order()
+ # try iteration
+ _ = [*order]
+ except CycleError:
+ return False
+
+ return True
+
+def check_dependency_graph(dependency_dir: str = dependency_dir,
+ supplement: str = None) -> bool:
+ d = read_dependency_dict(dependency_dir=dependency_dir)
+ if supplement is not None:
+ with open(supplement) as f:
+ d = dict_merge(json.load(f), d)
+
+ return is_acyclic(d)
diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py
index c8366cb58..7402da55a 100644
--- a/python/vyos/ifconfig/control.py
+++ b/python/vyos/ifconfig/control.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-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
@@ -49,6 +49,18 @@ class Control(Section):
return popen(command, self.debug)
def _cmd(self, command):
+ import re
+ if 'netns' in self.config:
+ # This command must be executed from default netns 'ip link set dev X netns X'
+ # exclude set netns cmd from netns to avoid:
+ # failed to run command: ip netns exec ns01 ip link set dev veth20 netns ns01
+ pattern = r'ip link set dev (\S+) netns (\S+)'
+ matches = re.search(pattern, command)
+ if matches and matches.group(2) == self.config['netns']:
+ # Command already includes netns and matches desired namespace:
+ command = command
+ else:
+ command = f'ip netns exec {self.config["netns"]} {command}'
return cmd(command, self.debug)
def _get_command(self, config, name):
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 41ce352ad..050095364 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -38,7 +38,9 @@ from vyos.utils.dict import dict_search
from vyos.utils.file import read_file
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_interface_namespace
+from vyos.utils.network import is_netns_interface
from vyos.utils.process import is_systemd_service_active
+from vyos.utils.process import run
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.utils.network import is_intf_addr_assigned
@@ -138,9 +140,6 @@ class Interface(Control):
'validate': assert_mtu,
'shellcmd': 'ip link set dev {ifname} mtu {value}',
},
- 'netns': {
- 'shellcmd': 'ip link set dev {ifname} netns {value}',
- },
'vrf': {
'convert': lambda v: f'master {v}' if v else 'nomaster',
'shellcmd': 'ip link set dev {ifname} {value}',
@@ -175,10 +174,6 @@ class Interface(Control):
'validate': assert_boolean,
'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding',
},
- 'rp_filter': {
- 'validate': lambda flt: assert_range(flt,0,3),
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter',
- },
'ipv6_accept_ra': {
'validate': lambda ara: assert_range(ara,0,3),
'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
@@ -252,9 +247,6 @@ class Interface(Control):
'ipv4_directed_broadcast': {
'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding',
},
- 'rp_filter': {
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter',
- },
'ipv6_accept_ra': {
'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
},
@@ -286,8 +278,11 @@ class Interface(Control):
}
@classmethod
- def exists(cls, ifname):
- return os.path.exists(f'/sys/class/net/{ifname}')
+ def exists(cls, ifname: str, netns: str=None) -> bool:
+ cmd = f'ip link show dev {ifname}'
+ if netns:
+ cmd = f'ip netns exec {netns} {cmd}'
+ return run(cmd) == 0
@classmethod
def get_config(cls):
@@ -355,7 +350,13 @@ class Interface(Control):
self.vrrp = VRRP(ifname)
def _create(self):
+ # Do not create interface that already exist or exists in netns
+ netns = self.config.get('netns', None)
+ if self.exists(f'{self.ifname}', netns=netns):
+ return
+
cmd = 'ip link add dev {ifname} type {type}'.format(**self.config)
+ if 'netns' in self.config: cmd = f'ip netns exec {netns} {cmd}'
self._cmd(cmd)
def remove(self):
@@ -390,6 +391,9 @@ class Interface(Control):
# after interface removal no other commands should be allowed
# to be called and instead should raise an Exception:
cmd = 'ip link del dev {ifname}'.format(**self.config)
+ # for delete we can't get data from self.config{'netns'}
+ netns = get_interface_namespace(self.ifname)
+ if netns: cmd = f'ip netns exec {netns} {cmd}'
return self._cmd(cmd)
def _set_vrf_ct_zone(self, vrf):
@@ -397,6 +401,10 @@ class Interface(Control):
Add/Remove rules in nftables to associate traffic in VRF to an
individual conntack zone
"""
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return None
+
if vrf:
# Get routing table ID for VRF
vrf_table_id = get_interface_config(vrf).get('linkinfo', {}).get(
@@ -540,36 +548,30 @@ class Interface(Control):
if prev_state == 'up':
self.set_admin_state('up')
- def del_netns(self, netns):
- """
- Remove interface from given NETNS.
- """
-
- # If NETNS does not exist then there is nothing to delete
+ def del_netns(self, netns: str) -> bool:
+ """ Remove interface from given network namespace """
+ # If network namespace does not exist then there is nothing to delete
if not os.path.exists(f'/run/netns/{netns}'):
- return None
-
- # As a PoC we only allow 'dummy' interfaces
- if 'dum' not in self.ifname:
- return None
+ return False
- # Check if interface realy exists in namespace
- if get_interface_namespace(self.ifname) != None:
- self._cmd(f'ip netns exec {get_interface_namespace(self.ifname)} ip link del dev {self.ifname}')
- return
+ # Check if interface exists in network namespace
+ if is_netns_interface(self.ifname, netns):
+ self._cmd(f'ip netns exec {netns} ip link del dev {self.ifname}')
+ return True
+ return False
- def set_netns(self, netns):
+ def set_netns(self, netns: str) -> bool:
"""
- Add interface from given NETNS.
+ Add interface from given network namespace
Example:
>>> from vyos.ifconfig import Interface
>>> Interface('dum0').set_netns('foo')
"""
+ self._cmd(f'ip link set dev {self.ifname} netns {netns}')
+ return True
- self.set_interface('netns', netns)
-
- def set_vrf(self, vrf):
+ def set_vrf(self, vrf: str) -> bool:
"""
Add/Remove interface from given VRF instance.
@@ -581,10 +583,11 @@ class Interface(Control):
tmp = self.get_interface('vrf')
if tmp == vrf:
- return None
+ return False
self.set_interface('vrf', vrf)
self._set_vrf_ct_zone(vrf)
+ return True
def set_arp_cache_tmo(self, tmo):
"""
@@ -621,6 +624,10 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_tcp_ipv4_mss(1340)
"""
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return None
+
self._cleanup_mss_rules('raw', self.ifname)
nft_prefix = 'nft add rule raw VYOS_TCP_MSS'
base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn'
@@ -641,6 +648,10 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_tcp_mss(1320)
"""
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return None
+
self._cleanup_mss_rules('ip6 raw', self.ifname)
nft_prefix = 'nft add rule ip6 raw VYOS_TCP_MSS'
base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn'
@@ -745,40 +756,36 @@ class Interface(Control):
return None
return self.set_interface('ipv4_directed_broadcast', forwarding)
- def set_ipv4_source_validation(self, value):
- """
- Help prevent attacks used by Spoofing IP Addresses. Reverse path
- filtering is a Kernel feature that, when enabled, is designed to ensure
- packets that are not routable to be dropped. The easiest example of this
- would be and IP Address of the range 10.0.0.0/8, a private IP Address,
- being received on the Internet facing interface of the router.
+ def _cleanup_ipv4_source_validation_rules(self, ifname):
+ results = self._cmd(f'nft -a list chain ip raw vyos_rpfilter').split("\n")
+ for line in results:
+ if f'iifname "{ifname}"' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ self._cmd(f'nft delete rule ip raw vyos_rpfilter handle {handle_search[1]}')
- As per RFC3074.
+ def set_ipv4_source_validation(self, mode):
"""
- if value == 'strict':
- value = 1
- elif value == 'loose':
- value = 2
- else:
- value = 0
-
- all_rp_filter = int(read_file('/proc/sys/net/ipv4/conf/all/rp_filter'))
- if all_rp_filter > value:
- global_setting = 'disable'
- if all_rp_filter == 1: global_setting = 'strict'
- elif all_rp_filter == 2: global_setting = 'loose'
-
- from vyos.base import Warning
- Warning(f'Global source-validation is set to "{global_setting}", this '\
- f'overrides per interface setting on "{self.ifname}"!')
+ Set IPv4 reverse path validation
- tmp = self.get_interface('rp_filter')
- if int(tmp) == value:
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_ipv4_source_validation('strict')
+ """
+ # Don't allow for netns yet
+ if 'netns' in self.config:
return None
- return self.set_interface('rp_filter', value)
+
+ self._cleanup_ipv4_source_validation_rules(self.ifname)
+ nft_prefix = f'nft insert rule ip raw vyos_rpfilter iifname "{self.ifname}"'
+ if mode in ['strict', 'loose']:
+ self._cmd(f"{nft_prefix} counter return")
+ if mode == 'strict':
+ self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop")
+ elif mode == 'loose':
+ self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop")
def _cleanup_ipv6_source_validation_rules(self, ifname):
- commands = []
results = self._cmd(f'nft -a list chain ip6 raw vyos_rpfilter').split("\n")
for line in results:
if f'iifname "{ifname}"' in line:
@@ -794,8 +801,14 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_ipv6_source_validation('strict')
"""
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return None
+
self._cleanup_ipv6_source_validation_rules(self.ifname)
- nft_prefix = f'nft add rule ip6 raw vyos_rpfilter iifname "{self.ifname}"'
+ nft_prefix = f'nft insert rule ip6 raw vyos_rpfilter iifname "{self.ifname}"'
+ if mode in ['strict', 'loose']:
+ self._cmd(f"{nft_prefix} counter return")
if mode == 'strict':
self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop")
elif mode == 'loose':
@@ -1143,13 +1156,17 @@ class Interface(Control):
if addr in self._addr:
return False
+ # get interface network namespace if specified
+ netns = self.config.get('netns', None)
+
# add to interface
if addr == 'dhcp':
self.set_dhcp(True)
elif addr == 'dhcpv6':
self.set_dhcpv6(True)
- elif not is_intf_addr_assigned(self.ifname, addr):
- tmp = f'ip addr add {addr} dev {self.ifname}'
+ elif not is_intf_addr_assigned(self.ifname, addr, netns=netns):
+ netns_cmd = f'ip netns exec {netns}' if netns else ''
+ tmp = f'{netns_cmd} ip addr add {addr} dev {self.ifname}'
# Add broadcast address for IPv4
if is_ipv4(addr): tmp += ' brd +'
@@ -1189,13 +1206,17 @@ class Interface(Control):
if not addr:
raise ValueError()
+ # get interface network namespace if specified
+ netns = self.config.get('netns', None)
+
# remove from interface
if addr == 'dhcp':
self.set_dhcp(False)
elif addr == 'dhcpv6':
self.set_dhcpv6(False)
- elif is_intf_addr_assigned(self.ifname, addr):
- self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"')
+ elif is_intf_addr_assigned(self.ifname, addr, netns=netns):
+ netns_cmd = f'ip netns exec {netns}' if netns else ''
+ self._cmd(f'{netns_cmd} ip addr del {addr} dev {self.ifname}')
else:
return False
@@ -1215,8 +1236,11 @@ class Interface(Control):
self.set_dhcp(False)
self.set_dhcpv6(False)
+ netns = get_interface_namespace(self.ifname)
+ netns_cmd = f'ip netns exec {netns}' if netns else ''
+ cmd = f'{netns_cmd} ip addr flush dev {self.ifname}'
# flush all addresses
- self._cmd(f'ip addr flush dev "{self.ifname}"')
+ self._cmd(cmd)
def add_to_bridge(self, bridge_dict):
"""
@@ -1371,6 +1395,11 @@ class Interface(Control):
# - https://man7.org/linux/man-pages/man8/tc-mirred.8.html
# Depening if we are the source or the target interface of the port
# mirror we need to setup some variables.
+
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return None
+
source_if = self.config['ifname']
mirror_config = None
@@ -1471,8 +1500,8 @@ class Interface(Control):
# Since the interface is pushed onto a separate logical stack
# Configure NETNS
if dict_search('netns', config) != None:
- self.set_netns(config.get('netns', ''))
- return
+ if not is_netns_interface(self.ifname, self.config['netns']):
+ self.set_netns(config.get('netns', ''))
else:
self.del_netns(config.get('netns', ''))
diff --git a/python/vyos/template.py b/python/vyos/template.py
index e167488c6..c1b57b883 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -663,6 +663,84 @@ def nat_static_rule(rule_conf, rule_id, nat_type):
from vyos.nat import parse_nat_static_rule
return parse_nat_static_rule(rule_conf, rule_id, nat_type)
+@register_filter('conntrack_ignore_rule')
+def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False):
+ ip_prefix = 'ip6' if ipv6 else 'ip'
+ def_suffix = '6' if ipv6 else ''
+ output = []
+
+ if 'inbound_interface' in rule_conf:
+ ifname = rule_conf['inbound_interface']
+ output.append(f'iifname {ifname}')
+
+ if 'protocol' in rule_conf:
+ proto = rule_conf['protocol']
+ output.append(f'meta l4proto {proto}')
+
+ for side in ['source', 'destination']:
+ if side in rule_conf:
+ side_conf = rule_conf[side]
+ prefix = side[0]
+
+ if 'address' in side_conf:
+ address = side_conf['address']
+ operator = ''
+ if address[0] == '!':
+ operator = '!='
+ address = address[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} {address}')
+
+ if 'port' in side_conf:
+ port = side_conf['port']
+ operator = ''
+ if port[0] == '!':
+ operator = '!='
+ port = port[1:]
+ output.append(f'th {prefix}port {operator} {port}')
+
+ if 'group' in side_conf:
+ group = side_conf['group']
+
+ if 'address_group' in group:
+ group_name = group['address_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @A{def_suffix}_{group_name}')
+ # Generate firewall group domain-group
+ elif 'domain_group' in group:
+ group_name = group['domain_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}')
+ elif 'network_group' in group:
+ group_name = group['network_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} @N{def_suffix}_{group_name}')
+ if 'port_group' in group:
+ group_name = group['port_group']
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+
+ output.append(f'{proto} {prefix}port {operator} @P_{group_name}')
+
+ output.append('counter notrack')
+ output.append(f'comment "ignore-{rule_id}"')
+
+ return " ".join(output)
+
@register_filter('range_to_regex')
def range_to_regex(num_range):
"""Convert range of numbers or list of ranges
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index bc6899e45..55ff29f0c 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -40,13 +40,19 @@ def interface_exists(interface) -> bool:
import os
return os.path.exists(f'/sys/class/net/{interface}')
-def interface_exists_in_netns(interface_name, netns):
+def is_netns_interface(interface, netns):
from vyos.utils.process import rc_cmd
- rc, out = rc_cmd(f'ip netns exec {netns} ip link show dev {interface_name}')
+ rc, out = rc_cmd(f'sudo ip netns exec {netns} ip link show dev {interface}')
if rc == 0:
return True
return False
+def get_netns_all() -> list:
+ from json import loads
+ from vyos.utils.process import cmd
+ tmp = loads(cmd('ip --json netns ls'))
+ return [ netns['name'] for netns in tmp ]
+
def get_vrf_members(vrf: str) -> list:
"""
Get list of interface VRF members
@@ -78,8 +84,7 @@ def get_interface_config(interface):
""" Returns the used encapsulation protocol for given interface.
If interface does not exist, None is returned.
"""
- import os
- if not os.path.exists(f'/sys/class/net/{interface}'):
+ if not interface_exists(interface):
return None
from json import loads
from vyos.utils.process import cmd
@@ -90,31 +95,30 @@ def get_interface_address(interface):
""" Returns the used encapsulation protocol for given interface.
If interface does not exist, None is returned.
"""
- import os
- if not os.path.exists(f'/sys/class/net/{interface}'):
+ if not interface_exists(interface):
return None
from json import loads
from vyos.utils.process import cmd
tmp = loads(cmd(f'ip --detail --json addr show dev {interface}'))[0]
return tmp
-def get_interface_namespace(iface):
+def get_interface_namespace(interface: str):
"""
Returns wich netns the interface belongs to
"""
from json import loads
from vyos.utils.process import cmd
- # Check if netns exist
- tmp = loads(cmd(f'ip --json netns ls'))
- if len(tmp) == 0:
- return None
- for ns in tmp:
+ # Bail out early if netns does not exist
+ tmp = cmd(f'ip --json netns ls')
+ if not tmp: return None
+
+ for ns in loads(tmp):
netns = f'{ns["name"]}'
# Search interface in each netns
data = loads(cmd(f'ip netns exec {netns} ip --json link show'))
for tmp in data:
- if iface == tmp["ifname"]:
+ if interface == tmp["ifname"]:
return netns
def is_ipv6_tentative(iface: str, ipv6_address: str) -> bool:
@@ -172,8 +176,7 @@ def is_wwan_connected(interface):
def get_bridge_fdb(interface):
""" Returns the forwarding database entries for a given interface """
- import os
- if not os.path.exists(f'/sys/class/net/{interface}'):
+ if not interface_exists(interface):
return None
from json import loads
from vyos.utils.process import cmd
@@ -305,57 +308,33 @@ def is_addr_assigned(ip_address, vrf=None) -> bool:
return False
-def is_intf_addr_assigned(intf, address) -> bool:
+def is_intf_addr_assigned(ifname: str, addr: str, netns: str=None) -> bool:
"""
Verify if the given IPv4/IPv6 address is assigned to specific interface.
It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR
address 192.0.2.1/24.
"""
- from vyos.template import is_ipv4
-
- from netifaces import ifaddresses
- from netifaces import AF_INET
- from netifaces import AF_INET6
-
- # check if the requested address type is configured at all
- # {
- # 17: [{'addr': '08:00:27:d9:5b:04', 'broadcast': 'ff:ff:ff:ff:ff:ff'}],
- # 2: [{'addr': '10.0.2.15', 'netmask': '255.255.255.0', 'broadcast': '10.0.2.255'}],
- # 10: [{'addr': 'fe80::a00:27ff:fed9:5b04%eth0', 'netmask': 'ffff:ffff:ffff:ffff::'}]
- # }
- try:
- addresses = ifaddresses(intf)
- except ValueError as e:
- print(e)
- return False
-
- # determine IP version (AF_INET or AF_INET6) depending on passed address
- addr_type = AF_INET if is_ipv4(address) else AF_INET6
-
- # Check every IP address on this interface for a match
- netmask = None
- if '/' in address:
- address, netmask = address.split('/')
- for ip in addresses.get(addr_type, []):
- # ip can have the interface name in the 'addr' field, we need to remove it
- # {'addr': 'fe80::a00:27ff:fec5:f821%eth2', 'netmask': 'ffff:ffff:ffff:ffff::'}
- ip_addr = ip['addr'].split('%')[0]
-
- if not _are_same_ip(address, ip_addr):
- continue
-
- # we do not have a netmask to compare against, they are the same
- if not netmask:
- return True
+ import json
+ import jmespath
- prefixlen = ''
- if is_ipv4(ip_addr):
- prefixlen = sum([bin(int(_)).count('1') for _ in ip['netmask'].split('.')])
- else:
- prefixlen = sum([bin(int(_,16)).count('1') for _ in ip['netmask'].split('/')[0].split(':') if _])
+ from vyos.utils.process import rc_cmd
+ from ipaddress import ip_interface
- if str(prefixlen) == netmask:
- return True
+ netns_cmd = f'ip netns exec {netns}' if netns else ''
+ rc, out = rc_cmd(f'{netns_cmd} ip --json address show dev {ifname}')
+ if rc == 0:
+ json_out = json.loads(out)
+ addresses = jmespath.search("[].addr_info[].{family: family, address: local, prefixlen: prefixlen}", json_out)
+ for address_info in addresses:
+ family = address_info['family']
+ address = address_info['address']
+ prefixlen = address_info['prefixlen']
+ # Remove the interface name if present in the given address
+ if '%' in addr:
+ addr = addr.split('%')[0]
+ interface = ip_interface(f"{address}/{prefixlen}")
+ if ip_interface(addr) == interface or address == addr:
+ return True
return False
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py
index e09c7d86d..c2ef98140 100644
--- a/python/vyos/utils/process.py
+++ b/python/vyos/utils/process.py
@@ -170,7 +170,7 @@ def rc_cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
(1, 'Device "eth99" does not exist.')
"""
out, code = popen(
- command, flag,
+ command.lstrip(), flag,
stdout=stdout, stderr=stderr,
input=input, timeout=timeout,
env=env, shell=shell,
diff --git a/smoketest/bin/vyos-configtest b/smoketest/bin/vyos-configtest
index 3e42b0380..c1b602737 100755
--- a/smoketest/bin/vyos-configtest
+++ b/smoketest/bin/vyos-configtest
@@ -24,6 +24,7 @@ from vyos.configsession import ConfigSession, ConfigSessionError
from vyos import ConfigError
config_dir = '/usr/libexec/vyos/tests/config'
+config_test_dir = '/usr/libexec/vyos/tests/config-tests'
save_config = '/tmp/vyos-configtest-save'
class DynamicClassBase(unittest.TestCase):
@@ -42,7 +43,7 @@ class DynamicClassBase(unittest.TestCase):
except OSError:
pass
-def make_test_function(filename):
+def make_test_function(filename, test_path=None):
def test_config_load(self):
config_path = os.path.join(config_dir, filename)
self.session.migrate_and_load_config(config_path)
@@ -51,6 +52,16 @@ def make_test_function(filename):
except (ConfigError, ConfigSessionError):
self.session.discard()
self.fail()
+
+ if test_path:
+ config_commands = self.session.show(['configuration', 'commands'])
+
+ with open(test_path, 'r') as f:
+ for line in f.readlines():
+ if not line or line.startswith("#"):
+ continue
+
+ self.assertIn(line, config_commands)
return test_config_load
def class_name_from_func_name(s):
@@ -69,10 +80,18 @@ if __name__ == '__main__':
config_list.sort()
for config in config_list:
- test_func = make_test_function(config)
+ test_path = os.path.join(config_test_dir, config)
+
+ if not os.path.exists(test_path):
+ test_path = None
+ else:
+ log.info(f'Loaded migration result test for config "{config}"')
+
+ test_func = make_test_function(config, test_path)
func_name = config.replace('-', '_')
klassname = f'TestConfig{class_name_from_func_name(func_name)}'
+
globals()[klassname] = type(klassname,
(DynamicClassBase,),
{f'test_{func_name}': test_func})
diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos
new file mode 100644
index 000000000..ef8bf374a
--- /dev/null
+++ b/smoketest/config-tests/basic-vyos
@@ -0,0 +1,62 @@
+set interfaces ethernet eth0 address '192.168.0.1/24'
+set interfaces ethernet eth0 duplex 'auto'
+set interfaces ethernet eth0 speed 'auto'
+set interfaces ethernet eth1 duplex 'auto'
+set interfaces ethernet eth1 speed 'auto'
+set interfaces ethernet eth2 duplex 'auto'
+set interfaces ethernet eth2 speed 'auto'
+set interfaces ethernet eth2 vif 100 address '100.100.0.1/24'
+set interfaces ethernet eth2 vif-s 200 address '100.64.200.254/24'
+set interfaces ethernet eth2 vif-s 200 vif-c 201 address '100.64.201.254/24'
+set interfaces ethernet eth2 vif-s 200 vif-c 202 address '100.64.202.254/24'
+set interfaces loopback lo
+set protocols static arp interface eth0 address 192.168.0.20 mac '00:50:00:00:00:20'
+set protocols static arp interface eth0 address 192.168.0.30 mac '00:50:00:00:00:30'
+set protocols static arp interface eth0 address 192.168.0.40 mac '00:50:00:00:00:40'
+set protocols static arp interface eth2.100 address 100.100.0.2 mac '00:50:00:00:02:02'
+set protocols static arp interface eth2.100 address 100.100.0.3 mac '00:50:00:00:02:03'
+set protocols static arp interface eth2.100 address 100.100.0.4 mac '00:50:00:00:02:04'
+set protocols static arp interface eth2.200 address 100.64.200.1 mac '00:50:00:00:00:01'
+set protocols static arp interface eth2.200 address 100.64.200.2 mac '00:50:00:00:00:02'
+set protocols static arp interface eth2.200.201 address 100.64.201.10 mac '00:50:00:00:00:10'
+set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50:00:00:00:20'
+set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30'
+set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40'
+set protocols static route 0.0.0.0/0 next-hop 100.64.0.1
+set service dhcp-server shared-network-name LAN authoritative
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.20'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240'
+set service dns forwarding allow-from '192.168.0.0/16'
+set service dns forwarding cache-size '10000'
+set service dns forwarding dnssec 'off'
+set service dns forwarding listen-address '192.168.0.1'
+set service ssh ciphers 'aes128-ctr'
+set service ssh ciphers 'aes192-ctr'
+set service ssh ciphers 'aes256-ctr'
+set service ssh ciphers 'chacha20-poly1305@openssh.com'
+set service ssh ciphers 'rijndael-cbc@lysator.liu.se'
+set service ssh key-exchange 'curve25519-sha256@libssh.org'
+set service ssh key-exchange 'diffie-hellman-group1-sha1'
+set service ssh key-exchange 'diffie-hellman-group-exchange-sha1'
+set service ssh key-exchange 'diffie-hellman-group-exchange-sha256'
+set service ssh listen-address '192.168.0.1'
+set service ssh port '22'
+set system config-management commit-revisions '100'
+set system console device ttyS0 speed '115200'
+set system host-name 'vyos'
+set system name-server '192.168.0.1'
+set system syslog console facility all level 'emerg'
+set system syslog console facility mail level 'info'
+set system syslog global facility all level 'info'
+set system syslog global facility auth level 'info'
+set system syslog global facility local7 level 'debug'
+set system syslog global preserve-fqdn
+set system syslog host syslog.vyos.net facility auth level 'warning'
+set system syslog host syslog.vyos.net facility local7 level 'notice'
+set system syslog host syslog.vyos.net format octet-counted
+set system syslog host syslog.vyos.net port '8000'
+set system time-zone 'Europe/Berlin'
diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn
new file mode 100644
index 000000000..37baee0fd
--- /dev/null
+++ b/smoketest/config-tests/dialup-router-medium-vpn
@@ -0,0 +1,321 @@
+set firewall global-options all-ping 'enable'
+set firewall global-options broadcast-ping 'disable'
+set firewall global-options ip-src-route 'disable'
+set firewall global-options ipv6-receive-redirects 'disable'
+set firewall global-options ipv6-src-route 'disable'
+set firewall global-options log-martians 'enable'
+set firewall global-options receive-redirects 'disable'
+set firewall global-options send-redirects 'enable'
+set firewall global-options source-validation 'disable'
+set firewall global-options syn-cookies 'disable'
+set firewall global-options twa-hazards-protection 'enable'
+set firewall ipv4 name test_tcp_flags rule 1 action 'drop'
+set firewall ipv4 name test_tcp_flags rule 1 protocol 'tcp'
+set firewall ipv4 name test_tcp_flags rule 1 tcp flags ack
+set firewall ipv4 name test_tcp_flags rule 1 tcp flags not fin
+set firewall ipv4 name test_tcp_flags rule 1 tcp flags not rst
+set firewall ipv4 name test_tcp_flags rule 1 tcp flags syn
+set high-availability vrrp group LAN address 192.168.0.1/24
+set high-availability vrrp group LAN hello-source-address '192.168.0.250'
+set high-availability vrrp group LAN interface 'eth1'
+set high-availability vrrp group LAN peer-address '192.168.0.251'
+set high-availability vrrp group LAN priority '200'
+set high-availability vrrp group LAN vrid '1'
+set high-availability vrrp sync-group failover-group member 'LAN'
+set interfaces ethernet eth0 duplex 'auto'
+set interfaces ethernet eth0 mtu '9000'
+set interfaces ethernet eth0 offload gro
+set interfaces ethernet eth0 offload gso
+set interfaces ethernet eth0 offload sg
+set interfaces ethernet eth0 offload tso
+set interfaces ethernet eth0 speed 'auto'
+set interfaces ethernet eth1 address '192.168.0.250/24'
+set interfaces ethernet eth1 duplex 'auto'
+set interfaces ethernet eth1 ip source-validation 'strict'
+set interfaces ethernet eth1 mtu '9000'
+set interfaces ethernet eth1 offload gro
+set interfaces ethernet eth1 offload gso
+set interfaces ethernet eth1 offload sg
+set interfaces ethernet eth1 offload tso
+set interfaces ethernet eth1 speed 'auto'
+set interfaces loopback lo
+set interfaces openvpn vtun0 encryption cipher 'aes256'
+set interfaces openvpn vtun0 hash 'sha512'
+set interfaces openvpn vtun0 ip adjust-mss '1380'
+set interfaces openvpn vtun0 ip source-validation 'strict'
+set interfaces openvpn vtun0 keep-alive failure-count '3'
+set interfaces openvpn vtun0 keep-alive interval '30'
+set interfaces openvpn vtun0 mode 'client'
+set interfaces openvpn vtun0 openvpn-option 'comp-lzo adaptive'
+set interfaces openvpn vtun0 openvpn-option 'fast-io'
+set interfaces openvpn vtun0 openvpn-option 'persist-key'
+set interfaces openvpn vtun0 openvpn-option 'reneg-sec 86400'
+set interfaces openvpn vtun0 persistent-tunnel
+set interfaces openvpn vtun0 remote-host '192.0.2.10'
+set interfaces openvpn vtun0 tls auth-key 'openvpn_vtun0_auth'
+set interfaces openvpn vtun0 tls ca-certificate 'openvpn_vtun0_1'
+set interfaces openvpn vtun0 tls ca-certificate 'openvpn_vtun0_2'
+set interfaces openvpn vtun0 tls certificate 'openvpn_vtun0'
+set interfaces openvpn vtun1 authentication password 'vyos1'
+set interfaces openvpn vtun1 authentication username 'vyos1'
+set interfaces openvpn vtun1 encryption cipher 'aes256'
+set interfaces openvpn vtun1 hash 'sha1'
+set interfaces openvpn vtun1 ip adjust-mss '1380'
+set interfaces openvpn vtun1 keep-alive failure-count '3'
+set interfaces openvpn vtun1 keep-alive interval '30'
+set interfaces openvpn vtun1 mode 'client'
+set interfaces openvpn vtun1 openvpn-option 'comp-lzo adaptive'
+set interfaces openvpn vtun1 openvpn-option 'tun-mtu 1500'
+set interfaces openvpn vtun1 openvpn-option 'tun-mtu-extra 32'
+set interfaces openvpn vtun1 openvpn-option 'mssfix 1300'
+set interfaces openvpn vtun1 openvpn-option 'persist-key'
+set interfaces openvpn vtun1 openvpn-option 'mute 10'
+set interfaces openvpn vtun1 openvpn-option 'route-nopull'
+set interfaces openvpn vtun1 openvpn-option 'fast-io'
+set interfaces openvpn vtun1 openvpn-option 'reneg-sec 86400'
+set interfaces openvpn vtun1 persistent-tunnel
+set interfaces openvpn vtun1 protocol 'udp'
+set interfaces openvpn vtun1 remote-host '01.foo.com'
+set interfaces openvpn vtun1 remote-port '1194'
+set interfaces openvpn vtun1 tls auth-key 'openvpn_vtun1_auth'
+set interfaces openvpn vtun1 tls ca-certificate 'openvpn_vtun1_1'
+set interfaces openvpn vtun1 tls ca-certificate 'openvpn_vtun1_2'
+set interfaces openvpn vtun2 authentication password 'vyos2'
+set interfaces openvpn vtun2 authentication username 'vyos2'
+set interfaces openvpn vtun2 disable
+set interfaces openvpn vtun2 encryption cipher 'aes256'
+set interfaces openvpn vtun2 hash 'sha512'
+set interfaces openvpn vtun2 ip adjust-mss '1380'
+set interfaces openvpn vtun2 keep-alive failure-count '3'
+set interfaces openvpn vtun2 keep-alive interval '30'
+set interfaces openvpn vtun2 mode 'client'
+set interfaces openvpn vtun2 openvpn-option 'tun-mtu 1500'
+set interfaces openvpn vtun2 openvpn-option 'tun-mtu-extra 32'
+set interfaces openvpn vtun2 openvpn-option 'mssfix 1300'
+set interfaces openvpn vtun2 openvpn-option 'persist-key'
+set interfaces openvpn vtun2 openvpn-option 'mute 10'
+set interfaces openvpn vtun2 openvpn-option 'route-nopull'
+set interfaces openvpn vtun2 openvpn-option 'fast-io'
+set interfaces openvpn vtun2 openvpn-option 'remote-random'
+set interfaces openvpn vtun2 openvpn-option 'reneg-sec 86400'
+set interfaces openvpn vtun2 persistent-tunnel
+set interfaces openvpn vtun2 protocol 'udp'
+set interfaces openvpn vtun2 remote-host '01.myvpn.com'
+set interfaces openvpn vtun2 remote-host '02.myvpn.com'
+set interfaces openvpn vtun2 remote-host '03.myvpn.com'
+set interfaces openvpn vtun2 remote-port '1194'
+set interfaces openvpn vtun2 tls auth-key 'openvpn_vtun2_auth'
+set interfaces openvpn vtun2 tls ca-certificate 'openvpn_vtun2_1'
+set interfaces pppoe pppoe0 authentication password 'password'
+set interfaces pppoe pppoe0 authentication username 'vyos'
+set interfaces pppoe pppoe0 mtu '1500'
+set interfaces pppoe pppoe0 source-interface 'eth0'
+set interfaces wireguard wg0 address '192.168.10.1/24'
+set interfaces wireguard wg0 ip adjust-mss '1380'
+set interfaces wireguard wg0 peer blue allowed-ips '192.168.10.3/32'
+set interfaces wireguard wg0 peer blue persistent-keepalive '20'
+set interfaces wireguard wg0 peer blue preshared-key 'ztFDOY9UyaDvn8N3X97SFMDwIfv7EEfuUIPP2yab6UI='
+set interfaces wireguard wg0 peer blue public-key 'G4pZishpMRrLmd96Kr6V7LIuNGdcUb81gWaYZ+FWkG0='
+set interfaces wireguard wg0 peer green allowed-ips '192.168.10.21/32'
+set interfaces wireguard wg0 peer green persistent-keepalive '25'
+set interfaces wireguard wg0 peer green preshared-key 'LQ9qmlTh9G4nZu4UgElxRUwg7JB/qoV799aADJOijnY='
+set interfaces wireguard wg0 peer green public-key '5iQUD3VoCDBTPXAPHOwUJ0p7xzKGHEY/wQmgvBVmaFI='
+set interfaces wireguard wg0 peer pink allowed-ips '192.168.10.14/32'
+set interfaces wireguard wg0 peer pink allowed-ips '192.168.10.16/32'
+set interfaces wireguard wg0 peer pink persistent-keepalive '25'
+set interfaces wireguard wg0 peer pink preshared-key 'Qi9Odyx0/5itLPN5C5bEy3uMX+tmdl15QbakxpKlWqQ='
+set interfaces wireguard wg0 peer pink public-key 'i4qNPmxyy9EETL4tIoZOLKJF4p7IlVmpAE15gglnAk4='
+set interfaces wireguard wg0 peer red allowed-ips '192.168.10.4/32'
+set interfaces wireguard wg0 peer red persistent-keepalive '20'
+set interfaces wireguard wg0 peer red preshared-key 'CumyXX7osvUT9AwnS+m2TEfCaL0Ptc2LfuZ78Sujuk8='
+set interfaces wireguard wg0 peer red public-key 'ALGWvMJCKpHF2tVH3hEIHqUe9iFfAmZATUUok/WQzks='
+set interfaces wireguard wg0 port '7777'
+set interfaces wireguard wg1 address '10.89.90.2/30'
+set interfaces wireguard wg1 ip adjust-mss '1380'
+set interfaces wireguard wg1 peer sam address '192.0.2.45'
+set interfaces wireguard wg1 peer sam allowed-ips '10.1.1.0/24'
+set interfaces wireguard wg1 peer sam allowed-ips '10.89.90.1/32'
+set interfaces wireguard wg1 peer sam persistent-keepalive '20'
+set interfaces wireguard wg1 peer sam port '1200'
+set interfaces wireguard wg1 peer sam preshared-key 'XpFtzx2Z+nR8pBv9/sSf7I94OkZkVYTz0AeU5Q/QQUE='
+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 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 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 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 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 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 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 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 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 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 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 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 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 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 translation address 'masquerade'
+set nat source rule 2000 outbound-interface '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 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'
+set policy prefix-list user2-routes rule 1 action 'permit'
+set policy prefix-list user2-routes rule 1 prefix '10.1.1.0/24'
+set policy route LAN-POLICY-BASED-ROUTING interface 'eth1'
+set policy route LAN-POLICY-BASED-ROUTING rule 10 destination
+set policy route LAN-POLICY-BASED-ROUTING rule 10 disable
+set policy route LAN-POLICY-BASED-ROUTING rule 10 set table '10'
+set policy route LAN-POLICY-BASED-ROUTING rule 10 source address '192.168.0.119/32'
+set policy route LAN-POLICY-BASED-ROUTING rule 20 destination
+set policy route LAN-POLICY-BASED-ROUTING rule 20 set table '100'
+set policy route LAN-POLICY-BASED-ROUTING rule 20 source address '192.168.0.240'
+set policy route-map rm-static-to-bgp rule 10 action 'permit'
+set policy route-map rm-static-to-bgp rule 10 match ip address prefix-list 'user1-routes'
+set policy route-map rm-static-to-bgp rule 100 action 'deny'
+set policy route6 LAN6-POLICY-BASED-ROUTING interface 'eth1'
+set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 destination
+set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 disable
+set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 set table '10'
+set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 source address '2002::1'
+set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 destination
+set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 set table '100'
+set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 source address '2008::f'
+set protocols bgp address-family ipv4-unicast redistribute connected route-map 'rm-static-to-bgp'
+set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast nexthop-self
+set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast prefix-list export 'user1-routes'
+set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast prefix-list import 'user2-routes'
+set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast soft-reconfiguration inbound
+set protocols bgp neighbor 10.89.90.1 password 'ericandre2020'
+set protocols bgp neighbor 10.89.90.1 remote-as '64589'
+set protocols bgp parameters log-neighbor-changes
+set protocols bgp parameters router-id '10.89.90.2'
+set protocols bgp system-as '64590'
+set protocols static route 100.64.160.23/32 interface pppoe0
+set protocols static route 100.64.165.25/32 interface pppoe0
+set protocols static route 100.64.165.26/32 interface pppoe0
+set protocols static route 100.64.198.0/24 interface vtun0
+set protocols static table 10 route 0.0.0.0/0 interface vtun1
+set protocols static table 100 route 0.0.0.0/0 next-hop 192.168.10.5
+set service conntrack-sync accept-protocol 'tcp'
+set service conntrack-sync accept-protocol 'udp'
+set service conntrack-sync accept-protocol 'icmp'
+set service conntrack-sync disable-external-cache
+set service conntrack-sync event-listen-queue-size '8'
+set service conntrack-sync expect-sync 'all'
+set service conntrack-sync failover-mechanism vrrp sync-group 'failover-group'
+set service conntrack-sync interface eth1 peer '192.168.0.251'
+set service conntrack-sync sync-queue-size '8'
+set service dhcp-server failover name 'DHCP02'
+set service dhcp-server failover remote '192.168.0.251'
+set service dhcp-server failover source-address '192.168.0.250'
+set service dhcp-server failover status 'primary'
+set service dhcp-server shared-network-name LAN authoritative
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 enable-failover
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 lease '86400'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.200'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio ip-address '192.168.0.107'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio mac-address '00:50:01:dc:91:14'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV ip-address '192.168.0.104'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV mac-address '00:50:01:31:b5:f6'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus ip-address '192.168.0.60'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus mac-address '00:50:01:58:ac:95'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 ip-address '192.168.0.109'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 mac-address '00:50:01:bc:ac:51'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 ip-address '192.168.0.11'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 mac-address '00:50:01:70:b9:4d'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 ip-address '192.168.0.12'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 mac-address '00:50:01:70:b7:4f'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV ip-address '192.168.0.101'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV mac-address '00:50:01:ba:62:79'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand ip-address '192.168.0.110'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand mac-address '00:50:01:af:c5:d2'
+set service dns forwarding allow-from '192.168.0.0/16'
+set service dns forwarding cache-size '8192'
+set service dns forwarding dnssec 'off'
+set service dns forwarding listen-address '192.168.0.1'
+set service dns forwarding name-server 100.64.0.1
+set service dns forwarding name-server 100.64.0.2
+set service ntp allow-client address '192.168.0.0/16'
+set service ntp server nz.pool.ntp.org prefer
+set service snmp community AwesomeCommunity authorization 'ro'
+set service snmp community AwesomeCommunity client '127.0.0.1'
+set service snmp community AwesomeCommunity network '192.168.0.0/24'
+set service ssh access-control allow user 'vyos'
+set service ssh client-keepalive-interval '60'
+set service ssh listen-address '192.168.0.1'
+set service ssh listen-address '192.168.10.1'
+set service ssh listen-address '192.168.0.250'
+set system config-management commit-revisions '100'
+set system console device ttyS0 speed '115200'
+set system host-name 'vyos'
+set system ip arp table-size '1024'
+set system name-server '192.168.0.1'
+set system name-server 'pppoe0'
+set system option ctrl-alt-delete 'ignore'
+set system option reboot-on-panic
+set system option startup-beep
+set system static-host-mapping host-name host60.vyos.net inet '192.168.0.60'
+set system static-host-mapping host-name host104.vyos.net inet '192.168.0.104'
+set system static-host-mapping host-name host107.vyos.net inet '192.168.0.107'
+set system static-host-mapping host-name host109.vyos.net inet '192.168.0.109'
+set system sysctl parameter net.core.default_qdisc value 'fq'
+set system sysctl parameter net.ipv4.tcp_congestion_control value 'bbr'
+set system syslog global facility all level 'info'
+set system syslog host 192.168.0.252 facility all level 'debug'
+set system syslog host 192.168.0.252 protocol 'udp'
+set system task-scheduler task Update-Blacklists executable path '/config/scripts/vyos-foo-update.script'
+set system task-scheduler task Update-Blacklists interval '3h'
+set system time-zone 'Pacific/Auckland'
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index 033c1a518..78dba3ee2 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -116,6 +116,18 @@ system {
speed 115200
}
}
+ conntrack {
+ ignore {
+ rule 1 {
+ destination {
+ address 192.0.2.2
+ }
+ source {
+ address 192.0.2.1
+ }
+ }
+ }
+ }
host-name vyos
login {
user vyos {
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 820024dc9..51ccbc9e6 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -834,8 +834,12 @@ class BasicInterfaceTest:
self.assertEqual('1', tmp)
if cli_defined(self._base_path + ['ip'], 'source-validation'):
- tmp = read_file(f'{proc_base}/rp_filter')
- self.assertEqual('2', tmp)
+ base_options = f'iifname "{interface}"'
+ out = cmd('sudo nft list chain ip raw vyos_rpfilter')
+ for line in out.splitlines():
+ if line.startswith(base_options):
+ self.assertIn('fib saddr oif 0', line)
+ self.assertIn('drop', line)
def test_interface_ipv6_options(self):
if not self._test_ipv6:
diff --git a/smoketest/scripts/cli/test_dependency_graph.py b/smoketest/scripts/cli/test_dependency_graph.py
deleted file mode 100755
index 45a40acc4..000000000
--- a/smoketest/scripts/cli/test_dependency_graph.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import json
-import unittest
-from graphlib import TopologicalSorter, CycleError
-
-DEP_FILE = '/usr/share/vyos/config-mode-dependencies.json'
-
-def graph_from_dict(d):
- g = {}
- for k in list(d):
- g[k] = set()
- # add the dependencies for every sub-case; should there be cases
- # that are mutally exclusive in the future, the graphs will be
- # distinguished
- for el in list(d[k]):
- g[k] |= set(d[k][el])
- return g
-
-class TestDependencyGraph(unittest.TestCase):
- def setUp(self):
- with open(DEP_FILE) as f:
- dd = json.load(f)
- self.dependency_graph = graph_from_dict(dd)
-
- def test_cycles(self):
- ts = TopologicalSorter(self.dependency_graph)
- out = None
- try:
- # get node iterator
- order = ts.static_order()
- # try iteration
- _ = [*order]
- except CycleError as e:
- out = e.args
-
- self.assertIsNone(out)
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 92fe322ce..f74ce4b72 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -564,23 +564,27 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
def test_source_validation(self):
# Strict
self.cli_set(['firewall', 'global-options', 'source-validation', 'strict'])
+ self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict'])
self.cli_commit()
nftables_strict_search = [
['fib saddr . iif oif 0', 'drop']
]
- self.verify_nftables(nftables_strict_search, 'inet vyos_global_rpfilter')
+ self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter')
+ self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter')
# Loose
self.cli_set(['firewall', 'global-options', 'source-validation', 'loose'])
+ self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose'])
self.cli_commit()
nftables_loose_search = [
['fib saddr oif 0', 'drop']
]
- self.verify_nftables(nftables_loose_search, 'inet vyos_global_rpfilter')
+ self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter')
+ self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter')
def test_sysfs(self):
for name, conf in sysfs_config.items():
diff --git a/smoketest/scripts/cli/test_interfaces_netns.py b/smoketest/scripts/cli/test_netns.py
index b8bebb221..fd04dd520 100755
--- a/smoketest/scripts/cli/test_interfaces_netns.py
+++ b/smoketest/scripts/cli/test_netns.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-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
@@ -16,7 +16,6 @@
import unittest
-from netifaces import interfaces
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSession
@@ -24,56 +23,61 @@ from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.utils.process import cmd
+from vyos.utils.network import is_netns_interface
+from vyos.utils.network import get_netns_all
base_path = ['netns']
-namespaces = ['mgmt', 'front', 'back', 'ams-ix']
+interfaces = ['dum10', 'dum12', 'dum50']
-class NETNSTest(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self._interfaces = ['dum10', 'dum12', 'dum50']
+class NetNSTest(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ self.cli_delete(base_path)
+ # commit changes
+ self.cli_commit()
+
+ # There should be no network namespace remaining
+ tmp = cmd('ip netns ls')
+ self.assertFalse(tmp)
+
+ super(NetNSTest, self).tearDown()
- def test_create_netns(self):
+ def test_netns_create(self):
+ namespaces = ['mgmt', 'front', 'back']
for netns in namespaces:
- base = base_path + ['name', netns]
- self.cli_set(base)
+ self.cli_set(base_path + ['name', netns])
# commit changes
self.cli_commit()
- netns_list = cmd('ip netns ls')
-
# Verify NETNS configuration
for netns in namespaces:
- self.assertTrue(netns in netns_list)
-
+ self.assertIn(netns, get_netns_all())
- def test_netns_assign_interface(self):
+ def test_netns_interface(self):
netns = 'foo'
- self.cli_set(['netns', 'name', netns])
+ self.cli_set(base_path + ['name', netns])
# Set
- for iface in self._interfaces:
+ for iface in interfaces:
self.cli_set(['interfaces', 'dummy', iface, 'netns', netns])
# commit changes
self.cli_commit()
- netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show')
-
- for iface in self._interfaces:
- self.assertTrue(iface in netns_iface_list)
+ for interface in interfaces:
+ self.assertTrue(is_netns_interface(interface, netns))
# Delete
- for iface in self._interfaces:
- self.cli_delete(['interfaces', 'dummy', iface, 'netns', netns])
+ for interface in interfaces:
+ self.cli_delete(['interfaces', 'dummy', interface])
# commit changes
self.cli_commit()
netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show')
- for iface in self._interfaces:
- self.assertNotIn(iface, netns_iface_list)
+ for interface in interfaces:
+ self.assertFalse(is_netns_interface(interface, netns))
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index 2a89aa98b..ea304783d 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -35,6 +35,17 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ def verify_nftables(self, nftables_search, table, inverse=False, args=''):
+ nftables_output = cmd(f'sudo nft {args} list table {table}')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
def test_conntrack_options(self):
conntrack_config = {
'net.netfilter.nf_conntrack_expect_max' : {
@@ -232,5 +243,51 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf')
self.assertIn(hash_size_default, tmp)
+ def test_conntrack_ignore(self):
+ address_group = 'conntracktest'
+ address_group_member = '192.168.0.1'
+ ipv6_address_group = 'conntracktest6'
+ ipv6_address_group_member = 'dead:beef::1'
+
+ self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member])
+ self.cli_set(['firewall', 'group', 'ipv6-address-group', ipv6_address_group, 'address', ipv6_address_group_member])
+
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1'])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2'])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'port', '22'])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'protocol', 'tcp'])
+
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1'])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group])
+
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'port', '22'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'protocol', 'tcp'])
+
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'source', 'address', 'fe80::1'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'destination', 'group', 'address-group', ipv6_address_group])
+
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'source', 'address', 'fe80::1'])
+ self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'destination', 'address', '!fe80::3'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'notrack'],
+ ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack']
+ ]
+
+ nftables6_search = [
+ ['ip6 saddr fe80::1', 'ip6 daddr fe80::2', 'tcp dport 22', 'notrack'],
+ ['ip6 saddr fe80::1', 'ip6 daddr @A6_conntracktest6', 'notrack'],
+ ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack']
+ ]
+
+ self.verify_nftables(nftables_search, 'raw')
+ self.verify_nftables(nftables6_search, 'ip6 raw')
+
+ self.cli_delete(['firewall'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index 9c43640a9..a0de914bc 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -24,7 +24,9 @@ from vyos.firewall import find_nftables_rule
from vyos.firewall import remove_nftables_rule
from vyos.utils.process import process_named_running
from vyos.utils.dict import dict_search
+from vyos.utils.dict import dict_search_args
from vyos.utils.process import cmd
+from vyos.utils.process import rc_cmd
from vyos.utils.process import run
from vyos.template import render
from vyos import ConfigError
@@ -62,6 +64,13 @@ module_map = {
},
}
+valid_groups = [
+ 'address_group',
+ 'domain_group',
+ 'network_group',
+ 'port_group'
+]
+
def resync_conntrackd():
tmp = run('/usr/libexec/vyos/conf_mode/conntrack_sync.py')
if tmp > 0:
@@ -78,15 +87,53 @@ def get_config(config=None):
get_first_key=True,
with_recursive_defaults=True)
+ conntrack['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
return conntrack
def verify(conntrack):
- if dict_search('ignore.rule', conntrack) != None:
- for rule, rule_config in conntrack['ignore']['rule'].items():
- if dict_search('destination.port', rule_config) or \
- dict_search('source.port', rule_config):
- if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']:
- raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}')
+ for inet in ['ipv4', 'ipv6']:
+ if dict_search_args(conntrack, 'ignore', inet, 'rule') != None:
+ for rule, rule_config in conntrack['ignore'][inet]['rule'].items():
+ if dict_search('destination.port', rule_config) or \
+ dict_search('destination.group.port_group', rule_config) or \
+ dict_search('source.port', rule_config) or \
+ dict_search('source.group.port_group', rule_config):
+ if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']:
+ raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}')
+
+ for side in ['destination', 'source']:
+ if side in rule_config:
+ side_conf = rule_config[side]
+
+ if 'group' in side_conf:
+ if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
+ raise ConfigError('Only one address-group, network-group or domain-group can be specified')
+
+ for group in valid_groups:
+ if group in side_conf['group']:
+ group_name = side_conf['group'][group]
+ error_group = group.replace("_", "-")
+
+ if group in ['address_group', 'network_group', 'domain_group']:
+ if 'address' in side_conf:
+ raise ConfigError(f'{error_group} and address cannot both be defined')
+
+ if group_name and group_name[0] == '!':
+ group_name = group_name[1:]
+
+ if inet == 'ipv6':
+ group = f'ipv6_{group}'
+
+ group_obj = dict_search_args(conntrack['firewall_group'], group, group_name)
+
+ if group_obj is None:
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule')
+
+ if not group_obj:
+ Warning(f'{error_group} "{group_name}" has no members!')
return None
@@ -94,26 +141,18 @@ def generate(conntrack):
render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack)
render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack)
render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack)
-
- # dry-run newly generated configuration
- tmp = run(f'nft -c -f {nftables_ct_file}')
- if tmp > 0:
- if os.path.exists(nftables_ct_file):
- os.unlink(nftables_ct_file)
- raise ConfigError('Configuration file errors encountered!')
-
return None
-def find_nftables_ct_rule(rule):
+def find_nftables_ct_rule(table, chain, rule):
helper_search = re.search('ct helper set "(\w+)"', rule)
if helper_search:
rule = helper_search[1]
- return find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule])
+ return find_nftables_rule(table, chain, [rule])
-def find_remove_rule(rule):
- handle = find_nftables_ct_rule(rule)
+def find_remove_rule(table, chain, rule):
+ handle = find_nftables_ct_rule(table, chain, rule)
if handle:
- remove_nftables_rule('raw', 'VYOS_CT_HELPER', handle)
+ remove_nftables_rule(table, chain, handle)
def apply(conntrack):
# Depending on the enable/disable state of the ALG (Application Layer Gateway)
@@ -127,18 +166,24 @@ def apply(conntrack):
cmd(f'rmmod {mod}')
if 'nftables' in module_config:
for rule in module_config['nftables']:
- find_remove_rule(rule)
+ find_remove_rule('raw', 'VYOS_CT_HELPER', rule)
+ find_remove_rule('ip6 raw', 'VYOS_CT_HELPER', rule)
else:
if 'ko' in module_config:
for mod in module_config['ko']:
cmd(f'modprobe {mod}')
if 'nftables' in module_config:
for rule in module_config['nftables']:
- if not find_nftables_ct_rule(rule):
- cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}')
+ if not find_nftables_ct_rule('raw', 'VYOS_CT_HELPER', rule):
+ cmd(f'nft insert rule raw VYOS_CT_HELPER {rule}')
+
+ if not find_nftables_ct_rule('ip6 raw', 'VYOS_CT_HELPER', rule):
+ cmd(f'nft insert rule ip6 raw VYOS_CT_HELPER {rule}')
# Load new nftables ruleset
- cmd(f'nft -f {nftables_ct_file}')
+ install_result, output = rc_cmd(f'nft -f {nftables_ct_file}')
+ if install_result == 1:
+ raise ConfigError(f'Failed to apply configuration: {output}')
if process_named_running('conntrackd'):
# Reload conntrack-sync daemon to fetch new sysctl values
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index 0121df11c..70f43ab52 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
import time
from sys import exit
@@ -24,6 +25,7 @@ from ipaddress import IPv6Interface
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdict import leaf_node_changed
from vyos.ifconfig.vrrp import VRRP
from vyos.template import render
from vyos.template import is_ipv4
@@ -35,6 +37,9 @@ from vyos import airbag
airbag.enable()
+systemd_override = r'/run/systemd/system/keepalived.service.d/10-override.conf'
+
+
def get_config(config=None):
if config:
conf = config
@@ -54,6 +59,9 @@ def get_config(config=None):
if conf.exists(conntrack_path):
ha['conntrack_sync_group'] = conf.return_value(conntrack_path)
+ if leaf_node_changed(conf, base + ['vrrp', 'disable-snmp']):
+ ha.update({'restart_required': {}})
+
return ha
def verify(ha):
@@ -164,19 +172,23 @@ def verify(ha):
def generate(ha):
if not ha or 'disable' in ha:
+ if os.path.isfile(systemd_override):
+ os.unlink(systemd_override)
return None
render(VRRP.location['config'], 'high-availability/keepalived.conf.j2', ha)
+ render(systemd_override, 'high-availability/10-override.conf.j2', ha)
return None
def apply(ha):
service_name = 'keepalived.service'
+ call('systemctl daemon-reload')
if not ha or 'disable' in ha:
call(f'systemctl stop {service_name}')
return None
# Check if IPv6 address is tentative T5533
- for group, group_config in ha['vrrp']['group'].items():
+ for group, group_config in ha.get('vrrp', {}).get('group', {}).items():
if 'hello_source_address' in group_config:
if is_ipv6(group_config['hello_source_address']):
ipv6_address = group_config['hello_source_address']
@@ -187,7 +199,11 @@ def apply(ha):
if is_ipv6_tentative(interface, ipv6_address):
time.sleep(interval)
- call(f'systemctl reload-or-restart {service_name}')
+ systemd_action = 'reload-or-restart'
+ if 'restart_required' in ha:
+ systemd_action = 'restart'
+
+ call(f'systemctl {systemd_action} {service_name}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py
index e771581e1..db768b94d 100755
--- a/src/conf_mode/interfaces-dummy.py
+++ b/src/conf_mode/interfaces-dummy.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2021 VyOS maintainers and contributors
+# 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
@@ -55,7 +55,7 @@ def generate(dummy):
return None
def apply(dummy):
- d = DummyIf(dummy['ifname'])
+ d = DummyIf(**dummy)
# Remove dummy interface
if 'deleted' in dummy:
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 9da7fbe80..08e96f10b 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -112,7 +112,7 @@ def verify_rule(config, err_msg, groups_dict):
group_obj = dict_search_args(groups_dict, group, group_name)
if group_obj is None:
- raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on nat rule')
if not group_obj:
Warning(f'{error_group} "{group_name}" has no members!')
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index f5d84be4b..ad43390bb 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -110,3 +110,7 @@ net.ipv6.neigh.default.gc_thresh3 = 8192
# Enable global RFS (Receive Flow Steering) configuration. RFS is inactive
# until explicitly configured at the interface level
net.core.rps_sock_flow_entries = 32768
+
+# Congestion control
+net.core.default_qdisc=fq
+net.ipv4.tcp_congestion_control=bbr
diff --git a/src/helpers/config_dependency.py b/src/helpers/config_dependency.py
new file mode 100755
index 000000000..50c72956e
--- /dev/null
+++ b/src/helpers/config_dependency.py
@@ -0,0 +1,58 @@
+#!/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 os
+import sys
+from argparse import ArgumentParser
+from argparse import ArgumentTypeError
+
+try:
+ from vyos.configdep import check_dependency_graph
+ from vyos.defaults import directories
+except ImportError:
+ # allow running during addon package build
+ _here = os.path.dirname(__file__)
+ sys.path.append(os.path.join(_here, '../../python/vyos'))
+ from configdep import check_dependency_graph
+ from defaults import directories
+
+# addon packages will need to specify the dependency directory
+dependency_dir = os.path.join(directories['data'],
+ 'config-mode-dependencies')
+
+def path_exists(s):
+ if not os.path.exists(s):
+ raise ArgumentTypeError("Must specify a valid vyos-1x dependency directory")
+ return s
+
+def main():
+ parser = ArgumentParser(description='generate and save dict from xml defintions')
+ parser.add_argument('--dependency-dir', type=path_exists,
+ default=dependency_dir,
+ help='location of vyos-1x dependency directory')
+ parser.add_argument('--supplement', type=str,
+ help='supplemental dependency file')
+ args = vars(parser.parse_args())
+
+ if not check_dependency_graph(**args):
+ sys.exit(1)
+
+ sys.exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py
index 7e2fe2462..eac3d37af 100755
--- a/src/helpers/vyos-domain-resolver.py
+++ b/src/helpers/vyos-domain-resolver.py
@@ -37,12 +37,14 @@ domain_state = {}
ipv4_tables = {
'ip vyos_mangle',
'ip vyos_filter',
- 'ip vyos_nat'
+ 'ip vyos_nat',
+ 'ip raw'
}
ipv6_tables = {
'ip6 vyos_mangle',
- 'ip6 vyos_filter'
+ 'ip6 vyos_filter',
+ 'ip6 raw'
}
def get_config(conf):
diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py
index 2812155e8..8af4a7916 100755
--- a/src/helpers/vyos-save-config.py
+++ b/src/helpers/vyos-save-config.py
@@ -44,7 +44,10 @@ ct = config.get_config_tree(effective=True)
write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name
with open(write_file, 'w') as f:
- f.write(ct.to_string())
+ # config_tree is None before boot configuration is complete;
+ # automated saves should check boot_configuration_complete
+ if ct is not None:
+ f.write(ct.to_string())
f.write("\n")
f.write(system_footer())
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 96f163213..a5d1a31fa 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -335,6 +335,9 @@ start ()
nfct helper add rpc inet tcp
nfct helper add rpc inet udp
nfct helper add tns inet tcp
+ nfct helper add rpc inet6 tcp
+ nfct helper add rpc inet6 udp
+ nfct helper add tns inet6 tcp
nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules"
rm -f /etc/hostname
diff --git a/src/migration-scripts/conntrack/3-to-4 b/src/migration-scripts/conntrack/3-to-4
new file mode 100755
index 000000000..e90c383af
--- /dev/null
+++ b/src/migration-scripts/conntrack/3-to-4
@@ -0,0 +1,50 @@
+#!/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/>.
+
+# Add support for IPv6 conntrack ignore, move existing nodes to `system conntrack ignore ipv4`
+
+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 = ['system', 'conntrack']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['ignore', 'rule']):
+ config.set(base + ['ignore', 'ipv4'])
+ config.copy(base + ['ignore', 'rule'], base + ['ignore', 'ipv4', 'rule'])
+ config.delete(base + ['ignore', 'rule'])
+
+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/system/13-to-14 b/src/migration-scripts/system/13-to-14
index 1fa781869..5b781158b 100755
--- a/src/migration-scripts/system/13-to-14
+++ b/src/migration-scripts/system/13-to-14
@@ -34,7 +34,7 @@ else:
# retrieve all valid timezones
try:
- tz_datas = cmd('find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/::')
+ tz_datas = cmd('timedatectl list-timezones')
except OSError:
tz_datas = ''
tz_data = tz_datas.split('\n')
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index 57d3cfed9..44d41219e 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -779,6 +779,45 @@ def show_ra_summary(raw: bool):
return _get_formatted_output_ra_summary(list_sa)
+# PSK block
+def _get_raw_psk():
+ conf: ConfigTreeQuery = ConfigTreeQuery()
+ config_path = ['vpn', 'ipsec', 'authentication', 'psk']
+ psk_config = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ psk_list = []
+ for psk, psk_data in psk_config.items():
+ psk_data['psk'] = psk
+ psk_list.append(psk_data)
+
+ return psk_list
+
+
+def _get_formatted_psk(psk_list):
+ headers = ["PSK", "Id", "Secret"]
+ formatted_data = []
+
+ for psk_data in psk_list:
+ formatted_data.append([psk_data["psk"], "\n".join(psk_data["id"]), psk_data["secret"]])
+
+ return tabulate(formatted_data, headers=headers)
+
+
+def show_psk(raw: bool):
+ config = ConfigTreeQuery()
+ if not config.exists('vpn ipsec authentication psk'):
+ raise vyos.opmode.UnconfiguredSubsystem('VPN ipsec psk authentication is not configured')
+
+ psk = _get_raw_psk()
+ if raw:
+ return psk
+ return _get_formatted_psk(psk)
+
+# PSK block end
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/otp.py b/src/op_mode/otp.py
new file mode 100755
index 000000000..c472a1ed3
--- /dev/null
+++ b/src/op_mode/otp.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+# Copyright 2017, 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+
+import sys
+import os
+import vyos.opmode
+from jinja2 import Template
+from vyos.configquery import ConfigTreeQuery
+from vyos.xml import defaults
+from vyos.configdict import dict_merge
+from vyos.util import popen
+
+
+users_otp_template = Template("""
+{% if info == "full" %}
+# You can share it with the user, he just needs to scan the QR in his OTP app
+# username: {{username}}
+# OTP KEY: {{key_base32}}
+# OTP URL: {{otp_url}}
+{{qrcode}}
+# To add this OTP key to configuration, run the following commands:
+set system login user {{username}} authentication otp key '{{key_base32}}'
+{% if rate_limit != "3" %}
+set system login user {{username}} authentication otp rate-limit '{{rate_limit}}'
+{% endif %}
+{% if rate_time != "30" %}
+set system login user {{username}} authentication otp rate-time '{{rate_time}}'
+{% endif %}
+{% if window_size != "3" %}
+set system login user {{username}} authentication otp window-size '{{window_size}}'
+{% endif %}
+{% elif info == "key-b32" %}
+# OTP key in Base32 for system user {{username}}:
+{{key_base32}}
+{% elif info == "qrcode" %}
+# QR code for system user '{{username}}'
+{{qrcode}}
+{% elif info == "uri" %}
+# URI for system user '{{username}}'
+{{otp_url}}
+{% endif %}
+""", trim_blocks=True, lstrip_blocks=True)
+
+
+def _check_uname_otp(username:str):
+ """
+ Check if "username" exists and have an OTP key
+ """
+ config = ConfigTreeQuery()
+ base_key = ['system', 'login', 'user', username, 'authentication', 'otp', 'key']
+ if not config.exists(base_key):
+ return None
+ return True
+
+def _get_login_otp(username: str, info:str):
+ """
+ Retrieve user settings from configuration and set some defaults
+ """
+ config = ConfigTreeQuery()
+ base = ['system', 'login', 'user', username]
+ if not config.exists(base):
+ return None
+ user_otp = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(['system', 'login', 'user'])
+ user_otp = dict_merge(default_values, user_otp)
+ result = user_otp['authentication']['otp']
+ # Filling in the system and default options
+ result['info'] = info
+ result['hostname'] = os.uname()[1]
+ result['username'] = username
+ result['key_base32'] = result['key']
+ result['otp_length'] = '6'
+ result['interval'] = '30'
+ result['token_type'] = 'hotp-time'
+ if result['token_type'] == 'hotp-time':
+ token_type_acrn = 'totp'
+ result['otp_url'] = ''.join(["otpauth://",token_type_acrn,"/",username,"@",\
+ result['hostname'],"?secret=",result['key_base32'],"&digits=",\
+ result['otp_length'],"&period=",result['interval']])
+ result['qrcode'],err = popen('qrencode -t ansiutf8', input=result['otp_url'])
+ return result
+
+def show_login(raw: bool, username: str, info:str):
+ '''
+ Display OTP parameters for <username>
+ '''
+ check_otp = _check_uname_otp(username)
+ if check_otp:
+ user_otp_params = _get_login_otp(username, info)
+ else:
+ print(f'There is no such user ("{username}") with an OTP key configured')
+ print('You can use the following command to generate a key for a user:\n')
+ print(f'generate system login username {username} otp-key hotp-time')
+ sys.exit(0)
+ if raw:
+ return user_otp_params
+ return users_otp_template.render(user_otp_params)
+
+
+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/pam-configs/radius b/src/pam-configs/radius
index 08247f77c..eee9cb93e 100644
--- a/src/pam-configs/radius
+++ b/src/pam-configs/radius
@@ -3,15 +3,18 @@ Default: no
Priority: 257
Auth-Type: Primary
Auth:
+ [default=ignore success=2] pam_succeed_if.so service = sudo
[default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
[authinfo_unavail=ignore success=end default=ignore] pam_radius_auth.so
Account-Type: Primary
Account:
+ [default=ignore success=2] pam_succeed_if.so service = sudo
[default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
[authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_radius_auth.so
Session-Type: Additional
Session:
+ [default=ignore success=2] pam_succeed_if.so service = sudo
[default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
[authinfo_unavail=ignore success=ok default=ignore] pam_radius_auth.so
diff --git a/src/tests/test_dependency_graph.py b/src/tests/test_dependency_graph.py
new file mode 100644
index 000000000..f682e87bb
--- /dev/null
+++ b/src/tests/test_dependency_graph.py
@@ -0,0 +1,31 @@
+#!/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 os
+from vyos.configdep import check_dependency_graph
+
+_here = os.path.dirname(__file__)
+ddir = os.path.join(_here, '../../data/config-mode-dependencies')
+
+from unittest import TestCase
+
+class TestDependencyGraph(TestCase):
+ def setUp(self):
+ pass
+
+ def test_acyclic(self):
+ res = check_dependency_graph(dependency_dir=ddir)
+ self.assertTrue(res)