• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#   Copyright 2016 - The Android Open Source Project
2#
3#   Licensed under the Apache License, Version 2.0 (the "License");
4#   you may not use this file except in compliance with the License.
5#   You may obtain a copy of the License at
6#
7#       http://www.apache.org/licenses/LICENSE-2.0
8#
9#   Unless required by applicable law or agreed to in writing, software
10#   distributed under the License is distributed on an "AS IS" BASIS,
11#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12#   See the License for the specific language governing permissions and
13#   limitations under the License.
14
15import enum
16import logging
17import os
18import collections
19import itertools
20
21from acts.controllers.ap_lib import hostapd_constants
22
23
24def ht40_plus_allowed(channel):
25    """Returns: True iff HT40+ is enabled for this configuration."""
26    channel_supported = (channel in hostapd_constants.HT40_ALLOW_MAP[
27        hostapd_constants.N_CAPABILITY_HT40_PLUS_CHANNELS])
28    return (channel_supported)
29
30
31def ht40_minus_allowed(channel):
32    """Returns: True iff HT40- is enabled for this configuration."""
33    channel_supported = (channel in hostapd_constants.HT40_ALLOW_MAP[
34        hostapd_constants.N_CAPABILITY_HT40_MINUS_CHANNELS])
35    return (channel_supported)
36
37
38def get_frequency_for_channel(channel):
39    """The frequency associated with a given channel number.
40
41    Args:
42        value: int channel number.
43
44    Returns:
45        int, frequency in MHz associated with the channel.
46
47    """
48    for frequency, channel_iter in \
49        hostapd_constants.CHANNEL_MAP.items():
50        if channel == channel_iter:
51            return frequency
52    else:
53        raise ValueError('Unknown channel value: %r.' % channel)
54
55
56def get_channel_for_frequency(frequency):
57    """The channel number associated with a given frequency.
58
59    Args:
60        value: int frequency in MHz.
61
62    Returns:
63        int, frequency associated with the channel.
64
65    """
66    return hostapd_constants.CHANNEL_MAP[frequency]
67
68
69class HostapdConfig(object):
70    """The root settings for the router.
71
72    All the settings for a router that are not part of an ssid.
73    """
74
75    def _get_11ac_center_channel_from_channel(self, channel):
76        """Returns the center channel of the selected channel band based
77           on the channel and channel bandwidth provided.
78        """
79        channel = int(channel)
80        center_channel_delta = hostapd_constants.CENTER_CHANNEL_MAP[
81            self._vht_oper_chwidth]['delta']
82
83        for channel_map in hostapd_constants.CENTER_CHANNEL_MAP[
84                self._vht_oper_chwidth]['channels']:
85            lower_channel_bound, upper_channel_bound = channel_map
86            if lower_channel_bound <= channel <= upper_channel_bound:
87                return lower_channel_bound + center_channel_delta
88        raise ValueError('Invalid channel for {channel_width}.'.format(
89            channel_width=self._vht_oper_chwidth))
90
91    @property
92    def _get_default_config(self):
93        """Returns: dict of default options for hostapd."""
94        return collections.OrderedDict([
95            ('logger_syslog', '-1'),
96            ('logger_syslog_level', '0'),
97            # default RTS and frag threshold to ``off''
98            ('rts_threshold', '2347'),
99            ('fragm_threshold', '2346'),
100            ('driver', hostapd_constants.DRIVER_NAME)
101        ])
102
103    @property
104    def _hostapd_ht_capabilities(self):
105        """Returns: string suitable for the ht_capab= line in a hostapd config.
106        """
107        ret = []
108        for cap in hostapd_constants.N_CAPABILITIES_MAPPING.keys():
109            if cap in self._n_capabilities:
110                ret.append(hostapd_constants.N_CAPABILITIES_MAPPING[cap])
111        return ''.join(ret)
112
113    @property
114    def _hostapd_vht_capabilities(self):
115        """Returns: string suitable for the vht_capab= line in a hostapd config.
116        """
117        ret = []
118        for cap in hostapd_constants.AC_CAPABILITIES_MAPPING.keys():
119            if cap in self._ac_capabilities:
120                ret.append(hostapd_constants.AC_CAPABILITIES_MAPPING[cap])
121        return ''.join(ret)
122
123    @property
124    def _require_ht(self):
125        """Returns: True iff clients should be required to support HT."""
126        # TODO(wiley) Why? (crbug.com/237370)
127        # DOES THIS APPLY TO US?
128        logging.warning('Not enforcing pure N mode because Snow does '
129                        'not seem to support it...')
130        return False
131
132    @property
133    def _require_vht(self):
134        """Returns: True if clients should be required to support VHT."""
135        return self._mode == hostapd_constants.MODE_11AC_PURE
136
137    @property
138    def hw_mode(self):
139        """Returns: string hardware mode understood by hostapd."""
140        if self._mode == hostapd_constants.MODE_11A:
141            return hostapd_constants.MODE_11A
142        if self._mode == hostapd_constants.MODE_11B:
143            return hostapd_constants.MODE_11B
144        if self._mode == hostapd_constants.MODE_11G:
145            return hostapd_constants.MODE_11G
146        if self.is_11n or self.is_11ac:
147            # For their own historical reasons, hostapd wants it this way.
148            if self._frequency > 5000:
149                return hostapd_constants.MODE_11A
150            return hostapd_constants.MODE_11G
151        raise ValueError('Invalid mode.')
152
153    @property
154    def is_11n(self):
155        """Returns: True if we're trying to host an 802.11n network."""
156        return self._mode in (hostapd_constants.MODE_11N_MIXED,
157                              hostapd_constants.MODE_11N_PURE)
158
159    @property
160    def is_11ac(self):
161        """Returns: True if we're trying to host an 802.11ac network."""
162        return self._mode in (hostapd_constants.MODE_11AC_MIXED,
163                              hostapd_constants.MODE_11AC_PURE)
164
165    @property
166    def channel(self):
167        """Returns: int channel number for self.frequency."""
168        return get_channel_for_frequency(self.frequency)
169
170    @channel.setter
171    def channel(self, value):
172        """Sets the channel number to configure hostapd to listen on.
173
174        Args:
175            value: int, channel number.
176
177        """
178        self.frequency = get_frequency_for_channel(value)
179
180    @property
181    def bssid(self):
182        return self._bssid
183
184    @bssid.setter
185    def bssid(self, value):
186        self._bssid = value
187
188    @property
189    def frequency(self):
190        """Returns: int, frequency for hostapd to listen on."""
191        return self._frequency
192
193    @frequency.setter
194    def frequency(self, value):
195        """Sets the frequency for hostapd to listen on.
196
197        Args:
198            value: int, frequency in MHz.
199
200        """
201        if value not in hostapd_constants.CHANNEL_MAP:
202            raise ValueError('Tried to set an invalid frequency: %r.' % value)
203
204        self._frequency = value
205
206    @property
207    def bss_lookup(self):
208        return self._bss_lookup
209
210    @property
211    def ssid(self):
212        """Returns: SsidSettings, The root Ssid settings being used."""
213        return self._ssid
214
215    @ssid.setter
216    def ssid(self, value):
217        """Sets the ssid for the hostapd.
218
219        Args:
220            value: SsidSettings, new ssid settings to use.
221
222        """
223        self._ssid = value
224
225    @property
226    def hidden(self):
227        """Returns: bool, True if the ssid is hidden, false otherwise."""
228        return self._hidden
229
230    @hidden.setter
231    def hidden(self, value):
232        """Sets if this ssid is hidden.
233
234        Args:
235            value: bool, If true the ssid will be hidden.
236        """
237        self.hidden = value
238
239    @property
240    def security(self):
241        """Returns: The security type being used."""
242        return self._security
243
244    @security.setter
245    def security(self, value):
246        """Sets the security options to use.
247
248        Args:
249            value: Security, The type of security to use.
250        """
251        self._security = value
252
253    @property
254    def ht_packet_capture_mode(self):
255        """Get an appropriate packet capture HT parameter.
256
257        When we go to configure a raw monitor we need to configure
258        the phy to listen on the correct channel.  Part of doing
259        so is to specify the channel width for HT channels.  In the
260        case that the AP is configured to be either HT40+ or HT40-,
261        we could return the wrong parameter because we don't know which
262        configuration will be chosen by hostap.
263
264        Returns:
265            string, HT parameter for frequency configuration.
266
267        """
268        if not self.is_11n:
269            return None
270
271        if ht40_plus_allowed(self.channel):
272            return 'HT40+'
273
274        if ht40_minus_allowed(self.channel):
275            return 'HT40-'
276
277        return 'HT20'
278
279    @property
280    def beacon_footer(self):
281        """Returns: bool _beacon_footer value."""
282        return self._beacon_footer
283
284    def beacon_footer(self, value):
285        """Changes the beacon footer.
286
287        Args:
288            value: bool, The beacon footer vlaue.
289        """
290        self._beacon_footer = value
291
292    @property
293    def scenario_name(self):
294        """Returns: string _scenario_name value, or None."""
295        return self._scenario_name
296
297    @property
298    def min_streams(self):
299        """Returns: int, _min_streams value, or None."""
300        return self._min_streams
301
302    def __init__(self,
303                 interface=None,
304                 mode=None,
305                 channel=None,
306                 frequency=None,
307                 n_capabilities=[],
308                 beacon_interval=None,
309                 dtim_period=None,
310                 frag_threshold=None,
311                 short_preamble=None,
312                 ssid=None,
313                 hidden=False,
314                 security=None,
315                 bssid=None,
316                 force_wmm=None,
317                 pmf_support=hostapd_constants.PMF_SUPPORT_DISABLED,
318                 obss_interval=None,
319                 vht_channel_width=None,
320                 vht_center_channel=None,
321                 ac_capabilities=[],
322                 beacon_footer='',
323                 spectrum_mgmt_required=None,
324                 scenario_name=None,
325                 min_streams=None,
326                 bss_settings=[],
327                 set_ap_defaults_model=None):
328        """Construct a HostapdConfig.
329
330        You may specify channel or frequency, but not both.  Both options
331        are checked for validity (i.e. you can't specify an invalid channel
332        or a frequency that will not be accepted).
333
334        Args:
335            interface: string, The name of the interface to use.
336            mode: string, MODE_11x defined above.
337            channel: int, channel number.
338            frequency: int, frequency of channel.
339            n_capabilities: list of N_CAPABILITY_x defined above.
340            beacon_interval: int, beacon interval of AP.
341            dtim_period: int, include a DTIM every |dtim_period| beacons.
342            frag_threshold: int, maximum outgoing data frame size.
343            short_preamble: Whether to use a short preamble.
344            ssid: string, The name of the ssid to brodcast.
345            hidden: bool, Should the ssid be hidden.
346            security: Security, the secuirty settings to use.
347            bssid: string, a MAC address like string for the BSSID.
348            force_wmm: True if we should force WMM on, False if we should
349                force it off, None if we shouldn't force anything.
350            pmf_support: one of PMF_SUPPORT_* above.  Controls whether the
351                client supports/must support 802.11w.
352            obss_interval: int, interval in seconds that client should be
353                required to do background scans for overlapping BSSes.
354            vht_channel_width: object channel width
355            vht_center_channel: int, center channel of segment 0.
356            ac_capabilities: list of AC_CAPABILITY_x defined above.
357            beacon_footer: string, containing (unvalidated) IE data to be
358                placed at the end of the beacon.
359            spectrum_mgmt_required: True if we require the DUT to support
360                spectrum management.
361            scenario_name: string to be included in file names, instead
362                of the interface name.
363            min_streams: int, number of spatial streams required.
364            control_interface: The file name to use as the control interface.
365            bss_settings: The settings for all bss.
366        """
367        self._interface = interface
368        if channel is not None and frequency is not None:
369            raise ValueError('Specify either frequency or channel '
370                             'but not both.')
371
372        self._wmm_enabled = False
373        unknown_caps = [
374            cap for cap in n_capabilities
375            if cap not in hostapd_constants.N_CAPABILITIES_MAPPING
376        ]
377        if unknown_caps:
378            raise ValueError('Unknown capabilities: %r' % unknown_caps)
379
380        self._frequency = None
381        if channel:
382            self.channel = channel
383        elif frequency:
384            self.frequency = frequency
385        else:
386            raise ValueError('Specify either frequency or channel.')
387        '''
388        if set_ap_defaults_model:
389            ap_default_config = hostapd_ap_default_configs.APDefaultConfig(
390                profile_name=set_ap_defaults_model, frequency=self.frequency)
391            force_wmm = ap_default_config.force_wmm
392            beacon_interval = ap_default_config.beacon_interval
393            dtim_period = ap_default_config.dtim_period
394            short_preamble = ap_default_config.short_preamble
395            self._interface = ap_default_config.interface
396            mode = ap_default_config.mode
397            if ap_default_config.n_capabilities:
398                n_capabilities = ap_default_config.n_capabilities
399            if ap_default_config.ac_capabilities:
400                ap_default_config = ap_default_config.ac_capabilities
401        '''
402
403        self._n_capabilities = set(n_capabilities)
404        if self._n_capabilities:
405            self._wmm_enabled = True
406        if self._n_capabilities and mode is None:
407            mode = hostapd_constants.MODE_11N_PURE
408        self._mode = mode
409
410        if not self.supports_frequency(self.frequency):
411            raise ValueError('Configured a mode %s that does not support '
412                             'frequency %d' % (self._mode, self.frequency))
413
414        self._beacon_interval = beacon_interval
415        self._dtim_period = dtim_period
416        self._frag_threshold = frag_threshold
417        self._short_preamble = short_preamble
418
419        self._ssid = ssid
420        self._hidden = hidden
421        self._security = security
422        self._bssid = bssid
423        if force_wmm is not None:
424            self._wmm_enabled = force_wmm
425        if pmf_support not in hostapd_constants.PMF_SUPPORT_VALUES:
426            raise ValueError('Invalid value for pmf_support: %r' % pmf_support)
427
428        self._pmf_support = pmf_support
429        self._obss_interval = obss_interval
430        if self.is_11ac:
431            if str(vht_channel_width) == '40' or str(
432                    vht_channel_width) == '20':
433                self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_40
434            elif str(vht_channel_width) == '80':
435                self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_80
436            elif str(vht_channel_width) == '160':
437                self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_160
438            elif str(vht_channel_width) == '80+80':
439                self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_80_80
440            elif vht_channel_width is not None:
441                raise ValueError('Invalid channel width')
442            else:
443                logging.warning(
444                    'No channel bandwidth specified.  Using 80MHz for 11ac.')
445                self._vht_oper_chwidth = 1
446            if not vht_channel_width == 20:
447                if not vht_center_channel:
448                    self._vht_oper_centr_freq_seg0_idx = self._get_11ac_center_channel_from_channel(
449                        self.channel)
450            else:
451                self._vht_oper_centr_freq_seg0_idx = vht_center_channel
452            self._ac_capabilities = set(ac_capabilities)
453        self._beacon_footer = beacon_footer
454        self._spectrum_mgmt_required = spectrum_mgmt_required
455        self._scenario_name = scenario_name
456        self._min_streams = min_streams
457
458        self._bss_lookup = collections.OrderedDict()
459        for bss in bss_settings:
460            if bss.name in self._bss_lookup:
461                raise ValueError('Cannot have multiple bss settings with the'
462                                 ' same name.')
463            self._bss_lookup[bss.name] = bss
464
465    def __repr__(self):
466        return ('%s(mode=%r, channel=%r, frequency=%r, '
467                'n_capabilities=%r, beacon_interval=%r, '
468                'dtim_period=%r, frag_threshold=%r, ssid=%r, bssid=%r, '
469                'wmm_enabled=%r, security_config=%r, '
470                'spectrum_mgmt_required=%r)' %
471                (self.__class__.__name__, self._mode, self.channel,
472                 self.frequency, self._n_capabilities, self._beacon_interval,
473                 self._dtim_period, self._frag_threshold, self._ssid,
474                 self._bssid, self._wmm_enabled, self._security,
475                 self._spectrum_mgmt_required))
476
477    def supports_channel(self, value):
478        """Check whether channel is supported by the current hardware mode.
479
480        @param value: int channel to check.
481        @return True iff the current mode supports the band of the channel.
482
483        """
484        for freq, channel in hostapd_constants.CHANNEL_MAP.iteritems():
485            if channel == value:
486                return self.supports_frequency(freq)
487
488        return False
489
490    def supports_frequency(self, frequency):
491        """Check whether frequency is supported by the current hardware mode.
492
493        @param frequency: int frequency to check.
494        @return True iff the current mode supports the band of the frequency.
495
496        """
497        if self._mode == hostapd_constants.MODE_11A and frequency < 5000:
498            return False
499
500        if self._mode in (hostapd_constants.MODE_11B,
501                          hostapd_constants.MODE_11G) and frequency > 5000:
502            return False
503
504        if frequency not in hostapd_constants.CHANNEL_MAP:
505            return False
506
507        channel = hostapd_constants.CHANNEL_MAP[frequency]
508        supports_plus = (channel in hostapd_constants.HT40_ALLOW_MAP[
509            hostapd_constants.N_CAPABILITY_HT40_PLUS_CHANNELS])
510        supports_minus = (channel in hostapd_constants.HT40_ALLOW_MAP[
511            hostapd_constants.N_CAPABILITY_HT40_MINUS_CHANNELS])
512        if (hostapd_constants.N_CAPABILITY_HT40_PLUS in self._n_capabilities
513                and not supports_plus):
514            return False
515
516        if (hostapd_constants.N_CAPABILITY_HT40_MINUS in self._n_capabilities
517                and not supports_minus):
518            return False
519
520        return True
521
522    def add_bss(self, bss):
523        """Adds a new bss setting.
524
525        Args:
526            bss: The bss settings to add.
527        """
528        if bss.name in self._bss_lookup:
529            raise ValueError('A bss with the same name already exists.')
530
531        self._bss_lookup[bss.name] = bss
532
533    def remove_bss(self, bss_name):
534        """Removes a bss setting from the config."""
535        del self._bss_lookup[bss_name]
536
537    def package_configs(self):
538        """Package the configs.
539
540        Returns:
541            A list of dictionaries, one dictionary for each section of the
542            config.
543        """
544        # Start with the default config parameters.
545        conf = self._get_default_config
546        if self._interface:
547            conf['interface'] = self._interface
548        if self._bssid:
549            conf['bssid'] = self._bssid
550        if self._ssid:
551            conf['ssid'] = self._ssid
552            conf['ignore_broadcast_ssid'] = 1 if self._hidden else 0
553        conf['channel'] = self.channel
554        conf['hw_mode'] = self.hw_mode
555        if self.is_11n or self.is_11ac:
556            conf['ieee80211n'] = 1
557            conf['ht_capab'] = self._hostapd_ht_capabilities
558        if self.is_11ac:
559            conf['ieee80211ac'] = 1
560            conf['vht_oper_chwidth'] = self._vht_oper_chwidth
561            conf['vht_oper_centr_freq_seg0_idx'] = \
562                    self._vht_oper_centr_freq_seg0_idx
563            conf['vht_capab'] = self._hostapd_vht_capabilities
564        if self._wmm_enabled:
565            conf['wmm_enabled'] = 1
566        if self._require_ht:
567            conf['require_ht'] = 1
568        if self._require_vht:
569            conf['require_vht'] = 1
570        if self._beacon_interval:
571            conf['beacon_int'] = self._beacon_interval
572        if self._dtim_period:
573            conf['dtim_period'] = self._dtim_period
574        if self._frag_threshold:
575            conf['fragm_threshold'] = self._frag_threshold
576        if self._pmf_support:
577            conf['ieee80211w'] = self._pmf_support
578        if self._obss_interval:
579            conf['obss_interval'] = self._obss_interval
580        if self._short_preamble:
581            conf['preamble'] = 1
582        if self._spectrum_mgmt_required:
583            # To set spectrum_mgmt_required, we must first set
584            # local_pwr_constraint. And to set local_pwr_constraint,
585            # we must first set ieee80211d. And to set ieee80211d, ...
586            # Point being: order matters here.
587            conf['country_code'] = 'US'  # Required for local_pwr_constraint
588            conf['ieee80211d'] = 1  # Required for local_pwr_constraint
589            conf['local_pwr_constraint'] = 0  # No local constraint
590            conf['spectrum_mgmt_required'] = 1  # Requires local_pwr_constraint
591
592        if self._security:
593            for k, v in self._security.generate_dict().items():
594                conf[k] = v
595
596        all_conf = [conf]
597
598        for bss in self._bss_lookup.values():
599            bss_conf = collections.OrderedDict()
600            for k, v in (bss.generate_dict()).items():
601                bss_conf[k] = v
602            all_conf.append(bss_conf)
603
604        return all_conf
605