summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/dns-forwarding/recursor.conf.tmpl2
-rw-r--r--data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl34
-rw-r--r--data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl28
-rw-r--r--data/templates/macsec/wpa_supplicant.conf.tmpl2
-rw-r--r--data/templates/openvpn/server.conf.tmpl10
-rw-r--r--data/templates/wifi/hostapd.conf.tmpl23
-rw-r--r--data/templates/wifi/wpa_supplicant.conf.tmpl8
-rw-r--r--data/templates/wwan/peer.tmpl2
-rw-r--r--debian/control4
-rw-r--r--interface-definitions/dns-forwarding.xml.in11
-rw-r--r--interface-definitions/include/accel-client-ipv6-pool.xml.i (renamed from interface-definitions/include/accel-client-ipv6-pool.xml.in)0
-rw-r--r--interface-definitions/include/accel-name-server.xml.i (renamed from interface-definitions/include/accel-name-server.xml.in)0
-rw-r--r--interface-definitions/include/accel-radius-additions.xml.i (renamed from interface-definitions/include/accel-radius-additions.xml.in)0
-rw-r--r--interface-definitions/include/interface-dial-on-demand.xml.i6
-rw-r--r--interface-definitions/include/vif-s.xml.i19
-rw-r--r--interface-definitions/include/vif.xml.i1
-rw-r--r--interface-definitions/interfaces-macsec.xml.in3
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in16
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in7
-rw-r--r--interface-definitions/interfaces-wireless.xml.in6
-rw-r--r--interface-definitions/interfaces-wirelessmodem.xml.in7
-rw-r--r--interface-definitions/service_ipoe-server.xml.in6
-rw-r--r--interface-definitions/service_pppoe-server.xml.in6
-rw-r--r--interface-definitions/vpn_l2tp.xml.in4
-rw-r--r--interface-definitions/vpn_pptp.xml.in2
-rw-r--r--interface-definitions/vpn_sstp.xml.in6
-rw-r--r--python/vyos/configdict.py237
-rw-r--r--python/vyos/configverify.py71
-rw-r--r--python/vyos/ifconfig/bridge.py6
-rw-r--r--python/vyos/ifconfig/geneve.py13
-rw-r--r--python/vyos/ifconfig/interface.py75
-rw-r--r--python/vyos/ifconfig/l2tpv3.py34
-rw-r--r--python/vyos/ifconfig/macsec.py16
-rw-r--r--python/vyos/ifconfig/macvlan.py15
-rw-r--r--python/vyos/ifconfig/vxlan.py14
-rw-r--r--python/vyos/ifconfig/wireless.py43
-rw-r--r--python/vyos/validate.py41
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py7
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_macsec.py136
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py10
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wirelessmodem.py2
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py22
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_forwarding.py192
-rwxr-xr-xsmoketest/scripts/cli/test_service_mdns-repeater.py4
-rwxr-xr-xsmoketest/scripts/cli/test_service_pppoe-server.py8
-rwxr-xr-xsmoketest/scripts/cli/test_service_router-advert.py6
-rwxr-xr-xsmoketest/scripts/cli/test_service_snmp.py14
-rwxr-xr-xsmoketest/scripts/cli/test_service_ssh.py15
-rwxr-xr-xsmoketest/scripts/cli/test_system_lcd.py9
-rwxr-xr-xsmoketest/scripts/cli/test_system_ntp.py13
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py8
-rwxr-xr-xsmoketest/scripts/system/test_kernel_options.py63
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py176
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py30
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py35
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py11
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py13
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py12
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py32
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py11
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py2
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py36
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py9
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py16
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py2
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py11
-rwxr-xr-xsrc/conf_mode/snmp.py2
-rwxr-xr-xsrc/migration-scripts/interfaces/12-to-1371
-rwxr-xr-xsrc/migration-scripts/system/10-to-1161
-rwxr-xr-xsrc/migration-scripts/system/11-to-1278
-rwxr-xr-xsrc/migration-scripts/system/12-to-1385
-rwxr-xr-xsrc/migration-scripts/system/13-to-1456
-rwxr-xr-xsrc/migration-scripts/system/14-to-1521
-rwxr-xr-xsrc/migration-scripts/system/15-to-1634
-rwxr-xr-xsrc/migration-scripts/system/16-to-1743
-rwxr-xr-xsrc/migration-scripts/system/17-to-18123
-rwxr-xr-xsrc/migration-scripts/system/18-to-19105
-rwxr-xr-xsrc/migration-scripts/system/9-to-1036
-rwxr-xr-xsrc/services/vyos-hostsd21
-rwxr-xr-xsrc/utils/vyos-hostsd-client6
81 files changed, 1440 insertions, 986 deletions
diff --git a/data/configd-include.json b/data/configd-include.json
index 0c75657e0..2711a29b8 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -2,6 +2,7 @@
"bcast_relay.py",
"dhcp_relay.py",
"dhcpv6_relay.py",
+"dns_forwarding.py",
"dynamic_dns.py",
"firewall_options.py",
"host_name.py",
diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl
index d233b8abc..b0ae3cc61 100644
--- a/data/templates/dns-forwarding/recursor.conf.tmpl
+++ b/data/templates/dns-forwarding/recursor.conf.tmpl
@@ -21,7 +21,7 @@ max-cache-entries={{ cache_size }}
max-negative-ttl={{ negative_ttl }}
# ignore-hosts-file
-export-etc-hosts={{ export_hosts_file }}
+export-etc-hosts={{ 'no' if ignore_hosts_file is defined else 'yes' }}
# listen-address
local-address={{ listen_address | join(',') }}
diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl
index de5eaee00..90f35ae1c 100644
--- a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl
+++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl
@@ -3,26 +3,26 @@
# dot zone (catch-all): '+' indicates recursion is desired
# (same as forward-zones-recurse)
-{#- the code below ensures the order of nameservers is determined first by #}
-{#- the order of tags, then by the order of nameservers within that tag #}
-{%- set n = namespace(dot_zone_ns='') %}
-{%- for tag in name_server_tags_recursor %}
-{%- set ns = '' %}
-{%- if tag in name_servers %}
-{%- set ns = ns + name_servers[tag]|join(', ') %}
-{%- set n.dot_zone_ns = (n.dot_zone_ns, ns)|join(', ') if n.dot_zone_ns != '' else ns %}
-{%- endif %}
+{# the code below ensures the order of nameservers is determined first by #}
+{# the order of tags, then by the order of nameservers within that tag #}
+{% set n = namespace(dot_zone_ns='') %}
+{% for tag in name_server_tags_recursor %}
+{% set ns = '' %}
+{% if tag in name_servers %}
+{% set ns = ns + name_servers[tag]|join(', ') %}
+{% set n.dot_zone_ns = (n.dot_zone_ns, ns)|join(', ') if n.dot_zone_ns != '' else ns %}
+{% endif %}
# {{ tag }}: {{ ns }}
-{%- endfor %}
+{% endfor %}
-{%- if n.dot_zone_ns %}
+{% if n.dot_zone_ns %}
+.={{ n.dot_zone_ns }}
-{%- endif %}
+{% endif %}
-{% if forward_zones -%}
+{% if forward_zones is defined %}
# zones added via 'service dns forwarding domain'
-{%- for zone, zonedata in forward_zones.items() %}
-{% if zonedata['recursion-desired'] %}+{% endif %}{{ zone }}={{ zonedata['nslist']|join(', ') }}
-{%- endfor %}
-{%- endif %}
+{% for zone, zonedata in forward_zones.items() %}
+{{ "+" if zonedata['recursion_desired'] is defined }}{{ zone }}={{ zonedata['server']|join(', ') }}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
index b0d99d9ae..784d5c360 100644
--- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
+++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
@@ -1,24 +1,24 @@
-- Autogenerated by VyOS (vyos-hostsd) --
-- Do not edit, your changes will get overwritten --
-{% if hosts -%}
+{% if hosts %}
-- from 'system static-host-mapping' and DHCP server
-{%- for tag, taghosts in hosts.items() %}
-{%- for host, hostprops in taghosts.items() %}
+{% for tag, taghosts in hosts.items() %}
+{% for host, hostprops in taghosts.items() %}
addNTA("{{ host }}.", "{{ tag }}")
-{%- for a in hostprops['aliases'] %}
+{% for a in hostprops['aliases'] %}
addNTA("{{ a }}.", "{{ tag }} alias")
-{%- endfor %}
-{%- endfor %}
-{%- endfor %}
-{%- endif %}
+{% endfor %}
+{% endfor %}
+{% endfor %}
+{% endif %}
-{% if forward_zones -%}
+{% if forward_zones is defined %}
-- from 'service dns forwarding domain'
-{%- for zone, zonedata in forward_zones.items() %}
-{%- if zonedata['addNTA'] %}
+{% for zone, zonedata in forward_zones.items() %}
+{% if zonedata['addnta'] is defined %}
addNTA("{{ zone }}", "static")
-{%- endif %}
-{%- endfor %}
-{%- endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/macsec/wpa_supplicant.conf.tmpl b/data/templates/macsec/wpa_supplicant.conf.tmpl
index 1731bf160..5b353def8 100644
--- a/data/templates/macsec/wpa_supplicant.conf.tmpl
+++ b/data/templates/macsec/wpa_supplicant.conf.tmpl
@@ -1,4 +1,4 @@
-# autogenerated by interfaces-macsec.py
+### Autogenerated by interfaces-macsec.py ###
# see full documentation:
# https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf
diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl
index 401f8e04b..fea310236 100644
--- a/data/templates/openvpn/server.conf.tmpl
+++ b/data/templates/openvpn/server.conf.tmpl
@@ -181,7 +181,11 @@ dh {{ tls_dh }}
{%- endif %}
{%- if tls_auth %}
-tls-auth {{tls_auth}}
+{%- if mode == 'client' %}
+tls-auth {{tls_auth}} 1
+{%- elif mode == 'server' %}
+tls-auth {{tls_auth}} 0
+{%- endif %}
{%- endif %}
{%- if tls_role %}
@@ -196,7 +200,9 @@ tls-server
# Encryption options
{%- if encryption %}
-{% if encryption == 'des' -%}
+{% if encryption == 'none' -%}
+cipher none
+{%- elif encryption == 'des' -%}
cipher des-cbc
{%- elif encryption == '3des' -%}
cipher des-ede3-cbc
diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl
index 132c4ce40..c5e4240d1 100644
--- a/data/templates/wifi/hostapd.conf.tmpl
+++ b/data/templates/wifi/hostapd.conf.tmpl
@@ -11,6 +11,21 @@ device_name={{ description | truncate(32, True) }}
# command line parameter.
interface={{ ifname }}
+{% if is_bridge_member is defined %}
+# In case of atheros and nl80211 driver interfaces, an additional
+# configuration parameter, bridge, may be used to notify hostapd if the
+# interface is included in a bridge. This parameter is not used with Host AP
+# driver. If the bridge parameter is not set, the drivers will automatically
+# figure out the bridge interface (assuming sysfs is enabled and mounted to
+# /sys) and this parameter may not be needed.
+#
+# For nl80211, this parameter can be used to request the AP interface to be
+# added to the bridge automatically (brctl may refuse to do this before hostapd
+# has been started to change the interface mode). If needed, the bridge
+# interface is also created.
+bridge={{ is_bridge_member }}
+{% endif %}
+
# Driver interface type (hostap/wired/none/nl80211/bsd);
# default: hostap). nl80211 is used with all Linux mac80211 drivers.
# Use driver=none if building hostapd as a standalone RADIUS server that does
@@ -433,6 +448,14 @@ macaddr_acl=0
max_num_sta={{ max_stations }}
{% endif %}
+{% if wds is defined %}
+# WDS (4-address frame) mode with per-station virtual interfaces
+# (only supported with driver=nl80211)
+# This mode allows associated stations to use 4-address frames to allow layer 2
+# bridging to be used.
+wds_sta=1
+{% endif %}
+
{% if isolate_stations is defined %}
# Client isolation can be used to prevent low-level bridging of frames between
# associated stations in the BSS. By default, this bridging is allowed.
diff --git a/data/templates/wifi/wpa_supplicant.conf.tmpl b/data/templates/wifi/wpa_supplicant.conf.tmpl
index 9ddad35fd..f84892dc0 100644
--- a/data/templates/wifi/wpa_supplicant.conf.tmpl
+++ b/data/templates/wifi/wpa_supplicant.conf.tmpl
@@ -1,7 +1,13 @@
-# WPA supplicant config
+### Autogenerated by interfaces-macsec.py ###
+
+# see full documentation:
+# https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf
+
network={
ssid="{{ ssid }}"
+ scan_ssid=1
{% if security is defined and security.wpa is defined and security.wpa.passphrase is defined %}
+ key_mgmt=WPA-PSK
psk="{{ security.wpa.passphrase }}"
{% else %}
key_mgmt=NONE
diff --git a/data/templates/wwan/peer.tmpl b/data/templates/wwan/peer.tmpl
index aa759f741..e23881bf8 100644
--- a/data/templates/wwan/peer.tmpl
+++ b/data/templates/wwan/peer.tmpl
@@ -21,7 +21,7 @@ noauth
crtscts
lock
persist
-{{ "demand" if ondemand is defined }}
+{{ "demand" if connect_on_demand is defined }}
connect '/usr/sbin/chat -v -t6 -f /etc/ppp/peers/chat.{{ ifname }}'
diff --git a/debian/control b/debian/control
index 0777ecc52..ebcfc6c43 100644
--- a/debian/control
+++ b/debian/control
@@ -18,7 +18,7 @@ Build-Depends:
Standards-Version: 3.9.6
Package: vyos-1x
-Architecture: all
+Architecture: amd64 arm64
Depends:
accel-ppp,
beep,
@@ -117,7 +117,7 @@ Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
Package: vyos-1x-vmware
-Architecture: amd64 i386
+Architecture: amd64
Depends:
vyos-1x,
open-vm-tools
diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in
index aaf8bb27d..07e63d54a 100644
--- a/interface-definitions/dns-forwarding.xml.in
+++ b/interface-definitions/dns-forwarding.xml.in
@@ -16,7 +16,7 @@
<children>
<leafNode name="cache-size">
<properties>
- <help>DNS forwarding cache size</help>
+ <help>DNS forwarding cache size (default: 10000)</help>
<valueHelp>
<format>0-10000</format>
<description>DNS forwarding cache size</description>
@@ -25,6 +25,7 @@
<validator name="numeric" argument="--range 0-10000"/>
</constraint>
</properties>
+ <defaultValue>10000</defaultValue>
</leafNode>
<leafNode name="dhcp">
<properties>
@@ -37,7 +38,7 @@
</leafNode>
<leafNode name="dnssec">
<properties>
- <help>DNSSEC mode</help>
+ <help>DNSSEC mode (default: process-no-validate)</help>
<completionHelp>
<list>off process-no-validate process log-fail validate</list>
</completionHelp>
@@ -62,9 +63,10 @@
<description>Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses.</description>
</valueHelp>
<constraint>
- <regex>(off|process-no-validate|process|log-fail|validate)</regex>
+ <regex>^(off|process-no-validate|process|log-fail|validate)$</regex>
</constraint>
</properties>
+ <defaultValue>process-no-validate</defaultValue>
</leafNode>
<tagNode name="domain">
<properties>
@@ -146,7 +148,7 @@
</leafNode>
<leafNode name="negative-ttl">
<properties>
- <help>Maximum amount of time negative entries are cached</help>
+ <help>Maximum amount of time negative entries are cached (default: 3600)</help>
<valueHelp>
<format>0-7200</format>
<description>Seconds to cache NXDOMAIN entries</description>
@@ -155,6 +157,7 @@
<validator name="numeric" argument="--range 0-7200"/>
</constraint>
</properties>
+ <defaultValue>3600</defaultValue>
</leafNode>
<leafNode name="name-server">
<properties>
diff --git a/interface-definitions/include/accel-client-ipv6-pool.xml.in b/interface-definitions/include/accel-client-ipv6-pool.xml.i
index 455ada6ef..455ada6ef 100644
--- a/interface-definitions/include/accel-client-ipv6-pool.xml.in
+++ b/interface-definitions/include/accel-client-ipv6-pool.xml.i
diff --git a/interface-definitions/include/accel-name-server.xml.in b/interface-definitions/include/accel-name-server.xml.i
index 82ed6771d..82ed6771d 100644
--- a/interface-definitions/include/accel-name-server.xml.in
+++ b/interface-definitions/include/accel-name-server.xml.i
diff --git a/interface-definitions/include/accel-radius-additions.xml.in b/interface-definitions/include/accel-radius-additions.xml.i
index e37b68514..e37b68514 100644
--- a/interface-definitions/include/accel-radius-additions.xml.in
+++ b/interface-definitions/include/accel-radius-additions.xml.i
diff --git a/interface-definitions/include/interface-dial-on-demand.xml.i b/interface-definitions/include/interface-dial-on-demand.xml.i
new file mode 100644
index 000000000..c14ddf6f5
--- /dev/null
+++ b/interface-definitions/include/interface-dial-on-demand.xml.i
@@ -0,0 +1,6 @@
+<leafNode name="connect-on-demand">
+ <properties>
+ <help>Establishment connection automatically when traffic is sent</help>
+ <valueless/>
+ </properties>
+</leafNode>
diff --git a/interface-definitions/include/vif-s.xml.i b/interface-definitions/include/vif-s.xml.i
index a6d7c81ce..cd0afe742 100644
--- a/interface-definitions/include/vif-s.xml.i
+++ b/interface-definitions/include/vif-s.xml.i
@@ -13,25 +13,26 @@
#include <include/dhcpv6-options.xml.i>
#include <include/interface-disable-link-detect.xml.i>
#include <include/interface-disable.xml.i>
- <leafNode name="ethertype">
+ <leafNode name="protocol">
<properties>
- <help>Set Ethertype</help>
+ <help>Protocol used for service VLAN (default: 802.1ad)</help>
<completionHelp>
- <list>0x88A8 0x8100</list>
+ <list>802.1ad 802.1q</list>
</completionHelp>
<valueHelp>
- <format>0x88A8</format>
- <description>802.1ad</description>
+ <format>802.1ad</format>
+ <description>Provider Bridging (IEEE 802.1ad, Q-inQ), ethertype 0x88a8</description>
</valueHelp>
<valueHelp>
- <format>0x8100</format>
- <description>802.1q</description>
+ <format>802.1q</format>
+ <description>VLAN-tagged frame (IEEE 802.1q), ethertype 0x8100</description>
</valueHelp>
<constraint>
- <regex>(0x88A8|0x8100)</regex>
+ <regex>(802.1q|802.1ad)</regex>
</constraint>
- <constraintErrorMessage>Ethertype must be 0x88A8 or 0x8100</constraintErrorMessage>
+ <constraintErrorMessage>Ethertype must be 802.1ad or 802.1q</constraintErrorMessage>
</properties>
+ <defaultValue>802.1ad</defaultValue>
</leafNode>
<node name="ip">
<children>
diff --git a/interface-definitions/include/vif.xml.i b/interface-definitions/include/vif.xml.i
index 5a4e52122..919e4d493 100644
--- a/interface-definitions/include/vif.xml.i
+++ b/interface-definitions/include/vif.xml.i
@@ -50,6 +50,7 @@
#include <include/interface-enable-arp-announce.xml.i>
#include <include/interface-enable-arp-ignore.xml.i>
#include <include/interface-enable-proxy-arp.xml.i>
+ #include <include/interface-proxy-arp-pvlan.xml.i>
</children>
</node>
<node name="ipv6">
diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in
index dfef387d2..9384726cc 100644
--- a/interface-definitions/interfaces-macsec.xml.in
+++ b/interface-definitions/interfaces-macsec.xml.in
@@ -107,8 +107,9 @@
</node>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
- #include <include/interface-vrf.xml.i>
+ #include <include/interface-mtu-68-9000.xml.i>
#include <include/source-interface-ethernet.xml.i>
+ #include <include/interface-vrf.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index 905c76507..5675379d5 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -63,9 +63,13 @@
<properties>
<help>Standard Data Encryption Algorithm</help>
<completionHelp>
- <list>des 3des bf128 bf256 aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
+ <list>none des 3des bf128 bf256 aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
</completionHelp>
<valueHelp>
+ <format>none</format>
+ <description>Disable encryption</description>
+ </valueHelp>
+ <valueHelp>
<format>des</format>
<description>DES algorithm</description>
</valueHelp>
@@ -106,7 +110,7 @@
<description>AES algorithm with 256-bit key GCM</description>
</valueHelp>
<constraint>
- <regex>(des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
+ <regex>(none|des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
</constraint>
</properties>
</leafNode>
@@ -114,9 +118,13 @@
<properties>
<help>Cipher negotiation list for use in server or client mode</help>
<completionHelp>
- <list>des 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
+ <list>none des 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
</completionHelp>
<valueHelp>
+ <format>none</format>
+ <description>Disable encryption</description>
+ </valueHelp>
+ <valueHelp>
<format>des</format>
<description>DES algorithm</description>
</valueHelp>
@@ -149,7 +157,7 @@
<description>AES algorithm with 256-bit key GCM</description>
</valueHelp>
<constraint>
- <regex>(des|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
+ <regex>(none|des|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 8a6c61312..b6208e0b9 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -42,12 +42,7 @@
</leafNode>
</children>
</node>
- <leafNode name="connect-on-demand">
- <properties>
- <help>Automatic establishment of PPPOE connection when traffic is sent</help>
- <valueless/>
- </properties>
- </leafNode>
+ #include <include/interface-dial-on-demand.xml.i>
<leafNode name="default-route">
<properties>
<help>Default route insertion behaviour (default: auto)</help>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index a0caf810f..8c594e758 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -770,6 +770,12 @@
</leafNode>
#include <include/vif.xml.i>
#include <include/vif-s.xml.i>
+ <leafNode name="wds">
+ <properties>
+ <help>Enable WDS (Wireless Distribution System)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/interfaces-wirelessmodem.xml.in b/interface-definitions/interfaces-wirelessmodem.xml.in
index d375b808d..96604ff00 100644
--- a/interface-definitions/interfaces-wirelessmodem.xml.in
+++ b/interface-definitions/interfaces-wirelessmodem.xml.in
@@ -80,12 +80,7 @@
<valueless/>
</properties>
</leafNode>
- <leafNode name="ondemand">
- <properties>
- <help>Only dial when traffic is available</help>
- <valueless/>
- </properties>
- </leafNode>
+ #include <include/interface-dial-on-demand.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in
index 9ee5d5156..ee09d01d6 100644
--- a/interface-definitions/service_ipoe-server.xml.in
+++ b/interface-definitions/service_ipoe-server.xml.in
@@ -111,8 +111,8 @@
</leafNode>
</children>
</tagNode>
- #include <include/accel-name-server.xml.in>
- #include <include/accel-client-ipv6-pool.xml.in>
+ #include <include/accel-name-server.xml.i>
+ #include <include/accel-client-ipv6-pool.xml.i>
<node name="authentication">
<properties>
<help>Client authentication methods</help>
@@ -198,7 +198,7 @@
</children>
</tagNode>
#include <include/radius-server.xml.i>
- #include <include/accel-radius-additions.xml.in>
+ #include <include/accel-radius-additions.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in
index 605f47b37..64fd6e4ef 100644
--- a/interface-definitions/service_pppoe-server.xml.in
+++ b/interface-definitions/service_pppoe-server.xml.in
@@ -109,7 +109,7 @@
</node>
#include <include/accel-auth-mode.xml.i>
#include <include/radius-server.xml.i>
- #include <include/accel-radius-additions.xml.in>
+ #include <include/accel-radius-additions.xml.i>
<node name="radius">
<children>
<node name="rate-limit">
@@ -200,8 +200,8 @@
</leafNode>
</children>
</node>
- #include <include/accel-client-ipv6-pool.xml.in>
- #include <include/accel-name-server.xml.in>
+ #include <include/accel-client-ipv6-pool.xml.i>
+ #include <include/accel-name-server.xml.i>
<tagNode name="interface">
<properties>
<help>interface(s) to listen on</help>
diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in
index 702ef8b5a..4de28d2bd 100644
--- a/interface-definitions/vpn_l2tp.xml.in
+++ b/interface-definitions/vpn_l2tp.xml.in
@@ -36,7 +36,7 @@
</constraint>
</properties>
</leafNode>
- #include <include/accel-name-server.xml.in>
+ #include <include/accel-name-server.xml.i>
<node name="lns">
<properties>
<help>L2TP Network Server (LNS)</help>
@@ -203,7 +203,7 @@
</leafNode>
</children>
</node>
- #include <include/accel-client-ipv6-pool.xml.in>
+ #include <include/accel-client-ipv6-pool.xml.i>
<leafNode name="description">
<properties>
<help>Description for L2TP remote-access settings</help>
diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn_pptp.xml.in
index 032455b4d..f37c9bd01 100644
--- a/interface-definitions/vpn_pptp.xml.in
+++ b/interface-definitions/vpn_pptp.xml.in
@@ -153,7 +153,7 @@
</children>
</node>
#include <include/radius-server.xml.i>
- #include <include/accel-radius-additions.xml.in>
+ #include <include/accel-radius-additions.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in
index f0c93b882..5da2f8f24 100644
--- a/interface-definitions/vpn_sstp.xml.in
+++ b/interface-definitions/vpn_sstp.xml.in
@@ -96,7 +96,7 @@
</properties>
</leafNode>
#include <include/radius-server.xml.i>
- #include <include/accel-radius-additions.xml.in>
+ #include <include/accel-radius-additions.xml.i>
<node name="radius">
<children>
<node name="rate-limit">
@@ -207,8 +207,8 @@
</leafNode>
</children>
</node>
- #include <include/accel-client-ipv6-pool.xml.in>
- #include <include/accel-name-server.xml.in>
+ #include <include/accel-client-ipv6-pool.xml.i>
+ #include <include/accel-name-server.xml.i>
#include <include/interface-mtu-68-1500.xml.i>
</children>
</node>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index bfc70b772..58ecd3f17 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -18,9 +18,12 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion.
"""
import os
-from enum import Enum
from copy import deepcopy
+from vyos.util import vyos_dict_search
+from vyos.xml import defaults
+from vyos.xml import is_tag
+from vyos.xml import is_leaf
from vyos import ConfigError
def retrieve_config(path_hash, base_path, config):
@@ -104,47 +107,6 @@ def list_diff(first, second):
second = set(second)
return [item for item in first if item not in second]
-def T2665_default_dict_cleanup(dict):
- """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """
- # Cleanup
- for vif in ['vif', 'vif_s']:
- if vif in dict:
- for key in ['ip', 'mtu', 'dhcpv6_options']:
- if key in dict[vif]:
- del dict[vif][key]
-
- # cleanup VIF-S defaults
- if 'vif_c' in dict[vif]:
- for key in ['ip', 'mtu', 'dhcpv6_options']:
- if key in dict[vif]['vif_c']:
- del dict[vif]['vif_c'][key]
- # If there is no vif-c defined and we just cleaned the default
- # keys - we can clean the entire vif-c dict as it's useless
- if not dict[vif]['vif_c']:
- del dict[vif]['vif_c']
-
- # If there is no real vif/vif-s defined and we just cleaned the default
- # keys - we can clean the entire vif dict as it's useless
- if not dict[vif]:
- del dict[vif]
-
- if 'dhcpv6_options' in dict and 'pd' in dict['dhcpv6_options']:
- if 'length' in dict['dhcpv6_options']['pd']:
- del dict['dhcpv6_options']['pd']['length']
-
- # delete empty dicts
- if 'dhcpv6_options' in dict:
- if 'pd' in dict['dhcpv6_options']:
- # test if 'pd' is an empty node so we can remove it
- if not dict['dhcpv6_options']['pd']:
- del dict['dhcpv6_options']['pd']
-
- # test if 'dhcpv6_options' is an empty node so we can remove it
- if not dict['dhcpv6_options']:
- del dict['dhcpv6_options']
-
- return dict
-
def leaf_node_changed(conf, path):
"""
Check if a leaf node was altered. If it has been altered - values has been
@@ -207,16 +169,100 @@ def get_removed_vlans(conf, dict):
return dict
+def T2665_set_dhcpv6pd_defaults(config_dict):
+ """ Properly configure DHCPv6 default options in the dictionary. If there is
+ no DHCPv6 configured at all, it is safe to remove the entire configuration.
+ """
+ # As this is the same for every interface type it is safe to assume this
+ # for ethernet
+ pd_defaults = defaults(['interfaces', 'ethernet', 'dhcpv6-options', 'pd'])
-def dict_add_dhcpv6pd_defaults(defaults, config_dict):
# Implant default dictionary for DHCPv6-PD instances
- if 'dhcpv6_options' in config_dict and 'pd' in config_dict['dhcpv6_options']:
- for pd, pd_config in config_dict['dhcpv6_options']['pd'].items():
- config_dict['dhcpv6_options']['pd'][pd] = dict_merge(
- defaults, pd_config)
+ if vyos_dict_search('dhcpv6_options.pd.length', config_dict):
+ del config_dict['dhcpv6_options']['pd']['length']
+
+ for pd in (vyos_dict_search('dhcpv6_options.pd', config_dict) or []):
+ config_dict['dhcpv6_options']['pd'][pd] = dict_merge(pd_defaults,
+ config_dict['dhcpv6_options']['pd'][pd])
return config_dict
+def is_member(conf, interface, intftype=None):
+ """
+ Checks if passed interface is member of other interface of specified type.
+ intftype is optional, if not passed it will search all known types
+ (currently bridge and bonding)
+
+ Returns:
+ None -> Interface is not a member
+ interface name -> Interface is a member of this interface
+ False -> interface type cannot have members
+ """
+ ret_val = None
+ intftypes = ['bonding', 'bridge']
+ if intftype not in intftypes + [None]:
+ raise ValueError((
+ f'unknown interface type "{intftype}" or it cannot '
+ f'have member interfaces'))
+
+ intftype = intftypes if intftype == None else [intftype]
+
+ # set config level to root
+ old_level = conf.get_level()
+ conf.set_level([])
+
+ for it in intftype:
+ base = ['interfaces', it]
+ for intf in conf.list_nodes(base):
+ memberintf = base + [intf, 'member', 'interface']
+ if is_tag(memberintf):
+ if interface in conf.list_nodes(memberintf):
+ ret_val = intf
+ break
+ elif is_leaf(memberintf):
+ if ( conf.exists(memberintf) and
+ interface in conf.return_values(memberintf) ):
+ ret_val = intf
+ break
+
+ old_level = conf.set_level(old_level)
+ return ret_val
+
+def is_source_interface(conf, interface, intftype=None):
+ """
+ Checks if passed interface is configured as source-interface of other
+ interfaces of specified type. intftype is optional, if not passed it will
+ search all known types (currently pppoe, macsec, pseudo-ethernet, tunnel
+ and vxlan)
+
+ Returns:
+ None -> Interface is not a member
+ interface name -> Interface is a member of this interface
+ False -> interface type cannot have members
+ """
+ ret_val = None
+ intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan']
+ if intftype not in intftypes + [None]:
+ raise ValueError(f'unknown interface type "{intftype}" or it can not '
+ 'have a source-interface')
+
+ intftype = intftypes if intftype == None else [intftype]
+
+ # set config level to root
+ old_level = conf.get_level()
+ conf.set_level([])
+
+ for it in intftype:
+ base = ['interfaces', it]
+ for intf in conf.list_nodes(base):
+ lower_intf = base + [intf, 'source-interface']
+ if conf.exists(lower_intf) and interface in conf.return_values(lower_intf):
+ ret_val = intf
+ break
+
+ old_level = conf.set_level(old_level)
+ return ret_val
+
def get_interface_dict(config, base, ifname=''):
"""
Common utility function to retrieve and mandgle the interfaces available
@@ -225,10 +271,6 @@ def get_interface_dict(config, base, ifname=''):
Will return a dictionary with the necessary interface configuration
"""
- from vyos.util import vyos_dict_search
- from vyos.validate import is_member
- from vyos.xml import defaults
-
if not ifname:
# determine tagNode instance
if 'VYOS_TAGNODE_VALUE' not in os.environ:
@@ -238,6 +280,12 @@ def get_interface_dict(config, base, ifname=''):
# retrieve interface default values
default_values = defaults(base)
+ # We take care about VLAN (vif, vif-s, vif-c) default values later on when
+ # parsing vlans in default dict and merge the "proper" values in correctly,
+ # see T2665.
+ for vif in ['vif', 'vif_s']:
+ if vif in default_values: del default_values[vif]
+
# setup config level which is extracted in get_removed_vlans()
config.set_level(base + [ifname])
dict = config.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
@@ -249,24 +297,42 @@ def get_interface_dict(config, base, ifname=''):
# Add interface instance name into dictionary
dict.update({'ifname': ifname})
+ # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
+ # remove the default values from the dict.
+ if 'dhcpv6_options' not in dict:
+ if 'dhcpv6_options' in default_values:
+ del default_values['dhcpv6_options']
+
# We have gathered the dict representation of the CLI, but there are
# default options which we need to update into the dictionary
# retrived.
dict = dict_merge(default_values, dict)
+ # XXX: T2665: blend in proper DHCPv6-PD default values
+ dict = T2665_set_dhcpv6pd_defaults(dict)
+
# Check if we are a member of a bridge device
bridge = is_member(config, ifname, 'bridge')
- if bridge:
- dict.update({'is_bridge_member' : bridge})
+ if bridge: dict.update({'is_bridge_member' : bridge})
# Check if we are a member of a bond device
bond = is_member(config, ifname, 'bonding')
- if bond:
- dict.update({'is_bond_member' : bond})
+ if bond: dict.update({'is_bond_member' : bond})
+
+ # Some interfaces come with a source_interface which must also not be part
+ # of any other bond or bridge interface as it is exclusivly assigned as the
+ # Kernels "lower" interface to this new "virtual/upper" interface.
+ if 'source_interface' in dict:
+ # Check if source interface is member of another bridge
+ tmp = is_member(config, dict['source_interface'], 'bridge')
+ if tmp: dict.update({'source_interface_is_bridge_member' : tmp})
+
+ # Check if source interface is member of another bridge
+ tmp = is_member(config, dict['source_interface'], 'bonding')
+ if tmp: dict.update({'source_interface_is_bond_member' : tmp})
mac = leaf_node_changed(config, ['mac'])
- if mac:
- dict.update({'mac_old' : mac})
+ if mac: dict.update({'mac_old' : mac})
eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64'])
if eui64:
@@ -276,36 +342,49 @@ def get_interface_dict(config, base, ifname=''):
else:
dict['ipv6']['address'].update({'eui64_old': eui64})
- # remove wrongly inserted values
- dict = T2665_default_dict_cleanup(dict)
-
- # Implant default dictionary for DHCPv6-PD instances
- default_pd_values = defaults(base + ['dhcpv6-options', 'pd'])
- dict = dict_add_dhcpv6pd_defaults(default_pd_values, dict)
-
# Implant default dictionary in vif/vif-s VLAN interfaces. Values are
# identical for all types of VLAN interfaces as they all include the same
# XML definitions which hold the defaults.
- default_vif_values = defaults(base + ['vif'])
for vif, vif_config in dict.get('vif', {}).items():
- dict['vif'][vif] = dict_add_dhcpv6pd_defaults(
- default_pd_values, vif_config)
- dict['vif'][vif] = T2665_default_dict_cleanup(
- dict_merge(default_vif_values, vif_config))
+ default_vif_values = defaults(base + ['vif'])
+ # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
+ # remove the default values from the dict.
+ if not 'dhcpv6_options' in vif_config:
+ del default_vif_values['dhcpv6_options']
+
+ dict['vif'][vif] = dict_merge(default_vif_values, vif_config)
+ # XXX: T2665: blend in proper DHCPv6-PD default values
+ dict['vif'][vif] = T2665_set_dhcpv6pd_defaults(dict['vif'][vif])
for vif_s, vif_s_config in dict.get('vif_s', {}).items():
- dict['vif_s'][vif_s] = dict_add_dhcpv6pd_defaults(
- default_pd_values, vif_s_config)
- dict['vif_s'][vif_s] = T2665_default_dict_cleanup(
- dict_merge(default_vif_values, vif_s_config))
+ default_vif_s_values = defaults(base + ['vif-s'])
+ # XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c
+ if 'vif_c' in default_vif_s_values: del default_vif_s_values['vif_c']
+
+ # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
+ # remove the default values from the dict.
+ if not 'dhcpv6_options' in vif_s_config:
+ del default_vif_s_values['dhcpv6_options']
+
+ dict['vif_s'][vif_s] = dict_merge(default_vif_s_values, vif_s_config)
+ # XXX: T2665: blend in proper DHCPv6-PD default values
+ dict['vif_s'][vif_s] = T2665_set_dhcpv6pd_defaults(
+ dict['vif_s'][vif_s])
+
for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
- dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_add_dhcpv6pd_defaults(
- default_pd_values, vif_c_config)
- dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_default_dict_cleanup(
- dict_merge(default_vif_values, vif_c_config))
+ default_vif_c_values = defaults(base + ['vif-s', 'vif-c'])
+
+ # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
+ # remove the default values from the dict.
+ if not 'dhcpv6_options' in vif_c_config:
+ del default_vif_c_values['dhcpv6_options']
+
+ dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_merge(
+ default_vif_c_values, vif_c_config)
+ # XXX: T2665: blend in proper DHCPv6-PD default values
+ dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_set_dhcpv6pd_defaults(
+ dict['vif_s'][vif_s]['vif_c'][vif_c])
# Check vif, vif-s/vif-c VLAN interfaces for removal
dict = get_removed_vlans(config, dict)
-
return dict
-
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 7e1930878..944fc4294 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -23,6 +23,57 @@
from vyos import ConfigError
+def verify_mtu(config):
+ """
+ Common helper function used by interface implementations to perform
+ recurring validation if the specified MTU can be used by the underlaying
+ hardware.
+ """
+ from vyos.ifconfig import Interface
+ if 'mtu' in config:
+ mtu = int(config['mtu'])
+
+ tmp = Interface(config['ifname'])
+ min_mtu = tmp.get_min_mtu()
+ max_mtu = tmp.get_max_mtu()
+
+ if mtu < min_mtu:
+ raise ConfigError(f'Interface MTU too low, ' \
+ f'minimum supported MTU is {min_mtu}!')
+ if mtu > max_mtu:
+ raise ConfigError(f'Interface MTU too high, ' \
+ f'maximum supported MTU is {max_mtu}!')
+
+def verify_mtu_ipv6(config):
+ """
+ Common helper function used by interface implementations to perform
+ recurring validation if the specified MTU can be used when IPv6 is
+ configured on the interface. IPv6 requires a 1280 bytes MTU.
+ """
+ from vyos.validate import is_ipv6
+ from vyos.util import vyos_dict_search
+ # IPv6 minimum required link mtu
+ min_mtu = 1280
+
+ if int(config['mtu']) < min_mtu:
+ interface = config['ifname']
+ error_msg = f'IPv6 address will be configured on interface "{interface}" ' \
+ f'thus the minimum MTU requirement is {min_mtu}!'
+
+ if not vyos_dict_search('ipv6.address.no_default_link_local', config):
+ raise ConfigError('link-local ' + error_msg)
+
+ for address in (vyos_dict_search('address', config) or []):
+ if address in ['dhcpv6'] or is_ipv6(address):
+ raise ConfigError(error_msg)
+
+ if vyos_dict_search('ipv6.address.autoconf', config):
+ raise ConfigError(error_msg)
+
+ if vyos_dict_search('ipv6.address.eui64', config):
+ raise ConfigError(error_msg)
+
+
def verify_vrf(config):
"""
Common helper function used by interface implementations to perform
@@ -82,9 +133,20 @@ def verify_source_interface(config):
if 'source_interface' not in config:
raise ConfigError('Physical source-interface required for '
'interface "{ifname}"'.format(**config))
+
if config['source_interface'] not in interfaces():
- raise ConfigError('Source interface {source_interface} does not '
- 'exist'.format(**config))
+ raise ConfigError('Specified source-interface {source_interface} does '
+ 'not exist'.format(**config))
+
+ if 'source_interface_is_bridge_member' in config:
+ raise ConfigError('Invalid source-interface {source_interface}. Interface '
+ 'is already a member of bridge '
+ '{source_interface_is_bridge_member}'.format(**config))
+
+ if 'source_interface_is_bond_member' in config:
+ raise ConfigError('Invalid source-interface {source_interface}. Interface '
+ 'is already a member of bond '
+ '{source_interface_is_bond_member}'.format(**config))
def verify_dhcpv6(config):
"""
@@ -102,6 +164,11 @@ def verify_dhcpv6(config):
# assigned IPv6 subnet from a delegated prefix
for pd in vyos_dict_search('dhcpv6_options.pd', config):
sla_ids = []
+
+ if not vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config):
+ raise ConfigError('DHCPv6-PD requires an interface where to assign '
+ 'the delegated prefix!')
+
for interface in vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config):
sla_id = vyos_dict_search(
f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id', config)
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index 4c76fe996..c133a56fc 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -13,6 +13,8 @@
# 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/>.
+from netifaces import interfaces
+
from vyos.ifconfig.interface import Interface
from vyos.ifconfig.stp import STP
from vyos.validate import assert_boolean
@@ -228,8 +230,8 @@ class BridgeIf(Interface):
# remove interface from bridge
tmp = vyos_dict_search('member.interface_remove', config)
- if tmp:
- for member in tmp:
+ for member in (tmp or []):
+ if member in interfaces():
self.del_port(member)
STPBridgeIf = STP.enable(BridgeIf)
diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py
index 0a13043cc..5c4597be8 100644
--- a/python/vyos/ifconfig/geneve.py
+++ b/python/vyos/ifconfig/geneve.py
@@ -13,7 +13,6 @@
# 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/>.
-from copy import deepcopy
from vyos.ifconfig.interface import Interface
@Interface.register
@@ -51,18 +50,6 @@ class GeneveIf(Interface):
# interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
- @classmethod
- def get_config(cls):
- """
- GENEVE interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = GeneveIf().get_config()
- """
- return deepcopy(cls.default)
-
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index be97b411b..d200fc7a8 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -50,14 +50,6 @@ from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
from vyos.ifconfig import Section
-def get_ethertype(ethertype_val):
- if ethertype_val == '0x88A8':
- return '802.1ad'
- elif ethertype_val == '0x8100':
- return '802.1q'
- else:
- raise ConfigError('invalid ethertype "{}"'.format(ethertype_val))
-
class Interface(Control):
# This is the class which will be used to create
# self.operational, it allows subclasses, such as
@@ -86,6 +78,14 @@ class Interface(Control):
'shellcmd': 'ip -json link show dev {ifname}',
'format': lambda j: 'up' if 'UP' in jmespath.search('[*].flags | [0]', json.loads(j)) else 'down',
},
+ 'min_mtu': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].min_mtu | [0]', json.loads(j)),
+ },
+ 'max_mtu': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].max_mtu | [0]', json.loads(j)),
+ },
}
_command_set = {
@@ -182,6 +182,15 @@ class Interface(Control):
def exists(cls, ifname):
return os.path.exists(f'/sys/class/net/{ifname}')
+ @classmethod
+ def get_config(cls):
+ """
+ Some but not all interfaces require a configuration when they are added
+ using iproute2. This method will provide the configuration dictionary
+ used by this class.
+ """
+ return deepcopy(cls.default)
+
def __init__(self, ifname, **kargs):
"""
This is the base interface class which supports basic IP/MAC address
@@ -281,6 +290,28 @@ class Interface(Control):
cmd = 'ip link del dev {ifname}'.format(**self.config)
return self._cmd(cmd)
+ def get_min_mtu(self):
+ """
+ Get hardware minimum supported MTU
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_min_mtu()
+ '60'
+ """
+ return int(self.get_interface('min_mtu'))
+
+ def get_max_mtu(self):
+ """
+ Get hardware maximum supported MTU
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_max_mtu()
+ '9000'
+ """
+ return int(self.get_interface('max_mtu'))
+
def get_mtu(self):
"""
Get/set interface mtu in bytes.
@@ -290,7 +321,7 @@ class Interface(Control):
>>> Interface('eth0').get_mtu()
'1500'
"""
- return self.get_interface('mtu')
+ return int(self.get_interface('mtu'))
def set_mtu(self, mtu):
"""
@@ -1013,7 +1044,7 @@ class Interface(Control):
# create/update 802.1ad (Q-in-Q VLANs)
for vif_s_id, vif_s_config in config.get('vif_s', {}).items():
tmp = deepcopy(VLANIf.get_config())
- tmp['ethertype'] = get_ethertype(vif_s_config.get('ethertype', '0x88A8'))
+ tmp['protocol'] = vif_s_config['protocol']
tmp['source_interface'] = ifname
tmp['vlan_id'] = vif_s_id
@@ -1061,13 +1092,13 @@ class VLANIf(Interface):
'type': 'vlan',
'source_interface': '',
'vlan_id': '',
- 'ethertype': '',
+ 'protocol': '',
'ingress_qos': '',
'egress_qos': '',
}
options = Interface.options + \
- ['source_interface', 'vlan_id', 'ethertype', 'ingress_qos', 'egress_qos']
+ ['source_interface', 'vlan_id', 'protocol', 'ingress_qos', 'egress_qos']
def remove(self):
"""
@@ -1092,12 +1123,12 @@ class VLANIf(Interface):
def _create(self):
# bail out early if interface already exists
- if os.path.exists(f'/sys/class/net/{self.ifname}'):
+ if self.exists(f'{self.ifname}'):
return
cmd = 'ip link add link {source_interface} name {ifname} type vlan id {vlan_id}'
- if self.config['ethertype']:
- cmd += ' proto {ethertype}'
+ if self.config['protocol']:
+ cmd += ' protocol {protocol}'
if self.config['ingress_qos']:
cmd += ' ingress-qos-map {ingress_qos}'
if self.config['egress_qos']:
@@ -1108,20 +1139,6 @@ class VLANIf(Interface):
# interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
- @staticmethod
- def get_config():
- """
- MACsec interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = VLANIf().get_config()
- """
- config = deepcopy(__class__.default)
- del config['type']
- return config
-
def set_admin_state(self, state):
"""
Set interface administrative state to be 'up' or 'down'
diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py
index 33740921e..5fd90f9cf 100644
--- a/python/vyos/ifconfig/l2tpv3.py
+++ b/python/vyos/ifconfig/l2tpv3.py
@@ -13,7 +13,6 @@
# 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 os
from vyos.ifconfig.interface import Interface
@Interface.register
@@ -28,6 +27,15 @@ class L2TPv3If(Interface):
default = {
'type': 'l2tp',
+ 'peer_tunnel_id': '',
+ 'local_port': 0,
+ 'remote_port': 0,
+ 'encapsulation': 'udp',
+ 'local_address': '',
+ 'remote_address': '',
+ 'session_id': '',
+ 'tunnel_id': '',
+ 'peer_session_id': ''
}
definition = {
**Interface.definition,
@@ -73,7 +81,7 @@ class L2TPv3If(Interface):
>>> i.remove()
"""
- if os.path.exists('/sys/class/net/{}'.format(self.config['ifname'])):
+ if self.exists(self.config['ifname']):
# interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
@@ -86,25 +94,3 @@ class L2TPv3If(Interface):
cmd = 'ip l2tp del tunnel tunnel_id {tunnel_id}'
self._cmd(cmd.format(**self.config))
- @staticmethod
- def get_config():
- """
- L2TPv3 interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = L2TPv3If().get_config()
- """
- config = {
- 'peer_tunnel_id': '',
- 'local_port': 0,
- 'remote_port': 0,
- 'encapsulation': 'udp',
- 'local_address': '',
- 'remote_address': '',
- 'session_id': '',
- 'tunnel_id': '',
- 'peer_session_id': ''
- }
- return config
diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py
index 6f570d162..456686ea6 100644
--- a/python/vyos/ifconfig/macsec.py
+++ b/python/vyos/ifconfig/macsec.py
@@ -56,22 +56,6 @@ class MACsecIf(Interface):
# interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
- @staticmethod
- def get_config():
- """
- MACsec interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = MACsecIf().get_config()
- """
- config = {
- 'security_cipher': '',
- 'source_interface': '',
- }
- return config
-
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py
index 9c1d09c1c..2447fec77 100644
--- a/python/vyos/ifconfig/macvlan.py
+++ b/python/vyos/ifconfig/macvlan.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2020 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
@@ -13,7 +13,6 @@
# 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/>.
-from copy import deepcopy
from vyos.ifconfig.interface import Interface
@Interface.register
@@ -53,18 +52,6 @@ class MACVLANIf(Interface):
cmd = f'ip link set dev {ifname} type macvlan mode {mode}'
return self._cmd(cmd)
- @classmethod
- def get_config(cls):
- """
- MACVLAN interfaces require a configuration when they are added using
- iproute2. This method will provide the configuration dictionary used
- by this class.
-
- Example:
- >> dict = MACVLANIf().get_config()
- """
- return deepcopy(cls.default)
-
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index dba62b61a..ad1f605ed 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -13,8 +13,6 @@
# 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/>.
-from copy import deepcopy
-
from vyos import ConfigError
from vyos.ifconfig.interface import Interface
@@ -97,18 +95,6 @@ class VXLANIf(Interface):
self._cmd(cmd)
- @classmethod
- def get_config(cls):
- """
- VXLAN interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = VXLANIf().get_config()
- """
- return deepcopy(cls.default)
-
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py
index 346577119..deca68bf0 100644
--- a/python/vyos/ifconfig/wireless.py
+++ b/python/vyos/ifconfig/wireless.py
@@ -23,8 +23,10 @@ class WiFiIf(Interface):
default = {
'type': 'wifi',
- 'phy': 'phy0'
+ 'phy': '',
+ 'wds': 'off',
}
+
definition = {
**Interface.definition,
**{
@@ -33,12 +35,19 @@ class WiFiIf(Interface):
'bridgeable': True,
}
}
+
options = Interface.options + \
['phy', 'op_mode']
+ _command_set = {**Interface._command_set, **{
+ '4addr': {
+ 'shellcmd': 'iw dev {ifname} set 4addr {value}',
+ },
+ }}
+
def _create(self):
# all interfaces will be added in monitor mode
- cmd = 'iw phy {phy} interface add {ifname} type monitor' \
+ cmd = 'iw phy {phy} interface add {ifname} type monitor 4addr {wds}' \
.format(**self.config)
self._cmd(cmd)
@@ -50,21 +59,8 @@ class WiFiIf(Interface):
.format(**self.config)
self._cmd(cmd)
- @staticmethod
- def get_config():
- """
- WiFi interfaces require a configuration when they are added using
- iw (type/phy). This static method will provide the configuration
- ictionary used by this class.
-
- Example:
- >> conf = WiFiIf().get_config()
- """
- config = {
- 'phy': 'phy0'
- }
- return config
-
+ def set_4aadr_mode(self, state):
+ return self.set_interface('4addr', state)
def update(self, config):
""" General helper function which works on a dictionary retrived by
@@ -72,22 +68,11 @@ class WiFiIf(Interface):
interface setup code and provide a single point of entry when workin
on any interface. """
- # We can not call add_to_bridge() until wpa_supplicant is running, thus
- # we will remove the key from the config dict and react to this specal
- # case in thie derived class.
- # re-add ourselves to any bridge we might have fallen out of
- bridge_member = ''
- if 'is_bridge_member' in config:
- bridge_member = config['is_bridge_member']
- del config['is_bridge_member']
+ self.set_4aadr_mode('on' if 'wds' in config else 'off')
# call base class first
super().update(config)
- # re-add ourselves to any bridge we might have fallen out of
- if bridge_member:
- self.add_to_bridge(bridge_member)
-
# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index ceeb6888a..691cf3c8e 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -19,7 +19,6 @@ import netifaces
import ipaddress
from vyos.util import cmd
-from vyos import xml
# Important note when you are adding new validation functions:
#
@@ -267,46 +266,6 @@ def assert_mac(m):
raise ValueError(f'{m} is a VRRP MAC address')
-def is_member(conf, interface, intftype=None):
- """
- Checks if passed interface is member of other interface of specified type.
- intftype is optional, if not passed it will search all known types
- (currently bridge and bonding)
-
- Returns:
- None -> Interface is not a member
- interface name -> Interface is a member of this interface
- False -> interface type cannot have members
- """
- ret_val = None
- if intftype not in ['bonding', 'bridge', None]:
- raise ValueError((
- f'unknown interface type "{intftype}" or it cannot '
- f'have member interfaces'))
-
- intftype = ['bonding', 'bridge'] if intftype == None else [intftype]
-
- # set config level to root
- old_level = conf.get_level()
- conf.set_level([])
-
- for it in intftype:
- base = ['interfaces', it]
- for intf in conf.list_nodes(base):
- memberintf = base + [intf, 'member', 'interface']
- if xml.is_tag(memberintf):
- if interface in conf.list_nodes(memberintf):
- ret_val = intf
- break
- elif xml.is_leaf(memberintf):
- if ( conf.exists(memberintf) and
- interface in conf.return_values(memberintf) ):
- ret_val = intf
- break
-
- old_level = conf.set_level(old_level)
- return ret_val
-
def has_address_configured(conf, intf):
"""
Checks if interface has an address configured.
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 14ec7e137..047c19dd0 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -14,12 +14,15 @@
import os
import unittest
+import json
from netifaces import ifaddresses, AF_INET, AF_INET6
from vyos.configsession import ConfigSession
from vyos.ifconfig import Interface
from vyos.util import read_file
+from vyos.util import cmd
+from vyos.util import vyos_dict_search
from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local
class BasicInterfaceTest:
@@ -212,8 +215,12 @@ class BasicInterfaceTest:
self.session.set(base + ['address', address])
self.session.commit()
+
for interface in self._interfaces:
for vif_s in self._qinq_range:
+ tmp = json.loads(cmd(f'ip -d -j link show dev {interface}.{vif_s}'))[0]
+ self.assertEqual(vyos_dict_search('linkinfo.info_data.protocol', tmp), '802.1ad')
+
for vif_c in self._vlan_range:
vif = f'{interface}.{vif_s}.{vif_c}'
for address in self._test_addr:
diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index 0f1b6486d..6d1be86ba 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -16,15 +16,17 @@
import re
import unittest
-from psutil import process_iter
-from vyos.ifconfig import Section
from base_interfaces_test import BasicInterfaceTest
+from netifaces import interfaces
+
from vyos.configsession import ConfigSessionError
+from vyos.ifconfig import Section
from vyos.util import read_file
+from vyos.util import process_named_running
-def get_config_value(intf, key):
- tmp = read_file(f'/run/wpa_supplicant/{intf}.conf')
+def get_config_value(interface, key):
+ tmp = read_file(f'/run/wpa_supplicant/{interface}.conf')
tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp)
return tmp[0]
@@ -32,71 +34,123 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest):
def setUp(self):
super().setUp()
self._base_path = ['interfaces', 'macsec']
- self._options = {
- 'macsec0': ['source-interface eth0',
- 'security cipher gcm-aes-128']
- }
+ self._options = { 'macsec0': ['source-interface eth0', 'security cipher gcm-aes-128'] }
# if we have a physical eth1 interface, add a second macsec instance
if 'eth1' in Section.interfaces("ethernet"):
- macsec = { 'macsec1': ['source-interface eth1', 'security cipher gcm-aes-128'] }
+ macsec = { 'macsec1': [f'source-interface eth1', 'security cipher gcm-aes-128'] }
self._options.update(macsec)
self._interfaces = list(self._options)
def test_encryption(self):
- """ MACsec can be operating in authentication and encryption
- mode - both using different mandatory settings, lets test
- encryption as the basic authentication test has been performed
- using the base class tests """
- intf = 'macsec0'
- src_intf = 'eth0'
+ """ MACsec can be operating in authentication and encryption mode - both
+ using different mandatory settings, lets test encryption as the basic
+ authentication test has been performed using the base class tests """
+
mak_cak = '232e44b7fda6f8e2d88a07bf78a7aff4'
mak_ckn = '40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836'
replay_window = '64'
- self.session.set(self._base_path + [intf, 'security', 'encrypt'])
- # check validate() - Cipher suite must be set for MACsec
- with self.assertRaises(ConfigSessionError):
- self.session.commit()
- self.session.set(self._base_path + [intf, 'security', 'cipher', 'gcm-aes-128'])
+ for interface, option_value in self._options.items():
+ for option in option_value:
+ if option.split()[0] == 'source-interface':
+ src_interface = option.split()[1]
- # check validate() - Physical source interface must be set for MACsec
- with self.assertRaises(ConfigSessionError):
+ self.session.set(self._base_path + [interface] + option.split())
+
+ # Encrypt link
+ self.session.set(self._base_path + [interface, 'security', 'encrypt'])
+
+ # check validate() - Physical source interface MTU must be higher then our MTU
+ self.session.set(self._base_path + [interface, 'mtu', '1500'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(self._base_path + [interface, 'mtu'])
+
+ # check validate() - MACsec security keys mandartory when encryption is enabled
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(self._base_path + [interface, 'security', 'mka', 'cak', mak_cak])
+
+ # check validate() - MACsec security keys mandartory when encryption is enabled
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(self._base_path + [interface, 'security', 'mka', 'ckn', mak_ckn])
+
+ self.session.set(self._base_path + [interface, 'security', 'replay-window', replay_window])
+
+ # final commit of settings
self.session.commit()
- self.session.set(self._base_path + [intf, 'source-interface', src_intf])
- # check validate() - MACsec security keys mandartory when encryption is enabled
+ tmp = get_config_value(src_interface, 'macsec_integ_only')
+ self.assertTrue("0" in tmp)
+
+ tmp = get_config_value(src_interface, 'mka_cak')
+ self.assertTrue(mak_cak in tmp)
+
+ tmp = get_config_value(src_interface, 'mka_ckn')
+ self.assertTrue(mak_ckn in tmp)
+
+ # check that the default priority of 255 is programmed
+ tmp = get_config_value(src_interface, 'mka_priority')
+ self.assertTrue("255" in tmp)
+
+ tmp = get_config_value(src_interface, 'macsec_replay_window')
+ self.assertTrue(replay_window in tmp)
+
+ tmp = read_file(f'/sys/class/net/{interface}/mtu')
+ self.assertEqual(tmp, '1460')
+
+ # Check for running process
+ self.assertTrue(process_named_running('wpa_supplicant'))
+
+ def test_mandatory_toptions(self):
+ interface = 'macsec1'
+ self.session.set(self._base_path + [interface])
+
+ # check validate() - source interface is mandatory
with self.assertRaises(ConfigSessionError):
self.session.commit()
- self.session.set(self._base_path + [intf, 'security', 'mka', 'cak', mak_cak])
+ self.session.set(self._base_path + [interface, 'source-interface', 'eth0'])
- # check validate() - MACsec security keys mandartory when encryption is enabled
+ # check validate() - cipher is mandatory
with self.assertRaises(ConfigSessionError):
self.session.commit()
- self.session.set(self._base_path + [intf, 'security', 'mka', 'ckn', mak_ckn])
+ self.session.set(self._base_path + [interface, 'security', 'cipher', 'gcm-aes-128'])
- self.session.set(self._base_path + [intf, 'security', 'replay-window', replay_window])
+ # final commit and verify
self.session.commit()
+ self.assertIn(interface, interfaces())
- tmp = get_config_value(src_intf, 'macsec_integ_only')
- self.assertTrue("0" in tmp)
+ def test_source_interface(self):
+ """ Ensure source-interface can bot be part of any other bond or bridge """
- tmp = get_config_value(src_intf, 'mka_cak')
- self.assertTrue(mak_cak in tmp)
+ base_bridge = ['interfaces', 'bridge', 'br200']
+ base_bond = ['interfaces', 'bonding', 'bond200']
- tmp = get_config_value(src_intf, 'mka_ckn')
- self.assertTrue(mak_ckn in tmp)
+ for interface, option_value in self._options.items():
+ for option in option_value:
+ self.session.set(self._base_path + [interface] + option.split())
+ if option.split()[0] == 'source-interface':
+ src_interface = option.split()[1]
- # check that the default priority of 255 is programmed
- tmp = get_config_value(src_intf, 'mka_priority')
- self.assertTrue("255" in tmp)
+ self.session.set(base_bridge + ['member', 'interface', src_interface])
+ # check validate() - Source interface must not already be a member of a bridge
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(base_bridge)
- tmp = get_config_value(src_intf, 'macsec_replay_window')
- self.assertTrue(replay_window in tmp)
+ self.session.set(base_bond + ['member', 'interface', src_interface])
+ # check validate() - Source interface must not already be a member of a bridge
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(base_bond)
- # Check for running process
- self.assertTrue("wpa_supplicant" in (p.name() for p in process_iter()))
+ # final commit and verify
+ self.session.commit()
+ self.assertIn(interface, interfaces())
if __name__ == '__main__':
unittest.main()
+
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index 691f633b7..0e93b6432 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -19,8 +19,7 @@ import re
import unittest
from base_interfaces_test import BasicInterfaceTest
-from psutil import process_iter
-
+from vyos.util import process_named_running
from vyos.util import check_kmod
from vyos.util import read_file
@@ -54,10 +53,10 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
for option, option_value in self._options.items():
if 'type access-point' in option_value:
# Check for running process
- self.assertIn('hostapd', (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running('hostapd'))
elif 'type station' in option_value:
# Check for running process
- self.assertIn('wpa_supplicant', (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running('wpa_supplicant'))
else:
self.assertTrue(False)
@@ -137,8 +136,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
self.assertIn(value, tmp)
# Check for running process
- self.assertIn('hostapd', (p.name() for p in process_iter()))
-
+ self.assertTrue(process_named_running('hostapd'))
if __name__ == '__main__':
check_kmod('mac80211_hwsim')
diff --git a/smoketest/scripts/cli/test_interfaces_wirelessmodem.py b/smoketest/scripts/cli/test_interfaces_wirelessmodem.py
index 40cd03b93..efc9c0e98 100755
--- a/smoketest/scripts/cli/test_interfaces_wirelessmodem.py
+++ b/smoketest/scripts/cli/test_interfaces_wirelessmodem.py
@@ -43,7 +43,7 @@ class WWANInterfaceTest(unittest.TestCase):
def test_wlm_1(self):
for interface in self._interfaces:
self.session.set(base_path + [interface, 'no-peer-dns'])
- self.session.set(base_path + [interface, 'ondemand'])
+ self.session.set(base_path + [interface, 'connect-on-demand'])
# check validate() - APN must be configure
with self.assertRaises(ConfigSessionError):
diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index be52360ed..c7ac87135 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -19,10 +19,12 @@ import os
import unittest
from getpass import getuser
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
from vyos.util import read_file
+from vyos.util import process_named_running
+PROCESS_NAME = 'ddclient'
DDCLIENT_CONF = '/run/ddclient/ddclient.conf'
base_path = ['service', 'dns', 'dynamic']
@@ -32,17 +34,6 @@ def get_config_value(key):
tmp = tmp[0].rstrip(',')
return tmp
-def check_process():
- """
- Check for running process, process name changes dynamically e.g.
- "ddclient - sleeping for 270 seconds", thus we need a different approach
- """
- running = False
- for p in process_iter():
- if "ddclient" in p.name():
- running = True
- return running
-
class TestServiceDDNS(unittest.TestCase):
def setUp(self):
self.session = ConfigSession(os.getpid())
@@ -104,8 +95,7 @@ class TestServiceDDNS(unittest.TestCase):
self.assertTrue(pwd == "'" + password + "'")
# Check for running process
- self.assertTrue(check_process())
-
+ self.assertTrue(process_named_running(PROCESS_NAME))
def test_rfc2136(self):
""" Check if DDNS service can be configured and runs """
@@ -135,7 +125,7 @@ class TestServiceDDNS(unittest.TestCase):
# TODO: inspect generated configuration file
# Check for running process
- self.assertTrue(check_process())
+ self.assertTrue(process_named_running(PROCESS_NAME))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
new file mode 100755
index 000000000..5e2f3dfbd
--- /dev/null
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import os
+import unittest
+
+from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos.util import read_file
+from vyos.util import process_named_running
+
+CONFIG_FILE = '/run/powerdns/recursor.conf'
+FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf'
+HOSTSD_FILE = '/run/powerdns/recursor.vyos-hostsd.conf.lua'
+PROCESS_NAME= 'pdns-r/worker'
+
+base_path = ['service', 'dns', 'forwarding']
+
+allow_from = ['192.0.2.0/24', '2001:db8::/32']
+listen_adress = ['127.0.0.1', '::1']
+
+def get_config_value(key, file=CONFIG_FILE):
+ tmp = read_file(file)
+ tmp = re.findall(r'\n{}=+(.*)'.format(key), tmp)
+ return tmp[0]
+
+class TestServicePowerDNS(unittest.TestCase):
+ def setUp(self):
+ self.session = ConfigSession(os.getpid())
+
+ def tearDown(self):
+ # Delete DNS forwarding configuration
+ self.session.delete(base_path)
+ self.session.commit()
+ del self.session
+
+ def test_basic_forwarding(self):
+ """ Check basic DNS forwarding settings """
+ cache_size = '20'
+ negative_ttl = '120'
+
+ self.session.set(base_path + ['cache-size', cache_size])
+ self.session.set(base_path + ['negative-ttl', negative_ttl])
+
+ # check validate() - allow from must be defined
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ for network in allow_from:
+ self.session.set(base_path + ['allow-from', network])
+
+ # check validate() - listen-address must be defined
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ for address in listen_adress:
+ self.session.set(base_path + ['listen-address', address])
+
+ # configure DNSSEC
+ self.session.set(base_path + ['dnssec', 'validate'])
+
+ # Do not use local /etc/hosts file in name resolution
+ self.session.set(base_path + ['ignore-hosts-file'])
+
+ # commit changes
+ self.session.commit()
+
+ # Check configured cache-size
+ tmp = get_config_value('max-cache-entries')
+ self.assertEqual(tmp, cache_size)
+
+ # Networks allowed to query this server
+ tmp = get_config_value('allow-from')
+ self.assertEqual(tmp, ','.join(allow_from))
+
+ # Addresses to listen for DNS queries
+ tmp = get_config_value('local-address')
+ self.assertEqual(tmp, ','.join(listen_adress))
+
+ # Maximum amount of time negative entries are cached
+ tmp = get_config_value('max-negative-ttl')
+ self.assertEqual(tmp, negative_ttl)
+
+ # Do not use local /etc/hosts file in name resolution
+ tmp = get_config_value('export-etc-hosts')
+ self.assertEqual(tmp, 'no')
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ def test_dnssec(self):
+ """ DNSSEC option testing """
+
+ for network in allow_from:
+ self.session.set(base_path + ['allow-from', network])
+ for address in listen_adress:
+ self.session.set(base_path + ['listen-address', address])
+
+ options = ['off', 'process-no-validate', 'process', 'log-fail', 'validate']
+ for option in options:
+ self.session.set(base_path + ['dnssec', option])
+
+ # commit changes
+ self.session.commit()
+
+ tmp = get_config_value('dnssec')
+ self.assertEqual(tmp, option)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ def test_external_nameserver(self):
+ """ Externe Domain Name Servers (DNS) addresses """
+
+ for network in allow_from:
+ self.session.set(base_path + ['allow-from', network])
+ for address in listen_adress:
+ self.session.set(base_path + ['listen-address', address])
+
+ nameservers = ['192.0.2.1', '192.0.2.2']
+ for nameserver in nameservers:
+ self.session.set(base_path + ['name-server', nameserver])
+
+ # commit changes
+ self.session.commit()
+
+ tmp = get_config_value(r'\+.', file=FORWARD_FILE)
+ self.assertEqual(tmp, ', '.join(nameservers))
+
+ # Do not use local /etc/hosts file in name resolution
+ # default: yes
+ tmp = get_config_value('export-etc-hosts')
+ self.assertEqual(tmp, 'yes')
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ def test_domain_forwarding(self):
+ """ Externe Domain Name Servers (DNS) addresses """
+
+ for network in allow_from:
+ self.session.set(base_path + ['allow-from', network])
+ for address in listen_adress:
+ self.session.set(base_path + ['listen-address', address])
+
+ domains = ['vyos.io', 'vyos.net', 'vyos.com']
+ nameservers = ['192.0.2.1', '192.0.2.2']
+ for domain in domains:
+ for nameserver in nameservers:
+ self.session.set(base_path + ['domain', domain, 'server', nameserver])
+
+ # Test 'recursion-desired' flag for only one domain
+ if domain == domains[0]:
+ self.session.set(base_path + ['domain', domain, 'recursion-desired'])
+
+ # Test 'negative trust anchor' flag for the second domain only
+ if domain == domains[1]:
+ self.session.set(base_path + ['domain', domain, 'addnta'])
+
+ # commit changes
+ self.session.commit()
+
+ # Test configured name-servers
+ hosts_conf = read_file(HOSTSD_FILE)
+ for domain in domains:
+ # Test 'recursion-desired' flag for the first domain only
+ if domain == domains[0]: key =f'\+{domain}'
+ else: key =f'{domain}'
+ tmp = get_config_value(key, file=FORWARD_FILE)
+ self.assertEqual(tmp, ', '.join(nameservers))
+
+ # Test 'negative trust anchor' flag for the second domain only
+ if domain == domains[1]:
+ self.assertIn(f'addNTA("{domain}", "static")', hosts_conf)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py
index 18900b6d2..de73b9914 100755
--- a/smoketest/scripts/cli/test_service_mdns-repeater.py
+++ b/smoketest/scripts/cli/test_service_mdns-repeater.py
@@ -17,8 +17,8 @@
import os
import unittest
-from psutil import process_iter
from vyos.configsession import ConfigSession
+from vyos.util import process_named_running
base_path = ['service', 'mdns', 'repeater']
intf_base = ['interfaces', 'dummy']
@@ -45,7 +45,7 @@ class TestServiceMDNSrepeater(unittest.TestCase):
self.session.commit()
# Check for running process
- self.assertTrue("mdns-repeater" in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running('mdns-repeater'))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py
index 901ca792d..3a6b12ef4 100755
--- a/smoketest/scripts/cli/test_service_pppoe-server.py
+++ b/smoketest/scripts/cli/test_service_pppoe-server.py
@@ -14,15 +14,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import re
import os
import unittest
from configparser import ConfigParser
-from psutil import process_iter
from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
+from vyos.util import process_named_running
+process_name = 'accel-pppd'
base_path = ['service', 'pppoe-server']
local_if = ['interfaces', 'dummy', 'dum667']
pppoe_conf = '/run/accel-pppd/pppoe.conf'
@@ -116,7 +116,7 @@ class TestServicePPPoEServer(unittest.TestCase):
self.assertEqual(conf['connlimit']['limit'], '20/min')
# Check for running process
- self.assertTrue('accel-pppd' in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running(process_name))
def test_radius_auth(self):
""" Test configuration of RADIUS authentication for PPPoE server """
@@ -161,7 +161,7 @@ class TestServicePPPoEServer(unittest.TestCase):
self.assertFalse(conf['ppp'].getboolean('ccp'))
# Check for running process
- self.assertTrue('accel-pppd' in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running(process_name))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index ec2110c8a..238f59e6d 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -18,9 +18,9 @@ import re
import os
import unittest
-from psutil import process_iter
from vyos.configsession import ConfigSession
from vyos.util import read_file
+from vyos.util import process_named_running
RADVD_CONF = '/run/radvd/radvd.conf'
@@ -90,10 +90,8 @@ class TestServiceRADVD(unittest.TestCase):
tmp = get_config_value('AdvOnLink')
self.assertEqual(tmp, 'off')
-
-
# Check for running process
- self.assertTrue('radvd' in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running('radvd'))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py
index fb5f5393f..067a3c76b 100755
--- a/smoketest/scripts/cli/test_service_snmp.py
+++ b/smoketest/scripts/cli/test_service_snmp.py
@@ -19,12 +19,15 @@ import re
import unittest
from vyos.validate import is_ipv4
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
from vyos.util import read_file
+from vyos.util import process_named_running
+PROCESS_NAME = 'snmpd'
SNMPD_CONF = '/etc/snmp/snmpd.conf'
+
base_path = ['service', 'snmp']
def get_config_value(key):
@@ -78,7 +81,7 @@ class TestSNMPService(unittest.TestCase):
self.assertTrue(expected in config)
# Check for running process
- self.assertTrue("snmpd" in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running(PROCESS_NAME))
def test_snmpv3_sha(self):
@@ -113,7 +116,7 @@ class TestSNMPService(unittest.TestCase):
# TODO: read in config file and check values
# Check for running process
- self.assertTrue("snmpd" in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running(PROCESS_NAME))
def test_snmpv3_md5(self):
""" Check if SNMPv3 can be configured with MD5 authentication and service runs"""
@@ -147,8 +150,7 @@ class TestSNMPService(unittest.TestCase):
# TODO: read in config file and check values
# Check for running process
- self.assertTrue("snmpd" in (p.name() for p in process_iter()))
-
+ self.assertTrue(process_named_running(PROCESS_NAME))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py
index 79850fe44..0cd00ccce 100755
--- a/smoketest/scripts/cli/test_service_ssh.py
+++ b/smoketest/scripts/cli/test_service_ssh.py
@@ -18,10 +18,12 @@ import re
import os
import unittest
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
from vyos.util import read_file
+from vyos.util import process_named_running
+PROCESS_NAME = 'sshd'
SSHD_CONF = '/run/ssh/sshd_config'
base_path = ['service', 'ssh']
@@ -30,9 +32,6 @@ def get_config_value(key):
tmp = re.findall(f'\n?{key}\s+(.*)', tmp)
return tmp
-def is_service_running():
- return 'sshd' in (p.name() for p in process_iter())
-
class TestServiceSSH(unittest.TestCase):
def setUp(self):
self.session = ConfigSession(os.getpid())
@@ -62,7 +61,7 @@ class TestServiceSSH(unittest.TestCase):
self.assertEqual('22', port)
# Check for running process
- self.assertTrue(is_service_running())
+ self.assertTrue(process_named_running(PROCESS_NAME))
def test_ssh_single(self):
""" Check if SSH service can be configured and runs """
@@ -101,7 +100,7 @@ class TestServiceSSH(unittest.TestCase):
self.assertTrue("100" in keepalive)
# Check for running process
- self.assertTrue(is_service_running())
+ self.assertTrue(process_named_running(PROCESS_NAME))
def test_ssh_multi(self):
""" Check if SSH service can be configured and runs with multiple
@@ -128,7 +127,7 @@ class TestServiceSSH(unittest.TestCase):
self.assertIn(address, tmp)
# Check for running process
- self.assertTrue(is_service_running())
+ self.assertTrue(process_named_running(PROCESS_NAME))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_system_lcd.py b/smoketest/scripts/cli/test_system_lcd.py
index 931a91c53..9385799b0 100755
--- a/smoketest/scripts/cli/test_system_lcd.py
+++ b/smoketest/scripts/cli/test_system_lcd.py
@@ -18,9 +18,10 @@ import os
import unittest
from configparser import ConfigParser
-from psutil import process_iter
from vyos.configsession import ConfigSession
+from vyos.util import process_named_running
+config_file = '/run/LCDd/LCDd.conf'
base_path = ['system', 'lcd']
class TestSystemLCD(unittest.TestCase):
@@ -42,13 +43,13 @@ class TestSystemLCD(unittest.TestCase):
# load up ini-styled LCDd.conf
conf = ConfigParser()
- conf.read('/run/LCDd/LCDd.conf')
+ conf.read(config_file)
self.assertEqual(conf['CFontzPacket']['Model'], '533')
self.assertEqual(conf['CFontzPacket']['Device'], '/dev/ttyS1')
- # both processes running
- self.assertTrue('LCDd' in (p.name() for p in process_iter()))
+ # Check for running process
+ self.assertTrue(process_named_running('LCDd'))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py
index 856a28916..2a7c64870 100755
--- a/smoketest/scripts/cli/test_system_ntp.py
+++ b/smoketest/scripts/cli/test_system_ntp.py
@@ -18,11 +18,14 @@ import re
import os
import unittest
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
-from vyos.template import vyos_address_from_cidr, vyos_netmask_from_cidr
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
+from vyos.template import vyos_address_from_cidr
+from vyos.template import vyos_netmask_from_cidr
from vyos.util import read_file
+from vyos.util import process_named_running
+PROCESS_NAME = 'ntpd'
NTP_CONF = '/etc/ntp.conf'
base_path = ['system', 'ntp']
@@ -63,7 +66,7 @@ class TestSystemNTP(unittest.TestCase):
self.assertTrue(test in tmp)
# Check for running process
- self.assertTrue("ntpd" in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running(PROCESS_NAME))
def test_ntp_clients(self):
""" Test the allowed-networks statement """
@@ -102,7 +105,7 @@ class TestSystemNTP(unittest.TestCase):
self.assertEqual(tmp, test)
# Check for running process
- self.assertTrue("ntpd" in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running(PROCESS_NAME))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py
index d2b82d686..2ba6aabf9 100755
--- a/smoketest/scripts/cli/test_vpn_openconnect.py
+++ b/smoketest/scripts/cli/test_vpn_openconnect.py
@@ -14,13 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import re
import os
import unittest
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
-from vyos.util import read_file
+from vyos.configsession import ConfigSession
+from vyos.util import process_named_running
OCSERV_CONF = '/run/ocserv/ocserv.conf'
base_path = ['vpn', 'openconnect']
@@ -52,7 +50,7 @@ class TestVpnOpenconnect(unittest.TestCase):
self.session.commit()
# Check for running process
- self.assertTrue("ocserv-main" in (p.name() for p in process_iter()))
+ self.assertTrue(process_named_running('ocserv-main'))
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py
new file mode 100755
index 000000000..043567c4f
--- /dev/null
+++ b/smoketest/scripts/system/test_kernel_options.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import platform
+import unittest
+
+from vyos.util import read_file
+
+kernel = platform.release()
+config = read_file(f'/boot/config-{kernel}')
+
+class TestKernelModules(unittest.TestCase):
+ """ VyOS makes use of a lot of Kernel drivers, modules and features. The
+ required modules which are essential for VyOS should be tested that they are
+ available in the Kernel that is run. """
+
+ def test_bond_interface(self):
+ """ The bond/lacp interface must be enabled in the OS Kernel """
+ for option in ['CONFIG_BONDING']:
+ tmp = re.findall(f'{option}=(y|m)', config)
+ self.assertTrue(tmp)
+
+ def test_bridge_interface(self):
+ """ The bridge interface must be enabled in the OS Kernel """
+ for option in ['CONFIG_BRIDGE',
+ 'CONFIG_BRIDGE_IGMP_SNOOPING',
+ 'CONFIG_BRIDGE_VLAN_FILTERING']:
+ tmp = re.findall(f'{option}=(y|m)', config)
+ self.assertTrue(tmp)
+
+ def test_qemu_support(self):
+ """ The bond/lacp interface must be enabled in the OS Kernel """
+ for option in ['CONFIG_VIRTIO_BLK', 'CONFIG_SCSI_VIRTIO',
+ 'CONFIG_VIRTIO_NET', 'CONFIG_VIRTIO_CONSOLE',
+ 'CONFIG_VIRTIO', 'CONFIG_VIRTIO_PCI',
+ 'CONFIG_VIRTIO_BALLOON', 'CONFIG_CRYPTO_DEV_VIRTIO',
+ 'CONFIG_X86_PLATFORM_DEVICES']:
+ tmp = re.findall(f'{option}=(y|m)', config)
+ self.assertTrue(tmp)
+
+ def test_vmware_support(self):
+ """ The bond/lacp interface must be enabled in the OS Kernel """
+ for option in ['CONFIG_VMXNET3']:
+ tmp = re.findall(f'{option}=(y|m)', config)
+ self.assertTrue(tmp)
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 53bc37882..5101c1e79 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -17,14 +17,17 @@
import os
from sys import exit
-from copy import deepcopy
from vyos.config import Config
+from vyos.configdict import dict_merge
from vyos.hostsd_client import Client as hostsd_client
-from vyos import ConfigError
-from vyos.util import call, chown
+from vyos.util import call
+from vyos.util import chown
+from vyos.util import vyos_dict_search
from vyos.template import render
+from vyos.xml import defaults
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -35,136 +38,84 @@ pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.l
pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf'
pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf'
-default_config_data = {
- 'allow_from': [],
- 'cache_size': 10000,
- 'export_hosts_file': 'yes',
- 'listen_address': [],
- 'name_servers': [],
- 'negative_ttl': 3600,
- 'system': False,
- 'domains': {},
- 'dnssec': 'process-no-validate',
- 'dhcp_interfaces': []
-}
-
hostsd_tag = 'static'
-def get_config(conf):
- dns = deepcopy(default_config_data)
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
base = ['service', 'dns', 'forwarding']
-
if not conf.exists(base):
return None
- conf.set_level(base)
-
- if conf.exists(['allow-from']):
- dns['allow_from'] = conf.return_values(['allow-from'])
-
- if conf.exists(['cache-size']):
- cache_size = conf.return_value(['cache-size'])
- dns['cache_size'] = cache_size
-
- if conf.exists('negative-ttl'):
- negative_ttl = conf.return_value(['negative-ttl'])
- dns['negative_ttl'] = negative_ttl
-
- if conf.exists(['domain']):
- for domain in conf.list_nodes(['domain']):
- conf.set_level(base + ['domain', domain])
- entry = {
- 'nslist': bracketize_ipv6_addrs(conf.return_values(['server'])),
- 'addNTA': conf.exists(['addnta']),
- 'recursion-desired': conf.exists(['recursion-desired'])
- }
- dns['domains'][domain] = entry
-
- conf.set_level(base)
+ dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ dns = dict_merge(default_values, dns)
- if conf.exists(['ignore-hosts-file']):
- dns['export_hosts_file'] = "no"
+ # some additions to the default dictionary
+ if 'system' in dns:
+ base_nameservers = ['system', 'name-server']
+ if conf.exists(base_nameservers):
+ dns.update({'system_name_server': conf.return_values(base_nameservers)})
- if conf.exists(['name-server']):
- dns['name_servers'] = bracketize_ipv6_addrs(
- conf.return_values(['name-server']))
-
- if conf.exists(['system']):
- dns['system'] = True
-
- if conf.exists(['listen-address']):
- dns['listen_address'] = conf.return_values(['listen-address'])
-
- if conf.exists(['dnssec']):
- dns['dnssec'] = conf.return_value(['dnssec'])
-
- if conf.exists(['dhcp']):
- dns['dhcp_interfaces'] = conf.return_values(['dhcp'])
+ base_nameservers_dhcp = ['system', 'name-servers-dhcp']
+ if conf.exists(base_nameservers_dhcp):
+ dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)})
return dns
-def bracketize_ipv6_addrs(addrs):
- """Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched."""
- return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs]
-
-def verify(conf, dns):
+def verify(dns):
# bail out early - looks like removal from running config
- if dns is None:
+ if not dns:
return None
- if not dns['listen_address']:
- raise ConfigError(
- "Error: DNS forwarding requires a listen-address")
-
- if not dns['allow_from']:
- raise ConfigError(
- "Error: DNS forwarding requires an allow-from network")
-
- if dns['domains']:
- for domain in dns['domains']:
- if not dns['domains'][domain]['nslist']:
- raise ConfigError((
- f'Error: No server configured for domain {domain}'))
-
- no_system_nameservers = False
- conf.set_level([])
- if dns['system'] and not (
- conf.exists(['system', 'name-server']) or
- conf.exists(['system', 'name-servers-dhcp']) ):
- no_system_nameservers = True
- print(("DNS forwarding warning: No 'system name-server' or "
- "'system name-servers-dhcp' set\n"))
-
- if (no_system_nameservers or not dns['system']) and not (
- dns['name_servers'] or dns['dhcp_interfaces']):
- print(("DNS forwarding warning: No 'dhcp', 'name-server' or 'system' "
- "nameservers set. Forwarding will operate as a recursor.\n"))
+ if 'listen_address' not in dns:
+ raise ConfigError('DNS forwarding requires a listen-address')
+
+ if 'allow_from' not in dns:
+ raise ConfigError('DNS forwarding requires an allow-from network')
+
+ # we can not use vyos_dict_search() when testing for domain servers
+ # as a domain will contains dot's which is out dictionary delimiter.
+ if 'domain' in dns:
+ for domain in dns['domain']:
+ if 'server' not in dns['domain'][domain]:
+ raise ConfigError(f'No server configured for domain {domain}!')
+
+ if 'system' in dns:
+ if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns):
+ print("Warning: No 'system name-server' or 'system " \
+ "name-servers-dhcp' configured")
return None
def generate(dns):
# bail out early - looks like removal from running config
- if dns is None:
+ if not dns:
return None
render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl',
- dns, user=pdns_rec_user, group=pdns_rec_group)
+ dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group)
render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl',
- dns, user=pdns_rec_user, group=pdns_rec_group)
+ dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group)
# if vyos-hostsd didn't create its files yet, create them (empty)
- for f in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]:
- with open(f, 'a'):
+ for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]:
+ with open(file, 'a'):
pass
- chown(f, user=pdns_rec_user, group=pdns_rec_group)
+ chown(file, user=pdns_rec_user, group=pdns_rec_group)
return None
def apply(dns):
- if dns is None:
+ if not dns:
# DNS forwarding is removed in the commit
- call("systemctl stop pdns-recursor.service")
+ call('systemctl stop pdns-recursor.service')
+
if os.path.isfile(pdns_rec_config_file):
os.unlink(pdns_rec_config_file)
else:
@@ -174,8 +125,8 @@ def apply(dns):
# add static nameservers to hostsd so they can be joined with other
# sources
hc.delete_name_servers([hostsd_tag])
- if dns['name_servers']:
- hc.add_name_servers({hostsd_tag: dns['name_servers']})
+ if 'name_server' in dns:
+ hc.add_name_servers({hostsd_tag: dns['name_server']})
# delete all nameserver tags
hc.delete_name_server_tags_recursor(hc.get_name_server_tags_recursor())
@@ -184,32 +135,33 @@ def apply(dns):
# our own tag (static)
hc.add_name_server_tags_recursor([hostsd_tag])
- if dns['system']:
+ if 'system' in dns:
hc.add_name_server_tags_recursor(['system'])
else:
hc.delete_name_server_tags_recursor(['system'])
# add dhcp nameserver tags for configured interfaces
- for intf in dns['dhcp_interfaces']:
- hc.add_name_server_tags_recursor(['dhcp-' + intf, 'dhcpv6-' + intf ])
+ if 'system_name_server_dhcp' in dns:
+ for interface in dns['system_name_server_dhcp']:
+ hc.add_name_server_tags_recursor(['dhcp-' + interface,
+ 'dhcpv6-' + interface ])
# hostsd will generate the forward-zones file
# the list and keys() are required as get returns a dict, not list
hc.delete_forward_zones(list(hc.get_forward_zones().keys()))
- if dns['domains']:
- hc.add_forward_zones(dns['domains'])
+ if 'domain' in dns:
+ hc.add_forward_zones(dns['domain'])
# call hostsd to generate forward-zones and its lua-config-file
hc.apply()
### finally (re)start pdns-recursor
- call("systemctl restart pdns-recursor.service")
+ call('systemctl restart pdns-recursor.service')
if __name__ == '__main__':
try:
- conf = Config()
- c = get_config(conf)
- verify(conf, c)
+ c = get_config()
+ verify(c)
generate(c)
apply(c)
except ConfigError as e:
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index a9679b47c..9763620ac 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -22,15 +22,18 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_member
+from vyos.configdict import is_source_interface
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_dhcpv6
from vyos.configverify import verify_source_interface
+from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
from vyos.ifconfig import BondIf
from vyos.ifconfig import Section
-from vyos.validate import is_member
+from vyos.util import vyos_dict_search
from vyos.validate import has_address_configured
from vyos import ConfigError
from vyos import airbag
@@ -98,18 +101,21 @@ def get_config(config=None):
# also present the interfaces to be removed from the bond as dictionary
bond['member'].update({'interface_remove': tmp})
- if 'member' in bond and 'interface' in bond['member']:
+ if vyos_dict_search('member.interface', bond):
for interface, interface_config in bond['member']['interface'].items():
- # Check if we are a member of another bond device
+ # Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
- if tmp:
- interface_config.update({'is_bridge_member' : tmp})
+ if tmp: interface_config.update({'is_bridge_member' : tmp})
- # Check if we are a member of a bond device
+ # Check if member interface is already member of a bond
tmp = is_member(conf, interface, 'bonding')
if tmp and tmp != bond['ifname']:
interface_config.update({'is_bond_member' : tmp})
+ # Check if member interface is used as source-interface on another interface
+ tmp = is_source_interface(conf, interface)
+ if tmp: interface_config.update({'is_source_interface' : tmp})
+
# bond members must not have an assigned address
tmp = has_address_configured(conf, interface)
if tmp: interface_config.update({'has_address' : ''})
@@ -136,6 +142,7 @@ def verify(bond):
raise ConfigError('Option primary - mode dependency failed, not'
'supported in mode {mode}!'.format(**bond))
+ verify_mtu_ipv6(bond)
verify_address(bond)
verify_dhcpv6(bond)
verify_vrf(bond)
@@ -144,10 +151,9 @@ def verify(bond):
verify_vlan_config(bond)
bond_name = bond['ifname']
- if 'member' in bond:
- member = bond.get('member')
- for interface, interface_config in member.get('interface', {}).items():
- error_msg = f'Can not add interface "{interface}" to bond "{bond_name}", '
+ if vyos_dict_search('member.interface', bond):
+ for interface, interface_config in bond['member']['interface'].items():
+ error_msg = f'Can not add interface "{interface}" to bond, '
if interface == 'lo':
raise ConfigError('Loopback interface "lo" can not be added to a bond')
@@ -163,6 +169,10 @@ def verify(bond):
tmp = interface_config['is_bond_member']
raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!')
+ if 'is_source_interface' in interface_config:
+ tmp = interface_config['is_source_interface']
+ raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!')
+
if 'has_address' in interface_config:
raise ConfigError(error_msg + 'it has an address assigned!')
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 47c8c05f9..485decb17 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -22,13 +22,16 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import node_changed
+from vyos.configdict import is_member
+from vyos.configdict import is_source_interface
from vyos.configverify import verify_dhcpv6
from vyos.configverify import verify_vrf
from vyos.ifconfig import BridgeIf
-from vyos.validate import is_member, has_address_configured
+from vyos.validate import has_address_configured
from vyos.xml import defaults
from vyos.util import cmd
+from vyos.util import vyos_dict_search
from vyos import ConfigError
from vyos import airbag
@@ -54,8 +57,8 @@ def get_config(config=None):
else:
bridge.update({'member': {'interface_remove': tmp }})
- if 'member' in bridge and 'interface' in bridge['member']:
- # XXX TT2665 we need a copy of the dict keys for iteration, else we will get:
+ if vyos_dict_search('member.interface', bridge):
+ # XXX: T2665: we need a copy of the dict keys for iteration, else we will get:
# RuntimeError: dictionary changed size during iteration
for interface in list(bridge['member']['interface']):
for key in ['cost', 'priority']:
@@ -69,20 +72,22 @@ def get_config(config=None):
for interface, interface_config in bridge['member']['interface'].items():
interface_config.update(default_member_values)
- # Check if we are a member of another bridge device
+ # Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
if tmp and tmp != bridge['ifname']:
interface_config.update({'is_bridge_member' : tmp})
- # Check if we are a member of a bond device
+ # Check if member interface is already member of a bond
tmp = is_member(conf, interface, 'bonding')
- if tmp:
- interface_config.update({'is_bond_member' : tmp})
+ if tmp: interface_config.update({'is_bond_member' : tmp})
+
+ # Check if member interface is used as source-interface on another interface
+ tmp = is_source_interface(conf, interface)
+ if tmp: interface_config.update({'is_source_interface' : tmp})
# Bridge members must not have an assigned address
tmp = has_address_configured(conf, interface)
- if tmp:
- interface_config.update({'has_address' : ''})
+ if tmp: interface_config.update({'has_address' : ''})
return bridge
@@ -93,11 +98,9 @@ def verify(bridge):
verify_dhcpv6(bridge)
verify_vrf(bridge)
- if 'member' in bridge:
- member = bridge.get('member')
- bridge_name = bridge['ifname']
- for interface, interface_config in member.get('interface', {}).items():
- error_msg = f'Can not add interface "{interface}" to bridge "{bridge_name}", '
+ if vyos_dict_search('member.interface', bridge):
+ for interface, interface_config in bridge['member']['interface'].items():
+ error_msg = f'Can not add interface "{interface}" to bridge, '
if interface == 'lo':
raise ConfigError('Loopback interface "lo" can not be added to a bridge')
@@ -113,6 +116,10 @@ def verify(bridge):
tmp = interface_config['is_bond_member']
raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!')
+ if 'is_source_interface' in interface_config:
+ tmp = interface_config['is_source_interface']
+ raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!')
+
if 'has_address' in interface_config:
raise ConfigError(error_msg + 'it has an address assigned!')
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index a8df64cce..1f622c003 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -20,11 +20,13 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configverify import verify_interface_exists
-from vyos.configverify import verify_dhcpv6
from vyos.configverify import verify_address
-from vyos.configverify import verify_vrf
+from vyos.configverify import verify_dhcpv6
+from vyos.configverify import verify_interface_exists
+from vyos.configverify import verify_mtu
+from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
+from vyos.configverify import verify_vrf
from vyos.ifconfig import EthernetIf
from vyos import ConfigError
from vyos import airbag
@@ -41,6 +43,7 @@ def get_config(config=None):
conf = Config()
base = ['interfaces', 'ethernet']
ethernet = get_interface_dict(conf, base)
+
return ethernet
def verify(ethernet):
@@ -57,6 +60,8 @@ def verify(ethernet):
if ethernet.get('speed', None) != 'auto':
raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too')
+ verify_mtu(ethernet)
+ verify_mtu_ipv6(ethernet)
verify_dhcpv6(ethernet)
verify_address(ethernet)
verify_vrf(ethernet)
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index cc2cf025a..979a5612e 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -17,12 +17,12 @@
import os
from sys import exit
-from copy import deepcopy
from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_address
+from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_bridge_delete
from vyos.ifconfig import GeneveIf
from vyos import ConfigError
@@ -48,6 +48,7 @@ def verify(geneve):
verify_bridge_delete(geneve)
return None
+ verify_mtu_ipv6(geneve)
verify_address(geneve)
if 'remote' not in geneve:
@@ -62,7 +63,6 @@ def verify(geneve):
def generate(geneve):
return None
-
def apply(geneve):
# Check if GENEVE interface already exists
if geneve['ifname'] in interfaces():
@@ -72,10 +72,11 @@ def apply(geneve):
g.remove()
if 'deleted' not in geneve:
- # GENEVE interface needs to be created on-block
- # instead of passing a ton of arguments, I just use a dict
- # that is managed by vyos.ifconfig
- conf = deepcopy(GeneveIf.get_config())
+ # This is a special type of interface which needs additional parameters
+ # when created using iproute2. Instead of passing a ton of arguments,
+ # use a dictionary provided by the interface class which holds all the
+ # options necessary.
+ conf = GeneveIf.get_config()
# Assign GENEVE instance configuration parameters to config dict
conf['vni'] = geneve['vni']
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 144cee5fe..1118143e4 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -17,7 +17,6 @@
import os
from sys import exit
-from copy import deepcopy
from netifaces import interfaces
from vyos.config import Config
@@ -25,6 +24,7 @@ from vyos.configdict import get_interface_dict
from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import L2TPv3If
from vyos.util import check_kmod
from vyos.validate import is_addr_assigned
@@ -81,6 +81,7 @@ def verify(l2tpv3):
raise ConfigError('L2TPv3 local-ip address '
'"{local_ip}" is not configured!'.format(**l2tpv3))
+ verify_mtu_ipv6(l2tpv3)
verify_address(l2tpv3)
return None
@@ -88,10 +89,11 @@ def generate(l2tpv3):
return None
def apply(l2tpv3):
- # L2TPv3 interface needs to be created/deleted on-block, instead of
- # passing a ton of arguments, I just use a dict that is managed by
- # vyos.ifconfig
- conf = deepcopy(L2TPv3If.get_config())
+ # This is a special type of interface which needs additional parameters
+ # when created using iproute2. Instead of passing a ton of arguments,
+ # use a dictionary provided by the interface class which holds all the
+ # options necessary.
+ conf = L2TPv3If.get_config()
# Check if L2TPv3 interface already exists
if l2tpv3['ifname'] in interfaces():
diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py
index 2866ccc0a..0a20a121b 100755
--- a/src/conf_mode/interfaces-macsec.py
+++ b/src/conf_mode/interfaces-macsec.py
@@ -16,17 +16,18 @@
import os
-from copy import deepcopy
from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.ifconfig import MACsecIf
+from vyos.ifconfig import Interface
from vyos.template import render
from vyos.util import call
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_source_interface
from vyos import ConfigError
from vyos import airbag
@@ -47,6 +48,14 @@ def get_config(config=None):
base = ['interfaces', 'macsec']
macsec = get_interface_dict(conf, base)
+ # MACsec is "special" the default MTU is 1460 - update accordingly
+ # as the config_level is already st in get_interface_dict() - we can use []
+ tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
+ if 'mtu' not in tmp:
+ # base MTU for MACsec is 1468 bytes, but we leave room for 802.1ad and
+ # 802.1q VLAN tags, thus the limit is 1460 bytes.
+ macsec['mtu'] = '1460'
+
# Check if interface has been removed
if 'deleted' in macsec:
source_interface = conf.return_effective_value(
@@ -63,6 +72,7 @@ def verify(macsec):
verify_source_interface(macsec)
verify_vrf(macsec)
+ verify_mtu_ipv6(macsec)
verify_address(macsec)
if not (('security' in macsec) and
@@ -80,6 +90,15 @@ def verify(macsec):
raise ConfigError('Missing mandatory MACsec security '
'keys as encryption is enabled!')
+ if 'source_interface' in macsec:
+ # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad
+ # and 802.1q) - we need to check the underlaying MTU if our configured
+ # MTU is at least 40 bytes less then the MTU of our physical interface.
+ lower_mtu = Interface(macsec['source_interface']).get_mtu()
+ if lower_mtu < (int(macsec['mtu']) + 40):
+ raise ConfigError('MACsec overhead does not fit into underlaying device MTU,\n' \
+ f'{underlay_mtu} bytes is too small!')
+
return None
@@ -102,12 +121,11 @@ def apply(macsec):
os.unlink(wpa_suppl_conf.format(**macsec))
else:
- # MACsec interfaces require a configuration when they are added using
- # iproute2. This static method will provide the configuration
- # dictionary used by this class.
-
- # XXX: subject of removal after completing T2653
- conf = deepcopy(MACsecIf.get_config())
+ # This is a special type of interface which needs additional parameters
+ # when created using iproute2. Instead of passing a ton of arguments,
+ # use a dictionary provided by the interface class which holds all the
+ # options necessary.
+ conf = MACsecIf.get_config()
conf['source_interface'] = macsec['source_interface']
conf['security_cipher'] = macsec['security']['cipher']
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 958b305dd..518dbdc0e 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -26,10 +26,11 @@ from shutil import rmtree
from vyos.config import Config
from vyos.configdict import list_diff
+from vyos.configdict import is_member
from vyos.ifconfig import VTunIf
from vyos.template import render
from vyos.util import call, chown, chmod_600, chmod_755
-from vyos.validate import is_addr_assigned, is_member, is_ipv4
+from vyos.validate import is_addr_assigned, is_ipv4
from vyos import ConfigError
from vyos import airbag
@@ -256,7 +257,10 @@ def get_config(config=None):
if conf.exists('encryption ncp-ciphers'):
_ncp_ciphers = []
for enc in conf.return_values('encryption ncp-ciphers'):
- if enc == 'des':
+ if enc == 'none':
+ _ncp_ciphers.append('none')
+ _ncp_ciphers.append('NONE')
+ elif enc == 'des':
_ncp_ciphers.append('des-cbc')
_ncp_ciphers.append('DES-CBC')
elif enc == '3des':
@@ -943,6 +947,9 @@ def verify(openvpn):
else:
print('Diffie-Hellman prime file is unspecified, assuming ECDH')
+ if openvpn['encryption'] == 'none':
+ print('Warning: "encryption none" was specified. NO encryption will be performed and tunnelled data WILL be transmitted in clear text over the network!')
+
#
# Auth user/pass
#
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index 1b4b9e4ee..ee3b142c8 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -24,6 +24,7 @@ from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.template import render
from vyos.util import call
from vyos import ConfigError
@@ -57,6 +58,7 @@ def verify(pppoe):
verify_source_interface(pppoe)
verify_vrf(pppoe)
+ verify_mtu_ipv6(pppoe)
if {'connect_on_demand', 'vrf'} <= set(pppoe):
raise ConfigError('On-demand dialing and VRF can not be used at the same time')
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index 59edca1cc..ddbef56ac 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -14,9 +14,6 @@
# 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 copy import deepcopy
from sys import exit
from vyos.config import Config
@@ -28,7 +25,6 @@ from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_vlan_config
from vyos.ifconfig import MACVLANIf
-from vyos.validate import is_member
from vyos import ConfigError
from vyos import airbag
@@ -47,19 +43,7 @@ def get_config(config=None):
peth = get_interface_dict(conf, base)
mode = leaf_node_changed(conf, ['mode'])
- if mode:
- peth.update({'mode_old' : mode})
-
- # Check if source-interface is member of a bridge device
- if 'source_interface' in peth:
- bridge = is_member(conf, peth['source_interface'], 'bridge')
- if bridge:
- peth.update({'source_interface_is_bridge_member' : bridge})
-
- # Check if we are a member of a bond device
- bond = is_member(conf, peth['source_interface'], 'bonding')
- if bond:
- peth.update({'source_interface_is_bond_member' : bond})
+ if mode: peth.update({'mode_old' : mode})
return peth
@@ -72,16 +56,6 @@ def verify(peth):
verify_vrf(peth)
verify_address(peth)
- if 'source_interface_is_bridge_member' in peth:
- raise ConfigError(
- 'Source interface "{source_interface}" can not be used as it is already a '
- 'member of bridge "{source_interface_is_bridge_member}"!'.format(**peth))
-
- if 'source_interface_is_bond_member' in peth:
- raise ConfigError(
- 'Source interface "{source_interface}" can not be used as it is already a '
- 'member of bond "{source_interface_is_bond_member}"!'.format(**peth))
-
# use common function to verify VLAN configuration
verify_vlan_config(peth)
return None
@@ -101,9 +75,11 @@ def apply(peth):
if 'mode_old' in peth:
MACVLANIf(peth['ifname']).remove()
- # MACVLAN interface needs to be created on-block instead of passing a ton
- # of arguments, I just use a dict that is managed by vyos.ifconfig
- conf = deepcopy(MACVLANIf.get_config())
+ # This is a special type of interface which needs additional parameters
+ # when created using iproute2. Instead of passing a ton of arguments,
+ # use a dictionary provided by the interface class which holds all the
+ # options necessary.
+ conf = MACVLANIf.get_config()
# Assign MACVLAN instance configuration parameters to config dict
conf['source_interface'] = peth['source_interface']
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 11d8d6edc..f1d885b15 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -22,10 +22,11 @@ from copy import deepcopy
from netifaces import interfaces
from vyos.config import Config
+from vyos.configdict import is_member
from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf
from vyos.ifconfig.afi import IP4, IP6
from vyos.configdict import list_diff
-from vyos.validate import is_ipv4, is_ipv6, is_member
+from vyos.validate import is_ipv4, is_ipv6
from vyos import ConfigError
from vyos.dicts import FixedDict
@@ -170,8 +171,8 @@ class ConfigurationState(object):
"""
>>> conf.get_values('addresses', 'address')
will place a list of the new IP present in 'interface dummy dum1 address'
- into the dictionnary entry "-add" (here 'addresses-add') using
- Config.return_values and will add the the one which were removed in into
+ into the dictionnary entry "-add" (here 'addresses-add') using
+ Config.return_values and will add the the one which were removed in into
the entry "-del" (here addresses-del')
"""
add_name = f'{name}-add'
@@ -263,7 +264,7 @@ class ConfigurationState(object):
d = d[lpath[-1]]
# XXX: it should have provided me the content and not the key
self._conf.set_level(l)
- return d
+ return d
def to_api(self):
"""
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index bea3aa25b..002f40aef 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -17,13 +17,13 @@
import os
from sys import exit
-from copy import deepcopy
from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_source_interface
from vyos.ifconfig import VXLANIf, Interface
from vyos import ConfigError
@@ -73,11 +73,12 @@ def verify(vxlan):
if 'source_interface' in vxlan:
# VXLAN adds a 50 byte overhead - we need to check the underlaying MTU
# if our configured MTU is at least 50 bytes less
- underlay_mtu = int(Interface(vxlan['source_interface']).get_mtu())
- if underlay_mtu < (int(vxlan['mtu']) + 50):
+ lower_mtu = Interface(vxlan['source_interface']).get_mtu()
+ if lower_mtu < (int(vxlan['mtu']) + 50):
raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \
f'MTU is to small ({underlay_mtu} bytes)')
+ verify_mtu_ipv6(vxlan)
verify_address(vxlan)
return None
@@ -95,10 +96,11 @@ def apply(vxlan):
v.remove()
if 'deleted' not in vxlan:
- # VXLAN interface needs to be created on-block
- # instead of passing a ton of arguments, I just use a dict
- # that is managed by vyos.ifconfig
- conf = deepcopy(VXLANIf.get_config())
+ # This is a special type of interface which needs additional parameters
+ # when created using iproute2. Instead of passing a ton of arguments,
+ # use a dictionary provided by the interface class which holds all the
+ # options necessary.
+ conf = VXLANIf.get_config()
# Assign VXLAN instance configuration parameters to config dict
for tmp in ['vni', 'group', 'source_address', 'source_interface', 'remote', 'port']:
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index e7c22da1a..d5800264f 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -27,6 +27,7 @@ from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import WireGuardIf
from vyos.util import check_kmod
from vyos import ConfigError
@@ -71,6 +72,7 @@ def verify(wireguard):
verify_bridge_delete(wireguard)
return None
+ verify_mtu_ipv6(wireguard)
verify_address(wireguard)
verify_vrf(wireguard)
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index c6c843e7b..f8520aecf 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -18,7 +18,6 @@ import os
from sys import exit
from re import findall
-from copy import deepcopy
from netaddr import EUI, mac_unix_expanded
from vyos.config import Config
@@ -233,13 +232,15 @@ def apply(wifi):
if 'deleted' in wifi:
WiFiIf(interface).remove()
else:
- # WiFi interface needs to be created on-block (e.g. mode or physical
- # interface) instead of passing a ton of arguments, I just use a dict
- # that is managed by vyos.ifconfig
- conf = deepcopy(WiFiIf.get_config())
+ # This is a special type of interface which needs additional parameters
+ # when created using iproute2. Instead of passing a ton of arguments,
+ # use a dictionary provided by the interface class which holds all the
+ # options necessary.
+ conf = WiFiIf.get_config()
# Assign WiFi instance configuration parameters to config dict
conf['phy'] = wifi['physical_device']
+ conf['wds'] = 'on' if 'wds' in wifi else 'off'
# Finally create the new interface
w = WiFiIf(interface, **conf)
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index e9806ef47..117bf0274 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -22,7 +22,7 @@ from vyos.config import Config
from vyos.configverify import verify_vrf
from vyos.snmpv3_hashgen import plaintext_to_md5, plaintext_to_sha1, random
from vyos.template import render
-from vyos.util import call
+from vyos.util import call, chmod_755
from vyos.validate import is_ipv4, is_addr_assigned
from vyos.version import get_version_data
from vyos import ConfigError, airbag
diff --git a/src/migration-scripts/interfaces/12-to-13 b/src/migration-scripts/interfaces/12-to-13
new file mode 100755
index 000000000..f866ca9a6
--- /dev/null
+++ b/src/migration-scripts/interfaces/12-to-13
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# - T2903: Change vif-s ethertype from numeric number to literal
+# - 0x88a8 -> 802.1ad
+# - 0x8100 -> 802.1q
+# - T2905: Change WWAN "ondemand" node to "connect-on-demand" to have identical
+# CLI nodes for both types of dialer interfaces
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+
+ #
+ # T2903
+ #
+ for type in config.list_nodes(['interfaces']):
+ for interface in config.list_nodes(['interfaces', type]):
+ if not config.exists(['interfaces', type, interface, 'vif-s']):
+ continue
+
+ for vif_s in config.list_nodes(['interfaces', type, interface, 'vif-s']):
+ base_path = ['interfaces', type, interface, 'vif-s', vif_s]
+ if config.exists(base_path + ['ethertype']):
+ protocol = '802.1ad'
+ tmp = config.return_value(base_path + ['ethertype'])
+ if tmp == '0x8100':
+ protocol = '802.1q'
+
+ config.set(base_path + ['protocol'], value=protocol)
+ config.delete(base_path + ['ethertype'])
+
+ #
+ # T2905
+ #
+ wwan_base = ['interfaces', 'wirelessmodem']
+ if config.exists(wwan_base):
+ for interface in config.list_nodes(wwan_base):
+ if config.exists(wwan_base + [interface, 'ondemand']):
+ config.rename(wwan_base + [interface, 'ondemand'], 'connect-on-demand')
+
+ 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/10-to-11 b/src/migration-scripts/system/10-to-11
index 1a0233c7d..3c49f0d95 100755
--- a/src/migration-scripts/system/10-to-11
+++ b/src/migration-scripts/system/10-to-11
@@ -1,9 +1,7 @@
#!/usr/bin/env python3
-# Unclutter RADIUS configuration
-#
-# Move radius-server top level tag nodes to a regular node which allows us
-# to specify additional general features for the RADIUS client.
+# Operator accounts have been deprecated due to a security issue. Those accounts
+# will be converted to regular admin accounts.
import sys
from vyos.configtree import ConfigTree
@@ -18,54 +16,21 @@ with open(file_name, 'r') as f:
config_file = f.read()
config = ConfigTree(config_file)
-cfg_base = ['system', 'login']
-if not (config.exists(cfg_base + ['radius-server']) or config.exists(cfg_base + ['radius-source-address'])):
- # Nothing to do
+base_level = ['system', 'login', 'user']
+
+if not config.exists(base_level):
+ # Nothing to do, which shouldn't happen anyway
+ # only if you wipe the config and reboot.
sys.exit(0)
else:
- #
- # Migrate "system login radius-source-address" to "system login radius"
- #
- if config.exists(cfg_base + ['radius-source-address']):
- address = config.return_value(cfg_base + ['radius-source-address'])
- # delete old configuration node
- config.delete(cfg_base + ['radius-source-address'])
- # write new configuration node
- config.set(cfg_base + ['radius', 'source-address'], value=address)
-
- #
- # Migrate "system login radius-server" tag node to new
- # "system login radius server" tag node and also rename the "secret" node to "key"
- #
- for server in config.list_nodes(cfg_base + ['radius-server']):
- base_server = cfg_base + ['radius-server', server]
- # "key" node is mandatory
- key = config.return_value(base_server + ['secret'])
- config.set(cfg_base + ['radius', 'server', server, 'key'], value=key)
-
- # "port" is optional
- if config.exists(base_server + ['port']):
- port = config.return_value(base_server + ['port'])
- config.set(cfg_base + ['radius', 'server', server, 'port'], value=port)
-
- # "timeout is optional"
- if config.exists(base_server + ['timeout']):
- timeout = config.return_value(base_server + ['timeout'])
- config.set(cfg_base + ['radius', 'server', server, 'timeout'], value=timeout)
-
- # format as tag node
- config.set_tag(cfg_base + ['radius', 'server'])
-
- # delete old configuration node
- config.delete(base_server)
-
- # delete top level tag node
- if config.exists(cfg_base + ['radius-server']):
- config.delete(cfg_base + ['radius-server'])
+ for user in config.list_nodes(base_level):
+ if config.exists(base_level + [user, 'level']):
+ if config.return_value(base_level + [user, 'level']) == 'operator':
+ config.set(base_level + [user, 'level'], value="admin", replace=True)
try:
- with open(file_name, 'w') as f:
- f.write(config.to_string())
+ open(file_name,'w').write(config.to_string())
+
except OSError as e:
print("Failed to save the modified config: {}".format(e))
sys.exit(1)
diff --git a/src/migration-scripts/system/11-to-12 b/src/migration-scripts/system/11-to-12
index 0c92a0746..1a0233c7d 100755
--- a/src/migration-scripts/system/11-to-12
+++ b/src/migration-scripts/system/11-to-12
@@ -1,47 +1,71 @@
#!/usr/bin/env python3
-# converts 'set system syslog host <address>:<port>'
-# to 'set system syslog host <address> port <port>'
+# Unclutter RADIUS configuration
+#
+# Move radius-server top level tag nodes to a regular node which allows us
+# to specify additional general features for the RADIUS client.
import sys
-import re
-
from vyos.configtree import ConfigTree
if (len(sys.argv) < 1):
- print("Must specify file name!")
- sys.exit(1)
+ print("Must specify file name!")
+ sys.exit(1)
file_name = sys.argv[1]
with open(file_name, 'r') as f:
- config_file = f.read()
+ config_file = f.read()
config = ConfigTree(config_file)
-cbase = ['system', 'syslog', 'host']
-
-if not config.exists(cbase):
+cfg_base = ['system', 'login']
+if not (config.exists(cfg_base + ['radius-server']) or config.exists(cfg_base + ['radius-source-address'])):
+ # Nothing to do
sys.exit(0)
+else:
+ #
+ # Migrate "system login radius-source-address" to "system login radius"
+ #
+ if config.exists(cfg_base + ['radius-source-address']):
+ address = config.return_value(cfg_base + ['radius-source-address'])
+ # delete old configuration node
+ config.delete(cfg_base + ['radius-source-address'])
+ # write new configuration node
+ config.set(cfg_base + ['radius', 'source-address'], value=address)
+
+ #
+ # Migrate "system login radius-server" tag node to new
+ # "system login radius server" tag node and also rename the "secret" node to "key"
+ #
+ for server in config.list_nodes(cfg_base + ['radius-server']):
+ base_server = cfg_base + ['radius-server', server]
+ # "key" node is mandatory
+ key = config.return_value(base_server + ['secret'])
+ config.set(cfg_base + ['radius', 'server', server, 'key'], value=key)
+
+ # "port" is optional
+ if config.exists(base_server + ['port']):
+ port = config.return_value(base_server + ['port'])
+ config.set(cfg_base + ['radius', 'server', server, 'port'], value=port)
+
+ # "timeout is optional"
+ if config.exists(base_server + ['timeout']):
+ timeout = config.return_value(base_server + ['timeout'])
+ config.set(cfg_base + ['radius', 'server', server, 'timeout'], value=timeout)
+
+ # format as tag node
+ config.set_tag(cfg_base + ['radius', 'server'])
+
+ # delete old configuration node
+ config.delete(base_server)
-for host in config.list_nodes(cbase):
- if re.search(':[0-9]{1,5}$',host):
- h = re.search('^[a-zA-Z\-0-9\.]+', host).group(0)
- p = re.sub(':', '', re.search(':[0-9]+$', host).group(0))
- config.set(cbase + [h])
- config.set(cbase + [h, 'port'], value=p)
- for fac in config.list_nodes(cbase + [host, 'facility']):
- config.set(cbase + [h, 'facility', fac])
- config.set_tag(cbase + [h, 'facility'])
- if config.exists(cbase + [host, 'facility', fac, 'protocol']):
- proto = config.return_value(cbase + [host, 'facility', fac, 'protocol'])
- config.set(cbase + [h, 'facility', fac, 'protocol'], value=proto)
- if config.exists(cbase + [host, 'facility', fac, 'level']):
- lvl = config.return_value(cbase + [host, 'facility', fac, 'level'])
- config.set(cbase + [h, 'facility', fac, 'level'], value=lvl)
- config.delete(cbase + [host])
+ # delete top level tag node
+ if config.exists(cfg_base + ['radius-server']):
+ config.delete(cfg_base + ['radius-server'])
try:
- open(file_name,'w').write(config.to_string())
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
except OSError as e:
print("Failed to save the modified config: {}".format(e))
sys.exit(1)
diff --git a/src/migration-scripts/system/12-to-13 b/src/migration-scripts/system/12-to-13
index 5b068f4fc..36311a19d 100755
--- a/src/migration-scripts/system/12-to-13
+++ b/src/migration-scripts/system/12-to-13
@@ -1,70 +1,47 @@
#!/usr/bin/env python3
-# Fixup non existent time-zones. Some systems have time-zone set to: Los*
-# (Los_Angeles), Den* (Denver), New* (New_York) ... but those are no real IANA
-# assigned time zones. In the past they have been silently remapped.
-#
-# Time to clean it up!
-#
-# Migrate all configured timezones to real IANA assigned timezones!
+# converts 'set system syslog host <address>:<port>'
+# to 'set system syslog host <address> port <port>'
-import re
import sys
+import re
from vyos.configtree import ConfigTree
-from vyos.util import cmd
-
if (len(sys.argv) < 1):
- print("Must specify file name!")
- sys.exit(1)
+ print("Must specify file name!")
+ sys.exit(1)
file_name = sys.argv[1]
with open(file_name, 'r') as f:
- config_file = f.read()
+ config_file = f.read()
config = ConfigTree(config_file)
-tz_base = ['system', 'time-zone']
-if not config.exists(tz_base):
- # Nothing to do
- sys.exit(0)
-else:
- tz = config.return_value(tz_base)
+cbase = ['system', 'syslog', 'host']
- # 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/::')
- except OSError:
- tz_datas = ''
- tz_data = tz_datas.split('\n')
-
- if re.match(r'[Ll][Oo][Ss].+', tz):
- tz = 'America/Los_Angeles'
- elif re.match(r'[Dd][Ee][Nn].+', tz):
- tz = 'America/Denver'
- elif re.match(r'[Hh][Oo][Nn][Oo].+', tz):
- tz = 'Pacific/Honolulu'
- elif re.match(r'[Nn][Ee][Ww].+', tz):
- tz = 'America/New_York'
- elif re.match(r'[Cc][Hh][Ii][Cc]*.+', tz):
- tz = 'America/Chicago'
- elif re.match(r'[Aa][Nn][Cc].+', tz):
- tz = 'America/Anchorage'
- elif re.match(r'[Pp][Hh][Oo].+', tz):
- tz = 'America/Phoenix'
- elif re.match(r'GMT(.+)?', tz):
- tz = 'Etc/' + tz
- elif tz not in tz_data:
- # assign default UTC timezone
- tz = 'UTC'
-
- # replace timezone data is required
- config.set(tz_base, value=tz)
+if not config.exists(cbase):
+ sys.exit(0)
- try:
- with open(file_name, 'w') as f:
- f.write(config.to_string())
- except OSError as e:
- print("Failed to save the modified config: {}".format(e))
- sys.exit(1)
+for host in config.list_nodes(cbase):
+ if re.search(':[0-9]{1,5}$',host):
+ h = re.search('^[a-zA-Z\-0-9\.]+', host).group(0)
+ p = re.sub(':', '', re.search(':[0-9]+$', host).group(0))
+ config.set(cbase + [h])
+ config.set(cbase + [h, 'port'], value=p)
+ for fac in config.list_nodes(cbase + [host, 'facility']):
+ config.set(cbase + [h, 'facility', fac])
+ config.set_tag(cbase + [h, 'facility'])
+ if config.exists(cbase + [host, 'facility', fac, 'protocol']):
+ proto = config.return_value(cbase + [host, 'facility', fac, 'protocol'])
+ config.set(cbase + [h, 'facility', fac, 'protocol'], value=proto)
+ if config.exists(cbase + [host, 'facility', fac, 'level']):
+ lvl = config.return_value(cbase + [host, 'facility', fac, 'level'])
+ config.set(cbase + [h, 'facility', fac, 'level'], value=lvl)
+ config.delete(cbase + [host])
+
+try:
+ open(file_name,'w').write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/13-to-14 b/src/migration-scripts/system/13-to-14
index c055dad1f..5b068f4fc 100755
--- a/src/migration-scripts/system/13-to-14
+++ b/src/migration-scripts/system/13-to-14
@@ -1,15 +1,19 @@
#!/usr/bin/env python3
+
+# Fixup non existent time-zones. Some systems have time-zone set to: Los*
+# (Los_Angeles), Den* (Denver), New* (New_York) ... but those are no real IANA
+# assigned time zones. In the past they have been silently remapped.
+#
+# Time to clean it up!
#
-# Delete 'system ipv6 blacklist' option as the IPv6 module can no longer be
-# blacklisted as it is required by e.g. WireGuard and thus will always be
-# loaded.
+# Migrate all configured timezones to real IANA assigned timezones!
-import os
+import re
import sys
-ipv6_blacklist_file = '/etc/modprobe.d/vyatta_blacklist_ipv6.conf'
-
from vyos.configtree import ConfigTree
+from vyos.util import cmd
+
if (len(sys.argv) < 1):
print("Must specify file name!")
@@ -21,16 +25,42 @@ with open(file_name, 'r') as f:
config_file = f.read()
config = ConfigTree(config_file)
-ip_base = ['system', 'ipv6']
-if not config.exists(ip_base):
+tz_base = ['system', 'time-zone']
+if not config.exists(tz_base):
# Nothing to do
sys.exit(0)
else:
- # delete 'system ipv6 blacklist' node
- if config.exists(ip_base + ['blacklist']):
- config.delete(ip_base + ['blacklist'])
- if os.path.isfile(ipv6_blacklist_file):
- os.unlink(ipv6_blacklist_file)
+ tz = config.return_value(tz_base)
+
+ # 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/::')
+ except OSError:
+ tz_datas = ''
+ tz_data = tz_datas.split('\n')
+
+ if re.match(r'[Ll][Oo][Ss].+', tz):
+ tz = 'America/Los_Angeles'
+ elif re.match(r'[Dd][Ee][Nn].+', tz):
+ tz = 'America/Denver'
+ elif re.match(r'[Hh][Oo][Nn][Oo].+', tz):
+ tz = 'Pacific/Honolulu'
+ elif re.match(r'[Nn][Ee][Ww].+', tz):
+ tz = 'America/New_York'
+ elif re.match(r'[Cc][Hh][Ii][Cc]*.+', tz):
+ tz = 'America/Chicago'
+ elif re.match(r'[Aa][Nn][Cc].+', tz):
+ tz = 'America/Anchorage'
+ elif re.match(r'[Pp][Hh][Oo].+', tz):
+ tz = 'America/Phoenix'
+ elif re.match(r'GMT(.+)?', tz):
+ tz = 'Etc/' + tz
+ elif tz not in tz_data:
+ # assign default UTC timezone
+ tz = 'UTC'
+
+ # replace timezone data is required
+ config.set(tz_base, value=tz)
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/system/14-to-15 b/src/migration-scripts/system/14-to-15
index 2491e3d0d..c055dad1f 100755
--- a/src/migration-scripts/system/14-to-15
+++ b/src/migration-scripts/system/14-to-15
@@ -1,10 +1,14 @@
#!/usr/bin/env python3
#
-# Make 'system options reboot-on-panic' valueless
+# Delete 'system ipv6 blacklist' option as the IPv6 module can no longer be
+# blacklisted as it is required by e.g. WireGuard and thus will always be
+# loaded.
import os
import sys
+ipv6_blacklist_file = '/etc/modprobe.d/vyatta_blacklist_ipv6.conf'
+
from vyos.configtree import ConfigTree
if (len(sys.argv) < 1):
@@ -17,17 +21,16 @@ with open(file_name, 'r') as f:
config_file = f.read()
config = ConfigTree(config_file)
-base = ['system', 'options']
-if not config.exists(base):
+ip_base = ['system', 'ipv6']
+if not config.exists(ip_base):
# Nothing to do
sys.exit(0)
else:
- if config.exists(base + ['reboot-on-panic']):
- reboot = config.return_value(base + ['reboot-on-panic'])
- config.delete(base + ['reboot-on-panic'])
- # create new valueless node if action was true
- if reboot == "true":
- config.set(base + ['reboot-on-panic'])
+ # delete 'system ipv6 blacklist' node
+ if config.exists(ip_base + ['blacklist']):
+ config.delete(ip_base + ['blacklist'])
+ if os.path.isfile(ipv6_blacklist_file):
+ os.unlink(ipv6_blacklist_file)
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/system/15-to-16 b/src/migration-scripts/system/15-to-16
index e70893d55..2491e3d0d 100755
--- a/src/migration-scripts/system/15-to-16
+++ b/src/migration-scripts/system/15-to-16
@@ -1,24 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-# * remove "system login user <user> group" node, Why should be add a user to a
-# 3rd party group when the system is fully managed by CLI?
-# * remove "system login user <user> level" node
-# This is the only privilege level left and also the default, what is the
-# sense in keeping this orphaned node?
+# Make 'system options reboot-on-panic' valueless
import os
import sys
@@ -35,17 +17,17 @@ with open(file_name, 'r') as f:
config_file = f.read()
config = ConfigTree(config_file)
-base = ['system', 'login', 'user']
+base = ['system', 'options']
if not config.exists(base):
# Nothing to do
sys.exit(0)
else:
- for user in config.list_nodes(base):
- if config.exists(base + [user, 'group']):
- config.delete(base + [user, 'group'])
-
- if config.exists(base + [user, 'level']):
- config.delete(base + [user, 'level'])
+ if config.exists(base + ['reboot-on-panic']):
+ reboot = config.return_value(base + ['reboot-on-panic'])
+ config.delete(base + ['reboot-on-panic'])
+ # create new valueless node if action was true
+ if reboot == "true":
+ config.set(base + ['reboot-on-panic'])
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/system/16-to-17 b/src/migration-scripts/system/16-to-17
index 8f762c0e2..e70893d55 100755
--- a/src/migration-scripts/system/16-to-17
+++ b/src/migration-scripts/system/16-to-17
@@ -14,8 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# remove "system console netconsole"
-# remove "system console device <device> modem"
+# * remove "system login user <user> group" node, Why should be add a user to a
+# 3rd party group when the system is fully managed by CLI?
+# * remove "system login user <user> level" node
+# This is the only privilege level left and also the default, what is the
+# sense in keeping this orphaned node?
import os
import sys
@@ -32,41 +35,17 @@ with open(file_name, 'r') as f:
config_file = f.read()
config = ConfigTree(config_file)
-base = ['system', 'console']
+base = ['system', 'login', 'user']
if not config.exists(base):
# Nothing to do
sys.exit(0)
else:
- # remove "system console netconsole" (T2561)
- if config.exists(base + ['netconsole']):
- config.delete(base + ['netconsole'])
+ for user in config.list_nodes(base):
+ if config.exists(base + [user, 'group']):
+ config.delete(base + [user, 'group'])
- if config.exists(base + ['device']):
- for device in config.list_nodes(base + ['device']):
- dev_path = base + ['device', device]
- # remove "system console device <device> modem" (T2570)
- if config.exists(dev_path + ['modem']):
- config.delete(dev_path + ['modem'])
-
- # Only continue on USB based serial consoles
- if not 'ttyUSB' in device:
- continue
-
- # A serial console has been configured but it does no longer
- # exist on the system - cleanup
- if not os.path.exists(f'/dev/{device}'):
- config.delete(dev_path)
- continue
-
- # migrate from ttyUSB device to new device in /dev/serial/by-bus
- for root, dirs, files in os.walk('/dev/serial/by-bus'):
- for usb_device in files:
- device_file = os.path.realpath(os.path.join(root, usb_device))
- # migrate to new USB device names (T2529)
- if os.path.basename(device_file) == device:
- config.copy(dev_path, base + ['device', usb_device])
- # Delete old USB node from config
- config.delete(dev_path)
+ if config.exists(base + [user, 'level']):
+ config.delete(base + [user, 'level'])
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/system/17-to-18 b/src/migration-scripts/system/17-to-18
index dd2abce00..8f762c0e2 100755
--- a/src/migration-scripts/system/17-to-18
+++ b/src/migration-scripts/system/17-to-18
@@ -13,93 +13,64 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# migrate disable-dhcp-nameservers (boolean) to name-servers-dhcp <interface>
-# if disable-dhcp-nameservers is set, just remove it
-# else retrieve all interface names that have configured dhcp(v6) address and
-# add them to the new name-servers-dhcp node
+# remove "system console netconsole"
+# remove "system console device <device> modem"
+
+import os
+import sys
-from sys import argv, exit
-from vyos.ifconfig import Interface
from vyos.configtree import ConfigTree
-if (len(argv) < 1):
+if (len(sys.argv) < 1):
print("Must specify file name!")
- exit(1)
+ sys.exit(1)
-file_name = argv[1]
+file_name = sys.argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
config = ConfigTree(config_file)
-
-base = ['system']
+base = ['system', 'console']
if not config.exists(base):
# Nothing to do
- exit(0)
-
-if config.exists(base + ['disable-dhcp-nameservers']):
- config.delete(base + ['disable-dhcp-nameservers'])
+ sys.exit(0)
else:
- dhcp_interfaces = []
-
- # go through all interfaces searching for 'address dhcp(v6)?'
- for sect in Interface.sections():
- sect_base = ['interfaces', sect]
-
- if not config.exists(sect_base):
- continue
-
- for intf in config.list_nodes(sect_base):
- intf_base = sect_base + [intf]
-
- # try without vlans
- if config.exists(intf_base + ['address']):
- for addr in config.return_values(intf_base + ['address']):
- if addr in ['dhcp', 'dhcpv6']:
- dhcp_interfaces.append(intf)
-
- # try vif
- if config.exists(intf_base + ['vif']):
- for vif in config.list_nodes(intf_base + ['vif']):
- vif_base = intf_base + ['vif', vif]
- if config.exists(vif_base + ['address']):
- for addr in config.return_values(vif_base + ['address']):
- if addr in ['dhcp', 'dhcpv6']:
- dhcp_interfaces.append(f'{intf}.{vif}')
-
- # try vif-s
- if config.exists(intf_base + ['vif-s']):
- for vif_s in config.list_nodes(intf_base + ['vif-s']):
- vif_s_base = intf_base + ['vif-s', vif_s]
- if config.exists(vif_s_base + ['address']):
- for addr in config.return_values(vif_s_base + ['address']):
- if addr in ['dhcp', 'dhcpv6']:
- dhcp_interfaces.append(f'{intf}.{vif_s}')
-
- # try vif-c
- if config.exists(intf_base + ['vif-c', vif_c]):
- for vif_c in config.list_nodes(vif_s_base + ['vif-c', vif_c]):
- vif_c_base = vif_s_base + ['vif-c', vif_c]
- if config.exists(vif_c_base + ['address']):
- for addr in config.return_values(vif_c_base + ['address']):
- if addr in ['dhcp', 'dhcpv6']:
- dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}')
-
- # set new config nodes
- for intf in dhcp_interfaces:
- config.set(base + ['name-servers-dhcp'], value=intf, replace=False)
-
- # delete old node
- config.delete(base + ['disable-dhcp-nameservers'])
-
-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)
-
-exit(0)
+ # remove "system console netconsole" (T2561)
+ if config.exists(base + ['netconsole']):
+ config.delete(base + ['netconsole'])
+
+ if config.exists(base + ['device']):
+ for device in config.list_nodes(base + ['device']):
+ dev_path = base + ['device', device]
+ # remove "system console device <device> modem" (T2570)
+ if config.exists(dev_path + ['modem']):
+ config.delete(dev_path + ['modem'])
+
+ # Only continue on USB based serial consoles
+ if not 'ttyUSB' in device:
+ continue
+
+ # A serial console has been configured but it does no longer
+ # exist on the system - cleanup
+ if not os.path.exists(f'/dev/{device}'):
+ config.delete(dev_path)
+ continue
+
+ # migrate from ttyUSB device to new device in /dev/serial/by-bus
+ for root, dirs, files in os.walk('/dev/serial/by-bus'):
+ for usb_device in files:
+ device_file = os.path.realpath(os.path.join(root, usb_device))
+ # migrate to new USB device names (T2529)
+ if os.path.basename(device_file) == device:
+ config.copy(dev_path, base + ['device', usb_device])
+ # Delete old USB node from config
+ config.delete(dev_path)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/18-to-19 b/src/migration-scripts/system/18-to-19
new file mode 100755
index 000000000..dd2abce00
--- /dev/null
+++ b/src/migration-scripts/system/18-to-19
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# migrate disable-dhcp-nameservers (boolean) to name-servers-dhcp <interface>
+# if disable-dhcp-nameservers is set, just remove it
+# else retrieve all interface names that have configured dhcp(v6) address and
+# add them to the new name-servers-dhcp node
+
+from sys import argv, exit
+from vyos.ifconfig import Interface
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['system']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['disable-dhcp-nameservers']):
+ config.delete(base + ['disable-dhcp-nameservers'])
+else:
+ dhcp_interfaces = []
+
+ # go through all interfaces searching for 'address dhcp(v6)?'
+ for sect in Interface.sections():
+ sect_base = ['interfaces', sect]
+
+ if not config.exists(sect_base):
+ continue
+
+ for intf in config.list_nodes(sect_base):
+ intf_base = sect_base + [intf]
+
+ # try without vlans
+ if config.exists(intf_base + ['address']):
+ for addr in config.return_values(intf_base + ['address']):
+ if addr in ['dhcp', 'dhcpv6']:
+ dhcp_interfaces.append(intf)
+
+ # try vif
+ if config.exists(intf_base + ['vif']):
+ for vif in config.list_nodes(intf_base + ['vif']):
+ vif_base = intf_base + ['vif', vif]
+ if config.exists(vif_base + ['address']):
+ for addr in config.return_values(vif_base + ['address']):
+ if addr in ['dhcp', 'dhcpv6']:
+ dhcp_interfaces.append(f'{intf}.{vif}')
+
+ # try vif-s
+ if config.exists(intf_base + ['vif-s']):
+ for vif_s in config.list_nodes(intf_base + ['vif-s']):
+ vif_s_base = intf_base + ['vif-s', vif_s]
+ if config.exists(vif_s_base + ['address']):
+ for addr in config.return_values(vif_s_base + ['address']):
+ if addr in ['dhcp', 'dhcpv6']:
+ dhcp_interfaces.append(f'{intf}.{vif_s}')
+
+ # try vif-c
+ if config.exists(intf_base + ['vif-c', vif_c]):
+ for vif_c in config.list_nodes(vif_s_base + ['vif-c', vif_c]):
+ vif_c_base = vif_s_base + ['vif-c', vif_c]
+ if config.exists(vif_c_base + ['address']):
+ for addr in config.return_values(vif_c_base + ['address']):
+ if addr in ['dhcp', 'dhcpv6']:
+ dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}')
+
+ # set new config nodes
+ for intf in dhcp_interfaces:
+ config.set(base + ['name-servers-dhcp'], value=intf, replace=False)
+
+ # delete old node
+ config.delete(base + ['disable-dhcp-nameservers'])
+
+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)
+
+exit(0)
diff --git a/src/migration-scripts/system/9-to-10 b/src/migration-scripts/system/9-to-10
deleted file mode 100755
index 3c49f0d95..000000000
--- a/src/migration-scripts/system/9-to-10
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python3
-
-# Operator accounts have been deprecated due to a security issue. Those accounts
-# will be converted to regular admin accounts.
-
-import sys
-from vyos.configtree import ConfigTree
-
-if (len(sys.argv) < 1):
- print("Must specify file name!")
- sys.exit(1)
-
-file_name = sys.argv[1]
-
-with open(file_name, 'r') as f:
- config_file = f.read()
-
-config = ConfigTree(config_file)
-base_level = ['system', 'login', 'user']
-
-if not config.exists(base_level):
- # Nothing to do, which shouldn't happen anyway
- # only if you wipe the config and reboot.
- sys.exit(0)
-else:
- for user in config.list_nodes(base_level):
- if config.exists(base_level + [user, 'level']):
- if config.return_value(base_level + [user, 'level']) == 'operator':
- config.set(base_level + [user, 'level'], value="admin", replace=True)
-
- try:
- open(file_name,'w').write(config.to_string())
-
- except OSError as e:
- print("Failed to save the modified config: {}".format(e))
- sys.exit(1)
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index 0079f7e5c..59dbeda17 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -107,16 +107,17 @@
#
### forward_zones
## Additional zones added to pdns-recursor forward-zones-file.
-## If recursion-desired is true, '+' will be prepended to the zone line.
-## If addNTA is true, a NTA will be added via lua-config-file.
+## If recursion_desired is true, '+' will be prepended to the zone line.
+## If addnta is true, a NTA (Negative Trust Anchor) will be added via
+## lua-config-file.
#
# { 'type': 'forward_zones',
# 'op': 'add',
# 'data': {
# '<str zone>': {
-# 'nslist': ['<str nameserver>', ...],
-# 'addNTA': <bool>,
-# 'recursion-desired': <bool>
+# 'server': ['<str nameserver>', ...],
+# 'addnta': <bool>,
+# 'recursion_desired': <bool>
# }
# ...
# }
@@ -305,12 +306,12 @@ tag_regex_schema = op_type_schema.extend({
forward_zone_add_schema = op_type_schema.extend({
'data': {
str: {
- 'nslist': [str],
- 'addNTA': bool,
- 'recursion-desired': bool
+ 'server': [str],
+ 'addnta': Any({}, None),
+ 'recursion_desired': Any({}, None),
}
}
- }, required=True)
+ }, required=False)
hosts_add_schema = op_type_schema.extend({
'data': {
@@ -586,7 +587,7 @@ if __name__ == '__main__':
context = zmq.Context()
socket = context.socket(zmq.REP)
-
+
# Set the right permissions on the socket, then change it back
o_mask = os.umask(0o007)
socket.bind(SOCKET_PATH)
diff --git a/src/utils/vyos-hostsd-client b/src/utils/vyos-hostsd-client
index 48ebc83f7..d4d38315a 100755
--- a/src/utils/vyos-hostsd-client
+++ b/src/utils/vyos-hostsd-client
@@ -99,9 +99,9 @@ try:
raise ValueError("--nameservers is required for this operation")
client.add_forward_zones(
{ args.add_forward_zone: {
- 'nslist': args.nameservers,
- 'addNTA': args.addnta,
- 'recursion-desired': args.recursion_desired
+ 'server': args.nameservers,
+ 'addnta': args.addnta,
+ 'recursion_desired': args.recursion_desired
}
})
elif args.delete_forward_zones: