diff options
author | Marcus Hoff <marcus.hoff@ring2.dk> | 2020-09-20 11:59:32 +0200 |
---|---|---|
committer | Marcus Hoff <marcus.hoff@ring2.dk> | 2020-09-20 11:59:32 +0200 |
commit | 45b30adfaaec7065f768d04085138a75a76ed376 (patch) | |
tree | a9cd47236468077141eee56068ba23027b0d4c7d | |
parent | 46fb580fa0131f6815bbcfc95631654f6fe999a8 (diff) | |
parent | e0797331774a02ca23e8363fbcfe5a49fb3ca2bd (diff) | |
download | vyos-1x-45b30adfaaec7065f768d04085138a75a76ed376.tar.gz vyos-1x-45b30adfaaec7065f768d04085138a75a76ed376.zip |
Merge remote-tracking branch 'upstream/current' into current
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><0-16></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><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></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><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></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; } |