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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
    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)
Beispiel #5
0
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
Beispiel #6
0
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
Beispiel #7
0
def _hostapd_debug_options():
    if experiment.enabled('WifiHostapdDebug'):
        return ['-d']
    else:
        return []
Beispiel #8
0
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)
Beispiel #9
0
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)