blob: b0b8edf0277d6cf06786677c6996d52334ea07c8 [file] [log] [blame]
tturney1bdf77d2015-12-28 17:46:13 -08001#!/usr/bin/env python3.4
Ang Li73697b32015-12-03 00:41:53 +00002#
tturney1bdf77d2015-12-28 17:46:13 -08003# Copyright 2016 - Google, Inc.
Ang Li73697b32015-12-03 00:41:53 +00004#
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 Peake85f112a2016-10-07 19:41:49 -070017import ipaddress
18import logging
Ang Li73697b32015-12-03 00:41:53 +000019
Benny Peake85f112a2016-10-07 19:41:49 -070020from acts.controllers.ap_lib import dhcp_config
21from acts.controllers.ap_lib import dhcp_server
22from acts.controllers.ap_lib import hostapd
23from acts.controllers.ap_lib import hostapd_config
24from acts.controllers.utils_lib.commands import ip
25from acts.controllers.utils_lib.commands import route
26from acts.controllers.utils_lib.ssh import connection
27from acts.controllers.utils_lib.ssh import settings
28
29ACTS_CONTROLLER_CONFIG_NAME = 'AccessPoint'
Benny Peake0f5049b2016-10-28 15:57:01 -070030ACTS_CONTROLLER_REFERENCE_NAME = 'access_points'
Benny Peake85f112a2016-10-07 19:41:49 -070031
32HOSTNAME_KEY = 'hostname'
33USERNAME_KEY = 'user'
34PORT_KEY = 'port'
Ang Li6efbf0e2016-03-22 19:13:56 -070035
Ang Li51df0452016-06-07 10:58:16 -070036
Ang Lia11f2cf2016-05-05 11:54:40 -070037def create(configs):
Benny Peake85f112a2016-10-07 19:41:49 -070038 """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 Li73697b32015-12-03 00:41:53 +000050 results = []
51 for c in configs:
Benny Peake85f112a2016-10-07 19:41:49 -070052 return AccessPoint(configuration[HOSTNAME_KEY],
53 configuration.get(USERNAME_KEY),
54 configuration.get(PORT_KEY))
55
Ang Li73697b32015-12-03 00:41:53 +000056 return results
57
Ang Li51df0452016-06-07 10:58:16 -070058
Benny Peake85f112a2016-10-07 19:41:49 -070059def destroy(aps):
60 """Destroys a list of access points.
Ang Li73697b32015-12-03 00:41:53 +000061
Benny Peake85f112a2016-10-07 19:41:49 -070062 Args:
63 aps: The list of access points to destroy.
Ang Li73697b32015-12-03 00:41:53 +000064 """
Benny Peake85f112a2016-10-07 19:41:49 -070065 for ap in aps:
66 ap.close()
Ang Li73697b32015-12-03 00:41:53 +000067
Ang Li73697b32015-12-03 00:41:53 +000068
Benny Peake85f112a2016-10-07 19:41:49 -070069def get_info(aps):
70 """Get information on a list of access points.
Ang Li73697b32015-12-03 00:41:53 +000071
Benny Peake85f112a2016-10-07 19:41:49 -070072 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
81class Error(Exception):
82 """Error raised when there is a problem with the access point."""
83
84
85class 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 Li73697b32015-12-03 00:41:53 +0000142
143 Args:
Benny Peake85f112a2016-10-07 19:41:49 -0700144 hostapd_config: hostapd_config.HostapdConfig, The configurations
145 to use when starting up the ap.
Ang Li73697b32015-12-03 00:41:53 +0000146
147 Returns:
Benny Peake85f112a2016-10-07 19:41:49 -0700148 An identifier for the ap being run. This identifier can be used
149 later by this controller to control the ap.
Ang Li73697b32015-12-03 00:41:53 +0000150
151 Raises:
Benny Peake85f112a2016-10-07 19:41:49 -0700152 Error: When the ap can't be brought up.
Ang Li73697b32015-12-03 00:41:53 +0000153 """
Benny Peake85f112a2016-10-07 19:41:49 -0700154 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 Li73697b32015-12-03 00:41:53 +0000166
Benny Peake85f112a2016-10-07 19:41:49 -0700167 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 Li73697b32015-12-03 00:41:53 +0000188
189 Args:
Benny Peake85f112a2016-10-07 19:41:49 -0700190 identifier: The identify of the ap that should be taken down.
Ang Li73697b32015-12-03 00:41:53 +0000191 """
Benny Peake85f112a2016-10-07 19:41:49 -0700192 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 Li73697b32015-12-03 00:41:53 +0000198
Benny Peake85f112a2016-10-07 19:41:49 -0700199 apd.stop()
Ang Li73697b32015-12-03 00:41:53 +0000200
Benny Peake85f112a2016-10-07 19:41:49 -0700201 self._ip_cmd.clear_ipv4_addresses(identifier)
Ang Li73697b32015-12-03 00:41:53 +0000202
Benny Peake85f112a2016-10-07 19:41:49 -0700203 # 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 Li73697b32015-12-03 00:41:53 +0000223 """
Benny Peake85f112a2016-10-07 19:41:49 -0700224 self.stop_all_aps()
225 self._dhcp.stop()
Ang Li73697b32015-12-03 00:41:53 +0000226
Benny Peake85f112a2016-10-07 19:41:49 -0700227 self.ssh.close()