summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.tmpl73
-rw-r--r--data/templates/ocserv/ocserv_config.tmpl2
-rw-r--r--data/templates/ocserv/radius_conf.tmpl2
-rw-r--r--data/templates/ocserv/radius_servers.tmpl2
-rw-r--r--data/templates/wifi/hostapd.conf.tmpl148
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/dns-dynamic.xml.in1
-rw-r--r--interface-definitions/interfaces-bonding.xml.in13
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in31
-rw-r--r--interface-definitions/interfaces-wireless.xml.in2
-rw-r--r--interface-definitions/vpn_openconnect.xml.in (renamed from interface-definitions/vpn_anyconnect.xml.in)4
-rw-r--r--op-mode-definitions/force-mtu-host.xml34
-rw-r--r--op-mode-definitions/monitor-bandwidth-test.xml4
-rw-r--r--op-mode-definitions/openconnect.xml (renamed from op-mode-definitions/anyconnect.xml)8
-rw-r--r--op-mode-definitions/show-interfaces-bonding.xml6
-rw-r--r--op-mode-definitions/show-log.xml2
-rw-r--r--op-mode-definitions/show-version.xml2
-rw-r--r--python/vyos/configdict.py6
-rw-r--r--python/vyos/ifconfig/bond.py56
-rw-r--r--python/vyos/ifconfig/control.py6
-rw-r--r--python/vyos/ifconfig/dummy.py2
-rw-r--r--python/vyos/ifconfig/ethernet.py23
-rw-r--r--python/vyos/ifconfig/geneve.py2
-rw-r--r--python/vyos/ifconfig/input.py2
-rw-r--r--python/vyos/ifconfig/interface.py200
-rw-r--r--python/vyos/ifconfig/l2tpv3.py3
-rw-r--r--python/vyos/ifconfig/loopback.py2
-rw-r--r--python/vyos/ifconfig/macvlan.py4
-rw-r--r--python/vyos/ifconfig/operational.py5
-rw-r--r--python/vyos/ifconfig/tunnel.py1
-rw-r--r--python/vyos/ifconfig/vlan.py142
-rw-r--r--python/vyos/ifconfig/vrrp.py4
-rw-r--r--python/vyos/ifconfig/vti.py2
-rw-r--r--python/vyos/ifconfig/vtun.py2
-rw-r--r--python/vyos/ifconfig/vxlan.py1
-rw-r--r--python/vyos/ifconfig/wireguard.py2
-rw-r--r--python/vyos/ifconfig/wireless.py18
-rw-r--r--python/vyos/util.py1
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bonding.py35
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py87
-rwxr-xr-xsmoketest/scripts/cli/test_service_ssh.py2
-rwxr-xr-xsmoketest/scripts/cli/test_system_acceleration_qat.py47
-rwxr-xr-xsmoketest/scripts/cli/test_system_login.py70
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py (renamed from smoketest/scripts/cli/test_vpn_anyconnect.py)6
-rwxr-xr-xsrc/completion/list_interfaces.py16
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py1
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py217
-rwxr-xr-xsrc/conf_mode/intel_qat.py145
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py42
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py5
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py6
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py (renamed from src/conf_mode/vpn_anyconnect.py)14
-rwxr-xr-xsrc/op_mode/force_mtu_host.sh52
-rwxr-xr-xsrc/op_mode/monitor_bandwidth_test.sh30
-rwxr-xr-xsrc/op_mode/openconnect-control.py (renamed from src/op_mode/anyconnect-control.py)10
-rwxr-xr-xsrc/op_mode/show_version.py5
-rwxr-xr-xsrc/services/vyos-configd21
-rw-r--r--src/shim/.gitignore2
-rw-r--r--src/shim/vyshim.c10
59 files changed, 1010 insertions, 632 deletions
diff --git a/data/templates/dynamic-dns/ddclient.conf.tmpl b/data/templates/dynamic-dns/ddclient.conf.tmpl
index 9c7219230..6fbbb50c3 100644
--- a/data/templates/dynamic-dns/ddclient.conf.tmpl
+++ b/data/templates/dynamic-dns/ddclient.conf.tmpl
@@ -3,44 +3,47 @@ daemon=1m
syslog=yes
ssl=yes
-{% for interface in interfaces -%}
+{% for iface in interface %}
+# ddclient configuration for interface "{{ iface }}"
+{% if interface[iface].use_web is defined and interface[iface].use_web is not none %}
+{% set web_skip = ", web-skip='" + interface[iface].use_web.skip + "'" if interface[iface].use_web.skip is defined else '' %}
+use=web, web='{{ interface[iface].use_web.url }}'{{ web_skip }}
+{% else %}
+use=if, if={{ iface }}
+{% endif %}
-#
-# ddclient configuration for interface "{{ interface.interface }}":
-#
-{% if interface.web_url -%}
-use=web, web='{{ interface.web_url}}' {%- if interface.web_skip %}, web-skip='{{ interface.web_skip }}'{% endif %}
-{% else -%}
-use=if, if={{ interface.interface }}
-{% endif -%}
-
-{% for rfc in interface.rfc2136 -%}
-{% for record in rfc.record %}
-# RFC2136 dynamic DNS configuration for {{ record }}.{{ rfc.zone }}
-server={{ rfc.server }}
+{% if interface[iface].rfc2136 is defined and interface[iface].rfc2136 is not none %}
+{% for rfc2136, config in interface[iface].rfc2136.items() %}
+{% for dns_record in config.record if config.record is defined %}
+# RFC2136 dynamic DNS configuration for {{ rfc2136 }}, {{ config.zone }}, {{ dns_record }}
+server={{ config.server }}
protocol=nsupdate
-password={{ rfc.keyfile }}
-ttl={{ rfc.ttl }}
-zone={{ rfc.zone }}
-{{ record }}
-{% endfor -%}
-{% endfor -%}
+password={{ config.keyfile }}
+ttl={{ config.ttl }}
+zone={{ config.zone }}
+{{ dns_record }}
+
+{% endfor %}
+{% endfor %}
+{% endif %}
-{% for srv in interface.service %}
-{% for host in srv.host %}
-# DynDNS provider configuration for {{ host }}
-protocol={{ srv.protocol }},
+{% if interface[iface].service is defined and interface[iface].service is not none %}
+{% for service, config in interface[iface].service.items() %}
+{% for dns_record in config.host_name %}
+# DynDNS provider configuration for {{ service }}, {{ dns_record }}
+protocol={{ config.protocol }},
max-interval=28d,
-login={{ srv.login }},
-password='{{ srv.password }}',
-{% if srv.server -%}
-server={{ srv.server }},
-{% endif -%}
-{% if srv.zone -%}
-zone={{ srv.zone }},
-{% endif -%}
-{{ host }}
-{% endfor %}
-{% endfor %}
+login={{ config.login }},
+password='{{ config.password }}',
+{% if config.server %}
+server={{ config.server }},
+{% endif %}
+{% if config.zone %}
+zone={{ config.zone }},
+{% endif %}
+{{ dns_record }}
+{% endfor %}
+{% endfor %}
+{% endif %}
{% endfor %}
diff --git a/data/templates/ocserv/ocserv_config.tmpl b/data/templates/ocserv/ocserv_config.tmpl
index 6aaeff693..328af0c0d 100644
--- a/data/templates/ocserv/ocserv_config.tmpl
+++ b/data/templates/ocserv/ocserv_config.tmpl
@@ -1,4 +1,4 @@
-### generated by vpn_anyconnect.py ###
+### generated by vpn_openconnect.py ###
tcp-port = {{ listen_ports.tcp }}
udp-port = {{ listen_ports.udp }}
diff --git a/data/templates/ocserv/radius_conf.tmpl b/data/templates/ocserv/radius_conf.tmpl
index 2d19306a0..1712d83ef 100644
--- a/data/templates/ocserv/radius_conf.tmpl
+++ b/data/templates/ocserv/radius_conf.tmpl
@@ -1,4 +1,4 @@
-### generated by cpn_anyconnect.py ###
+### generated by vpn_openconnect.py ###
nas-identifier VyOS
{% for srv in server %}
{% if not "disable" in server[srv] %}
diff --git a/data/templates/ocserv/radius_servers.tmpl b/data/templates/ocserv/radius_servers.tmpl
index ba21fa074..7bacac992 100644
--- a/data/templates/ocserv/radius_servers.tmpl
+++ b/data/templates/ocserv/radius_servers.tmpl
@@ -1,4 +1,4 @@
-### generated by cpn_anyconnect.py ###
+### generated by vpn_openconnect.py ###
# server key
{% for srv in server %}
{% if not "disable" in server[srv] %}
diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl
index a7efee6d5..132c4ce40 100644
--- a/data/templates/wifi/hostapd.conf.tmpl
+++ b/data/templates/wifi/hostapd.conf.tmpl
@@ -51,10 +51,6 @@ ssid={{ ssid }}
# (default: 0, i.e., not set)
# Please note that some drivers do not use this value from hostapd and the
# channel will need to be configured separately with iwconfig.
-#
-# If CONFIG_ACS build option is enabled, the channel can be selected
-# automatically at run time by setting channel=acs_survey or channel=0, both of
-# which will enable the ACS survey based algorithm.
channel={{ channel }}
{% endif %}
@@ -124,38 +120,58 @@ ieee80211w=2
# DSSS/CCK Mode in 40 MHz: [DSSS_CCK-40] = allowed (not allowed if not set)
# 40 MHz intolerant [40-INTOLERANT] (not advertised if not set)
# L-SIG TXOP protection support: [LSIG-TXOP-PROT] (disabled if not set)
-{% set output = '' %}
-{% set output = output + '[40-INTOLERANT]' if capabilities.ht.fourtymhz_incapable is defined else '' %}
-{% set output = output + '[DELAYED-BA]' if capabilities.ht.delayed_block_ack is defined else '' %}
-{% set output = output + '[DSSS_CCK-40]' if capabilities.ht.dsss_cck_40 is defined else '' %}
-{% set output = output + '[GF]' if capabilities.ht.greenfield is defined else '' %}
-{% set output = output + '[LDPC]' if capabilities.ht.ldpc is defined else '' %}
-{% set output = output + '[LSIG-TXOP-PROT]' if capabilities.ht.lsig_protection is defined else '' %}
-{% set output = output + '[TX-STBC]' if capabilities.ht.stbc.tx is defined else '' %}
-{% set output = output + '[RX-STBC-' + capabilities.ht.stbc.rx | upper + ']' if capabilities.ht.stbc.tx is defined else '' %}
-{% set output = output + '[MAX-AMSDU-' + capabilities.ht.max_amsdu + ']' if capabilities.ht.max_amsdu is defined else '' %}
-{% set output = output + '[SMPS-' + capabilities.ht.smps | upper + ']' if capabilities.ht.smps is defined else '' %}
+{% set output = namespace(value='') %}
+
+{% if capabilities.ht.fourtymhz_incapable is defined %}
+{% set output.value = output.value + '[40-INTOLERANT]' %}
+{% endif %}
+{% if capabilities.ht.delayed_block_ack is defined %}
+{% set output.value = output.value + '[DELAYED-BA]' %}
+{% endif %}
+{% if capabilities.ht.dsss_cck_40 is defined %}
+{% set output.value = output.value + '[DSSS_CCK-40]' %}
+{% endif %}
+{% if capabilities.ht.greenfield is defined %}
+{% set output.value = output.value + '[GF]' %}
+{% endif %}
+{% if capabilities.ht.ldpc is defined %}
+{% set output.value = output.value + '[LDPC]' %}
+{% endif %}
+{% if capabilities.ht.lsig_protection is defined %}
+{% set output.value = output.value + '[LSIG-TXOP-PROT]' %}
+{% endif %}
+{% if capabilities.ht.stbc is defined and capabilities.ht.stbc.tx is defined %}
+{% set output.value = output.value + '[TX-STBC]' %}
+{% endif %}
+{% if capabilities.ht.stbc is defined and capabilities.ht.stbc.rx is defined %}
+{% set output.value = output.value + '[RX-STBC-' + capabilities.ht.stbc.rx | upper + ']' %}
+{% endif %}
+{% if capabilities.ht.max_amsdu is defined %}
+{% set output.value = output.value + '[MAX-AMSDU-' + capabilities.ht.max_amsdu + ']' %}
+{% endif %}
+{% if capabilities.ht.smps is defined %}
+{% set output.value = output.value + '[SMPS-' + capabilities.ht.smps | upper + ']' %}
+{% endif %}
{% if capabilities.ht.channel_set_width is defined %}
{% for csw in capabilities.ht.channel_set_width %}
-{% set output = output + '[' + csw | upper + ']' %}
+{% set output.value = output.value + '[' + csw | upper + ']' %}
{% endfor %}
{% endif %}
{% if capabilities.ht.short_gi is defined %}
{% for short_gi in capabilities.ht.short_gi %}
-{% set output = output + '[SHORT-GI-' + short_gi | upper + ']' %}
+{% set output.value = output.value + '[SHORT-GI-' + short_gi | upper + ']' %}
{% endfor %}
{% endif %}
-ht_capab={{ output }}
+ht_capab={{ output.value }}
-{% if capabilities.ht.auto_powersave is defined %}
+{% if capabilities.ht.auto_powersave is defined %}
# WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD]
# Enable this flag if U-APSD supported outside hostapd (eg., Firmware/driver)
uapsd_advertisement_enabled=1
-{% endif %}
-
+{% endif %}
{% endif %}
# Required for full HT and VHT functionality
@@ -286,14 +302,14 @@ require_ht=1
# 0 = Tx antenna pattern might change during the lifetime of an association
# 1 = Tx antenna pattern does not change during the lifetime of an
-{% if capabilities.vht.center_channel_freq.freq_1 is defined %}
+{% if capabilities.vht.center_channel_freq is defined and capabilities.vht.center_channel_freq.freq_1 is defined %}
# center freq = 5 GHz + (5 * index)
# So index 42 gives center freq 5.210 GHz
# which is channel 42 in 5G band
vht_oper_centr_freq_seg0_idx={{ capabilities.vht.center_channel_freq.freq_1 }}
{% endif %}
-{% if capabilities.vht.center_channel_freq.freq_2 is defined %}
+{% if capabilities.vht.center_channel_freq is defined and capabilities.vht.center_channel_freq.freq_2 is defined %}
# center freq = 5 GHz + (5 * index)
# So index 159 gives center freq 5.795 GHz
# which is channel 159 in 5G band
@@ -304,55 +320,71 @@ vht_oper_centr_freq_seg1_idx={{ capabilities.vht.center_channel_freq.freq_2 }}
vht_oper_chwidth={{ capabilities.vht.channel_set_width }}
{% endif %}
-{% set output = '' %}
-{% set output = output + '[TX-STBC-2BY1]' if capabilities.vht.stbc.tx is defined else '' %}
-{% set output = output + '[RXLDPC]' if capabilities.vht.ldpc is defined else '' %}
-{% set output = output + '[VHT-TXOP-PS]' if capabilities.vht.tx_powersave is defined else '' %}
-{% set output = output + '[HTC-VHT]' if capabilities.vht.vht_cf is defined else '' %}
-{% set output = output + '[RX-ANTENNA-PATTERN]' if capabilities.vht.antenna_pattern_fixed is defined else '' %}
-{% set output = output + '[TX-ANTENNA-PATTERN]' if capabilities.vht.antenna_pattern_fixed is defined else '' %}
-
-{% set output = output + '[RX-STBC-' + capabilities.vht.stbc.rx + ']' if capabilities.vht.stbc.rx is defined else '' %}
-{% set output = output + '[MAX-MPDU-' + capabilities.vht.max_mpdu + ']' if capabilities.vht.max_mpdu is defined else '' %}
-{% set output = output + '[MAX-A-MPDU-LEN-EXP-' + capabilities.vht.max_mpdu_exp + ']' if capabilities.vht.max_mpdu_exp is defined else '' %}
-{% set output = output + '[MAX-A-MPDU-LEN-EXP-' + capabilities.vht.max_mpdu_exp + ']' if capabilities.vht.max_mpdu_exp is defined else '' %}
-
-{% set output = output + '[VHT160]' if capabilities.vht.max_mpdu_exp is defined and capabilities.vht.max_mpdu_exp == '2' else '' %}
-{% set output = output + '[VHT160-80PLUS80]' if capabilities.vht.max_mpdu_exp is defined and capabilities.vht.max_mpdu_exp == '3' else '' %}
-{% set output = output + '[VHT-LINK-ADAPT2]' if capabilities.vht.link_adaptation is defined and capabilities.vht.link_adaptation == 'unsolicited' else '' %}
-{% set output = output + '[VHT-LINK-ADAPT3]' if capabilities.vht.link_adaptation is defined and capabilities.vht.link_adaptation == 'both' else '' %}
-
-{% if capabilities.vht.short_gi is defined %}
-{% for short_gi in capabilities.vht.short_gi %}
-{% set output = output + '[SHORT-GI-' + short_gi | upper + ']' %}
-{% endfor %}
+{% set output = namespace(value='') %}
+{% if capabilities.vht.stbc is defined and capabilities.vht.stbc.tx is defined %}
+{% set output.value = output.value + '[TX-STBC-2BY1]' %}
{% endif %}
-
-{% if capabilities.vht.beamform %}
-{% for beamform in capabilities.vht.beamform %}
-{% set output = output + '[SU-BEAMFORMER]' if beamform == 'single-user-beamformer' else '' %}
-{% set output = output + '[SU-BEAMFORMEE]' if beamform == 'single-user-beamformee' else '' %}
-{% set output = output + '[MU-BEAMFORMER]' if beamform == 'multi-user-beamformer' else '' %}
-{% set output = output + '[MU-BEAMFORMEE]' if beamform == 'multi-user-beamformee' else '' %}
-{% endfor %}
+{% if capabilities.vht.stbc is defined and capabilities.vht.stbc.rx is defined %}
+{% set output.value = output.value + '[RX-STBC-' + capabilities.vht.stbc.rx + ']' %}
+{% endif %}
+{% if capabilities.vht.ldpc is defined %}
+{% set output.value = output.value + '[RXLDPC]' %}
+{% endif %}
+{% if capabilities.vht.tx_powersave is defined %}
+{% set output.value = output.value + '[VHT-TXOP-PS]' %}
+{% endif %}
+{% if capabilities.vht.vht_cf is defined %}
+{% set output.value = output.value + '[HTC-VHT]' %}
+{% endif %}
+{% if capabilities.vht.antenna_pattern_fixed is defined %}
+{% set output.value = output.value + '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]' %}
+{% endif %}
+{% if capabilities.vht.max_mpdu is defined %}
+{% set output.value = output.value + '[MAX-MPDU-' + capabilities.vht.max_mpdu + ']' %}
+{% endif %}
+{% if capabilities.vht.max_mpdu_exp is defined %}
+{% set output.value = output.value + '[MAX-A-MPDU-LEN-EXP-' + capabilities.vht.max_mpdu_exp + ']' %}
{% endif %}
+{% if capabilities.vht.max_mpdu_exp is defined and capabilities.vht.max_mpdu_exp == '2' %}
+{% set output.value = output.value + '[VHT160]' %}
+{% endif %}
+{% if capabilities.vht.max_mpdu_exp is defined and capabilities.vht.max_mpdu_exp == '3' %}
+{% set output.value = output.value + '[VHT160-80PLUS80]' %}
+{% endif %}
+{% if capabilities.vht.link_adaptation is defined and capabilities.vht.link_adaptation == 'unsolicited' %}
+{% set output.value = output.value + '[VHT-LINK-ADAPT2]' %}
+{% endif %}
+{% if capabilities.vht.link_adaptation is defined and capabilities.vht.link_adaptation == 'both' %}
+{% set output.value = output.value + '[VHT-LINK-ADAPT3]' %}
+{% endif %}
+
+{% for short_gi in capabilities.vht.short_gi if capabilities.vht.short_gi is defined %}
+{% set output.value = output.value + '[SHORT-GI-' + short_gi | upper + ']' %}
+{% endfor %}
+
+{% for beamform in capabilities.vht.beamform if capabilities.vht.beamform is defined %}
+{% set output.value = output.value + '[SU-BEAMFORMER]' if beamform == 'single-user-beamformer' else '' %}
+{% set output.value = output.value + '[SU-BEAMFORMEE]' if beamform == 'single-user-beamformee' else '' %}
+{% set output.value = output.value + '[MU-BEAMFORMER]' if beamform == 'multi-user-beamformer' else '' %}
+{% set output.value = output.value + '[MU-BEAMFORMEE]' if beamform == 'multi-user-beamformee' else '' %}
+{% endfor %}
{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 %}
{% if capabilities.vht.beamform %}
{% if beamform == 'single-user-beamformer' %}
{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 and capabilities.vht.antenna_count|int < 6 %}
-{% set output = output + '[BF-ANTENNA-' + capabilities.vht.antenna_count|int -1 + ']' %}
-{% set output = output + '[SOUNDING-DIMENSION-' + capabilities.vht.antenna_count|int -1 + ']' %}
+{% set output.value = output.value + '[BF-ANTENNA-' + capabilities.vht.antenna_count|int -1 + ']' %}
+{% set output.value = output.value + '[SOUNDING-DIMENSION-' + capabilities.vht.antenna_count|int -1 + ']' %}
{% endif %}
{% endif %}
{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 and capabilities.vht.antenna_count|int < 5 %}
-{% set output = output + '[BF-ANTENNA-' + capabilities.vht.antenna_count + ']' %}
-{% set output = output + '[SOUNDING-DIMENSION-' + capabilities.vht.antenna_count+ ']' %}
+{% set output.value = output.value + '[BF-ANTENNA-' + capabilities.vht.antenna_count + ']' %}
+{% set output.value = output.value + '[SOUNDING-DIMENSION-' + capabilities.vht.antenna_count+ ']' %}
{% endif %}
{% endif %}
{% endif %}
-vht_capab={{ output }}
+vht_capab={{ output.value }}
{% endif %}
# ieee80211n: Whether IEEE 802.11n (HT) is enabled
diff --git a/debian/control b/debian/control
index d9663d07b..0777ecc52 100644
--- a/debian/control
+++ b/debian/control
@@ -110,6 +110,7 @@ Depends:
vyos-utils,
wide-dhcpv6-client,
wireguard-tools,
+ wireguard-modules,
wireless-regdb,
wpasupplicant (>= 0.6.7)
Description: VyOS configuration scripts and data
diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in
index 143c04ef6..34a31a7c5 100644
--- a/interface-definitions/dns-dynamic.xml.in
+++ b/interface-definitions/dns-dynamic.xml.in
@@ -58,6 +58,7 @@
<validator name="numeric" argument="--range 1-86400"/>
</constraint>
</properties>
+ <defaultValue>600</defaultValue>
</leafNode>
<leafNode name="zone">
<properties>
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in
index 7d658f6a0..b28be387b 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -99,6 +99,19 @@
</children>
</node>
#include <include/interface-mac.xml.i>
+ <leafNode name="min-links">
+ <properties>
+ <help>Minimum number of member interfaces required up before enabling bond</help>
+ <valueHelp>
+ <format>&lt;0-16&gt;</format>
+ <description>Minimum number of member interfaces required up before enabling bond</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16"/>
+ </constraint>
+ </properties>
+ <defaultValue>0</defaultValue>
+ </leafNode>
<leafNode name="mode">
<properties>
<help>Bonding mode</help>
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index e8f3f09f1..0aef0d332 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -268,6 +268,37 @@
</properties>
<defaultValue>auto</defaultValue>
</leafNode>
+ <node name="ring-buffer">
+ <properties>
+ <help>Shared buffer between the device driver and NIC</help>
+ </properties>
+ <children>
+ <leafNode name="rx">
+ <properties>
+ <help>RX ring buffer</help>
+ <valueHelp>
+ <format>80-16384</format>
+ <description>ring buffer size</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 80-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx">
+ <properties>
+ <help>TX ring buffer</help>
+ <valueHelp>
+ <format>80-16384</format>
+ <description>ring buffer size</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 80-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
#include <include/vif-s.xml.i>
#include <include/vif.xml.i>
</children>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index 6f0ec9e71..a0caf810f 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -110,7 +110,6 @@
<constraint>
<regex>(3839|7935)</regex>
</constraint>
- <multi/>
</properties>
</leafNode>
<leafNode name="short-gi">
@@ -150,7 +149,6 @@
<constraint>
<regex>(static|dynamic)</regex>
</constraint>
- <multi/>
</properties>
</leafNode>
<node name="stbc">
diff --git a/interface-definitions/vpn_anyconnect.xml.in b/interface-definitions/vpn_openconnect.xml.in
index e74326986..16fe660a9 100644
--- a/interface-definitions/vpn_anyconnect.xml.in
+++ b/interface-definitions/vpn_openconnect.xml.in
@@ -2,9 +2,9 @@
<interfaceDefinition>
<node name="vpn">
<children>
- <node name="anyconnect" owner="${vyos_conf_scripts_dir}/vpn_anyconnect.py">
+ <node name="openconnect" owner="${vyos_conf_scripts_dir}/vpn_openconnect.py">
<properties>
- <help>SSL VPN AnyConnect</help>
+ <help>SSL VPN OpenConnect, AnyConnect compatible server</help>
<priority>901</priority>
</properties>
<children>
diff --git a/op-mode-definitions/force-mtu-host.xml b/op-mode-definitions/force-mtu-host.xml
new file mode 100644
index 000000000..b92179f11
--- /dev/null
+++ b/op-mode-definitions/force-mtu-host.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="force">
+ <children>
+ <node name="mtu">
+ <properties>
+ <help>Show MTU max value for remote host protocol TCP</help>
+ </properties>
+ <children>
+ <tagNode name="host">
+ <properties>
+ <help>IP address of the remote host</help>
+ <completionHelp>
+ <list>&lt;hostname&gt; &lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/force_mtu_host.sh $4</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Source interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/force_mtu_host.sh $4 $6</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/monitor-bandwidth-test.xml b/op-mode-definitions/monitor-bandwidth-test.xml
index d1e459b17..5b36b1da5 100644
--- a/op-mode-definitions/monitor-bandwidth-test.xml
+++ b/op-mode-definitions/monitor-bandwidth-test.xml
@@ -11,7 +11,7 @@
<properties>
<help>Wait for bandwidth test connections (port TCP/5001)</help>
</properties>
- <command>iperf -s</command>
+ <command>/usr/bin/iperf -V -s</command>
</leafNode>
<tagNode name="initiate">
<properties>
@@ -20,7 +20,7 @@
<list>&lt;hostname&gt; &lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
</completionHelp>
</properties>
- <command>iperf -c $4</command>
+ <command>${vyos_op_scripts_dir}/monitor_bandwidth_test.sh "$4"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/anyconnect.xml b/op-mode-definitions/openconnect.xml
index 7e8cdd35b..9b82b114e 100644
--- a/op-mode-definitions/anyconnect.xml
+++ b/op-mode-definitions/openconnect.xml
@@ -2,16 +2,16 @@
<interfaceDefinition>
<node name="show">
<children>
- <node name="anyconnect-server">
+ <node name="openconnect-server">
<properties>
- <help>show anyconnect-server information</help>
+ <help>show openconnect-server information</help>
</properties>
<children>
<leafNode name="sessions">
<properties>
- <help>Show active anyconnect server sessions</help>
+ <help>Show active openconnect server sessions</help>
</properties>
- <command>${vyos_op_scripts_dir}/anyconnect-control.py --action="show_sessions"</command>
+ <command>${vyos_op_scripts_dir}/openconnect-control.py --action="show_sessions"</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-bonding.xml b/op-mode-definitions/show-interfaces-bonding.xml
index 568b215af..c1c76b059 100644
--- a/op-mode-definitions/show-interfaces-bonding.xml
+++ b/op-mode-definitions/show-interfaces-bonding.xml
@@ -19,6 +19,12 @@
</properties>
<command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
</leafNode>
+ <leafNode name="detail">
+ <properties>
+ <help>Show detailed interface information</help>
+ </properties>
+ <command>if [ -f "/proc/net/bonding/$4" ]; then cat "/proc/net/bonding/$4"; else echo "Interface $4 does not exist!"; fi</command>
+ </leafNode>
<tagNode name="vif">
<properties>
<help>Show specified virtual network interface (vif) information</help>
diff --git a/op-mode-definitions/show-log.xml b/op-mode-definitions/show-log.xml
index 0c4da647b..b00e4cfec 100644
--- a/op-mode-definitions/show-log.xml
+++ b/op-mode-definitions/show-log.xml
@@ -135,7 +135,7 @@
</properties>
<command>egrep -i "kernel:.*\[NAT-[A-Z]{3,}-[0-9]+(-MASQ)?\]" $(find /var/log -maxdepth 1 -type f -name messages\* | sort -t. -k2nr)</command>
</leafNode>
- <leafNode name="nat">
+ <leafNode name="openvpn">
<properties>
<help>Show log for OpenVPN</help>
</properties>
diff --git a/op-mode-definitions/show-version.xml b/op-mode-definitions/show-version.xml
index aae5bb008..905a4865c 100644
--- a/op-mode-definitions/show-version.xml
+++ b/op-mode-definitions/show-version.xml
@@ -18,7 +18,7 @@
<properties>
<help>Show system version and versions of all packages</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_version.py --all</command>
+ <command>echo "Package versions:"; dpkg -l | awk '$0~/>/{exit}1'</command>
</leafNode>
<leafNode name="quagga">
<properties>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index e8c0aa5b3..bfc70b772 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -148,8 +148,8 @@ def T2665_default_dict_cleanup(dict):
def leaf_node_changed(conf, path):
"""
Check if a leaf node was altered. If it has been altered - values has been
- changed, or it was added/removed, we will return the old value. If nothing
- has been changed, None is returned
+ changed, or it was added/removed, we will return a list containing the old
+ value(s). If nothing has been changed, None is returned
"""
from vyos.configdiff import get_config_diff
D = get_config_diff(conf, key_mangling=('-', '_'))
@@ -157,7 +157,7 @@ def leaf_node_changed(conf, path):
(new, old) = D.get_value_diff(path)
if new != old:
if isinstance(old, str):
- return old
+ return [old]
elif isinstance(old, list):
if isinstance(new, str):
new = [new]
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index 64407401b..9108fc180 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.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
@@ -16,15 +16,12 @@
import os
from vyos.ifconfig.interface import Interface
-from vyos.ifconfig.vlan import VLAN
-
from vyos.util import cmd
from vyos.util import vyos_dict_search
from vyos.validate import assert_list
from vyos.validate import assert_positive
@Interface.register
-@VLAN.enable
class BondIf(Interface):
"""
The Linux bonding driver provides a method for aggregating multiple network
@@ -52,6 +49,10 @@ class BondIf(Interface):
'validate': lambda v: assert_list(v, ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']),
'location': '/sys/class/net/{ifname}/bonding/xmit_hash_policy',
},
+ 'bond_min_links': {
+ 'validate': assert_positive,
+ 'location': '/sys/class/net/{ifname}/bonding/min_links',
+ },
'bond_miimon': {
'validate': assert_positive,
'location': '/sys/class/net/{ifname}/bonding/miimon'
@@ -130,6 +131,29 @@ class BondIf(Interface):
"""
self.set_interface('bond_hash_policy', mode)
+ def set_min_links(self, number):
+ """
+ Specifies the minimum number of links that must be active before
+ asserting carrier. It is similar to the Cisco EtherChannel min-links
+ feature. This allows setting the minimum number of member ports that
+ must be up (link-up state) before marking the bond device as up
+ (carrier on). This is useful for situations where higher level services
+ such as clustering want to ensure a minimum number of low bandwidth
+ links are active before switchover. This option only affect 802.3ad
+ mode.
+
+ The default value is 0. This will cause carrier to be asserted (for
+ 802.3ad mode) whenever there is an active aggregator, regardless of the
+ number of available links in that aggregator. Note that, because an
+ aggregator cannot be active without at least one available link,
+ setting this option to 0 or to 1 has the exact same effect.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_min_links('0')
+ """
+ self.set_interface('bond_min_links', number)
+
def set_arp_interval(self, interval):
"""
Specifies the ARP link monitoring frequency in milliseconds.
@@ -347,12 +371,21 @@ class BondIf(Interface):
value = config.get('hash_policy')
if value: self.set_hash_policy(value)
+ # Minimum number of member interfaces
+ value = config.get('min_links')
+ if value: self.set_min_links(value)
+
# Some interface options can only be changed if the interface is
# administratively down
if self.get_admin_state() == 'down':
- # Delete bond member port(s)
+ # Remove ALL bond member interfaces
for interface in self.get_slaves():
self.del_port(interface)
+ # Removing an interface from a bond will always place the underlaying
+ # physical interface in admin-down state! If physical interface is
+ # not disabled, re-enable it.
+ if not vyos_dict_search(f'member.interface_remove.{interface}.disable', config):
+ Interface(interface).set_admin_state('up')
# Bonding policy/mode
value = config.get('mode')
@@ -360,13 +393,12 @@ class BondIf(Interface):
# Add (enslave) interfaces to bond
value = vyos_dict_search('member.interface', config)
- if value:
- for interface in value:
- # if we've come here we already verified the interface
- # does not have an addresses configured so just flush
- # any remaining ones
- Interface(interface).flush_addrs()
- self.add_port(interface)
+ for interface in (value or []):
+ # if we've come here we already verified the interface
+ # does not have an addresses configured so just flush
+ # any remaining ones
+ Interface(interface).flush_addrs()
+ self.add_port(interface)
# Primary device interface - must be set after 'mode'
value = config.get('primary')
diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py
index a6fc8ac6c..43136f361 100644
--- a/python/vyos/ifconfig/control.py
+++ b/python/vyos/ifconfig/control.py
@@ -13,8 +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/>.
-
import os
+
from inspect import signature
from inspect import _empty
@@ -30,9 +30,9 @@ class Control(Section):
_signature = {}
def __init__(self, **kargs):
- # some commands (such as operation comands - show interfaces, etc.)
+ # some commands (such as operation comands - show interfaces, etc.)
# need to query the interface statistics. If the interface
- # code is used and the debugging is enabled, the screen output
+ # code is used and the debugging is enabled, the screen output
# will include both the command but also the debugging for that command
# to prevent this, debugging can be explicitely disabled
diff --git a/python/vyos/ifconfig/dummy.py b/python/vyos/ifconfig/dummy.py
index 43614cd1c..19ef9d304 100644
--- a/python/vyos/ifconfig/dummy.py
+++ b/python/vyos/ifconfig/dummy.py
@@ -13,10 +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 vyos.ifconfig.interface import Interface
-
@Interface.register
class DummyIf(Interface):
"""
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 17c1bd64d..1d48941f9 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -17,13 +17,11 @@ import os
import re
from vyos.ifconfig.interface import Interface
-from vyos.ifconfig.vlan import VLAN
from vyos.validate import assert_list
from vyos.util import run
from vyos.util import vyos_dict_search
@Interface.register
-@VLAN.enable
class EthernetIf(Interface):
"""
Abstraction of a Linux Ethernet Interface
@@ -253,6 +251,22 @@ class EthernetIf(Interface):
"""
return self.set_interface('ufo', state)
+ def set_ring_buffer(self, b_type, b_size):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_ring_buffer('rx', '4096')
+ """
+ cmd = '/sbin/ethtool -G {0} {1} {2}'.format(self.config['ifname'], b_type, b_size)
+ output, code = self._popen(cmd)
+ # ethtool error codes:
+ # 80 - value already setted
+ # 81 - does not possible to set value
+ if code and code != 80:
+ print('could not set {0} ring-buffer for {1}'.format(b_type, self.config['ifname']))
+ return output
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
@@ -298,6 +312,11 @@ class EthernetIf(Interface):
duplex = config.get('duplex')
self.set_speed_duplex(speed, duplex)
+ # Set interface ring buffer
+ if 'ring_buffer' in config:
+ for b_type in config['ring_buffer']:
+ self.set_ring_buffer(b_type, config['ring_buffer'][b_type])
+
# 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/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py
index dd0658668..0a13043cc 100644
--- a/python/vyos/ifconfig/geneve.py
+++ b/python/vyos/ifconfig/geneve.py
@@ -14,10 +14,8 @@
# 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
class GeneveIf(Interface):
"""
diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py
index bfab36335..a6e566d87 100644
--- a/python/vyos/ifconfig/input.py
+++ b/python/vyos/ifconfig/input.py
@@ -13,10 +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 vyos.ifconfig.interface import Interface
-
@Interface.register
class InputIf(Interface):
default = {
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index ef2336c17..be97b411b 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -86,10 +86,6 @@ 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',
},
- 'vlan_protocol': {
- 'shellcmd': 'ip -json -details link show dev {ifname}',
- 'format': lambda j: jmespath.search('[*].linkinfo.info_data.protocol | [0]', json.loads(j)),
- },
}
_command_set = {
@@ -464,10 +460,13 @@ class Interface(Control):
Calculate the EUI64 from the interface's MAC, then assign it
with the given prefix to the interface.
"""
-
- eui64 = mac2eui64(self.get_mac(), prefix)
- prefixlen = prefix.split('/')[1]
- self.add_addr(f'{eui64}/{prefixlen}')
+ # T2863: only add a link-local IPv6 address if the interface returns
+ # a MAC address. This is not the case on e.g. WireGuard interfaces.
+ mac = self.get_mac()
+ if mac:
+ eui64 = mac2eui64(mac, prefix)
+ prefixlen = prefix.split('/')[1]
+ self.add_addr(f'{eui64}/{prefixlen}')
def del_ipv6_eui64_address(self, prefix):
"""
@@ -559,17 +558,6 @@ class Interface(Control):
"""
self.set_interface('alias', ifalias)
- def get_vlan_protocol(self):
- """
- Retrieve VLAN protocol in use, this can be 802.1Q, 802.1ad or None
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0.10').get_vlan_protocol()
- '802.1Q'
- """
- return self.get_interface('vlan_protocol')
-
def get_admin_state(self):
"""
Get interface administrative state. Function will return 'up' or 'down'
@@ -591,17 +579,6 @@ class Interface(Control):
>>> Interface('eth0').get_admin_state()
'down'
"""
- # A VLAN interface can only be placed in admin up state when
- # the lower interface is up, too
- if self.get_vlan_protocol():
- lower_interface = glob(f'/sys/class/net/{self.ifname}/lower*/flags')[0]
- with open(lower_interface, 'r') as f:
- flags = f.read()
- # If parent is not up - bail out as we can not bring up the VLAN.
- # Flags are defined in kernel source include/uapi/linux/if.h
- if not int(flags, 16) & 1:
- return None
-
if state == 'up':
self._admin_state_down_cnt -= 1
if self._admin_state_down_cnt < 1:
@@ -1028,33 +1005,160 @@ class Interface(Control):
self.add_to_bridge(bridge)
# remove no longer required 802.1ad (Q-in-Q VLANs)
+ ifname = config['ifname']
for vif_s_id in config.get('vif_s_remove', {}):
- self.del_vlan(vif_s_id)
+ vif_s_ifname = f'{ifname}.{vif_s_id}'
+ VLANIf(vif_s_ifname).remove()
# create/update 802.1ad (Q-in-Q VLANs)
- ifname = config['ifname']
- for vif_s_id, vif_s in config.get('vif_s', {}).items():
- tmp=get_ethertype(vif_s.get('ethertype', '0x88A8'))
- s_vlan = self.add_vlan(vif_s_id, ethertype=tmp)
- vif_s['ifname'] = f'{ifname}.{vif_s_id}'
- s_vlan.update(vif_s)
+ 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['source_interface'] = ifname
+ tmp['vlan_id'] = vif_s_id
+
+ vif_s_ifname = f'{ifname}.{vif_s_id}'
+ vif_s_config['ifname'] = vif_s_ifname
+ s_vlan = VLANIf(vif_s_ifname, **tmp)
+ s_vlan.update(vif_s_config)
# remove no longer required client VLAN (vif-c)
- for vif_c_id in vif_s.get('vif_c_remove', {}):
- s_vlan.del_vlan(vif_c_id)
+ for vif_c_id in vif_s_config.get('vif_c_remove', {}):
+ vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}'
+ VLANIf(vif_c_ifname).remove()
# create/update client VLAN (vif-c) interface
- for vif_c_id, vif_c in vif_s.get('vif_c', {}).items():
- c_vlan = s_vlan.add_vlan(vif_c_id)
- vif_c['ifname'] = f'{ifname}.{vif_s_id}.{vif_c_id}'
- c_vlan.update(vif_c)
+ for vif_c_id, vif_c_config in vif_s_config.get('vif_c', {}).items():
+ tmp = deepcopy(VLANIf.get_config())
+ tmp['source_interface'] = vif_s_ifname
+ tmp['vlan_id'] = vif_c_id
+
+ vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}'
+ vif_c_config['ifname'] = vif_c_ifname
+ c_vlan = VLANIf(vif_c_ifname, **tmp)
+ c_vlan.update(vif_c_config)
# remove no longer required 802.1q VLAN interfaces
for vif_id in config.get('vif_remove', {}):
- self.del_vlan(vif_id)
+ vif_ifname = f'{ifname}.{vif_id}'
+ VLANIf(vif_ifname).remove()
# create/update 802.1q VLAN interfaces
- for vif_id, vif in config.get('vif', {}).items():
- vlan = self.add_vlan(vif_id)
- vif['ifname'] = f'{ifname}.{vif_id}'
- vlan.update(vif)
+ for vif_id, vif_config in config.get('vif', {}).items():
+ tmp = deepcopy(VLANIf.get_config())
+ tmp['source_interface'] = ifname
+ tmp['vlan_id'] = vif_id
+
+ vif_ifname = f'{ifname}.{vif_id}'
+ vif_config['ifname'] = vif_ifname
+ vlan = VLANIf(vif_ifname, **tmp)
+ vlan.update(vif_config)
+
+
+class VLANIf(Interface):
+ """ Specific class which abstracts 802.1q and 802.1ad (Q-in-Q) VLAN interfaces """
+ default = {
+ 'type': 'vlan',
+ 'source_interface': '',
+ 'vlan_id': '',
+ 'ethertype': '',
+ 'ingress_qos': '',
+ 'egress_qos': '',
+ }
+
+ options = Interface.options + \
+ ['source_interface', 'vlan_id', 'ethertype', 'ingress_qos', 'egress_qos']
+
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> VLANIf('eth0.10').remove
+ """
+ # Do we have sub interfaces (VLANs)? As interfaces need to be deleted
+ # "in order" starting from Q-in-Q we delete them first.
+ for upper in glob(f'/sys/class/net/{self.ifname}/upper*'):
+ # an upper interface could be named: upper_bond0.1000.1100, thus
+ # we need top drop the upper_ prefix
+ vif_c = os.path.basename(upper)
+ vif_c = vif_c.replace('upper_', '')
+ VLANIf(vif_c).remove()
+
+ super().remove()
+
+ def _create(self):
+ # bail out early if interface already exists
+ if os.path.exists(f'/sys/class/net/{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['ingress_qos']:
+ cmd += ' ingress-qos-map {ingress_qos}'
+ if self.config['egress_qos']:
+ cmd += ' egress-qos-map {egress_qos}'
+
+ self._cmd(cmd.format(**self.config))
+
+ # 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'
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0.10').set_admin_state('down')
+ >>> Interface('eth0.10').get_admin_state()
+ 'down'
+ """
+ # A VLAN interface can only be placed in admin up state when
+ # the lower interface is up, too
+ lower_interface = glob(f'/sys/class/net/{self.ifname}/lower*/flags')[0]
+ with open(lower_interface, 'r') as f:
+ flags = f.read()
+ # If parent is not up - bail out as we can not bring up the VLAN.
+ # Flags are defined in kernel source include/uapi/linux/if.h
+ if not int(flags, 16) & 1:
+ return None
+
+ return super().set_admin_state(state)
+
+ 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
+ interface setup code and provide a single point of entry when workin
+ on any interface. """
+
+ # call base class first
+ super().update(config)
+
+ # 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
+ # often as 'down'. This is required by some interface implementations
+ # as certain parameters can only be changed when the interface is
+ # in admin-down state. This ensures the link does not flap during
+ # reconfiguration.
+ state = 'down' if 'disable' in config else 'up'
+ self.set_admin_state(state)
diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py
index 34147eb38..33740921e 100644
--- a/python/vyos/ifconfig/l2tpv3.py
+++ b/python/vyos/ifconfig/l2tpv3.py
@@ -13,12 +13,9 @@
# 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
class L2TPv3If(Interface):
"""
diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py
index c70e1773f..0e632d826 100644
--- a/python/vyos/ifconfig/loopback.py
+++ b/python/vyos/ifconfig/loopback.py
@@ -13,10 +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 vyos.ifconfig.interface import Interface
-
@Interface.register
class LoopbackIf(Interface):
"""
diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py
index b068ce873..9c1d09c1c 100644
--- a/python/vyos/ifconfig/macvlan.py
+++ b/python/vyos/ifconfig/macvlan.py
@@ -14,13 +14,9 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
from copy import deepcopy
-
from vyos.ifconfig.interface import Interface
-from vyos.ifconfig.vlan import VLAN
-
@Interface.register
-@VLAN.enable
class MACVLANIf(Interface):
"""
Abstraction of a Linux MACvlan interface
diff --git a/python/vyos/ifconfig/operational.py b/python/vyos/ifconfig/operational.py
index d585c1873..33e8614f0 100644
--- a/python/vyos/ifconfig/operational.py
+++ b/python/vyos/ifconfig/operational.py
@@ -14,20 +14,19 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
import os
+
from time import time
from datetime import datetime
from functools import reduce
-
from tabulate import tabulate
from vyos.ifconfig import Control
-
class Operational(Control):
"""
A class able to load Interface statistics
"""
-
+
cache_magic = 'XYZZYX'
_stat_names = {
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 85c22b5b4..964ffe383 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -16,7 +16,6 @@
# https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/
# https://community.hetzner.com/tutorials/linux-setup-gre-tunnel
-
from copy import deepcopy
from vyos.ifconfig.interface import Interface
diff --git a/python/vyos/ifconfig/vlan.py b/python/vyos/ifconfig/vlan.py
deleted file mode 100644
index d68e8f6cd..000000000
--- a/python/vyos/ifconfig/vlan.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-
-
-import os
-import re
-
-from vyos.ifconfig.interface import Interface
-
-
-# This is an internal implementation class
-class VLAN:
- """
- This class handels the creation and removal of a VLAN interface. It serves
- as base class for BondIf and EthernetIf.
- """
-
- _novlan_remove = lambda : None
-
- @classmethod
- def enable (cls,adaptee):
- adaptee._novlan_remove = adaptee.remove
- adaptee.remove = cls.remove
- adaptee.add_vlan = cls.add_vlan
- adaptee.del_vlan = cls.del_vlan
- adaptee.definition['vlan'] = True
- return adaptee
-
- def remove(self):
- """
- Remove interface from operating system. Removing the interface
- deconfigures all assigned IP addresses and clear possible DHCP(v6)
- client processes.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> i = Interface('eth0')
- >>> i.remove()
- """
- ifname = self.config['ifname']
-
- # Do we have sub interfaces (VLANs)? We apply a regex matching
- # subinterfaces (indicated by a .) of a parent interface.
- #
- # As interfaces need to be deleted "in order" starting from Q-in-Q
- # we delete them first.
- vlan_ifs = [f for f in os.listdir(r'/sys/class/net')
- if re.match(ifname + r'(?:\.\d+)(?:\.\d+)', f)]
-
- for vlan in vlan_ifs:
- Interface(vlan).remove()
-
- # After deleting all Q-in-Q interfaces delete other VLAN interfaces
- # which probably acted as parent to Q-in-Q or have been regular 802.1q
- # interface.
- vlan_ifs = [f for f in os.listdir(r'/sys/class/net')
- if re.match(ifname + r'(?:\.\d+)', f)]
-
- for vlan in vlan_ifs:
- # self.__class__ is already VLAN.enabled
- self.__class__(vlan)._novlan_remove()
-
- # All subinterfaces are now removed, continue on the physical interface
- self._novlan_remove()
-
- def add_vlan(self, vlan_id, ethertype='', ingress_qos='', egress_qos=''):
- """
- A virtual LAN (VLAN) is any broadcast domain that is partitioned and
- isolated in a computer network at the data link layer (OSI layer 2).
- Use this function to create a new VLAN interface on a given physical
- interface.
-
- This function creates both 802.1q and 802.1ad (Q-in-Q) interfaces. Proto
- parameter is used to indicate VLAN type.
-
- A new object of type VLANIf is returned once the interface has been
- created.
-
- @param ethertype: If specified, create 802.1ad or 802.1q Q-in-Q VLAN
- interface
- @param ingress_qos: Defines a mapping of VLAN header prio field to the
- Linux internal packet priority on incoming frames.
- @param ingress_qos: Defines a mapping of Linux internal packet priority
- to VLAN header prio field but for outgoing frames.
-
- Example:
- >>> from vyos.ifconfig import MACVLANIf
- >>> i = MACVLANIf('eth0')
- >>> i.add_vlan(10)
- """
- vlan_ifname = self.config['ifname'] + '.' + str(vlan_id)
- if os.path.exists(f'/sys/class/net/{vlan_ifname}'):
- return self.__class__(vlan_ifname)
-
- if ethertype:
- self._ethertype = ethertype
- ethertype = 'proto {}'.format(ethertype)
-
- # Optional ingress QOS mapping
- opt_i = ''
- if ingress_qos:
- opt_i = 'ingress-qos-map ' + ingress_qos
- # Optional egress QOS mapping
- opt_e = ''
- if egress_qos:
- opt_e = 'egress-qos-map ' + egress_qos
-
- # create interface in the system
- cmd = 'ip link add link {ifname} name {ifname}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \
- .format(ifname=self.ifname, vlan=vlan_id, proto=ethertype, opt_e=opt_e, opt_i=opt_i)
- self._cmd(cmd)
-
- # return new object mapping to the newly created interface
- # we can now work on this object for e.g. IP address setting
- # or interface description and so on
- return self.__class__(vlan_ifname)
-
- def del_vlan(self, vlan_id):
- """
- Remove VLAN interface from operating system. Removing the interface
- deconfigures all assigned IP addresses and clear possible DHCP(v6)
- client processes.
-
- Example:
- >>> from vyos.ifconfig import MACVLANIf
- >>> i = MACVLANIf('eth0.10')
- >>> i.del_vlan()
- """
- ifname = self.config['ifname']
- self.__class__(f'{ifname}.{vlan_id}')._novlan_remove()
diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py
index 01a7cc7ab..d3e9d5df2 100644
--- a/python/vyos/ifconfig/vrrp.py
+++ b/python/vyos/ifconfig/vrrp.py
@@ -16,15 +16,13 @@
import os
import json
import signal
+
from time import time
from time import sleep
-
from tabulate import tabulate
-from vyos import airbag
from vyos import util
-
class VRRPError(Exception):
pass
diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py
index 56ebe01d1..d0745898c 100644
--- a/python/vyos/ifconfig/vti.py
+++ b/python/vyos/ifconfig/vti.py
@@ -13,10 +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 vyos.ifconfig.interface import Interface
-
@Interface.register
class VTIIf(Interface):
default = {
diff --git a/python/vyos/ifconfig/vtun.py b/python/vyos/ifconfig/vtun.py
index 60c178b9a..b25e32d63 100644
--- a/python/vyos/ifconfig/vtun.py
+++ b/python/vyos/ifconfig/vtun.py
@@ -13,10 +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 vyos.ifconfig.interface import Interface
-
@Interface.register
class VTunIf(Interface):
default = {
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 18a500336..dba62b61a 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -18,7 +18,6 @@ from copy import deepcopy
from vyos import ConfigError
from vyos.ifconfig.interface import Interface
-
@Interface.register
class VXLANIf(Interface):
"""
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index fad4ef282..d8e89229d 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -13,9 +13,9 @@
# 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
import time
+
from datetime import timedelta
from hurry.filesize import size
diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py
index a50346ffa..346577119 100644
--- a/python/vyos/ifconfig/wireless.py
+++ b/python/vyos/ifconfig/wireless.py
@@ -13,14 +13,9 @@
# 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
-from vyos.ifconfig.vlan import VLAN
-
@Interface.register
-@VLAN.enable
class WiFiIf(Interface):
"""
Handle WIFI/WLAN interfaces.
@@ -77,9 +72,22 @@ 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']
+
# 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/util.py b/python/vyos/util.py
index 84aa16791..79e11a86d 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -654,6 +654,7 @@ def get_bridge_member_config(conf, br, intf):
def check_kmod(k_mod):
""" Common utility function to load required kernel modules on demand """
+ from vyos import ConfigError
if isinstance(k_mod, str):
k_mod = k_mod.split()
for module in k_mod:
diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py
index e3d3b25ee..ac5e01e50 100755
--- a/smoketest/scripts/cli/test_interfaces_bonding.py
+++ b/smoketest/scripts/cli/test_interfaces_bonding.py
@@ -20,6 +20,7 @@ import unittest
from base_interfaces_test import BasicInterfaceTest
from vyos.ifconfig import Section
+from vyos.ifconfig.interface import Interface
from vyos.configsession import ConfigSessionError
from vyos.util import read_file
@@ -57,5 +58,39 @@ class BondingInterfaceTest(BasicInterfaceTest.BaseTest):
slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split()
self.assertListEqual(slaves, self._members)
+ def test_8021q_vlan(self):
+ """ Testcase for 802.1q VLAN interfaces created on top of a lacp / bond
+ interface. This is the testcase for T2894 """
+ super().test_8021q_vlan()
+
+ for interface in self._interfaces:
+ slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split()
+ self.assertListEqual(slaves, self._members)
+
+ def test_remove_member(self):
+ """ T2515: when removing a bond member the previously enslaved/member
+ interface must be in its former admin-up/down state. Here we ensure that
+ it is admin-up as it was admin-up before. """
+
+ # configure member interfaces
+ for interface in self._interfaces:
+ for option in self._options.get(interface, []):
+ self.session.set(self._base_path + [interface] + option.split())
+
+ self.session.commit()
+
+ # remove single bond member port
+ for interface in self._interfaces:
+ remove_member = self._members[0]
+ self.session.delete(self._base_path + [interface, 'member', 'interface', remove_member])
+
+ self.session.commit()
+
+ # removed member port must be admin-up
+ for interface in self._interfaces:
+ remove_member = self._members[0]
+ state = Interface(remove_member).get_admin_state()
+ self.assertEqual('up', state)
+
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index fae233244..691f633b7 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -15,11 +15,19 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import re
import unittest
from base_interfaces_test import BasicInterfaceTest
from psutil import process_iter
+
from vyos.util import check_kmod
+from vyos.util import read_file
+
+def get_config_value(interface, key):
+ tmp = read_file(f'/run/hostapd/{interface}.conf')
+ tmp = re.findall(r'\n?{}=+(.*)'.format(key), tmp)
+ return tmp[0]
class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
def setUp(self):
@@ -53,6 +61,85 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
else:
self.assertTrue(False)
+ def test_hostapd_config(self):
+ """ Check if hostapd config is properly generated """
+
+ # Only set the hostapd (access-point) options
+ interface = 'wlan0'
+ phy = 'phy0'
+ ssid = 'ssid'
+ channel = '1'
+
+ self.session.set(self._base_path + [interface, 'physical-device', phy])
+ self.session.set(self._base_path + [interface, 'ssid', ssid])
+ self.session.set(self._base_path + [interface, 'type', 'access-point'])
+ self.session.set(self._base_path + [interface, 'channel', channel])
+ # auto-powersave is special
+ self.session.set(self._base_path + [interface, 'capabilities', 'ht', 'auto-powersave'])
+
+ ht_opt = {
+ # VyOS CLI option hostapd - ht_capab setting
+ '40mhz-incapable' : '[40-INTOLERANT]',
+ 'delayed-block-ack' : '[DELAYED-BA]',
+ 'greenfield' : '[GF]',
+ 'ldpc' : '[LDPC]',
+ 'lsig-protection' : '[LSIG-TXOP-PROT]',
+ 'channel-set-width ht40+' : '[HT40+]',
+ 'stbc tx' : '[TX-STBC]',
+ 'stbc rx 123' : '[RX-STBC-123]',
+ 'max-amsdu 7935' : '[MAX-AMSDU-7935]',
+ 'smps static' : '[SMPS-STATIC]',
+ }
+ for key in ht_opt:
+ self.session.set(self._base_path + [interface, 'capabilities', 'ht'] + key.split())
+
+ vht_opt = {
+ # VyOS CLI option hostapd - ht_capab setting
+ 'stbc tx' : '[TX-STBC-2BY1]',
+ 'stbc rx 12' : '[RX-STBC-12]',
+ 'ldpc' : '[RXLDPC]',
+ 'tx-powersave' : '[VHT-TXOP-PS]',
+ 'vht-cf' : '[HTC-VHT]',
+ 'antenna-pattern-fixed' : '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]',
+ 'max-mpdu 11454' : '[MAX-MPDU-11454]',
+ 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2][VHT160]',
+ 'link-adaptation both' : '[VHT-LINK-ADAPT3]',
+ 'short-gi 80' : '[SHORT-GI-80]',
+ 'short-gi 160' : '[SHORT-GI-160]',
+ }
+ for key in vht_opt:
+ self.session.set(self._base_path + [interface, 'capabilities', 'vht'] + key.split())
+
+ self.session.commit()
+
+ #
+ # Validate Config
+ #
+
+ # ssid
+ tmp = get_config_value(interface, 'ssid')
+ self.assertEqual(ssid, tmp)
+
+ # channel
+ tmp = get_config_value(interface, 'channel')
+ self.assertEqual(channel, tmp)
+
+ # auto-powersave is special
+ tmp = get_config_value(interface, 'uapsd_advertisement_enabled')
+ self.assertEqual('1', tmp)
+
+ tmp = get_config_value(interface, 'ht_capab')
+ for key, value in ht_opt.items():
+ self.assertIn(value, tmp)
+
+ tmp = get_config_value(interface, 'vht_capab')
+ for key, value in vht_opt.items():
+ self.assertIn(value, tmp)
+
+ # Check for running process
+ self.assertIn('hostapd', (p.name() for p in process_iter()))
+
+
if __name__ == '__main__':
check_kmod('mac80211_hwsim')
unittest.main()
diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py
index 1038b8775..79850fe44 100755
--- a/smoketest/scripts/cli/test_service_ssh.py
+++ b/smoketest/scripts/cli/test_service_ssh.py
@@ -27,7 +27,7 @@ base_path = ['service', 'ssh']
def get_config_value(key):
tmp = read_file(SSHD_CONF)
- tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp)
+ tmp = re.findall(f'\n?{key}\s+(.*)', tmp)
return tmp
def is_service_running():
diff --git a/smoketest/scripts/cli/test_system_acceleration_qat.py b/smoketest/scripts/cli/test_system_acceleration_qat.py
new file mode 100755
index 000000000..c937c810e
--- /dev/null
+++ b/smoketest/scripts/cli/test_system_acceleration_qat.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 Francois Mertz fireboxled@gmail.com
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import unittest
+
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
+
+base_path = ['system', 'acceleration', 'qat']
+
+class TestSystemLCD(unittest.TestCase):
+ def setUp(self):
+ self.session = ConfigSession(os.getpid())
+
+ def tearDown(self):
+ self.session.delete(base_path)
+ self.session.commit()
+ del self.session
+
+ def test_basic(self):
+ """ Check if configuration script is in place and that the config
+ script throws an error as QAT device is not present in Qemu. This *must*
+ be extended with QAT autodetection once run on a QAT enabled device """
+
+ # configure some system display
+ self.session.set(base_path)
+
+ # An error must be thrown if QAT device could not be found
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py
index 3c4b1fa28..48ae78ccf 100755
--- a/smoketest/scripts/cli/test_system_login.py
+++ b/smoketest/scripts/cli/test_system_login.py
@@ -16,11 +16,15 @@
import os
import re
+import platform
import unittest
+from platform import release as kernel_version
from subprocess import Popen, PIPE
-from vyos.configsession import ConfigSession, ConfigSessionError
-import vyos.util as util
+
+from vyos.configsession import ConfigSession
+from vyos.util import cmd
+from vyos.util import read_file
base_path = ['system', 'login']
users = ['vyos1', 'vyos2']
@@ -37,7 +41,7 @@ class TestSystemLogin(unittest.TestCase):
self.session.commit()
del self.session
- def test_user(self):
+ def test_local_user(self):
""" Check if user can be created and we can SSH to localhost """
self.session.set(['service', 'ssh', 'port', '22'])
@@ -63,5 +67,65 @@ class TestSystemLogin(unittest.TestCase):
# b'Linux vyos 4.19.101-amd64-vyos #1 SMP Sun Feb 2 10:18:07 UTC 2020 x86_64 GNU/Linux\n'
self.assertTrue(len(stdout) > 40)
+ def test_radius_kernel_features(self):
+ """ T2886: RADIUS requires some Kernel options to be present """
+ kernel = platform.release()
+ kernel_config = read_file(f'/boot/config-{kernel}')
+
+ # T2886 - RADIUS authentication - check for statically compiled
+ # options (=y)
+ for option in ['CONFIG_AUDIT', 'CONFIG_HAVE_ARCH_AUDITSYSCALL',
+ 'CONFIG_AUDITSYSCALL', 'CONFIG_AUDIT_WATCH',
+ 'CONFIG_AUDIT_TREE', 'CONFIG_AUDIT_ARCH']:
+ self.assertIn(f'{option}=y', kernel_config)
+
+ def test_radius_config(self):
+ """ Verify generated RADIUS configuration files """
+
+ radius_key = 'VyOSsecretVyOS'
+ radius_server = '172.16.100.10'
+ radius_source = '127.0.0.1'
+ radius_port = '2000'
+ radius_timeout = '1'
+
+ self.session.set(base_path + ['radius', 'server', radius_server, 'key', radius_key])
+ self.session.set(base_path + ['radius', 'server', radius_server, 'port', radius_port])
+ self.session.set(base_path + ['radius', 'server', radius_server, 'timeout', radius_timeout])
+ self.session.set(base_path + ['radius', 'source-address', radius_source])
+
+ self.session.commit()
+
+ # this file must be read with higher permissions
+ pam_radius_auth_conf = cmd('sudo cat /etc/pam_radius_auth.conf')
+ tmp = re.findall(r'\n?{}:{}\s+{}\s+{}\s+{}'.format(radius_server,
+ radius_port, radius_key, radius_timeout,
+ radius_source), pam_radius_auth_conf)
+ self.assertTrue(tmp)
+
+ # required, static options
+ self.assertIn('priv-lvl 15', pam_radius_auth_conf)
+ self.assertIn('mapped_priv_user radius_priv_user', pam_radius_auth_conf)
+
+ # PAM
+ pam_common_account = read_file('/etc/pam.d/common-account')
+ self.assertIn('pam_radius_auth.so', pam_common_account)
+
+ pam_common_auth = read_file('/etc/pam.d/common-auth')
+ self.assertIn('pam_radius_auth.so', pam_common_auth)
+
+ pam_common_session = read_file('/etc/pam.d/common-session')
+ self.assertIn('pam_radius_auth.so', pam_common_session)
+
+ pam_common_session_noninteractive = read_file('/etc/pam.d/common-session-noninteractive')
+ self.assertIn('pam_radius_auth.so', pam_common_session_noninteractive)
+
+ # NSS
+ nsswitch_conf = read_file('/etc/nsswitch.conf')
+ tmp = re.findall(r'passwd:\s+mapuid\s+files\s+mapname', nsswitch_conf)
+ self.assertTrue(tmp)
+
+ tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf)
+ self.assertTrue(tmp)
+
if __name__ == '__main__':
unittest.main()
diff --git a/smoketest/scripts/cli/test_vpn_anyconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py
index dd8ab1609..d2b82d686 100755
--- a/smoketest/scripts/cli/test_vpn_anyconnect.py
+++ b/smoketest/scripts/cli/test_vpn_openconnect.py
@@ -23,16 +23,16 @@ from vyos.configsession import ConfigSession, ConfigSessionError
from vyos.util import read_file
OCSERV_CONF = '/run/ocserv/ocserv.conf'
-base_path = ['vpn', 'anyconnect']
+base_path = ['vpn', 'openconnect']
cert = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
cert_key = '/etc/ssl/private/ssl-cert-snakeoil.key'
-class TestVpnAnyconnect(unittest.TestCase):
+class TestVpnOpenconnect(unittest.TestCase):
def setUp(self):
self.session = ConfigSession(os.getpid())
def tearDown(self):
- # Delete vpn anyconnect configuration
+ # Delete vpn openconnect configuration
self.session.delete(base_path)
self.session.commit()
diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py
index e27281433..b19b90156 100755
--- a/src/completion/list_interfaces.py
+++ b/src/completion/list_interfaces.py
@@ -1,16 +1,28 @@
#!/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 sys
import argparse
from vyos.ifconfig import Section
-
def matching(feature):
for section in Section.feature(feature):
for intf in Section.interfaces(section):
yield intf
-
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-t", "--type", type=str, help="List interfaces of specific type")
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 51631dc16..53bc37882 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -127,6 +127,7 @@ def verify(conf, dns):
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']) ):
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index 57c910a68..93e995b78 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -17,14 +17,13 @@
import os
from sys import exit
-from copy import deepcopy
-from stat import S_IRUSR, S_IWUSR
from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import call
+from vyos.configdict import dict_merge
from vyos.template import render
-
+from vyos.util import call
+from vyos.xml import defaults
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -45,197 +44,101 @@ default_service_protocol = {
'zoneedit': 'zoneedit1'
}
-default_config_data = {
- 'interfaces': [],
- 'deleted': False
-}
-
def get_config(config=None):
- dyndns = deepcopy(default_config_data)
if config:
conf = config
else:
conf = Config()
- base_level = ['service', 'dns', 'dynamic']
+ base_level = ['service', 'dns', 'dynamic']
if not conf.exists(base_level):
- dyndns['deleted'] = True
- return dyndns
-
- for interface in conf.list_nodes(base_level + ['interface']):
- node = {
- 'interface': interface,
- 'rfc2136': [],
- 'service': [],
- 'web_skip': '',
- 'web_url': ''
- }
-
- # set config level to e.g. "service dns dynamic interface eth0"
- conf.set_level(base_level + ['interface', interface])
- # Handle RFC2136 - Dynamic Updates in the Domain Name System
- for rfc2136 in conf.list_nodes(['rfc2136']):
- rfc = {
- 'name': rfc2136,
- 'keyfile': '',
- 'record': [],
- 'server': '',
- 'ttl': '600',
- 'zone': ''
- }
-
- # set config level
- conf.set_level(base_level + ['interface', interface, 'rfc2136', rfc2136])
-
- if conf.exists(['key']):
- rfc['keyfile'] = conf.return_value(['key'])
-
- if conf.exists(['record']):
- rfc['record'] = conf.return_values(['record'])
-
- if conf.exists(['server']):
- rfc['server'] = conf.return_value(['server'])
-
- if conf.exists(['ttl']):
- rfc['ttl'] = conf.return_value(['ttl'])
-
- if conf.exists(['zone']):
- rfc['zone'] = conf.return_value(['zone'])
-
- node['rfc2136'].append(rfc)
-
- # set config level to e.g. "service dns dynamic interface eth0"
- conf.set_level(base_level + ['interface', interface])
- # Handle DynDNS service providers
- for service in conf.list_nodes(['service']):
- srv = {
- 'provider': service,
- 'host': [],
- 'login': '',
- 'password': '',
- 'protocol': '',
- 'server': '',
- 'custom' : False,
- 'zone' : ''
- }
-
- # set config level
- conf.set_level(base_level + ['interface', interface, 'service', service])
-
- # preload protocol from default service mapping
- if service in default_service_protocol.keys():
- srv['protocol'] = default_service_protocol[service]
- else:
- srv['custom'] = True
-
- if conf.exists(['login']):
- srv['login'] = conf.return_value(['login'])
-
- if conf.exists(['host-name']):
- srv['host'] = conf.return_values(['host-name'])
-
- if conf.exists(['protocol']):
- srv['protocol'] = conf.return_value(['protocol'])
-
- if conf.exists(['password']):
- srv['password'] = conf.return_value(['password'])
-
- if conf.exists(['server']):
- srv['server'] = conf.return_value(['server'])
-
- if conf.exists(['zone']):
- srv['zone'] = conf.return_value(['zone'])
- elif srv['provider'] == 'cloudflare':
- # default populate zone entry with bar.tld if
- # host-name is foo.bar.tld
- srv['zone'] = srv['host'][0].split('.',1)[1]
-
- node['service'].append(srv)
-
- # Set config back to appropriate level for these options
- conf.set_level(base_level + ['interface', interface])
-
- # Additional settings in CLI
- if conf.exists(['use-web', 'skip']):
- node['web_skip'] = conf.return_value(['use-web', 'skip'])
-
- if conf.exists(['use-web', 'url']):
- node['web_url'] = conf.return_value(['use-web', 'url'])
-
- # set config level back to top level
- conf.set_level(base_level)
-
- dyndns['interfaces'].append(node)
+ return None
+
+ dyndns = conf.get_config_dict(base_level, 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.
+ for interface in dyndns['interface']:
+ if 'service' in dyndns['interface'][interface]:
+ # 'Autodetect' protocol used by DynDNS service
+ for service in dyndns['interface'][interface]['service']:
+ if service in default_service_protocol:
+ dyndns['interface'][interface]['service'][service].update(
+ {'protocol' : default_service_protocol.get(service)})
+ else:
+ dyndns['interface'][interface]['service'][service].update(
+ {'custom': ''})
+
+ if 'rfc2136' in dyndns['interface'][interface]:
+ default_values = defaults(base_level + ['interface', 'rfc2136'])
+ for rfc2136 in dyndns['interface'][interface]['rfc2136']:
+ dyndns['interface'][interface]['rfc2136'][rfc2136] = dict_merge(
+ default_values, dyndns['interface'][interface]['rfc2136'][rfc2136])
return dyndns
def verify(dyndns):
# bail out early - looks like removal from running config
- if dyndns['deleted']:
+ if not dyndns:
return None
# A 'node' corresponds to an interface
- for node in dyndns['interfaces']:
+ if 'interface' not in dyndns:
+ return None
+ for interface in dyndns['interface']:
# RFC2136 - configuration validation
- for rfc2136 in node['rfc2136']:
- if not rfc2136['record']:
- raise ConfigError('Set key for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+ if 'rfc2136' in dyndns['interface'][interface]:
+ for rfc2136, config in dyndns['interface'][interface]['rfc2136'].items():
- if not rfc2136['zone']:
- raise ConfigError('Set zone for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+ for tmp in ['record', 'zone', 'server', 'key']:
+ if tmp not in config:
+ raise ConfigError(f'"{tmp}" required for rfc2136 based '
+ f'DynDNS service on "{interface}"')
- if not rfc2136['keyfile']:
- raise ConfigError('Set keyfile for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
- else:
- if not os.path.isfile(rfc2136['keyfile']):
- raise ConfigError('Keyfile for service "{0}" to send DDNS updates for interface "{1}" does not exist'.format(rfc2136['name'], node['interface']))
-
- if not rfc2136['server']:
- raise ConfigError('Set server for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+ if not os.path.isfile(config['key']):
+ raise ConfigError(f'"key"-file not found for rfc2136 based '
+ f'DynDNS service on "{interface}"')
# DynDNS service provider - configuration validation
- for service in node['service']:
- if not service['host']:
- raise ConfigError('Set host-name for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'service' in dyndns['interface'][interface]:
+ for service, config in dyndns['interface'][interface]['service'].items():
+ error_msg = f'required for DynDNS service "{service}" on "{interface}"'
+ if 'host_name' not in config:
+ raise ConfigError(f'"host-name" {error_msg}')
- if not service['login']:
- raise ConfigError('Set login for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'login' not in config:
+ raise ConfigError(f'"login" (username) {error_msg}')
- if not service['password']:
- raise ConfigError('Set password for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'password' not in config:
+ raise ConfigError(f'"password" {error_msg}')
- if service['custom'] is True:
- if not service['protocol']:
- raise ConfigError('Set protocol for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'zone' in config:
+ if service != 'cloudflare':
+ raise ConfigError(f'"zone" option only supported with CloudFlare')
- if not service['server']:
- raise ConfigError('Set server for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+ if 'custom' in config:
+ if 'protocol' not in config:
+ raise ConfigError(f'"protocol" {error_msg}')
- if service['zone']:
- if service['provider'] != 'cloudflare':
- raise ConfigError('Zone option not allowed for "{0}", it can only be used for CloudFlare'.format(service['provider']))
+ if 'server' not in config:
+ raise ConfigError(f'"server" {error_msg}')
return None
def generate(dyndns):
# bail out early - looks like removal from running config
- if dyndns['deleted']:
+ if not dyndns:
return None
- render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns)
-
- # Config file must be accessible only by its owner
- os.chmod(config_file, S_IRUSR | S_IWUSR)
-
+ render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns, trim_blocks=True, permission=0o600)
return None
def apply(dyndns):
- if dyndns['deleted']:
+ if not dyndns:
call('systemctl stop ddclient.service')
if os.path.exists(config_file):
os.unlink(config_file)
-
else:
call('systemctl restart ddclient.service')
diff --git a/src/conf_mode/intel_qat.py b/src/conf_mode/intel_qat.py
index 1e5101a9f..86dbccaf0 100755
--- a/src/conf_mode/intel_qat.py
+++ b/src/conf_mode/intel_qat.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# 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
@@ -13,94 +13,87 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-#
-import sys
import os
import re
+from sys import exit
+
from vyos.config import Config
-from vyos import ConfigError
from vyos.util import popen, run
-
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
-# Define for recovering
-gl_ipsec_conf = None
+qat_init_script = '/etc/init.d/qat_service'
def get_config(config=None):
- if config:
- c = config
- else:
- c = Config()
- config_data = {
- 'qat_conf' : None,
- 'ipsec_conf' : None,
- 'openvpn_conf' : None,
- }
-
- if c.exists('system acceleration qat'):
- config_data['qat_conf'] = True
-
- if c.exists('vpn ipsec '):
- gl_ipsec_conf = True
- config_data['ipsec_conf'] = True
-
- if c.exists('interfaces openvpn'):
- config_data['openvpn_conf'] = True
-
- return config_data
-
-# Control configured VPN service which can use QAT
-def vpn_control(action):
- # XXX: Should these commands report failure
- if action == 'restore' and gl_ipsec_conf:
- return run('ipsec start')
- return run(f'ipsec {action}')
-
-def verify(c):
- # Check if QAT service installed
- if not os.path.exists('/etc/init.d/qat_service'):
- raise ConfigError("Warning: QAT init file not found")
-
- if c['qat_conf'] == None:
- return
-
- # Check if QAT device exist
- output, err = popen('lspci -nn', decode='utf-8')
- if not err:
- data = re.findall('(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)', output)
- #If QAT devices found
- if not data:
- print("\t No QAT acceleration device found")
- sys.exit(1)
-
-def apply(c):
- if c['ipsec_conf']:
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ data = {}
+
+ if conf.exists(['system', 'acceleration', 'qat']):
+ data.update({'qat_enable' : ''})
+
+ if conf.exists(['vpn', 'ipsec']):
+ data.update({'ipsec' : ''})
+
+ if conf.exists(['interfaces', 'openvpn']):
+ data.update({'openvpn' : ''})
+
+ return data
+
+
+def vpn_control(action, force_ipsec=False):
+ # XXX: Should these commands report failure?
+ if action == 'restore' and force_ipsec:
+ return run('ipsec start')
+
+ return run(f'ipsec {action}')
+
+
+def verify(qat):
+ if 'qat_enable' not in qat:
+ return
+
+ # Check if QAT service installed
+ if not os.path.exists(qat_init_script):
+ raise ConfigError('QAT init script not found')
+
+ # Check if QAT device exist
+ output, err = popen('lspci -nn', decode='utf-8')
+ if not err:
+ data = re.findall(
+ '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)', output)
+ # If QAT devices found
+ if not data:
+ raise ConfigError('No QAT acceleration device found')
+
+def apply(qat):
# Shutdown VPN service which can use QAT
- vpn_control('stop')
+ if 'ipsec' in qat:
+ vpn_control('stop')
+
+ # Enable/Disable QAT service
+ if 'qat_enable' in qat:
+ run(f'{qat_init_script} start')
+ else:
+ run(f'{qat_init_script} stop')
- # Disable QAT service
- if c['qat_conf'] == None:
- run('/etc/init.d/qat_service stop')
- if c['ipsec_conf']:
- vpn_control('start')
- return
+ # Recover VPN service
+ if 'ipsec' in qat:
+ vpn_control('start')
- # Run qat init.d script
- run('/etc/init.d/qat_service start')
- if c['ipsec_conf']:
- # Recovery VPN service
- vpn_control('start')
if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- apply(c)
- except ConfigError as e:
- print(e)
- vpn_control('restore')
- sys.exit(1)
+ try:
+ c = get_config()
+ verify(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ vpn_control('restore', force_ipsec=('ipsec' in c))
+ exit(1)
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index 16e6e4f6e..a9679b47c 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -29,6 +29,7 @@ from vyos.configverify import verify_source_interface
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.validate import has_address_configured
from vyos import ConfigError
@@ -69,31 +70,33 @@ def get_config(config=None):
# into a dictionary - we will use this to add additional information
# later on for wach member
if 'member' in bond and 'interface' in bond['member']:
- # first convert it to a list if only one member is given
- if isinstance(bond['member']['interface'], str):
- bond['member']['interface'] = [bond['member']['interface']]
-
- tmp={}
- for interface in bond['member']['interface']:
- tmp.update({interface: {}})
-
- bond['member']['interface'] = tmp
+ # convert list if member interfaces to a dictionary
+ bond['member']['interface'] = dict.fromkeys(
+ bond['member']['interface'], {})
if 'mode' in bond:
bond['mode'] = get_bond_mode(bond['mode'])
tmp = leaf_node_changed(conf, ['mode'])
- if tmp:
- bond.update({'shutdown_required': ''})
+ if tmp: bond.update({'shutdown_required': {}})
# determine which members have been removed
- tmp = leaf_node_changed(conf, ['member', 'interface'])
- if tmp:
- bond.update({'shutdown_required': ''})
- if 'member' in bond:
- bond['member'].update({'interface_remove': tmp })
- else:
- bond.update({'member': {'interface_remove': tmp }})
+ interfaces_removed = leaf_node_changed(conf, ['member', 'interface'])
+ if interfaces_removed:
+ bond.update({'shutdown_required': {}})
+ if 'member' not in bond:
+ bond.update({'member': {}})
+
+ tmp = {}
+ for interface in interfaces_removed:
+ section = Section.section(interface) # this will be 'ethernet' for 'eth0'
+ if conf.exists(['insterfaces', section, interface, 'disable']):
+ tmp.update({interface : {'disable': ''}})
+ else:
+ tmp.update({interface : {}})
+
+ # 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']:
for interface, interface_config in bond['member']['interface'].items():
@@ -109,8 +112,7 @@ def get_config(config=None):
# bond 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 bond
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 8250a3df8..144cee5fe 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -56,10 +56,11 @@ def get_config(config=None):
# To delete an l2tpv3 interface we need the current tunnel and session-id
if 'deleted' in l2tpv3:
tmp = leaf_node_changed(conf, ['tunnel-id'])
- l2tpv3.update({'tunnel_id': tmp})
+ # leaf_node_changed() returns a list
+ l2tpv3.update({'tunnel_id': tmp[0]})
tmp = leaf_node_changed(conf, ['session-id'])
- l2tpv3.update({'session_id': tmp})
+ l2tpv3.update({'session_id': tmp[0]})
return l2tpv3
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 9861f72db..c6c843e7b 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -33,6 +33,7 @@ from vyos.configverify import verify_vrf
from vyos.ifconfig import WiFiIf
from vyos.template import render
from vyos.util import call
+from vyos.util import vyos_dict_search
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -213,6 +214,11 @@ def generate(wifi):
mac.dialect = mac_unix_expanded
wifi['mac'] = str(mac)
+ # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number
+ if '40mhz_incapable' in (vyos_dict_search('capabilities.ht', wifi) or []):
+ wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable']
+ del wifi['capabilities']['ht']['40mhz_incapable']
+
# render appropriate new config files depending on access-point or station mode
if wifi['type'] == 'access-point':
render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi, trim_blocks=True)
diff --git a/src/conf_mode/vpn_anyconnect.py b/src/conf_mode/vpn_openconnect.py
index 158e1a117..af8604972 100755
--- a/src/conf_mode/vpn_anyconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -42,7 +42,7 @@ def get_hash(password):
def get_config():
conf = Config()
- base = ['vpn', 'anyconnect']
+ base = ['vpn', 'openconnect']
if not conf.exists(base):
return None
@@ -61,24 +61,24 @@ def verify(ocserv):
if "mode" in ocserv["authentication"]:
if "local" in ocserv["authentication"]["mode"]:
if not ocserv["authentication"]["local_users"] or not ocserv["authentication"]["local_users"]["username"]:
- raise ConfigError('Anyconect mode local required at leat one user')
+ raise ConfigError('openconnect mode local required at leat one user')
else:
for user in ocserv["authentication"]["local_users"]["username"]:
if not "password" in ocserv["authentication"]["local_users"]["username"][user]:
raise ConfigError(f'password required for user {user}')
else:
- raise ConfigError('anyconnect authentication mode required')
+ raise ConfigError('openconnect authentication mode required')
else:
- raise ConfigError('anyconnect authentication credentials required')
+ raise ConfigError('openconnect authentication credentials required')
# Check ssl
if "ssl" in ocserv:
req_cert = ['ca_cert_file', 'cert_file', 'key_file']
for cert in req_cert:
if not cert in ocserv["ssl"]:
- raise ConfigError('anyconnect ssl {0} required'.format(cert.replace('_', '-')))
+ raise ConfigError('openconnect ssl {0} required'.format(cert.replace('_', '-')))
else:
- raise ConfigError('anyconnect ssl required')
+ raise ConfigError('openconnect ssl required')
# Check network settings
if "network_settings" in ocserv:
@@ -90,7 +90,7 @@ def verify(ocserv):
else:
ocserv["network_settings"]["push_route"] = "default"
else:
- raise ConfigError('anyconnect network settings required')
+ raise ConfigError('openconnect network settings required')
def generate(ocserv):
diff --git a/src/op_mode/force_mtu_host.sh b/src/op_mode/force_mtu_host.sh
new file mode 100755
index 000000000..02955c729
--- /dev/null
+++ b/src/op_mode/force_mtu_host.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+#
+# Module: vyos-show-ram.sh
+# Displays memory usage information in minimalistic format
+#
+# 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 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/>.
+
+target=$1
+interface=$2
+
+# IPv4 header 20 byte + TCP header 20 byte
+ipv4_overhead=40
+
+# IPv6 headter 40 byte + TCP header 20 byte
+ipv6_overhead=60
+
+# If no arguments
+if [[ $# -eq 0 ]] ; then
+ echo "Target host not defined"
+ exit 1
+fi
+
+# If one argument, it's ip address. If 2, the second arg "interface"
+if [[ $# -eq 1 ]] ; then
+ mtu=$(sudo nmap -T4 --script path-mtu -F $target | grep "PMTU" | awk {'print $NF'})
+elif [[ $# -eq 2 ]]; then
+ mtu=$(sudo nmap -T4 -e $interface --script path-mtu -F $target | grep "PMTU" | awk {'print $NF'})
+fi
+
+tcpv4_mss=$(($mtu-$ipv4_overhead))
+tcpv6_mss=$(($mtu-$ipv6_overhead))
+
+echo "
+Recommended maximum values (or less) for target $target:
+---
+MTU: $mtu
+TCP-MSS: $tcpv4_mss
+TCP-MSS_IPv6: $tcpv6_mss
+"
+
diff --git a/src/op_mode/monitor_bandwidth_test.sh b/src/op_mode/monitor_bandwidth_test.sh
new file mode 100755
index 000000000..6da0291c5
--- /dev/null
+++ b/src/op_mode/monitor_bandwidth_test.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# 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/>.
+
+if ipaddrcheck --is-ipv6 $1; then
+ # Set address family to IPv6 when an IPv6 address was specified
+ OPT="-V"
+elif [[ $(dig $1 AAAA +short | grep -v '\.$' | wc -l) -gt 0 ]]; then
+ # CNAME is also part of the dig answer thus we must remove any
+ # CNAME response and only shot the AAAA response(s), this is done
+ # by grep -v '\.$'
+
+ # Set address family to IPv6 when FQDN has at least one AAAA record
+ OPT="-V"
+fi
+
+/usr/bin/iperf $OPT -c $1
+
diff --git a/src/op_mode/anyconnect-control.py b/src/op_mode/openconnect-control.py
index 6382016b7..ef9fe618c 100755
--- a/src/op_mode/anyconnect-control.py
+++ b/src/op_mode/openconnect-control.py
@@ -28,7 +28,7 @@ occtl_socket = '/run/ocserv/occtl.socket'
def show_sessions():
out, code = popen("sudo {0} -j -s {1} show users".format(occtl, occtl_socket),stderr=DEVNULL)
if code:
- sys.exit('Cannot get anyconnect users information')
+ sys.exit('Cannot get openconnect users information')
else:
headers = ["interface", "username", "ip", "remote IP", "RX", "TX", "state", "uptime"]
sessions = json.loads(out)
@@ -38,11 +38,11 @@ def show_sessions():
if len(ses_list) > 0:
print(tabulate(ses_list, headers))
else:
- print("No active anyconnect sessions")
+ print("No active openconnect sessions")
def is_ocserv_configured():
- if not Config().exists_effective('vpn anyconnect'):
- print("vpn anyconnect server is not configured")
+ if not Config().exists_effective('vpn openconnect'):
+ print("vpn openconnect server is not configured")
sys.exit(1)
def main():
@@ -54,7 +54,7 @@ def main():
args = parser.parse_args()
- # Check is IPoE configured
+ # Check is Openconnect server configured
is_ocserv_configured()
if args.action == "restart":
diff --git a/src/op_mode/show_version.py b/src/op_mode/show_version.py
index d0d5c6785..5bbc2e1f1 100755
--- a/src/op_mode/show_version.py
+++ b/src/op_mode/show_version.py
@@ -27,7 +27,6 @@ from sys import exit
from vyos.util import call
parser = argparse.ArgumentParser()
-parser.add_argument("-a", "--all", action="store_true", help="Include individual package versions")
parser.add_argument("-f", "--funny", action="store_true", help="Add something funny to the output")
parser.add_argument("-j", "--json", action="store_true", help="Produce JSON output")
@@ -65,9 +64,5 @@ if __name__ == '__main__':
tmpl = Template(version_output_tmpl)
print(tmpl.render(version_data))
- if args.all:
- print("Package versions:")
- call("dpkg -l")
-
if args.funny:
print(vyos.limericks.get_random())
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index 75f84d3df..642952936 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -62,6 +62,8 @@ configd_env_file = '/etc/default/vyos-configd-env'
active_string = ''
session_string = ''
+session_tty = None
+
def key_name_from_file_name(f):
return os.path.splitext(f)[0]
@@ -105,6 +107,14 @@ conf_mode_scripts = dict(zip(imports, modules))
exclude_set = {key_name_from_file_name(f) for f in filenames if f not in include}
include_set = {key_name_from_file_name(f) for f in filenames if f in include}
+def explicit_print(t, m):
+ try:
+ with open(t, 'w') as f:
+ f.write(m)
+ f.write("\n")
+ f.flush()
+ except Exception:
+ pass
def run_script(script, config) -> int:
config.set_level([])
@@ -115,6 +125,7 @@ def run_script(script, config) -> int:
script.apply(c)
except ConfigError as e:
logger.critical(e)
+ explicit_print(session_tty, str(e))
return R_ERROR_COMMIT
except Exception:
return R_ERROR_DAEMON
@@ -122,6 +133,7 @@ def run_script(script, config) -> int:
return R_SUCCESS
def initialization(socket):
+ global session_tty
# Reset config strings:
active_string = ''
session_string = ''
@@ -132,6 +144,15 @@ def initialization(socket):
session_string = socket.recv().decode()
resp = "session"
socket.send(resp.encode())
+ pid_string = socket.recv().decode()
+ resp = "pid"
+ socket.send(resp.encode())
+
+ logger.debug(f"config session pid is {pid_string}")
+ try:
+ session_tty = os.readlink(f"/proc/{pid_string}/fd/1")
+ except FileNotFoundError:
+ session_tty = None
configsource = ConfigSourceString(running_config_text=active_string,
session_config_text=session_string)
diff --git a/src/shim/.gitignore b/src/shim/.gitignore
new file mode 100644
index 000000000..d33538138
--- /dev/null
+++ b/src/shim/.gitignore
@@ -0,0 +1,2 @@
+/mkjson/obj/
+/vyshim
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index 8b6feab99..196e3221e 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -162,6 +162,10 @@ int initialization(void* Requester)
double prev_time_value, time_value;
double time_diff;
+ char *pid_val = getenv("VYATTA_CONFIG_TMP");
+ strsep(&pid_val, "_");
+ debug_print("config session pid: %s\n", pid_val);
+
debug_print("Sending init announcement\n");
char *init_announce = mkjson(MKJSON_OBJ, 1,
MKJSON_STRING, "type", "init");
@@ -219,6 +223,12 @@ int initialization(void* Requester)
free(session_str);
+ debug_print("Sending config session pid\n");
+ zmq_send(Requester, pid_val, strlen(pid_val), 0);
+ zmq_recv(Requester, buffer, 16, 0);
+ debug_print("Received pid receipt\n");
+
+
return 0;
}