def hostapd_options(band, ssid): """Returns hostapd options for bandsteering. Respects the experiments WifiBandsteering and WifiReverseBandsteering, in that order. Uses (and renames if necessary) a preexisting bandsteering directory for this band, if one exists. Otherwise, creates that directory. Args: band: The band on which hostapd is being started. ssid: The SSID of the AP. Returns: A list containing options to be passed to hostapd. Raises: BinWifiException: If the directory for storing bandsteering timestamps cannot be created. """ if experiment.enabled('WifiBandsteering'): target = '5' elif experiment.enabled('WifiReverseBandsteering'): target = '2.4' elif experiment.enabled('WifiHostapdLogging'): target = '' else: return [] band_dir = _bandsteering_dir(band, ssid) target_dir = _bandsteering_dir(target, ssid) # Make sure band_dir exist, since we want hostapd to write to it. If there's # a pre-existing one for the same band, use that; otherwise, create it. subdirs = (os.path.normpath(path) for path in glob.glob(os.path.join(_BANDSTEERING_DIR, '*/.'))) for subdir in subdirs: if os.path.basename(subdir).startswith(band): try: os.rename(subdir, band_dir) except OSError: raise utils.BinWifiException( "Couldn't update bandsteering directory") break else: try: os.makedirs(band_dir) except OSError as e: if e.errno != errno.EEXIST: raise utils.BinWifiException( "Couldn't create bandsteering directory %s", band_dir) result = ['-L', band_dir] if target and band != target: result += ['-S', target_dir] return result
def restore_wifi(opt): """Restore hostapd and wpa_supplicant on both bands from persisted settings. Nothing happens if persisted settings are not available. Args: opt: The OptDict parsed from command line options. Returns: True. """ # If both bands are specified, restore 5 GHz first so that STAs are more # likely to join it. restored = set() for band in sorted( opt.band.split(), reverse=not experiment.enabled('WifiReverseBandsteering')): client_interface = iw.find_interface_from_band( band, iw.INTERFACE_TYPE.client, opt.interface_suffix) ap_interface = iw.find_interface_from_band(band, iw.INTERFACE_TYPE.ap, opt.interface_suffix) for interface, program in ((client_interface, 'wpa_supplicant'), (ap_interface, 'hostapd')): if interface and interface not in restored: restored.add(interface) _restore_wifi(interface, program) return True
def set_client_wifi(opt): """Set up a wifi client in response to the 'setclient' command. Args: opt: The OptDict parsed from command line options. Returns: Whether wpa_supplicant successfully started and associated. Raises: BinWifiException: On various errors. """ if not opt.ssid: raise utils.BinWifiException('You must specify an ssid with --ssid') band = opt.band if band not in ('2.4', '5'): raise utils.BinWifiException('You must specify band with -b2.4 or -b5') psk = os.environ.get('WIFI_CLIENT_PSK', None) if band == '5' and quantenna.set_client_wifi(opt): return True mwifiex.set_recovery(experiment.enabled('MwifiexFirmwareRecovery')) phy = iw.find_phy(band, 'auto') if phy is None: utils.log("Couldn't find phy for band %s", band) return False interface = iw.find_interface_from_phy(phy, iw.INTERFACE_TYPE.client, opt.interface_suffix) if interface is None: # Create the client interface if it does not exist, using the same number as # an existing AP interface, which is stable across driver reloads. interface = client_interface_name(phy, opt.interface_suffix) if interface is None: raise utils.BinWifiException( 'AP interface not initialized for %s' % phy) if not iw.does_interface_exist(interface): utils.log('Creating client interface %s', interface) utils.subprocess_quiet(('iw', 'phy', phy, 'interface', 'add', interface, 'type', 'station'), no_stdout=True) ap_mac_address = utils.get_mac_address_for_interface( iw.find_interface_from_phy(phy, iw.INTERFACE_TYPE.ap, opt.interface_suffix)) mac_address = utils.increment_mac_address(ap_mac_address) subprocess.check_call( ('ip', 'link', 'set', interface, 'address', mac_address)) wpa_config = configs.generate_wpa_supplicant_config(opt.ssid, psk, opt) if not _set_wpa_supplicant_config(interface, wpa_config, opt): return False return True
def _connection_check(self, check_acs): """Support for WifiSimulateWireless.""" failure_s = self._acs_session_failure_s() if (experiment.enabled('WifiSimulateWireless') and failure_s < MAX_ACS_FAILURE_S): self.logger.info( 'WifiSimulateWireless: failing bridge connection check%s' ' (no ACS contact for %d seconds, max %d seconds)', ' (ACS)' if check_acs else '', failure_s, MAX_ACS_FAILURE_S) return False return super(Bridge, self)._connection_check(check_acs)
def qca8990_calibration(): """Main QCA8990 calibration check.""" if not _is_ath10k('wlan1'): if not _cal_dir_exists(): _log('This system does not use ath10k') _create_cal_dir() return if not experiment.enabled(CAL_EXPERIMENT): if _patch_exists(): os.remove(os.path.join(CALIBRATION_DIR, CAL_PATCH_FILE)) _reload_driver() _log('experiment {} removed. Removed patch and reloaded driver.'. format(CAL_EXPERIMENT)) return if not _cal_dir_exists(): _log( 'experiment {} not active. Skipping calibration check.'.format( CAL_EXPERIMENT)) _create_cal_dir() return # Experiment is enabled. calibration_state = _is_module_miscalibrated() if calibration_state is not None and not _patch_exists(): _create_cal_dir() if _generate_calibration_patch(calibration_state): _log('generated new patch.') _reload_driver() return if calibration_state is None: if not _cal_dir_exists(): _log('This system does not need calibration.') _create_cal_dir() return
def _start_hostapd(interface, config_filename, band, ssid): """Starts a babysat hostapd. Args: interface: The interface on which to start hostapd. config_filename: The filename of the hostapd configuration. band: The band on which hostapd is being started. ssid: The SSID with which hostapd is being started. Returns: Whether hostapd was started successfully. """ aggfiles = glob.glob('/sys/kernel/debug/ieee80211/phy*/' + 'netdev:%s/default_agg_timeout' % interface) if not aggfiles: # This can happen on non-mac80211 interfaces. utils.log('agg_timeout: no default_agg_timeout files for %r', interface) else: if experiment.enabled('WifiShortAggTimeout'): utils.log('Using short agg_timeout.') agg = 500 elif experiment.enabled('WifiNoAggTimeout'): utils.log('Disabling agg_timeout.') agg = 0 else: utils.log('Using default long agg_timeout.') agg = 5000 for aggfile in aggfiles: open(aggfile, 'w').write(str(agg)) pid_filename = utils.get_filename('hostapd', utils.FILENAME_KIND.pid, interface, tmp=True) alivemonitor_filename = utils.get_filename('hostapd', utils.FILENAME_KIND.alive, interface, tmp=True) # Don't use alivemonitor on Windcharger since no waveguide. b/32376077 if _is_wind_charger() or experiment.enabled('WifiNoAliveMonitor'): alive_monitor = [] else: alive_monitor = [ 'alivemonitor', alivemonitor_filename, '30', '2', '65' ] utils.log('Starting hostapd.') utils.babysit( alive_monitor + [ 'hostapd', '-A', alivemonitor_filename, '-F', _FINGERPRINTS_DIRECTORY ] + bandsteering.hostapd_options(band, ssid) + _hostapd_debug_options() + [config_filename], 'hostapd-%s' % interface, 10, pid_filename) # Wait for hostapd to start, and return False if it doesn't. i = j = 0 for i in xrange(100): if utils.check_pid(pid_filename): break sys.stderr.write('.') sys.stderr.flush() time.sleep(0.1) else: return False # hostapd_cli returns success on command timeouts. If we time this perfectly # and manage to connect but then hostapd dies right after, we'd think it # succeeded. So sleep a bit to try to give hostapd a chance to die from its # error before we try to connect to it. time.sleep(0.5) for j in xrange(100): if not utils.check_pid(pid_filename): break if _is_hostapd_running(interface): utils.log('started after %.1fs', (i + j) / 10.0) return True sys.stderr.write('.') sys.stderr.flush() time.sleep(0.1) utils.log('failed after %.1fs', (i + j) / 10.0) return False
def _hostapd_debug_options(): if experiment.enabled('WifiHostapdDebug'): return ['-d'] else: return []
def set_wifi(opt): """Set up an access point in response to the 'set' command. Args: opt: The OptDict parsed from command line options. Returns: Whether setting up the AP succeeded. Raises: BinWifiException: On various errors. """ band = opt.band width = opt.width channel = opt.channel autotype = opt.autotype protocols = set(opt.protocols.split('/')) utils.validate_set_wifi_options(opt) psk = None if opt.encryption == 'WEP' or '_PSK_' in opt.encryption: psk = os.environ['WIFI_PSK'] if band == '5' and quantenna.set_wifi(opt): return True if iw.RUNNABLE_WL() and not iw.RUNNABLE_IW(): _set_wifi_broadcom(opt) return True if not iw.RUNNABLE_IW(): raise utils.BinWifiException("Can't proceed without iw") # If this phy is running client mode, we need to use its width/channel. phy = iw.find_phy(band, channel) if phy is None: raise utils.BinWifiException('no wifi phy for band=%s channel=%s', band, channel) # Check for calibration errors on ath10k. qca9880_cal.qca8990_calibration() mwifiex.set_recovery(experiment.enabled('MwifiexFirmwareRecovery')) client_interface = iw.find_interface_from_phy(phy, iw.INTERFACE_TYPE.client, opt.interface_suffix) if (client_interface is not None and _is_wpa_supplicant_running(client_interface)): # Wait up to ten seconds for interface width and channel to be available # (only relevant if wpa_supplicant was started recently). # TODO(rofrankel): Consider shortcutting this loop if wpa_cli shows status # is SCANNING (and other values)? utils.log( 'Client running on same band; finding its width and channel.') for _ in xrange(50): client_band = _get_wpa_band(client_interface) client_width, client_channel = iw.find_width_and_channel( client_interface) sys.stderr.write('.') sys.stderr.flush() if None not in (client_band, client_width, client_channel): band, width, channel = client_band, client_width, client_channel utils.log( 'Using band=%s, channel=%s, width=%s MHz from client', band, channel, width) break time.sleep(0.2) else: utils.log("Couldn't find band, width, and channel used by client " "(it may not be connected)") interface = iw.find_interface_from_phy(phy, iw.INTERFACE_TYPE.ap, opt.interface_suffix) if interface is None: raise utils.BinWifiException( 'no wifi interface found for band=%s channel=%s suffix=%s', band, channel, opt.interface_suffix) for ap_interface in iw.find_all_interfaces_from_phy( phy, iw.INTERFACE_TYPE.ap): if not _is_hostapd_running(ap_interface): continue if ap_interface == interface: continue # TODO(rofrankel): Figure out what to do about width. Unlike channel, # there's no 'auto' default; we don't know if 20 was requested or just # defaulted to. So it's not clear whether to override the other AP's # choice. _, other_ap_channel = iw.find_width_and_channel(ap_interface) if channel == 'auto': channel = other_ap_channel else: _restart_hostapd(ap_interface, '-c', channel) utils.log('interface: %s', interface) utils.log('Configuring cfg80211 wifi.') pid_filename = utils.get_filename('hostapd', utils.FILENAME_KIND.pid, interface, tmp=True) utils.log('pidfile: %s', pid_filename) autotype_filename = '/tmp/autotype.%s' % interface band_filename = '/tmp/band.%s' % interface width_filename = '/tmp/width.%s' % interface autochan_filename = '/tmp/autochan.%s' % interface old_autotype = utils.read_or_empty(autotype_filename) old_band = utils.read_or_empty(band_filename) old_width = utils.read_or_empty(width_filename) # Special case: if autochannel enabled and we've done it before, just use the # old autochannel. The main reason for this is we may not be able to run the # autochannel algorithm without stopping hostapd first, which defeats the code # that tries not to restart hostapd unnecessarily. if (channel == 'auto' and (autotype, band, width) == (old_autotype, old_band, old_width)): # ...but only if not forced mode. If it's forced, don't use the old # value, but don't wipe it either. if not opt.force_restart: autochan = utils.read_or_empty(autochan_filename) if autochan and int(autochan) > 0: utils.log('Reusing old autochannel=%s', autochan) channel = autochan else: # forget old autochannel setting if os.path.exists(autochan_filename): try: os.remove(autochan_filename) except OSError: utils.log('Failed to remove autochan file.') if channel == 'auto': utils.atomic_write(autochan_filename, '') try: channel = autochannel.scan(interface, band, autotype, width) except ValueError as e: raise utils.BinWifiException('Autochannel scan failed: %s', e) utils.atomic_write(autochan_filename, channel) utils.atomic_write(autotype_filename, autotype) utils.atomic_write(band_filename, band) utils.atomic_write(width_filename, width) utils.log('using channel=%s', channel) try: utils.log('getting phy info...') with open(os.devnull, 'w') as devnull: try: phy_info = subprocess.check_output(('iw', 'phy', phy, 'info'), stderr=devnull) except subprocess.CalledProcessError as e: raise utils.BinWifiException( 'Failed to get phy info for phy %s: %s', phy, e) hostapd_config = configs.generate_hostapd_config( phy_info, interface, band, channel, width, protocols, psk, opt) except ValueError as e: raise utils.BinWifiException('Invalid option: %s', e) return _maybe_restart_hostapd(interface, hostapd_config, opt)
def generate_hostapd_config(phy_info, interface, band, channel, width, protocols, psk, opt): """Generates a hostpad config from the given arguments. Args: phy_info: The result of running 'iw phy <phy> info' where <phy> is the phy on which hostapd will run. interface: The interface on which hostapd will run. band: The band on which hostapd will run. channel: The channel on which hostapd will run. width: The channel width with which hostapd will run. protocols: The supported 802.11 protocols, as a collection of single-character strings (e.g. ['a', 'g', 'n']) psk: The PSK to use for the AP. opt: The OptDict parsed from command line options. Returns: The generated hostapd config, as a string. Raises: ValueError: For certain invalid combinations of arguments. """ utils.log('generating configuration...') if band == '2.4': hostapd_band = 'g' if set(('n', 'g')) & protocols else 'b' else: hostapd_band = 'a' ampdu = '' enable_80211n = '' enable_80211ac = '' require_ht = '' require_vht = '' ht20 = '' ht40 = '' ht_rxstbc = '' vht_settings = '' guard_interval = ('[SHORT-GI-20][SHORT-GI-40]' if opt.short_guard_interval else '') vht_guard_interval = '[SHORT-GI-80]' if opt.short_guard_interval else '' if 'RX STBC 3-stream' in phy_info: ht_rxstbc = '[RX-STBC123]' if 'RX STBC 2-stream' in phy_info: ht_rxstbc = '[RX-STBC12]' if 'RX STBC 1-stream' in phy_info: ht_rxstbc = '[RX-STBC1]' if 'n' in protocols: enable_80211n = 'ieee80211n=1' ht20 = '[HT20]' if 'ac' in protocols: if width == '80': enable_80211ac = 'ieee80211ac=1' if 'Maximum RX AMPDU length 16383 bytes' in phy_info: ampdu = '[MAX-A-MPDU-LEN-EXP1]' if 'Maximum RX AMPDU length 32767 bytes' in phy_info: ampdu = '[MAX-A-MPDU-LEN-EXP2]' if 'Maximum RX AMPDU length 65535 bytes' in phy_info: ampdu = '[MAX-A-MPDU-LEN-EXP3]' if 'Maximum RX AMPDU length 131071 bytes' in phy_info: ampdu = '[MAX-A-MPDU-LEN-EXP4]' if 'Maximum RX AMPDU length 262143 bytes' in phy_info: ampdu = '[MAX-A-MPDU-LEN-EXP5]' if 'Maximum RX AMPDU length 524287 bytes' in phy_info: ampdu = '[MAX-A-MPDU-LEN-EXP6]' if 'Maximum RX AMPDU length 1048575 bytes' in phy_info: ampdu = '[MAX-A-MPDU-LEN-EXP7]' if not set(('a', 'b', 'ab', 'g')) & protocols: require_ht = 'require_ht=1' if not set(('a', 'b', 'ab', 'g', 'n')) & protocols: require_vht = 'require_vht=1' if opt.encryption.startswith('WPA_PSK_'): auth_algs, wpa = 1, 1 elif opt.encryption.startswith('WPA2_PSK_'): auth_algs, wpa = 1, 2 elif opt.encryption.startswith('WPA12_PSK_'): auth_algs, wpa = 1, 3 elif opt.encryption.startswith('WEP'): auth_algs, wpa = 3, 0 elif opt.encryption.startswith('NONE'): auth_algs, wpa = 1, 0 else: raise ValueError('Invalid crypto protocol: %s' % opt.encryption) if opt.encryption[-4:] in ('_AES', 'WEP', 'NONE'): wpa_pairwise = 'CCMP' elif opt.encryption.endswith('_TKIP'): wpa_pairwise = 'TKIP' else: raise ValueError('Invalid crypto protocol: %s' % opt.encryption) if int(width) >= 40: if '%s+' % channel in _HT_DIRECTIONS.split(): ht40 = '[HT40+]' elif '%s-' % channel in _HT_DIRECTIONS.split(): ht40 = '[HT40-]' else: raise ValueError( 'HT40 requested but not available on channel %s.' % channel) if width == '80': ldpc = '[RXLDPC]' vht_base = '' for base in _VHT_BASES.split(): if base.startswith('%s=' % channel): vht_base = base.split('=')[1] break if vht_base: vht_settings = _VHT_SETTINGS_TPL.format( ampdu=ampdu, vht_guard_interval=vht_guard_interval, ldpc=ldpc, vht_base=int(vht_base) + 6) else: raise ValueError( 'VHT80 requested but not available on channel %s' % channel) try: bssid = None if subprocess.call(('is-network-box')) == 0: mac_addr_hnvram = ('MAC_ADDR_WIFI' + ('' if interface.startswith('wlan0') else '2')) bssid = utils.subprocess_output_or_none( ('hnvram', '-qr', mac_addr_hnvram)) if bssid is None: bssid = utils.subprocess_output_or_none( ('hnvram', '-rq', 'MAC_ADDR')) if bssid is None: raise utils.BinWifiException( 'Box has no MAC_ADDR_WIFI, MAC_ADDR_WIFI2, or MAC_ADDR. You can ' 'set these with e.g. ' "'# hnvram -w MAC_ADDR_WIFI=00:00:00:00:00:00'") except OSError: pass enable_wmm = 'wmm_enabled=1' if opt.enable_wmm else '' hidden = 'ignore_broadcast_ssid=1' if opt.hidden_mode else '' bridge = 'bridge=%s' % opt.bridge if opt.bridge else '' ap_isolate = 'ap_isolate=1' if opt.client_isolation else '' wds = 'wds_sta=1' if opt.wds else '' hostapd_conf_parts = [ _HOSTCONF_TPL.format(interface=interface, band=band, channel=channel, width=width, protocols=protocols, hostapd_band=hostapd_band, enable_80211n=enable_80211n, enable_80211ac=enable_80211ac, require_ht=require_ht, require_vht=require_vht, ht20=ht20, ht40=ht40, ht_rxstbc=ht_rxstbc, vht_settings=vht_settings, guard_interval=guard_interval, enable_wmm=enable_wmm, hidden=hidden, ap_isolate=ap_isolate, auth_algs=auth_algs, bridge=bridge, ssid=utils.sanitize_ssid(opt.ssid), wds=wds, vendor_elements=get_vendor_elements(opt)) ] if opt.encryption != 'NONE': hostapd_conf_parts.append( _HOSTCONF_WPA_TPL.format(psk=utils.validate_and_sanitize_psk(psk), wpa=wpa, wpa_pairwise=wpa_pairwise)) if experiment.enabled('Wifi80211k'): hostapd_conf_parts.append( _EXPERIMENT_80211K_TPL.format(interface=interface)) if opt.yottasecond_timeouts: hostapd_conf_parts.append(_YOTTASECOND_TIMEOUTS_TPL) elif opt.extra_short_timeouts >= 2: hostapd_conf_parts.append(_EXTRA_SHORT_TIMEOUTS2_TPL) elif opt.extra_short_timeouts >= 1: hostapd_conf_parts.append(_EXTRA_SHORT_TIMEOUTS1_TPL) # Track the active experiments the last time hostapd was started: # - for easier examination of the state # - to make sure the config counts as changed whenever the set of # experiments changes. active_experiments = [i for i in EXPERIMENTS if experiment.enabled(i)] hostapd_conf_parts.append('# Experiments: (%s)\n' % ','.join(active_experiments)) utils.log('configuration ready.') return '\n'.join(hostapd_conf_parts)