# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """ VirtualEthernetPair provides methods for setting up and tearing down a virtual ethernet interface for use in tests. You will probably need to be root on test devices to use this class. The constructor allows you to specify your IP's to assign to both ends of the pair, however, if you wish to leave the interface unconfigured, simply pass None. You may also specify the subnet of your ip addresses. Failing to do so leaves them with default in ifconfig. Example usage: vif = virtual_ethernet_pair.VirtualEthernetPair(interface_name="master", peer_interface_name="peer", interface_ip="10.9.8.1/24", peer_interface_ip=None) vif.setup() if not vif.is_healthy: # bad things happened while creating the interface # ... abort gracefully interface_name = vif.interface_name peer_interface_name = vif.peer_interface_name #... do things with your interface # You must call this if you want to leave the system in a good state. vif.teardown() Alternatively: with virtual_ethernet_pair.VirtualEthernetPair(...) as vif: if not vif.is_healthy: # bad things happened while creating the interface # ... abort gracefully interface_name = vif.interface_name peer_interface_name = vif.peer_interface_name #... do things with your interface """ import logging from autotest_lib.client.bin import utils from autotest_lib.client.common_lib.cros.network import interface class VirtualEthernetPair(object): """ Class for configuring virtual ethernet device pair. """ def __init__(self, interface_name='veth_master', peer_interface_name='veth_slave', interface_ip='10.9.8.1/24', peer_interface_ip='10.9.8.2/24', interface_ipv6=None, peer_interface_ipv6=None, ignore_shutdown_errors=False, host=None): """ Construct a object managing a virtual ethernet pair. One end of the interface will be called |interface_name|, and the peer end |peer_interface_name|. You may get the interface names later with VirtualEthernetPair.get_[peer_]interface_name(). The ends of the interface are manually configured with the given IPv4 address strings (like "10.9.8.2/24"). You may skip the IP configuration by passing None as the address for either interface. """ super(VirtualEthernetPair, self).__init__() self._is_healthy = True self._interface_name = interface_name self._peer_interface_name = peer_interface_name self._interface_ip = interface_ip self._peer_interface_ip = peer_interface_ip self._interface_ipv6 = interface_ipv6 self._peer_interface_ipv6 = peer_interface_ipv6 self._ignore_shutdown_errors = ignore_shutdown_errors self._run = utils.run self._host = host if host is not None: self._run = host.run def setup(self): """ Installs a virtual ethernet interface and configures one side with an IP address. First does some sanity checking and tries to remove an existing interface by the same name, and logs messages on failures. """ self._is_healthy = False if self._either_interface_exists(): logging.warning('At least one test interface already existed.' ' Attempting to remove.') self._remove_test_interface() if self._either_interface_exists(): logging.error('Failed to remove unexpected test ' 'interface. Aborting.') return self._create_test_interface() if not self._interface_exists(self._interface_name): logging.error('Failed to create master test interface.') return if not self._interface_exists(self._peer_interface_name): logging.error('Failed to create peer test interface.') return # Unless you tell the firewall about the interface, you're not going to # get any IP traffic through. Since this is basically a loopback # device, just allow all traffic. for name in (self._interface_name, self._peer_interface_name): status = self._run('iptables -I INPUT -i %s -j ACCEPT' % name, ignore_status=True) if status.exit_status != 0: logging.error('iptables rule addition failed for interface %s', name) self._is_healthy = True def teardown(self): """ Removes the interface installed by VirtualEthernetPair.setup(), with some simple sanity checks that print warnings when either the interface isn't there or fails to be removed. """ for name in (self._interface_name, self._peer_interface_name): self._run('iptables -D INPUT -i %s -j ACCEPT' % name, ignore_status=True) if not self._either_interface_exists(): logging.warning('VirtualEthernetPair.teardown() called, ' 'but no interface was found.') return self._remove_test_interface() if self._either_interface_exists(): logging.error('Failed to destroy test interface.') @property def is_healthy(self): """@return True if virtual ethernet pair is configured.""" return self._is_healthy @property def interface_name(self): """@return string name of the interface.""" return self._interface_name @property def peer_interface_name(self): """@return string name of the peer interface.""" return self._peer_interface_name @property def interface_ip(self): """@return string IPv4 address of the interface.""" return interface.Interface(self.interface_name).ipv4_address @property def peer_interface_ip(self): """@return string IPv4 address of the peer interface.""" return interface.Interface(self.peer_interface_name).ipv4_address @property def interface_subnet_mask(self): """@return string IPv4 subnet mask of the interface.""" return interface.Interface(self.interface_name).ipv4_subnet_mask @property def interface_prefix(self): """@return int IPv4 prefix length.""" return interface.Interface(self.interface_name).ipv4_prefix @property def peer_interface_subnet_mask(self): """@return string IPv4 subnet mask of the peer interface.""" return interface.Interface(self.peer_interface_name).ipv4_subnet_mask @property def interface_mac(self): """@return string MAC address of the interface.""" return interface.Interface(self.interface_name).mac_address @property def peer_interface_mac(self): """@return string MAC address of the peer interface.""" return interface.Interface(self._peer_interface_name).mac_address def __enter__(self): self.setup() return self def __exit__(self, exc_type, exc_value, traceback): self.teardown() def _interface_exists(self, interface_name): """ Returns True iff we found an interface with name |interface_name|. """ return interface.Interface(interface_name, host=self._host).exists def _either_interface_exists(self): return (self._interface_exists(self._interface_name) or self._interface_exists(self._peer_interface_name)) def _remove_test_interface(self): """ Remove the virtual ethernet device installed by _create_test_interface(). """ self._run('ip link set %s down' % self._interface_name, ignore_status=self._ignore_shutdown_errors) self._run('ip link set %s down' % self._peer_interface_name, ignore_status=self._ignore_shutdown_errors) self._run('ip link delete %s >/dev/null 2>&1' % self._interface_name, ignore_status=self._ignore_shutdown_errors) # Under most normal circumstances a successful deletion of # |_interface_name| should also remove |_peer_interface_name|, # but if we elected to ignore failures above, that may not be # the case. self._run('ip link delete %s >/dev/null 2>&1' % self._peer_interface_name, ignore_status=True) def _create_test_interface(self): """ Set up a virtual ethernet device and configure the host side with a fake IP address. """ self._run('ip link add name %s ' 'type veth peer name %s >/dev/null 2>&1' % (self._interface_name, self._peer_interface_name)) self._run('ip link set %s up' % self._interface_name) self._run('ip link set %s up' % self._peer_interface_name) if self._interface_ip is not None: self._run('ip addr add %s dev %s' % (self._interface_ip, self._interface_name)) if self._peer_interface_ip is not None: self._run('ip addr add %s dev %s' % (self._peer_interface_ip, self._peer_interface_name)) if self._interface_ipv6 is not None: self._run('ip -6 addr add %s dev %s' % (self._interface_ipv6, self._interface_name)) if self._peer_interface_ipv6 is not None: self._run('ip -6 addr add %s dev %s' % (self._peer_interface_ipv6, self._peer_interface_name))