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