1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Liimport logging 7*9c5db199SXin Liimport os 8*9c5db199SXin Liimport re 9*9c5db199SXin Liimport time 10*9c5db199SXin Li 11*9c5db199SXin Lifrom autotest_lib.client.bin import local_host 12*9c5db199SXin Lifrom autotest_lib.client.bin import utils 13*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 14*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.network import interface 15*9c5db199SXin Li 16*9c5db199SXin Li# Flag file used to tell backchannel script it's okay to run. 17*9c5db199SXin LiBACKCHANNEL_FILE = '/mnt/stateful_partition/etc/enable_backchannel_network' 18*9c5db199SXin Li# Backchannel interface name. 19*9c5db199SXin LiBACKCHANNEL_IFACE_NAME = 'eth_test' 20*9c5db199SXin Li# Script that handles backchannel heavy lifting. 21*9c5db199SXin LiBACKCHANNEL_SCRIPT = '/usr/local/lib/flimflam/test/backchannel' 22*9c5db199SXin Li 23*9c5db199SXin Li 24*9c5db199SXin Liclass Backchannel(object): 25*9c5db199SXin Li """Wrap backchannel in a context manager so it can be used with with. 26*9c5db199SXin Li 27*9c5db199SXin Li Example usage: 28*9c5db199SXin Li with backchannel.Backchannel(): 29*9c5db199SXin Li block 30*9c5db199SXin Li The backchannel will be torn down whether or not 'block' throws. 31*9c5db199SXin Li """ 32*9c5db199SXin Li 33*9c5db199SXin Li def __init__(self, host=None, *args, **kwargs): 34*9c5db199SXin Li self.args = args 35*9c5db199SXin Li self.kwargs = kwargs 36*9c5db199SXin Li self.gateway = None 37*9c5db199SXin Li self.interface = None 38*9c5db199SXin Li if host is not None: 39*9c5db199SXin Li self.host = host 40*9c5db199SXin Li else: 41*9c5db199SXin Li self.host = local_host.LocalHost() 42*9c5db199SXin Li self._run = self.host.run 43*9c5db199SXin Li 44*9c5db199SXin Li def __enter__(self): 45*9c5db199SXin Li self.setup(*self.args, **self.kwargs) 46*9c5db199SXin Li return self 47*9c5db199SXin Li 48*9c5db199SXin Li def __exit__(self, exception, value, traceback): 49*9c5db199SXin Li self.teardown() 50*9c5db199SXin Li return False 51*9c5db199SXin Li 52*9c5db199SXin Li def setup(self, create_ssh_routes=True): 53*9c5db199SXin Li """ 54*9c5db199SXin Li Enables the backchannel interface. 55*9c5db199SXin Li 56*9c5db199SXin Li @param create_ssh_routes: If True set up routes so that all existing 57*9c5db199SXin Li SSH sessions will remain open. 58*9c5db199SXin Li 59*9c5db199SXin Li @returns True if the backchannel is already set up, or was set up by 60*9c5db199SXin Li this call, otherwise False. 61*9c5db199SXin Li 62*9c5db199SXin Li """ 63*9c5db199SXin Li 64*9c5db199SXin Li # If the backchannel interface is already up there's nothing 65*9c5db199SXin Li # for us to do. 66*9c5db199SXin Li if self._is_test_iface_running(): 67*9c5db199SXin Li return True 68*9c5db199SXin Li 69*9c5db199SXin Li # Retrieve the gateway for the default route. 70*9c5db199SXin Li try: 71*9c5db199SXin Li # Poll here until we have route information. 72*9c5db199SXin Li # If shill was recently started, it will take some time before 73*9c5db199SXin Li # DHCP gives us an address. 74*9c5db199SXin Li line = utils.poll_for_condition( 75*9c5db199SXin Li lambda: self._get_default_route(), 76*9c5db199SXin Li exception=utils.TimeoutError( 77*9c5db199SXin Li 'Timed out waiting for route information'), 78*9c5db199SXin Li timeout=30) 79*9c5db199SXin Li self.gateway, self.interface = line.strip().split(' ') 80*9c5db199SXin Li 81*9c5db199SXin Li # Retrieve list of open ssh sessions so we can reopen 82*9c5db199SXin Li # routes afterward. 83*9c5db199SXin Li if create_ssh_routes: 84*9c5db199SXin Li out = self._run( 85*9c5db199SXin Li "netstat -tanp | grep :22 | " 86*9c5db199SXin Li "grep ESTABLISHED | awk '{print $5}'").stdout 87*9c5db199SXin Li # Extract IP from IP:PORT listing. Uses set to remove 88*9c5db199SXin Li # duplicates. 89*9c5db199SXin Li open_ssh = list(set(item.strip().split(':')[0] for item in 90*9c5db199SXin Li out.split('\n') if item.strip())) 91*9c5db199SXin Li 92*9c5db199SXin Li # Build a command that will set up the test interface and add 93*9c5db199SXin Li # ssh routes in one shot. This is necessary since we'll lose 94*9c5db199SXin Li # connectivity to a remote host between these steps. 95*9c5db199SXin Li cmd = '%s setup %s' % (BACKCHANNEL_SCRIPT, self.interface) 96*9c5db199SXin Li if create_ssh_routes: 97*9c5db199SXin Li for ip in open_ssh: 98*9c5db199SXin Li # Add route using the pre-backchannel gateway. 99*9c5db199SXin Li cmd += '&& %s reach %s %s' % (BACKCHANNEL_SCRIPT, ip, 100*9c5db199SXin Li self.gateway) 101*9c5db199SXin Li 102*9c5db199SXin Li self._run(cmd) 103*9c5db199SXin Li 104*9c5db199SXin Li # Make sure we have a route to the gateway before continuing. 105*9c5db199SXin Li logging.info('Waiting for route to gateway %s', self.gateway) 106*9c5db199SXin Li utils.poll_for_condition( 107*9c5db199SXin Li lambda: self._is_route_ready(), 108*9c5db199SXin Li exception=utils.TimeoutError('Timed out waiting for route'), 109*9c5db199SXin Li timeout=30) 110*9c5db199SXin Li except Exception as e: 111*9c5db199SXin Li logging.error(e) 112*9c5db199SXin Li return False 113*9c5db199SXin Li finally: 114*9c5db199SXin Li # Remove backchannel file flag so system reverts to normal 115*9c5db199SXin Li # on reboot. 116*9c5db199SXin Li if os.path.isfile(BACKCHANNEL_FILE): 117*9c5db199SXin Li os.remove(BACKCHANNEL_FILE) 118*9c5db199SXin Li 119*9c5db199SXin Li return True 120*9c5db199SXin Li 121*9c5db199SXin Li def teardown(self): 122*9c5db199SXin Li """Tears down the backchannel.""" 123*9c5db199SXin Li if self.interface: 124*9c5db199SXin Li self._run('%s teardown %s' % (BACKCHANNEL_SCRIPT, self.interface)) 125*9c5db199SXin Li 126*9c5db199SXin Li # Hack around broken Asix network adaptors that may flake out when we 127*9c5db199SXin Li # bring them up and down (crbug.com/349264). 128*9c5db199SXin Li # TODO(thieule): Remove this when the adaptor/driver is fixed 129*9c5db199SXin Li # (crbug.com/350172). 130*9c5db199SXin Li try: 131*9c5db199SXin Li if self.gateway: 132*9c5db199SXin Li logging.info('Waiting for route restore to gateway %s', 133*9c5db199SXin Li self.gateway) 134*9c5db199SXin Li utils.poll_for_condition( 135*9c5db199SXin Li lambda: self._is_route_ready(), 136*9c5db199SXin Li exception=utils.TimeoutError( 137*9c5db199SXin Li 'Timed out waiting for route'), 138*9c5db199SXin Li timeout=30) 139*9c5db199SXin Li except utils.TimeoutError: 140*9c5db199SXin Li if self.host is None: 141*9c5db199SXin Li self._reset_usb_ethernet_device() 142*9c5db199SXin Li 143*9c5db199SXin Li 144*9c5db199SXin Li def is_using_ethernet(self): 145*9c5db199SXin Li """ 146*9c5db199SXin Li Checks to see if the backchannel is using an ethernet device. 147*9c5db199SXin Li 148*9c5db199SXin Li @returns True if the backchannel is using an ethernet device. 149*9c5db199SXin Li 150*9c5db199SXin Li """ 151*9c5db199SXin Li # Check the port type reported by ethtool. 152*9c5db199SXin Li result = self._run('ethtool %s' % BACKCHANNEL_IFACE_NAME, 153*9c5db199SXin Li ignore_status=True) 154*9c5db199SXin Li if (result.exit_status == 0 and 155*9c5db199SXin Li re.search('Port: (TP|Twisted Pair|MII|Media Independent Interface)', 156*9c5db199SXin Li result.stdout)): 157*9c5db199SXin Li return True 158*9c5db199SXin Li 159*9c5db199SXin Li # ethtool doesn't report the port type for some Ethernet adapters. 160*9c5db199SXin Li # Fall back to check against a list of known Ethernet adapters: 161*9c5db199SXin Li # 162*9c5db199SXin Li # 13b1:0041 - Linksys USB3GIG USB 3.0 Gigabit Ethernet Adapter 163*9c5db199SXin Li properties = self._get_udev_properties(BACKCHANNEL_IFACE_NAME) 164*9c5db199SXin Li # Depending on the udev version, ID_VENDOR_ID/ID_MODEL_ID may or may 165*9c5db199SXin Li # not have the 0x prefix, so we convert them to an integer value first. 166*9c5db199SXin Li bus = properties.get('ID_BUS', 'unknown').lower() 167*9c5db199SXin Li vendor_id = int(properties.get('ID_VENDOR_ID', '0000'), 16) 168*9c5db199SXin Li model_id = int(properties.get('ID_MODEL_ID', '0000'), 16) 169*9c5db199SXin Li device_id = '%s:%04x:%04x' % (bus, vendor_id, model_id) 170*9c5db199SXin Li if device_id in ['usb:13b1:0041']: 171*9c5db199SXin Li return True 172*9c5db199SXin Li 173*9c5db199SXin Li return False 174*9c5db199SXin Li 175*9c5db199SXin Li 176*9c5db199SXin Li def _get_udev_properties(self, iface): 177*9c5db199SXin Li properties = {} 178*9c5db199SXin Li result = self._run('udevadm info -q property /sys/class/net/%s' % iface, 179*9c5db199SXin Li ignore_status=True) 180*9c5db199SXin Li if result.exit_status == 0: 181*9c5db199SXin Li for line in result.stdout.splitlines(): 182*9c5db199SXin Li key, value = line.split('=', 1) 183*9c5db199SXin Li properties[key] = value 184*9c5db199SXin Li 185*9c5db199SXin Li return properties 186*9c5db199SXin Li 187*9c5db199SXin Li 188*9c5db199SXin Li def _reset_usb_ethernet_device(self): 189*9c5db199SXin Li try: 190*9c5db199SXin Li # Use the absolute path to the USB device instead of accessing it 191*9c5db199SXin Li # via the path with the interface name because once we 192*9c5db199SXin Li # deauthorize the USB device, the interface name will be gone. 193*9c5db199SXin Li usb_authorized_path = os.path.realpath( 194*9c5db199SXin Li '/sys/class/net/%s/device/../authorized' % self.interface) 195*9c5db199SXin Li logging.info('Reset ethernet device at %s', usb_authorized_path) 196*9c5db199SXin Li utils.system('echo 0 > %s' % usb_authorized_path) 197*9c5db199SXin Li time.sleep(10) 198*9c5db199SXin Li utils.system('echo 1 > %s' % usb_authorized_path) 199*9c5db199SXin Li except error.CmdError: 200*9c5db199SXin Li pass 201*9c5db199SXin Li 202*9c5db199SXin Li 203*9c5db199SXin Li def _get_default_route(self): 204*9c5db199SXin Li """Retrieves default route information.""" 205*9c5db199SXin Li # Get default routes and parse out the gateway and interface. 206*9c5db199SXin Li cmd = "ip -4 route show table 0 | awk '/^default via/ { print $3, $5 }'" 207*9c5db199SXin Li return self._run(cmd).stdout.split('\n')[0] 208*9c5db199SXin Li 209*9c5db199SXin Li 210*9c5db199SXin Li def _is_test_iface_running(self): 211*9c5db199SXin Li """Checks whether the test interface is running.""" 212*9c5db199SXin Li return interface.Interface(BACKCHANNEL_IFACE_NAME).is_link_operational() 213*9c5db199SXin Li 214*9c5db199SXin Li 215*9c5db199SXin Li def _is_route_ready(self): 216*9c5db199SXin Li """Checks for a route to the specified destination.""" 217*9c5db199SXin Li dest = self.gateway 218*9c5db199SXin Li result = self._run('ping -c 1 %s' % dest, ignore_status=True) 219*9c5db199SXin Li if result.exit_status: 220*9c5db199SXin Li logging.warning('Route to %s is not ready.', dest) 221*9c5db199SXin Li return False 222*9c5db199SXin Li logging.info('Route to %s is ready.', dest) 223*9c5db199SXin Li return True 224