1# Copyright (c) 2014 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 5from autotest_lib.client.common_lib import error 6from autotest_lib.client.cros import dhcp_handling_rule 7from autotest_lib.client.cros import dhcp_packet 8from autotest_lib.client.cros import dhcp_test_base 9 10# Length of time the lease from the DHCP server is valid. 11LEASE_TIME_SECONDS = 3600 12# We'll fill in the subnet and give this address to the client. 13INTENDED_IP_SUFFIX = "0.0.0.101" 14 15class network_DhcpNak(dhcp_test_base.DhcpTestBase): 16 """ 17 Tests a DHCP client's handling of NAK messages. 18 19 Negotiates a lease, and then tests how the DHCP client processes 20 NAKs in two scenarios. In the first scenario, the DHCP client 21 is started anew, but with the cached lease on disk. In the 22 second scenario, an already-running DHCP client is asked to 23 renew its lease. 24 25 In both scenarios, the NAK messages omit the DHCP server-id 26 option. This is to emulate some DHCP servers (e.g. OpenBSD 4.6), 27 which omit the DHCP server-id in NAK messages. 28 """ 29 30 def common_setup(self): 31 """ 32 Run common setup steps. 33 """ 34 subnet_mask = self.ethernet_pair.interface_subnet_mask 35 self.intended_ip = dhcp_test_base.DhcpTestBase.rewrite_ip_suffix( 36 subnet_mask, self.server_ip, INTENDED_IP_SUFFIX) 37 self.interface_name = self.ethernet_pair.peer_interface_name 38 self.dhcp_options = { 39 dhcp_packet.OPTION_SERVER_ID : self.server_ip, 40 dhcp_packet.OPTION_SUBNET_MASK : subnet_mask, 41 dhcp_packet.OPTION_IP_LEASE_TIME : LEASE_TIME_SECONDS, 42 dhcp_packet.OPTION_REQUESTED_IP : self.intended_ip, 43 dhcp_packet.OPTION_DNS_SERVERS : [], 44 dhcp_packet.OPTION_DOMAIN_NAME : '', 45 dhcp_packet.OPTION_DNS_DOMAIN_SEARCH_LIST : [], 46 } 47 self.negotiate_and_check_lease(self.dhcp_options) 48 49 def reconnect_service(self): 50 """ 51 Disconnect and reconnect Ethernet. 52 53 Ask shill to disconnect and reconnect the Service for our 54 virtual Ethernet link. This causes shill to shut down and 55 restart dhcpcd for the link. 56 """ 57 service = self.find_ethernet_service(self.interface_name) 58 service.Disconnect() 59 rules = [ 60 # Respond to DISCOVERY, but then NAK the REQUEST. 61 dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 62 self.intended_ip, self.server_ip, self.dhcp_options, {}), 63 dhcp_handling_rule.DhcpHandlingRule_RejectRequest(), 64 65 # Allow a successful negotiation the second time around. 66 dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 67 self.intended_ip, self.server_ip, self.dhcp_options, {}), 68 dhcp_handling_rule.DhcpHandlingRule_RespondToRequest( 69 self.intended_ip, self.server_ip, self.dhcp_options, {}), 70 ] 71 rules[-1].is_final_handler = True 72 self.server.start_test( 73 rules, dhcp_test_base.DHCP_NEGOTIATION_TIMEOUT_SECONDS) 74 service.Connect() 75 76 def force_dhcp_renew(self): 77 """ 78 Force a DHCP renewal. 79 80 Ask shill to Refresh the configuration for the IPConfig object 81 associated with our virtual Ethernet link. This causes shill 82 to ask dhcpcd to renew its DHCP lease. 83 """ 84 rules = [ 85 # Reject REQUEST from renewal attempt. 86 dhcp_handling_rule.DhcpHandlingRule_RejectRequest(), 87 88 # Allow a successful negotiation after that. 89 dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 90 self.intended_ip, self.server_ip, self.dhcp_options, {}), 91 dhcp_handling_rule.DhcpHandlingRule_RespondToRequest( 92 self.intended_ip, self.server_ip, self.dhcp_options, {}), 93 ] 94 rules[-1].is_final_handler = True 95 self.server.start_test( 96 rules, dhcp_test_base.DHCP_NEGOTIATION_TIMEOUT_SECONDS) 97 self.get_interface_ipconfig_objects(self.interface_name)[0].Refresh() 98 99 def send_ack_then_nak(self): 100 """ 101 Send an ACK followed by a NAK on re-connect to Ethernet. 102 103 Ask shill to disconnect and reconnect the Service for our 104 virtual Ethernet link. This causes shill to shut down and 105 restart dhcpcd for the link. Then perform a test where 106 the server responds to a REQUEST with an ACK followed by 107 an ACK. 108 """ 109 service = self.find_ethernet_service(self.interface_name) 110 service.Disconnect() 111 rules = [ 112 # Respond to DISCOVERY, but then both ACK and NAK the REQUEST. 113 dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 114 self.intended_ip, self.server_ip, self.dhcp_options, {}), 115 dhcp_handling_rule.DhcpHandlingRule_RejectAndRespondToRequest( 116 self.intended_ip, self.server_ip, self.dhcp_options, {}, 117 False), 118 ] 119 rules[-1].is_final_handler = True 120 self.server.start_test( 121 rules, dhcp_test_base.DHCP_NEGOTIATION_TIMEOUT_SECONDS) 122 service.Connect() 123 124 def send_nak_then_ack_with_conflict(self): 125 """ 126 Send an NAK followed by an ACK on re-connect to with address conflict. 127 128 Ask shill to disconnect and reconnect the Service for our 129 virtual Ethernet link. This causes shill to shut down and 130 restart dhcpcd for the link. 131 132 On reconnect, perform a test where the server responds to a 133 REQUEST with a NAK followed by an ACK, however with a lease 134 for an invalid address (the same IP address as the DHCP server). 135 136 Ensure that the client rejects the invalid lease with a DECLINE, 137 and that it also ignores the first OFFER for the same invalid 138 address. 139 """ 140 service = self.find_ethernet_service(self.interface_name) 141 service.Disconnect() 142 rules = [ 143 # Respond to DISCOVERY, but then both NAK then ACK the REQUEST, 144 # supplying the server's own IP address. 145 dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 146 self.server_ip, self.server_ip, self.dhcp_options, {}), 147 dhcp_handling_rule.DhcpHandlingRule_RejectAndRespondToRequest( 148 self.server_ip, self.server_ip, self.dhcp_options, {}, 149 True), 150 151 # The client should eventually reject this lease since this 152 # address is in use. 153 dhcp_handling_rule.DhcpHandlingRule_AcceptDecline( 154 self.server_ip, self.dhcp_options, {}), 155 156 # Offer up the same (invalid) IP address. 157 dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 158 self.server_ip, self.server_ip, self.dhcp_options, {}), 159 160 # The client should ignore the previous offer and perform 161 # another DISCOVER request. 162 dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 163 self.intended_ip, self.server_ip, self.dhcp_options, {}), 164 dhcp_handling_rule.DhcpHandlingRule_RespondToRequest( 165 self.intended_ip, self.server_ip, self.dhcp_options, {}), 166 ] 167 rules[-1].is_final_handler = True 168 self.server.start_test( 169 rules, dhcp_test_base.DHCP_NEGOTIATION_TIMEOUT_SECONDS) 170 service.Connect() 171 172 def send_nak_then_ack_then_verify(self): 173 """ 174 Send an NAK followed by an ACK then verify client IP address. 175 176 Ask shill to disconnect and reconnect the Service for our 177 virtual Ethernet link. This causes shill to shut down and 178 restart dhcpcd for the link. 179 180 On reconnect, perform a test where the server responds to a 181 REQUEST with a NAK followed by an ACK. This method asserts 182 that the client does not DECLINE this address. 183 """ 184 service = self.find_ethernet_service(self.interface_name) 185 service.Disconnect() 186 187 # This rule serves two purposes: First it asserts that the client 188 # does not send a DECLINE response. Second, it waits until the 189 # test timeout, by which time client will have completed an "ARP 190 # self" operation to validate the offered IP adddres. 191 decline_rule = dhcp_handling_rule.DhcpHandlingRule_AcceptDecline( 192 self.intended_ip, self.dhcp_options, {}) 193 194 rules = [ 195 # Respond to DISCOVERY, but then both NAK then ACK the REQUEST, 196 # supplying the server's own IP address. 197 dhcp_handling_rule.DhcpHandlingRule_RespondToDiscovery( 198 self.intended_ip, self.server_ip, self.dhcp_options, {}), 199 dhcp_handling_rule.DhcpHandlingRule_RejectAndRespondToRequest( 200 self.intended_ip, self.server_ip, self.dhcp_options, {}, 201 True), 202 decline_rule 203 ] 204 rules[-1].is_final_handler = True 205 self.server.start_test( 206 rules, dhcp_test_base.DHCP_NEGOTIATION_TIMEOUT_SECONDS) 207 service.Connect() 208 self.server.wait_for_test_to_finish() 209 210 # This is a negative test, since we expect the last rule to fail. 211 if self.server.last_test_passed: 212 raise error.TestFail('DHCP DECLINE message was received') 213 elif self.server.current_rule != decline_rule: 214 raise error.TestFail('Failed on %s rule' % self.server.current_rule) 215 216 dhcp_config = self.get_interface_ipconfig( 217 self.ethernet_pair.peer_interface_name) 218 if dhcp_config is None: 219 raise error.TestFail('Did not get a DHCP config') 220 if dhcp_config[dhcp_test_base.DHCPCD_KEY_ADDRESS] != self.intended_ip: 221 raise error.TestFail('Client did not attain expected address %s' % 222 self.intended_ip) 223 224 def test_body(self): 225 """ 226 Entry point for this test. 227 228 This is called from DhcpTestBase.run_once(). 229 """ 230 self.common_setup() 231 for sub_test in (self.reconnect_service, 232 self.force_dhcp_renew, 233 self.send_ack_then_nak, 234 self.send_nak_then_ack_with_conflict): 235 sub_test() 236 self.server.wait_for_test_to_finish() 237 if not self.server.last_test_passed: 238 raise error.TestFail('Test failed (%s): active rule is %s' % ( 239 sub_test.__name__, self.server.current_rule)) 240 241 # This method is outside the loop above since it performs its own 242 # special verification. 243 self.send_nak_then_ack_then_verify() 244