xref: /aosp_15_r20/external/autotest/client/cros/dhcp_test_base.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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