1#!/usr/bin/env python3 2# 3# Copyright 2016 - Google, Inc. 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 collections 18import ipaddress 19import os 20import time 21 22from acts import logger 23from acts import utils 24 25from acts.controllers import pdu 26from acts.controllers.ap_lib import ap_get_interface 27from acts.controllers.ap_lib import ap_iwconfig 28from acts.controllers.ap_lib import bridge_interface 29from acts.controllers.ap_lib import dhcp_config 30from acts.controllers.ap_lib import dhcp_server 31from acts.controllers.ap_lib import hostapd 32from acts.controllers.ap_lib import hostapd_constants 33from acts.controllers.ap_lib import hostapd_config 34from acts.controllers.utils_lib.commands import ip 35from acts.controllers.utils_lib.commands import route 36from acts.controllers.utils_lib.commands import shell 37from acts.controllers.utils_lib.ssh import connection 38from acts.controllers.utils_lib.ssh import settings 39from acts.libs.proc import job 40 41MOBLY_CONTROLLER_CONFIG_NAME = 'AccessPoint' 42ACTS_CONTROLLER_REFERENCE_NAME = 'access_points' 43_BRCTL = 'brctl' 44 45LIFETIME = 180 46PROC_NET_SNMP6 = '/proc/net/snmp6' 47SCAPY_INSTALL_COMMAND = 'sudo python setup.py install' 48RA_MULTICAST_ADDR = '33:33:00:00:00:01' 49RA_SCRIPT = 'sendra.py' 50 51 52def create(configs): 53 """Creates ap controllers from a json config. 54 55 Creates an ap controller from either a list, or a single 56 element. The element can either be just the hostname or a dictionary 57 containing the hostname and username of the ap to connect to over ssh. 58 59 Args: 60 The json configs that represent this controller. 61 62 Returns: 63 A new AccessPoint. 64 """ 65 return [AccessPoint(c) for c in configs] 66 67 68def destroy(aps): 69 """Destroys a list of access points. 70 71 Args: 72 aps: The list of access points to destroy. 73 """ 74 for ap in aps: 75 ap.close() 76 77 78def get_info(aps): 79 """Get information on a list of access points. 80 81 Args: 82 aps: A list of AccessPoints. 83 84 Returns: 85 A list of all aps hostname. 86 """ 87 return [ap.ssh_settings.hostname for ap in aps] 88 89 90class Error(Exception): 91 """Error raised when there is a problem with the access point.""" 92 93 94_ApInstance = collections.namedtuple('_ApInstance', ['hostapd', 'subnet']) 95 96# These ranges were split this way since each physical radio can have up 97# to 8 SSIDs so for the 2GHz radio the DHCP range will be 98# 192.168.1 - 8 and the 5Ghz radio will be 192.168.9 - 16 99_AP_2GHZ_SUBNET_STR_DEFAULT = '192.168.1.0/24' 100_AP_5GHZ_SUBNET_STR_DEFAULT = '192.168.9.0/24' 101 102# The last digit of the ip for the bridge interface 103BRIDGE_IP_LAST = '100' 104 105 106class AccessPoint(object): 107 """An access point controller. 108 109 Attributes: 110 ssh: The ssh connection to this ap. 111 ssh_settings: The ssh settings being used by the ssh connection. 112 dhcp_settings: The dhcp server settings being used. 113 """ 114 def __init__(self, configs): 115 """ 116 Args: 117 configs: configs for the access point from config file. 118 """ 119 self.ssh_settings = settings.from_config(configs['ssh_config']) 120 self.log = logger.create_logger(lambda msg: '[Access Point|%s] %s' % 121 (self.ssh_settings.hostname, msg)) 122 self.device_pdu_config = configs.get('PduDevice', None) 123 124 if 'ap_subnet' in configs: 125 self._AP_2G_SUBNET_STR = configs['ap_subnet']['2g'] 126 self._AP_5G_SUBNET_STR = configs['ap_subnet']['5g'] 127 else: 128 self._AP_2G_SUBNET_STR = _AP_2GHZ_SUBNET_STR_DEFAULT 129 self._AP_5G_SUBNET_STR = _AP_5GHZ_SUBNET_STR_DEFAULT 130 131 self._AP_2G_SUBNET = dhcp_config.Subnet( 132 ipaddress.ip_network(self._AP_2G_SUBNET_STR)) 133 self._AP_5G_SUBNET = dhcp_config.Subnet( 134 ipaddress.ip_network(self._AP_5G_SUBNET_STR)) 135 136 self.ssh = connection.SshConnection(self.ssh_settings) 137 138 # Singleton utilities for running various commands. 139 self._ip_cmd = ip.LinuxIpCommand(self.ssh) 140 self._route_cmd = route.LinuxRouteCommand(self.ssh) 141 142 # A map from network interface name to _ApInstance objects representing 143 # the hostapd instance running against the interface. 144 self._aps = dict() 145 self._dhcp = None 146 self._dhcp_bss = dict() 147 self.bridge = bridge_interface.BridgeInterface(self) 148 self.interfaces = ap_get_interface.ApInterfaces(self) 149 self.iwconfig = ap_iwconfig.ApIwconfig(self) 150 151 # Get needed interface names and initialize the unneccessary ones. 152 self.wan = self.interfaces.get_wan_interface() 153 self.wlan = self.interfaces.get_wlan_interface() 154 self.wlan_2g = self.wlan[0] 155 self.wlan_5g = self.wlan[1] 156 self.lan = self.interfaces.get_lan_interface() 157 self._initial_ap() 158 self.scapy_install_path = None 159 self.setup_bridge = False 160 161 def _initial_ap(self): 162 """Initial AP interfaces. 163 164 Bring down hostapd if instance is running, bring down all bridge 165 interfaces. 166 """ 167 # This is necessary for Gale/Whirlwind flashed with dev channel image 168 # Unused interfaces such as existing hostapd daemon, guest, mesh 169 # interfaces need to be brought down as part of the AP initialization 170 # process, otherwise test would fail. 171 try: 172 self.ssh.run('stop wpasupplicant') 173 except job.Error: 174 self.log.info('No wpasupplicant running') 175 try: 176 self.ssh.run('stop hostapd') 177 except job.Error: 178 self.log.info('No hostapd running') 179 # Bring down all wireless interfaces 180 for iface in self.wlan: 181 WLAN_DOWN = 'ifconfig {} down'.format(iface) 182 self.ssh.run(WLAN_DOWN) 183 # Bring down all bridge interfaces 184 bridge_interfaces = self.interfaces.get_bridge_interface() 185 if bridge_interfaces: 186 for iface in bridge_interfaces: 187 BRIDGE_DOWN = 'ifconfig {} down'.format(iface) 188 BRIDGE_DEL = 'brctl delbr {}'.format(iface) 189 self.ssh.run(BRIDGE_DOWN) 190 self.ssh.run(BRIDGE_DEL) 191 192 def start_ap(self, 193 hostapd_config, 194 setup_bridge=False, 195 additional_parameters=None): 196 """Starts as an ap using a set of configurations. 197 198 This will start an ap on this host. To start an ap the controller 199 selects a network interface to use based on the configs given. It then 200 will start up hostapd on that interface. Next a subnet is created for 201 the network interface and dhcp server is refreshed to give out ips 202 for that subnet for any device that connects through that interface. 203 204 Args: 205 hostapd_config: hostapd_config.HostapdConfig, The configurations 206 to use when starting up the ap. 207 setup_bridge: Whether to bridge the LAN interface WLAN interface. 208 Only one WLAN interface can be bridged with the LAN interface 209 and none of the guest networks can be bridged. 210 additional_parameters: A dictionary of parameters that can sent 211 directly into the hostapd config file. This can be used for 212 debugging and or adding one off parameters into the config. 213 214 Returns: 215 An identifier for each ssid being started. These identifiers can be 216 used later by this controller to control the ap. 217 218 Raises: 219 Error: When the ap can't be brought up. 220 """ 221 if hostapd_config.frequency < 5000: 222 interface = self.wlan_2g 223 subnet = self._AP_2G_SUBNET 224 else: 225 interface = self.wlan_5g 226 subnet = self._AP_5G_SUBNET 227 228 # In order to handle dhcp servers on any interface, the initiation of 229 # the dhcp server must be done after the wlan interfaces are figured 230 # out as opposed to being in __init__ 231 self._dhcp = dhcp_server.DhcpServer(self.ssh, interface=interface) 232 233 # For multi bssid configurations the mac address 234 # of the wireless interface needs to have enough space to mask out 235 # up to 8 different mac addresses. So in for one interface the range is 236 # hex 0-7 and for the other the range is hex 8-f. 237 interface_mac_orig = None 238 cmd = "ifconfig %s|grep ether|awk -F' ' '{print $2}'" % interface 239 interface_mac_orig = self.ssh.run(cmd) 240 if interface == self.wlan_5g: 241 hostapd_config.bssid = interface_mac_orig.stdout[:-1] + '0' 242 last_octet = 1 243 if interface == self.wlan_2g: 244 hostapd_config.bssid = interface_mac_orig.stdout[:-1] + '8' 245 last_octet = 9 246 if interface in self._aps: 247 raise ValueError('No WiFi interface available for AP on ' 248 'channel %d' % hostapd_config.channel) 249 250 apd = hostapd.Hostapd(self.ssh, interface) 251 new_instance = _ApInstance(hostapd=apd, subnet=subnet) 252 self._aps[interface] = new_instance 253 254 # Turn off the DHCP server, we're going to change its settings. 255 self.stop_dhcp() 256 # Clear all routes to prevent old routes from interfering. 257 self._route_cmd.clear_routes(net_interface=interface) 258 259 if hostapd_config.bss_lookup: 260 # The self._dhcp_bss dictionary is created to hold the key/value 261 # pair of the interface name and the ip scope that will be 262 # used for the particular interface. The a, b, c, d 263 # variables below are the octets for the ip address. The 264 # third octet is then incremented for each interface that 265 # is requested. This part is designed to bring up the 266 # hostapd interfaces and not the DHCP servers for each 267 # interface. 268 self._dhcp_bss = dict() 269 counter = 1 270 for bss in hostapd_config.bss_lookup: 271 if interface_mac_orig: 272 hostapd_config.bss_lookup[bss].bssid = ( 273 interface_mac_orig.stdout[:-1] + hex(last_octet)[-1:]) 274 self._route_cmd.clear_routes(net_interface=str(bss)) 275 if interface is self.wlan_2g: 276 starting_ip_range = self._AP_2G_SUBNET_STR 277 else: 278 starting_ip_range = self._AP_5G_SUBNET_STR 279 a, b, c, d = starting_ip_range.split('.') 280 self._dhcp_bss[bss] = dhcp_config.Subnet( 281 ipaddress.ip_network('%s.%s.%s.%s' % 282 (a, b, str(int(c) + counter), d))) 283 counter = counter + 1 284 last_octet = last_octet + 1 285 286 apd.start(hostapd_config, additional_parameters=additional_parameters) 287 288 # The DHCP serer requires interfaces to have ips and routes before 289 # the server will come up. 290 interface_ip = ipaddress.ip_interface( 291 '%s/%s' % (subnet.router, subnet.network.netmask)) 292 if setup_bridge is True: 293 bridge_interface_name = 'br_lan' 294 self.create_bridge(bridge_interface_name, [interface, self.lan]) 295 self._ip_cmd.set_ipv4_address(bridge_interface_name, interface_ip) 296 else: 297 self._ip_cmd.set_ipv4_address(interface, interface_ip) 298 if hostapd_config.bss_lookup: 299 # This loop goes through each interface that was setup for 300 # hostapd and assigns the DHCP scopes that were defined but 301 # not used during the hostapd loop above. The k and v 302 # variables represent the interface name, k, and dhcp info, v. 303 for k, v in self._dhcp_bss.items(): 304 bss_interface_ip = ipaddress.ip_interface( 305 '%s/%s' % (self._dhcp_bss[k].router, 306 self._dhcp_bss[k].network.netmask)) 307 self._ip_cmd.set_ipv4_address(str(k), bss_interface_ip) 308 309 # Restart the DHCP server with our updated list of subnets. 310 configured_subnets = [x.subnet for x in self._aps.values()] 311 if hostapd_config.bss_lookup: 312 for k, v in self._dhcp_bss.items(): 313 configured_subnets.append(v) 314 315 self.start_dhcp(subnets=configured_subnets) 316 self.start_nat() 317 318 bss_interfaces = [bss for bss in hostapd_config.bss_lookup] 319 bss_interfaces.append(interface) 320 321 return bss_interfaces 322 323 def start_dhcp(self, subnets): 324 """Start a DHCP server for the specified subnets. 325 326 This allows consumers of the access point objects to control DHCP. 327 328 Args: 329 subnets: A list of Subnets. 330 """ 331 return self._dhcp.start(config=dhcp_config.DhcpConfig(subnets)) 332 333 def stop_dhcp(self): 334 """Stop DHCP for this AP object. 335 336 This allows consumers of the access point objects to control DHCP. 337 """ 338 return self._dhcp.stop() 339 340 def start_nat(self): 341 """Start NAT on the AP. 342 343 This allows consumers of the access point objects to enable NAT 344 on the AP. 345 346 Note that this is currently a global setting, since we don't 347 have per-interface masquerade rules. 348 """ 349 # The following three commands are needed to enable NAT between 350 # the WAN and LAN/WLAN ports. This means anyone connecting to the 351 # WLAN/LAN ports will be able to access the internet if the WAN port 352 # is connected to the internet. 353 self.ssh.run('iptables -t nat -F') 354 self.ssh.run('iptables -t nat -A POSTROUTING -o %s -j MASQUERADE' % 355 self.wan) 356 self.ssh.run('echo 1 > /proc/sys/net/ipv4/ip_forward') 357 self.ssh.run('echo 1 > /proc/sys/net/ipv6/conf/all/forwarding') 358 359 def stop_nat(self): 360 """Stop NAT on the AP. 361 362 This allows consumers of the access point objects to disable NAT on the 363 AP. 364 365 Note that this is currently a global setting, since we don't have 366 per-interface masquerade rules. 367 """ 368 self.ssh.run('iptables -t nat -F') 369 self.ssh.run('echo 0 > /proc/sys/net/ipv4/ip_forward') 370 self.ssh.run('echo 0 > /proc/sys/net/ipv6/conf/all/forwarding') 371 372 def create_bridge(self, bridge_name, interfaces): 373 """Create the specified bridge and bridge the specified interfaces. 374 375 Args: 376 bridge_name: The name of the bridge to create. 377 interfaces: A list of interfaces to add to the bridge. 378 """ 379 380 # Create the bridge interface 381 self.ssh.run( 382 'brctl addbr {bridge_name}'.format(bridge_name=bridge_name)) 383 384 for interface in interfaces: 385 self.ssh.run('brctl addif {bridge_name} {interface}'.format( 386 bridge_name=bridge_name, interface=interface)) 387 388 def remove_bridge(self, bridge_name): 389 """Removes the specified bridge 390 391 Args: 392 bridge_name: The name of the bridge to remove. 393 """ 394 # Check if the bridge exists. 395 # 396 # Cases where it may not are if we failed to initialize properly 397 # 398 # Or if we're doing 2.4Ghz and 5Ghz SSIDs and we've already torn 399 # down the bridge once, but we got called for each band. 400 result = self.ssh.run( 401 'brctl show {bridge_name}'.format(bridge_name=bridge_name), 402 ignore_status=True) 403 404 # If the bridge exists, we'll get an exit_status of 0, indicating 405 # success, so we can continue and remove the bridge. 406 if result.exit_status == 0: 407 self.ssh.run('ip link set {bridge_name} down'.format( 408 bridge_name=bridge_name)) 409 self.ssh.run( 410 'brctl delbr {bridge_name}'.format(bridge_name=bridge_name)) 411 412 def get_bssid_from_ssid(self, ssid, band): 413 """Gets the BSSID from a provided SSID 414 415 Args: 416 ssid: An SSID string. 417 band: 2G or 5G Wifi band. 418 Returns: The BSSID if on the AP or None if SSID could not be found. 419 """ 420 if band == hostapd_constants.BAND_2G: 421 interfaces = [self.wlan_2g, ssid] 422 else: 423 interfaces = [self.wlan_5g, ssid] 424 425 # Get the interface name associated with the given ssid. 426 for interface in interfaces: 427 cmd = "iw dev %s info|grep ssid|awk -F' ' '{print $2}'" % ( 428 str(interface)) 429 iw_output = self.ssh.run(cmd) 430 if 'command failed: No such device' in iw_output.stderr: 431 continue 432 else: 433 # If the configured ssid is equal to the given ssid, we found 434 # the right interface. 435 if iw_output.stdout == ssid: 436 cmd = "iw dev %s info|grep addr|awk -F' ' '{print $2}'" % ( 437 str(interface)) 438 iw_output = self.ssh.run(cmd) 439 return iw_output.stdout 440 return None 441 442 def stop_ap(self, identifier): 443 """Stops a running ap on this controller. 444 445 Args: 446 identifier: The identify of the ap that should be taken down. 447 """ 448 449 if identifier not in list(self._aps.keys()): 450 raise ValueError('Invalid identifier %s given' % identifier) 451 452 instance = self._aps.get(identifier) 453 454 instance.hostapd.stop() 455 try: 456 self.stop_dhcp() 457 except dhcp_server.NoInterfaceError: 458 pass 459 self._ip_cmd.clear_ipv4_addresses(identifier) 460 461 del self._aps[identifier] 462 bridge_interfaces = self.interfaces.get_bridge_interface() 463 if bridge_interfaces: 464 for iface in bridge_interfaces: 465 BRIDGE_DOWN = 'ifconfig {} down'.format(iface) 466 BRIDGE_DEL = 'brctl delbr {}'.format(iface) 467 self.ssh.run(BRIDGE_DOWN) 468 self.ssh.run(BRIDGE_DEL) 469 470 def stop_all_aps(self): 471 """Stops all running aps on this device.""" 472 473 for ap in list(self._aps.keys()): 474 self.stop_ap(ap) 475 476 def close(self): 477 """Called to take down the entire access point. 478 479 When called will stop all aps running on this host, shutdown the dhcp 480 server, and stop the ssh connection. 481 """ 482 483 if self._aps: 484 self.stop_all_aps() 485 self.ssh.close() 486 487 def generate_bridge_configs(self, channel): 488 """Generate a list of configs for a bridge between LAN and WLAN. 489 490 Args: 491 channel: the channel WLAN interface is brought up on 492 iface_lan: the LAN interface to bridge 493 Returns: 494 configs: tuple containing iface_wlan, iface_lan and bridge_ip 495 """ 496 497 if channel < 15: 498 iface_wlan = self.wlan_2g 499 subnet_str = self._AP_2G_SUBNET_STR 500 else: 501 iface_wlan = self.wlan_5g 502 subnet_str = self._AP_5G_SUBNET_STR 503 504 iface_lan = self.lan 505 506 a, b, c, _ = subnet_str.strip('/24').split('.') 507 bridge_ip = "%s.%s.%s.%s" % (a, b, c, BRIDGE_IP_LAST) 508 509 configs = (iface_wlan, iface_lan, bridge_ip) 510 511 return configs 512 513 def install_scapy(self, scapy_path, send_ra_path): 514 """Install scapy 515 516 Args: 517 scapy_path: path where scapy tar file is located on server 518 send_ra_path: path where sendra path is located on server 519 """ 520 self.scapy_install_path = self.ssh.run('mktemp -d').stdout.rstrip() 521 self.log.info("Scapy install path: %s" % self.scapy_install_path) 522 self.ssh.send_file(scapy_path, self.scapy_install_path) 523 self.ssh.send_file(send_ra_path, self.scapy_install_path) 524 525 scapy = os.path.join(self.scapy_install_path, 526 scapy_path.split('/')[-1]) 527 528 untar_res = self.ssh.run('tar -xvf %s -C %s' % 529 (scapy, self.scapy_install_path)) 530 531 instl_res = self.ssh.run( 532 'cd %s; %s' % (self.scapy_install_path, SCAPY_INSTALL_COMMAND)) 533 534 def cleanup_scapy(self): 535 """ Cleanup scapy """ 536 if self.scapy_install_path: 537 cmd = 'rm -rf %s' % self.scapy_install_path 538 self.log.info("Cleaning up scapy %s" % cmd) 539 output = self.ssh.run(cmd) 540 self.scapy_install_path = None 541 542 def send_ra(self, 543 iface, 544 mac=RA_MULTICAST_ADDR, 545 interval=1, 546 count=None, 547 lifetime=LIFETIME, 548 rtt=0): 549 """Invoke scapy and send RA to the device. 550 551 Args: 552 iface: string of the WiFi interface to use for sending packets. 553 mac: string HWAddr/MAC address to send the packets to. 554 interval: int Time to sleep between consecutive packets. 555 count: int Number of packets to be sent. 556 lifetime: int original RA's router lifetime in seconds. 557 rtt: retrans timer of the RA packet 558 """ 559 scapy_command = os.path.join(self.scapy_install_path, RA_SCRIPT) 560 options = ' -m %s -i %d -c %d -l %d -in %s -rtt %s' % ( 561 mac, interval, count, lifetime, iface, rtt) 562 self.log.info("Scapy cmd: %s" % scapy_command + options) 563 res = self.ssh.run(scapy_command + options) 564 565 def get_icmp6intype134(self): 566 """Read the value of Icmp6InType134 and return integer. 567 568 Returns: 569 Integer value >0 if grep is successful; 0 otherwise. 570 """ 571 ra_count_str = self.ssh.run('grep Icmp6InType134 %s || true' % 572 PROC_NET_SNMP6).stdout 573 if ra_count_str: 574 return int(ra_count_str.split()[1]) 575 576 def is_pingable(self): 577 """Attempts to ping the access point. 578 579 Returns: 580 True if ping is successful, else False 581 """ 582 return utils.is_pingable(self.ssh_settings.hostname) 583 584 def is_sshable(self): 585 """Attempts to run command via ssh. 586 587 Returns: 588 True if no exceptions, else False 589 """ 590 try: 591 self.ssh.run('echo') 592 except connection.Error: 593 return False 594 return True 595 596 def hard_power_cycle(self, 597 pdus, 598 unreachable_timeout=30, 599 ping_timeout=60, 600 ssh_timeout=30, 601 hostapd_configs=None): 602 """Kills, then restores power to AccessPoint, verifying it goes down and 603 comes back online cleanly. 604 605 Args: 606 pdus: list, PduDevices in the testbed 607 unreachable_timeout: int, time to wait for AccessPoint to become 608 unreachable 609 ping_timeout: int, time to wait for AccessPoint to responsd to pings 610 ssh_timeout: int, time to wait for AccessPoint to allow SSH 611 hostapd_configs (optional): list, containing hostapd settings. If 612 present, these networks will be spun up after the AP has 613 rebooted. This list can either contain HostapdConfig objects, or 614 dictionaries with the start_ap params 615 (i.e { 'hostapd_config': <HostapdConfig>, 616 'setup_bridge': <bool>, 617 'additional_parameters': <dict> } ). 618 Raise: 619 Error, if no PduDevice is provided in AccessPoint config. 620 ConnectionError, if AccessPoint fails to go offline or come back. 621 """ 622 if not self.device_pdu_config: 623 raise Error('No PduDevice provided in AccessPoint config.') 624 625 if hostapd_configs is None: 626 hostapd_configs = [] 627 628 self.log.info('Power cycling AccessPoint (%s)' % 629 self.ssh_settings.hostname) 630 ap_pdu, ap_pdu_port = pdu.get_pdu_port_for_device( 631 self.device_pdu_config, pdus) 632 633 self.log.info('Killing power to AccessPoint (%s)' % 634 self.ssh_settings.hostname) 635 ap_pdu.off(str(ap_pdu_port)) 636 637 self.log.info('Verifying AccessPoint is unreachable.') 638 timeout = time.time() + unreachable_timeout 639 while time.time() < timeout: 640 if not self.is_pingable(): 641 self.log.info('AccessPoint is unreachable as expected.') 642 break 643 else: 644 self.log.debug( 645 'AccessPoint is still responding to pings. Retrying in 1 ' 646 'second.') 647 time.sleep(1) 648 else: 649 raise ConnectionError('Failed to bring down AccessPoint (%s)' % 650 self.ssh_settings.hostname) 651 self._aps.clear() 652 653 self.log.info('Restoring power to AccessPoint (%s)' % 654 self.ssh_settings.hostname) 655 ap_pdu.on(str(ap_pdu_port)) 656 657 self.log.info('Waiting for AccessPoint to respond to pings.') 658 timeout = time.time() + ping_timeout 659 while time.time() < timeout: 660 if self.is_pingable(): 661 self.log.info('AccessPoint responded to pings.') 662 break 663 else: 664 self.log.debug('AccessPoint is not responding to pings. ' 665 'Retrying in 1 second.') 666 time.sleep(1) 667 else: 668 raise ConnectionError('Timed out waiting for AccessPoint (%s) to ' 669 'respond to pings.' % 670 self.ssh_settings.hostname) 671 672 self.log.info('Waiting for AccessPoint to allow ssh connection.') 673 timeout = time.time() + ssh_timeout 674 while time.time() < timeout: 675 if self.is_sshable(): 676 self.log.info('AccessPoint available via ssh.') 677 break 678 else: 679 self.log.debug('AccessPoint is not allowing ssh connection. ' 680 'Retrying in 1 second.') 681 time.sleep(1) 682 else: 683 raise ConnectionError('Timed out waiting for AccessPoint (%s) to ' 684 'allow ssh connection.' % 685 self.ssh_settings.hostname) 686 687 # Allow 5 seconds for OS to finish getting set up 688 time.sleep(5) 689 self._initial_ap() 690 self.log.info('AccessPoint (%s) power cycled successfully.' % 691 self.ssh_settings.hostname) 692 693 for settings in hostapd_configs: 694 if type(settings) == hostapd_config.HostapdConfig: 695 config = settings 696 setup_bridge = False 697 additional_parameters = None 698 699 elif type(settings) == dict: 700 config = settings['hostapd_config'] 701 setup_bridge = settings.get('setup_bridge', False) 702 additional_parameters = settings.get('additional_parameters', 703 None) 704 else: 705 raise TypeError( 706 'Items in hostapd_configs list must either be ' 707 'hostapd.HostapdConfig objects or dictionaries.') 708 709 self.log.info('Restarting network (%s) on AccessPoint.' % 710 config.ssid) 711 self.start_ap(config, 712 setup_bridge=setup_bridge, 713 additional_parameters=additional_parameters) 714