Bindu Mahadev | e57533d | 2018-05-22 16:35:03 -0700 | [diff] [blame^] | 1 | #!/usr/bin/env python3 |
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 | |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 17 | import collections |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 18 | import ipaddress |
Bindu Mahadev | e57533d | 2018-05-22 16:35:03 -0700 | [diff] [blame^] | 19 | import logging |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 20 | import time |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 21 | |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 22 | from acts.controllers.ap_lib import ap_get_interface |
Qi Jiang | c3e8785 | 2017-10-04 22:45:52 -0700 | [diff] [blame] | 23 | from acts.controllers.ap_lib import bridge_interface |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 24 | from acts.controllers.ap_lib import dhcp_config |
| 25 | from acts.controllers.ap_lib import dhcp_server |
| 26 | from acts.controllers.ap_lib import hostapd |
Bindu Mahadev | e57533d | 2018-05-22 16:35:03 -0700 | [diff] [blame^] | 27 | from acts.controllers.ap_lib import hostapd_config |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 28 | from acts.controllers.utils_lib.commands import ip |
| 29 | from acts.controllers.utils_lib.commands import route |
Bindu Mahadev | e57533d | 2018-05-22 16:35:03 -0700 | [diff] [blame^] | 30 | from acts.controllers.utils_lib.commands import shell |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 31 | from acts.controllers.utils_lib.ssh import connection |
| 32 | from acts.controllers.utils_lib.ssh import settings |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 33 | from acts.libs.proc import job |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 34 | |
| 35 | ACTS_CONTROLLER_CONFIG_NAME = 'AccessPoint' |
Benny Peake | 0f5049b | 2016-10-28 15:57:01 -0700 | [diff] [blame] | 36 | ACTS_CONTROLLER_REFERENCE_NAME = 'access_points' |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 37 | _BRCTL = 'brctl' |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 38 | |
Ang Li | 51df045 | 2016-06-07 10:58:16 -0700 | [diff] [blame] | 39 | |
Ang Li | a11f2cf | 2016-05-05 11:54:40 -0700 | [diff] [blame] | 40 | def create(configs): |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 41 | """Creates ap controllers from a json config. |
| 42 | |
| 43 | Creates an ap controller from either a list, or a single |
| 44 | element. The element can either be just the hostname or a dictionary |
| 45 | containing the hostname and username of the ap to connect to over ssh. |
| 46 | |
| 47 | Args: |
| 48 | The json configs that represent this controller. |
| 49 | |
| 50 | Returns: |
| 51 | A new AccessPoint. |
| 52 | """ |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 53 | return [AccessPoint(c) for c in configs] |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 54 | |
Ang Li | 51df045 | 2016-06-07 10:58:16 -0700 | [diff] [blame] | 55 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 56 | def destroy(aps): |
| 57 | """Destroys a list of access points. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 58 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 59 | Args: |
| 60 | aps: The list of access points to destroy. |
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 | for ap in aps: |
| 63 | ap.close() |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 64 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 65 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 66 | def get_info(aps): |
| 67 | """Get information on a list of access points. |
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 | Args: |
| 70 | aps: A list of AccessPoints. |
| 71 | |
| 72 | Returns: |
| 73 | A list of all aps hostname. |
| 74 | """ |
Christopher Wiley | 7dae1d9 | 2016-10-31 14:47:58 -0700 | [diff] [blame] | 75 | return [ap.ssh_settings.hostname for ap in aps] |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 76 | |
| 77 | |
| 78 | class Error(Exception): |
| 79 | """Error raised when there is a problem with the access point.""" |
| 80 | |
| 81 | |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 82 | _ApInstance = collections.namedtuple('_ApInstance', ['hostapd', 'subnet']) |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 83 | |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 84 | # These ranges were split this way since each physical radio can have up |
| 85 | # to 8 SSIDs so for the 2GHz radio the DHCP range will be |
| 86 | # 192.168.1 - 8 and the 5Ghz radio will be 192.168.9 - 16 |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 87 | _AP_2GHZ_SUBNET_STR_DEFAULT = '192.168.1.0/24' |
| 88 | _AP_5GHZ_SUBNET_STR_DEFAULT = '192.168.9.0/24' |
| 89 | |
Qi Jiang | c3e8785 | 2017-10-04 22:45:52 -0700 | [diff] [blame] | 90 | # The last digit of the ip for the bridge interface |
| 91 | BRIDGE_IP_LAST = '100' |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 92 | |
| 93 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 94 | class AccessPoint(object): |
| 95 | """An access point controller. |
| 96 | |
| 97 | Attributes: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 98 | ssh: The ssh connection to this ap. |
markdr | b629b32 | 2017-05-22 13:44:06 -0700 | [diff] [blame] | 99 | ssh_settings: The ssh settings being used by the ssh connection. |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 100 | dhcp_settings: The dhcp server settings being used. |
| 101 | """ |
| 102 | |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 103 | def __init__(self, configs): |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 104 | """ |
| 105 | Args: |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 106 | configs: configs for the access point from config file. |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 107 | """ |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 108 | self.ssh_settings = settings.from_config(configs['ssh_config']) |
| 109 | if 'ap_subnet' in configs: |
| 110 | self._AP_2G_SUBNET_STR = configs['ap_subnet']['2g'] |
| 111 | self._AP_5G_SUBNET_STR = configs['ap_subnet']['5g'] |
| 112 | else: |
| 113 | self._AP_2G_SUBNET_STR = _AP_2GHZ_SUBNET_STR_DEFAULT |
| 114 | self._AP_5G_SUBNET_STR = _AP_5GHZ_SUBNET_STR_DEFAULT |
| 115 | |
| 116 | self._AP_2G_SUBNET = dhcp_config.Subnet( |
| 117 | ipaddress.ip_network(self._AP_2G_SUBNET_STR)) |
| 118 | self._AP_5G_SUBNET = dhcp_config.Subnet( |
| 119 | ipaddress.ip_network(self._AP_5G_SUBNET_STR)) |
| 120 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 121 | self.ssh = connection.SshConnection(self.ssh_settings) |
| 122 | |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 123 | # Singleton utilities for running various commands. |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 124 | self._ip_cmd = ip.LinuxIpCommand(self.ssh) |
| 125 | self._route_cmd = route.LinuxRouteCommand(self.ssh) |
| 126 | |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 127 | # A map from network interface name to _ApInstance objects representing |
| 128 | # the hostapd instance running against the interface. |
| 129 | self._aps = dict() |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 130 | self.bridge = bridge_interface.BridgeInterface(self) |
| 131 | self.interfaces = ap_get_interface.ApInterfaces(self) |
| 132 | |
Bindu Mahadev | e57533d | 2018-05-22 16:35:03 -0700 | [diff] [blame^] | 133 | # Get needed interface names and initialize the unneccessary ones. |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 134 | self.wan = self.interfaces.get_wan_interface() |
| 135 | self.wlan = self.interfaces.get_wlan_interface() |
| 136 | self.wlan_2g = self.wlan[0] |
| 137 | self.wlan_5g = self.wlan[1] |
| 138 | self.lan = self.interfaces.get_lan_interface() |
| 139 | self.__initial_ap() |
| 140 | |
| 141 | def __initial_ap(self): |
| 142 | """Initial AP interfaces. |
| 143 | |
| 144 | Bring down hostapd if instance is running, bring down all bridge |
Bindu Mahadev | 18c5f60 | 2018-03-26 23:10:27 +0000 | [diff] [blame] | 145 | interfaces. |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 146 | """ |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 147 | try: |
Qi Jiang | 0a4c5e8 | 2018-04-13 12:13:14 -0700 | [diff] [blame] | 148 | # This is necessary for Gale/Whirlwind flashed with dev channel image |
| 149 | # Unused interfaces such as existing hostapd daemon, guest, mesh |
| 150 | # interfaces need to be brought down as part of the AP initialization |
| 151 | # process, otherwise test would fail. |
Mark De Ruyter | 4a4cd7c | 2018-03-30 20:17:43 +0000 | [diff] [blame] | 152 | try: |
| 153 | self.ssh.run('stop hostapd') |
| 154 | except job.Error: |
| 155 | self.log.debug('No hostapd running') |
| 156 | # Bring down all wireless interfaces |
| 157 | for iface in self.wlan: |
| 158 | WLAN_DOWN = 'ifconfig {} down'.format(iface) |
| 159 | self.ssh.run(WLAN_DOWN) |
| 160 | # Bring down all bridge interfaces |
| 161 | bridge_interfaces = self.interfaces.get_bridge_interface() |
| 162 | if bridge_interfaces: |
| 163 | for iface in bridge_interfaces: |
| 164 | BRIDGE_DOWN = 'ifconfig {} down'.format(iface) |
| 165 | BRIDGE_DEL = 'brctl delbr {}'.format(iface) |
| 166 | self.ssh.run(BRIDGE_DOWN) |
| 167 | self.ssh.run(BRIDGE_DEL) |
| 168 | except Exception: |
| 169 | # TODO(b/76101464): APs may not clean up properly from previous |
| 170 | # runs. Rebooting the AP can put them back into the correct state. |
| 171 | self.log.exception('Unable to bring down hostapd. Rebooting.') |
| 172 | # Reboot the AP. |
| 173 | try: |
| 174 | self.ssh.run('reboot') |
| 175 | # This sleep ensures the device had time to go down. |
| 176 | time.sleep(10) |
| 177 | self.ssh.run('echo connected', timeout=300) |
| 178 | except Exception as e: |
| 179 | self.log.exception("Error in rebooting AP: %s", e) |
| 180 | raise |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 181 | |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 182 | def start_ap(self, hostapd_config, additional_parameters=None): |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 183 | """Starts as an ap using a set of configurations. |
| 184 | |
| 185 | This will start an ap on this host. To start an ap the controller |
| 186 | selects a network interface to use based on the configs given. It then |
| 187 | will start up hostapd on that interface. Next a subnet is created for |
| 188 | the network interface and dhcp server is refreshed to give out ips |
| 189 | for that subnet for any device that connects through that interface. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 190 | |
| 191 | Args: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 192 | hostapd_config: hostapd_config.HostapdConfig, The configurations |
| 193 | to use when starting up the ap. |
markdr | b629b32 | 2017-05-22 13:44:06 -0700 | [diff] [blame] | 194 | additional_parameters: A dictionary of parameters that can sent |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 195 | directly into the hostapd config file. This |
| 196 | can be used for debugging and or adding one |
| 197 | off parameters into the config. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 198 | |
| 199 | Returns: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 200 | An identifier for the ap being run. This identifier can be used |
| 201 | later by this controller to control the ap. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 202 | |
| 203 | Raises: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 204 | Error: When the ap can't be brought up. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 205 | """ |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 206 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 207 | if hostapd_config.frequency < 5000: |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 208 | interface = self.wlan_2g |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 209 | subnet = self._AP_2G_SUBNET |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 210 | else: |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 211 | interface = self.wlan_5g |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 212 | subnet = self._AP_5G_SUBNET |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 213 | |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 214 | # In order to handle dhcp servers on any interface, the initiation of |
| 215 | # the dhcp server must be done after the wlan interfaces are figured |
| 216 | # out as opposed to being in __init__ |
| 217 | self._dhcp = dhcp_server.DhcpServer(self.ssh, interface=interface) |
| 218 | |
| 219 | # For multi bssid configurations the mac address |
| 220 | # of the wireless interface needs to have enough space to mask out |
| 221 | # up to 8 different mac addresses. The easiest way to do this |
| 222 | # is to set the last byte to 0. While technically this could |
| 223 | # cause a duplicate mac address it is unlikely and will allow for |
Bindu Mahadev | 2ef0c3e | 2017-07-13 15:39:55 -0700 | [diff] [blame] | 224 | # one radio to have up to 8 APs on the interface. |
Joe Brennan | 3d00b2a | 2017-04-13 14:27:05 -0700 | [diff] [blame] | 225 | interface_mac_orig = None |
Bindu Mahadev | 2ef0c3e | 2017-07-13 15:39:55 -0700 | [diff] [blame] | 226 | cmd = "ifconfig %s|grep ether|awk -F' ' '{print $2}'" % interface |
| 227 | interface_mac_orig = self.ssh.run(cmd) |
| 228 | hostapd_config.bssid = interface_mac_orig.stdout[:-1] + '0' |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 229 | |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 230 | if interface in self._aps: |
| 231 | raise ValueError('No WiFi interface available for AP on ' |
| 232 | 'channel %d' % hostapd_config.channel) |
| 233 | |
| 234 | apd = hostapd.Hostapd(self.ssh, interface) |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 235 | new_instance = _ApInstance(hostapd=apd, subnet=subnet) |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 236 | self._aps[interface] = new_instance |
| 237 | |
| 238 | # Turn off the DHCP server, we're going to change its settings. |
| 239 | self._dhcp.stop() |
| 240 | # Clear all routes to prevent old routes from interfering. |
| 241 | self._route_cmd.clear_routes(net_interface=interface) |
| 242 | |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 243 | if hostapd_config.bss_lookup: |
| 244 | # The dhcp_bss dictionary is created to hold the key/value |
| 245 | # pair of the interface name and the ip scope that will be |
| 246 | # used for the particular interface. The a, b, c, d |
| 247 | # variables below are the octets for the ip address. The |
| 248 | # third octet is then incremented for each interface that |
| 249 | # is requested. This part is designed to bring up the |
| 250 | # hostapd interfaces and not the DHCP servers for each |
| 251 | # interface. |
| 252 | dhcp_bss = {} |
| 253 | counter = 1 |
| 254 | for bss in hostapd_config.bss_lookup: |
Bindu Mahadev | 2ef0c3e | 2017-07-13 15:39:55 -0700 | [diff] [blame] | 255 | if interface_mac_orig: |
Bindu Mahadev | e57533d | 2018-05-22 16:35:03 -0700 | [diff] [blame^] | 256 | hostapd_config.bss_lookup[ |
| 257 | bss].bssid = interface_mac_orig.stdout[:-1] + str( |
| 258 | counter) |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 259 | self._route_cmd.clear_routes(net_interface=str(bss)) |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 260 | if interface is self.wlan_2g: |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 261 | starting_ip_range = self._AP_2G_SUBNET_STR |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 262 | else: |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 263 | starting_ip_range = self._AP_5G_SUBNET_STR |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 264 | a, b, c, d = starting_ip_range.split('.') |
| 265 | dhcp_bss[bss] = dhcp_config.Subnet( |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 266 | ipaddress.ip_network('%s.%s.%s.%s' % |
| 267 | (a, b, str(int(c) + counter), d))) |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 268 | counter = counter + 1 |
| 269 | |
| 270 | apd.start(hostapd_config, additional_parameters=additional_parameters) |
| 271 | |
| 272 | # The DHCP serer requires interfaces to have ips and routes before |
| 273 | # the server will come up. |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 274 | interface_ip = ipaddress.ip_interface( |
| 275 | '%s/%s' % (subnet.router, subnet.network.netmask)) |
| 276 | self._ip_cmd.set_ipv4_address(interface, interface_ip) |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 277 | if hostapd_config.bss_lookup: |
| 278 | # This loop goes through each interface that was setup for |
| 279 | # hostapd and assigns the DHCP scopes that were defined but |
| 280 | # not used during the hostapd loop above. The k and v |
| 281 | # variables represent the interface name, k, and dhcp info, v. |
| 282 | for k, v in dhcp_bss.items(): |
| 283 | bss_interface_ip = ipaddress.ip_interface( |
markdr | 85a5e1a | 2017-07-17 14:11:34 -0700 | [diff] [blame] | 284 | '%s/%s' % (dhcp_bss[k].router, |
| 285 | dhcp_bss[k].network.netmask)) |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 286 | self._ip_cmd.set_ipv4_address(str(k), bss_interface_ip) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 287 | |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 288 | # Restart the DHCP server with our updated list of subnets. |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 289 | configured_subnets = [x.subnet for x in self._aps.values()] |
| 290 | if hostapd_config.bss_lookup: |
| 291 | for k, v in dhcp_bss.items(): |
| 292 | configured_subnets.append(v) |
| 293 | |
| 294 | self._dhcp.start(config=dhcp_config.DhcpConfig(configured_subnets)) |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 295 | |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 296 | # The following three commands are needed to enable bridging between |
| 297 | # the WAN and LAN/WLAN ports. This means anyone connecting to the |
| 298 | # WLAN/LAN ports will be able to access the internet if the WAN port |
| 299 | # is connected to the internet. |
| 300 | self.ssh.run('iptables -t nat -F') |
| 301 | self.ssh.run( |
| 302 | 'iptables -t nat -A POSTROUTING -o %s -j MASQUERADE' % self.wan) |
| 303 | self.ssh.run('echo 1 > /proc/sys/net/ipv4/ip_forward') |
| 304 | |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 305 | return interface |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 306 | |
Joe Brennan | 3d00b2a | 2017-04-13 14:27:05 -0700 | [diff] [blame] | 307 | def get_bssid_from_ssid(self, ssid): |
| 308 | """Gets the BSSID from a provided SSID |
| 309 | |
| 310 | Args: |
| 311 | ssid: An SSID string |
Bindu Mahadev | 9bc52e6 | 2017-06-28 17:01:12 -0700 | [diff] [blame] | 312 | Returns: The BSSID if on the AP or None if SSID could not be found. |
Joe Brennan | 3d00b2a | 2017-04-13 14:27:05 -0700 | [diff] [blame] | 313 | """ |
| 314 | |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 315 | interfaces = [self.wlan_2g, self.wlan_5g, ssid] |
Bindu Mahadev | 9bc52e6 | 2017-06-28 17:01:12 -0700 | [diff] [blame] | 316 | # Get the interface name associated with the given ssid. |
| 317 | for interface in interfaces: |
markdr | 85a5e1a | 2017-07-17 14:11:34 -0700 | [diff] [blame] | 318 | cmd = "iw dev %s info|grep ssid|awk -F' ' '{print $2}'" % ( |
Bindu Mahadev | 9bc52e6 | 2017-06-28 17:01:12 -0700 | [diff] [blame] | 319 | str(interface)) |
| 320 | iw_output = self.ssh.run(cmd) |
| 321 | if 'command failed: No such device' in iw_output.stderr: |
| 322 | continue |
| 323 | else: |
| 324 | # If the configured ssid is equal to the given ssid, we found |
| 325 | # the right interface. |
| 326 | if iw_output.stdout == ssid: |
markdr | 85a5e1a | 2017-07-17 14:11:34 -0700 | [diff] [blame] | 327 | cmd = "iw dev %s info|grep addr|awk -F' ' '{print $2}'" % ( |
Bindu Mahadev | 9bc52e6 | 2017-06-28 17:01:12 -0700 | [diff] [blame] | 328 | str(interface)) |
| 329 | iw_output = self.ssh.run(cmd) |
| 330 | return iw_output.stdout |
| 331 | return None |
Joe Brennan | 3d00b2a | 2017-04-13 14:27:05 -0700 | [diff] [blame] | 332 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 333 | def stop_ap(self, identifier): |
| 334 | """Stops a running ap on this controller. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 335 | |
| 336 | Args: |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 337 | identifier: The identify of the ap that should be taken down. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 338 | """ |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 339 | |
Qi Jiang | 293bc99 | 2017-09-14 17:15:20 -0700 | [diff] [blame] | 340 | if identifier not in list(self._aps.keys()): |
markdr | b629b32 | 2017-05-22 13:44:06 -0700 | [diff] [blame] | 341 | raise ValueError('Invalid identifier %s given' % identifier) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 342 | |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 343 | instance = self._aps.get(identifier) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 344 | |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 345 | instance.hostapd.stop() |
| 346 | self._dhcp.stop() |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 347 | self._ip_cmd.clear_ipv4_addresses(identifier) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 348 | |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 349 | # DHCP server needs to refresh in order to tear down the subnet no |
| 350 | # longer being used. In the event that all interfaces are torn down |
| 351 | # then an exception gets thrown. We need to catch this exception and |
| 352 | # check that all interfaces should actually be down. |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 353 | configured_subnets = [x.subnet for x in self._aps.values()] |
Qi Jiang | aa818ba | 2017-09-16 17:09:47 -0700 | [diff] [blame] | 354 | del self._aps[identifier] |
Christopher Wiley | ede81f0 | 2016-11-01 09:15:30 -0700 | [diff] [blame] | 355 | if configured_subnets: |
| 356 | self._dhcp.start(dhcp_config.DhcpConfig(configured_subnets)) |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 357 | |
| 358 | def stop_all_aps(self): |
| 359 | """Stops all running aps on this device.""" |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 360 | |
Benny Peake | 7336fa4 | 2017-08-17 19:30:35 -0700 | [diff] [blame] | 361 | for ap in list(self._aps.keys()): |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 362 | try: |
| 363 | self.stop_ap(ap) |
| 364 | except dhcp_server.NoInterfaceError as e: |
| 365 | pass |
Benny Peake | 85f112a | 2016-10-07 19:41:49 -0700 | [diff] [blame] | 366 | |
| 367 | def close(self): |
| 368 | """Called to take down the entire access point. |
| 369 | |
| 370 | When called will stop all aps running on this host, shutdown the dhcp |
markdr | b629b32 | 2017-05-22 13:44:06 -0700 | [diff] [blame] | 371 | server, and stop the ssh connection. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 372 | """ |
Joe Brennan | 88279af | 2017-02-15 18:11:53 -0800 | [diff] [blame] | 373 | |
| 374 | if self._aps: |
| 375 | self.stop_all_aps() |
markdr | 85a5e1a | 2017-07-17 14:11:34 -0700 | [diff] [blame] | 376 | self.ssh.close() |
Qi Jiang | c3e8785 | 2017-10-04 22:45:52 -0700 | [diff] [blame] | 377 | |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 378 | def generate_bridge_configs(self, channel): |
Qi Jiang | c3e8785 | 2017-10-04 22:45:52 -0700 | [diff] [blame] | 379 | """Generate a list of configs for a bridge between LAN and WLAN. |
| 380 | |
| 381 | Args: |
| 382 | channel: the channel WLAN interface is brought up on |
| 383 | iface_lan: the LAN interface to bridge |
| 384 | Returns: |
| 385 | configs: tuple containing iface_wlan, iface_lan and bridge_ip |
| 386 | """ |
| 387 | |
| 388 | if channel < 15: |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 389 | iface_wlan = self.wlan_2g |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 390 | subnet_str = self._AP_2G_SUBNET_STR |
Qi Jiang | c3e8785 | 2017-10-04 22:45:52 -0700 | [diff] [blame] | 391 | else: |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 392 | iface_wlan = self.wlan_5g |
Qi Jiang | 4d8b7cb | 2018-02-06 23:13:44 -0800 | [diff] [blame] | 393 | subnet_str = self._AP_5G_SUBNET_STR |
Qi Jiang | c3e8785 | 2017-10-04 22:45:52 -0700 | [diff] [blame] | 394 | |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 395 | iface_lan = self.lan |
Qi Jiang | c3e8785 | 2017-10-04 22:45:52 -0700 | [diff] [blame] | 396 | |
| 397 | a, b, c, d = subnet_str.strip('/24').split('.') |
| 398 | bridge_ip = "%s.%s.%s.%s" % (a, b, c, BRIDGE_IP_LAST) |
| 399 | |
| 400 | configs = (iface_wlan, iface_lan, bridge_ip) |
| 401 | |
| 402 | return configs |