Add automated test for FQDN

Some modems provide a Fully Qualified Domain Name option in their
ACK response.  It's been discovered that some versions of dhcpcd
can crash when trying to convert this to a D-Bus event.  This
test ensures that after a fix is in place that this problem does
not regress.

BUG=chromium:500112
TEST=Run this test

Change-Id: I82db1283bc2b289148b891bbeaa0eb276629e362
Reviewed-on: https://chromium-review.googlesource.com/280380
Reviewed-by: Rebecca Silberstein <silberst@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
diff --git a/client/cros/dhcp_packet.py b/client/cros/dhcp_packet.py
index 273d9af..0b591c0 100644
--- a/client/cros/dhcp_packet.py
+++ b/client/cros/dhcp_packet.py
@@ -345,6 +345,7 @@
 OPTION_CLIENT_ID = RawOption("client_id", 61)
 OPTION_TFTP_SERVER_NAME = RawOption("tftp_server_name", 66)
 OPTION_BOOTFILE_NAME = RawOption("bootfile_name", 67)
+OPTION_FULLY_QUALIFIED_DOMAIN_NAME = RawOption("fqdn", 81)
 OPTION_DNS_DOMAIN_SEARCH_LIST = DomainListOption("domain_search_list", 119)
 OPTION_CLASSLESS_STATIC_ROUTES = ClasslessStaticRoutesOption(
         "classless_static_routes", 121)
@@ -477,6 +478,7 @@
         OPTION_CLIENT_ID,
         OPTION_TFTP_SERVER_NAME,
         OPTION_BOOTFILE_NAME,
+        OPTION_FULLY_QUALIFIED_DOMAIN_NAME,
         OPTION_DNS_DOMAIN_SEARCH_LIST,
         OPTION_CLASSLESS_STATIC_ROUTES,
         OPTION_WEB_PROXY_AUTO_DISCOVERY,
diff --git a/client/site_tests/network_DhcpFQDN/control b/client/site_tests/network_DhcpFQDN/control
new file mode 100644
index 0000000..4d6ef1f
--- /dev/null
+++ b/client/site_tests/network_DhcpFQDN/control
@@ -0,0 +1,19 @@
+# Copyright (c) 2015 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.
+
+AUTHOR = 'pstew, quiche, wiley'
+NAME = 'network_DhcpFQDN'
+TIME = 'SHORT'
+TEST_TYPE = 'client'
+
+DOC = """
+  Tests that DHCP option 81 (Fully Qualified Domain Name) is successfully
+  accepted.  It was observed that this caused a crash in dhcpcd in some
+  releases.  This test ensures that the DHCP client does not regress to
+  repeat this failure by ensuring that the DHCP client succeeds long enough
+  to provide an IPConfig back to shill.
+
+"""
+
+job.run_test('network_DhcpFQDN')
diff --git a/client/site_tests/network_DhcpFQDN/network_DhcpFQDN.py b/client/site_tests/network_DhcpFQDN/network_DhcpFQDN.py
new file mode 100644
index 0000000..65a70f7
--- /dev/null
+++ b/client/site_tests/network_DhcpFQDN/network_DhcpFQDN.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2015 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.
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.cros.network import interface
+from autotest_lib.client.cros import dhcp_handling_rule
+from autotest_lib.client.cros import dhcp_packet
+from autotest_lib.client.cros import dhcp_test_base
+from autotest_lib.client.cros.networking import shill_proxy
+
+# Length of time the lease from the DHCP server is valid.
+LEASE_TIME_SECONDS = 60
+# We'll fill in the subnet and give this address to the client.
+INTENDED_IP_SUFFIX = "0.0.0.101"
+# We should be able to complete a DHCP negotiation in this amount of time.
+DHCP_NEGOTIATION_TIMEOUT_SECONDS = 10
+
+class network_DhcpFQDN(dhcp_test_base.DhcpTestBase):
+    """Test implemenation of client completing negotiation with FQDN flag."""
+
+    def test_body(self):
+        """Main body of the test."""
+        subnet_mask = self.ethernet_pair.interface_subnet_mask
+        intended_ip = dhcp_test_base.DhcpTestBase.rewrite_ip_suffix(
+                subnet_mask,
+                self.server_ip,
+                INTENDED_IP_SUFFIX)
+        # It doesn't matter what is contained in this option value, except that
+        # the DHCP client does not crash decoding it or passing its
+        # interpretation of it back to shill.
+        fqdn_option = '\x03\xff\x00'
+        # This is the pool of information the server will give out to the client
+        # upon request.
+        dhcp_options = {
+                dhcp_packet.OPTION_SERVER_ID : self.server_ip,
+                dhcp_packet.OPTION_SUBNET_MASK : subnet_mask,
+                dhcp_packet.OPTION_IP_LEASE_TIME : LEASE_TIME_SECONDS,
+                dhcp_packet.OPTION_REQUESTED_IP : intended_ip,
+                dhcp_packet.OPTION_FULLY_QUALIFIED_DOMAIN_NAME : fqdn_option
+                }
+        rules = [
+                dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery(
+                        intended_ip, self.server_ip, dhcp_options, {}),
+                dhcp_handling_rule.DhcpHandlingRule_RespondToRequest(
+                        intended_ip, self.server_ip, dhcp_options, {})
+                ]
+        rules[-1].is_final_handler = True
+
+        # In some DHCP server implementations, the FQDN option is provided in
+        # the DHCP ACK response without the client requesting it.
+        rules[-1].force_reply_options = [
+                dhcp_packet.OPTION_FULLY_QUALIFIED_DOMAIN_NAME ]
+
+        self.server.start_test(rules, DHCP_NEGOTIATION_TIMEOUT_SECONDS)
+        self.server.wait_for_test_to_finish()
+        if not self.server.last_test_passed:
+            raise error.TestFail('Test server didn\'t get all the messages it '
+                                 'was told to expect during negotiation.')
+
+        # This test passes if the DHCP client lives long enough to send
+        # the network configuration to shill.
+        self.wait_for_dhcp_propagation()
+        self.check_dhcp_config(dhcp_options)