#!/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 . 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 re import findall from subprocess import Popen, PIPE from psutil import pid_exists from netifaces import interfaces from netaddr import * from vyos.ifconfig import WiFiIf from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config 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 # 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 }} # The own IP address of the access point (used as NAS-IP-Address) own_ip_addr={{ sec_wpa_radius_source }} {% else %} # The own IP address of the access point (used as NAS-IP-Address) own_ip_addr=127.0.0.1 {% endif %} {% for radius in sec_wpa_radius -%} {%- if not radius.disabled -%} # 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 %} {% endif %} {% endfor %} {% endif %} {% else %} # Open system auth_algs=1 {% endif %} # TX queue parameters (EDCF / bursting) # tx_queue__ # 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 }}" {% else %} key_mgmt=NONE {% 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, 'ip_disable_arp_filter': 1, 'ip_enable_arp_accept': 0, 'ip_enable_arp_announce': 0, 'ip_enable_arp_ignore': 0, 'ipv6_forwarding': 1, 'ipv6_dup_addr_detect': 1, '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' : '', 'op_mode' : 'monitor', 'vif': [], 'vif_remove': [], 'vrf': '' } 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 get_config(): wifi = deepcopy(default_config_data) conf = Config() # determine tagNode instance 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'] 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 # ARP filter configuration if conf.exists('ip disable-arp-filter'): wifi['ip_disable_arp_filter'] = 0 # ARP enable accept if conf.exists('ip enable-arp-accept'): wifi['ip_enable_arp_accept'] = 1 # ARP enable announce if conf.exists('ip enable-arp-announce'): wifi['ip_enable_arp_announce'] = 1 # 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') # 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') # retrieve VRF instance if conf.exists('vrf'): wifi['vrf'] = conf.return_value('vrf') # 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' : '', 'disabled': False, '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 # 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') # 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'): 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) # 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)) # disable interface if conf.exists('disable'): wifi['disable'] = True # 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['op_mode'] != 'monitor' and not wifi['ssid']: raise ConfigError('SSID must be set for {}'.format(wifi['intf'])) 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' \ 'use "set system wifi-regulatory-domain".') 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') 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'])) 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 # 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' cmd += ' --stop ' cmd += ' --quiet' cmd += ' --oknodo' 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' cmd += ' --stop ' cmd += ' --quiet' cmd += ' --oknodo' 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 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: tmp = EUI(f.read().rstrip()).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['op_mode'] == 'ap': 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['op_mode'] == '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): if wifi['deleted']: w = WiFiIf(wifi['intf']) # delete interface w.remove() else: # WiFi interface needs to be created on-block (e.g. mode or physical # interface) instead of passing a ton of arguments, I just use a dict # that is managed by vyos.ifconfig conf = deepcopy(WiFiIf.get_config()) # 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']) # 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']) # configure ARP filter configuration w.set_arp_filter(wifi['ip_disable_arp_filter']) # configure ARP accept w.set_arp_accept(wifi['ip_enable_arp_accept']) # configure ARP announce w.set_arp_announce(wifi['ip_enable_arp_announce']) # configure ARP ignore w.set_arp_ignore(wifi['ip_enable_arp_ignore']) # Disable IPv6 forwarding on this interface 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 # - 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) # 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': cmd = 'start-stop-daemon' cmd += ' --start ' cmd += ' --quiet' cmd += ' --oknodo' cmd += ' --pidfile ' + get_pid('hostapd', wifi['intf']) cmd += ' --exec /usr/sbin/hostapd' # now pass arguments to hostapd binary cmd += ' -- ' cmd += ' -B' cmd += ' -P ' + get_pid('hostapd', wifi['intf']) cmd += ' ' + get_conf_file('hostapd', wifi['intf']) # execute assembled command subprocess_cmd(cmd) elif wifi['op_mode'] == 'station': cmd = 'start-stop-daemon' cmd += ' --start ' cmd += ' --quiet' cmd += ' --oknodo' cmd += ' --pidfile ' + get_pid('hostapd', wifi['intf']) cmd += ' --exec /sbin/wpa_supplicant' # now pass arguments to hostapd binary cmd += ' -- ' cmd += ' -s -B -D nl80211' cmd += ' -P ' + get_pid('wpa_supplicant', wifi['intf']) cmd += ' -i ' + wifi['intf'] cmd += ' -c ' + 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)