diff options
Diffstat (limited to 'src/conf_mode/interfaces-wireless.py')
-rwxr-xr-x | src/conf_mode/interfaces-wireless.py | 1038 |
1 files changed, 210 insertions, 828 deletions
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 098aa8d97..138f27755 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -15,756 +15,26 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os +from sys import exit +from re import findall -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 jinja2 import FileSystemLoader, Environment -from subprocess import Popen, PIPE -from psutil import pid_exists +from netifaces import interfaces +from netaddr import EUI, mac_unix_expanded -from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import apply_vlan_config -from vyos.configdict import list_diff, vlan_to_dict from vyos.config import Config +from vyos.configdict import list_diff, vlan_to_dict +from vyos.defaults import directories as vyos_data_dir +from vyos.ifconfig import WiFiIf +from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config +from vyos.util import process_running, chmod_x, chown, run, is_bridge_member 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': [], @@ -792,7 +62,7 @@ default_config_data = { 'cap_vht_center_freq_2' : '', 'cap_vht_chan_set_width' : '', 'cap_vht_ldpc' : False, - 'cap_vht_link_adaptation' : False, + 'cap_vht_link_adaptation' : '', 'cap_vht_max_mpdu_exp' : '', 'cap_vht_max_mpdu' : '', 'cap_vht_short_gi' : [], @@ -820,6 +90,10 @@ default_config_data = { 'ip_enable_arp_accept': 0, 'ip_enable_arp_announce': 0, 'ip_enable_arp_ignore': 0, + 'ipv6_autoconf': 0, + 'ipv6_eui64_prefix': '', + 'ipv6_forwarding': 1, + 'ipv6_dup_addr_detect': 1, 'mac' : '', 'max_stations' : '', 'mgmt_frame_protection' : 'disabled', @@ -834,9 +108,10 @@ default_config_data = { 'sec_wpa_passphrase' : '', 'sec_wpa_radius' : [], 'ssid' : '', - 'type' : 'monitor', + 'op_mode' : 'monitor', 'vif': [], - 'vif_remove': [] + 'vif_remove': [], + 'vrf': '' } def get_conf_file(conf_type, intf): @@ -845,11 +120,8 @@ def get_conf_file(conf_type, intf): # 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) + chmod_x(cfg_dir) + chown(cfg_dir, user, group) cfg_file = cfg_dir + r'/{}.cfg'.format(intf) return cfg_file @@ -860,11 +132,8 @@ def get_pid(conf_type, intf): # 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) + chmod_x(cfg_dir) + chown(cfg_dir, user, group) cfg_file = cfg_dir + r'/{}.pid'.format(intf) return cfg_file @@ -876,28 +145,22 @@ def get_wpa_suppl_config_name(intf): # 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) + chmod_x(cfg_dir) + chown(cfg_dir, user, group) 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 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") + if 'VYOS_TAGNODE_VALUE' not in os.environ: + raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') + + wifi['intf'] = os.environ['VYOS_TAGNODE_VALUE'] # check if wireless interface has been removed cfg_base = 'interfaces wireless ' + wifi['intf'] @@ -1031,7 +294,7 @@ def get_config(): # VHT link adaptation capabilities if conf.exists('capabilities vht link-adaptation'): wifi['cap_vht'] = True - wifi['cap_vht_link_adaptation'] = True + wifi['cap_vht_link_adaptation'] = conf.return_value('capabilities vht link-adaptation') # Set the maximum length of A-MPDU pre-EOF padding that the station can receive if conf.exists('capabilities vht max-mpdu-exp'): @@ -1128,10 +391,30 @@ def get_config(): if conf.exists('ip enable-arp-announce'): wifi['ip_enable_arp_announce'] = 1 + # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) + if conf.exists('ipv6 address autoconf'): + wifi['ipv6_autoconf'] = 1 + + # Get prefix for IPv6 addressing based on MAC address (EUI-64) + if conf.exists('ipv6 address eui64'): + wifi['ipv6_eui64_prefix'] = conf.return_value('ipv6 address eui64') + # ARP enable ignore if conf.exists('ip enable-arp-ignore'): wifi['ip_enable_arp_ignore'] = 1 + # Disable IPv6 forwarding on this interface + if conf.exists('ipv6 disable-forwarding'): + wifi['ipv6_forwarding'] = 0 + + # IPv6 Duplicate Address Detection (DAD) tries + if conf.exists('ipv6 dup-addr-detect-transmits'): + wifi['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) + + # Wireless physical device + if conf.exists('physical-device'): + wifi['phy'] = conf.return_value('physical-device') + # Media Access Control (MAC) address if conf.exists('mac'): wifi['mac'] = conf.return_value('mac') @@ -1148,9 +431,9 @@ def get_config(): if conf.exists('mode'): wifi['mode'] = conf.return_value('mode') - # Wireless physical device - if conf.exists('phy'): - wifi['phy'] = conf.return_value('phy') + # retrieve VRF instance + if conf.exists('vrf'): + wifi['vrf'] = conf.return_value('vrf') # Transmission power reduction in dBm if conf.exists('reduce-transmit-power'): @@ -1204,6 +487,7 @@ def get_config(): radius = { 'server' : server, 'acc_port' : '', + 'disabled': False, 'port' : 1812, 'key' : '' } @@ -1216,6 +500,10 @@ def get_config(): if conf.exists('accounting'): radius['acc_port'] = radius['port'] + 1 + # Check if RADIUS server was temporary disabled + if conf.exists(['disable']): + radius['disabled'] = True + # RADIUS server shared-secret if conf.exists('key'): radius['key'] = conf.return_value('key') @@ -1232,7 +520,11 @@ def get_config(): # Wireless device type for this interface if conf.exists('type'): - wifi['type'] = conf.return_value('type') + tmp = conf.return_value('type') + if tmp == 'access-point': + tmp = 'ap' + + wifi['op_mode'] = tmp # re-set configuration level to parse new nodes conf.set_level(cfg_base) @@ -1248,6 +540,9 @@ def get_config(): conf.set_level(cfg_base + ' vif ' + vif) wifi['vif'].append(vlan_to_dict(conf)) + # disable interface + if conf.exists('disable'): + wifi['disable'] = True # retrieve configured regulatory domain conf.set_level('system') @@ -1259,12 +554,23 @@ def get_config(): def verify(wifi): if wifi['deleted']: + interface = wifi['intf'] + is_member, bridge = is_bridge_member(interface) + if is_member: + # can not use a f'' formatted-string here as bridge would not get + # expanded in the print statement + raise ConfigError('Can not delete interface "{0}" as it ' \ + 'is a member of bridge "{1}"!'.format(interface, bridge)) return None - if wifi['type'] != 'monitor' and not wifi['ssid']: + + if wifi['op_mode'] != 'monitor' and not wifi['ssid']: raise ConfigError('SSID must be set for {}'.format(wifi['intf'])) - if wifi['type'] == 'access-point': + if not wifi['phy']: + raise ConfigError('You must specify physical-device') + + if wifi['op_mode'] == 'ap': c = Config() if not c.exists('system wifi-regulatory-domain'): raise ConfigError('Wireless regulatory domain is mandatory,\n' \ @@ -1273,7 +579,6 @@ def verify(wifi): if 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') @@ -1283,6 +588,10 @@ def verify(wifi): if wifi['cap_vht_beamform'] and wifi['cap_vht_antenna_cnt'] == 1: raise ConfigError('Cannot use beam forming with just one antenna!') + if wifi['cap_vht_beamform'] == 'single-user-beamformer' and wifi['cap_vht_antenna_cnt'] < 3: + # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 + raise ConfigError('Single-user beam former requires at least 3 antennas!') + if wifi['sec_wep'] and (len(wifi['sec_wep_key']) == 0): raise ConfigError('Missing WEP keys') @@ -1293,35 +602,55 @@ def verify(wifi): if not radius['key']: raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server'])) + vrf_name = wifi['vrf'] + if vrf_name and vrf_name not in interfaces(): + raise ConfigError(f'VRF "{vrf_name}" does not exist') + # use common function to verify VLAN configuration + verify_vlan_config(wifi) + + conf = Config() + # Only one wireless interface per phy can be in station mode + base = ['interfaces', 'wireless'] + for phy in os.listdir('/sys/class/ieee80211'): + stations = [] + for wlan in conf.list_nodes(base): + # the following node is mandatory + if conf.exists(base + [wlan, 'physical-device', phy]): + tmp = conf.return_value(base + [wlan, 'type']) + if tmp == 'station': + stations.append(wlan) + + if len(stations) > 1: + raise ConfigError('Only one station per wireless physical interface possible!') return None def generate(wifi): - pid = 0 + # Prepare Jinja2 template loader from files + tmpl_path = os.path.join(vyos_data_dir["data"], "templates", "wifi") + fs_loader = FileSystemLoader(tmpl_path) + env = Environment(loader=fs_loader) + # 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) + if process_running(pidfile): + command = 'start-stop-daemon' + command += ' --stop ' + command += ' --quiet' + command += ' --oknodo' + command += ' --pidfile ' + pidfile + run(command) # 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) + if process_running(pidfile): + command = 'start-stop-daemon' + command += ' --stop ' + command += ' --quiet' + command += ' --oknodo' + command += ' --pidfile ' + pidfile + run(command) # Delete config files if interface is removed if wifi['deleted']: @@ -1333,15 +662,37 @@ def generate(wifi): return None + if not wifi['mac']: + # http://wiki.stocksy.co.uk/wiki/Multiple_SSIDs_with_hostapd + # generate locally administered MAC address from used phy interface + with open('/sys/class/ieee80211/{}/addresses'.format(wifi['phy']), 'r') as f: + # some PHYs tend to have multiple interfaces and thus supply multiple MAC + # addresses - we only need the first one for our calculation + tmp = f.readline().rstrip() + tmp = EUI(tmp).value + # mask last nibble from the MAC address + tmp &= 0xfffffffffff0 + # set locally administered bit in MAC address + tmp |= 0x020000000000 + # we now need to add an offset to our MAC address indicating this + # subinterfaces index + tmp += int(findall(r'\d+', wifi['intf'])[0]) + + # convert integer to "real" MAC address representation + mac = EUI(hex(tmp).split('x')[-1]) + # change dialect to use : as delimiter instead of - + mac.dialect = mac_unix_expanded + wifi['mac'] = str(mac) + # render appropriate new config files depending on access-point or station mode - if wifi['type'] == 'access-point': - tmpl = Template(config_hostapd_tmpl) + if wifi['op_mode'] == 'ap': + tmpl = env.get_template('hostapd.conf.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) + elif wifi['op_mode'] == 'station': + tmpl = env.get_template('wpa_supplicant.conf.tmpl') config_text = tmpl.render(wifi) with open(get_conf_file('wpa_supplicant', wifi['intf']), 'w') as f: f.write(config_text) @@ -1349,13 +700,24 @@ def generate(wifi): return None def apply(wifi): - w = EthernetIf(wifi['intf']) if wifi['deleted']: + w = WiFiIf(wifi['intf']) # delete interface w.remove() else: - # Some parts e.g. MAC address can't be changed when interface is up - w.set_state('down') + # WiFi interface needs to be created on-block (e.g. mode or physical + # interface) instead of passing a ton of arguments, I just use a dict + # that is managed by vyos.ifconfig + conf = deepcopy(WiFiIf.get_config()) + + # Assign WiFi instance configuration parameters to config dict + conf['phy'] = wifi['phy'] + + # Finally create the new interface + w = WiFiIf(wifi['intf'], **conf) + + # assign/remove VRF + w.set_vrf(wifi['vrf']) # update interface description used e.g. within SNMP w.set_alias(wifi['description']) @@ -1394,7 +756,7 @@ def apply(wifi): # if custom mac is removed if wifi['mac']: w.set_mac(wifi['mac']) - else: + elif wifi['hw_id']: w.set_mac(wifi['hw_id']) # configure ARP filter configuration @@ -1405,10 +767,14 @@ def apply(wifi): w.set_arp_announce(wifi['ip_enable_arp_announce']) # configure ARP ignore w.set_arp_ignore(wifi['ip_enable_arp_ignore']) - - # enable interface - if not wifi['disable']: - w.set_state('up') + # IPv6 address autoconfiguration + w.set_ipv6_autoconf(wifi['ipv6_autoconf']) + # IPv6 EUI-based address + w.set_ipv6_eui64_address(wifi['ipv6_eui64_prefix']) + # IPv6 forwarding + w.set_ipv6_forwarding(wifi['ipv6_forwarding']) + # IPv6 Duplicate Address Detection (DAD) tries + w.set_ipv6_dad_messages(wifi['ipv6_dup_addr_detect']) # Configure interface address(es) # - not longer required addresses get removed first @@ -1437,30 +803,46 @@ def apply(wifi): vlan = e.add_vlan(vif['id']) apply_vlan_config(vlan, vif) - # Physical interface is now configured. Proceed by starting hostapd or - # wpa_supplicant daemon. When type is monitor we can just skip this. - if wifi['type'] == 'access-point': - cmd = 'start-stop-daemon --start --quiet' - 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'])) - - # execute assembled command - subprocess_cmd(cmd) - - elif wifi['type'] == 'station': - cmd = 'start-stop-daemon --start --quiet' - 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) + # Enable/Disable interface - interface is always placed in + # administrative down state in WiFiIf class + if not wifi['disable']: + w.set_admin_state('up') + + # Physical interface is now configured. Proceed by starting hostapd or + # wpa_supplicant daemon. When type is monitor we can just skip this. + if wifi['op_mode'] == 'ap': + command = 'start-stop-daemon' + command += ' --start ' + command += ' --quiet' + command += ' --oknodo' + command += ' --pidfile ' + get_pid('hostapd', wifi['intf']) + command += ' --exec /usr/sbin/hostapd' + # now pass arguments to hostapd binary + command += ' -- ' + command += ' -B' + command += ' -P ' + get_pid('hostapd', wifi['intf']) + command += ' ' + get_conf_file('hostapd', wifi['intf']) + + # execute assembled command + run(command) + + elif wifi['op_mode'] == 'station': + command = 'start-stop-daemon' + command += ' --start ' + command += ' --quiet' + command += ' --oknodo' + command += ' --pidfile ' + get_pid('hostapd', wifi['intf']) + command += ' --exec /sbin/wpa_supplicant' + # now pass arguments to hostapd binary + command += ' -- ' + command += ' -s -B -D nl80211' + command += ' -P ' + get_pid('wpa_supplicant', wifi['intf']) + command += ' -i ' + wifi['intf'] + command += ' -c ' + \ + get_conf_file('wpa_supplicant', wifi['intf']) + + # execute assembled command + run(command) return None |