Home | History | Annotate | Download | only in cros
      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 """
      5 VirtualEthernetPair provides methods for setting up and tearing down a virtual
      6 ethernet interface for use in tests.  You will probably need to be root on test
      7 devices to use this class.  The constructor allows you to specify your IP's to
      8 assign to both ends of the pair, however, if you wish to leave the interface
      9 unconfigured, simply pass None.  You may also specify the subnet of your ip
     10 addresses.  Failing to do so leaves them with default in ifconfig.
     11 
     12 Example usage:
     13 vif = 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)
     17 vif.setup()
     18 if not vif.is_healthy:
     19     # bad things happened while creating the interface
     20     # ... abort gracefully
     21 
     22 interface_name = vif.interface_name
     23 peer_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.
     27 vif.teardown()
     28 
     29 Alternatively:
     30 
     31 with 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 
     42 import logging
     43 
     44 from autotest_lib.client.bin import utils
     45 from autotest_lib.client.common_lib.cros.network import interface
     46 
     47 class 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