1#!/usr/bin/env python3 2# 3# Copyright 2017 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import fcntl 18import os 19import selenium 20import splinter 21import time 22from acts import logger 23from acts.controllers import access_point 24from acts.controllers.ap_lib import bridge_interface 25from acts.controllers.ap_lib import hostapd_security 26from acts.controllers.ap_lib import hostapd_ap_preset 27 28BROWSER_WAIT_SHORT = 1 29BROWSER_WAIT_MED = 3 30BROWSER_WAIT_LONG = 30 31BROWSER_WAIT_EXTRA_LONG = 60 32 33 34def create(configs): 35 """Factory method for retail AP class. 36 37 Args: 38 configs: list of dicts containing ap settings. ap settings must contain 39 the following: brand, model, ip_address, username and password 40 """ 41 SUPPORTED_APS = { 42 ("Netgear", "R7000"): "NetgearR7000AP", 43 ("Netgear", "R7000NA"): "NetgearR7000NAAP", 44 ("Netgear", "R7500"): "NetgearR7500AP", 45 ("Netgear", "R7800"): "NetgearR7800AP", 46 ("Netgear", "R8000"): "NetgearR8000AP", 47 ("Netgear", "R8500"): "NetgearR8500AP", 48 ("Netgear", "RAX"): "NetgearRAXAP", 49 ("Google", "Wifi"): "GoogleWifiAP" 50 } 51 objs = [] 52 for config in configs: 53 try: 54 ap_class_name = SUPPORTED_APS[(config["brand"], config["model"])] 55 ap_class = globals()[ap_class_name] 56 except KeyError: 57 raise KeyError("Invalid retail AP brand and model combination.") 58 objs.append(ap_class(config)) 59 return objs 60 61 62def detroy(objs): 63 for obj in objs: 64 obj.teardown() 65 66 67class BlockingBrowser(splinter.driver.webdriver.chrome.WebDriver): 68 """Class that implements a blocking browser session on top of selenium. 69 70 The class inherits from and builds upon splinter/selenium's webdriver class 71 and makes sure that only one such webdriver is active on a machine at any 72 single time. The class ensures single session operation using a lock file. 73 The class is to be used within context managers (e.g. with statements) to 74 ensure locks are always properly released. 75 """ 76 def __init__(self, headless, timeout): 77 """Constructor for BlockingBrowser class. 78 79 Args: 80 headless: boolean to control visible/headless browser operation 81 timeout: maximum time allowed to launch browser 82 """ 83 self.log = logger.create_tagged_trace_logger("ChromeDriver") 84 self.chrome_options = splinter.driver.webdriver.chrome.Options() 85 self.chrome_options.add_argument("--no-proxy-server") 86 self.chrome_options.add_argument("--no-sandbox") 87 self.chrome_options.add_argument("--allow-running-insecure-content") 88 self.chrome_options.add_argument("--ignore-certificate-errors") 89 self.chrome_capabilities = selenium.webdriver.common.desired_capabilities.DesiredCapabilities.CHROME.copy( 90 ) 91 self.chrome_capabilities["acceptSslCerts"] = True 92 self.chrome_capabilities["acceptInsecureCerts"] = True 93 if headless: 94 self.chrome_options.add_argument("--headless") 95 self.chrome_options.add_argument("--disable-gpu") 96 self.lock_file_path = "/usr/local/bin/chromedriver" 97 self.timeout = timeout 98 99 def __enter__(self): 100 """Entry context manager for BlockingBrowser. 101 102 The enter context manager for BlockingBrowser attempts to lock the 103 browser file. If successful, it launches and returns a chromedriver 104 session. If an exception occurs while starting the browser, the lock 105 file is released. 106 """ 107 self.lock_file = open(self.lock_file_path, "r") 108 start_time = time.time() 109 while time.time() < start_time + self.timeout: 110 try: 111 fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) 112 except BlockingIOError: 113 time.sleep(BROWSER_WAIT_SHORT) 114 continue 115 try: 116 self.driver = selenium.webdriver.Chrome( 117 options=self.chrome_options, 118 desired_capabilities=self.chrome_capabilities) 119 self.element_class = splinter.driver.webdriver.WebDriverElement 120 self._cookie_manager = splinter.driver.webdriver.cookie_manager.CookieManager( 121 self.driver) 122 super(splinter.driver.webdriver.chrome.WebDriver, 123 self).__init__(2) 124 return super(BlockingBrowser, self).__enter__() 125 except: 126 fcntl.flock(self.lock_file, fcntl.LOCK_UN) 127 self.lock_file.close() 128 raise RuntimeError("Error starting browser. " 129 "Releasing lock file.") 130 raise TimeoutError("Could not start chrome browser in time.") 131 132 def __exit__(self, exc_type, exc_value, traceback): 133 """Exit context manager for BlockingBrowser. 134 135 The exit context manager simply calls the parent class exit and 136 releases the lock file. 137 """ 138 try: 139 super(BlockingBrowser, self).__exit__(exc_type, exc_value, 140 traceback) 141 except: 142 raise RuntimeError("Failed to quit browser. Releasing lock file.") 143 finally: 144 fcntl.flock(self.lock_file, fcntl.LOCK_UN) 145 self.lock_file.close() 146 147 def restart(self): 148 """Method to restart browser session without releasing lock file.""" 149 self.quit() 150 self.__enter__() 151 152 def visit_persistent(self, 153 url, 154 page_load_timeout, 155 num_tries, 156 backup_url="about:blank", 157 check_for_element=None): 158 """Method to visit webpages and retry upon failure. 159 160 The function visits a web page and checks the the resulting URL matches 161 the intended URL, i.e. no redirects have happened 162 163 Args: 164 url: the intended url 165 page_load_timeout: timeout for page visits 166 num_tries: number of tries before url is declared unreachable 167 backup_url: url to visit if first url is not reachable. This can be 168 used to simply refresh the browser and try again or to re-login to 169 the AP 170 check_for_element: element id to check for existence on page 171 """ 172 self.driver.set_page_load_timeout(page_load_timeout) 173 for idx in range(num_tries): 174 try: 175 self.visit(url) 176 except: 177 self.restart() 178 179 page_reached = self.url.split("/")[-1] == url.split("/")[-1] 180 if check_for_element: 181 time.sleep(BROWSER_WAIT_MED) 182 element = self.find_by_id(check_for_element) 183 if not element: 184 page_reached = 0 185 if page_reached: 186 break 187 else: 188 try: 189 self.visit(backup_url) 190 except: 191 self.restart() 192 193 if idx == num_tries - 1: 194 self.log.error("URL unreachable. Current URL: {}".format( 195 self.url)) 196 raise RuntimeError("URL unreachable.") 197 198 199class WifiRetailAP(object): 200 """Base class implementation for retail ap. 201 202 Base class provides functions whose implementation is shared by all aps. 203 If some functions such as set_power not supported by ap, checks will raise 204 exceptions. 205 """ 206 def __init__(self, ap_settings): 207 self.ap_settings = ap_settings.copy() 208 self.log = logger.create_tagged_trace_logger("AccessPoint|{}".format( 209 self._get_control_ip_address())) 210 # Lock AP 211 if self.ap_settings.get('lock_ap', 0): 212 self.lock_timeout = self.ap_settings.get('lock_timeout', 3600) 213 self._lock_ap() 214 215 def teardown(self): 216 """Function to perform destroy operations.""" 217 self._unlock_ap() 218 219 def reset(self): 220 """Function that resets AP. 221 222 Function implementation is AP dependent and intended to perform any 223 necessary reset operations as part of controller destroy. 224 """ 225 pass 226 227 def read_ap_settings(self): 228 """Function that reads current ap settings. 229 230 Function implementation is AP dependent and thus base class raises exception 231 if function not implemented in child class. 232 """ 233 raise NotImplementedError 234 235 def validate_ap_settings(self): 236 """Function to validate ap settings. 237 238 This function compares the actual ap settings read from the web GUI 239 with the assumed settings saved in the AP object. When called after AP 240 configuration, this method helps ensure that our configuration was 241 successful. 242 Note: Calling this function updates the stored ap_settings 243 244 Raises: 245 ValueError: If read AP settings do not match stored settings. 246 """ 247 assumed_ap_settings = self.ap_settings.copy() 248 actual_ap_settings = self.read_ap_settings() 249 if assumed_ap_settings != actual_ap_settings: 250 self.log.warning( 251 "Discrepancy in AP settings. Some settings may have been overwritten." 252 ) 253 254 def configure_ap(self, **config_flags): 255 """Function that configures ap based on values of ap_settings. 256 257 Function implementation is AP dependent and thus base class raises exception 258 if function not implemented in child class. 259 260 Args: 261 config_flags: optional configuration flags 262 """ 263 raise NotImplementedError 264 265 def set_region(self, region): 266 """Function that sets AP region. 267 268 This function sets the region for the AP. Note that this may overwrite 269 channel and bandwidth settings in cases where the new region does not 270 support the current wireless configuration. 271 272 Args: 273 region: string indicating AP region 274 """ 275 self.log.warning("Updating region may overwrite wireless settings.") 276 setting_to_update = {"region": region} 277 self.update_ap_settings(setting_to_update) 278 279 def set_radio_on_off(self, network, status): 280 """Function that turns the radio on or off. 281 282 Args: 283 network: string containing network identifier (2G, 5G_1, 5G_2) 284 status: boolean indicating on or off (0: off, 1: on) 285 """ 286 setting_to_update = {"status_{}".format(network): int(status)} 287 self.update_ap_settings(setting_to_update) 288 289 def set_ssid(self, network, ssid): 290 """Function that sets network SSID. 291 292 Args: 293 network: string containing network identifier (2G, 5G_1, 5G_2) 294 ssid: string containing ssid 295 """ 296 setting_to_update = {"ssid_{}".format(network): str(ssid)} 297 self.update_ap_settings(setting_to_update) 298 299 def set_channel(self, network, channel): 300 """Function that sets network channel. 301 302 Args: 303 network: string containing network identifier (2G, 5G_1, 5G_2) 304 channel: string or int containing channel 305 """ 306 setting_to_update = {"channel_{}".format(network): str(channel)} 307 self.update_ap_settings(setting_to_update) 308 309 def set_bandwidth(self, network, bandwidth): 310 """Function that sets network bandwidth/mode. 311 312 Args: 313 network: string containing network identifier (2G, 5G_1, 5G_2) 314 bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80. 315 """ 316 setting_to_update = {"bandwidth_{}".format(network): str(bandwidth)} 317 self.update_ap_settings(setting_to_update) 318 319 def set_power(self, network, power): 320 """Function that sets network transmit power. 321 322 Args: 323 network: string containing network identifier (2G, 5G_1, 5G_2) 324 power: string containing power level, e.g., 25%, 100% 325 """ 326 setting_to_update = {"power_{}".format(network): str(power)} 327 self.update_ap_settings(setting_to_update) 328 329 def set_security(self, network, security_type, *password): 330 """Function that sets network security setting and password. 331 332 Args: 333 network: string containing network identifier (2G, 5G_1, 5G_2) 334 security: string containing security setting, e.g., WPA2-PSK 335 password: optional argument containing password 336 """ 337 if (len(password) == 1) and (type(password[0]) == str): 338 setting_to_update = { 339 "security_type_{}".format(network): str(security_type), 340 "password_{}".format(network): str(password[0]) 341 } 342 else: 343 setting_to_update = { 344 "security_type_{}".format(network): str(security_type) 345 } 346 self.update_ap_settings(setting_to_update) 347 348 def set_rate(self): 349 """Function that configures rate used by AP. 350 351 Function implementation is not supported by most APs and thus base 352 class raises exception if function not implemented in child class. 353 """ 354 raise NotImplementedError 355 356 def update_ap_settings(self, dict_settings={}, **named_settings): 357 """Function to update settings of existing AP. 358 359 Function copies arguments into ap_settings and calls configure_retail_ap 360 to apply them. 361 362 Args: 363 *dict_settings accepts single dictionary of settings to update 364 **named_settings accepts named settings to update 365 Note: dict and named_settings cannot contain the same settings. 366 """ 367 settings_to_update = dict(dict_settings, **named_settings) 368 if len(settings_to_update) != len(dict_settings) + len(named_settings): 369 raise KeyError("The following keys were passed twice: {}".format( 370 (set(dict_settings.keys()).intersection( 371 set(named_settings.keys()))))) 372 if not set(settings_to_update.keys()).issubset( 373 set(self.ap_settings.keys())): 374 raise KeyError( 375 "The following settings are invalid for this AP: {}".format( 376 set(settings_to_update.keys()).difference( 377 set(self.ap_settings.keys())))) 378 379 updates_requested = False 380 status_toggle_flag = False 381 for setting, value in settings_to_update.items(): 382 if self.ap_settings[setting] != value: 383 self.ap_settings[setting] = value 384 if "status" in setting: 385 status_toggle_flag = True 386 updates_requested = True 387 388 if updates_requested: 389 self.configure_ap(status_toggled=status_toggle_flag) 390 391 def band_lookup_by_channel(self, channel): 392 """Function that gives band name by channel number. 393 394 Args: 395 channel: channel number to lookup 396 Returns: 397 band: name of band which this channel belongs to on this ap 398 """ 399 for key, value in self.channel_band_map.items(): 400 if channel in value: 401 return key 402 raise ValueError("Invalid channel passed in argument.") 403 404 def _get_control_ip_address(self): 405 """Function to get AP's Control Interface IP address.""" 406 if "ssh_config" in self.ap_settings.keys(): 407 return self.ap_settings["ssh_config"]["host"] 408 else: 409 return self.ap_settings["ip_address"] 410 411 def _lock_ap(self): 412 """Function to lock the ap while tests are running.""" 413 self.lock_file_path = "/tmp/{}_{}_{}.lock".format( 414 self.ap_settings['brand'], self.ap_settings['model'], 415 self._get_control_ip_address()) 416 if not os.path.exists(self.lock_file_path): 417 with open(self.lock_file_path, 'w'): 418 pass 419 self.lock_file = open(self.lock_file_path, "r") 420 start_time = time.time() 421 self.log.info('Trying to acquire AP lock.') 422 while time.time() < start_time + self.lock_timeout: 423 try: 424 fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) 425 except BlockingIOError: 426 time.sleep(BROWSER_WAIT_SHORT) 427 continue 428 self.log.info('AP lock acquired.') 429 return 430 raise RuntimeError("Could not lock AP in time.") 431 432 def _unlock_ap(self): 433 """Function to unlock the AP when tests are done.""" 434 self.log.info('Releasing AP lock.') 435 if hasattr(self, "lock_file"): 436 fcntl.flock(self.lock_file, fcntl.LOCK_UN) 437 self.lock_file.close() 438 439 440class NetgearR7000AP(WifiRetailAP): 441 """Class that implements Netgear R7000 AP.""" 442 def __init__(self, ap_settings): 443 super().__init__(ap_settings) 444 self.init_gui_data() 445 # Read and update AP settings 446 self.read_ap_settings() 447 if not set(ap_settings.items()).issubset(self.ap_settings.items()): 448 self.update_ap_settings(ap_settings) 449 450 def init_gui_data(self): 451 """Function to initialize data used while interacting with web GUI""" 452 self.config_page = ( 453 "{protocol}://{username}:{password}@" 454 "{ip_address}:{port}/WLG_wireless_dual_band_r10.htm").format( 455 protocol=self.ap_settings["protocol"], 456 username=self.ap_settings["admin_username"], 457 password=self.ap_settings["admin_password"], 458 ip_address=self.ap_settings["ip_address"], 459 port=self.ap_settings["port"]) 460 self.config_page_nologin = ( 461 "{protocol}://{ip_address}:{port}/" 462 "WLG_wireless_dual_band_r10.htm").format( 463 protocol=self.ap_settings["protocol"], 464 ip_address=self.ap_settings["ip_address"], 465 port=self.ap_settings["port"]) 466 self.config_page_advanced = ( 467 "{protocol}://{username}:{password}@" 468 "{ip_address}:{port}/WLG_adv_dual_band2.htm").format( 469 protocol=self.ap_settings["protocol"], 470 username=self.ap_settings["admin_username"], 471 password=self.ap_settings["admin_password"], 472 ip_address=self.ap_settings["ip_address"], 473 port=self.ap_settings["port"]) 474 self.networks = ["2G", "5G_1"] 475 self.channel_band_map = { 476 "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 477 "5G_1": [ 478 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 479 124, 128, 132, 136, 140, 149, 153, 157, 161, 165 480 ] 481 } 482 self.region_map = { 483 "1": "Africa", 484 "2": "Asia", 485 "3": "Australia", 486 "4": "Canada", 487 "5": "Europe", 488 "6": "Israel", 489 "7": "Japan", 490 "8": "Korea", 491 "9": "Mexico", 492 "10": "South America", 493 "11": "United States", 494 "12": "Middle East(Algeria/Syria/Yemen)", 495 "14": "Russia", 496 "16": "China", 497 "17": "India", 498 "18": "Malaysia", 499 "19": "Middle East(Iran/Labanon/Qatar)", 500 "20": "Middle East(Turkey/Egypt/Tunisia/Kuwait)", 501 "21": "Middle East(Saudi Arabia)", 502 "22": "Middle East(United Arab Emirates)", 503 "23": "Singapore", 504 "24": "Taiwan" 505 } 506 self.config_page_fields = { 507 "region": "WRegion", 508 ("2G", "status"): "enable_ap", 509 ("5G_1", "status"): "enable_ap_an", 510 ("2G", "ssid"): "ssid", 511 ("5G_1", "ssid"): "ssid_an", 512 ("2G", "channel"): "w_channel", 513 ("5G_1", "channel"): "w_channel_an", 514 ("2G", "bandwidth"): "opmode", 515 ("5G_1", "bandwidth"): "opmode_an", 516 ("2G", "power"): "enable_tpc", 517 ("5G_1", "power"): "enable_tpc_an", 518 ("2G", "security_type"): "security_type", 519 ("5G_1", "security_type"): "security_type_an", 520 ("2G", "password"): "passphrase", 521 ("5G_1", "password"): "passphrase_an" 522 } 523 self.bw_mode_values = { 524 "g and b": "11g", 525 "145Mbps": "VHT20", 526 "300Mbps": "VHT40", 527 "HT80": "VHT80" 528 } 529 self.power_mode_values = { 530 "1": "100%", 531 "2": "75%", 532 "3": "50%", 533 "4": "25%" 534 } 535 self.bw_mode_text = { 536 "11g": "Up to 54 Mbps", 537 "VHT20": "Up to 289 Mbps", 538 "VHT40": "Up to 600 Mbps", 539 "VHT80": "Up to 1300 Mbps" 540 } 541 542 def read_ap_settings(self): 543 """Function to read ap settings.""" 544 with BlockingBrowser(self.ap_settings["headless_browser"], 545 900) as browser: 546 # Visit URL 547 browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10) 548 549 for key, value in self.config_page_fields.items(): 550 if "status" in key: 551 browser.visit_persistent(self.config_page_advanced, 552 BROWSER_WAIT_MED, 10) 553 config_item = browser.find_by_name(value) 554 self.ap_settings["{}_{}".format(key[1], key[0])] = int( 555 config_item.first.checked) 556 browser.visit_persistent(self.config_page, 557 BROWSER_WAIT_MED, 10) 558 else: 559 config_item = browser.find_by_name(value) 560 if "bandwidth" in key: 561 self.ap_settings["{}_{}".format( 562 key[1], key[0])] = self.bw_mode_values[ 563 config_item.first.value] 564 elif "power" in key: 565 self.ap_settings["{}_{}".format( 566 key[1], key[0])] = self.power_mode_values[ 567 config_item.first.value] 568 elif "region" in key: 569 self.ap_settings["region"] = self.region_map[ 570 config_item.first.value] 571 elif "security_type" in key: 572 for item in config_item: 573 if item.checked: 574 self.ap_settings["{}_{}".format( 575 key[1], key[0])] = item.value 576 else: 577 config_item = browser.find_by_name(value) 578 self.ap_settings["{}_{}".format( 579 key[1], key[0])] = config_item.first.value 580 return self.ap_settings.copy() 581 582 def configure_ap(self, **config_flags): 583 """Function to configure ap wireless settings.""" 584 # Turn radios on or off 585 if config_flags["status_toggled"]: 586 self.configure_radio_on_off() 587 # Configure radios 588 with BlockingBrowser(self.ap_settings["headless_browser"], 589 900) as browser: 590 # Visit URL 591 browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10) 592 browser.visit_persistent(self.config_page_nologin, 593 BROWSER_WAIT_MED, 10, self.config_page) 594 595 # Update region, and power/bandwidth for each network 596 config_item = browser.find_by_name( 597 self.config_page_fields["region"]).first 598 config_item.select_by_text(self.ap_settings["region"]) 599 for key, value in self.config_page_fields.items(): 600 if "power" in key: 601 config_item = browser.find_by_name(value).first 602 config_item.select_by_text(self.ap_settings["{}_{}".format( 603 key[1], key[0])]) 604 elif "bandwidth" in key: 605 config_item = browser.find_by_name(value).first 606 try: 607 config_item.select_by_text( 608 self.bw_mode_text[self.ap_settings["{}_{}".format( 609 key[1], key[0])]]) 610 except AttributeError: 611 self.log.warning( 612 "Cannot select bandwidth. Keeping AP default.") 613 614 # Update security settings (passwords updated only if applicable) 615 for key, value in self.config_page_fields.items(): 616 if "security_type" in key: 617 browser.choose( 618 value, self.ap_settings["{}_{}".format(key[1], 619 key[0])]) 620 if self.ap_settings["{}_{}".format(key[1], 621 key[0])] == "WPA2-PSK": 622 config_item = browser.find_by_name( 623 self.config_page_fields[(key[0], 624 "password")]).first 625 config_item.fill(self.ap_settings["{}_{}".format( 626 "password", key[0])]) 627 628 # Update SSID and channel for each network 629 # NOTE: Update ordering done as such as workaround for R8000 630 # wherein channel and SSID get overwritten when some other 631 # variables are changed. However, region does have to be set before 632 # channel in all cases. 633 for key, value in self.config_page_fields.items(): 634 if "ssid" in key: 635 config_item = browser.find_by_name(value).first 636 config_item.fill(self.ap_settings["{}_{}".format( 637 key[1], key[0])]) 638 elif "channel" in key: 639 config_item = browser.find_by_name(value).first 640 try: 641 config_item.select(self.ap_settings["{}_{}".format( 642 key[1], key[0])]) 643 time.sleep(BROWSER_WAIT_SHORT) 644 except AttributeError: 645 self.log.warning( 646 "Cannot select channel. Keeping AP default.") 647 try: 648 alert = browser.get_alert() 649 alert.accept() 650 except: 651 pass 652 653 time.sleep(BROWSER_WAIT_SHORT) 654 browser.find_by_name("Apply").first.click() 655 time.sleep(BROWSER_WAIT_SHORT) 656 try: 657 alert = browser.get_alert() 658 alert.accept() 659 time.sleep(BROWSER_WAIT_SHORT) 660 except: 661 time.sleep(BROWSER_WAIT_SHORT) 662 browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG, 663 10) 664 665 def configure_radio_on_off(self): 666 """Helper configuration function to turn radios on/off.""" 667 with BlockingBrowser(self.ap_settings["headless_browser"], 668 900) as browser: 669 # Visit URL 670 browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10) 671 browser.visit_persistent(self.config_page_advanced, 672 BROWSER_WAIT_MED, 10) 673 674 # Turn radios on or off 675 for key, value in self.config_page_fields.items(): 676 if "status" in key: 677 config_item = browser.find_by_name(value).first 678 if self.ap_settings["{}_{}".format(key[1], key[0])]: 679 config_item.check() 680 else: 681 config_item.uncheck() 682 683 time.sleep(BROWSER_WAIT_SHORT) 684 browser.find_by_name("Apply").first.click() 685 time.sleep(BROWSER_WAIT_EXTRA_LONG) 686 browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG, 687 10) 688 689 690class NetgearR7000NAAP(NetgearR7000AP): 691 """Class that implements Netgear R7000 NA AP.""" 692 def init_gui_data(self): 693 """Function to initialize data used while interacting with web GUI""" 694 super.init_gui_data() 695 self.region_map["11"] = "North America" 696 697 698class NetgearR7500AP(WifiRetailAP): 699 """Class that implements Netgear R7500 AP.""" 700 def __init__(self, ap_settings): 701 super().__init__(ap_settings) 702 self.init_gui_data() 703 # Read and update AP settings 704 self.read_ap_settings() 705 if not set(ap_settings.items()).issubset(self.ap_settings.items()): 706 self.update_ap_settings(ap_settings) 707 708 def init_gui_data(self): 709 """Function to initialize data used while interacting with web GUI""" 710 self.config_page = ("{protocol}://{username}:{password}@" 711 "{ip_address}:{port}/index.htm").format( 712 protocol=self.ap_settings["protocol"], 713 username=self.ap_settings["admin_username"], 714 password=self.ap_settings["admin_password"], 715 ip_address=self.ap_settings["ip_address"], 716 port=self.ap_settings["port"]) 717 self.config_page_advanced = ( 718 "{protocol}://{username}:{password}@" 719 "{ip_address}:{port}/adv_index.htm").format( 720 protocol=self.ap_settings["protocol"], 721 username=self.ap_settings["admin_username"], 722 password=self.ap_settings["admin_password"], 723 ip_address=self.ap_settings["ip_address"], 724 port=self.ap_settings["port"]) 725 self.networks = ["2G", "5G_1"] 726 self.channel_band_map = { 727 "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 728 "5G_1": [ 729 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 730 124, 128, 132, 136, 140, 149, 153, 157, 161, 165 731 ] 732 } 733 self.config_page_fields = { 734 "region": "WRegion", 735 ("2G", "status"): "enable_ap", 736 ("5G_1", "status"): "enable_ap_an", 737 ("2G", "ssid"): "ssid", 738 ("5G_1", "ssid"): "ssid_an", 739 ("2G", "channel"): "w_channel", 740 ("5G_1", "channel"): "w_channel_an", 741 ("2G", "bandwidth"): "opmode", 742 ("5G_1", "bandwidth"): "opmode_an", 743 ("2G", "security_type"): "security_type", 744 ("5G_1", "security_type"): "security_type_an", 745 ("2G", "password"): "passphrase", 746 ("5G_1", "password"): "passphrase_an" 747 } 748 self.region_map = { 749 "0": "Africa", 750 "1": "Asia", 751 "2": "Australia", 752 "3": "Canada", 753 "4": "Europe", 754 "5": "Israel", 755 "6": "Japan", 756 "7": "Korea", 757 "8": "Mexico", 758 "9": "South America", 759 "10": "United States", 760 "11": "China", 761 "12": "India", 762 "13": "Malaysia", 763 "14": "Middle East(Algeria/Syria/Yemen)", 764 "15": "Middle East(Iran/Labanon/Qatar)", 765 "16": "Middle East(Turkey/Egypt/Tunisia/Kuwait)", 766 "17": "Middle East(Saudi Arabia)", 767 "18": "Middle East(United Arab Emirates)", 768 "19": "Russia", 769 "20": "Singapore", 770 "21": "Taiwan" 771 } 772 self.bw_mode_text_2g = { 773 "11g": "Up to 54 Mbps", 774 "VHT20": "Up to 289 Mbps", 775 "VHT40": "Up to 600 Mbps" 776 } 777 self.bw_mode_text_5g = { 778 "VHT20": "Up to 347 Mbps", 779 "VHT40": "Up to 800 Mbps", 780 "VHT80": "Up to 1733 Mbps" 781 } 782 self.bw_mode_values = { 783 "1": "11g", 784 "2": "VHT20", 785 "3": "VHT40", 786 "7": "VHT20", 787 "8": "VHT40", 788 "9": "VHT80" 789 } 790 791 def read_ap_settings(self): 792 """Function to read ap wireless settings.""" 793 # Get radio status (on/off) 794 self.read_radio_on_off() 795 # Get radio configuration. Note that if both radios are off, the below 796 # code will result in an error 797 with BlockingBrowser(self.ap_settings["headless_browser"], 798 900) as browser: 799 browser.visit_persistent(self.config_page, 800 BROWSER_WAIT_MED, 801 10, 802 check_for_element="wireless") 803 wireless_button = browser.find_by_id("wireless").first 804 wireless_button.click() 805 time.sleep(BROWSER_WAIT_MED) 806 807 with browser.get_iframe("formframe") as iframe: 808 for key, value in self.config_page_fields.items(): 809 if "bandwidth" in key: 810 config_item = iframe.find_by_name(value).first 811 self.ap_settings["{}_{}".format( 812 key[1], 813 key[0])] = self.bw_mode_values[config_item.value] 814 elif "region" in key: 815 config_item = iframe.find_by_name(value).first 816 self.ap_settings["region"] = self.region_map[ 817 config_item.value] 818 elif "password" in key: 819 try: 820 config_item = iframe.find_by_name(value).first 821 self.ap_settings["{}_{}".format( 822 key[1], key[0])] = config_item.value 823 self.ap_settings["{}_{}".format( 824 "security_type", key[0])] = "WPA2-PSK" 825 except: 826 self.ap_settings["{}_{}".format( 827 key[1], key[0])] = "defaultpassword" 828 self.ap_settings["{}_{}".format( 829 "security_type", key[0])] = "Disable" 830 elif ("channel" in key) or ("ssid" in key): 831 config_item = iframe.find_by_name(value).first 832 self.ap_settings["{}_{}".format( 833 key[1], key[0])] = config_item.value 834 else: 835 pass 836 return self.ap_settings.copy() 837 838 def configure_ap(self, **config_flags): 839 """Function to configure ap wireless settings.""" 840 # Turn radios on or off 841 if config_flags["status_toggled"]: 842 self.configure_radio_on_off() 843 # Configure radios 844 with BlockingBrowser(self.ap_settings["headless_browser"], 845 900) as browser: 846 browser.visit_persistent(self.config_page, 847 BROWSER_WAIT_MED, 848 10, 849 check_for_element="wireless") 850 wireless_button = browser.find_by_id("wireless").first 851 wireless_button.click() 852 time.sleep(BROWSER_WAIT_MED) 853 854 with browser.get_iframe("formframe") as iframe: 855 # Update AP region. Must be done before channel setting 856 config_item = iframe.find_by_name( 857 self.config_page_fields["region"]).first 858 config_item.select_by_text(self.ap_settings["region"]) 859 # Update wireless settings for each network 860 for key, value in self.config_page_fields.items(): 861 if "ssid" in key: 862 config_item = iframe.find_by_name(value).first 863 config_item.fill(self.ap_settings["{}_{}".format( 864 key[1], key[0])]) 865 elif "channel" in key: 866 channel_string = "0" * (int(self.ap_settings[ 867 "{}_{}".format(key[1], key[0])]) < 10) + str( 868 self.ap_settings["{}_{}".format( 869 key[1], key[0])]) + "(DFS)" * (48 < int( 870 self.ap_settings["{}_{}".format( 871 key[1], key[0])]) < 149) 872 config_item = iframe.find_by_name(value).first 873 try: 874 config_item.select_by_text(channel_string) 875 except AttributeError: 876 self.log.warning( 877 "Cannot select channel. Keeping AP default.") 878 elif key == ("2G", "bandwidth"): 879 config_item = iframe.find_by_name(value).first 880 try: 881 config_item.select_by_text( 882 str(self.bw_mode_text_2g[self.ap_settings[ 883 "{}_{}".format(key[1], key[0])]])) 884 except AttributeError: 885 self.log.warning( 886 "Cannot select bandwidth. Keeping AP default.") 887 elif key == ("5G_1", "bandwidth"): 888 config_item = iframe.find_by_name(value).first 889 try: 890 config_item.select_by_text( 891 str(self.bw_mode_text_5g[self.ap_settings[ 892 "{}_{}".format(key[1], key[0])]])) 893 except AttributeError: 894 self.log.warning( 895 "Cannot select bandwidth. Keeping AP default.") 896 # Update passwords for WPA2-PSK protected networks 897 # (Must be done after security type is selected) 898 for key, value in self.config_page_fields.items(): 899 if "security_type" in key: 900 iframe.choose( 901 value, 902 self.ap_settings["{}_{}".format(key[1], key[0])]) 903 if self.ap_settings["{}_{}".format( 904 key[1], key[0])] == "WPA2-PSK": 905 config_item = iframe.find_by_name( 906 self.config_page_fields[(key[0], 907 "password")]).first 908 config_item.fill(self.ap_settings["{}_{}".format( 909 "password", key[0])]) 910 911 apply_button = iframe.find_by_name("Apply") 912 apply_button[0].click() 913 time.sleep(BROWSER_WAIT_SHORT) 914 try: 915 alert = browser.get_alert() 916 alert.accept() 917 except: 918 pass 919 time.sleep(BROWSER_WAIT_SHORT) 920 try: 921 alert = browser.get_alert() 922 alert.accept() 923 except: 924 pass 925 time.sleep(BROWSER_WAIT_SHORT) 926 time.sleep(BROWSER_WAIT_EXTRA_LONG) 927 browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG, 928 10) 929 930 def configure_radio_on_off(self): 931 """Helper configuration function to turn radios on/off.""" 932 with BlockingBrowser(self.ap_settings["headless_browser"], 933 900) as browser: 934 browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10) 935 browser.visit_persistent(self.config_page_advanced, 936 BROWSER_WAIT_MED, 937 10, 938 check_for_element="advanced_bt") 939 advanced_button = browser.find_by_id("advanced_bt").first 940 advanced_button.click() 941 time.sleep(BROWSER_WAIT_MED) 942 wireless_button = browser.find_by_id("wladv").first 943 wireless_button.click() 944 time.sleep(BROWSER_WAIT_MED) 945 946 with browser.get_iframe("formframe") as iframe: 947 # Turn radios on or off 948 for key, value in self.config_page_fields.items(): 949 if "status" in key: 950 config_item = iframe.find_by_name(value).first 951 if self.ap_settings["{}_{}".format(key[1], key[0])]: 952 config_item.check() 953 else: 954 config_item.uncheck() 955 956 time.sleep(BROWSER_WAIT_SHORT) 957 browser.find_by_name("Apply").first.click() 958 time.sleep(BROWSER_WAIT_EXTRA_LONG) 959 browser.visit_persistent(self.config_page, 960 BROWSER_WAIT_EXTRA_LONG, 10) 961 962 def read_radio_on_off(self): 963 """Helper configuration function to read radio status.""" 964 with BlockingBrowser(self.ap_settings["headless_browser"], 965 900) as browser: 966 browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10) 967 browser.visit_persistent(self.config_page_advanced, 968 BROWSER_WAIT_MED, 969 10, 970 check_for_element="advanced_bt") 971 advanced_button = browser.find_by_id("advanced_bt").first 972 advanced_button.click() 973 time.sleep(BROWSER_WAIT_SHORT) 974 wireless_button = browser.find_by_id("wladv").first 975 wireless_button.click() 976 time.sleep(BROWSER_WAIT_MED) 977 978 with browser.get_iframe("formframe") as iframe: 979 # Turn radios on or off 980 for key, value in self.config_page_fields.items(): 981 if "status" in key: 982 config_item = iframe.find_by_name(value).first 983 self.ap_settings["{}_{}".format(key[1], key[0])] = int( 984 config_item.checked) 985 986 987class NetgearR7800AP(NetgearR7500AP): 988 """Class that implements Netgear R7800 AP. 989 990 Since most of the class' implementation is shared with the R7500, this 991 class inherits from NetgearR7500AP and simply redefines config parameters 992 """ 993 def __init__(self, ap_settings): 994 super().__init__(ap_settings) 995 self.init_gui_data() 996 # Overwrite minor differences from R7500 AP 997 self.bw_mode_text_2g["VHT20"] = "Up to 347 Mbps" 998 # Read and update AP settings 999 self.read_ap_settings() 1000 if not set(ap_settings.items()).issubset(self.ap_settings.items()): 1001 self.update_ap_settings(ap_settings) 1002 1003 1004class NetgearR8000AP(NetgearR7000AP): 1005 """Class that implements Netgear R8000 AP. 1006 1007 Since most of the class' implementation is shared with the R7000, this 1008 class inherits from NetgearR7000AP and simply redefines config parameters 1009 """ 1010 def init_gui_data(self): 1011 super().init_gui_data() 1012 # Overwrite minor differences from R7000 AP 1013 self.config_page = ( 1014 "{protocol}://{username}:{password}@" 1015 "{ip_address}:{port}/WLG_wireless_dual_band_r8000.htm").format( 1016 protocol=self.ap_settings["protocol"], 1017 username=self.ap_settings["admin_username"], 1018 password=self.ap_settings["admin_password"], 1019 ip_address=self.ap_settings["ip_address"], 1020 port=self.ap_settings["port"]) 1021 self.config_page_nologin = ( 1022 "{protocol}://{ip_address}:{port}/" 1023 "WLG_wireless_dual_band_r8000.htm").format( 1024 protocol=self.ap_settings["protocol"], 1025 ip_address=self.ap_settings["ip_address"], 1026 port=self.ap_settings["port"]) 1027 self.config_page_advanced = ( 1028 "{protocol}://{username}:{password}@" 1029 "{ip_address}:{port}/WLG_adv_dual_band2_r8000.htm").format( 1030 protocol=self.ap_settings["protocol"], 1031 username=self.ap_settings["admin_username"], 1032 password=self.ap_settings["admin_password"], 1033 ip_address=self.ap_settings["ip_address"], 1034 port=self.ap_settings["port"]) 1035 self.networks = ["2G", "5G_1", "5G_2"] 1036 self.channel_band_map = { 1037 "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 1038 "5G_1": [36, 40, 44, 48], 1039 "5G_2": [149, 153, 157, 161, 165] 1040 } 1041 self.config_page_fields = { 1042 "region": "WRegion", 1043 ("2G", "status"): "enable_ap", 1044 ("5G_1", "status"): "enable_ap_an", 1045 ("5G_2", "status"): "enable_ap_an_2", 1046 ("2G", "ssid"): "ssid", 1047 ("5G_1", "ssid"): "ssid_an", 1048 ("5G_2", "ssid"): "ssid_an_2", 1049 ("2G", "channel"): "w_channel", 1050 ("5G_1", "channel"): "w_channel_an", 1051 ("5G_2", "channel"): "w_channel_an_2", 1052 ("2G", "bandwidth"): "opmode", 1053 ("5G_1", "bandwidth"): "opmode_an", 1054 ("5G_2", "bandwidth"): "opmode_an_2", 1055 ("2G", "security_type"): "security_type", 1056 ("5G_1", "security_type"): "security_type_an", 1057 ("5G_2", "security_type"): "security_type_an_2", 1058 ("2G", "password"): "passphrase", 1059 ("5G_1", "password"): "passphrase_an", 1060 ("5G_2", "password"): "passphrase_an_2" 1061 } 1062 1063 1064class NetgearR8500AP(NetgearR7000AP): 1065 """Class that implements Netgear R8500 AP. 1066 1067 Since most of the class' implementation is shared with the R7000, this 1068 class inherits from NetgearR7000AP and simply redefines config parameters 1069 """ 1070 def __init__(self, ap_settings): 1071 super().__init__(ap_settings) 1072 self.init_gui_data() 1073 # Overwrite minor differences from R8000 AP 1074 self.config_page = ( 1075 "{protocol}://{username}:{password}@" 1076 "{ip_address}:{port}/WLG_wireless_tri_band.htm").format( 1077 protocol=self.ap_settings["protocol"], 1078 username=self.ap_settings["admin_username"], 1079 password=self.ap_settings["admin_password"], 1080 ip_address=self.ap_settings["ip_address"], 1081 port=self.ap_settings["port"]) 1082 self.config_page_nologin = ( 1083 "{protocol}://{ip_address}:{port}/" 1084 "WLG_wireless_tri_band.htm").format( 1085 protocol=self.ap_settings["protocol"], 1086 ip_address=self.ap_settings["ip_address"], 1087 port=self.ap_settings["port"]) 1088 self.config_page_advanced = ( 1089 "{protocol}://{username}:{password}@" 1090 "{ip_address}:{port}/WLG_adv_tri_band2.htm").format( 1091 protocol=self.ap_settings["protocol"], 1092 username=self.ap_settings["admin_username"], 1093 password=self.ap_settings["admin_password"], 1094 ip_address=self.ap_settings["ip_address"], 1095 port=self.ap_settings["port"]) 1096 self.networks = ["2G", "5G_1", "5G_2"] 1097 self.channel_band_map = { 1098 "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 1099 "5G_1": [36, 40, 44, 48], 1100 "5G_2": [149, 153, 157, 161, 165] 1101 } 1102 self.config_page_fields = { 1103 "region": "WRegion", 1104 ("2G", "status"): "enable_ap", 1105 ("5G_1", "status"): "enable_ap_an", 1106 ("5G_2", "status"): "enable_ap_an_2", 1107 ("2G", "ssid"): "ssid", 1108 ("5G_1", "ssid"): "ssid_an", 1109 ("5G_2", "ssid"): "ssid_an_2", 1110 ("2G", "channel"): "w_channel", 1111 ("5G_1", "channel"): "w_channel_an", 1112 ("5G_2", "channel"): "w_channel_an_2", 1113 ("2G", "bandwidth"): "opmode", 1114 ("5G_1", "bandwidth"): "opmode_an", 1115 ("5G_2", "bandwidth"): "opmode_an_2", 1116 ("2G", "security_type"): "security_type", 1117 ("5G_1", "security_type"): "security_type_an", 1118 ("5G_2", "security_type"): "security_type_an_2", 1119 ("2G", "password"): "passphrase", 1120 ("5G_1", "password"): "passphrase_an", 1121 ("5G_2", "password"): "passphrase_an_2" 1122 } 1123 self.bw_mode_text = { 1124 "11g": "Up to 54 Mbps", 1125 "VHT20": "Up to 433 Mbps", 1126 "VHT40": "Up to 1000 Mbps", 1127 "VHT80": "Up to 2165 Mbps" 1128 } 1129 # Read and update AP settings 1130 self.read_ap_settings() 1131 if not set(ap_settings.items()).issubset(self.ap_settings.items()): 1132 self.update_ap_settings(ap_settings) 1133 1134 1135class NetgearRAXAP(NetgearR7000AP): 1136 """Class that implements Netgear RAX AP. 1137 1138 Since most of the class' implementation is shared with the R7000, this 1139 class inherits from NetgearR7000AP and simply redefines config parameters 1140 """ 1141 def init_gui_data(self): 1142 super().init_gui_data() 1143 # Overwrite minor differences from R7000 AP 1144 self.config_page = ( 1145 "{protocol}://{username}:{password}@" 1146 "{ip_address}:{port}/WLG_wireless_dual_band_r10.htm").format( 1147 protocol=self.ap_settings["protocol"], 1148 username=self.ap_settings["admin_username"], 1149 password=self.ap_settings["admin_password"], 1150 ip_address=self.ap_settings["ip_address"], 1151 port=self.ap_settings["port"]) 1152 self.config_page_nologin = ( 1153 "{protocol}://{ip_address}:{port}/" 1154 "WLG_wireless_dual_band_r10.htm").format( 1155 protocol=self.ap_settings["protocol"], 1156 ip_address=self.ap_settings["ip_address"], 1157 port=self.ap_settings["port"]) 1158 self.config_page_advanced = ( 1159 "{protocol}://{username}:{password}@" 1160 "{ip_address}:{port}/WLG_adv_dual_band2.htm").format( 1161 protocol=self.ap_settings["protocol"], 1162 username=self.ap_settings["admin_username"], 1163 password=self.ap_settings["admin_password"], 1164 ip_address=self.ap_settings["ip_address"], 1165 port=self.ap_settings["port"]) 1166 self.networks = ["2G", "5G_1", "5G_2"] 1167 self.channel_band_map = { 1168 "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 1169 "5G_1": [ 1170 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 1171 124, 128, 132, 136, 140, 149, 153, 157, 161, 165 1172 ] 1173 } 1174 1175 self.bw_mode_values = { 1176 "g and b": "11g", 1177 "145Mbps": "VHT20", 1178 "300Mbps": "VHT40", 1179 "HT80": "VHT80", 1180 "HT160": "VHT160" 1181 } 1182 self.bw_mode_text = { 1183 "11g": "Up to 54 Mbps", 1184 "VHT20": "Up to 600 Mbps", 1185 "VHT40": "Up to 1200 Mbps", 1186 "VHT80": "Up to 2400 Mbps", 1187 "VHT160": "Up to 4800 Mbps" 1188 } 1189 1190 1191class GoogleWifiAP(WifiRetailAP): 1192 """ Class that implements Google Wifi AP. 1193 1194 This class is a work in progress 1195 """ 1196 def __init__(self, ap_settings): 1197 super().__init__(ap_settings) 1198 # Initialize AP 1199 if self.ap_settings["status_2G"] and self.ap_settings["status_5G_1"]: 1200 raise ValueError("Error initializing Google Wifi AP. " 1201 "Only one interface can be enabled at a time.") 1202 self.channel_band_map = { 1203 "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 1204 "5G_1": [36, 40, 44, 48, 149, 153, 157, 161, 165] 1205 } 1206 self.BW_MODE_MAP = { 1207 "legacy": 20, 1208 "VHT20": 20, 1209 "VHT40": 40, 1210 "VHT80": 80 1211 } 1212 self.default_settings = { 1213 "region": "United States", 1214 "brand": "Google", 1215 "model": "Wifi", 1216 "hostapd_profile": "whirlwind", 1217 "status_2G": 0, 1218 "status_5G_1": 0, 1219 "ssid_2G": "GoogleWifi_2G", 1220 "ssid_5G_1": "GoogleWifi_5G", 1221 "channel_2G": 11, 1222 "channel_5G_1": 149, 1223 "bandwidth_2G": "VHT20", 1224 "bandwidth_5G_1": "VHT20", 1225 "power_2G": "auto", 1226 "power_5G_1": "auto", 1227 "mode_2G": None, 1228 "num_streams_2G": None, 1229 "rate_2G": "auto", 1230 "short_gi_2G": 0, 1231 "mode_5G_1": None, 1232 "num_streams_5G_1": None, 1233 "rate_5G_1": "auto", 1234 "short_gi_5G_1": 0, 1235 "security_type_2G": "Open", 1236 "security_type_5G_1": "Open", 1237 "subnet_2G": "192.168.1.0/24", 1238 "subnet_5G_1": "192.168.9.0/24", 1239 "password_2G": "password", 1240 "password_5G_1": "password" 1241 } 1242 1243 for setting in self.default_settings.keys(): 1244 if setting not in self.ap_settings: 1245 self.log.debug( 1246 "{0} not found during init. Setting {0} = {1}".format( 1247 setting, self.default_settings[setting])) 1248 self.ap_settings[setting] = self.default_settings[setting] 1249 init_settings = self.ap_settings.copy() 1250 init_settings["ap_subnet"] = { 1251 "2g": self.ap_settings["subnet_2G"], 1252 "5g": self.ap_settings["subnet_5G_1"] 1253 } 1254 self.access_point = access_point.AccessPoint(init_settings) 1255 self.configure_ap() 1256 1257 def read_ap_settings(self): 1258 """Function that reads current ap settings.""" 1259 return self.ap_settings.copy() 1260 1261 def update_ap_settings(self, dict_settings={}, **named_settings): 1262 """Function to update settings of existing AP. 1263 1264 Function copies arguments into ap_settings and calls configure_ap 1265 to apply them. 1266 1267 Args: 1268 dict_settings: single dictionary of settings to update 1269 **named_settings: named settings to update 1270 Note: dict and named_settings cannot contain the same settings. 1271 """ 1272 settings_to_update = dict(dict_settings, **named_settings) 1273 if len(settings_to_update) != len(dict_settings) + len(named_settings): 1274 raise KeyError("The following keys were passed twice: {}".format( 1275 (set(dict_settings.keys()).intersection( 1276 set(named_settings.keys()))))) 1277 if not set(settings_to_update.keys()).issubset( 1278 set(self.ap_settings.keys())): 1279 raise KeyError( 1280 "The following settings are invalid for this AP: {}".format( 1281 set(settings_to_update.keys()).difference( 1282 set(self.ap_settings.keys())))) 1283 1284 updating_2G = any(["2G" in x for x in settings_to_update.keys()]) 1285 updating_5G_1 = any(["5G_1" in x for x in settings_to_update.keys()]) 1286 if updating_2G and updating_5G_1: 1287 raise ValueError( 1288 "Error updating Google WiFi AP. " 1289 "One interface can be activated and updated at a time") 1290 elif updating_2G: 1291 # If updating an interface and not explicitly setting its status, 1292 # it is assumed that the interface is to be ENABLED and updated 1293 if "status_2G" not in settings_to_update: 1294 settings_to_update["status_2G"] = 1 1295 settings_to_update["status_5G_1"] = 0 1296 elif updating_5G_1: 1297 if "status_5G_1" not in settings_to_update: 1298 settings_to_update["status_2G"] = 0 1299 settings_to_update["status_5G_1"] = 1 1300 1301 updates_requested = False 1302 for setting, value in settings_to_update.items(): 1303 if self.ap_settings[setting] != value: 1304 self.ap_settings[setting] = value 1305 updates_requested = True 1306 1307 if updates_requested: 1308 self.configure_ap() 1309 1310 def configure_ap(self): 1311 """Function to configure Google Wifi.""" 1312 self.log.info("Stopping Google Wifi interfaces.") 1313 self.access_point.stop_all_aps() 1314 1315 if self.ap_settings["status_2G"] == 1: 1316 network = "2G" 1317 self.log.info("Bringing up 2.4 GHz network.") 1318 elif self.ap_settings["status_5G_1"] == 1: 1319 network = "5G_1" 1320 self.log.info("Bringing up 5 GHz network.") 1321 else: 1322 return 1323 1324 bss_settings = [] 1325 ssid = self.ap_settings["ssid_{}".format(network)] 1326 security_mode = self.ap_settings["security_type_{}".format( 1327 network)].lower() 1328 if "wpa" in security_mode: 1329 password = self.ap_settings["password_{}".format(network)] 1330 security = hostapd_security.Security(security_mode=security_mode, 1331 password=password) 1332 else: 1333 security = hostapd_security.Security(security_mode=None, 1334 password=None) 1335 channel = int(self.ap_settings["channel_{}".format(network)]) 1336 bandwidth = self.BW_MODE_MAP[self.ap_settings["bandwidth_{}".format( 1337 network)]] 1338 config = hostapd_ap_preset.create_ap_preset( 1339 channel=channel, 1340 ssid=ssid, 1341 security=security, 1342 bss_settings=bss_settings, 1343 vht_bandwidth=bandwidth, 1344 profile_name=self.ap_settings["hostapd_profile"], 1345 iface_wlan_2g=self.access_point.wlan_2g, 1346 iface_wlan_5g=self.access_point.wlan_5g) 1347 config_bridge = self.access_point.generate_bridge_configs(channel) 1348 brconfigs = bridge_interface.BridgeInterfaceConfigs( 1349 config_bridge[0], "lan0", config_bridge[2]) 1350 self.access_point.bridge.startup(brconfigs) 1351 self.access_point.start_ap(config) 1352 self.set_power(network, self.ap_settings["power_{}".format(network)]) 1353 self.set_rate( 1354 network, 1355 mode=self.ap_settings["mode_{}".format(network)], 1356 num_streams=self.ap_settings["num_streams_{}".format(network)], 1357 rate=self.ap_settings["rate_{}".format(network)], 1358 short_gi=self.ap_settings["short_gi_{}".format(network)]) 1359 self.log.info("AP started on channel {} with SSID {}".format( 1360 channel, ssid)) 1361 1362 def set_power(self, network, power): 1363 """Function that sets network transmit power. 1364 1365 Args: 1366 network: string containing network identifier (2G, 5G_1, 5G_2) 1367 power: power level in dBm 1368 """ 1369 if power == "auto": 1370 power_string = "auto" 1371 else: 1372 if not float(power).is_integer(): 1373 self.log.info( 1374 "Power in dBm must be an integer. Setting to {}".format( 1375 int(power))) 1376 power = int(power) 1377 power_string = "fixed {}".format(int(power) * 100) 1378 1379 if "2G" in network: 1380 interface = self.access_point.wlan_2g 1381 self.ap_settings["power_2G"] = power 1382 elif "5G_1" in network: 1383 interface = self.access_point.wlan_5g 1384 self.ap_settings["power_5G_1"] = power 1385 self.access_point.ssh.run("iw dev {} set txpower {}".format( 1386 interface, power_string)) 1387 1388 def set_rate(self, 1389 network, 1390 mode=None, 1391 num_streams=None, 1392 rate='auto', 1393 short_gi=0): 1394 """Function that sets rate. 1395 1396 Args: 1397 network: string containing network identifier (2G, 5G_1, 5G_2) 1398 mode: string indicating the WiFi standard to use 1399 num_streams: number of MIMO streams. used only for VHT 1400 rate: data rate of MCS index to use 1401 short_gi: boolean controlling the use of short guard interval 1402 """ 1403 if "2G" in network: 1404 interface = self.access_point.wlan_2g 1405 interface_short = "2.4" 1406 self.ap_settings["mode_2G"] = mode 1407 self.ap_settings["num_streams_2G"] = num_streams 1408 self.ap_settings["rate_2G"] = rate 1409 self.ap_settings["short_gi_2G"] = short_gi 1410 elif "5G_1" in network: 1411 interface = self.access_point.wlan_5g 1412 interface_short = "5" 1413 self.ap_settings["mode_5G_1"] = mode 1414 self.ap_settings["num_streams_5G_1"] = num_streams 1415 self.ap_settings["rate_5G_1"] = rate 1416 self.ap_settings["short_gi_5G_1"] = short_gi 1417 1418 if rate == "auto": 1419 cmd_string = "iw dev {0} set bitrates".format(interface) 1420 elif "legacy" in mode.lower(): 1421 cmd_string = "iw dev {0} set bitrates legacy-{1} {2} ht-mcs-{1} vht-mcs-{1}".format( 1422 interface, interface_short, rate) 1423 elif "vht" in mode.lower(): 1424 cmd_string = "iw dev {0} set bitrates legacy-{1} ht-mcs-{1} vht-mcs-{1} {2}:{3}".format( 1425 interface, interface_short, num_streams, rate) 1426 if short_gi: 1427 cmd_string = cmd_string + " sgi-{}".format(interface_short) 1428 elif "ht" in mode.lower(): 1429 cmd_string = "iw dev {0} set bitrates legacy-{1} ht-mcs-{1} {2} vht-mcs-{1}".format( 1430 interface, interface_short, rate) 1431 if short_gi: 1432 cmd_string = cmd_string + " sgi-{}".format(interface_short) 1433 self.access_point.ssh.run(cmd_string) 1434