site_linux_router.py revision 6ddeba733cfdb86ae4399159a2d8c1b71ed54fa6
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 6import random 7import re 8import string 9 10from autotest_lib.client.common_lib import base_utils 11from autotest_lib.client.common_lib import error 12from autotest_lib.client.common_lib.cros.network import interface 13from autotest_lib.server import site_linux_system 14from autotest_lib.server.cros import wifi_test_utils 15from autotest_lib.server.cros.network import hostap_config 16 17def isLinuxRouter(host): 18 """Check if host is a linux router. 19 20 @param host Host object representing the remote machine. 21 @return True iff remote system is a Linux system. 22 23 """ 24 router_uname = host.run('uname').stdout 25 return re.search('Linux', router_uname) 26 27 28class LinuxRouter(site_linux_system.LinuxSystem): 29 """Linux/mac80211-style WiFi Router support for WiFiTest class. 30 31 This class implements test methods/steps that communicate with a 32 router implemented with Linux/mac80211. The router must 33 be pre-configured to enable ssh access and have a mac80211-based 34 wireless device. We also assume hostapd 0.7.x and iw are present 35 and any necessary modules are pre-loaded. 36 37 """ 38 39 KNOWN_TEST_PREFIX = 'network_WiFi' 40 STARTUP_POLLING_INTERVAL_SECONDS = 0.5 41 STARTUP_TIMEOUT_SECONDS = 10 42 SUFFIX_LETTERS = string.ascii_lowercase + string.digits 43 SUBNET_PREFIX_OCTETS = (192, 168) 44 45 def get_capabilities(self): 46 """@return iterable object of AP capabilities for this system.""" 47 caps = set() 48 try: 49 self.cmd_send_management_frame = wifi_test_utils.must_be_installed( 50 self.router, '/usr/bin/send_management_frame') 51 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME) 52 except error.TestFail: 53 pass 54 return super(LinuxRouter, self).get_capabilities().union(caps) 55 56 57 def __init__(self, host, params, test_name): 58 """Build a LinuxRouter. 59 60 @param host Host object representing the remote machine. 61 @param params dict of settings from site_wifitest based tests. 62 @param test_name string name of this test. Used in SSID creation. 63 64 """ 65 site_linux_system.LinuxSystem.__init__(self, host, params, 'router') 66 self._remove_interfaces() 67 68 # Router host. 69 self.router = host 70 71 self.cmd_dhcpd = params.get('cmd_dhcpd', '/usr/sbin/dhcpd') 72 self.cmd_hostapd = wifi_test_utils.must_be_installed( 73 host, params.get('cmd_hostapd', '/usr/sbin/hostapd')) 74 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed( 75 host, params.get('cmd_hostapd_cli', '/usr/sbin/hostapd_cli')) 76 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed( 77 host, params.get('cmd_wpa_supplicant', 78 '/usr/sbin/wpa_supplicant')) 79 self.dhcpd_conf = '/tmp/dhcpd.%s.conf' 80 self.dhcpd_leases = '/tmp/dhcpd.leases' 81 82 # hostapd configuration persists throughout the test, subsequent 83 # 'config' commands only modify it. 84 self.ssid_prefix = test_name 85 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX): 86 # Many of our tests start with an uninteresting prefix. 87 # Remove it so we can have more unique bytes. 88 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):] 89 self.ssid_prefix = self.ssid_prefix.lstrip('_') 90 self.ssid_prefix += '_' 91 92 self.default_config = { 93 'hw_mode': 'g', 94 'ctrl_interface': '/tmp/hostapd-test.control', 95 'logger_syslog': '-1', 96 'logger_syslog_level': '0' 97 } 98 self.hostapd = { 99 'configured': False, 100 'config_file': "/tmp/hostapd-test-%s.conf", 101 'log_file': "/tmp/hostapd-test-%s.log", 102 'pid_file': "/tmp/hostapd-test-%s.pid", 103 'log_count': 0, 104 'driver': "nl80211", 105 'conf': self.default_config.copy() 106 } 107 self.station = { 108 'configured': False, 109 'config_file': "/tmp/wpa-supplicant-test-%s.conf", 110 'log_file': "/tmp/wpa-supplicant-test-%s.log", 111 'pid_file': "/tmp/wpa-supplicant-test-%s.pid", 112 'conf': {}, 113 } 114 self.local_servers = [] 115 self.hostapd_instances = [] 116 self.force_local_server = "force_local_server" in params 117 self.dhcp_low = 1 118 self.dhcp_high = 128 119 120 # Kill hostapd and dhcp server if already running. 121 self.kill_hostapd() 122 self.stop_dhcp_servers() 123 124 # Place us in the US by default 125 self.iw_runner.set_regulatory_domain('US') 126 127 128 def close(self): 129 """Close global resources held by this system.""" 130 self.destroy() 131 super(LinuxRouter, self).close() 132 133 134 def create(self, params): 135 """Create a wifi device of the specified type. 136 137 @param params dict containing the device type under key 'type'. 138 139 """ 140 self.create_wifi_device(params['type']) 141 142 143 def create_wifi_device(self, device_type='hostap'): 144 """Create a wifi device of the specified type. 145 146 Defaults to creating a hostap managed device. 147 148 @param device_type string device type. 149 150 """ 151 # 152 # AP mode is handled entirely by hostapd so we only 153 # have to setup others (mapping the bsd type to what 154 # iw wants) 155 # 156 # map from bsd types to iw types 157 self.apmode = device_type in ('ap', 'hostap') 158 if not self.apmode: 159 self.station['type'] = device_type 160 self.phytype = { 161 'sta' : 'managed', 162 'monitor' : 'monitor', 163 'adhoc' : 'adhoc', 164 'ibss' : 'ibss', 165 'ap' : 'managed', # NB: handled by hostapd 166 'hostap' : 'managed', # NB: handled by hostapd 167 'mesh' : 'mesh', 168 'wds' : 'wds', 169 }[device_type] 170 171 172 def destroy(self, params={}): 173 """Destroy a previously created device. 174 175 @param params dict of site_wifitest parameters. 176 177 """ 178 self.deconfig(params) 179 self.hostapd['conf'] = self.default_config.copy() 180 181 182 def has_local_server(self): 183 """@return True iff this router has local servers configured.""" 184 return bool(self.local_servers) 185 186 187 def cleanup(self, params): 188 """Clean up any resources in use. 189 190 @param params dict of site_wifitest parameters. 191 192 """ 193 # For linux, this is a no-op 194 pass 195 196 197 def get_hostapd_start_command(self, log_file, pid_file, conf_file): 198 return '%s -dd -t -P %s %s &> %s &' % ( 199 self.cmd_hostapd, pid_file, conf_file, log_file) 200 201 202 def start_hostapd(self, conf, params): 203 """Start a hostapd instance described by conf. 204 205 @param conf dict of hostapd configuration parameters. 206 @param params dict of site_wifitest parameters. 207 208 """ 209 logging.info('Starting hostapd with parameters: %r', conf) 210 # Figure out the correct interface. 211 interface = self._get_wlanif(self.hostapd['frequency'], 212 self.phytype, 213 mode=conf.get('hw_mode', 'b')) 214 215 conf_file = self.hostapd['config_file'] % interface 216 log_file = self.hostapd['log_file'] % interface 217 pid_file = self.hostapd['pid_file'] % interface 218 conf['interface'] = interface 219 220 # Generate hostapd.conf. 221 self._pre_config_hook(conf) 222 self.router.run("cat <<EOF >%s\n%s\nEOF\n" % 223 (conf_file, '\n'.join( 224 "%s=%s" % kv for kv in conf.iteritems()))) 225 226 # Run hostapd. 227 logging.info("Starting hostapd...") 228 self.router.run('rm %s' % log_file, ignore_status=True) 229 self.router.run('rm %s' % pid_file, ignore_status=True) 230 self._pre_start_hook(params) 231 start_command = self.get_hostapd_start_command( 232 log_file, pid_file, conf_file) 233 self.router.run(start_command) 234 self.hostapd_instances.append({ 235 'ssid': conf['ssid'], 236 'conf_file': conf_file, 237 'log_file': log_file, 238 'interface': interface, 239 'pid_file': pid_file, 240 'config_dict': conf.copy() 241 }) 242 243 244 def _kill_process_instance(self, process, instance=None, wait=0): 245 """Kill a process on the router. 246 247 Kills program named |process|, optionally only a specific 248 |instance|. If |wait| is specified, we makes sure |process| exits 249 before returning. 250 251 @param process string name of process to kill. 252 @param instance string instance of process to kill. 253 @param wait int timeout in seconds to wait for. 254 255 """ 256 if instance: 257 search_arg = '-f "%s.*%s"' % (process, instance) 258 else: 259 search_arg = process 260 261 cmd = "pkill %s >/dev/null 2>&1" % search_arg 262 263 if wait: 264 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" % 265 search_arg) 266 self.router.run(cmd, timeout=wait, ignore_status=True) 267 else: 268 self.router.run(cmd, ignore_status=True) 269 270 271 def kill_hostapd_instance(self, instance): 272 """Kills a hostapd instance. 273 274 @param instance string instance to kill. 275 276 """ 277 self._kill_process_instance('hostapd', instance, 30) 278 279 280 def kill_hostapd(self): 281 """Kill all hostapd instances.""" 282 self.kill_hostapd_instance(None) 283 284 285 def __get_default_hostap_config(self): 286 """@return dict of default options for hostapd.""" 287 conf = self.hostapd['conf'] 288 # default RTS and frag threshold to ``off'' 289 conf['rts_threshold'] = '2347' 290 conf['fragm_threshold'] = '2346' 291 conf['driver'] = self.hostapd['driver'] 292 conf['ssid'] = self._build_ssid('') 293 return conf 294 295 296 def _build_ssid(self, suffix): 297 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS) 298 for x in range(5)]) 299 return (self.ssid_prefix + unique_salt + suffix)[-32:] 300 301 302 def hostap_configure(self, configuration, multi_interface=None): 303 """Build up a hostapd configuration file and start hostapd. 304 305 Also setup a local server if this router supports them. 306 307 @param configuration HosetapConfig object. 308 @param multi_interface bool True iff multiple interfaces allowed. 309 310 """ 311 if multi_interface is None and (self.hostapd['configured'] or 312 self.station['configured']): 313 self.deconfig() 314 # Start with the default hostapd config parameters. 315 conf = self.__get_default_hostap_config() 316 conf['ssid'] = (configuration.ssid or 317 self._build_ssid(configuration.ssid_suffix)) 318 if configuration.bssid: 319 conf['bssid'] = configuration.bssid 320 conf['channel'] = configuration.channel 321 self.hostapd['frequency'] = configuration.frequency 322 conf['hw_mode'] = configuration.hw_mode 323 if configuration.hide_ssid: 324 conf['ignore_broadcast_ssid'] = 1 325 if configuration.is_11n: 326 conf['ieee80211n'] = 1 327 conf['ht_capab'] = configuration.hostapd_ht_capabilities 328 if configuration.wmm_enabled: 329 conf['wmm_enabled'] = 1 330 if configuration.require_ht: 331 conf['require_ht'] = 1 332 if configuration.beacon_interval: 333 conf['beacon_int'] = configuration.beacon_interval 334 if configuration.dtim_period: 335 conf['dtim_period'] = configuration.dtim_period 336 if configuration.frag_threshold: 337 conf['fragm_threshold'] = configuration.frag_threshold 338 if configuration.pmf_support: 339 conf['ieee80211w'] = configuration.pmf_support 340 if configuration.obss_interval: 341 conf['obss_interval'] = configuration.obss_interval 342 conf.update(configuration.get_security_hostapd_conf()) 343 344 # TODO(wiley): Remove this multi_interface flag when the bridge router 345 # class is gone. 346 params = {'multi_interface': 1} if multi_interface else {} 347 self.start_hostapd(conf, params) 348 # Configure transmit power 349 tx_power_params = {'interface': conf['interface']} 350 # TODO(wiley) support for setting transmit power 351 self.set_txpower(tx_power_params) 352 if self.force_local_server: 353 self.start_local_server(conf['interface']) 354 self._post_start_hook(params) 355 logging.info('AP configured.') 356 self.hostapd['configured'] = True 357 358 359 def hostap_config(self, params): 360 """Configure the AP per test requirements. 361 362 @param params dict of site_wifitest parameters. 363 364 """ 365 # keep parameter modifications local-only 366 orig_params = params 367 params = params.copy() 368 369 multi_interface = 'multi_interface' in params 370 if multi_interface: 371 # remove non-hostapd config item from params 372 params.pop('multi_interface') 373 elif self.hostapd['configured'] or self.station['configured']: 374 self.deconfig() 375 376 local_server = params.pop('local_server', False) 377 378 conf = self.__get_default_hostap_config() 379 tx_power_params = {} 380 htcaps = set() 381 382 for k, v in params.iteritems(): 383 if k == 'ssid': 384 conf['ssid'] = v 385 elif k == 'ssid_suffix': 386 conf['ssid'] = self._build_ssid(v) 387 elif k == 'channel': 388 freq = int(v) 389 self.hostapd['frequency'] = freq 390 391 # 2.4GHz 392 if freq <= 2484: 393 # Make sure hw_mode is set 394 if conf.get('hw_mode') == 'a': 395 conf['hw_mode'] = 'g' 396 397 # Freq = 5 * chan + 2407, except channel 14 398 if freq == 2484: 399 conf['channel'] = 14 400 else: 401 conf['channel'] = (freq - 2407) / 5 402 # 5GHz 403 else: 404 # Make sure hw_mode is set 405 conf['hw_mode'] = 'a' 406 # Freq = 5 * chan + 4000 407 if freq < 5000: 408 conf['channel'] = (freq - 4000) / 5 409 # Freq = 5 * chan + 5000 410 else: 411 conf['channel'] = (freq - 5000) / 5 412 413 elif k == 'country': 414 conf['country_code'] = v 415 elif k == 'dotd': 416 conf['ieee80211d'] = 1 417 elif k == '-dotd': 418 conf['ieee80211d'] = 0 419 elif k == 'mode': 420 if v == '11a': 421 conf['hw_mode'] = 'a' 422 elif v == '11g': 423 conf['hw_mode'] = 'g' 424 elif v == '11b': 425 conf['hw_mode'] = 'b' 426 elif v == '11n': 427 conf['ieee80211n'] = 1 428 elif k == 'bintval': 429 conf['beacon_int'] = v 430 elif k == 'dtimperiod': 431 conf['dtim_period'] = v 432 elif k == 'rtsthreshold': 433 conf['rts_threshold'] = v 434 elif k == 'fragthreshold': 435 conf['fragm_threshold'] = v 436 elif k == 'shortpreamble': 437 conf['preamble'] = 1 438 elif k == 'authmode': 439 if v == "open": 440 conf['auth_algs'] = 1 441 elif v == "shared": 442 conf['auth_algs'] = 2 443 elif k == 'hidessid': 444 conf['ignore_broadcast_ssid'] = 1 445 elif k == 'wme': 446 conf['wmm_enabled'] = 1 447 elif k == '-wme': 448 conf['wmm_enabled'] = 0 449 elif k == 'deftxkey': 450 conf['wep_default_key'] = v 451 elif k == 'ht20': 452 htcaps.add('') # NB: ensure 802.11n setup below 453 conf['wmm_enabled'] = 1 454 elif k == 'ht40': 455 htcaps.add('[HT40-]') 456 htcaps.add('[HT40+]') 457 conf['wmm_enabled'] = 1 458 elif k in ('ht40+', 'ht40-'): 459 htcaps.add('[%s]' % k.upper()) 460 conf['wmm_enabled'] = 1 461 elif k == 'shortgi': 462 htcaps.add('[SHORT-GI-20]') 463 htcaps.add('[SHORT-GI-40]') 464 elif k == 'pureg': 465 pass # TODO(sleffler) need hostapd support 466 elif k == 'puren': 467 pass # TODO(sleffler) need hostapd support 468 elif k == 'protmode': 469 pass # TODO(sleffler) need hostapd support 470 elif k == 'ht': 471 htcaps.add('') # NB: ensure 802.11n setup below 472 elif k == 'htprotmode': 473 pass # TODO(sleffler) need hostapd support 474 elif k == 'rifs': 475 pass # TODO(sleffler) need hostapd support 476 elif k == 'wepmode': 477 pass # NB: meaningless for hostapd; ignore 478 elif k == '-ampdu': 479 pass # TODO(sleffler) need hostapd support 480 elif k == 'txpower': 481 tx_power_params['power'] = v 482 else: 483 conf[k] = v 484 485 # Aggregate ht_capab. 486 if htcaps: 487 conf['ieee80211n'] = 1 488 conf['ht_capab'] = ''.join(htcaps) 489 490 self.start_hostapd(conf, orig_params) 491 492 # Configure transmit power 493 tx_power_params['interface'] = conf['interface'] 494 self.set_txpower(tx_power_params) 495 496 if self.force_local_server or local_server is not False: 497 self.start_local_server(conf['interface']) 498 499 self._post_start_hook(orig_params) 500 501 logging.info("AP configured.") 502 self.hostapd['configured'] = True 503 504 505 @staticmethod 506 def ip_addr(netblock, idx): 507 """Simple IPv4 calculator. 508 509 Takes host address in "IP/bits" notation and returns netmask, broadcast 510 address as well as integer offsets into the address range. 511 512 @param netblock string host address in "IP/bits" notation. 513 @param idx string describing what to return. 514 @return string containing something you hopefully requested. 515 516 """ 517 addr_str,bits = netblock.split('/') 518 addr = map(int, addr_str.split('.')) 519 mask_bits = (-1 << (32-int(bits))) & 0xffffffff 520 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)] 521 if idx == 'local': 522 return addr_str 523 elif idx == 'netmask': 524 return '.'.join(map(str, mask)) 525 elif idx == 'broadcast': 526 offset = [m ^ 0xff for m in mask] 527 else: 528 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)] 529 return '.'.join(map(str, [(a & m) + o 530 for a, m, o in zip(addr, mask, offset)])) 531 532 533 def ibss_configure(self, config): 534 """Configure a station based AP in IBSS mode. 535 536 Extract relevant configuration objects from |config| despite not 537 actually being a hostap managed endpoint. 538 539 @param config HostapConfig object. 540 541 """ 542 if self.station['configured'] or self.hostapd['configured']: 543 self.deconfig() 544 interface = self._get_wlanif(config.frequency, self.phytype, 545 config.hw_mode) 546 self.station['conf']['ssid'] = (config.ssid or 547 self._build_ssid(config.ssid_suffix)) 548 # Connect the station 549 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 550 self.iw_runner.ibss_join( 551 interface, self.station['conf']['ssid'], config.frequency) 552 # Always start a local server. 553 self.start_local_server(interface) 554 # Remember that this interface is up. 555 self.station['configured'] = True 556 self.station['interface'] = interface 557 558 559 def local_server_address(self, index): 560 """Get the local server address for an interface. 561 562 When we multiple local servers, we give them static IP addresses 563 like 192.168.*.254. 564 565 @param index int describing which local server this is for. 566 567 """ 568 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254)) 569 570 571 def local_peer_ip_address(self, index): 572 """Get the IP address allocated for the peer associated to the AP. 573 574 This address is assigned to a locally associated peer device that 575 is created for the DUT to perform connectivity tests with. 576 When we have multiple local servers, we give them static IP addresses 577 like 192.168.*.253. 578 579 @param index int describing which local server this is for. 580 581 """ 582 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253)) 583 584 585 def local_peer_mac_address(self): 586 """Get the MAC address of the peer interface. 587 588 @return string MAC address of the peer interface. 589 """ 590 iface = interface.Interface(self.station['interface'], self.router) 591 return iface.mac_address 592 593 594 def start_local_server(self, interface): 595 """Start a local server on an interface. 596 597 @param interface string (e.g. wlan0) 598 599 """ 600 logging.info("Starting up local server...") 601 602 if len(self.local_servers) >= 256: 603 raise error.TestFail('Exhausted available local servers') 604 605 netblock = '%s/24' % self.local_server_address(len(self.local_servers)) 606 607 params = {} 608 params['netblock'] = netblock 609 params['subnet'] = self.ip_addr(netblock, 0) 610 params['netmask'] = self.ip_addr(netblock, 'netmask') 611 params['dhcp_range'] = ' '.join( 612 (self.ip_addr(netblock, self.dhcp_low), 613 self.ip_addr(netblock, self.dhcp_high))) 614 params['interface'] = interface 615 616 params['ip_params'] = ("%s broadcast %s dev %s" % 617 (netblock, 618 self.ip_addr(netblock, 'broadcast'), 619 interface)) 620 self.local_servers.append(params) 621 622 self.router.run("%s addr flush %s" % 623 (self.cmd_ip, interface)) 624 self.router.run("%s addr add %s" % 625 (self.cmd_ip, params['ip_params'])) 626 self.router.run("%s link set %s up" % 627 (self.cmd_ip, interface)) 628 self.start_dhcp_server(interface) 629 630 631 def start_dhcp_server(self, interface): 632 """Start a dhcp server on an interface. 633 634 @param interface string (e.g. wlan0) 635 636 """ 637 conf_file = self.dhcpd_conf % interface 638 dhcp_conf = '\n'.join(map( 639 lambda server_conf: \ 640 "subnet %(subnet)s netmask %(netmask)s {\n" \ 641 " range %(dhcp_range)s;\n" \ 642 "}" % server_conf, 643 self.local_servers)) 644 self.router.run("cat <<EOF >%s\n%s\nEOF\n" % 645 (conf_file, 646 '\n'.join(('ddns-update-style none;', dhcp_conf)))) 647 self.router.run("touch %s" % self.dhcpd_leases) 648 649 self.router.run("pkill dhcpd >/dev/null 2>&1", ignore_status=True) 650 self.router.run("%s -q -cf %s -lf %s" % 651 (self.cmd_dhcpd, conf_file, self.dhcpd_leases)) 652 653 654 def stop_dhcp_server(self, instance=None): 655 """Stop a dhcp server on the router. 656 657 @param instance string instance to kill. 658 659 """ 660 self._kill_process_instance('dhcpd', instance, 0) 661 662 663 def stop_dhcp_servers(self): 664 """Stop all dhcp servers on the router.""" 665 self.stop_dhcp_server(None) 666 667 668 def config(self, params): 669 """Configure an AP based on site_wifitest parameters. 670 671 @param params dict of site_wifitest parameters. 672 673 """ 674 if self.apmode: 675 self.hostap_config(params) 676 else: 677 config = hostap_config.HostapConfig( 678 frequency=int(params.get('channel', None))) 679 self.ibss_configure(config) 680 681 682 def get_wifi_ip(self, ap_num): 683 """Return IP address on the WiFi subnet of a local server on the router. 684 685 If no local servers are configured (e.g. for an RSPro), a TestFail will 686 be raised. 687 688 @param ap_num int which local server to get an address from. 689 690 """ 691 if self.local_servers: 692 return self.ip_addr(self.local_servers[ap_num]['netblock'], 693 'local') 694 else: 695 raise error.TestFail("No IP address assigned") 696 697 698 def get_hostapd_mac(self, ap_num): 699 """Return the MAC address of an AP in the test. 700 701 @param ap_num int index of local server to read the MAC address from. 702 @return string MAC address like 00:11:22:33:44:55. 703 704 """ 705 instance = self.hostapd_instances[ap_num] 706 interface = instance['interface'] 707 result = self.router.run('%s addr show %s' % (self.cmd_ip, interface)) 708 # Example response: 709 # 1: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 UP qlen 1000 710 # link/ether 99:88:77:66:55:44 brd ff:ff:ff:ff:ff:ff 711 # inet 10.0.0.1/8 brd 10.255.255.255 scope global eth0 712 # inet6 fe80::6a7f:74ff:fe66:5544/64 scope link 713 # we want the MAC address after the "link/ether" above. 714 parts = result.stdout.split(' ') 715 return parts[parts.index('link/ether') + 1] 716 717 718 def deconfig(self, params={}): 719 """De-configure the AP (will also bring wlan down). 720 721 @param params dict of parameters from site_wifitest. 722 723 """ 724 self.deconfig_aps(instance=params.get('instance', None), 725 silent='silent' in params) 726 727 728 def deconfig_aps(self, instance=None, silent=False): 729 """De-configure an AP (will also bring wlan down). 730 731 @param instance: int or None. If instance is None, will bring down all 732 instances of hostapd. 733 @param silent: True if instances should be brought without de-authing 734 the DUT. 735 736 """ 737 if not self.hostapd['configured'] and not self.station['configured']: 738 return 739 740 if self.hostapd['configured']: 741 local_servers = [] 742 if instance is not None: 743 instances = [ self.hostapd_instances.pop(instance) ] 744 for server in self.local_servers: 745 if server['interface'] == instances[0]['interface']: 746 local_servers = [server] 747 self.local_servers.remove(server) 748 break 749 else: 750 instances = self.hostapd_instances 751 self.hostapd_instances = [] 752 local_servers = self.local_servers 753 self.local_servers = [] 754 755 for instance in instances: 756 if silent: 757 # Deconfigure without notifying DUT. Remove the interface 758 # hostapd uses to send beacon and DEAUTH packets. 759 self._remove_interface(instance['interface'], True) 760 761 self.kill_hostapd_instance(instance['conf_file']) 762 if wifi_test_utils.is_installed(self.host, 763 instance['log_file']): 764 self.router.get_file(instance['log_file'], 765 'debug/hostapd_router_%d_%s.log' % 766 (self.hostapd['log_count'], 767 instance['interface'])) 768 else: 769 logging.error('Did not collect hostapd log file because ' 770 'it was missing.') 771 self._release_wlanif(instance['interface']) 772# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance) 773 self.hostapd['log_count'] += 1 774 if self.station['configured']: 775 local_servers = self.local_servers 776 self.local_servers = [] 777 if self.station['type'] == 'ibss': 778 self.iw_runner.ibss_leave(self.station['interface']) 779 if self.station['type'] == 'supplicant': 780 self._kill_process_instance('wpa_supplicant', 781 self.station['interface']) 782 else: 783 self.iw_runner.disconnect_station(self.station['interface']) 784 self.router.run("%s link set %s down" % (self.cmd_ip, 785 self.station['interface'])) 786 787 for server in local_servers: 788 self.stop_dhcp_server(server['interface']) 789 self.router.run("%s addr del %s" % 790 (self.cmd_ip, server['ip_params']), 791 ignore_status=True) 792 793 self.hostapd['configured'] = False 794 self.station['configured'] = False 795 796 797 def verify_pmksa_auth(self, params): 798 """Verify that the PMKSA auth was cached on a hostapd instance. 799 800 @param params dict with optional key 'instance' (defaults to 0). 801 802 """ 803 instance_num = params.get('instance', 0) 804 instance = self.hostapd_instances[instance_num] 805 pmksa_match = 'PMK from PMKSA cache - skip IEEE 802.1X.EAP' 806 self.router.run('grep -q "%s" %s' % (pmksa_match, instance['log_file'])) 807 808 809 def get_ssid(self, instance=None): 810 """@return string ssid for the network stemming from this router.""" 811 if instance is None: 812 instance = 0 813 if len(self.hostapd_instances) > 1: 814 raise error.TestFail('No instance of hostapd specified with ' 815 'multiple instances present.') 816 817 if self.hostapd_instances: 818 return self.hostapd_instances[instance]['ssid'] 819 820 if not 'ssid' in self.station['conf']: 821 raise error.TestFail('Requested ssid of an unconfigured AP.') 822 823 return self.station['conf']['ssid'] 824 825 826 def set_txpower(self, params): 827 """Set the transmission power for an interface. 828 829 Assumes that we want to refer to the first hostapd instance unless 830 'interface' is defined in params. Sets the transmission power to 831 'auto' if 'power' is not defined in params. 832 833 @param params dict of parameters as described above. 834 835 """ 836 interface = params.get('interface', 837 self.hostapd_instances[0]['interface']) 838 power = params.get('power', 'auto') 839 self.iw_runner.set_tx_power(interface, power) 840 841 842 def deauth_client(self, client_mac): 843 """Deauthenticates a client described in params. 844 845 @param client_mac string containing the mac address of the client to be 846 deauthenticated. 847 848 """ 849 self.router.run('%s -p%s deauthenticate %s' % 850 (self.cmd_hostapd_cli, 851 self.hostapd['conf']['ctrl_interface'], 852 client_mac)) 853 854 855 @base_utils.deprecated 856 def deauth(self, params): 857 """Deauthenticates a client described in params. 858 859 Deprecated: Call 'deauth_client', instead. 860 861 @param params dict containing a key 'client'. 862 863 """ 864 self.deauth_client(params['client']) 865 866 867 def send_management_frame(self, frame_type, instance=0): 868 """Injects a management frame into an active hostapd session. 869 870 @param frame_type string the type of frame to send. 871 @param instance int indicating which hostapd instance to inject into. 872 873 """ 874 hostap_interface = self.hostapd_instances[instance]['interface'] 875 interface = self._get_wlanif(0, 'monitor', same_phy_as=hostap_interface) 876 self.router.run("%s link set %s up" % (self.cmd_ip, interface)) 877 self.router.run('%s %s %s' % 878 (self.cmd_send_management_frame, interface, frame_type)) 879 self._release_wlanif(interface) 880 881 882 def detect_client_deauth(self, client_mac, instance=0): 883 """Detects whether hostapd has logged a deauthentication from 884 |client_mac|. 885 886 @param client_mac string the MAC address of the client to detect. 887 @param instance int indicating which hostapd instance to query. 888 889 """ 890 interface = self.hostapd_instances[instance]['interface'] 891 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac) 892 log_file = self.hostapd_instances[instance]['log_file'] 893 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file), 894 ignore_status=True) 895 return result.exit_status == 0 896 897 898 def detect_client_coexistence_report(self, client_mac, instance=0): 899 """Detects whether hostapd has logged an action frame from 900 |client_mac| indicating information about 20/40MHz BSS coexistence. 901 902 @param client_mac string the MAC address of the client to detect. 903 @param instance int indicating which hostapd instance to query. 904 905 """ 906 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): ' 907 '.. .. .. .. .. .. .. .. .. .. %s ' 908 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' % 909 ' '.join(client_mac.split(':'))) 910 log_file = self.hostapd_instances[instance]['log_file'] 911 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file), 912 ignore_status=True) 913 return result.exit_status == 0 914 915 916 def add_connected_peer(self, instance=0): 917 """Configure a station connected to a running AP instance. 918 919 Extract relevant configuration objects from the hostap 920 configuration for |instance| and generate a wpa_supplicant 921 instance that connects to it. This allows the DUT to interact 922 with a client entity that is also connected to the same AP. A 923 full wpa_supplicant instance is necessary here (instead of just 924 using the "iw" command to connect) since we want to enable 925 advanced features such as TDLS. 926 927 @param instance int indicating which hostapd instance to connect to. 928 929 """ 930 if not self.hostapd_instances: 931 raise error.TestFail('Hostapd is not configured.') 932 933 if self.station['configured']: 934 raise error.TestFail('Station is already configured.') 935 936 client_conf = self.station['conf'] 937 client_conf['ssid'] = self.get_ssid(instance) 938 939 hostap_conf = self.hostapd_instances[instance]['config_dict'] 940 frequency = hostap_config.HostapConfig.get_frequency_for_channel( 941 hostap_conf['channel']) 942 interface = self._get_wlanif( 943 frequency, 'managed', hostap_conf['hw_mode']) 944 client_conf['interface'] = interface 945 946 # TODO(pstew): Configure other bits like PSK, 802.11n if tests 947 # require them... 948 supplicant_config = ( 949 'network={\n' 950 ' ssid="%(ssid)s"\n' 951 ' key_mgmt=NONE\n' 952 '}\n' % client_conf 953 ) 954 955 conf_file = self.station['config_file'] % interface 956 log_file = self.station['log_file'] % interface 957 pid_file = self.station['pid_file'] % interface 958 959 self.router.run('cat <<EOF >%s\n%s\nEOF\n' % 960 (conf_file, supplicant_config)) 961 962 # Connect the station. 963 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 964 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' % 965 (self.cmd_wpa_supplicant, 966 interface, pid_file, conf_file, 967 self.hostapd['driver'], log_file)) 968 self.router.run(start_command) 969 self.iw_runner.wait_for_link(interface) 970 971 # Assign an IP address to this interface. 972 self.router.run('%s addr add %s/24 dev %s' % 973 (self.cmd_ip, self.local_peer_ip_address(instance), 974 interface)) 975 976 # Since we now have two network interfaces connected to the same 977 # network, we need to disable the kernel's protection against 978 # incoming packets to an "unexpected" interface. 979 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' % 980 interface) 981 982 self.station['configured'] = True 983 self.station['type'] = 'supplicant' 984 self.station['interface'] = interface 985 986 987 def _pre_config_hook(self, config): 988 """Hook for subclasses. 989 990 Run after gathering configuration parameters, 991 but before writing parameters to config file. 992 993 @param config dict containing hostapd config parameters. 994 995 """ 996 pass 997 998 999 def _pre_start_hook(self, params): 1000 """Hook for subclasses. 1001 1002 Run after generating hostapd config file, but before starting hostapd. 1003 1004 @param params dict parameters from site_wifitest. 1005 1006 """ 1007 pass 1008 1009 1010 def _post_start_hook(self, params): 1011 """Hook for subclasses run after starting hostapd. 1012 1013 @param params dict parameters from site_wifitest. 1014 1015 """ 1016 pass 1017