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