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