1# Copyright (c) 2013 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 os
6import urlparse
7
8
9import common
10from autotest_lib.client.bin import utils
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.common_lib.cros import virtual_ethernet_pair
13from autotest_lib.client.cros import network, network_chroot
14from autotest_lib.client.cros.cellular import test_endpoint
15
16class PseudoNetInterface(object):
17    """
18    PseudoNetInterface provides a pseudo modem network interface.  This
19    network interface is one end of a virtual Ethernet pair.  The other end
20    of the virtual Ethernet pair is connected to a minijail that provides DHCP
21    and DNS services.  Also in the minijail is a test endpoint (web server)
22    that is needed to pass portal detection and perform data transfer tests.
23
24    """
25    ARP_ANNOUNCE_CONF = '/proc/sys/net/ipv4/conf/all/arp_announce'
26    IFACE_NAME = 'pseudomodem0'
27    PEER_IFACE_NAME = IFACE_NAME + 'p'
28    IFACE_IP_BASE = '192.168.7'
29    IFACE_NETWORK_PREFIX = 24
30    NETWORK_CHROOT_CONFIG = {
31            'etc/passwd' :
32                    'root:x:0:0:root:/root:/bin/bash\n'
33                    'nobody:x:65534:65534:nobody:/dev/null:/bin/false\n',
34            'etc/group' :
35                    'nobody::65534:\n'}
36    SHILL_PORTAL_DETECTION_SERVER = 'www.gstatic.com'
37
38    def __init__(self):
39        self._arp_announce = 0
40        peer_ip = self.IFACE_IP_BASE + '.1'
41        peer_interface_ip = peer_ip + '/' + str(self.IFACE_NETWORK_PREFIX)
42        self.vif = virtual_ethernet_pair.VirtualEthernetPair(
43                interface_name=self.IFACE_NAME,
44                peer_interface_name=self.PEER_IFACE_NAME,
45                interface_ip=None,
46                peer_interface_ip=peer_interface_ip,
47                ignore_shutdown_errors=True)
48        self.chroot = network_chroot.NetworkChroot(self.PEER_IFACE_NAME,
49                                                   peer_ip,
50                                                   self.IFACE_NETWORK_PREFIX)
51        self.chroot.add_config_templates(self.NETWORK_CHROOT_CONFIG)
52        self.chroot.add_startup_command(
53                'iptables -I INPUT -p udp --dport 67 -j ACCEPT')
54        self.chroot.add_startup_command(
55                'iptables -I INPUT -p tcp --dport 80 -j ACCEPT')
56        self._dnsmasq_command = self._GetDnsmasqCommand(peer_ip)
57        self.chroot.add_startup_command(self._dnsmasq_command)
58        self._test_endpoint_command = self._GetTestEndpointCommand()
59        self.chroot.add_startup_command(self._test_endpoint_command)
60
61    @staticmethod
62    def _GetDnsmasqCommand(peer_ip):
63        dnsmasq_command = (
64                'dnsmasq '
65                '--dhcp-leasefile=/tmp/dnsmasq.leases '
66                '--dhcp-range=%s.2,%s.254 '
67                '--no-resolv '
68                '--no-hosts ' %
69                (PseudoNetInterface.IFACE_IP_BASE,
70                 PseudoNetInterface.IFACE_IP_BASE))
71        test_fetch_url_host = \
72                urlparse.urlparse(network.FETCH_URL_PATTERN_FOR_TEST).netloc
73        dns_lookup_table = {
74                PseudoNetInterface.SHILL_PORTAL_DETECTION_SERVER: peer_ip,
75                test_fetch_url_host: peer_ip }
76        for host, ip in dns_lookup_table.iteritems():
77            dnsmasq_command += '--address=/%s/%s ' % (host, ip)
78        return dnsmasq_command
79
80    @staticmethod
81    def _GetTestEndpointCommand():
82        test_endpoint_path = os.path.abspath(test_endpoint.__file__)
83        if test_endpoint_path.endswith('.pyc'):
84            test_endpoint_path = test_endpoint_path[:-1]
85        return test_endpoint_path
86
87    def _ChrootRunCmdIgnoreErrors(self, cmd):
88        try:
89            self.chroot.run(cmd)
90        except error.CmdError:
91            pass
92
93    def BringInterfaceUp(self):
94        """
95        Brings up the pseudo modem network interface.
96
97        """
98        utils.run('sudo ip link set %s up' % self.IFACE_NAME)
99
100    def BringInterfaceDown(self):
101        """
102        Brings down the pseudo modem network interface.
103
104        """
105        utils.run('sudo ip link set %s down' % self.IFACE_NAME);
106
107    def Setup(self):
108        """
109        Sets up the virtual Ethernet pair and starts dnsmasq.
110
111        """
112        # Make sure ARP requests for the pseudo modem network addresses
113        # go out the pseudo modem network interface.
114        self._arp_announce = utils.system_output(
115                'cat %s' % self.ARP_ANNOUNCE_CONF)
116        utils.run('echo 1 > %s' % self.ARP_ANNOUNCE_CONF)
117
118        self.vif.setup()
119        self.BringInterfaceDown()
120        if not self.vif.is_healthy:
121            raise Exception('Could not initialize virtual ethernet pair')
122        self.chroot.startup()
123
124    def Teardown(self):
125        """
126        Stops dnsmasq and takes down the virtual Ethernet pair.
127
128        """
129        self._ChrootRunCmdIgnoreErrors(['/bin/bash', '-c', '"pkill dnsmasq"'])
130        self._ChrootRunCmdIgnoreErrors(['/bin/bash', '-c',
131                                        '"pkill -f test_endpoint"'])
132        self.vif.teardown()
133        self.chroot.shutdown()
134        utils.run('echo %s > %s' % (self._arp_announce, self.ARP_ANNOUNCE_CONF))
135
136    def Restart(self):
137        """
138        Restarts the configuration.
139
140        """
141        self.Teardown()
142        self.Setup()
143
144