diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/completion/list_wireless_phys.sh | 5 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-wireless.py | 1471 | ||||
| -rwxr-xr-x | src/conf_mode/system-wifi-regdom.py | 111 | ||||
| -rwxr-xr-x | src/migration-scripts/interfaces/3-to-4 | 97 | ||||
| -rwxr-xr-x | src/op_mode/show_wireless.py | 158 | 
5 files changed, 1842 insertions, 0 deletions
| diff --git a/src/completion/list_wireless_phys.sh b/src/completion/list_wireless_phys.sh new file mode 100755 index 000000000..70b8d1ff9 --- /dev/null +++ b/src/completion/list_wireless_phys.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ -d /sys/class/ieee80211 ]; then +    ls -x /sys/class/ieee80211 +fi diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py new file mode 100755 index 000000000..17b0876a0 --- /dev/null +++ b/src/conf_mode/interfaces-wireless.py @@ -0,0 +1,1471 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import os + +from jinja2 import Template +from copy import deepcopy +from sys import exit +from stat import S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH +from pwd import getpwnam +from grp import getgrnam + +from subprocess import Popen, PIPE +from psutil import pid_exists + +from vyos.ifconfig import EthernetIf, VLANIf +from vyos.configdict import list_diff, vlan_to_dict +from vyos.config import Config +from vyos import ConfigError + +user = 'root' +group = 'vyattacfg' + +# Please be careful if you edit the template. +config_hostapd_tmpl = """ +### Autogenerated by interfaces-wireless.py ### +{% if description %} +# Description: {{ description }} +# User-friendly description of device; up to 32 octets encoded in UTF-8 +device_name={{ description | truncate(32, True) }} +{% endif %} + +# AP netdevice name (without 'ap' postfix, i.e., wlan0 uses wlan0ap for +# management frames with the Host AP driver); wlan0 with many nl80211 drivers +# Note: This attribute can be overridden by the values supplied with the '-i' +# command line parameter. +interface={{ intf }} + +# Driver interface type (hostap/wired/none/nl80211/bsd); +# default: hostap). nl80211 is used with all Linux mac80211 drivers. +# Use driver=none if building hostapd as a standalone RADIUS server that does +# not control any wireless/wired driver. +driver=nl80211 + +# +# What about bridge? +#   bridge=br0 +#   wds_sta=1 +# + +# Levels (minimum value for logged events): +#  0 = verbose debugging +#  1 = debugging +#  2 = informational messages +#  3 = notification +#  4 = warning +logger_syslog=-1 +logger_syslog_level=0 +logger_stdout=-1 +logger_stdout_level=0 + +{%- if country_code %} + +# Country code (ISO/IEC 3166-1). Used to set regulatory domain. +# Set as needed to indicate country in which device is operating. +# This can limit available channels and transmit power. +country_code={{ country_code }} + +# Enable IEEE 802.11d. This advertises the country_code and the set of allowed +# channels and transmit power levels based on the regulatory limits. The +# country_code setting must be configured with the correct country for +# IEEE 802.11d functions. +ieee80211d=1 +{% endif %} + +{%- if ssid %} + +# SSID to be used in IEEE 802.11 management frames +ssid={{ ssid }} +{% endif %} + +{%- if channel %} + +# Channel number (IEEE 802.11) +# (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 %} + +{%- if mode %} + +# Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz), +# g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used +# with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this +# needs to be set to hw_mode=a. For IEEE 802.11ax (HE) on 6 GHz this needs +# to be set to hw_mode=a. When using ACS (see channel parameter), a +# special value "any" can be used to indicate that any support band can be used. +# This special case is currently supported only with drivers with which +# offloaded ACS is used. +{% if 'n' in mode -%} +hw_mode=g +ieee80211n=1 +{% elif 'ac' in mode -%} +hw_mode=a +ieee80211h=1 +ieee80211ac=1 +{% else -%} +hw_mode={{ mode }} +{% endif %} +{% endif %} + +# ieee80211w: Whether management frame protection (MFP) is enabled +# 0 = disabled (default) +# 1 = optional +# 2 = required +{% if 'disabled' in mgmt_frame_protection -%} +ieee80211w=0 +{% elif 'optional' in mgmt_frame_protection -%} +ieee80211w=1 +{% elif 'required' in mgmt_frame_protection -%} +ieee80211w=2 +{% endif %} + +# ht_capab: HT capabilities (list of flags) +# LDPC coding capability: [LDPC] = supported +# Supported channel width set: [HT40-] = both 20 MHz and 40 MHz with secondary +#	channel below the primary channel; [HT40+] = both 20 MHz and 40 MHz +#	with secondary channel above the primary channel +#	(20 MHz only if neither is set) +#	Note: There are limits on which channels can be used with HT40- and +#	HT40+. Following table shows the channels that may be available for +#	HT40- and HT40+ use per IEEE 802.11n Annex J: +#	freq		HT40-		HT40+ +#	2.4 GHz		5-13		1-7 (1-9 in Europe/Japan) +#	5 GHz		40,48,56,64	36,44,52,60 +#	(depending on the location, not all of these channels may be available +#	for use) +#	Please note that 40 MHz channels may switch their primary and secondary +#	channels if needed or creation of 40 MHz channel maybe rejected based +#	on overlapping BSSes. These changes are done automatically when hostapd +#	is setting up the 40 MHz channel. +# Spatial Multiplexing (SM) Power Save: [SMPS-STATIC] or [SMPS-DYNAMIC] +#	(SMPS disabled if neither is set) +# HT-greenfield: [GF] (disabled if not set) +# Short GI for 20 MHz: [SHORT-GI-20] (disabled if not set) +# Short GI for 40 MHz: [SHORT-GI-40] (disabled if not set) +# Tx STBC: [TX-STBC] (disabled if not set) +# Rx STBC: [RX-STBC1] (one spatial stream), [RX-STBC12] (one or two spatial +#	streams), or [RX-STBC123] (one, two, or three spatial streams); Rx STBC +#	disabled if none of these set +# HT-delayed Block Ack: [DELAYED-BA] (disabled if not set) +# Maximum A-MSDU length: [MAX-AMSDU-7935] for 7935 octets (3839 octets if not +#	set) +# 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) +{% if cap_ht %} +ht_capab= +{%- endif -%} + +{%- if cap_ht_40mhz_incapable -%} +[40-INTOLERANT] +{%- endif -%} + +{%- if cap_ht_delayed_block_ack -%} +[DELAYED-BA] +{%- endif -%} + +{%- if cap_ht_dsss_cck_40 -%} +[DSSS_CCK-40] +{%- endif -%} + +{%- if cap_ht_greenfield -%} +[GF] +{%- endif -%} + +{%- if cap_ht_ldpc -%} +[LDPC] +{%- endif -%} + +{%- if cap_ht_lsig_protection -%} +[LSIG-TXOP-PROT] +{%- endif -%} + +{%- if cap_ht_max_amsdu -%} +[MAX-AMSDU-{{ cap_ht_max_amsdu }}] +{%- endif -%} + +{%- if cap_ht_smps -%} +[SMPS-{{ cap_ht_smps | upper }}] +{%- endif -%} + +{%- if cap_ht_chan_set_width -%} +{%- for csw in cap_ht_chan_set_width -%} +[{{ csw | upper }}] +{%- endfor -%} +{%- endif -%} + +{%- if cap_ht_short_gi -%} +{%- for gi in cap_ht_short_gi -%} +[SHORT-GI-{{ gi }}] +{%- endfor -%} +{%- endif -%} + +{%- if cap_ht_stbc_tx -%} +[TX-STBC] +{%- endif -%} +{%- if cap_ht_stbc_rx -%} +[RX-STBC{{ cap_ht_stbc_rx }}] +{%- endif %} + +# Required for full HT and VHT functionality +wme_enabled=1 + +{% if cap_ht_powersave -%} +# 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 %} + +{% if cap_req_ht -%} +# Require stations to support HT PHY (reject association if they do not) +require_ht=1 +{% endif %} + +# vht_capab: VHT capabilities (list of flags) +# +# vht_max_mpdu_len: [MAX-MPDU-7991] [MAX-MPDU-11454] +# Indicates maximum MPDU length +# 0 = 3895 octets (default) +# 1 = 7991 octets +# 2 = 11454 octets +# 3 = reserved +# +# supported_chan_width: [VHT160] [VHT160-80PLUS80] +# Indicates supported Channel widths +# 0 = 160 MHz & 80+80 channel widths are not supported (default) +# 1 = 160 MHz channel width is supported +# 2 = 160 MHz & 80+80 channel widths are supported +# 3 = reserved +# +# Rx LDPC coding capability: [RXLDPC] +# Indicates support for receiving LDPC coded pkts +# 0 = Not supported (default) +# 1 = Supported +# +# Short GI for 80 MHz: [SHORT-GI-80] +# Indicates short GI support for reception of packets transmitted with TXVECTOR +# params format equal to VHT and CBW = 80Mhz +# 0 = Not supported (default) +# 1 = Supported +# +# Short GI for 160 MHz: [SHORT-GI-160] +# Indicates short GI support for reception of packets transmitted with TXVECTOR +# params format equal to VHT and CBW = 160Mhz +# 0 = Not supported (default) +# 1 = Supported +# +# Tx STBC: [TX-STBC-2BY1] +# Indicates support for the transmission of at least 2x1 STBC +# 0 = Not supported (default) +# 1 = Supported +# +# Rx STBC: [RX-STBC-1] [RX-STBC-12] [RX-STBC-123] [RX-STBC-1234] +# Indicates support for the reception of PPDUs using STBC +# 0 = Not supported (default) +# 1 = support of one spatial stream +# 2 = support of one and two spatial streams +# 3 = support of one, two and three spatial streams +# 4 = support of one, two, three and four spatial streams +# 5,6,7 = reserved +# +# SU Beamformer Capable: [SU-BEAMFORMER] +# Indicates support for operation as a single user beamformer +# 0 = Not supported (default) +# 1 = Supported +# +# SU Beamformee Capable: [SU-BEAMFORMEE] +# Indicates support for operation as a single user beamformee +# 0 = Not supported (default) +# 1 = Supported +# +# Compressed Steering Number of Beamformer Antennas Supported: +# [BF-ANTENNA-2] [BF-ANTENNA-3] [BF-ANTENNA-4] +#   Beamformee's capability indicating the maximum number of beamformer +#   antennas the beamformee can support when sending compressed beamforming +#   feedback +# If SU beamformer capable, set to maximum value minus 1 +# else reserved (default) +# +# Number of Sounding Dimensions: +# [SOUNDING-DIMENSION-2] [SOUNDING-DIMENSION-3] [SOUNDING-DIMENSION-4] +# Beamformer's capability indicating the maximum value of the NUM_STS parameter +# in the TXVECTOR of a VHT NDP +# If SU beamformer capable, set to maximum value minus 1 +# else reserved (default) +# +# MU Beamformer Capable: [MU-BEAMFORMER] +# Indicates support for operation as an MU beamformer +# 0 = Not supported or sent by Non-AP STA (default) +# 1 = Supported +# +# VHT TXOP PS: [VHT-TXOP-PS] +# Indicates whether or not the AP supports VHT TXOP Power Save Mode +#  or whether or not the STA is in VHT TXOP Power Save mode +# 0 = VHT AP doesn't support VHT TXOP PS mode (OR) VHT STA not in VHT TXOP PS +#  mode +# 1 = VHT AP supports VHT TXOP PS mode (OR) VHT STA is in VHT TXOP power save +#  mode +# +# +HTC-VHT Capable: [HTC-VHT] +# Indicates whether or not the STA supports receiving a VHT variant HT Control +# field. +# 0 = Not supported (default) +# 1 = supported +# +# Maximum A-MPDU Length Exponent: [MAX-A-MPDU-LEN-EXP0]..[MAX-A-MPDU-LEN-EXP7] +# Indicates the maximum length of A-MPDU pre-EOF padding that the STA can recv +# This field is an integer in the range of 0 to 7. +# The length defined by this field is equal to +# 2 pow(13 + Maximum A-MPDU Length Exponent) -1 octets +# +# VHT Link Adaptation Capable: [VHT-LINK-ADAPT2] [VHT-LINK-ADAPT3] +# Indicates whether or not the STA supports link adaptation using VHT variant +# HT Control field +# If +HTC-VHTcapable is 1 +#  0 = (no feedback) if the STA does not provide VHT MFB (default) +#  1 = reserved +#  2 = (Unsolicited) if the STA provides only unsolicited VHT MFB +#  3 = (Both) if the STA can provide VHT MFB in response to VHT MRQ and if the +#      STA provides unsolicited VHT MFB +# Reserved if +HTC-VHTcapable is 0 +# +# Rx Antenna Pattern Consistency: [RX-ANTENNA-PATTERN] +# Indicates the possibility of Rx antenna pattern change +# 0 = Rx antenna pattern might change during the lifetime of an association +# 1 = Rx antenna pattern does not change during the lifetime of an association +# +# Tx Antenna Pattern Consistency: [TX-ANTENNA-PATTERN] +# Indicates the possibility of Tx antenna pattern change +# 0 = Tx antenna pattern might change during the lifetime of an association +# 1 = Tx antenna pattern does not change during the lifetime of an association +{% if cap_vht %} +vht_capab= +{%- endif -%} + +{%- if cap_vht_max_mpdu -%} +[MAX-MPDU-{{ cap_vht_max_mpdu }}] +{%- endif -%} + +{%- if cap_vht_max_mpdu_exp -%} +[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}] +{%- endif -%} + +{%- if cap_vht_chan_set_width -%} +[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}] +{%- endif -%} + +{%- if cap_vht_chan_set_width -%} +{%- if '2' in cap_vht_chan_set_width -%} +[VHT160] +{%- elif '3' in cap_vht_chan_set_width -%} +[VHT160-80PLUS80] +{%- endif -%} +{%- endif -%} + +{%- if cap_vht_stbc_tx -%} +[TX-STBC-2BY1] +{%- endif -%} + +{%- if cap_vht_stbc_rx -%} +[RX-STBC-{{ cap_vht_stbc_rx }}] +{%- endif -%} + +{%- if cap_vht_link_adaptation -%} +{%- if 'unsolicited' in cap_vht_link_adaptation -%} +[VHT-LINK-ADAPT2] +{%- elif 'both' in cap_vht_link_adaptation -%} +[VHT-LINK-ADAPT3] +{%- endif -%} +{%- endif -%} + +{%- if cap_vht_short_gi -%} +{%- for gi in cap_vht_short_gi -%} +[SHORT-GI-{{ gi }}] +{%- endfor -%} +{%- endif -%} + +{%- if cap_vht_ldpc -%} +[RXLDPC] +{%- endif -%} + +{%- if cap_vht_tx_powersave -%} +[VHT-TXOP-PS] +{%- endif -%} + +{%- if cap_vht_vht_cf -%} +[HTC-VHT] +{%- endif -%} + +{%- if cap_vht_beamform -%} +{%- for beamform in cap_vht_beamform -%} +{%- if 'single-user-beamformer' in beamform -%} +[SU-BEAMFORMER] +{%- elif 'single-user-beamformee' in beamform -%} +[SU-BEAMFORMEE] +{%- elif 'multi-user-beamformer' in beamform -%} +[MU-BEAMFORMER] +{%- elif 'multi-user-beamformee' in beamform -%} +[MU-BEAMFORMEE] +{%- endif -%} +{%- endfor -%} +{%- endif -%} + +{%- if cap_vht_antenna_fixed -%} +[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] +{%- endif -%} + +{%- if cap_vht_antenna_cnt -%} +{%- for beamform in cap_vht_beamform -%} +{%- if 'single-user-beamformer' in beamform -%} +[BF-ANTENNA-{{ cap_vht_antenna_cnt|int -1 }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt|int -1}}] +{%- else -%} +[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}] +{%- endif -%} +{%- endfor -%} +{%- endif %} + +# ieee80211n: Whether IEEE 802.11n (HT) is enabled +# 0 = disabled (default) +# 1 = enabled +# Note: You will also need to enable WMM for full HT functionality. +# Note: hw_mode=g (2.4 GHz) and hw_mode=a (5 GHz) is used to specify the band. +{% if cap_req_vht -%} +ieee80211n=0 +# Require stations to support VHT PHY (reject association if they do not) +require_vht=1 +{% endif %} + +{% if cap_vht_center_freq_1 -%} +# 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={{ cap_vht_center_freq_1 }} +{% endif %} + +{% if cap_vht_center_freq_2 -%} +# center freq = 5 GHz + (5 * index) +# So index 159 gives center freq 5.795 GHz +# which is channel 159 in 5G band +vht_oper_centr_freq_seg1_idx={{ cap_vht_center_freq_2 }} +{% endif %} + +{% if disable_broadcast_ssid -%} +# Send empty SSID in beacons and ignore probe request frames that do not +# specify full SSID, i.e., require stations to know SSID. +# default: disabled (0) +# 1 = send empty (length=0) SSID in beacon and ignore probe request for +#     broadcast SSID +# 2 = clear SSID (ASCII 0), but keep the original length (this may be required +#     with some clients that do not support empty SSID) and ignore probe +#     requests for broadcast SSID +ignore_broadcast_ssid=1 +{% endif %} + +# Station MAC address -based authentication +# Please note that this kind of access control requires a driver that uses +# hostapd to take care of management frame processing and as such, this can be +# used with driver=hostap or driver=nl80211, but not with driver=atheros. +# 0 = accept unless in deny list +# 1 = deny unless in accept list +# 2 = use external RADIUS server (accept/deny lists are searched first) +macaddr_acl=0 + +{% if max_stations -%} +# Maximum number of stations allowed in station table. New stations will be +# rejected after the station table is full. IEEE 802.11 has a limit of 2007 +# different association IDs, so this number should not be larger than that. +# (default: 2007) +max_num_sta={{ max_stations }} +{% endif %} + +{% if isolate_stations -%} +# Client isolation can be used to prevent low-level bridging of frames between +# associated stations in the BSS. By default, this bridging is allowed. +ap_isolate=1 +{% endif %} + +{% if reduce_transmit_power -%} +# Add Power Constraint element to Beacon and Probe Response frames +# This config option adds Power Constraint element when applicable and Country +# element is added. Power Constraint element is required by Transmit Power +# Control. This can be used only with ieee80211d=1. +# Valid values are 0..255. +local_pwr_constraint={{ reduce_transmit_power }} +{% endif %} + +{% if expunge_failing_stations -%} +# Disassociate stations based on excessive transmission failures or other +# indications of connection loss. This depends on the driver capabilities and +# may not be available with all drivers. +disassoc_low_ack=1 +{% endif %} + +{% if sec_wep -%} +# IEEE 802.11 specifies two authentication algorithms. hostapd can be +# configured to allow both of these or only one. Open system authentication +# should be used with IEEE 802.1X. +# Bit fields of allowed authentication algorithms: +# bit 0 = Open System Authentication +# bit 1 = Shared Key Authentication (requires WEP) +auth_algs=2 + +# WEP rekeying (disabled if key lengths are not set or are set to 0) +# Key lengths for default/broadcast and individual/unicast keys: +# 5 = 40-bit WEP (also known as 64-bit WEP with 40 secret bits) +# 13 = 104-bit WEP (also known as 128-bit WEP with 104 secret bits) +wep_key_len_broadcast=5 +wep_key_len_unicast=5 + +# Static WEP key configuration +# +# The key number to use when transmitting. +# It must be between 0 and 3, and the corresponding key must be set. +# default: not set +wep_default_key=0 + +# The WEP keys to use. +# A key may be a quoted string or unquoted hexadecimal digits. +# The key length should be 5, 13, or 16 characters, or 10, 26, or 32 +# digits, depending on whether 40-bit (64-bit), 104-bit (128-bit), or +# 128-bit (152-bit) WEP is used. +# Only the default key must be supplied; the others are optional. +{% if sec_wep_key -%} +{% for key in sec_wep_key -%} +wep_key{{ loop.index -1 }}={{ key}} +{% endfor %} +{%- endif %} + +{% elif sec_wpa -%} +##### WPA/IEEE 802.11i configuration ########################################## + +# Enable WPA. Setting this variable configures the AP to require WPA (either +# WPA-PSK or WPA-RADIUS/EAP based on other configuration). For WPA-PSK, either +# wpa_psk or wpa_passphrase must be set and wpa_key_mgmt must include WPA-PSK. +# Instead of wpa_psk / wpa_passphrase, wpa_psk_radius might suffice. +# For WPA-RADIUS/EAP, ieee8021x must be set (but without dynamic WEP keys), +# RADIUS authentication server must be configured, and WPA-EAP must be included +# in wpa_key_mgmt. +# This field is a bit field that can be used to enable WPA (IEEE 802.11i/D3.0) +# and/or WPA2 (full IEEE 802.11i/RSN): +# bit0 = WPA +# bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled) +{% if 'both' in sec_wpa_mode -%} +wpa=3 +{%- elif 'wpa2' in sec_wpa_mode -%} +wpa=2 +{%- elif 'wpa' in sec_wpa_mode -%} +wpa=1 +{%- endif %} + +{% if sec_wpa_cipher -%} +# Set of accepted cipher suites (encryption algorithms) for pairwise keys +# (unicast packets). This is a space separated list of algorithms: +# CCMP = AES in Counter mode with CBC-MAC (CCMP-128) +# TKIP = Temporal Key Integrity Protocol +# CCMP-256 = AES in Counter mode with CBC-MAC with 256-bit key +# GCMP = Galois/counter mode protocol (GCMP-128) +# GCMP-256 = Galois/counter mode protocol with 256-bit key +# Group cipher suite (encryption algorithm for broadcast and multicast frames) +# is automatically selected based on this configuration. If only CCMP is +# allowed as the pairwise cipher, group cipher will also be CCMP. Otherwise, +# TKIP will be used as the group cipher. The optional group_cipher parameter can +# be used to override this automatic selection. +{% if 'wpa2' in sec_wpa_mode -%} +# Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) +rsn_pairwise={{ sec_wpa_cipher | join(" ") }} +{% else -%} +# Pairwise cipher for WPA (v1) (default: TKIP) +wpa_pairwise={{ sec_wpa_cipher | join(" ") }} +{%- endif -%} +{% endif %} + +{% if sec_wpa_passphrase -%} +# IEEE 802.11 specifies two authentication algorithms. hostapd can be +# configured to allow both of these or only one. Open system authentication +# should be used with IEEE 802.1X. +# Bit fields of allowed authentication algorithms: +# bit 0 = Open System Authentication +# bit 1 = Shared Key Authentication (requires WEP) +auth_algs=1 + +# WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit +# secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase +# (8..63 characters) that will be converted to PSK. This conversion uses SSID +# so the PSK changes when ASCII passphrase is used and the SSID is changed. +wpa_passphrase={{ sec_wpa_passphrase }} + +# Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The +# entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be +# added to enable SHA256-based stronger algorithms. +# WPA-PSK = WPA-Personal / WPA2-Personal +# WPA-PSK-SHA256 = WPA2-Personal using SHA256 +wpa_key_mgmt=WPA-PSK + +{% elif sec_wpa_radius -%} +##### IEEE 802.1X-2004 related configuration ################################## +# Require IEEE 802.1X authorization +ieee8021x=1 + +# Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The +# entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be +# added to enable SHA256-based stronger algorithms. +# WPA-EAP = WPA-Enterprise / WPA2-Enterprise +# WPA-EAP-SHA256 = WPA2-Enterprise using SHA256 +wpa_key_mgmt=WPA-EAP + +{% if sec_wpa_radius_source -%} +# RADIUS client forced local IP address for the access point +# Normally the local IP address is determined automatically based on configured +# IP addresses, but this field can be used to force a specific address to be +# used, e.g., when the device has multiple IP addresses. +radius_client_addr={{ sec_wpa_radius_source }} +{% endif %} + +{% for radius in sec_wpa_radius -%} +# RADIUS authentication server +auth_server_addr={{ radius.server }} +auth_server_port={{ radius.port }} +auth_server_shared_secret={{ radius.key }} +{% if radius.acc_port -%} +# RADIUS accounting server +acct_server_addr={{ radius.server }} +acct_server_port={{ radius.acc_port }} +acct_server_shared_secret={{ radius.key }} +{% endif %} +{% endfor %} + +{% endif %} + +{% else %} +# Open system +auth_algs=1 +{% endif %} + +# TX queue parameters (EDCF / bursting) +# tx_queue_<queue name>_<param> +# queues: data0, data1, data2, data3 +#		(data0 is the highest priority queue) +# parameters: +#   aifs: AIFS (default 2) +#   cwmin: cwMin (1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, +#	   16383, 32767) +#   cwmax: cwMax (same values as cwMin, cwMax >= cwMin) +#   burst: maximum length (in milliseconds with precision of up to 0.1 ms) for +#          bursting +# +# Default WMM parameters (IEEE 802.11 draft; 11-03-0504-03-000e): +# These parameters are used by the access point when transmitting frames +# to the clients. +# +# Low priority / AC_BK = background +tx_queue_data3_aifs=7 +tx_queue_data3_cwmin=15 +tx_queue_data3_cwmax=1023 +tx_queue_data3_burst=0 +# Note: for IEEE 802.11b mode: cWmin=31 cWmax=1023 burst=0 +# +# Normal priority / AC_BE = best effort +tx_queue_data2_aifs=3 +tx_queue_data2_cwmin=15 +tx_queue_data2_cwmax=63 +tx_queue_data2_burst=0 +# Note: for IEEE 802.11b mode: cWmin=31 cWmax=127 burst=0 +# +# High priority / AC_VI = video +tx_queue_data1_aifs=1 +tx_queue_data1_cwmin=7 +tx_queue_data1_cwmax=15 +tx_queue_data1_burst=3.0 +# Note: for IEEE 802.11b mode: cWmin=15 cWmax=31 burst=6.0 +# +# Highest priority / AC_VO = voice +tx_queue_data0_aifs=1 +tx_queue_data0_cwmin=3 +tx_queue_data0_cwmax=7 +tx_queue_data0_burst=1.5 + +# Default WMM parameters (IEEE 802.11 draft; 11-03-0504-03-000e): +# for 802.11a or 802.11g networks +# These parameters are sent to WMM clients when they associate. +# The parameters will be used by WMM clients for frames transmitted to the +# access point. +# +# note - txop_limit is in units of 32microseconds +# note - acm is admission control mandatory flag. 0 = admission control not +# required, 1 = mandatory +# note - Here cwMin and cmMax are in exponent form. The actual cw value used +# will be (2^n)-1 where n is the value given here. The allowed range for these +# wmm_ac_??_{cwmin,cwmax} is 0..15 with cwmax >= cwmin. +# +wmm_enabled=1 + +# Low priority / AC_BK = background +wmm_ac_bk_cwmin=4 +wmm_ac_bk_cwmax=10 +wmm_ac_bk_aifs=7 +wmm_ac_bk_txop_limit=0 +wmm_ac_bk_acm=0 +# Note: for IEEE 802.11b mode: cWmin=5 cWmax=10 +# +# Normal priority / AC_BE = best effort +wmm_ac_be_aifs=3 +wmm_ac_be_cwmin=4 +wmm_ac_be_cwmax=10 +wmm_ac_be_txop_limit=0 +wmm_ac_be_acm=0 +# Note: for IEEE 802.11b mode: cWmin=5 cWmax=7 +# +# High priority / AC_VI = video +wmm_ac_vi_aifs=2 +wmm_ac_vi_cwmin=3 +wmm_ac_vi_cwmax=4 +wmm_ac_vi_txop_limit=94 +wmm_ac_vi_acm=0 +# Note: for IEEE 802.11b mode: cWmin=4 cWmax=5 txop_limit=188 +# +# Highest priority / AC_VO = voice +wmm_ac_vo_aifs=2 +wmm_ac_vo_cwmin=2 +wmm_ac_vo_cwmax=3 +wmm_ac_vo_txop_limit=47 +wmm_ac_vo_acm=0 + +""" + +# Please be careful if you edit the template. +config_wpa_suppl_tmpl = """ +# WPA supplicant config +network={ +    ssid="{{ ssid }}" +{%- if sec_wpa_passphrase %} +    psk="{{ sec_wpa_passphrase }}" +{% endif %} +} + +""" + +default_config_data = { +    'address': [], +    'address_remove': [], +    'cap_ht' : False, +    'cap_ht_40mhz_incapable' : False, +    'cap_ht_powersave' : False, +    'cap_ht_chan_set_width' : '', +    'cap_ht_delayed_block_ack' : False, +    'cap_ht_dsss_cck_40' : False, +    'cap_ht_greenfield' : False, +    'cap_ht_ldpc' : False, +    'cap_ht_lsig_protection' : False, +    'cap_ht_max_amsdu' : '', +    'cap_ht_short_gi' :  [], +    'cap_ht_smps' : '', +    'cap_ht_stbc_rx' : '', +    'cap_ht_stbc_tx' : False, +    'cap_req_ht' : False, +    'cap_req_vht' : False, +    'cap_vht' : False, +    'cap_vht_antenna_cnt' : '', +    'cap_vht_antenna_fixed' : False, +    'cap_vht_beamform' : '', +    'cap_vht_center_freq_1' : '', +    'cap_vht_center_freq_2' : '', +    'cap_vht_chan_set_width' : '', +    'cap_vht_ldpc' : False, +    'cap_vht_link_adaptation' : False, +    'cap_vht_max_mpdu_exp' : '', +    'cap_vht_max_mpdu' : '', +    'cap_vht_short_gi' : [], +    'cap_vht_stbc_rx' : '', +    'cap_vht_stbc_tx' : False, +    'cap_vht_tx_powersave' : False, +    'cap_vht_vht_cf' : False, +    'channel': '', +    'country_code': '', +    'description': '', +    'deleted': False, +    'dhcp_client_id': '', +    'dhcp_hostname': '', +    'dhcp_vendor_class_id': '', +    'dhcpv6_prm_only': False, +    'dhcpv6_temporary': False, +    'disable': False, +    'disable_broadcast_ssid' : False, +    'disable_link_detect' : 1, +    'expunge_failing_stations' : False, +    'hw_id' : '', +    'intf': '', +    'isolate_stations' : False, +    'mac' : '', +    'max_stations' : '', +    'mgmt_frame_protection' : 'disabled', +    'mode' : 'g', +    'phy' : '', +    'reduce_transmit_power' : '', +    'sec_wep' : False, +    'sec_wep_key' : [], +    'sec_wpa' : False, +    'sec_wpa_cipher' : [], +    'sec_wpa_mode' : 'both', +    'sec_wpa_passphrase' : '', +    'sec_wpa_radius' : [], +    'ssid' : '', +    'type' : 'monitor', +    'vif': [], +    'vif_remove': [] +} + +def get_conf_file(conf_type, intf): +    cfg_dir = '/var/run/' + conf_type + +    # create directory on demand +    if not os.path.exists(cfg_dir): +        os.mkdir(cfg_dir) +        # fix permissions - corresponds to mode 755 +        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) +        uid = getpwnam(user).pw_uid +        gid = getgrnam(group).gr_gid +        os.chown(cfg_dir, uid, gid) + +    cfg_file = cfg_dir + r'/{}.cfg'.format(intf) +    return cfg_file + +def get_pid(conf_type, intf): +    cfg_dir = '/var/run/' + conf_type + +    # create directory on demand +    if not os.path.exists(cfg_dir): +        os.mkdir(cfg_dir) +        # fix permissions - corresponds to mode 755 +        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) +        uid = getpwnam(user).pw_uid +        gid = getgrnam(group).gr_gid +        os.chown(cfg_dir, uid, gid) + +    cfg_file = cfg_dir + r'/{}.pid'.format(intf) +    return cfg_file + + +def get_wpa_suppl_config_name(intf): +    cfg_dir = '/var/run/wpa_supplicant' + +    # create directory on demand +    if not os.path.exists(cfg_dir): +        os.mkdir(cfg_dir) +        # fix permissions - corresponds to mode 755 +        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) +        uid = getpwnam(user).pw_uid +        gid = getgrnam(group).gr_gid +        os.chown(cfg_dir, uid, gid) + +    cfg_file = cfg_dir + r'/{}.cfg'.format(intf) +    return cfg_file + +def subprocess_cmd(command): +    p = Popen(command, stdout=PIPE, shell=True) +    p.communicate() + + +def apply_vlan_config(vlan, config): +    """ +    Generic function to apply a VLAN configuration from a dictionary +    to a VLAN interface +    """ + +    if type(vlan) != type(VLANIf("lo")): +        raise TypeError() + +    # update interface description used e.g. within SNMP +    vlan.set_alias(config['description']) +    # ignore link state changes +    vlan.set_link_detect(config['disable_link_detect']) +    # Maximum Transmission Unit (MTU) +    vlan.set_mtu(config['mtu']) +    # Change VLAN interface MAC address +    if config['mac']: +        vlan.set_mac(config['mac']) + +    # enable/disable VLAN interface +    if config['disable']: +        vlan.set_state('down') +    else: +        vlan.set_state('up') + +    # Configure interface address(es) +    # - not longer required addresses get removed first +    # - newly addresses will be added second +    for addr in config['address_remove']: +        vlan.del_addr(addr) +    for addr in config['address']: +        vlan.add_addr(addr) + + +def get_config(): +    wifi = deepcopy(default_config_data) +    conf = Config() + +    # determine tagNode instance +    try: +        wifi['intf'] = os.environ['VYOS_TAGNODE_VALUE'] +    except KeyError as E: +        print("Interface not specified") + +    # check if wireless interface has been removed +    cfg_base = 'interfaces wireless ' + wifi['intf'] +    if not conf.exists(cfg_base): +        wifi['deleted'] = True +        # we can not bail out early as wireless interface can not be removed +        # Kernel will complain with: RTNETLINK answers: Operation not supported. +        # Thus we need to remove individual settings +        return wifi + +    # set new configuration level +    conf.set_level(cfg_base) + +    # retrieve configured interface addresses +    if conf.exists('address'): +        wifi['address'] = conf.return_values('address') + +    # get interface addresses (currently effective) - to determine which +    # address is no longer valid and needs to be removed +    eff_addr = conf.return_effective_values('address') +    wifi['address_remove'] = list_diff(eff_addr, wifi['address']) + +    # 40MHz intolerance, use 20MHz only +    if conf.exists('capabilities ht 40mhz-incapable'): +        wifi['cap_ht'] = True +        wifi['cap_ht_40mhz_incapable'] = True + +    # WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD] +    if conf.exists('capabilities ht auto-powersave'): +        wifi['cap_ht'] = True +        wifi['cap_ht_powersave'] = True + +    # Supported channel set width +    if conf.exists('capabilities ht channel-set-width'): +        wifi['cap_ht'] = True +        wifi['cap_ht_chan_set_width'] = conf.return_values('capabilities ht channel-set-width') + +    # HT-delayed Block Ack +    if conf.exists('capabilities ht delayed-block-ack'): +        wifi['cap_ht'] = True +        wifi['cap_ht_delayed_block_ack'] = True + +    # DSSS/CCK Mode in 40 MHz +    if conf.exists('capabilities ht dsss-cck-40'): +        wifi['cap_ht'] = True +        wifi['cap_ht_dsss_cck_40'] = True + +    # HT-greenfield capability +    if conf.exists('capabilities ht greenfield'): +        wifi['cap_ht'] = True +        wifi['cap_ht_greenfield'] = True + +    # LDPC coding capability +    if conf.exists('capabilities ht ldpc'): +        wifi['cap_ht'] = True +        wifi['cap_ht_ldpc'] = True + +    # L-SIG TXOP protection capability +    if conf.exists('capabilities ht lsig-protection'): +        wifi['cap_ht'] = True +        wifi['cap_ht_lsig_protection'] = True + +    # Set Maximum A-MSDU length +    if conf.exists('capabilities ht max-amsdu'): +        wifi['cap_ht'] = True +        wifi['cap_ht_max_amsdu'] = conf.return_value('capabilities ht max-amsdu') + +    # Short GI capabilities +    if conf.exists('capabilities ht short-gi'): +        wifi['cap_ht'] = True +        wifi['cap_ht_short_gi'] = conf.return_values('capabilities ht short-gi') + +    # Spatial Multiplexing Power Save (SMPS) settings +    if conf.exists('capabilities ht smps'): +        wifi['cap_ht'] = True +        wifi['cap_ht_smps'] = conf.return_value('capabilities ht smps') + +    # Support for receiving PPDU using STBC (Space Time Block Coding) +    if conf.exists('capabilities ht stbc rx'): +        wifi['cap_ht'] = True +        wifi['cap_ht_stbc_rx'] = conf.return_value('capabilities ht stbc rx') + +    # Support for sending PPDU using STBC (Space Time Block Coding) +    if conf.exists('capabilities ht stbc tx'): +        wifi['cap_ht'] = True +        wifi['cap_ht_stbc_tx'] = True + +    # Require stations to support HT PHY (reject association if they do not) +    if conf.exists('capabilities require-ht'): +        wifi['cap_req_ht'] = True + +    # Require stations to support VHT PHY (reject association if they do not) +    if conf.exists('capabilities require-vht'): +        wifi['cap_req_vht'] = True + +    # Number of antennas on this card +    if conf.exists('capabilities vht antenna-count'): +        wifi['cap_vht'] = True +        wifi['cap_vht_antenna_cnt'] = conf.return_value('capabilities vht antenna-count') + +    # set if antenna pattern does not change during the lifetime of an association +    if conf.exists('capabilities vht antenna-pattern-fixed'): +        wifi['cap_vht'] = True +        wifi['cap_vht_antenna_fixed'] = True + +    # Beamforming capabilities +    if conf.exists('capabilities vht beamform'): +        wifi['cap_vht'] = True +        wifi['cap_vht_beamform'] = conf.return_values('capabilities vht beamform') + +    # VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes) +    if conf.exists('capabilities vht center-channel-freq freq-1'): +        wifi['cap_vht'] = True +        wifi['cap_vht_center_freq_1'] = conf.return_value('capabilities vht center-channel-freq freq-1') + +    # VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode) +    if conf.exists('capabilities vht center-channel-freq freq-2'): +        wifi['cap_vht'] = True +        wifi['cap_vht_center_freq_2'] = conf.return_value('capabilities vht center-channel-freq freq-2') + +    # VHT operating Channel width +    if conf.exists('capabilities vht channel-set-width'): +        wifi['cap_vht'] = True +        wifi['cap_vht_chan_set_width'] = conf.return_value('capabilities vht channel-set-width') + +    # LDPC coding capability +    if conf.exists('capabilities vht ldpc'): +        wifi['cap_vht'] = True +        wifi['cap_vht_ldpc'] = True + +    # VHT link adaptation capabilities +    if conf.exists('capabilities vht link-adaptation'): +        wifi['cap_vht'] = True +        wifi['cap_vht_link_adaptation'] = True + +    # Set the maximum length of A-MPDU pre-EOF padding that the station can receive +    if conf.exists('capabilities vht max-mpdu-exp'): +        wifi['cap_vht'] = True +        wifi['cap_vht_max_mpdu_exp'] = conf.return_value('capabilities vht max-mpdu-exp') + +    # Increase Maximum MPDU length +    if conf.exists('capabilities vht max-mpdu'): +        wifi['cap_vht'] = True +        wifi['cap_vht_max_mpdu'] = conf.return_value('capabilities vht max-mpdu') + +    # Increase Maximum MPDU length +    if conf.exists('capabilities vht short-gi'): +        wifi['cap_vht'] = True +        wifi['cap_vht_short_gi'] = conf.return_values('capabilities vht short-gi') + +    # Support for receiving PPDU using STBC (Space Time Block Coding) +    if conf.exists('capabilities vht stbc rx'): +        wifi['cap_vht'] = True +        wifi['cap_vht_stbc_rx'] = conf.return_value('capabilities vht stbc rx') + +    # Support for the transmission of at least 2x1 STBC (Space Time Block Coding) +    if conf.exists('capabilities vht stbc tx'): +        wifi['cap_vht'] = True +        wifi['cap_vht_stbc_tx'] = True + +    # Support for VHT TXOP Power Save Mode +    if conf.exists('capabilities vht tx-powersave'): +        wifi['cap_vht'] = True +        wifi['cap_vht_tx_powersave'] = True + +    # STA supports receiving a VHT variant HT Control field +    if conf.exists('capabilities vht vht-cf'): +        wifi['cap_vht'] = True +        wifi['cap_vht_vht_cf'] = True + +    # Wireless radio channel +    if conf.exists('channel'): +        wifi['channel'] = conf.return_value('channel') + +    # retrieve interface description +    if conf.exists('description'): +        wifi['description'] = conf.return_value('description') + +    # get DHCP client identifier +    if conf.exists('dhcp-options client-id'): +        wifi['dhcp_client_id'] = conf.return_value('dhcp-options client-id') + +    # DHCP client host name (overrides the system host name) +    if conf.exists('dhcp-options host-name'): +        wifi['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + +    # DHCP client vendor identifier +    if conf.exists('dhcp-options vendor-class-id'): +        wifi['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') + +    # DHCPv6 only acquire config parameters, no address +    if conf.exists('dhcpv6-options parameters-only'): +        wifi['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only') + +    # DHCPv6 temporary IPv6 address +    if conf.exists('dhcpv6-options temporary'): +        wifi['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary') + +    # Disable broadcast of SSID from access-point +    if conf.exists('disable-broadcast-ssid'): +        wifi['disable_broadcast_ssid'] = True + +    # ignore link state changes on this interface +    if conf.exists('disable-link-detect'): +        wifi['disable_link_detect'] = 2 + +    # Disassociate stations based on excessive transmission failures +    if conf.exists('expunge-failing-stations'): +        wifi['expunge_failing_stations'] = True + +    # retrieve real hardware address +    if conf.exists('hw-id'): +        wifi['hw_id'] = conf.return_value('hw-id') + +    # Isolate stations on the AP so they cannot see each other +    if conf.exists('isolate-stations'): +        wifi['isolate_stations'] = True + +    # Media Access Control (MAC) address +    if conf.exists('mac'): +        wifi['mac'] = conf.return_value('mac') + +    # Maximum number of wireless radio stations +    if conf.exists('max-stations'): +        wifi['max_stations'] = conf.return_value('max-stations') + +    # Management Frame Protection (MFP) according to IEEE 802.11w +    if conf.exists('mgmt-frame-protection'): +        wifi['mgmt_frame_protection'] = conf.return_value('mgmt-frame-protection') + +    # Wireless radio mode +    if conf.exists('mode'): +        wifi['mode'] = conf.return_value('mode') + +    # Wireless physical device +    if conf.exists('phy'): +        wifi['phy'] = conf.return_value('phy') + +    # Transmission power reduction in dBm +    if conf.exists('reduce-transmit-power'): +        wifi['reduce_transmit_power'] = conf.return_value('reduce-transmit-power') + +    # WEP enabled? +    if conf.exists('security wep'): +        wifi['sec_wep'] = True + +    # WEP encryption key(s) +    if conf.exists('security wep key'): +        wifi['sec_wep_key'] = conf.return_values('security wep key') + +    # WPA enabled? +    if conf.exists('security wpa'): +        wifi['sec_wpa'] = True + +    # WPA Cipher suite +    if conf.exists('security wpa cipher'): +        wifi['sec_wpa_cipher'] = conf.return_values('security wpa cipher') + +    # WPA mode +    if conf.exists('security wpa mode'): +        wifi['sec_wpa_mode'] = conf.return_value('security wpa mode') + +    # WPA default ciphers depend on WPA mode +    if not wifi['sec_wpa_cipher']: +        if wifi['sec_wpa_mode'] == 'wpa': +            wifi['sec_wpa_cipher'].append('TKIP') +            wifi['sec_wpa_cipher'].append('CCMP') + +        elif wifi['sec_wpa_mode'] == 'wpa2': +            wifi['sec_wpa_cipher'].append('CCMP') + +        elif wifi['sec_wpa_mode'] == 'both': +            wifi['sec_wpa_cipher'].append('CCMP') +            wifi['sec_wpa_cipher'].append('TKIP') + +    # WPA personal shared pass phrase +    if conf.exists('security wpa passphrase'): +        wifi['sec_wpa_passphrase'] = conf.return_value('security wpa passphrase') + +    # WPA RADIUS source address +    if conf.exists('security wpa radius source-address'): +        wifi['sec_wpa_radius_source'] = conf.return_value('security wpa radius source-address') + +    # WPA RADIUS server +    for server in conf.list_nodes('security wpa radius server'): +        # set new configuration level +        conf.set_level(cfg_base + ' security wpa radius server ' + server) +        radius = { +            'server' : server, +            'acc_port' : '', +            'port' : 1812, +            'key' : '' +        } + +        # RADIUS server port +        if conf.exists('port'): +            radius['port'] = int(conf.return_value('port')) + +        # receive RADIUS accounting info +        if conf.exists('accounting'): +            radius['acc_port'] = radius['port'] + 1 + +        # RADIUS server shared-secret +        if conf.exists('key'): +            radius['key'] = conf.return_value('key') + +        # append RADIUS server to list of servers +        wifi['sec_wpa_radius'].append(radius) + +    # re-set configuration level to parse new nodes +    conf.set_level(cfg_base) + +    # Wireless access-point service set identifier (SSID) +    if conf.exists('ssid'): +        wifi['ssid'] = conf.return_value('ssid') + +    # Wireless device type for this interface +    if conf.exists('type'): +        wifi['type'] = conf.return_value('type') + +    # re-set configuration level to parse new nodes +    conf.set_level(cfg_base) +    # Determine vif interfaces (currently effective) - to determine which +    # vif interface is no longer present and needs to be removed +    eff_intf = conf.list_effective_nodes('vif') +    act_intf = conf.list_nodes('vif') +    wifi['vif_remove'] = list_diff(eff_intf, act_intf) + +    if conf.exists('vif'): +        for vif in conf.list_nodes('vif'): +            # set config level to vif interface +            conf.set_level(cfg_base + ' vif ' + vif) +            wifi['vif'].append(vlan_to_dict(conf)) + + +    # retrieve configured regulatory domain +    conf.set_level('system') +    if conf.exists('wifi-regulatory-domain'): +        wifi['country_code'] = conf.return_value('wifi-regulatory-domain') + +    return wifi + + +def verify(wifi): +    if wifi['deleted']: +        return None + +    if wifi['type'] != 'monitor' and not wifi['ssid']: +        raise ConfigError('SSID must be set for {}'.format(wifi['intf'])) + +    if wifi['type'] == 'access-point' and not wifi['channel']: +        raise ConfigError('Channel must be set for {}'.format(wifi['intf'])) + +    if len(wifi['sec_wep_key']) > 4: +        raise ConfigError('No more then 4 WEP keys configurable') + +    if wifi['cap_vht'] and not wifi['cap_ht']: +        raise ConfigError('Specify HT flags if you want to use VHT!') + +    if wifi['cap_vht_beamform'] and wifi['cap_vht_antenna_cnt'] == 1: +        raise ConfigError('Cannot use beam forming with just one antenna!') + +    if wifi['sec_wep'] and (len(wifi['sec_wep_key']) == 0): +        raise ConfigError('Missing WEP keys') + +    if wifi['sec_wpa'] and not (wifi['sec_wpa_passphrase'] or wifi['sec_wpa_radius']): +        raise ConfigError('Misssing WPA key or RADIUS server') + +    for radius in wifi['sec_wpa_radius']: +        if not radius['key']: +            raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server'])) + +    conf = Config() +    if not conf.exists('system wifi-regulatory-domain'): +        raise ConfigError('Wireless regulatory domain is mandatory.\n' \ +                          'Use "set system wifi-regulatory-domain" to set.') + +    return None + +def generate(wifi): +    pid = 0 +    # always stop hostapd service first before reconfiguring it +    pidfile = get_pid('hostapd', wifi['intf']) +    if os.path.isfile(pidfile): +        pid = 0 +        with open(pidfile, 'r') as f: +            pid = int(f.read()) + +    if pid_exists(pid): +        cmd  = 'start-stop-daemon --stop --quiet' +        cmd += ' --pidfile ' + pidfile +        subprocess_cmd(cmd) + +    # always stop wpa_supplicant service first before reconfiguring it +    pidfile = get_pid('wpa_supplicant', wifi['intf']) +    if os.path.isfile(pidfile): +        pid = 0 +        with open(pidfile, 'r') as f: +            pid = int(f.read()) + +    if pid_exists(pid): +        cmd  = 'start-stop-daemon --stop --quiet' +        cmd += ' --pidfile ' + pidfile +        subprocess_cmd(cmd) + +    # Delete config files if interface is removed +    if wifi['deleted']: +        if os.path.isfile(get_conf_file('hostapd', wifi['intf'])): +            os.unlink(get_conf_file('hostapd', wifi['intf'])) + +        if os.path.isfile(get_conf_file('wpa_supplicant', wifi['intf'])): +            os.unlink(get_conf_file('wpa_supplicant', wifi['intf'])) + +        return None + +    # render appropriate new config files depending on access-point or station mode +    if wifi['type'] == 'access-point': +        tmpl = Template(config_hostapd_tmpl) +        config_text = tmpl.render(wifi) +        with open(get_conf_file('hostapd', wifi['intf']), 'w') as f: +            f.write(config_text) + +    elif wifi['type'] == 'station': +        tmpl = Template(config_wpa_suppl_tmpl) +        config_text = tmpl.render(wifi) +        with open(get_conf_file('wpa_supplicant', wifi['intf']), 'w') as f: +            f.write(config_text) + +    return None + +def apply(wifi): +    w = EthernetIf(wifi['intf']) +    if wifi['deleted']: +        # delete interface +        w.remove() +    else: +        # Some parts e.g. MAC address can't be changed when interface is up +        w.set_state('down') + +        # update interface description used e.g. within SNMP +        w.set_alias(wifi['description']) + +        # get DHCP config dictionary and update values +        opt = w.get_dhcp_options() + +        if wifi['dhcp_client_id']: +            opt['client_id'] = wifi['dhcp_client_id'] + +        if wifi['dhcp_hostname']: +            opt['hostname'] = wifi['dhcp_hostname'] + +        if wifi['dhcp_vendor_class_id']: +            opt['vendor_class_id'] = wifi['dhcp_vendor_class_id'] + +        # store DHCP config dictionary - used later on when addresses are aquired +        w.set_dhcp_options(opt) + +        # get DHCPv6 config dictionary and update values +        opt = w.get_dhcpv6_options() + +        if wifi['dhcpv6_prm_only']: +            opt['dhcpv6_prm_only'] = True + +        if wifi['dhcpv6_temporary']: +            opt['dhcpv6_temporary'] = True + +        # store DHCPv6 config dictionary - used later on when addresses are aquired +        w.set_dhcpv6_options(opt) + +        # ignore link state changes +        w.set_link_detect(wifi['disable_link_detect']) + +        # Change interface MAC address - re-set to real hardware address (hw-id) +        # if custom mac is removed +        if wifi['mac']: +            w.set_mac(wifi['mac']) +        else: +            w.set_mac(wifi['hw_id']) + +        # enable interface +        if not wifi['disable']: +            w.set_state('up') + +        # Configure interface address(es) +        # - not longer required addresses get removed first +        # - newly addresses will be added second +        for addr in wifi['address_remove']: +            w.del_addr(addr) +        for addr in wifi['address']: +            w.add_addr(addr) + +        # remove no longer required VLAN interfaces (vif) +        for vif in wifi['vif_remove']: +            e.del_vlan(vif) + +        # create VLAN interfaces (vif) +        for vif in wifi['vif']: +            # QoS priority mapping can only be set during interface creation +            # so we delete the interface first if required. +            if vif['egress_qos_changed'] or vif['ingress_qos_changed']: +                try: +                    # on system bootup the above condition is true but the interface +                    # does not exists, which throws an exception, but that's legal +                    e.del_vlan(vif['id']) +                except: +                    pass + +            vlan = e.add_vlan(vif['id']) +            apply_vlan_config(vlan, vif) + +    # Physical interface is now configured. Proceed by starting hostapd or +    # wpa_supplicant daemon +    cmd  = 'start-stop-daemon --start --quiet' +    if wifi['type'] == 'access-point': +        cmd += ' --exec /usr/sbin/hostapd' +        # now pass arguments to hostapd binary +        cmd += ' -- -B' +        cmd += ' -P {}'.format(get_pid('hostapd', wifi['intf'])) +        cmd += ' {}'.format(get_conf_file('hostapd', wifi['intf'])) +    elif wifi['type'] == 'station': +        cmd += ' --exec /sbin/wpa_supplicant' +        # now pass arguments to hostapd binary +        cmd += ' -- -s -B -D nl80211' +        cmd += ' -P {}'.format(get_pid('wpa_supplicant', wifi['intf'])) +        cmd += ' -i {}'.format(wifi['intf']) +        cmd += ' -c {}'.format(get_conf_file('wpa_supplicant', wifi['intf'])) + +    # execute assembled command +    subprocess_cmd(cmd) + +    return None + +if __name__ == '__main__': +    try: +        c = get_config() +        verify(c) +        generate(c) +        apply(c) +    except ConfigError as e: +        print(e) +        exit(1) diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py new file mode 100755 index 000000000..01dc92a20 --- /dev/null +++ b/src/conf_mode/system-wifi-regdom.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import os +import jinja2 + +from copy import deepcopy +from sys import exit + +from vyos.config import Config +from vyos import ConfigError + +config_80211_file='/etc/modprobe.d/cfg80211.conf' +config_crda_file='/etc/default/crda' + +# Please be careful if you edit the template. +config_80211_tmpl = """ +{%- if regdom -%} +options cfg80211 ieee80211_regdom={{ regdom }} +{% endif %} +""" + +# Please be careful if you edit the template. +config_crda_tmpl = """ +{%- if regdom -%} +REGDOMAIN={{ regdom }} +{% endif %} +""" + +default_config_data = { +    'regdom' : '', +    'deleted' : False +} + + +def get_config(): +    regdom = deepcopy(default_config_data) +    conf = Config() + +    # set new configuration level +    conf.set_level('system') + +    # Check if interface has been removed +    if not conf.exists('wifi-regulatory-domain'): +        regdom['deleted'] = True +        return regdom + +    # retrieve configured regulatory domain +    if conf.exists('wifi-regulatory-domain'): +        regdom['regdom'] = conf.return_value('wifi-regulatory-domain') + +    return regdom + +def verify(regdom): +    if regdom['deleted']: +        return None + +    if not regdom['regdom']: +        raise ConfigError("Wireless regulatory domain is mandatory.") + +    return None + +def generate(regdom): +    print("Changing the wireless regulatory domain requires a system reboot.") + +    if regdom['deleted']: +        if os.path.isfile(config_80211_file): +            os.unlink(config_80211_file) + +        if os.path.isfile(config_crda_file): +            os.unlink(config_crda_file) + +        return None + +    tmpl = jinja2.Template(config_80211_tmpl) +    config_text = tmpl.render(regdom) +    with open(config_80211_file, 'w') as f: +        f.write(config_text) + +    tmpl = jinja2.Template(config_crda_tmpl) +    config_text = tmpl.render(regdom) +    with open(config_crda_file, 'w') as f: +        f.write(config_text) + +    return None + +def apply(regdom): +    return None + +if __name__ == '__main__': +    try: +        c = get_config() +        verify(c) +        generate(c) +        apply(c) +    except ConfigError as e: +        print(e) +        exit(1) diff --git a/src/migration-scripts/interfaces/3-to-4 b/src/migration-scripts/interfaces/3-to-4 new file mode 100755 index 000000000..e3bd25a68 --- /dev/null +++ b/src/migration-scripts/interfaces/3-to-4 @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# Change syntax of wireless interfaces +# Migrate boolean nodes to valueless + +import sys +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): +    print("Must specify file name!") +    sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['interfaces', 'wireless'] + +if not config.exists(base): +    # Nothing to do +    sys.exit(0) +else: +    for wifi in config.list_nodes(base): +        # as converting a node to bool is always the same, we can script it +        to_bool_nodes = ['capabilities ht 40MHz-incapable', +                         'capabilities ht auto-powersave', +                         'capabilities ht delayed-block-ack', +                         'capabilities ht dsss-cck-40', +                         'capabilities ht greenfield', +                         'capabilities ht ldpc', +                         'capabilities ht lsig-protection', +                         'capabilities ht stbc tx', +                         'capabilities require-ht', +                         'capabilities require-vht', +                         'capabilities vht antenna-pattern-fixed', +                         'capabilities vht ldpc', +                         'capabilities vht stbc tx', +                         'capabilities vht tx-powersave', +                         'capabilities vht vht-cf', +                         'expunge-failing-stations', +                         'isolate-stations'] + +        for node in to_bool_nodes: +            if config.exists(base + [wifi, node]): +                tmp = config.return_value(base + [wifi, node]) +                # delete old node +                config.delete(base + [wifi, node]) +                # set new node if it was enabled +                if tmp == 'true': +                    # OLD CLI used camel casing in 40MHz-incapable which is +                    # not supported in the new backend. Convert all to lower-case +                    config.set(base + [wifi, node.lower()]) + +        # Remove debug node +        if config.exists(base + [wifi, 'debug']): +            config.delete(base + [wifi, 'debug']) + +        # RADIUS servers +        if config.exists(base + [wifi, 'security', 'wpa', 'radius-server']): +            for server in config.list_nodes(base + [wifi, 'security', 'wpa', 'radius-server']): +                base_server = base + [wifi, 'security', 'wpa', 'radius-server', server] + +                # Migrate RADIUS shared secret +                if config.exists(base_server + ['secret']): +                    key = config.return_value(base_server + ['secret']) +                    # write new configuration node +                    config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'key'], value=key) +                    # format as tag node +                    config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) + +                # Migrate RADIUS port +                if config.exists(base_server + ['port']): +                    port = config.return_value(base_server + ['port']) +                    # write new configuration node +                    config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'port'], value=port) +                    # format as tag node +                    config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) + +                # Migrate RADIUS accounting +                if config.exists(base_server + ['accounting']): +                    port = config.return_value(base_server + ['accounting']) +                    # write new configuration node +                    config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'accounting']) +                    # format as tag node +                    config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) + +            # delete old radius-server nodes +            config.delete(base + [wifi, 'security', 'wpa', 'radius-server']) + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        sys.exit(1) diff --git a/src/op_mode/show_wireless.py b/src/op_mode/show_wireless.py new file mode 100755 index 000000000..aff882559 --- /dev/null +++ b/src/op_mode/show_wireless.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import argparse +import re + +from sys import exit +from copy import deepcopy +from subprocess import Popen, PIPE, STDOUT + +from vyos.config import Config + +parser = argparse.ArgumentParser() +parser.add_argument("-s", "--scan", help="Scan for Wireless APs on given interface, e.g. 'wlan0'") +parser.add_argument("-b", "--brief", action="store_true", help="Show wireless configuration") +parser.add_argument("-c", "--stations", help="Show wireless clients connected on interface, e.g. 'wlan0'") + +def _cmd(command): +    p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True) +    tmp = p.communicate()[0].strip() +    return tmp.decode() + +def show_brief(): +    config = Config() +    if len(config.list_effective_nodes('interfaces wireless')) == 0: +        print("No Wireless interfaces configured") +        exit(0) + +    interfaces = [] +    for intf in config.list_effective_nodes('interfaces wireless'): +        config.set_level('interfaces wireless {}'.format(intf)) +        data = { +            'name': intf, +            'type': '', +            'ssid': '', +            'channel': '' +        } +        data['type'] = config.return_effective_value('type') +        data['ssid'] = config.return_effective_value('ssid') +        data['channel'] = config.return_effective_value('channel') + +        interfaces.append(data) + +    return interfaces + +def ssid_scan(intf): +    tmp = _cmd('/sbin/iw dev {} scan ap-force'.format(intf)) +    networks = [] +    data = { +        'ssid': '', +        'mac': '', +        'channel': '', +        'signal': '' +    } +    re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') +    for line in tmp.splitlines(): +        if line.startswith('BSS '): +            ssid = deepcopy(data) +            ssid['mac'] = re.search(re_mac, line).group() + +        elif line.lstrip().startswith('SSID: '): +            # SSID can be "    SSID: WLAN-57 6405", thus strip all leading whitespaces +            ssid['ssid'] = line.lstrip().split(':')[-1].lstrip() + +        elif line.lstrip().startswith('signal: '): +            # Siganl can be "   signal: -67.00 dBm", thus strip all leading whitespaces +            ssid['signal'] = line.lstrip().split(':')[-1].split()[0] + +        elif line.lstrip().startswith('DS Parameter set: channel'): +            # Channel can be "        DS Parameter set: channel 6" , thus +            # strip all leading whitespaces +            ssid['channel'] = line.lstrip().split(':')[-1].split()[-1] +            networks.append(ssid) +            continue + +    return networks + +def show_clients(intf): +    tmp = _cmd('/sbin/iw dev {} station dump'.format(intf)) +    clients = [] +    data = { +        'mac': '', +        'signal': '', +        'rx_bytes': '', +        'rx_packets': '', +        'tx_bytes': '', +        'tx_packets': '' +    } +    re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') +    for line in tmp.splitlines(): +        if line.startswith('Station'): +            client = deepcopy(data) +            client['mac'] = re.search(re_mac, line).group() + +        elif line.lstrip().startswith('signal avg:'): +            client['signal'] = line.lstrip().split(':')[-1].lstrip().split()[0] + +        elif line.lstrip().startswith('rx bytes:'): +            client['rx_bytes'] = line.lstrip().split(':')[-1].lstrip() + +        elif line.lstrip().startswith('rx packets:'): +            client['rx_packets'] = line.lstrip().split(':')[-1].lstrip() + +        elif line.lstrip().startswith('tx bytes:'): +            client['tx_bytes'] = line.lstrip().split(':')[-1].lstrip() + +        elif line.lstrip().startswith('tx packets:'): +            client['tx_packets'] = line.lstrip().split(':')[-1].lstrip() +            clients.append(client) +            continue + +    return clients + +if __name__ == '__main__': +    args = parser.parse_args() + +    if args.scan: +        print("Address            SSID                          Channel  Signal (dbm)") +        for network in ssid_scan(args.scan): +            print("{:<17}  {:<32}  {:>3}  {}".format(network['mac'], +                                                     network['ssid'], +                                                     network['channel'], +                                                     network['signal'])) +        exit(0) + +    elif args.brief: +        print("Interface  Type          SSID                         Channel") +        for intf in show_brief(): +            print("{:<9}  {:<12}  {:<32} {:>3}".format(intf['name'], +                                                      intf['type'], +                                                      intf['ssid'], +                                                      intf['channel'])) +        exit(0) + +    elif args.stations: +        print("Station            Signal     RX: bytes    packets        TX: bytes     packets") +        for client in show_clients(args.stations): +            print("{:<17}  {:>3}  {:>15}  {:>9}  {:>15}  {:>10} ".format(client['mac'], +                                                 client['signal'], client['rx_bytes'], client['rx_packets'], client['tx_bytes'], client['tx_packets'])) + +        exit(0) + +    else: +        parser.print_help() +        exit(1) | 
