tturney | 1bdf77d | 2015-12-28 17:46:13 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3.4 |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 2 | # |
tturney | 1bdf77d | 2015-12-28 17:46:13 -0800 | [diff] [blame] | 3 | # Copyright 2016 - Google, Inc. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 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 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 17 | import ipaddress |
| 18 | import logging |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 19 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 20 | from acts.controllers.ap_lib import dhcp_config |
| 21 | from acts.controllers.ap_lib import dhcp_server |
| 22 | from acts.controllers.ap_lib import hostapd |
| 23 | from acts.controllers.ap_lib import hostapd_config |
| 24 | from acts.controllers.utils_lib.commands import ip |
| 25 | from acts.controllers.utils_lib.commands import route |
| 26 | from acts.controllers.utils_lib.ssh import connection |
| 27 | from acts.controllers.utils_lib.ssh import settings |
| 28 | |
| 29 | ACTS_CONTROLLER_CONFIG_NAME = 'AccessPoint' |
Benny Peake | 0f5049b | 2016-10-28 15:57:01 -0700 | [diff] [blame^] | 30 | ACTS_CONTROLLER_REFERENCE_NAME = 'access_points' |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 31 | |
| 32 | HOSTNAME_KEY = 'hostname' |
| 33 | USERNAME_KEY = 'user' |
| 34 | PORT_KEY = 'port' |
Ang Li | 6efbf0e | 2016-03-22 19:13:56 -0700 | [diff] [blame] | 35 | |
Ang Li | 51df045 | 2016-06-07 10:58:16 -0700 | [diff] [blame] | 36 | |
Ang Li | a11f2cf | 2016-05-05 11:54:40 -0700 | [diff] [blame] | 37 | def create(configs): |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 38 | """Creates ap controllers from a json config. |
| 39 | |
| 40 | Creates an ap controller from either a list, or a single |
| 41 | element. The element can either be just the hostname or a dictionary |
| 42 | containing the hostname and username of the ap to connect to over ssh. |
| 43 | |
| 44 | Args: |
| 45 | The json configs that represent this controller. |
| 46 | |
| 47 | Returns: |
| 48 | A new AccessPoint. |
| 49 | """ |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 50 | results = [] |
| 51 | for c in configs: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 52 | return AccessPoint(configuration[HOSTNAME_KEY], |
| 53 | configuration.get(USERNAME_KEY), |
| 54 | configuration.get(PORT_KEY)) |
| 55 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 56 | return results |
| 57 | |
Ang Li | 51df045 | 2016-06-07 10:58:16 -0700 | [diff] [blame] | 58 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 59 | def destroy(aps): |
| 60 | """Destroys a list of access points. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 61 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 62 | Args: |
| 63 | aps: The list of access points to destroy. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 64 | """ |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 65 | for ap in aps: |
| 66 | ap.close() |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 67 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 68 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 69 | def get_info(aps): |
| 70 | """Get information on a list of access points. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 71 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 72 | Args: |
| 73 | aps: A list of AccessPoints. |
| 74 | |
| 75 | Returns: |
| 76 | A list of all aps hostname. |
| 77 | """ |
| 78 | return [ap.hostname for ap in aps] |
| 79 | |
| 80 | |
| 81 | class Error(Exception): |
| 82 | """Error raised when there is a problem with the access point.""" |
| 83 | |
| 84 | |
| 85 | class AccessPoint(object): |
| 86 | """An access point controller. |
| 87 | |
| 88 | Attributes: |
| 89 | hostname: The hostname of the ap. |
| 90 | ssh: The ssh connection to this ap. |
| 91 | ssh_settings: The ssh settings being used by the ssh conneciton. |
| 92 | dhcp_settings: The dhcp server settings being used. |
| 93 | """ |
| 94 | |
| 95 | AP_2GHZ_INTERFACE = 'wlan0' |
| 96 | AP_5GHZ_INTERFACE = 'wlan1' |
| 97 | |
| 98 | AP_2GHZ_SUBNET = dhcp_config.Subnet(ipaddress.ip_network('192.168.1.0/24')) |
| 99 | AP_5GHZ_SUBNET = dhcp_config.Subnet(ipaddress.ip_network('192.168.2.0/24')) |
| 100 | |
| 101 | def __init__(self, hostname, username=None, port=None): |
| 102 | """ |
| 103 | Args: |
| 104 | hostname: The hostname of the access point. |
| 105 | username: The username to connect with. |
| 106 | port: The port to connect with. |
| 107 | """ |
| 108 | if port is None: |
| 109 | # TODO: Change settings to do this. |
| 110 | port = 22 |
| 111 | |
| 112 | if username is None: |
| 113 | username = 'root' |
| 114 | |
| 115 | self.hostname = hostname |
| 116 | self.ssh_settings = settings.SshSettings(hostname, username, port=port) |
| 117 | self.ssh = connection.SshConnection(self.ssh_settings) |
| 118 | |
| 119 | # Spawn interface for dhcp server. |
| 120 | self.dhcp_settings = dhcp_config.DhcpConfig( |
| 121 | [self.AP_2GHZ_SUBNET, self.AP_5GHZ_SUBNET]) |
| 122 | self._dhcp = dhcp_server.DhcpServer(self.ssh) |
| 123 | |
| 124 | # Spawn interfaces for hostapd on both of the interfaces. |
| 125 | self._hostapd_2ghz = hostapd.Hostapd(self.ssh, self.AP_2GHZ_INTERFACE) |
| 126 | self._hostapd_5ghz = hostapd.Hostapd(self.ssh, self.AP_5GHZ_SUBNET) |
| 127 | |
| 128 | self._ip_cmd = ip.LinuxIpCommand(self.ssh) |
| 129 | self._route_cmd = route.LinuxRouteCommand(self.ssh) |
| 130 | |
| 131 | def __del__(self): |
| 132 | self.close() |
| 133 | |
| 134 | def start_ap(self, hostapd_config): |
| 135 | """Starts as an ap using a set of configurations. |
| 136 | |
| 137 | This will start an ap on this host. To start an ap the controller |
| 138 | selects a network interface to use based on the configs given. It then |
| 139 | will start up hostapd on that interface. Next a subnet is created for |
| 140 | the network interface and dhcp server is refreshed to give out ips |
| 141 | for that subnet for any device that connects through that interface. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 142 | |
| 143 | Args: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 144 | hostapd_config: hostapd_config.HostapdConfig, The configurations |
| 145 | to use when starting up the ap. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 146 | |
| 147 | Returns: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 148 | An identifier for the ap being run. This identifier can be used |
| 149 | later by this controller to control the ap. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 150 | |
| 151 | Raises: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 152 | Error: When the ap can't be brought up. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 153 | """ |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 154 | if hostapd_config.frequency < 5000: |
| 155 | if self._hostapd_2ghz.is_alive(): |
| 156 | raise Error('2GHz ap already up.') |
| 157 | identifier = self.AP_2GHZ_INTERFACE |
| 158 | subnet = self.AP_2GHZ_SUBNET |
| 159 | apd = self._hostapd_2ghz |
| 160 | else: |
| 161 | if self._hostapd_5ghz.is_alive(): |
| 162 | raise Error('5GHz ap already up.') |
| 163 | identifier = self.AP_5GHZ_INTERFACE |
| 164 | subnet = self.AP_5GHZ_SUBNET |
| 165 | apd = self._hostapd_5ghz |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 166 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 167 | apd.start(hostapd_config) |
| 168 | |
| 169 | # Clear all routes to prevent old routes from interfering. |
| 170 | self._route_cmd.clear_routes(net_interface=identifier) |
| 171 | |
| 172 | # dhcp server requires interfaces to have ips and routes before coming |
| 173 | # up. |
| 174 | router_address = subnet.router |
| 175 | network = subnet.network |
| 176 | router_interface = ipaddress.ip_interface('%s/%s' % (router_address, |
| 177 | network.netmask)) |
| 178 | self._ip_cmd.set_ipv4_address(identifier, router_interface) |
| 179 | |
| 180 | # DHCP server needs to restart to take into account any Interface |
| 181 | # change. |
| 182 | self._dhcp.start(self.dhcp_settings) |
| 183 | |
| 184 | return identifier |
| 185 | |
| 186 | def stop_ap(self, identifier): |
| 187 | """Stops a running ap on this controller. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 188 | |
| 189 | Args: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 190 | identifier: The identify of the ap that should be taken down. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 191 | """ |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 192 | if identifier == self.AP_5GHZ_INTERFACE: |
| 193 | apd = self._hostapd_5ghz |
| 194 | elif identifier == self.AP_2GHZ_INTERFACE: |
| 195 | apd = self._hostapd_2ghz |
| 196 | else: |
| 197 | raise ValueError('Invalid identifer %s given' % identifier) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 198 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 199 | apd.stop() |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 200 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 201 | self._ip_cmd.clear_ipv4_addresses(identifier) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 202 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 203 | # DHCP server needs to refresh in order to tear down the subnet no |
| 204 | # longer being used. In the event that all interfaces are torn down |
| 205 | # then an exception gets thrown. We need to catch this exception and |
| 206 | # check that all interfaces should actually be down. |
| 207 | try: |
| 208 | self._dhcp.start(self.dhcp_settings) |
| 209 | except dhcp_server.NoInterfaceError: |
| 210 | if self._hostapd_2ghz.is_alive() or self._hostapd_5ghz.is_alive(): |
| 211 | raise |
| 212 | |
| 213 | def stop_all_aps(self): |
| 214 | """Stops all running aps on this device.""" |
| 215 | self.stop_ap(self.AP_2GHZ_INTERFACE) |
| 216 | self.stop_ap(self.AP_5GHZ_INTERFACE) |
| 217 | |
| 218 | def close(self): |
| 219 | """Called to take down the entire access point. |
| 220 | |
| 221 | When called will stop all aps running on this host, shutdown the dhcp |
| 222 | server, and stop the ssh conneciton. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 223 | """ |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 224 | self.stop_all_aps() |
| 225 | self._dhcp.stop() |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 226 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 227 | self.ssh.close() |