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