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