264 lines
10 KiB
Python
264 lines
10 KiB
Python
# 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.
|
|
|
|
# TODO b:169251326 terms below are set outside of this codebase
|
|
# and should be updated when possible. ("master" -> "main", "slave" -> "node")
|
|
|
|
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 main 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 -w -I INPUT -i %s -j ACCEPT' % name,
|
|
ignore_status=True)
|
|
if status.exit_status != 0:
|
|
logging.error('iptables rule addition failed for interface %s: '
|
|
'%s', name, status.stderr)
|
|
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 -w -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))
|