| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import copy |
| import logging |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types |
| |
| |
| class HostapConfig(object): |
| """Parameters for router configuration.""" |
| |
| # A mapping of frequency to channel number. This includes some |
| # frequencies used outside the US. |
| CHANNEL_MAP = {2412: 1, |
| 2417: 2, |
| 2422: 3, |
| 2427: 4, |
| 2432: 5, |
| 2437: 6, |
| 2442: 7, |
| 2447: 8, |
| 2452: 9, |
| 2457: 10, |
| 2462: 11, |
| # 12, 13 are only legitimate outside the US. |
| 2467: 12, |
| 2472: 13, |
| # 34 valid in Japan. |
| 5170: 34, |
| # 36-116 valid in the US, except 38, 42, and 46, which have |
| # mixed international support. |
| 5180: 36, |
| 5190: 38, |
| 5200: 40, |
| 5210: 42, |
| 5220: 44, |
| 5230: 46, |
| 5240: 48, |
| 5260: 52, |
| 5280: 56, |
| 5300: 60, |
| 5320: 64, |
| 5500: 100, |
| 5520: 104, |
| 5540: 108, |
| 5560: 112, |
| 5580: 116, |
| # 120, 124, 128 valid in Europe/Japan. |
| 5600: 120, |
| 5620: 124, |
| 5640: 128, |
| # 132+ valid in US. |
| 5660: 132, |
| 5680: 136, |
| 5700: 140, |
| 5745: 149, |
| 5765: 153, |
| 5785: 157, |
| 5805: 161, |
| 5825: 165} |
| |
| MODE_11A = 'a' |
| MODE_11B = 'b' |
| MODE_11G = 'g' |
| MODE_11N_MIXED = 'n-mixed' |
| MODE_11N_PURE = 'n-only' |
| |
| N_CAPABILITY_HT20 = object() |
| N_CAPABILITY_HT40 = object() |
| N_CAPABILITY_HT40_PLUS = object() |
| N_CAPABILITY_HT40_MINUS = object() |
| N_CAPABILITY_GREENFIELD = object() |
| N_CAPABILITY_SGI20 = object() |
| N_CAPABILITY_SGI40 = object() |
| ALL_N_CAPABILITIES = [N_CAPABILITY_HT20, |
| N_CAPABILITY_HT40, |
| N_CAPABILITY_HT40_PLUS, |
| N_CAPABILITY_HT40_MINUS, |
| N_CAPABILITY_GREENFIELD, |
| N_CAPABILITY_SGI20, |
| N_CAPABILITY_SGI40] |
| |
| # This is a loose merging of the rules for US and EU regulatory |
| # domains as taken from IEEE Std 802.11-2012 Appendix E. For instance, |
| # we tolerate HT40 in channels 149-161 (not allowed in EU), but also |
| # tolerate HT40+ on channel 7 (not allowed in the US). We take the loose |
| # definition so that we don't prohibit testing in either domain. |
| HT40_ALLOW_MAP = {N_CAPABILITY_HT40_MINUS: range(6, 14) + |
| range(40, 65, 8) + |
| range(104, 137, 8) + |
| [153, 161], |
| N_CAPABILITY_HT40_PLUS: range(1, 8) + |
| range(36, 61, 8) + |
| range(100, 133, 8) + |
| [149, 157]} |
| |
| PMF_SUPPORT_DISABLED = 0 |
| PMF_SUPPORT_ENABLED = 1 |
| PMF_SUPPORT_REQUIRED = 2 |
| PMF_SUPPORT_VALUES = (PMF_SUPPORT_DISABLED, |
| PMF_SUPPORT_ENABLED, |
| PMF_SUPPORT_REQUIRED) |
| |
| |
| @property |
| def channel(self): |
| """@return int channel number for self.frequency.""" |
| return self.CHANNEL_MAP[self.frequency] |
| |
| |
| @channel.setter |
| def channel(self, value): |
| """Sets the channel number to configure hostapd to listen on. |
| |
| @param value: int channel number. |
| |
| """ |
| for frequency, channel in self.CHANNEL_MAP.iteritems(): |
| if channel == value: |
| self.frequency = frequency |
| break |
| |
| else: |
| raise error.TestFail('Tried to set an invalid channel: %r.' % |
| value) |
| |
| |
| @property |
| def frequency(self): |
| """@return int frequency for hostapd to listen on.""" |
| return self._frequency |
| |
| |
| @frequency.setter |
| def frequency(self, value): |
| """Sets the frequency for hostapd to listen on. |
| |
| @param value: int frequency in MHz. |
| |
| """ |
| if value not in self.CHANNEL_MAP or not self.supports_frequency(value): |
| raise error.TestFail('Tried to set an invalid frequency: %r.' % |
| value) |
| |
| self._frequency = value |
| |
| |
| @property |
| def hostapd_ht_capabilities(self): |
| """@return string suitable for the ht_capab= line in a hostapd config""" |
| ret = [] |
| if self.ht40_plus_allowed: |
| ret.append('[HT40+]') |
| elif self.ht40_minus_allowed: |
| ret.append('[HT40-]') |
| if self.N_CAPABILITY_GREENFIELD in self._n_capabilities: |
| logging.warning('Greenfield flag is ignored for hostap...') |
| if self.N_CAPABILITY_SGI20 in self._n_capabilities: |
| ret.append('[SHORT-GI-20]') |
| if self.N_CAPABILITY_SGI40 in self._n_capabilities: |
| ret.append('[SHORT-GI-40]') |
| return ''.join(ret) |
| |
| |
| @property |
| def ht40_plus_allowed(self): |
| """@return True iff HT40+ is enabled for this configuration.""" |
| channel_supported = (self.channel in |
| self.HT40_ALLOW_MAP[self.N_CAPABILITY_HT40_PLUS]) |
| return ((self.N_CAPABILITY_HT40_PLUS in self._n_capabilities or |
| self.N_CAPABILITY_HT40 in self._n_capabilities) and |
| channel_supported) |
| |
| |
| @property |
| def ht40_minus_allowed(self): |
| """@return True iff HT40- is enabled for this configuration.""" |
| channel_supported = (self.channel in |
| self.HT40_ALLOW_MAP[self.N_CAPABILITY_HT40_MINUS]) |
| return ((self.N_CAPABILITY_HT40_MINUS in self._n_capabilities or |
| self.N_CAPABILITY_HT40 in self._n_capabilities) and |
| channel_supported) |
| |
| |
| @property |
| def ht_packet_capture_mode(self): |
| """Get an appropriate packet capture HT parameter. |
| |
| When we go to configure a raw monitor we need to configure |
| the phy to listen on the correct channel. Part of doing |
| so is to specify the channel width for HT channels. In the |
| case that the AP is configured to be either HT40+ or HT40-, |
| we could return the wrong parameter because we don't know which |
| configuration will be chosen by hostap. |
| |
| @return string HT parameter for frequency configuration. |
| |
| """ |
| if not self.is_11n: |
| return None |
| |
| if self.ht40_plus_allowed: |
| return 'HT40+' |
| |
| if self.ht40_minus_allowed: |
| return 'HT40-' |
| |
| return 'HT20' |
| |
| |
| @property |
| def hw_mode(self): |
| """@return string hardware mode understood by hostapd.""" |
| if self._mode == self.MODE_11A: |
| return self.MODE_11A |
| if self._mode == self.MODE_11B: |
| return self.MODE_11B |
| if self._mode == self.MODE_11G: |
| return self.MODE_11G |
| if self._mode in (self.MODE_11N_MIXED, self.MODE_11N_PURE): |
| # For their own historical reasons, hostapd wants it this way. |
| if self.frequency > 5000: |
| return self.MODE_11A |
| |
| return self.MODE_11G |
| |
| raise error.TestFail('Invalid mode.') |
| |
| |
| @property |
| def is_11n(self): |
| """@return True iff we're trying to host an 802.11n network.""" |
| return self._mode in (self.MODE_11N_MIXED, self.MODE_11N_PURE) |
| |
| |
| @property |
| def printable_mode(self): |
| """@return human readable mode string.""" |
| if self.is_11n: |
| return self.ht_packet_capture_mode |
| |
| return '11' + self.hw_mode.upper() |
| |
| |
| @property |
| def require_ht(self): |
| """@return True iff clients should be required to support HT.""" |
| # TODO(wiley) Why? (crbug.com/237370) |
| logging.warning('Not enforcing pure N mode because Snow does ' |
| 'not seem to support it...') |
| return False |
| |
| |
| @property |
| def ssid_suffix(self): |
| """@return meaningful suffix for SSID.""" |
| return '_ch%d' % self.channel |
| |
| |
| def __init__(self, mode=MODE_11B, channel=None, frequency=None, |
| n_capabilities=[], hide_ssid=None, beacon_interval=None, |
| dtim_period=None, frag_threshold=None, ssid=None, bssid=None, |
| force_wmm=None, security_config=None, |
| pmf_support=PMF_SUPPORT_DISABLED, |
| obss_interval=None): |
| """Construct a HostapConfig. |
| |
| You may specify channel or frequency, but not both. Both options |
| are checked for validity (i.e. you can't specify an invalid channel |
| or a frequency that will not be accepted). |
| |
| @param mode string MODE_11x defined above. |
| @param channel int channel number. |
| @param frequency int frequency of channel. |
| @param n_capabilities list of N_CAPABILITY_x defined above. |
| @param hide_ssid True if we should set up a hidden SSID. |
| @param beacon_interval int beacon interval of AP. |
| @param dtim_period int include a DTIM every |dtim_period| beacons. |
| @param frag_threshold int maximum outgoing data frame size. |
| @param ssid string up to 32 byte SSID overriding the router default. |
| @param bssid string like 00:11:22:33:44:55. |
| @param force_wmm True if we should force WMM on, False if we should |
| force it off, None if we shouldn't force anything. |
| @param security_config SecurityConfig object. |
| @param pmf_support one of PMF_SUPPORT_* above. Controls whether the |
| client supports/must support 802.11w. |
| @param obss_interval int interval in seconds that client should be |
| required to do background scans for overlapping BSSes. |
| |
| """ |
| super(HostapConfig, self).__init__() |
| if channel is not None and frequency is not None: |
| raise error.TestError('Specify either frequency or channel ' |
| 'but not both.') |
| |
| self.wmm_enabled = False |
| unknown_caps = [cap for cap in n_capabilities |
| if cap not in self.ALL_N_CAPABILITIES] |
| if unknown_caps: |
| raise error.TestError('Unknown capabilities: %r' % unknown_caps) |
| |
| self._n_capabilities = set(n_capabilities) |
| if self._n_capabilities: |
| self.wmm_enabled = True |
| if self._n_capabilities and mode is None: |
| mode = self.MODE_11N_PURE |
| self._mode = mode |
| |
| self._frequency = None |
| if channel: |
| self.channel = channel |
| elif frequency: |
| self.frequency = frequency |
| else: |
| raise error.TestError('Specify either frequency or channel.') |
| |
| if not self.supports_frequency(self.frequency): |
| raise error.TestFail('Configured a mode %s that does not support ' |
| 'frequency %d' % (self._mode, self.frequency)) |
| |
| self.hide_ssid = hide_ssid |
| self.beacon_interval = beacon_interval |
| self.dtim_period = dtim_period |
| self.frag_threshold = frag_threshold |
| if ssid and len(ssid) > 32: |
| raise error.TestFail('Tried to specify SSID that was too long.') |
| |
| self.ssid = ssid |
| self.bssid = bssid |
| if force_wmm is not None: |
| self.wmm_enabled = force_wmm |
| if pmf_support not in self.PMF_SUPPORT_VALUES: |
| raise error.TestFail('Invalid value for pmf_support: %r' % |
| pmf_support) |
| |
| self.pmf_support = pmf_support |
| self.security_config = (copy.copy(security_config) or |
| xmlrpc_security_types.SecurityConfig()) |
| self.obss_interval = obss_interval |
| |
| |
| def __repr__(self): |
| return ('%s(mode=%r, channel=%r, frequency=%r, ' |
| 'n_capabilities=%r, hide_ssid=%r, beacon_interval=%r, ' |
| 'dtim_period=%r, frag_threshold=%r, ssid=%r, bssid=%r, ' |
| 'wmm_enabled=%r, security_config=%r)' % ( |
| self.__class__.__name__, |
| self._mode, |
| self.channel, |
| self.frequency, |
| self._n_capabilities, |
| self.hide_ssid, |
| self.beacon_interval, |
| self.dtim_period, |
| self.frag_threshold, |
| self.ssid, |
| self.bssid, |
| self.wmm_enabled, |
| self.security_config)) |
| |
| |
| def get_security_hostapd_conf(self): |
| """@return dict of hostapd settings related to security.""" |
| return self.security_config.get_hostapd_config() |
| |
| |
| def supports_channel(self, value): |
| """Check whether channel is supported by the current hardware mode. |
| |
| @param value: int channel to check. |
| @return True iff the current mode supports the band of the channel. |
| |
| """ |
| for freq, channel in self.CHANNEL_MAP.iteritems(): |
| if channel == value: |
| return self.supports_frequency(freq) |
| |
| return False |
| |
| |
| def supports_frequency(self, frequency): |
| """Check whether frequency is supported by the current hardware mode. |
| |
| @param frequency: int frequency to check. |
| @return True iff the current mode supports the band of the frequency. |
| |
| """ |
| if self._mode == self.MODE_11A and frequency < 5000: |
| return False |
| |
| if self._mode in (self.MODE_11B, self.MODE_11G) and frequency > 5000: |
| return False |
| |
| if frequency not in self.CHANNEL_MAP: |
| return False |
| |
| channel = self.CHANNEL_MAP[frequency] |
| supports_plus = (channel in |
| self.HT40_ALLOW_MAP[self.N_CAPABILITY_HT40_PLUS]) |
| supports_minus = (channel in |
| self.HT40_ALLOW_MAP[self.N_CAPABILITY_HT40_MINUS]) |
| if (self.N_CAPABILITY_HT40_PLUS in self._n_capabilities and |
| not supports_plus): |
| return False |
| |
| if (self.N_CAPABILITY_HT40_MINUS in self._n_capabilities and |
| not supports_minus): |
| return False |
| |
| if (self.N_CAPABILITY_HT40 in self._n_capabilities and |
| not supports_plus and not supports_minus): |
| return False |
| |
| return True |