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