1"""Controller for Open WRT access point.""" 2 3import ast 4import logging 5import random 6import re 7import time 8 9from acts import logger 10from acts import signals 11from acts.controllers.ap_lib import hostapd_constants 12from acts.controllers.openwrt_lib import network_settings 13from acts.controllers.openwrt_lib import openwrt_authentication 14from acts.controllers.openwrt_lib import wireless_config 15from acts.controllers.openwrt_lib import wireless_settings_applier 16from acts.controllers.openwrt_lib.openwrt_constants import OpenWrtModelMap as modelmap 17from acts.controllers.openwrt_lib.openwrt_constants import OpenWrtWifiSetting 18from acts.controllers.openwrt_lib.openwrt_constants import SYSTEM_INFO_CMD 19from acts.controllers.utils_lib.ssh import connection 20from acts.controllers.utils_lib.ssh import settings 21import yaml 22 23 24MOBLY_CONTROLLER_CONFIG_NAME = "OpenWrtAP" 25ACTS_CONTROLLER_REFERENCE_NAME = "access_points" 26OPEN_SECURITY = "none" 27PSK1_SECURITY = "psk" 28PSK_SECURITY = "psk2" 29WEP_SECURITY = "wep" 30ENT_SECURITY = "wpa2" 31OWE_SECURITY = "owe" 32SAE_SECURITY = "sae" 33SAEMIXED_SECURITY = "sae-mixed" 34ENABLE_RADIO = "0" 35PMF_ENABLED = 2 36WIFI_2G = "wifi2g" 37WIFI_5G = "wifi5g" 38WAIT_TIME = 20 39DEFAULT_RADIOS = ("radio0", "radio1") 40 41 42def create(configs): 43 """Creates ap controllers from a json config. 44 45 Creates an ap controller from either a list, or a single element. The element 46 can either be just the hostname or a dictionary containing the hostname and 47 username of the AP to connect to over SSH. 48 49 Args: 50 configs: The json configs that represent this controller. 51 52 Returns: 53 AccessPoint object 54 55 Example: 56 Below is the config file entry for OpenWrtAP as a list. A testbed can have 57 1 or more APs to configure. Each AP has a "ssh_config" key to provide SSH 58 login information. OpenWrtAP#__init__() uses this to create SSH object. 59 60 "OpenWrtAP": [ 61 { 62 "ssh_config": { 63 "user" : "root", 64 "host" : "192.168.1.1" 65 } 66 }, 67 { 68 "ssh_config": { 69 "user" : "root", 70 "host" : "192.168.1.2" 71 } 72 } 73 ] 74 """ 75 return [OpenWrtAP(c) for c in configs] 76 77 78def destroy(aps): 79 """Destroys a list of AccessPoints. 80 81 Args: 82 aps: The list of AccessPoints to destroy. 83 """ 84 for ap in aps: 85 ap.close() 86 ap.close_ssh() 87 88 89def get_info(aps): 90 """Get information on a list of access points. 91 92 Args: 93 aps: A list of AccessPoints. 94 95 Returns: 96 A list of all aps hostname. 97 """ 98 return [ap.ssh_settings.hostname for ap in aps] 99 100 101class OpenWrtAP(object): 102 """An AccessPoint controller. 103 104 Attributes: 105 ssh: The ssh connection to the AP. 106 ssh_settings: The ssh settings being used by the ssh connection. 107 log: Logging object for AccessPoint. 108 wireless_setting: object holding wireless configuration. 109 network_setting: Object for network configuration. 110 model: OpenWrt HW model. 111 radios: Fit interface for test. 112 """ 113 114 def __init__(self, config): 115 """Initialize AP.""" 116 try: 117 self.ssh_settings = settings.from_config(config["ssh_config"]) 118 self.ssh = connection.SshConnection(self.ssh_settings) 119 self.ssh.setup_master_ssh() 120 except connection.Error: 121 logging.info("OpenWrt AP instance is not initialized, use SSH Auth...") 122 openwrt_auth = openwrt_authentication.OpenWrtAuth( 123 self.ssh_settings.hostname) 124 openwrt_auth.generate_rsa_key() 125 openwrt_auth.send_public_key_to_remote_host() 126 self.ssh_settings.identity_file = openwrt_auth.private_key_file 127 self.ssh = connection.SshConnection(self.ssh_settings) 128 self.ssh.setup_master_ssh() 129 self.log = logger.create_logger( 130 lambda msg: "[OpenWrtAP|%s] %s" % (self.ssh_settings.hostname, msg)) 131 self.wireless_setting = None 132 self.network_setting = network_settings.NetworkSettings( 133 self.ssh, self.ssh_settings, self.log) 134 self.model = self.get_model_name() 135 self.log.info("OpenWrt AP: %s has been initiated." % self.model) 136 if self.model in modelmap.__dict__: 137 self.radios = modelmap.__dict__[self.model] 138 else: 139 self.radios = DEFAULT_RADIOS 140 141 def configure_ap(self, wifi_configs, channel_2g, channel_5g): 142 """Configure AP with the required settings. 143 144 Each test class inherits WifiBaseTest. Based on the test, we may need to 145 configure PSK, WEP, OPEN, ENT networks on 2G and 5G bands in any 146 combination. We call WifiBaseTest methods get_psk_network(), 147 get_open_network(), get_wep_network() and get_ent_network() to create 148 dictionaries which contains this information. 'wifi_configs' is a list of 149 such dictionaries. Example below configures 2 WiFi networks - 1 PSK 2G and 150 1 Open 5G on one AP. configure_ap() is called from WifiBaseTest to 151 configure the APs. 152 153 wifi_configs = [ 154 { 155 '2g': { 156 'SSID': '2g_AkqXWPK4', 157 'security': 'psk2', 158 'password': 'YgYuXqDO9H', 159 'hiddenSSID': False 160 }, 161 }, 162 { 163 '5g': { 164 'SSID': '5g_8IcMR1Sg', 165 'security': 'none', 166 'hiddenSSID': False 167 }, 168 } 169 ] 170 171 Args: 172 wifi_configs: list of network settings for 2G and 5G bands. 173 channel_2g: channel for 2G band. 174 channel_5g: channel for 5G band. 175 """ 176 # generate wifi configs to configure 177 wireless_configs = self.generate_wireless_configs(wifi_configs) 178 self.wireless_setting = wireless_settings_applier.WirelessSettingsApplier( 179 self.ssh, wireless_configs, channel_2g, channel_5g, self.radios[1], self.radios[0]) 180 self.wireless_setting.apply_wireless_settings() 181 182 def start_ap(self): 183 """Starts the AP with the settings in /etc/config/wireless.""" 184 self.log.info("wifi up") 185 self.ssh.run("wifi up") 186 curr_time = time.time() 187 while time.time() < curr_time + WAIT_TIME: 188 if self.get_wifi_status(): 189 return 190 time.sleep(3) 191 if not self.get_wifi_status(): 192 raise ValueError("Failed to turn on WiFi on the AP.") 193 194 def stop_ap(self): 195 """Stops the AP.""" 196 self.log.info("wifi down") 197 self.ssh.run("wifi down") 198 curr_time = time.time() 199 while time.time() < curr_time + WAIT_TIME: 200 if not self.get_wifi_status(): 201 return 202 time.sleep(3) 203 if self.get_wifi_status(): 204 raise ValueError("Failed to turn off WiFi on the AP.") 205 206 def get_bssids_for_wifi_networks(self): 207 """Get BSSIDs for wifi networks configured. 208 209 Returns: 210 Dictionary of SSID - BSSID map for both bands. 211 """ 212 bssid_map = {"2g": {}, "5g": {}} 213 for radio in self.radios: 214 ssid_ifname_map = self.get_ifnames_for_ssids(radio) 215 if radio == self.radios[0]: 216 for ssid, ifname in ssid_ifname_map.items(): 217 bssid_map["5g"][ssid] = self.get_bssid(ifname) 218 elif radio == self.radios[1]: 219 for ssid, ifname in ssid_ifname_map.items(): 220 bssid_map["2g"][ssid] = self.get_bssid(ifname) 221 return bssid_map 222 223 def get_ifnames_for_ssids(self, radio): 224 """Get interfaces for wifi networks. 225 226 Args: 227 radio: 2g or 5g radio get the bssids from. 228 229 Returns: 230 dictionary of ssid - ifname mappings. 231 """ 232 ssid_ifname_map = {} 233 str_output = self.ssh.run("wifi status %s" % radio).stdout 234 wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""), 235 Loader=yaml.SafeLoader) 236 wifi_status = wifi_status[radio] 237 if wifi_status["up"]: 238 interfaces = wifi_status["interfaces"] 239 for config in interfaces: 240 ssid = config["config"]["ssid"] 241 ifname = config["ifname"] 242 ssid_ifname_map[ssid] = ifname 243 return ssid_ifname_map 244 245 def get_bssid(self, ifname): 246 """Get MAC address from an interface. 247 248 Args: 249 ifname: interface name of the corresponding MAC. 250 251 Returns: 252 BSSID of the interface. 253 """ 254 ifconfig = self.ssh.run("ifconfig %s" % ifname).stdout 255 mac_addr = ifconfig.split("\n")[0].split()[-1] 256 return mac_addr 257 258 def set_wpa_encryption(self, encryption): 259 """Set different encryptions to wpa or wpa2. 260 261 Args: 262 encryption: ccmp, tkip, or ccmp+tkip. 263 """ 264 str_output = self.ssh.run("wifi status").stdout 265 wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""), 266 Loader=yaml.SafeLoader) 267 268 # Counting how many interface are enabled. 269 total_interface = 0 270 for radio in self.radios: 271 num_interface = len(wifi_status[radio]["interfaces"]) 272 total_interface += num_interface 273 274 # Iterates every interface to get and set wpa encryption. 275 default_extra_interface = 2 276 for i in range(total_interface + default_extra_interface): 277 origin_encryption = self.ssh.run( 278 "uci get wireless.@wifi-iface[{}].encryption".format(i)).stdout 279 origin_psk_pattern = re.match(r"psk\b", origin_encryption) 280 target_psk_pattern = re.match(r"psk\b", encryption) 281 origin_psk2_pattern = re.match(r"psk2\b", origin_encryption) 282 target_psk2_pattern = re.match(r"psk2\b", encryption) 283 284 if origin_psk_pattern == target_psk_pattern: 285 self.ssh.run( 286 "uci set wireless.@wifi-iface[{}].encryption={}".format( 287 i, encryption)) 288 289 if origin_psk2_pattern == target_psk2_pattern: 290 self.ssh.run( 291 "uci set wireless.@wifi-iface[{}].encryption={}".format( 292 i, encryption)) 293 294 self.ssh.run("uci commit wireless") 295 self.ssh.run("wifi") 296 297 def set_password(self, pwd_5g=None, pwd_2g=None): 298 """Set password for individual interface. 299 300 Args: 301 pwd_5g: 8 ~ 63 chars, ascii letters and digits password for 5g network. 302 pwd_2g: 8 ~ 63 chars, ascii letters and digits password for 2g network. 303 """ 304 if pwd_5g: 305 if len(pwd_5g) < 8 or len(pwd_5g) > 63: 306 self.log.error("Password must be 8~63 characters long") 307 # Only accept ascii letters and digits 308 elif not re.match("^[A-Za-z0-9]*$", pwd_5g): 309 self.log.error("Password must only contains ascii letters and digits") 310 else: 311 self.ssh.run( 312 "uci set wireless.@wifi-iface[{}].key={}".format(3, pwd_5g)) 313 self.log.info("Set 5G password to :{}".format(pwd_5g)) 314 315 if pwd_2g: 316 if len(pwd_2g) < 8 or len(pwd_2g) > 63: 317 self.log.error("Password must be 8~63 characters long") 318 # Only accept ascii letters and digits 319 elif not re.match("^[A-Za-z0-9]*$", pwd_2g): 320 self.log.error("Password must only contains ascii letters and digits") 321 else: 322 self.ssh.run( 323 "uci set wireless.@wifi-iface[{}].key={}".format(2, pwd_2g)) 324 self.log.info("Set 2G password to :{}".format(pwd_2g)) 325 326 self.ssh.run("uci commit wireless") 327 self.ssh.run("wifi") 328 329 def set_ssid(self, ssid_5g=None, ssid_2g=None): 330 """Set SSID for individual interface. 331 332 Args: 333 ssid_5g: 8 ~ 63 chars for 5g network. 334 ssid_2g: 8 ~ 63 chars for 2g network. 335 """ 336 if ssid_5g: 337 if len(ssid_5g) < 8 or len(ssid_5g) > 63: 338 self.log.error("SSID must be 8~63 characters long") 339 # Only accept ascii letters and digits 340 else: 341 self.ssh.run( 342 "uci set wireless.@wifi-iface[{}].ssid={}".format(3, ssid_5g)) 343 self.log.info("Set 5G SSID to :{}".format(ssid_5g)) 344 345 if ssid_2g: 346 if len(ssid_2g) < 8 or len(ssid_2g) > 63: 347 self.log.error("SSID must be 8~63 characters long") 348 # Only accept ascii letters and digits 349 else: 350 self.ssh.run( 351 "uci set wireless.@wifi-iface[{}].ssid={}".format(2, ssid_2g)) 352 self.log.info("Set 2G SSID to :{}".format(ssid_2g)) 353 354 self.ssh.run("uci commit wireless") 355 self.ssh.run("wifi") 356 357 def generate_mobility_domain(self): 358 """Generate 4-character hexadecimal ID. 359 360 Returns: 361 String; a 4-character hexadecimal ID. 362 """ 363 md = "{:04x}".format(random.getrandbits(16)) 364 self.log.info("Mobility Domain ID: {}".format(md)) 365 return md 366 367 def enable_80211r(self, iface, md): 368 """Enable 802.11r for one single radio. 369 370 Args: 371 iface: index number of wifi-iface. 372 2: radio1 373 3: radio0 374 md: mobility domain. a 4-character hexadecimal ID. 375 Raises: 376 TestSkip if 2g or 5g radio is not up or 802.11r is not enabled. 377 """ 378 str_output = self.ssh.run("wifi status").stdout 379 wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""), 380 Loader=yaml.SafeLoader) 381 # Check if the radio is up. 382 if iface == OpenWrtWifiSetting.IFACE_2G: 383 if wifi_status[self.radios[1]]["up"]: 384 self.log.info("2g network is ENABLED") 385 else: 386 raise signals.TestSkip("2g network is NOT ENABLED") 387 elif iface == OpenWrtWifiSetting.IFACE_5G: 388 if wifi_status[self.radios[0]]["up"]: 389 self.log.info("5g network is ENABLED") 390 else: 391 raise signals.TestSkip("5g network is NOT ENABLED") 392 393 # Setup 802.11r. 394 self.ssh.run( 395 "uci set wireless.@wifi-iface[{}].ieee80211r='1'".format(iface)) 396 self.ssh.run( 397 "uci set wireless.@wifi-iface[{}].ft_psk_generate_local='1'" 398 .format(iface)) 399 self.ssh.run( 400 "uci set wireless.@wifi-iface[{}].mobility_domain='{}'" 401 .format(iface, md)) 402 self.ssh.run( 403 "uci commit wireless") 404 self.ssh.run("wifi") 405 406 # Check if 802.11r is enabled. 407 result = self.ssh.run( 408 "uci get wireless.@wifi-iface[{}].ieee80211r".format(iface)).stdout 409 if result == "1": 410 self.log.info("802.11r is ENABLED") 411 else: 412 raise signals.TestSkip("802.11r is NOT ENABLED") 413 414 def generate_wireless_configs(self, wifi_configs): 415 """Generate wireless configs to configure. 416 417 Converts wifi_configs from configure_ap() to a list of 'WirelessConfig' 418 objects. Each object represents a wifi network to configure on the AP. 419 420 Args: 421 wifi_configs: Network list of different security types and bands. 422 423 Returns: 424 wireless configuration for openwrt AP. 425 """ 426 num_2g = 1 427 num_5g = 1 428 wireless_configs = [] 429 430 for i in range(len(wifi_configs)): 431 if hostapd_constants.BAND_2G in wifi_configs[i]: 432 config = wifi_configs[i][hostapd_constants.BAND_2G] 433 if config["security"] == PSK_SECURITY: 434 wireless_configs.append( 435 wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g), 436 config["SSID"], 437 config["security"], 438 hostapd_constants.BAND_2G, 439 password=config["password"], 440 hidden=config["hiddenSSID"], 441 ieee80211w=config["ieee80211w"])) 442 elif config["security"] == PSK1_SECURITY: 443 wireless_configs.append( 444 wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g), 445 config["SSID"], 446 config["security"], 447 hostapd_constants.BAND_2G, 448 password=config["password"], 449 hidden=config["hiddenSSID"], 450 ieee80211w=config["ieee80211w"])) 451 elif config["security"] == WEP_SECURITY: 452 wireless_configs.append( 453 wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g), 454 config["SSID"], 455 config["security"], 456 hostapd_constants.BAND_2G, 457 wep_key=config["wepKeys"][0], 458 hidden=config["hiddenSSID"])) 459 elif config["security"] == OPEN_SECURITY: 460 wireless_configs.append( 461 wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g), 462 config["SSID"], 463 config["security"], 464 hostapd_constants.BAND_2G, 465 hidden=config["hiddenSSID"])) 466 elif config["security"] == OWE_SECURITY: 467 wireless_configs.append( 468 wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g), 469 config["SSID"], 470 config["security"], 471 hostapd_constants.BAND_2G, 472 hidden=config["hiddenSSID"], 473 ieee80211w=PMF_ENABLED)) 474 elif config["security"] == SAE_SECURITY: 475 wireless_configs.append( 476 wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g), 477 config["SSID"], 478 config["security"], 479 hostapd_constants.BAND_2G, 480 password=config["password"], 481 hidden=config["hiddenSSID"], 482 ieee80211w=PMF_ENABLED)) 483 elif config["security"] == SAEMIXED_SECURITY: 484 wireless_configs.append( 485 wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g), 486 config["SSID"], 487 config["security"], 488 hostapd_constants.BAND_2G, 489 password=config["password"], 490 hidden=config["hiddenSSID"], 491 ieee80211w=config["ieee80211w"])) 492 elif config["security"] == ENT_SECURITY: 493 wireless_configs.append( 494 wireless_config.WirelessConfig( 495 "%s%s" % (WIFI_2G, num_2g), 496 config["SSID"], 497 config["security"], 498 hostapd_constants.BAND_2G, 499 radius_server_ip=config["radius_server_ip"], 500 radius_server_port=config["radius_server_port"], 501 radius_server_secret=config["radius_server_secret"], 502 hidden=config["hiddenSSID"])) 503 num_2g += 1 504 if hostapd_constants.BAND_5G in wifi_configs[i]: 505 config = wifi_configs[i][hostapd_constants.BAND_5G] 506 if config["security"] == PSK_SECURITY: 507 wireless_configs.append( 508 wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g), 509 config["SSID"], 510 config["security"], 511 hostapd_constants.BAND_5G, 512 password=config["password"], 513 hidden=config["hiddenSSID"], 514 ieee80211w=config["ieee80211w"])) 515 elif config["security"] == PSK1_SECURITY: 516 wireless_configs.append( 517 wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g), 518 config["SSID"], 519 config["security"], 520 hostapd_constants.BAND_5G, 521 password=config["password"], 522 hidden=config["hiddenSSID"], 523 ieee80211w=config["ieee80211w"])) 524 elif config["security"] == WEP_SECURITY: 525 wireless_configs.append( 526 wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g), 527 config["SSID"], 528 config["security"], 529 hostapd_constants.BAND_5G, 530 wep_key=config["wepKeys"][0], 531 hidden=config["hiddenSSID"])) 532 elif config["security"] == OPEN_SECURITY: 533 wireless_configs.append( 534 wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g), 535 config["SSID"], 536 config["security"], 537 hostapd_constants.BAND_5G, 538 hidden=config["hiddenSSID"])) 539 elif config["security"] == OWE_SECURITY: 540 wireless_configs.append( 541 wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g), 542 config["SSID"], 543 config["security"], 544 hostapd_constants.BAND_5G, 545 hidden=config["hiddenSSID"], 546 ieee80211w=PMF_ENABLED)) 547 elif config["security"] == SAE_SECURITY: 548 wireless_configs.append( 549 wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g), 550 config["SSID"], 551 config["security"], 552 hostapd_constants.BAND_5G, 553 password=config["password"], 554 hidden=config["hiddenSSID"], 555 ieee80211w=PMF_ENABLED)) 556 elif config["security"] == SAEMIXED_SECURITY: 557 wireless_configs.append( 558 wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g), 559 config["SSID"], 560 config["security"], 561 hostapd_constants.BAND_5G, 562 password=config["password"], 563 hidden=config["hiddenSSID"], 564 ieee80211w=config["ieee80211w"])) 565 elif config["security"] == ENT_SECURITY: 566 wireless_configs.append( 567 wireless_config.WirelessConfig( 568 "%s%s" % (WIFI_5G, num_5g), 569 config["SSID"], 570 config["security"], 571 hostapd_constants.BAND_5G, 572 radius_server_ip=config["radius_server_ip"], 573 radius_server_port=config["radius_server_port"], 574 radius_server_secret=config["radius_server_secret"], 575 hidden=config["hiddenSSID"])) 576 num_5g += 1 577 578 return wireless_configs 579 580 def get_wifi_network(self, security=None, band=None): 581 """Return first match wifi interface's config. 582 583 Args: 584 security: psk2 or none 585 band: '2g' or '5g' 586 587 Returns: 588 A dict contains match wifi interface's config. 589 """ 590 591 for wifi_iface in self.wireless_setting.wireless_configs: 592 match_list = [] 593 wifi_network = wifi_iface.__dict__ 594 if security: 595 match_list.append(security == wifi_network["security"]) 596 if band: 597 match_list.append(band == wifi_network["band"]) 598 599 if all(match_list): 600 wifi_network["SSID"] = wifi_network["ssid"] 601 if not wifi_network["password"]: 602 del wifi_network["password"] 603 return wifi_network 604 return None 605 606 def get_wifi_status(self): 607 """Check if radios are up. Default are 2G and 5G bands. 608 609 Returns: 610 True if both radios are up. False if not. 611 """ 612 status = True 613 for radio in self.radios: 614 try: 615 str_output = self.ssh.run("wifi status %s" % radio).stdout 616 wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""), 617 Loader=yaml.SafeLoader) 618 status = wifi_status[radio]["up"] and status 619 except: 620 self.log.info("Failed to make ssh connection to the OpenWrt") 621 return False 622 return status 623 624 def verify_wifi_status(self, timeout=20): 625 """Ensure wifi interfaces are ready. 626 627 Args: 628 timeout: An integer that is the number of times to try 629 wait for interface ready. 630 Returns: 631 True if both radios are up. False if not. 632 """ 633 start_time = time.time() 634 end_time = start_time + timeout 635 while time.time() < end_time: 636 if self.get_wifi_status(): 637 return True 638 time.sleep(1) 639 return False 640 641 def get_model_name(self): 642 """Get Openwrt model name. 643 644 Returns: 645 A string include device brand and model. e.g. NETGEAR_R8000 646 """ 647 out = self.ssh.run(SYSTEM_INFO_CMD).stdout.split("\n") 648 for line in out: 649 if "board_name" in line: 650 line = (line.split()[1].strip("\",").split(",")) 651 board_name = "_".join(map(lambda i: i.upper(), line)) 652 filter_list = "-" 653 for i in filter_list: 654 board_name = str(board_name.replace(i, "_")) 655 return board_name 656 self.log.info("Failed to retrieve OpenWrt model information.") 657 return None 658 659 def get_version(self): 660 """Get Openwrt version. 661 662 Returns: 663 A string with version number. 664 """ 665 out = self.ssh.run(SYSTEM_INFO_CMD).stdout 666 return ast.literal_eval(out)["release"]["version"] 667 668 def is_version_under_20(self): 669 """Boolean if version under 20.""" 670 return int(self.get_version().split(".")[0]) < 20 671 672 def close(self): 673 """Reset wireless and network settings to default and stop AP.""" 674 try: 675 self.network_setting.cleanup_network_settings() 676 except AttributeError as e: 677 self.log.warning("OpenWrtAP object has no attribute 'network_setting'") 678 if self.wireless_setting: 679 self.wireless_setting.cleanup_wireless_settings() 680 681 def close_ssh(self): 682 """Close SSH connection to AP.""" 683 self.ssh.close() 684 685 def reboot(self): 686 """Reboot Openwrt.""" 687 self.ssh.run("reboot") 688