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 if pmf_support is None: 447 if self.security and self.security.wpa3: 448 self._pmf_support = hostapd_constants.PMF_SUPPORT_REQUIRED 449 else: 450 self._pmf_support = hostapd_constants.PMF_SUPPORT_DISABLED 451 elif pmf_support not in hostapd_constants.PMF_SUPPORT_VALUES: 452 raise ValueError('Invalid value for pmf_support: %r' % pmf_support) 453 elif (pmf_support != hostapd_constants.PMF_SUPPORT_REQUIRED 454 and self.security and self.security.wpa3): 455 raise ValueError('PMF support must be required with wpa3.') 456 else: 457 self._pmf_support = pmf_support 458 self._obss_interval = obss_interval 459 if self.is_11ac: 460 if str(vht_channel_width) == '40' or str( 461 vht_channel_width) == '20': 462 self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_40 463 elif str(vht_channel_width) == '80': 464 self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_80 465 elif str(vht_channel_width) == '160': 466 self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_160 467 elif str(vht_channel_width) == '80+80': 468 self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_80_80 469 elif vht_channel_width is not None: 470 raise ValueError('Invalid channel width') 471 else: 472 logging.warning( 473 'No channel bandwidth specified. Using 80MHz for 11ac.') 474 self._vht_oper_chwidth = 1 475 if vht_center_channel is not None: 476 self._vht_oper_centr_freq_seg0_idx = vht_center_channel 477 elif vht_channel_width == 20: 478 self._vht_oper_centr_freq_seg0_idx = channel 479 else: 480 self._vht_oper_centr_freq_seg0_idx = self._get_11ac_center_channel_from_channel( 481 self.channel) 482 self._ac_capabilities = set(ac_capabilities) 483 self._beacon_footer = beacon_footer 484 self._spectrum_mgmt_required = spectrum_mgmt_required 485 self._scenario_name = scenario_name 486 self._min_streams = min_streams 487 self._additional_parameters = additional_parameters 488 489 self._bss_lookup = collections.OrderedDict() 490 for bss in bss_settings: 491 if bss.name in self._bss_lookup: 492 raise ValueError('Cannot have multiple bss settings with the' 493 ' same name.') 494 self._bss_lookup[bss.name] = bss 495 496 def __repr__(self): 497 return ( 498 '%s(mode=%r, channel=%r, frequency=%r, ' 499 'n_capabilities=%r, beacon_interval=%r, ' 500 'dtim_period=%r, frag_threshold=%r, ssid=%r, bssid=%r, ' 501 'wmm_enabled=%r, security_config=%r, ' 502 'spectrum_mgmt_required=%r)' % 503 (self.__class__.__name__, self._mode, self.channel, self.frequency, 504 self._n_capabilities, self._beacon_interval, self._dtim_period, 505 self._frag_threshold, self._ssid, self._bssid, self._wmm_enabled, 506 self._security, self._spectrum_mgmt_required)) 507 508 def supports_channel(self, value): 509 """Check whether channel is supported by the current hardware mode. 510 511 @param value: int channel to check. 512 @return True iff the current mode supports the band of the channel. 513 514 """ 515 for freq, channel in hostapd_constants.CHANNEL_MAP.iteritems(): 516 if channel == value: 517 return self.supports_frequency(freq) 518 519 return False 520 521 def supports_frequency(self, frequency): 522 """Check whether frequency is supported by the current hardware mode. 523 524 @param frequency: int frequency to check. 525 @return True iff the current mode supports the band of the frequency. 526 527 """ 528 if self._mode == hostapd_constants.MODE_11A and frequency < 5000: 529 return False 530 531 if self._mode in (hostapd_constants.MODE_11B, 532 hostapd_constants.MODE_11G) and frequency > 5000: 533 return False 534 535 if frequency not in hostapd_constants.CHANNEL_MAP: 536 return False 537 538 channel = hostapd_constants.CHANNEL_MAP[frequency] 539 supports_plus = (channel in hostapd_constants.HT40_ALLOW_MAP[ 540 hostapd_constants.N_CAPABILITY_HT40_PLUS_CHANNELS]) 541 supports_minus = (channel in hostapd_constants.HT40_ALLOW_MAP[ 542 hostapd_constants.N_CAPABILITY_HT40_MINUS_CHANNELS]) 543 if (hostapd_constants.N_CAPABILITY_HT40_PLUS in self._n_capabilities 544 and not supports_plus): 545 return False 546 547 if (hostapd_constants.N_CAPABILITY_HT40_MINUS in self._n_capabilities 548 and not supports_minus): 549 return False 550 551 return True 552 553 def add_bss(self, bss): 554 """Adds a new bss setting. 555 556 Args: 557 bss: The bss settings to add. 558 """ 559 if bss.name in self._bss_lookup: 560 raise ValueError('A bss with the same name already exists.') 561 562 self._bss_lookup[bss.name] = bss 563 564 def remove_bss(self, bss_name): 565 """Removes a bss setting from the config.""" 566 del self._bss_lookup[bss_name] 567 568 def package_configs(self): 569 """Package the configs. 570 571 Returns: 572 A list of dictionaries, one dictionary for each section of the 573 config. 574 """ 575 # Start with the default config parameters. 576 conf = self._get_default_config 577 578 if self._interface: 579 conf['interface'] = self._interface 580 if self._bssid: 581 conf['bssid'] = self._bssid 582 if self._ssid: 583 conf['ssid'] = self._ssid 584 conf['ignore_broadcast_ssid'] = 1 if self._hidden else 0 585 conf['channel'] = self.channel 586 conf['hw_mode'] = self.hw_mode 587 if self.is_11n or self.is_11ac: 588 conf['ieee80211n'] = 1 589 conf['ht_capab'] = self._hostapd_ht_capabilities 590 if self.is_11ac: 591 conf['ieee80211ac'] = 1 592 conf['vht_oper_chwidth'] = self._vht_oper_chwidth 593 conf['vht_oper_centr_freq_seg0_idx'] = \ 594 self._vht_oper_centr_freq_seg0_idx 595 conf['vht_capab'] = self._hostapd_vht_capabilities 596 if self._wmm_enabled is not None: 597 conf['wmm_enabled'] = self._wmm_enabled 598 if self._require_ht: 599 conf['require_ht'] = 1 600 if self._require_vht: 601 conf['require_vht'] = 1 602 if self._beacon_interval: 603 conf['beacon_int'] = self._beacon_interval 604 if self._dtim_period: 605 conf['dtim_period'] = self._dtim_period 606 if self._frag_threshold: 607 conf['fragm_threshold'] = self._frag_threshold 608 if self._rts_threshold: 609 conf['rts_threshold'] = self._rts_threshold 610 if self._pmf_support: 611 conf['ieee80211w'] = self._pmf_support 612 if self._obss_interval: 613 conf['obss_interval'] = self._obss_interval 614 if self._short_preamble: 615 conf['preamble'] = 1 616 if self._spectrum_mgmt_required: 617 # To set spectrum_mgmt_required, we must first set 618 # local_pwr_constraint. And to set local_pwr_constraint, 619 # we must first set ieee80211d. And to set ieee80211d, ... 620 # Point being: order matters here. 621 conf['country_code'] = 'US' # Required for local_pwr_constraint 622 conf['ieee80211d'] = 1 # Required for local_pwr_constraint 623 conf['local_pwr_constraint'] = 0 # No local constraint 624 conf['spectrum_mgmt_required'] = 1 # Requires local_pwr_constraint 625 626 if self._security: 627 for k, v in self._security.generate_dict().items(): 628 conf[k] = v 629 630 all_conf = [conf] 631 632 for bss in self._bss_lookup.values(): 633 bss_conf = collections.OrderedDict() 634 for k, v in (bss.generate_dict()).items(): 635 bss_conf[k] = v 636 all_conf.append(bss_conf) 637 638 if self._additional_parameters: 639 all_conf.append(self._additional_parameters) 640 641 return all_conf 642