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