1# Copyright (c) 2012 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"""
5VirtualEthernetPair provides methods for setting up and tearing down a virtual
6ethernet interface for use in tests.  You will probably need to be root on test
7devices to use this class.  The constructor allows you to specify your IP's to
8assign to both ends of the pair, however, if you wish to leave the interface
9unconfigured, simply pass None.  You may also specify the subnet of your ip
10addresses.  Failing to do so leaves them with default in ifconfig.
11
12# TODO b:169251326 terms below are set outside of this codebase
13# and should be updated when possible. ("master" -> "main", "slave" -> "node")
14
15Example usage:
16vif = virtual_ethernet_pair.VirtualEthernetPair(interface_name="master",
17                                                peer_interface_name="peer",
18                                                interface_ip="10.9.8.1/24",
19                                                peer_interface_ip=None)
20vif.setup()
21if not vif.is_healthy:
22    # bad things happened while creating the interface
23    # ... abort gracefully
24
25interface_name = vif.interface_name
26peer_interface_name = vif.peer_interface_name
27#... do things with your interface
28
29# You must call this if you want to leave the system in a good state.
30vif.teardown()
31
32Alternatively:
33
34with virtual_ethernet_pair.VirtualEthernetPair(...) as vif:
35    if not vif.is_healthy:
36        # bad things happened while creating the interface
37        # ... abort gracefully
38
39    interface_name = vif.interface_name
40    peer_interface_name = vif.peer_interface_name
41    #... do things with your interface
42
43"""
44
45import logging
46
47from autotest_lib.client.bin import utils
48from autotest_lib.client.common_lib.cros.network import interface
49
50class VirtualEthernetPair(object):
51    """ Class for configuring virtual ethernet device pair. """
52
53    def __init__(self,
54                 interface_name='veth_master',
55                 peer_interface_name='veth_slave',
56                 interface_ip='10.9.8.1/24',
57                 peer_interface_ip='10.9.8.2/24',
58                 interface_ipv6=None,
59                 peer_interface_ipv6=None,
60                 ignore_shutdown_errors=False,
61                 host=None):
62        """
63        Construct a object managing a virtual ethernet pair.  One end of the
64        interface will be called |interface_name|, and the peer end
65        |peer_interface_name|.  You may get the interface names later with
66        VirtualEthernetPair.get_[peer_]interface_name().  The ends of the
67        interface are manually configured with the given IPv4 address strings
68        (like "10.9.8.2/24").  You may skip the IP configuration by passing None
69        as the address for either interface.
70        """
71        super(VirtualEthernetPair, self).__init__()
72        self._is_healthy = True
73        self._interface_name = interface_name
74        self._peer_interface_name = peer_interface_name
75        self._interface_ip = interface_ip
76        self._peer_interface_ip = peer_interface_ip
77        self._interface_ipv6 = interface_ipv6
78        self._peer_interface_ipv6 = peer_interface_ipv6
79        self._ignore_shutdown_errors = ignore_shutdown_errors
80        self._run = utils.run
81        self._host = host
82        if host is not None:
83            self._run = host.run
84
85
86    def setup(self):
87        """
88        Installs a virtual ethernet interface and configures one side with an IP
89        address.  First does some sanity checking and tries to remove an
90        existing interface by the same name, and logs messages on failures.
91        """
92        self._is_healthy = False
93        if self._either_interface_exists():
94            logging.warning('At least one test interface already existed.'
95                            '  Attempting to remove.')
96            self._remove_test_interface()
97            if self._either_interface_exists():
98                logging.error('Failed to remove unexpected test '
99                              'interface.  Aborting.')
100                return
101
102        self._create_test_interface()
103        if not self._interface_exists(self._interface_name):
104            logging.error('Failed to create main test interface.')
105            return
106
107        if not self._interface_exists(self._peer_interface_name):
108            logging.error('Failed to create peer test interface.')
109            return
110        # Unless you tell the firewall about the interface, you're not going to
111        # get any IP traffic through.  Since this is basically a loopback
112        # device, just allow all traffic.
113        for name in (self._interface_name, self._peer_interface_name):
114            status = self._run('iptables -w -I INPUT -i %s -j ACCEPT' % name,
115                               ignore_status=True)
116            if status.exit_status != 0:
117                logging.error('iptables rule addition failed for interface %s: '
118                              '%s', name, status.stderr)
119        self._is_healthy = True
120
121
122    def teardown(self):
123        """
124        Removes the interface installed by VirtualEthernetPair.setup(), with
125        some simple sanity checks that print warnings when either the interface
126        isn't there or fails to be removed.
127        """
128        for name in (self._interface_name, self._peer_interface_name):
129            self._run('iptables -w -D INPUT -i %s -j ACCEPT' % name,
130                      ignore_status=True)
131        if not self._either_interface_exists():
132            logging.warning('VirtualEthernetPair.teardown() called, '
133                            'but no interface was found.')
134            return
135
136        self._remove_test_interface()
137        if self._either_interface_exists():
138            logging.error('Failed to destroy test interface.')
139
140
141    @property
142    def is_healthy(self):
143        """@return True if virtual ethernet pair is configured."""
144        return self._is_healthy
145
146
147    @property
148    def interface_name(self):
149        """@return string name of the interface."""
150        return self._interface_name
151
152
153    @property
154    def peer_interface_name(self):
155        """@return string name of the peer interface."""
156        return self._peer_interface_name
157
158
159    @property
160    def interface_ip(self):
161        """@return string IPv4 address of the interface."""
162        return interface.Interface(self.interface_name).ipv4_address
163
164
165    @property
166    def peer_interface_ip(self):
167        """@return string IPv4 address of the peer interface."""
168        return interface.Interface(self.peer_interface_name).ipv4_address
169
170
171    @property
172    def interface_subnet_mask(self):
173        """@return string IPv4 subnet mask of the interface."""
174        return interface.Interface(self.interface_name).ipv4_subnet_mask
175
176
177    @property
178    def interface_prefix(self):
179        """@return int IPv4 prefix length."""
180        return interface.Interface(self.interface_name).ipv4_prefix
181
182
183    @property
184    def peer_interface_subnet_mask(self):
185        """@return string IPv4 subnet mask of the peer interface."""
186        return interface.Interface(self.peer_interface_name).ipv4_subnet_mask
187
188
189    @property
190    def interface_mac(self):
191        """@return string MAC address of the interface."""
192        return interface.Interface(self.interface_name).mac_address
193
194
195    @property
196    def peer_interface_mac(self):
197        """@return string MAC address of the peer interface."""
198        return interface.Interface(self._peer_interface_name).mac_address
199
200
201    def __enter__(self):
202        self.setup()
203        return self
204
205
206    def __exit__(self, exc_type, exc_value, traceback):
207        self.teardown()
208
209
210    def _interface_exists(self, interface_name):
211        """
212        Returns True iff we found an interface with name |interface_name|.
213        """
214        return interface.Interface(interface_name, host=self._host).exists
215
216
217    def _either_interface_exists(self):
218        return (self._interface_exists(self._interface_name) or
219                self._interface_exists(self._peer_interface_name))
220
221
222    def _remove_test_interface(self):
223        """
224        Remove the virtual ethernet device installed by
225        _create_test_interface().
226        """
227        self._run('ip link set %s down' % self._interface_name,
228                  ignore_status=self._ignore_shutdown_errors)
229        self._run('ip link set %s down' % self._peer_interface_name,
230                  ignore_status=self._ignore_shutdown_errors)
231        self._run('ip link delete %s >/dev/null 2>&1' % self._interface_name,
232                  ignore_status=self._ignore_shutdown_errors)
233
234        # Under most normal circumstances a successful deletion of
235        # |_interface_name| should also remove |_peer_interface_name|,
236        # but if we elected to ignore failures above, that may not be
237        # the case.
238        self._run('ip link delete %s >/dev/null 2>&1' %
239                  self._peer_interface_name, ignore_status=True)
240
241
242    def _create_test_interface(self):
243        """
244        Set up a virtual ethernet device and configure the host side with a
245        fake IP address.
246        """
247        self._run('ip link add name %s '
248                  'type veth peer name %s >/dev/null 2>&1' %
249                  (self._interface_name, self._peer_interface_name))
250        self._run('ip link set %s up' % self._interface_name)
251        self._run('ip link set %s up' % self._peer_interface_name)
252        if self._interface_ip is not None:
253            self._run('ip addr add %s dev %s' % (self._interface_ip,
254                                                 self._interface_name))
255        if self._peer_interface_ip is not None:
256            self._run('ip addr add %s dev %s' % (self._peer_interface_ip,
257                                                 self._peer_interface_name))
258        if self._interface_ipv6 is not None:
259            self._run('ip -6 addr add %s dev %s' % (self._interface_ipv6,
260                                                    self._interface_name))
261        if self._peer_interface_ipv6 is not None:
262            self._run('ip -6 addr add %s dev %s' % (self._peer_interface_ipv6,
263                                                    self._peer_interface_name))
264