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 Li""" 7*9c5db199SXin LiBase class for DHCP tests. This class just sets up a little bit of plumbing, 8*9c5db199SXin Lilike a virtual ethernet device with one end that looks like a real ethernet 9*9c5db199SXin Lidevice to shill and a DHCP test server on the end that doesn't look like a real 10*9c5db199SXin Liethernet interface to shill. Child classes should override test_body() with the 11*9c5db199SXin Lilogic of their test. The plumbing of DhcpTestBase is accessible via properties. 12*9c5db199SXin Li""" 13*9c5db199SXin Li 14*9c5db199SXin Lifrom __future__ import absolute_import 15*9c5db199SXin Lifrom __future__ import division 16*9c5db199SXin Lifrom __future__ import print_function 17*9c5db199SXin Li 18*9c5db199SXin Liimport logging 19*9c5db199SXin Liimport six 20*9c5db199SXin Lifrom six.moves import filter 21*9c5db199SXin Lifrom six.moves import range 22*9c5db199SXin Liimport socket 23*9c5db199SXin Liimport struct 24*9c5db199SXin Liimport time 25*9c5db199SXin Liimport traceback 26*9c5db199SXin Li 27*9c5db199SXin Lifrom autotest_lib.client.bin import test 28*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 29*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import virtual_ethernet_pair 30*9c5db199SXin Lifrom autotest_lib.client.cros import dhcp_handling_rule 31*9c5db199SXin Lifrom autotest_lib.client.cros import dhcp_packet 32*9c5db199SXin Lifrom autotest_lib.client.cros import dhcp_test_server 33*9c5db199SXin Lifrom autotest_lib.client.cros.networking import shill_proxy 34*9c5db199SXin Li 35*9c5db199SXin Li 36*9c5db199SXin Li# These are keys that may be used with the DBus dictionary returned from 37*9c5db199SXin Li# DhcpTestBase.get_interface_ipconfig(). 38*9c5db199SXin LiDHCPCD_KEY_NAMESERVERS = 'NameServers' 39*9c5db199SXin LiDHCPCD_KEY_GATEWAY = 'Gateway' 40*9c5db199SXin LiDHCPCD_KEY_BROADCAST_ADDR = 'Broadcast' 41*9c5db199SXin LiDHCPCD_KEY_ADDRESS = 'Address' 42*9c5db199SXin LiDHCPCD_KEY_PREFIX_LENGTH = 'Prefixlen' 43*9c5db199SXin LiDHCPCD_KEY_DOMAIN_NAME = 'DomainName' 44*9c5db199SXin LiDHCPCD_KEY_ACCEPTED_HOSTNAME = 'AcceptedHostname' 45*9c5db199SXin LiDHCPCD_KEY_SEARCH_DOMAIN_LIST = 'SearchDomains' 46*9c5db199SXin Li 47*9c5db199SXin Li# We should be able to complete a DHCP negotiation in this amount of time. 48*9c5db199SXin LiDHCP_NEGOTIATION_TIMEOUT_SECONDS = 10 49*9c5db199SXin Li 50*9c5db199SXin Li# After DHCP completes, an ipconfig should appear shortly after 51*9c5db199SXin LiIPCONFIG_POLL_COUNT = 5 52*9c5db199SXin LiIPCONFIG_POLL_PERIOD_SECONDS = 0.5 53*9c5db199SXin Li 54*9c5db199SXin Liclass DhcpTestBase(test.test): 55*9c5db199SXin Li """Parent class for tests that work verify DHCP behavior.""" 56*9c5db199SXin Li version = 1 57*9c5db199SXin Li 58*9c5db199SXin Li def __init__(self, job, bindir, outputdir, namespace='autotest'): 59*9c5db199SXin Li test.test.__init__(self, job, bindir, outputdir) 60*9c5db199SXin Li self._namespace = namespace 61*9c5db199SXin Li 62*9c5db199SXin Li @staticmethod 63*9c5db199SXin Li def rewrite_ip_suffix(subnet_mask, ip_in_subnet, ip_suffix): 64*9c5db199SXin Li """ 65*9c5db199SXin Li Create a new IPv4 address in a subnet by bitwise and'ing an existing 66*9c5db199SXin Li address |ip_in_subnet| with |subnet_mask| and bitwise or'ing in 67*9c5db199SXin Li |ip_suffix|. For safety, bitwise or the suffix with the complement of 68*9c5db199SXin Li the subnet mask. 69*9c5db199SXin Li 70*9c5db199SXin Li Usage: rewrite_ip_suffix("255.255.255.0", "192.168.1.1", "0.0.0.105") 71*9c5db199SXin Li 72*9c5db199SXin Li The example usage will return "192.168.1.105". 73*9c5db199SXin Li 74*9c5db199SXin Li @param subnet_mask string subnet mask, e.g. "255.255.255.0" 75*9c5db199SXin Li @param ip_in_subnet string an IP address in the desired subnet 76*9c5db199SXin Li @param ip_suffix string suffix desired for new address, e.g. "0.0.0.105" 77*9c5db199SXin Li 78*9c5db199SXin Li @return string IP address on in the same subnet with specified suffix. 79*9c5db199SXin Li 80*9c5db199SXin Li """ 81*9c5db199SXin Li mask = struct.unpack('!I', socket.inet_aton(subnet_mask))[0] 82*9c5db199SXin Li subnet = mask & struct.unpack('!I', socket.inet_aton(ip_in_subnet))[0] 83*9c5db199SXin Li suffix = ~mask & struct.unpack('!I', socket.inet_aton(ip_suffix))[0] 84*9c5db199SXin Li return socket.inet_ntoa(struct.pack('!I', (subnet | suffix))) 85*9c5db199SXin Li 86*9c5db199SXin Li 87*9c5db199SXin Li def get_device(self, interface_name): 88*9c5db199SXin Li """Finds the corresponding Device object for an interface with 89*9c5db199SXin Li the name |interface_name|. 90*9c5db199SXin Li 91*9c5db199SXin Li @param interface_name string The name of the interface to check. 92*9c5db199SXin Li 93*9c5db199SXin Li @return DBus interface object representing the associated device. 94*9c5db199SXin Li 95*9c5db199SXin Li """ 96*9c5db199SXin Li return self.shill_proxy.find_object('Device', 97*9c5db199SXin Li {'Name': interface_name}) 98*9c5db199SXin Li 99*9c5db199SXin Li 100*9c5db199SXin Li def find_ethernet_service(self, interface_name): 101*9c5db199SXin Li """Finds the corresponding service object for an Ethernet interface. 102*9c5db199SXin Li 103*9c5db199SXin Li @param interface_name string The name of the associated interface 104*9c5db199SXin Li 105*9c5db199SXin Li @return Service object representing the associated service. 106*9c5db199SXin Li 107*9c5db199SXin Li """ 108*9c5db199SXin Li device = self.get_device(interface_name) 109*9c5db199SXin Li device_path = shill_proxy.ShillProxy.dbus2primitive(device.object_path) 110*9c5db199SXin Li return self.shill_proxy.find_object('Service', {'Device': device_path}) 111*9c5db199SXin Li 112*9c5db199SXin Li 113*9c5db199SXin Li def get_interface_ipconfig_objects(self, interface_name): 114*9c5db199SXin Li """ 115*9c5db199SXin Li Returns a list of dbus object proxies for |interface_name|. 116*9c5db199SXin Li Returns an empty list if no such interface exists. 117*9c5db199SXin Li 118*9c5db199SXin Li @param interface_name string name of the device to query (e.g., "eth0"). 119*9c5db199SXin Li 120*9c5db199SXin Li @return list of objects representing DBus IPConfig RPC endpoints. 121*9c5db199SXin Li 122*9c5db199SXin Li """ 123*9c5db199SXin Li device = self.get_device(interface_name) 124*9c5db199SXin Li if device is None: 125*9c5db199SXin Li return [] 126*9c5db199SXin Li 127*9c5db199SXin Li if six.PY2: 128*9c5db199SXin Li device_properties = device.GetProperties(utf8_strings=True) 129*9c5db199SXin Li else: 130*9c5db199SXin Li device_properties = device.GetProperties() 131*9c5db199SXin Li proxy = self.shill_proxy 132*9c5db199SXin Li 133*9c5db199SXin Li ipconfig_object = proxy.DBUS_TYPE_IPCONFIG 134*9c5db199SXin Li return list(filter(bool, 135*9c5db199SXin Li [ proxy.get_dbus_object(ipconfig_object, property_path) 136*9c5db199SXin Li for property_path in device_properties['IPConfigs'] ])) 137*9c5db199SXin Li 138*9c5db199SXin Li 139*9c5db199SXin Li def get_interface_ipconfig(self, interface_name): 140*9c5db199SXin Li """ 141*9c5db199SXin Li Returns a dictionary containing settings for an |interface_name| set 142*9c5db199SXin Li via DHCP. Returns None if no such interface or setting bundle on 143*9c5db199SXin Li that interface can be found in shill. 144*9c5db199SXin Li 145*9c5db199SXin Li @param interface_name string name of the device to query (e.g., "eth0"). 146*9c5db199SXin Li 147*9c5db199SXin Li @return dict containing the the properties of the IPConfig stripped 148*9c5db199SXin Li of DBus meta-data or None. 149*9c5db199SXin Li 150*9c5db199SXin Li """ 151*9c5db199SXin Li dhcp_properties = None 152*9c5db199SXin Li for ipconfig in self.get_interface_ipconfig_objects(interface_name): 153*9c5db199SXin Li logging.info('Looking at ipconfig %r', ipconfig) 154*9c5db199SXin Li if six.PY2: 155*9c5db199SXin Li ipconfig_properties = ipconfig.GetProperties(utf8_strings=True) 156*9c5db199SXin Li else: 157*9c5db199SXin Li ipconfig_properties = ipconfig.GetProperties() 158*9c5db199SXin Li if 'Method' not in ipconfig_properties: 159*9c5db199SXin Li logging.info('Found ipconfig object with no method field') 160*9c5db199SXin Li continue 161*9c5db199SXin Li if ipconfig_properties['Method'] != 'dhcp': 162*9c5db199SXin Li logging.info('Found ipconfig object with method != dhcp') 163*9c5db199SXin Li continue 164*9c5db199SXin Li if dhcp_properties != None: 165*9c5db199SXin Li raise error.TestFail('Found multiple ipconfig objects ' 166*9c5db199SXin Li 'with method == dhcp') 167*9c5db199SXin Li dhcp_properties = ipconfig_properties 168*9c5db199SXin Li if dhcp_properties is None: 169*9c5db199SXin Li logging.info('Did not find IPConfig object with method == dhcp') 170*9c5db199SXin Li return None 171*9c5db199SXin Li logging.info('Got raw dhcp config dbus object: %s.', dhcp_properties) 172*9c5db199SXin Li return shill_proxy.ShillProxy.dbus2primitive(dhcp_properties) 173*9c5db199SXin Li 174*9c5db199SXin Li 175*9c5db199SXin Li def run_once(self): 176*9c5db199SXin Li self._server = None 177*9c5db199SXin Li self._server_ip = None 178*9c5db199SXin Li self._ethernet_pair = None 179*9c5db199SXin Li self._server = None 180*9c5db199SXin Li self._shill_proxy = shill_proxy.ShillProxy() 181*9c5db199SXin Li try: 182*9c5db199SXin Li self._ethernet_pair = virtual_ethernet_pair.VirtualEthernetPair( 183*9c5db199SXin Li interface_ns=self._namespace, 184*9c5db199SXin Li peer_interface_name='pseudoethernet0', 185*9c5db199SXin Li peer_interface_ip=None) 186*9c5db199SXin Li self._ethernet_pair.setup() 187*9c5db199SXin Li if not self._ethernet_pair.is_healthy: 188*9c5db199SXin Li raise error.TestFail('Could not create virtual ethernet pair.') 189*9c5db199SXin Li self._server_ip = self._ethernet_pair.interface_ip 190*9c5db199SXin Li self._server = dhcp_test_server.DhcpTestServer( 191*9c5db199SXin Li interface=self._ethernet_pair.interface_name, 192*9c5db199SXin Li ingress_address='', 193*9c5db199SXin Li namespace=self._namespace) 194*9c5db199SXin Li self._server.start() 195*9c5db199SXin Li if not self._server.is_healthy: 196*9c5db199SXin Li raise error.TestFail('Could not start DHCP test server.') 197*9c5db199SXin Li self._subnet_mask = self._ethernet_pair.interface_subnet_mask 198*9c5db199SXin Li self.test_body() 199*9c5db199SXin Li except (error.TestFail, error.TestNAError): 200*9c5db199SXin Li # Pass these through without modification. 201*9c5db199SXin Li raise 202*9c5db199SXin Li except Exception as e: 203*9c5db199SXin Li logging.error('Caught exception: %s.', str(e)) 204*9c5db199SXin Li logging.error('Trace: %s', traceback.format_exc()) 205*9c5db199SXin Li raise error.TestFail('Caught exception: %s.' % str(e)) 206*9c5db199SXin Li finally: 207*9c5db199SXin Li if self._server is not None: 208*9c5db199SXin Li self._server.stop() 209*9c5db199SXin Li if self._ethernet_pair is not None: 210*9c5db199SXin Li self._ethernet_pair.teardown() 211*9c5db199SXin Li 212*9c5db199SXin Li def test_body(self): 213*9c5db199SXin Li """ 214*9c5db199SXin Li Override this method with the body of your test. You may safely assume 215*9c5db199SXin Li that the the properties exposed by DhcpTestBase correctly return 216*9c5db199SXin Li references to the test apparatus. 217*9c5db199SXin Li """ 218*9c5db199SXin Li raise error.TestFail('No test body implemented') 219*9c5db199SXin Li 220*9c5db199SXin Li @property 221*9c5db199SXin Li def server_ip(self): 222*9c5db199SXin Li """ 223*9c5db199SXin Li Return the IP address of the side of the interface that the DHCP test 224*9c5db199SXin Li server is bound to. The server itself is bound the the broadcast 225*9c5db199SXin Li address on the interface. 226*9c5db199SXin Li """ 227*9c5db199SXin Li return self._server_ip 228*9c5db199SXin Li 229*9c5db199SXin Li @property 230*9c5db199SXin Li def server(self): 231*9c5db199SXin Li """ 232*9c5db199SXin Li Returns a reference to the DHCP test server. Use this to add handlers 233*9c5db199SXin Li and run tests. 234*9c5db199SXin Li """ 235*9c5db199SXin Li return self._server 236*9c5db199SXin Li 237*9c5db199SXin Li @property 238*9c5db199SXin Li def ethernet_pair(self): 239*9c5db199SXin Li """ 240*9c5db199SXin Li Returns a reference to the virtual ethernet pair created to run DHCP 241*9c5db199SXin Li tests on. 242*9c5db199SXin Li """ 243*9c5db199SXin Li return self._ethernet_pair 244*9c5db199SXin Li 245*9c5db199SXin Li @property 246*9c5db199SXin Li def shill_proxy(self): 247*9c5db199SXin Li """ 248*9c5db199SXin Li Returns a the shill proxy instance. 249*9c5db199SXin Li """ 250*9c5db199SXin Li return self._shill_proxy 251*9c5db199SXin Li 252*9c5db199SXin Li def negotiate_and_check_lease(self, 253*9c5db199SXin Li dhcp_options, 254*9c5db199SXin Li custom_fields={}, 255*9c5db199SXin Li disable_check=False): 256*9c5db199SXin Li """ 257*9c5db199SXin Li Perform DHCP lease negotiation, and ensure that the resulting 258*9c5db199SXin Li ipconfig matches the DHCP options provided to the server. 259*9c5db199SXin Li 260*9c5db199SXin Li @param dhcp_options dict of properties the DHCP server should provide. 261*9c5db199SXin Li @param custom_fields dict of custom DHCP parameters to add to server. 262*9c5db199SXin Li @param disable_check bool whether to perform IPConfig parameter 263*9c5db199SXin Li checking. 264*9c5db199SXin Li 265*9c5db199SXin Li """ 266*9c5db199SXin Li if dhcp_packet.OPTION_REQUESTED_IP not in dhcp_options: 267*9c5db199SXin Li raise error.TestFail('You must specify OPTION_REQUESTED_IP to ' 268*9c5db199SXin Li 'negotiate a DHCP lease') 269*9c5db199SXin Li intended_ip = dhcp_options[dhcp_packet.OPTION_REQUESTED_IP] 270*9c5db199SXin Li # Build up the handling rules for the server and start the test. 271*9c5db199SXin Li rules = [] 272*9c5db199SXin Li rules.append(dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 273*9c5db199SXin Li intended_ip, 274*9c5db199SXin Li self.server_ip, 275*9c5db199SXin Li dhcp_options, 276*9c5db199SXin Li custom_fields)) 277*9c5db199SXin Li rules.append(dhcp_handling_rule.DhcpHandlingRule_RespondToRequest( 278*9c5db199SXin Li intended_ip, 279*9c5db199SXin Li self.server_ip, 280*9c5db199SXin Li dhcp_options, 281*9c5db199SXin Li custom_fields)) 282*9c5db199SXin Li rules[-1].is_final_handler = True 283*9c5db199SXin Li self.server.start_test(rules, DHCP_NEGOTIATION_TIMEOUT_SECONDS) 284*9c5db199SXin Li logging.info('Server is negotiating new lease with options: %s', 285*9c5db199SXin Li dhcp_options) 286*9c5db199SXin Li self.server.wait_for_test_to_finish() 287*9c5db199SXin Li if not self.server.last_test_passed: 288*9c5db199SXin Li raise error.TestFail( 289*9c5db199SXin Li 'Test failed: active rule is %s' % self.server.current_rule) 290*9c5db199SXin Li 291*9c5db199SXin Li if disable_check: 292*9c5db199SXin Li logging.info('Skipping check of negotiated DHCP lease parameters.') 293*9c5db199SXin Li else: 294*9c5db199SXin Li self.wait_for_dhcp_propagation() 295*9c5db199SXin Li self.check_dhcp_config(dhcp_options) 296*9c5db199SXin Li 297*9c5db199SXin Li def wait_for_dhcp_propagation(self): 298*9c5db199SXin Li """ 299*9c5db199SXin Li Wait for configuration to propagate over dbus to shill. 300*9c5db199SXin Li TODO(wiley) Make this event based. This is pretty sloppy. 301*9c5db199SXin Li """ 302*9c5db199SXin Li time.sleep(0.1) 303*9c5db199SXin Li 304*9c5db199SXin Li def check_dhcp_config(self, dhcp_options): 305*9c5db199SXin Li """ 306*9c5db199SXin Li Compare the DHCP ipconfig with DHCP lease parameters to ensure 307*9c5db199SXin Li that the DUT attained the correct values. 308*9c5db199SXin Li 309*9c5db199SXin Li @param dhcp_options dict of properties the DHCP server provided. 310*9c5db199SXin Li 311*9c5db199SXin Li """ 312*9c5db199SXin Li # The config is what the interface was actually configured with, as 313*9c5db199SXin Li # opposed to dhcp_options, which is what the server expected it be 314*9c5db199SXin Li # configured with. 315*9c5db199SXin Li for attempt in range(IPCONFIG_POLL_COUNT): 316*9c5db199SXin Li dhcp_config = self.get_interface_ipconfig( 317*9c5db199SXin Li self.ethernet_pair.peer_interface_name) 318*9c5db199SXin Li if dhcp_config is not None: 319*9c5db199SXin Li break 320*9c5db199SXin Li time.sleep(IPCONFIG_POLL_PERIOD_SECONDS) 321*9c5db199SXin Li else: 322*9c5db199SXin Li raise error.TestFail('Failed to retrieve DHCP ipconfig object ' 323*9c5db199SXin Li 'from shill.') 324*9c5db199SXin Li 325*9c5db199SXin Li logging.debug('Got DHCP config: %s', str(dhcp_config)) 326*9c5db199SXin Li expected_address = dhcp_options.get(dhcp_packet.OPTION_REQUESTED_IP) 327*9c5db199SXin Li configured_address = dhcp_config.get(DHCPCD_KEY_ADDRESS) 328*9c5db199SXin Li if expected_address != configured_address: 329*9c5db199SXin Li raise error.TestFail('Interface configured with IP address not ' 330*9c5db199SXin Li 'granted by the DHCP server after DHCP ' 331*9c5db199SXin Li 'negotiation. Expected %s but got %s.' % 332*9c5db199SXin Li (expected_address, configured_address)) 333*9c5db199SXin Li 334*9c5db199SXin Li # While DNS related settings only propagate to the system when the 335*9c5db199SXin Li # service is marked as the default service, we can still check the 336*9c5db199SXin Li # IP address on the interface, since that is set immediately. 337*9c5db199SXin Li interface_address = self.ethernet_pair.peer_interface_ip 338*9c5db199SXin Li if expected_address != interface_address: 339*9c5db199SXin Li raise error.TestFail('shill somehow knew about the proper DHCP ' 340*9c5db199SXin Li 'assigned address: %s, but configured the ' 341*9c5db199SXin Li 'interface with something completely ' 342*9c5db199SXin Li 'different: %s.' % 343*9c5db199SXin Li (expected_address, interface_address)) 344*9c5db199SXin Li 345*9c5db199SXin Li expected_dns_servers = dhcp_options.get(dhcp_packet.OPTION_DNS_SERVERS) 346*9c5db199SXin Li configured_dns_servers = dhcp_config.get(DHCPCD_KEY_NAMESERVERS) 347*9c5db199SXin Li if (expected_dns_servers is not None and 348*9c5db199SXin Li expected_dns_servers != configured_dns_servers): 349*9c5db199SXin Li raise error.TestFail('Expected to be configured with DNS server ' 350*9c5db199SXin Li 'list %s, but was configured with %s ' 351*9c5db199SXin Li 'instead.' % (expected_dns_servers, 352*9c5db199SXin Li configured_dns_servers)) 353*9c5db199SXin Li 354*9c5db199SXin Li expected_domain_name = dhcp_options.get(dhcp_packet.OPTION_DOMAIN_NAME) 355*9c5db199SXin Li configured_domain_name = dhcp_config.get(DHCPCD_KEY_DOMAIN_NAME) 356*9c5db199SXin Li if (expected_domain_name is not None and 357*9c5db199SXin Li expected_domain_name != configured_domain_name): 358*9c5db199SXin Li raise error.TestFail('Expected to be configured with domain ' 359*9c5db199SXin Li 'name %s, but got %s instead.' % 360*9c5db199SXin Li (expected_domain_name, configured_domain_name)) 361*9c5db199SXin Li 362*9c5db199SXin Li expected_host_name = dhcp_options.get(dhcp_packet.OPTION_HOST_NAME) 363*9c5db199SXin Li configured_host_name = dhcp_config.get(DHCPCD_KEY_ACCEPTED_HOSTNAME) 364*9c5db199SXin Li if (expected_host_name is not None and 365*9c5db199SXin Li expected_host_name != configured_host_name): 366*9c5db199SXin Li raise error.TestFail('Expected to be configured with host ' 367*9c5db199SXin Li 'name %s, but got %s instead.' % 368*9c5db199SXin Li (expected_host_name, configured_host_name)) 369*9c5db199SXin Li 370*9c5db199SXin Li expected_search_list = dhcp_options.get( 371*9c5db199SXin Li dhcp_packet.OPTION_DNS_DOMAIN_SEARCH_LIST) 372*9c5db199SXin Li configured_search_list = dhcp_config.get(DHCPCD_KEY_SEARCH_DOMAIN_LIST) 373*9c5db199SXin Li if (expected_search_list is not None and 374*9c5db199SXin Li expected_search_list != configured_search_list): 375*9c5db199SXin Li raise error.TestFail('Expected to be configured with domain ' 376*9c5db199SXin Li 'search list %s, but got %s instead.' % 377*9c5db199SXin Li (expected_search_list, configured_search_list)) 378*9c5db199SXin Li 379*9c5db199SXin Li expected_routers = dhcp_options.get(dhcp_packet.OPTION_ROUTERS) 380*9c5db199SXin Li if (not expected_routers and 381*9c5db199SXin Li dhcp_options.get(dhcp_packet.OPTION_CLASSLESS_STATIC_ROUTES)): 382*9c5db199SXin Li classless_static_routes = dhcp_options[ 383*9c5db199SXin Li dhcp_packet.OPTION_CLASSLESS_STATIC_ROUTES] 384*9c5db199SXin Li for prefix, destination, gateway in classless_static_routes: 385*9c5db199SXin Li if not prefix: 386*9c5db199SXin Li logging.info('Using %s as the default gateway', gateway) 387*9c5db199SXin Li expected_routers = [ gateway ] 388*9c5db199SXin Li break 389*9c5db199SXin Li configured_router = dhcp_config.get(DHCPCD_KEY_GATEWAY) 390*9c5db199SXin Li if expected_routers and expected_routers[0] != configured_router: 391*9c5db199SXin Li raise error.TestFail('Expected to be configured with gateway %s, ' 392*9c5db199SXin Li 'but got %s instead.' % 393*9c5db199SXin Li (expected_routers[0], configured_router)) 394*9c5db199SXin Li 395*9c5db199SXin Li self.server.wait_for_test_to_finish() 396*9c5db199SXin Li if not self.server.last_test_passed: 397*9c5db199SXin Li raise error.TestFail('Test server didn\'t get all the messages it ' 398*9c5db199SXin Li 'was told to expect for renewal.') 399