site_linux_router.py revision 7b23a544ba76d38c6b8caa0714b736040186efb4
1# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging, re, time 6from autotest_lib.client.common_lib import error 7from autotest_lib.server import site_linux_system 8 9def isLinuxRouter(router): 10 router_uname = router.run('uname').stdout 11 return re.search('Linux', router_uname) 12 13class LinuxRouter(site_linux_system.LinuxSystem): 14 """ 15 Linux/mac80211-style WiFi Router support for WiFiTest class. 16 17 This class implements test methods/steps that communicate with a 18 router implemented with Linux/mac80211. The router must 19 be pre-configured to enable ssh access and have a mac80211-based 20 wireless device. We also assume hostapd 0.7.x and iw are present 21 and any necessary modules are pre-loaded. 22 """ 23 24 25 def __init__(self, host, params, defssid): 26 site_linux_system.LinuxSystem.__init__(self, host, params, "router") 27 self._remove_interfaces() 28 29 self.cmd_hostapd = params.get("cmd_hostapd", "/usr/sbin/hostapd") 30 self.cmd_hostapd_cli = \ 31 params.get("cmd_hostapd_cli", "/usr/sbin/hostapd_cli") 32 self.dhcpd_conf = "/tmp/dhcpd.conf" 33 self.dhcpd_leases = "/tmp/dhcpd.leases" 34 35 # Router host. 36 self.router = host 37 38 # hostapd configuration persists throughout the test, subsequent 39 # 'config' commands only modify it. 40 self.defssid = defssid 41 self.hostapd = { 42 'configured': False, 43 'file': "/tmp/hostapd-test.conf", 44 'log': "/tmp/hostapd-test.log", 45 'log_count': 0, 46 'driver': "nl80211", 47 'conf': { 48 'ssid': defssid, 49 'hw_mode': 'g', 50 'ctrl_interface': '/tmp/hostapd-test.control', 51 'logger_syslog': '-1', 52 'logger_syslog_level': '0' 53 } 54 } 55 self.station = { 56 'configured': False, 57 'conf': { 58 'ssid': defssid, 59 }, 60 } 61 self.default_interface = None 62 self.local_servers = [] 63 self.force_local_server = "force_local_server" in params 64 self.dhcp_low = 1 65 self.dhcp_high = 128 66 67 # Kill hostapd if already running. 68 self.kill_hostapd() 69 70 # Place us in the US by default 71 self.router.run("%s reg set US" % self.cmd_iw) 72 73 74 def create(self, params): 75 """ Create a wifi device of the specified type """ 76 # 77 # AP mode is handled entirely by hostapd so we only 78 # have to setup others (mapping the bsd type to what 79 # iw wants) 80 # 81 # map from bsd types to iw types 82 self.apmode = params['type'] in ("ap", "hostap") 83 if not self.apmode: 84 self.station['type'] = params['type'] 85 self.phytype = { 86 "sta" : "managed", 87 "monitor" : "monitor", 88 "adhoc" : "adhoc", 89 "ibss" : "ibss", 90 "ap" : "managed", # NB: handled by hostapd 91 "hostap" : "managed", # NB: handled by hostapd 92 "mesh" : "mesh", 93 "wds" : "wds", 94 }[params['type']] 95 96 97 def destroy(self, params): 98 """ Destroy a previously created device """ 99 # For linux, this is the same as deconfig. 100 self.deconfig(params) 101 102 def has_local_server(self): 103 return bool(self.local_servers) 104 105 def cleanup(self, params): 106 """ Clean up any resources in use """ 107 # For linux, this is a no-op 108 pass 109 110 def kill_hostapd(self): 111 """ 112 Kills the hostapd process. Makes sure hostapd exits before 113 continuing since it sets the interface back to station mode in its 114 cleanup path. If we start another instance of hostapd before the 115 previous instance exits, the interface station mode will overwrite the 116 ap mode. 117 """ 118 self.router.run("pkill hostapd >/dev/null 2>&1 && " 119 "while pgrep hostapd &> /dev/null; do sleep 1; done", 120 timeout=30, 121 ignore_status=True) 122 123 def hostap_config(self, params): 124 """ Configure the AP per test requirements """ 125 126 # keep parameter modifications local-only 127 orig_params = params 128 params = params.copy() 129 130 multi_interface = 'multi_interface' in params 131 if multi_interface: 132 # remove non-hostapd config item from params 133 params.pop('multi_interface') 134 elif self.hostapd['configured'] or self.station['configured']: 135 self.deconfig({}) 136 137 local_server = params.pop('local_server', False) 138 139 # Construct the hostapd.conf file and start hostapd. 140 conf = self.hostapd['conf'] 141 # default RTS and frag threshold to ``off'' 142 conf['rts_threshold'] = '2347' 143 conf['fragm_threshold'] = '2346' 144 145 tx_power_params = {} 146 htcaps = set() 147 148 conf['driver'] = params.get('hostapd_driver', 149 self.hostapd['driver']) 150 151 for k, v in params.iteritems(): 152 if k == 'ssid': 153 conf['ssid'] = v 154 elif k == 'ssid_suffix': 155 conf['ssid'] = self.defssid + v 156 elif k == 'channel': 157 freq = int(v) 158 self.hostapd['frequency'] = freq 159 160 # 2.4GHz 161 if freq <= 2484: 162 # Make sure hw_mode is set 163 if conf.get('hw_mode') == 'a': 164 conf['hw_mode'] = 'g' 165 166 # Freq = 5 * chan + 2407, except channel 14 167 if freq == 2484: 168 conf['channel'] = 14 169 else: 170 conf['channel'] = (freq - 2407) / 5 171 # 5GHz 172 else: 173 # Make sure hw_mode is set 174 conf['hw_mode'] = 'a' 175 # Freq = 5 * chan + 4000 176 if freq < 5000: 177 conf['channel'] = (freq - 4000) / 5 178 # Freq = 5 * chan + 5000 179 else: 180 conf['channel'] = (freq - 5000) / 5 181 182 elif k == 'country': 183 conf['country_code'] = v 184 elif k == 'dotd': 185 conf['ieee80211d'] = 1 186 elif k == '-dotd': 187 conf['ieee80211d'] = 0 188 elif k == 'mode': 189 if v == '11a': 190 conf['hw_mode'] = 'a' 191 elif v == '11g': 192 conf['hw_mode'] = 'g' 193 elif v == '11b': 194 conf['hw_mode'] = 'b' 195 elif v == '11n': 196 conf['ieee80211n'] = 1 197 elif k == 'bintval': 198 conf['beacon_int'] = v 199 elif k == 'dtimperiod': 200 conf['dtim_period'] = v 201 elif k == 'rtsthreshold': 202 conf['rts_threshold'] = v 203 elif k == 'fragthreshold': 204 conf['fragm_threshold'] = v 205 elif k == 'shortpreamble': 206 conf['preamble'] = 1 207 elif k == 'authmode': 208 if v == "open": 209 conf['auth_algs'] = 1 210 elif v == "shared": 211 conf['auth_algs'] = 2 212 elif k == 'hidessid': 213 conf['ignore_broadcast_ssid'] = 1 214 elif k == 'wme': 215 conf['wmm_enabled'] = 1 216 elif k == '-wme': 217 conf['wmm_enabled'] = 0 218 elif k == 'deftxkey': 219 conf['wep_default_key'] = v 220 elif k == 'ht20': 221 htcaps.add('') # NB: ensure 802.11n setup below 222 conf['wmm_enabled'] = 1 223 elif k == 'ht40': 224 htcaps.add('[HT40-]') 225 htcaps.add('[HT40+]') 226 conf['wmm_enabled'] = 1 227 elif k in ('ht40+', 'ht40-'): 228 htcaps.add('[%s]' % k.upper()) 229 conf['wmm_enabled'] = 1 230 elif k == 'shortgi': 231 htcaps.add('[SHORT-GI-20]') 232 htcaps.add('[SHORT-GI-40]') 233 elif k == 'pureg': 234 pass # TODO(sleffler) need hostapd support 235 elif k == 'puren': 236 pass # TODO(sleffler) need hostapd support 237 elif k == 'protmode': 238 pass # TODO(sleffler) need hostapd support 239 elif k == 'ht': 240 htcaps.add('') # NB: ensure 802.11n setup below 241 elif k == 'htprotmode': 242 pass # TODO(sleffler) need hostapd support 243 elif k == 'rifs': 244 pass # TODO(sleffler) need hostapd support 245 elif k == 'wepmode': 246 pass # NB: meaningless for hostapd; ignore 247 elif k == '-ampdu': 248 pass # TODO(sleffler) need hostapd support 249 elif k == 'txpower': 250 tx_power_params['power'] = v 251 else: 252 conf[k] = v 253 254 # Aggregate ht_capab. 255 if htcaps: 256 conf['ieee80211n'] = 1 257 conf['ht_capab'] = ''.join(htcaps) 258 259 # Figure out the correct interface. 260 conf['interface'] = self._get_wlanif(self.hostapd['frequency'], 261 self.phytype, 262 mode=conf.get('hw_mode', 'b')) 263 264 # Generate hostapd.conf. 265 self._pre_config_hook(conf) 266 self.router.run("cat <<EOF >%s\n%s\nEOF\n" % 267 (self.hostapd['file'], '\n'.join( 268 "%s=%s" % kv for kv in conf.iteritems()))) 269 270 self._pre_start_hook(orig_params) 271 272 # Run hostapd. 273 logging.info("Starting hostapd...") 274 self.router.run("%s -dd %s &> %s &" % 275 (self.cmd_hostapd, self.hostapd['file'], self.hostapd['log'])) 276 277 if not multi_interface: 278 self.default_interface = conf['interface'] 279 280 # Configure transmit power 281 tx_power_params['interface'] = conf['interface'] 282 self.set_txpower(tx_power_params) 283 284 if self.force_local_server or local_server is not False: 285 self.start_local_server(conf['interface']) 286 287 self._post_start_hook(orig_params) 288 289 logging.info("AP configured.") 290 self.hostapd['configured'] = True 291 292 @staticmethod 293 def ip_addr(netblock, idx): 294 """ 295 Simple IPv4 calculator. Takes host address in "IP/bits" notation 296 and returns netmask, broadcast address as well as integer offsets 297 into the address range. 298 """ 299 addr_str,bits = netblock.split('/') 300 addr = map(int, addr_str.split('.')) 301 mask_bits = (-1 << (32-int(bits))) & 0xffffffff 302 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)] 303 if idx == 'local': 304 return addr_str 305 elif idx == 'netmask': 306 return '.'.join(map(str, mask)) 307 elif idx == 'broadcast': 308 offset = [m ^ 0xff for m in mask] 309 else: 310 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)] 311 return '.'.join(map(str, [(a & m) + o 312 for a, m, o in zip(addr, mask, offset)])) 313 314 315 def station_config(self, params): 316 # keep parameter modifications local-only 317 orig_params = params 318 params = params.copy() 319 320 if 'multi_interface' in params: 321 raise NotImplementedError("station with multi_interface") 322 323 if self.station['type'] != 'ibss': 324 raise NotImplementedError("non-ibss station") 325 326 if self.station['configured'] or self.hostapd['configured']: 327 self.deconfig({}) 328 329 local_server = params.pop('local_server', False) 330 mode = None 331 conf = self.station['conf'] 332 for k, v in params.iteritems(): 333 if k == 'ssid_suffix': 334 conf['ssid'] = self.defssid + v 335 elif k == 'channel': 336 freq = int(v) 337 if freq > 2484: 338 mode = 'a' 339 elif k == 'mode': 340 if v == '11a': 341 mode = 'a' 342 else: 343 conf[k] = v 344 345 interface = self._get_wlanif(freq, self.phytype, mode) 346 347 # Run interface configuration commands 348 for k, v in conf.iteritems(): 349 if k != 'ssid': 350 self.router.run("%s dev %s set %s %s" % 351 (self.cmd_iw, interface, k, v)) 352 353 # Connect the station 354 self.router.run("%s link set %s up" % (self.cmd_ip, interface)) 355 self.router.run("%s dev %s ibss join %s %d" % 356 (self.cmd_iw, interface, conf['ssid'], freq)) 357 358 if self.force_local_server or local_server is not False: 359 self.start_local_server(interface) 360 361 self.station['configured'] = True 362 self.station['interface'] = interface 363 364 365 def start_local_server(self, interface): 366 logging.info("Starting up local server...") 367 368 if len(self.local_servers) >= 256: 369 raise error.TestFail('Exhausted available local servers') 370 371 netblock = '%d.%d.%d.%d/24' % \ 372 (192, 168, len(self.local_servers), 254) 373 374 params = {} 375 params['netblock'] = netblock 376 params['subnet'] = self.ip_addr(netblock, 0) 377 params['netmask'] = self.ip_addr(netblock, 'netmask') 378 params['dhcp_range'] = ' '.join( 379 (self.ip_addr(netblock, self.dhcp_low), 380 self.ip_addr(netblock, self.dhcp_high))) 381 params['interface'] = interface 382 383 params['ip_params'] = ("%s broadcast %s dev %s" % 384 (netblock, 385 self.ip_addr(netblock, 'broadcast'), 386 interface)) 387 self.local_servers.append(params) 388 389 self.router.run("%s addr flush %s" % 390 (self.cmd_ip, interface)) 391 self.router.run("%s addr add %s" % 392 (self.cmd_ip, params['ip_params'])) 393 self.router.run("%s link set %s up" % 394 (self.cmd_ip, interface)) 395 self.start_dhcp_server() 396 397 def start_dhcp_server(self): 398 dhcp_conf = '\n'.join(map( 399 lambda server_conf: \ 400 "subnet %(subnet)s netmask %(netmask)s {\n" \ 401 " range %(dhcp_range)s;\n" \ 402 "}" % server_conf, 403 self.local_servers)) 404 self.router.run("cat <<EOF >%s\n%s\nEOF\n" % 405 (self.dhcpd_conf, 406 '\n'.join(('ddns-update-style none;', dhcp_conf)))) 407 self.router.run("touch %s" % self.dhcpd_leases) 408 409 self.router.run("pkill dhcpd >/dev/null 2>&1", ignore_status=True) 410 self.router.run("%s -q -cf %s -lf %s" % 411 (self.cmd_dhcpd, self.dhcpd_conf, self.dhcpd_leases)) 412 413 414 def config(self, params): 415 if self.apmode: 416 self.hostap_config(params) 417 else: 418 self.station_config(params) 419 420 421 def get_wifi_ip(self, ap_num): 422 if self.local_servers: 423 return self.ip_addr(self.local_servers[ap_num]['netblock'], 424 'local') 425 else: 426 raise error.TestFail("No IP address assigned") 427 428 429 def deconfig(self, params): 430 """ De-configure the AP (will also bring wlan down) """ 431 432 if not self.hostapd['configured'] and not self.station['configured']: 433 return 434 435 # Taking down hostapd takes wlan0 and mon.wlan0 down. 436 if self.hostapd['configured']: 437 if 'silent' in params: 438 # Deconfigure without notifying DUT. Remove the monitor 439 # interface hostapd uses to send beacon and DEAUTH packets 440 self._remove_interfaces() 441 442 self.kill_hostapd() 443# self.router.run("rm -f %s" % self.hostapd['file']) 444 self.router.get_file(self.hostapd['log'], 445 'debug/hostapd_router_%d.log' % 446 self.hostapd['log_count']) 447 self.hostapd['log_count'] += 1 448 if self.station['configured']: 449 if self.station['type'] == 'ibss': 450 self.router.run("%s dev %s ibss leave" % 451 (self.cmd_iw, self.station['interface'])) 452 else: 453 self.router.run("%s dev %s disconnect" % 454 (self.cmd_iw, self.station['interface'])) 455 self.router.run("%s link set %s down" % (self.cmd_ip, 456 self.station['interface'])) 457 458 if self.local_servers: 459 self.router.run("pkill dhcpd >/dev/null 2>&1", 460 ignore_status=True) 461 for server in self.local_servers: 462 self.router.run("%s addr del %s" % 463 (self.cmd_ip, server['ip_params'])) 464 self.local_servers = [] 465 466 self.hostapd['configured'] = False 467 self.station['configured'] = False 468 469 470 def get_ssid(self): 471 return self.hostapd['conf']['ssid'] 472 473 474 def set_txpower(self, params): 475 self.router.run("%s dev %s set txpower %s" % 476 (self.cmd_iw, params.get('interface', 477 self.default_interface), 478 params.get('power', 'auto'))) 479 480 481 def deauth(self, params): 482 self.router.run('%s -p%s deauthenticate %s' % 483 (self.cmd_hostapd_cli, 484 self.hostapd['conf']['ctrl_interface'], 485 params['client'])) 486 487 488 def _pre_config_hook(self, config): 489 """ 490 Hook for subclasses. Run after gathering configuration parameters, 491 but before writing parameters to config file. 492 """ 493 pass 494 495 496 def _pre_start_hook(self, params): 497 """ 498 Hook for subclasses. Run after generating hostapd config file, but 499 before starting hostapd. 500 """ 501 pass 502 503 504 def _post_start_hook(self, params): 505 """Hook for subclasses. Run after starting hostapd.""" 506 pass 507