site_linux_router.py revision aeef9b52c54a27098925b44d93f270a6d8f29e3b
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 collections
6import logging
7import random
8import string
9import time
10
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
17
18StationInstance = collections.namedtuple('StationInstance',
19                                         ['ssid', 'interface', 'dev_type'])
20
21
22class LinuxRouter(site_linux_system.LinuxSystem):
23    """Linux/mac80211-style WiFi Router support for WiFiTest class.
24
25    This class implements test methods/steps that communicate with a
26    router implemented with Linux/mac80211.  The router must
27    be pre-configured to enable ssh access and have a mac80211-based
28    wireless device.  We also assume hostapd 0.7.x and iw are present
29    and any necessary modules are pre-loaded.
30
31    """
32
33    KNOWN_TEST_PREFIX = 'network_WiFi'
34    STARTUP_POLLING_INTERVAL_SECONDS = 0.5
35    STARTUP_TIMEOUT_SECONDS = 10
36    SUFFIX_LETTERS = string.ascii_lowercase + string.digits
37    SUBNET_PREFIX_OCTETS = (192, 168)
38
39    HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
40    HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
41    HOSTAPD_PID_FILE_PATTERN = '/tmp/hostapd-test-%s.pid'
42    HOSTAPD_CONTROL_INTERFACE_PATTERN = '/tmp/hostapd-test-%s.ctrl'
43    HOSTAPD_DRIVER_NAME = 'nl80211'
44
45    STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
46    STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
47    STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
48
49    def get_capabilities(self):
50        """@return iterable object of AP capabilities for this system."""
51        caps = set([self.CAPABILITY_IBSS])
52        try:
53            self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
54                    self.host, '/usr/bin/send_management_frame')
55            caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
56        except error.TestFail:
57            pass
58        return super(LinuxRouter, self).get_capabilities().union(caps)
59
60
61    @property
62    def router(self):
63        """Deprecated.  Use self.host instead.
64
65        @return Host object representing the remote router.
66
67        """
68        return self.host
69
70
71    @property
72    def wifi_ip(self):
73        """Simple accessor for the WiFi IP when there is only one AP.
74
75        @return string IP of WiFi interface.
76
77        """
78        if len(self.local_servers) != 1:
79            raise error.TestError('Could not pick a WiFi IP to return.')
80
81        return self.get_wifi_ip(0)
82
83
84    def __init__(self, host, test_name):
85        """Build a LinuxRouter.
86
87        @param host Host object representing the remote machine.
88        @param test_name string name of this test.  Used in SSID creation.
89
90        """
91        super(LinuxRouter, self).__init__(host, 'router')
92
93        self.cmd_dhcpd = '/usr/sbin/dhcpd'
94        self.cmd_hostapd = wifi_test_utils.must_be_installed(
95                host, '/usr/sbin/hostapd')
96        self.cmd_hostapd_cli = wifi_test_utils.must_be_installed(
97                host, '/usr/sbin/hostapd_cli')
98        self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed(
99                host, '/usr/sbin/wpa_supplicant')
100        self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
101        self.dhcpd_leases = '/tmp/dhcpd.leases'
102
103        # hostapd configuration persists throughout the test, subsequent
104        # 'config' commands only modify it.
105        self.ssid_prefix = test_name
106        if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
107            # Many of our tests start with an uninteresting prefix.
108            # Remove it so we can have more unique bytes.
109            self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
110        self.ssid_prefix = self.ssid_prefix.lstrip('_')
111        self.ssid_prefix += '_'
112
113        self._total_hostapd_instances = 0
114        self.local_servers = []
115        self.hostapd_instances = []
116        self.station_instances = []
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        # Reset all antennas to be active
128        self.set_default_antenna_bitmap()
129
130
131    def close(self):
132        """Close global resources held by this system."""
133        self.deconfig()
134        super(LinuxRouter, self).close()
135
136
137    def has_local_server(self):
138        """@return True iff this router has local servers configured."""
139        return bool(self.local_servers)
140
141
142    def start_hostapd(self, configuration):
143        """Start a hostapd instance described by conf.
144
145        @param configuration HostapConfig object.
146
147        """
148        # Figure out the correct interface.
149        interface = self.get_wlanif(configuration.frequency, 'managed')
150
151        conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
152        log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
153        pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
154        control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
155        hostapd_conf_dict = configuration.generate_dict(
156                interface, control_interface,
157                self._build_ssid(configuration.ssid_suffix))
158        logging.info('Starting hostapd with parameters: %r', hostapd_conf_dict)
159
160        # Generate hostapd.conf.
161        self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
162            (conf_file, '\n'.join(
163            "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
164
165        # Run hostapd.
166        logging.info("Starting hostapd...")
167        self.router.run('rm %s' % log_file, ignore_status=True)
168        self.router.run('rm %s' % pid_file, ignore_status=True)
169        self.router.run('stop wpasupplicant', ignore_status=True)
170        start_command = '%s -dd -B -t -f %s -P %s %s' % (
171                self.cmd_hostapd, log_file, pid_file, conf_file)
172        self.router.run(start_command)
173        self.hostapd_instances.append({
174            'ssid': hostapd_conf_dict['ssid'],
175            'conf_file': conf_file,
176            'log_file': log_file,
177            'interface': interface,
178            'pid_file': pid_file,
179            'config_dict': hostapd_conf_dict.copy()
180        })
181
182        # Wait for confirmation that the router came up.
183        pid = int(self.router.run('cat %s' % pid_file).stdout)
184        logging.info('Waiting for hostapd to startup.')
185        start_time = time.time()
186        while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
187            success = self.router.run(
188                    'grep "Completing interface initialization" %s' % log_file,
189                    ignore_status=True).exit_status == 0
190            if success:
191                break
192
193            # A common failure is an invalid router configuration.
194            # Detect this and exit early if we see it.
195            bad_config = self.router.run(
196                    'grep "Interface initialization failed" %s' % log_file,
197                    ignore_status=True).exit_status == 0
198            if bad_config:
199                raise error.TestFail('hostapd failed to initialize AP '
200                                     'interface.')
201
202            if pid:
203                early_exit = self.router.run('kill -0 %d' % pid,
204                                             ignore_status=True).exit_status
205                if early_exit:
206                    raise error.TestFail('hostapd process terminated.')
207
208            time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
209        else:
210            raise error.TestFail('Timed out while waiting for hostapd '
211                                 'to start.')
212
213
214    def _kill_process_instance(self, process, instance=None, wait=0):
215        """Kill a process on the router.
216
217        Kills program named |process|, optionally only a specific
218        |instance|.  If |wait| is specified, we makes sure |process| exits
219        before returning.
220
221        @param process string name of process to kill.
222        @param instance string instance of process to kill.
223        @param wait int timeout in seconds to wait for.
224
225        """
226        if instance:
227            search_arg = '-f "%s.*%s"' % (process, instance)
228        else:
229            search_arg = process
230
231        cmd = "pkill %s >/dev/null 2>&1" % search_arg
232
233        if wait:
234            cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
235                    search_arg)
236            self.router.run(cmd, timeout=wait, ignore_status=True)
237        else:
238            self.router.run(cmd, ignore_status=True)
239
240
241    def kill_hostapd_instance(self, instance):
242        """Kills a hostapd instance.
243
244        @param instance string instance to kill.
245
246        """
247        self._kill_process_instance('hostapd', instance, 30)
248
249
250    def kill_hostapd(self):
251        """Kill all hostapd instances."""
252        self.kill_hostapd_instance(None)
253
254
255    def _build_ssid(self, suffix):
256        unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
257                               for x in range(5)])
258        return (self.ssid_prefix + unique_salt + suffix)[-32:]
259
260
261    def hostap_configure(self, configuration, multi_interface=None):
262        """Build up a hostapd configuration file and start hostapd.
263
264        Also setup a local server if this router supports them.
265
266        @param configuration HosetapConfig object.
267        @param multi_interface bool True iff multiple interfaces allowed.
268
269        """
270        if multi_interface is None and (self.hostapd_instances or
271                                        self.station_instances):
272            self.deconfig()
273        self.start_hostapd(configuration)
274        interface = self.hostapd_instances[-1]['interface']
275        self.iw_runner.set_tx_power(interface, 'auto')
276        self.start_local_server(interface)
277        logging.info('AP configured.')
278
279
280    @staticmethod
281    def ip_addr(netblock, idx):
282        """Simple IPv4 calculator.
283
284        Takes host address in "IP/bits" notation and returns netmask, broadcast
285        address as well as integer offsets into the address range.
286
287        @param netblock string host address in "IP/bits" notation.
288        @param idx string describing what to return.
289        @return string containing something you hopefully requested.
290
291        """
292        addr_str,bits = netblock.split('/')
293        addr = map(int, addr_str.split('.'))
294        mask_bits = (-1 << (32-int(bits))) & 0xffffffff
295        mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
296        if idx == 'local':
297            return addr_str
298        elif idx == 'netmask':
299            return '.'.join(map(str, mask))
300        elif idx == 'broadcast':
301            offset = [m ^ 0xff for m in mask]
302        else:
303            offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
304        return '.'.join(map(str, [(a & m) + o
305                                  for a, m, o in zip(addr, mask, offset)]))
306
307
308    def ibss_configure(self, config):
309        """Configure a station based AP in IBSS mode.
310
311        Extract relevant configuration objects from |config| despite not
312        actually being a hostap managed endpoint.
313
314        @param config HostapConfig object.
315
316        """
317        if self.station_instances or self.hostapd_instances:
318            self.deconfig()
319        interface = self.get_wlanif(config.frequency, 'ibss')
320        ssid = (config.ssid or self._build_ssid(config.ssid_suffix))
321        # Connect the station
322        self.router.run('%s link set %s up' % (self.cmd_ip, interface))
323        self.iw_runner.ibss_join(interface, ssid, config.frequency)
324        # Always start a local server.
325        self.start_local_server(interface)
326        # Remember that this interface is up.
327        self.station_instances.append(
328                StationInstance(ssid=ssid, interface=interface,
329                                dev_type='ibss'))
330
331
332    def local_server_address(self, index):
333        """Get the local server address for an interface.
334
335        When we multiple local servers, we give them static IP addresses
336        like 192.168.*.254.
337
338        @param index int describing which local server this is for.
339
340        """
341        return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
342
343
344    def local_peer_ip_address(self, index):
345        """Get the IP address allocated for the peer associated to the AP.
346
347        This address is assigned to a locally associated peer device that
348        is created for the DUT to perform connectivity tests with.
349        When we have multiple local servers, we give them static IP addresses
350        like 192.168.*.253.
351
352        @param index int describing which local server this is for.
353
354        """
355        return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
356
357
358    def local_peer_mac_address(self):
359        """Get the MAC address of the peer interface.
360
361        @return string MAC address of the peer interface.
362
363        """
364        iface = interface.Interface(self.station_instances[0].interface,
365                                    self.router)
366        return iface.mac_address
367
368
369    def start_local_server(self, interface):
370        """Start a local server on an interface.
371
372        @param interface string (e.g. wlan0)
373
374        """
375        logging.info("Starting up local server...")
376
377        if len(self.local_servers) >= 256:
378            raise error.TestFail('Exhausted available local servers')
379
380        netblock = '%s/24' % self.local_server_address(len(self.local_servers))
381
382        params = {}
383        params['netblock'] = netblock
384        params['subnet'] = self.ip_addr(netblock, 0)
385        params['netmask'] = self.ip_addr(netblock, 'netmask')
386        params['dhcp_range'] = ' '.join(
387            (self.ip_addr(netblock, self.dhcp_low),
388             self.ip_addr(netblock, self.dhcp_high)))
389        params['interface'] = interface
390
391        params['ip_params'] = ("%s broadcast %s dev %s" %
392                               (netblock,
393                                self.ip_addr(netblock, 'broadcast'),
394                                interface))
395        self.local_servers.append(params)
396
397        self.router.run("%s addr flush %s" %
398                        (self.cmd_ip, interface))
399        self.router.run("%s addr add %s" %
400                        (self.cmd_ip, params['ip_params']))
401        self.router.run("%s link set %s up" %
402                        (self.cmd_ip, interface))
403        self.start_dhcp_server(interface)
404
405
406    def start_dhcp_server(self, interface):
407        """Start a dhcp server on an interface.
408
409        @param interface string (e.g. wlan0)
410
411        """
412        for server in self.local_servers:
413            if server['interface'] == interface:
414                params = server
415                break
416        else:
417            raise error.TestFail('Could not find local server '
418                                 'to match interface: %r' % interface)
419
420        dhcpd_conf_file = self.dhcpd_conf % interface
421        dhcp_conf = '\n'.join([
422            'port=0',  # disables DNS server
423            'bind-interfaces',
424            'log-dhcp',
425            'dhcp-range=%s' % params['dhcp_range'].replace(' ', ','),
426            'interface=%s' % params['interface'],
427            'dhcp-leasefile=%s' % self.dhcpd_leases])
428        self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
429            (dhcpd_conf_file, dhcp_conf))
430        self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
431
432
433    def stop_dhcp_server(self, instance=None):
434        """Stop a dhcp server on the router.
435
436        @param instance string instance to kill.
437
438        """
439        self._kill_process_instance('dnsmasq', instance, 0)
440
441
442    def stop_dhcp_servers(self):
443        """Stop all dhcp servers on the router."""
444        self.stop_dhcp_server(None)
445
446
447    def get_wifi_channel(self, ap_num):
448        """Return channel of BSS corresponding to |ap_num|.
449
450        @param ap_num int which BSS to get the channel of.
451        @return int primary channel of BSS.
452
453        """
454        instance = self.hostapd_instances[ap_num]
455        return instance['config_dict']['channel']
456
457
458    def get_wifi_ip(self, ap_num):
459        """Return IP address on the WiFi subnet of a local server on the router.
460
461        If no local servers are configured (e.g. for an RSPro), a TestFail will
462        be raised.
463
464        @param ap_num int which local server to get an address from.
465
466        """
467        if self.local_servers:
468            return self.ip_addr(self.local_servers[ap_num]['netblock'],
469                                'local')
470        else:
471            raise error.TestFail("No IP address assigned")
472
473
474    def get_hostapd_interface(self, ap_num):
475        """Get the name of the interface associated with a hostapd instance.
476
477        @param ap_num: int hostapd instance number.
478        @return string interface name (e.g. 'managed0').
479
480        """
481        if ap_num not in range(len(self.hostapd_instances)):
482            raise error.TestFail('Invalid instance number (%d) with %d '
483                                 'instances configured.' %
484                                 (ap_num, len(self.hostapd_instances)))
485
486        instance = self.hostapd_instances[ap_num]
487        return instance['interface']
488
489
490    def get_hostapd_mac(self, ap_num):
491        """Return the MAC address of an AP in the test.
492
493        @param ap_num int index of local server to read the MAC address from.
494        @return string MAC address like 00:11:22:33:44:55.
495
496        """
497        interface_name = self.get_hostapd_interface(ap_num)
498        ap_interface = interface.Interface(interface_name, self.host)
499        return ap_interface.mac_address
500
501
502    def get_hostapd_phy(self, ap_num):
503        """Get name of phy for hostapd instance.
504
505        @param ap_num int index of hostapd instance.
506        @return string phy name of phy corresponding to hostapd's
507                managed interface.
508
509        """
510        interface = self.iw_runner.get_interface(
511                self.get_hostapd_interface(ap_num))
512        return interface.phy
513
514
515    def deconfig(self):
516        """A legacy, deprecated alias for deconfig_aps."""
517        self.deconfig_aps()
518
519
520    def deconfig_aps(self, instance=None, silent=False):
521        """De-configure an AP (will also bring wlan down).
522
523        @param instance: int or None.  If instance is None, will bring down all
524                instances of hostapd.
525        @param silent: True if instances should be brought without de-authing
526                the DUT.
527
528        """
529        if not self.hostapd_instances and not self.station_instances:
530            return
531
532        if self.hostapd_instances:
533            local_servers = []
534            if instance is not None:
535                instances = [ self.hostapd_instances.pop(instance) ]
536                for server in self.local_servers:
537                    if server['interface'] == instances[0]['interface']:
538                        local_servers = [server]
539                        self.local_servers.remove(server)
540                        break
541            else:
542                instances = self.hostapd_instances
543                self.hostapd_instances = []
544                local_servers = self.local_servers
545                self.local_servers = []
546
547            for instance in instances:
548                if silent:
549                    # Deconfigure without notifying DUT.  Remove the interface
550                    # hostapd uses to send beacon and DEAUTH packets.
551                    self.remove_interface(instance['interface'])
552
553                self.kill_hostapd_instance(instance['conf_file'])
554                if wifi_test_utils.is_installed(self.host,
555                                                instance['log_file']):
556                    self.router.get_file(instance['log_file'],
557                                         'debug/hostapd_router_%d_%s.log' %
558                                         (self._total_hostapd_instances,
559                                          instance['interface']))
560                else:
561                    logging.error('Did not collect hostapd log file because '
562                                  'it was missing.')
563                self.release_interface(instance['interface'])
564#               self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
565            self._total_hostapd_instances += 1
566        if self.station_instances:
567            local_servers = self.local_servers
568            self.local_servers = []
569            instance = self.station_instances.pop()
570            if instance.dev_type == 'ibss':
571                self.iw_runner.ibss_leave(instance.interface)
572            elif instance.dev_type == 'managed':
573                self._kill_process_instance('wpa_supplicant',
574                                            instance.interface)
575            else:
576                self.iw_runner.disconnect_station(instance.interface)
577            self.router.run('%s link set %s down' %
578                            (self.cmd_ip, instance.interface))
579
580        for server in local_servers:
581            self.stop_dhcp_server(server['interface'])
582            self.router.run("%s addr del %s" %
583                            (self.cmd_ip, server['ip_params']),
584                             ignore_status=True)
585
586
587    def confirm_pmksa_cache_use(self, instance=0):
588        """Verify that the PMKSA auth was cached on a hostapd instance.
589
590        @param instance int router instance number.
591
592        """
593        log_file = self.hostapd_instances[instance]['log_file']
594        pmksa_match = 'PMK from PMKSA cache'
595        result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
596                                 ignore_status=True)
597        if result.exit_status:
598            raise error.TestFail('PMKSA cache was not used in roaming.')
599
600
601    def get_ssid(self, instance=None):
602        """@return string ssid for the network stemming from this router."""
603        if instance is None:
604            instance = 0
605            if len(self.hostapd_instances) > 1:
606                raise error.TestFail('No instance of hostapd specified with '
607                                     'multiple instances present.')
608
609        if self.hostapd_instances:
610            return self.hostapd_instances[instance]['ssid']
611
612        if self.station_instances:
613            return self.station_instances[0].ssid
614
615        raise error.TestFail('Requested ssid of an unconfigured AP.')
616
617
618    def deauth_client(self, client_mac):
619        """Deauthenticates a client described in params.
620
621        @param client_mac string containing the mac address of the client to be
622               deauthenticated.
623
624        """
625        control_if = self.hostapd_instances[-1]['config_dict']['ctrl_interface']
626        self.router.run('%s -p%s deauthenticate %s' %
627                        (self.cmd_hostapd_cli, control_if, client_mac))
628
629
630    def send_management_frame(self, frame_type, instance=0):
631        """Injects a management frame into an active hostapd session.
632
633        @param frame_type string the type of frame to send.
634        @param instance int indicating which hostapd instance to inject into.
635
636        """
637        hostap_interface = self.hostapd_instances[instance]['interface']
638        interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
639        self.router.run("%s link set %s up" % (self.cmd_ip, interface))
640        self.router.run('%s %s %s' %
641                        (self.cmd_send_management_frame, interface, frame_type))
642        self.release_interface(interface)
643
644
645    def detect_client_deauth(self, client_mac, instance=0):
646        """Detects whether hostapd has logged a deauthentication from
647        |client_mac|.
648
649        @param client_mac string the MAC address of the client to detect.
650        @param instance int indicating which hostapd instance to query.
651
652        """
653        interface = self.hostapd_instances[instance]['interface']
654        deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
655        log_file = self.hostapd_instances[instance]['log_file']
656        result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
657                                 ignore_status=True)
658        return result.exit_status == 0
659
660
661    def detect_client_coexistence_report(self, client_mac, instance=0):
662        """Detects whether hostapd has logged an action frame from
663        |client_mac| indicating information about 20/40MHz BSS coexistence.
664
665        @param client_mac string the MAC address of the client to detect.
666        @param instance int indicating which hostapd instance to query.
667
668        """
669        coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
670                    '.. .. .. .. .. .. .. .. .. .. %s '
671                    '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
672                    ' '.join(client_mac.split(':')))
673        log_file = self.hostapd_instances[instance]['log_file']
674        result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
675                                 ignore_status=True)
676        return result.exit_status == 0
677
678
679    def add_connected_peer(self, instance=0):
680        """Configure a station connected to a running AP instance.
681
682        Extract relevant configuration objects from the hostap
683        configuration for |instance| and generate a wpa_supplicant
684        instance that connects to it.  This allows the DUT to interact
685        with a client entity that is also connected to the same AP.  A
686        full wpa_supplicant instance is necessary here (instead of just
687        using the "iw" command to connect) since we want to enable
688        advanced features such as TDLS.
689
690        @param instance int indicating which hostapd instance to connect to.
691
692        """
693        if not self.hostapd_instances:
694            raise error.TestFail('Hostapd is not configured.')
695
696        if self.station_instances:
697            raise error.TestFail('Station is already configured.')
698
699        ssid = self.get_ssid(instance)
700        hostap_conf = self.hostapd_instances[instance]['config_dict']
701        frequency = hostap_config.HostapConfig.get_frequency_for_channel(
702                hostap_conf['channel'])
703        interface = self.get_wlanif(frequency, 'managed')
704
705        # TODO(pstew): Configure other bits like PSK, 802.11n if tests
706        # require them...
707        supplicant_config = (
708                'network={\n'
709                '  ssid="%(ssid)s"\n'
710                '  key_mgmt=NONE\n'
711                '}\n' % {'ssid': ssid}
712        )
713
714        conf_file = self.STATION_CONF_FILE_PATTERN % interface
715        log_file = self.STATION_LOG_FILE_PATTERN % interface
716        pid_file = self.STATION_PID_FILE_PATTERN % interface
717
718        self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
719            (conf_file, supplicant_config))
720
721        # Connect the station.
722        self.router.run('%s link set %s up' % (self.cmd_ip, interface))
723        start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
724                         (self.cmd_wpa_supplicant,
725                         interface, pid_file, conf_file,
726                         self.HOSTAPD_DRIVER_NAME, log_file))
727        self.router.run(start_command)
728        self.iw_runner.wait_for_link(interface)
729
730        # Assign an IP address to this interface.
731        self.router.run('%s addr add %s/24 dev %s' %
732                        (self.cmd_ip, self.local_peer_ip_address(instance),
733                         interface))
734
735        # Since we now have two network interfaces connected to the same
736        # network, we need to disable the kernel's protection against
737        # incoming packets to an "unexpected" interface.
738        self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
739                        interface)
740
741        # Similarly, we'd like to prevent the hostap interface from
742        # replying to ARP requests for the peer IP address and vice
743        # versa.
744        self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
745                        interface)
746        self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
747                        hostap_conf['interface'])
748
749        self.station_instances.append(
750                StationInstance(ssid=ssid, interface=interface,
751                                dev_type='managed'))
752