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