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