1*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Li""" 6*9c5db199SXin LiDHCP handling rules are ways to record expectations for a DhcpTestServer. 7*9c5db199SXin Li 8*9c5db199SXin LiWhen a handling rule reaches the front of the DhcpTestServer handling rule 9*9c5db199SXin Liqueue, the server begins to ask the rule what it should do with each incoming 10*9c5db199SXin LiDHCP packet (in the form of a DhcpPacket). The handle() method is expected to 11*9c5db199SXin Lireturn a tuple (response, action) where response indicates whether the packet 12*9c5db199SXin Lishould be ignored or responded to and whether the test failed, succeeded, or is 13*9c5db199SXin Licontinuing. The action part of the tuple refers to whether or not the rule 14*9c5db199SXin Lishould be be removed from the test server's handling rule queue. 15*9c5db199SXin Li""" 16*9c5db199SXin Li 17*9c5db199SXin Liimport logging 18*9c5db199SXin Liimport time 19*9c5db199SXin Li 20*9c5db199SXin Lifrom autotest_lib.client.cros import dhcp_packet 21*9c5db199SXin Li 22*9c5db199SXin Li# Drops the packet and acts like it never happened. 23*9c5db199SXin LiRESPONSE_NO_ACTION = 0 24*9c5db199SXin Li# Signals that the handler wishes to send a packet. 25*9c5db199SXin LiRESPONSE_HAVE_RESPONSE = 1 << 0 26*9c5db199SXin Li# Signals that the handler wishes to be removed from the handling queue. 27*9c5db199SXin Li# The handler will be asked to generate a packet first if the handler signalled 28*9c5db199SXin Li# that it wished to do so with RESPONSE_HAVE_RESPONSE. 29*9c5db199SXin LiRESPONSE_POP_HANDLER = 1 << 1 30*9c5db199SXin Li# Signals that the handler wants to end the test on a failure. 31*9c5db199SXin LiRESPONSE_TEST_FAILED = 1 << 2 32*9c5db199SXin Li# Signals that the handler wants to end the test because it succeeded. 33*9c5db199SXin Li# Note that the failure bit has precedence over the success bit. 34*9c5db199SXin LiRESPONSE_TEST_SUCCEEDED = 1 << 3 35*9c5db199SXin Li 36*9c5db199SXin Liclass DhcpHandlingRule(object): 37*9c5db199SXin Li """ 38*9c5db199SXin Li DhcpHandlingRule defines an interface between the DhcpTestServer and 39*9c5db199SXin Li subclasses of DhcpHandlingRule. A handling rule at the front of the 40*9c5db199SXin Li DhcpTestServer rule queue is first asked what should be done with a packet 41*9c5db199SXin Li via handle(). handle() returns a bitfield as described above. If the 42*9c5db199SXin Li response from handle() indicates that a packet should be sent in response, 43*9c5db199SXin Li the server asks the handling rule to construct a response packet via 44*9c5db199SXin Li respond(). 45*9c5db199SXin Li """ 46*9c5db199SXin Li 47*9c5db199SXin Li def __init__(self, message_type, additional_options, custom_fields): 48*9c5db199SXin Li """ 49*9c5db199SXin Li |message_type| should be a MessageType, from DhcpPacket. 50*9c5db199SXin Li |additional_options| should be a dictionary that maps from 51*9c5db199SXin Li dhcp_packet.OPTION_* to values. For instance: 52*9c5db199SXin Li 53*9c5db199SXin Li {dhcp_packet.OPTION_SERVER_ID : "10.10.10.1"} 54*9c5db199SXin Li 55*9c5db199SXin Li These options are injected into response packets if the client requests 56*9c5db199SXin Li it. See inject_options(). 57*9c5db199SXin Li """ 58*9c5db199SXin Li super(DhcpHandlingRule, self).__init__() 59*9c5db199SXin Li self._is_final_handler = False 60*9c5db199SXin Li self._logger = logging.getLogger("dhcp.handling_rule") 61*9c5db199SXin Li self._options = additional_options 62*9c5db199SXin Li self._fields = custom_fields 63*9c5db199SXin Li self._target_time_seconds = None 64*9c5db199SXin Li self._allowable_time_delta_seconds = 0.5 65*9c5db199SXin Li self._force_reply_options = [] 66*9c5db199SXin Li self._message_type = message_type 67*9c5db199SXin Li self._last_warning = None 68*9c5db199SXin Li 69*9c5db199SXin Li def __str__(self): 70*9c5db199SXin Li if self._last_warning: 71*9c5db199SXin Li return '%s (%s)' % (self.__class__.__name__, self._last_warning) 72*9c5db199SXin Li else: 73*9c5db199SXin Li return self.__class__.__name__ 74*9c5db199SXin Li 75*9c5db199SXin Li @property 76*9c5db199SXin Li def logger(self): 77*9c5db199SXin Li return self._logger 78*9c5db199SXin Li 79*9c5db199SXin Li @property 80*9c5db199SXin Li def is_final_handler(self): 81*9c5db199SXin Li return self._is_final_handler 82*9c5db199SXin Li 83*9c5db199SXin Li @is_final_handler.setter 84*9c5db199SXin Li def is_final_handler(self, value): 85*9c5db199SXin Li self._is_final_handler = value 86*9c5db199SXin Li 87*9c5db199SXin Li @property 88*9c5db199SXin Li def options(self): 89*9c5db199SXin Li """ 90*9c5db199SXin Li Returns a dictionary that maps from DhcpPacket options to their values. 91*9c5db199SXin Li """ 92*9c5db199SXin Li return self._options 93*9c5db199SXin Li 94*9c5db199SXin Li @property 95*9c5db199SXin Li def fields(self): 96*9c5db199SXin Li """ 97*9c5db199SXin Li Returns a dictionary that maps from DhcpPacket fields to their values. 98*9c5db199SXin Li """ 99*9c5db199SXin Li return self._fields 100*9c5db199SXin Li 101*9c5db199SXin Li @property 102*9c5db199SXin Li def target_time_seconds(self): 103*9c5db199SXin Li """ 104*9c5db199SXin Li If this is not None, packets will be rejected if they don't fall within 105*9c5db199SXin Li |self.allowable_time_delta_seconds| seconds of 106*9c5db199SXin Li |self.target_time_seconds|. A value of None will cause this handler to 107*9c5db199SXin Li ignore the target packet time. 108*9c5db199SXin Li 109*9c5db199SXin Li Defaults to None. 110*9c5db199SXin Li """ 111*9c5db199SXin Li return self._target_time_seconds 112*9c5db199SXin Li 113*9c5db199SXin Li @target_time_seconds.setter 114*9c5db199SXin Li def target_time_seconds(self, value): 115*9c5db199SXin Li self._target_time_seconds = value 116*9c5db199SXin Li 117*9c5db199SXin Li @property 118*9c5db199SXin Li def allowable_time_delta_seconds(self): 119*9c5db199SXin Li """ 120*9c5db199SXin Li A configurable fudge factor for |self.target_time_seconds|. If a packet 121*9c5db199SXin Li comes in at time T and: 122*9c5db199SXin Li 123*9c5db199SXin Li delta = abs(T - |self.target_time_seconds|) 124*9c5db199SXin Li 125*9c5db199SXin Li Then if delta < |self.allowable_time_delta_seconds|, we accept the 126*9c5db199SXin Li packet. Otherwise we either fail the test or ignore the packet, 127*9c5db199SXin Li depending on whether this packet is before or after the window. 128*9c5db199SXin Li 129*9c5db199SXin Li Defaults to 0.5 seconds. 130*9c5db199SXin Li """ 131*9c5db199SXin Li return self._allowable_time_delta_seconds 132*9c5db199SXin Li 133*9c5db199SXin Li @allowable_time_delta_seconds.setter 134*9c5db199SXin Li def allowable_time_delta_seconds(self, value): 135*9c5db199SXin Li self._allowable_time_delta_seconds = value 136*9c5db199SXin Li 137*9c5db199SXin Li @property 138*9c5db199SXin Li def packet_is_too_late(self): 139*9c5db199SXin Li if self.target_time_seconds is None: 140*9c5db199SXin Li return False 141*9c5db199SXin Li delta = time.time() - self.target_time_seconds 142*9c5db199SXin Li logging.debug("Handler received packet %0.2f seconds from target time.", 143*9c5db199SXin Li delta) 144*9c5db199SXin Li if delta > self._allowable_time_delta_seconds: 145*9c5db199SXin Li logging.info("Packet was too late for handling (+%0.2f seconds)", 146*9c5db199SXin Li delta - self._allowable_time_delta_seconds) 147*9c5db199SXin Li return True 148*9c5db199SXin Li logging.info("Packet was not too late for handling.") 149*9c5db199SXin Li return False 150*9c5db199SXin Li 151*9c5db199SXin Li @property 152*9c5db199SXin Li def packet_is_too_soon(self): 153*9c5db199SXin Li if self.target_time_seconds is None: 154*9c5db199SXin Li return False 155*9c5db199SXin Li delta = time.time() - self.target_time_seconds 156*9c5db199SXin Li logging.debug("Handler received packet %0.2f seconds from target time.", 157*9c5db199SXin Li delta) 158*9c5db199SXin Li if -delta > self._allowable_time_delta_seconds: 159*9c5db199SXin Li logging.info("Packet arrived too soon for handling: " 160*9c5db199SXin Li "(-%0.2f seconds)", 161*9c5db199SXin Li -delta - self._allowable_time_delta_seconds) 162*9c5db199SXin Li return True 163*9c5db199SXin Li logging.info("Packet was not too soon for handling.") 164*9c5db199SXin Li return False 165*9c5db199SXin Li 166*9c5db199SXin Li @property 167*9c5db199SXin Li def force_reply_options(self): 168*9c5db199SXin Li return self._force_reply_options 169*9c5db199SXin Li 170*9c5db199SXin Li @force_reply_options.setter 171*9c5db199SXin Li def force_reply_options(self, value): 172*9c5db199SXin Li self._force_reply_options = value 173*9c5db199SXin Li 174*9c5db199SXin Li @property 175*9c5db199SXin Li def response_packet_count(self): 176*9c5db199SXin Li return 1 177*9c5db199SXin Li 178*9c5db199SXin Li def emit_warning(self, warning): 179*9c5db199SXin Li """ 180*9c5db199SXin Li Log a warning, and retain that warning as |_last_warning|. 181*9c5db199SXin Li 182*9c5db199SXin Li @param warning: The warning message 183*9c5db199SXin Li """ 184*9c5db199SXin Li self.logger.warning(warning) 185*9c5db199SXin Li self._last_warning = warning 186*9c5db199SXin Li 187*9c5db199SXin Li def handle(self, query_packet): 188*9c5db199SXin Li """ 189*9c5db199SXin Li The DhcpTestServer will call this method to ask a handling rule whether 190*9c5db199SXin Li it wants to take some action in response to a packet. The handler 191*9c5db199SXin Li should return some combination of RESPONSE_* bits as described above. 192*9c5db199SXin Li 193*9c5db199SXin Li |packet| is a valid DHCP packet, but the values of fields and presence 194*9c5db199SXin Li of options is not guaranteed. 195*9c5db199SXin Li """ 196*9c5db199SXin Li if self.packet_is_too_late: 197*9c5db199SXin Li return RESPONSE_TEST_FAILED 198*9c5db199SXin Li if self.packet_is_too_soon: 199*9c5db199SXin Li return RESPONSE_NO_ACTION 200*9c5db199SXin Li return self.handle_impl(query_packet) 201*9c5db199SXin Li 202*9c5db199SXin Li def handle_impl(self, query_packet): 203*9c5db199SXin Li logging.error("DhcpHandlingRule.handle_impl() called.") 204*9c5db199SXin Li return RESPONSE_TEST_FAILED 205*9c5db199SXin Li 206*9c5db199SXin Li def respond(self, query_packet): 207*9c5db199SXin Li """ 208*9c5db199SXin Li Called by the DhcpTestServer to generate a packet to send back to the 209*9c5db199SXin Li client. This method is called if and only if the response returned from 210*9c5db199SXin Li handle() had RESPONSE_HAVE_RESPONSE set. 211*9c5db199SXin Li """ 212*9c5db199SXin Li return None 213*9c5db199SXin Li 214*9c5db199SXin Li def inject_options(self, packet, requested_parameters): 215*9c5db199SXin Li """ 216*9c5db199SXin Li Adds options listed in the intersection of |requested_parameters| and 217*9c5db199SXin Li |self.options| to |packet|. Also include the options in the 218*9c5db199SXin Li intersection of |self.force_reply_options| and |self.options|. 219*9c5db199SXin Li 220*9c5db199SXin Li |packet| is a DhcpPacket. 221*9c5db199SXin Li 222*9c5db199SXin Li |requested_parameters| is a list of options numbers as you would find in 223*9c5db199SXin Li a DHCP_DISCOVER or DHCP_REQUEST packet after being parsed by DhcpPacket 224*9c5db199SXin Li (e.g. [1, 121, 33, 3, 6, 12]). 225*9c5db199SXin Li 226*9c5db199SXin Li Subclassed handling rules may call this to inject options into response 227*9c5db199SXin Li packets to the client. This process emulates a real DHCP server which 228*9c5db199SXin Li would have a pool of configuration settings to hand out to DHCP clients 229*9c5db199SXin Li upon request. 230*9c5db199SXin Li """ 231*9c5db199SXin Li for option, value in self.options.items(): 232*9c5db199SXin Li if (option.number in requested_parameters or 233*9c5db199SXin Li option in self.force_reply_options): 234*9c5db199SXin Li packet.set_option(option, value) 235*9c5db199SXin Li 236*9c5db199SXin Li def inject_fields(self, packet): 237*9c5db199SXin Li """ 238*9c5db199SXin Li Adds fields listed in |self.fields| to |packet|. 239*9c5db199SXin Li 240*9c5db199SXin Li |packet| is a DhcpPacket. 241*9c5db199SXin Li 242*9c5db199SXin Li Subclassed handling rules may call this to inject fields into response 243*9c5db199SXin Li packets to the client. This process emulates a real DHCP server which 244*9c5db199SXin Li would have a pool of configuration settings to hand out to DHCP clients 245*9c5db199SXin Li upon request. 246*9c5db199SXin Li """ 247*9c5db199SXin Li for field, value in self.fields.items(): 248*9c5db199SXin Li packet.set_field(field, value) 249*9c5db199SXin Li 250*9c5db199SXin Li def is_our_message_type(self, packet): 251*9c5db199SXin Li """ 252*9c5db199SXin Li Checks if the Message Type DHCP Option in |packet| matches the message 253*9c5db199SXin Li type handled by this rule. Logs a warning if the types do not match. 254*9c5db199SXin Li 255*9c5db199SXin Li @param packet: a DhcpPacket 256*9c5db199SXin Li 257*9c5db199SXin Li @returns True or False 258*9c5db199SXin Li """ 259*9c5db199SXin Li if packet.message_type == self._message_type: 260*9c5db199SXin Li return True 261*9c5db199SXin Li else: 262*9c5db199SXin Li self.emit_warning("Packet's message type was %s, not %s." % ( 263*9c5db199SXin Li packet.message_type.name, 264*9c5db199SXin Li self._message_type.name)) 265*9c5db199SXin Li return False 266*9c5db199SXin Li 267*9c5db199SXin Li 268*9c5db199SXin Liclass DhcpHandlingRule_RespondToDiscovery(DhcpHandlingRule): 269*9c5db199SXin Li """ 270*9c5db199SXin Li This handler will accept any DISCOVER packet received by the server. In 271*9c5db199SXin Li response to such a packet, the handler will construct an OFFER packet 272*9c5db199SXin Li offering |intended_ip| from a server at |server_ip| (from the constructor). 273*9c5db199SXin Li """ 274*9c5db199SXin Li def __init__(self, 275*9c5db199SXin Li intended_ip, 276*9c5db199SXin Li server_ip, 277*9c5db199SXin Li additional_options, 278*9c5db199SXin Li custom_fields, 279*9c5db199SXin Li should_respond=True): 280*9c5db199SXin Li """ 281*9c5db199SXin Li |intended_ip| is an IPv4 address string like "192.168.1.100". 282*9c5db199SXin Li 283*9c5db199SXin Li |server_ip| is an IPv4 address string like "192.168.1.1". 284*9c5db199SXin Li 285*9c5db199SXin Li |additional_options| is handled as explained by DhcpHandlingRule. 286*9c5db199SXin Li """ 287*9c5db199SXin Li super(DhcpHandlingRule_RespondToDiscovery, self).__init__( 288*9c5db199SXin Li dhcp_packet.MESSAGE_TYPE_DISCOVERY, additional_options, 289*9c5db199SXin Li custom_fields) 290*9c5db199SXin Li self._intended_ip = intended_ip 291*9c5db199SXin Li self._server_ip = server_ip 292*9c5db199SXin Li self._should_respond = should_respond 293*9c5db199SXin Li 294*9c5db199SXin Li def handle_impl(self, query_packet): 295*9c5db199SXin Li if not self.is_our_message_type(query_packet): 296*9c5db199SXin Li return RESPONSE_NO_ACTION 297*9c5db199SXin Li 298*9c5db199SXin Li self.logger.info("Received valid DISCOVERY packet. Processing.") 299*9c5db199SXin Li ret = RESPONSE_POP_HANDLER 300*9c5db199SXin Li if self.is_final_handler: 301*9c5db199SXin Li ret |= RESPONSE_TEST_SUCCEEDED 302*9c5db199SXin Li if self._should_respond: 303*9c5db199SXin Li ret |= RESPONSE_HAVE_RESPONSE 304*9c5db199SXin Li return ret 305*9c5db199SXin Li 306*9c5db199SXin Li def respond(self, query_packet): 307*9c5db199SXin Li if not self.is_our_message_type(query_packet): 308*9c5db199SXin Li return None 309*9c5db199SXin Li 310*9c5db199SXin Li self.logger.info("Responding to DISCOVERY packet.") 311*9c5db199SXin Li response_packet = dhcp_packet.DhcpPacket.create_offer_packet( 312*9c5db199SXin Li query_packet.transaction_id, 313*9c5db199SXin Li query_packet.client_hw_address, 314*9c5db199SXin Li self._intended_ip, 315*9c5db199SXin Li self._server_ip) 316*9c5db199SXin Li requested_parameters = query_packet.get_option( 317*9c5db199SXin Li dhcp_packet.OPTION_PARAMETER_REQUEST_LIST) 318*9c5db199SXin Li if requested_parameters is not None: 319*9c5db199SXin Li self.inject_options(response_packet, requested_parameters) 320*9c5db199SXin Li self.inject_fields(response_packet) 321*9c5db199SXin Li return response_packet 322*9c5db199SXin Li 323*9c5db199SXin Li 324*9c5db199SXin Liclass DhcpHandlingRule_RejectRequest(DhcpHandlingRule): 325*9c5db199SXin Li """ 326*9c5db199SXin Li This handler receives a REQUEST packet, and responds with a NAK. 327*9c5db199SXin Li """ 328*9c5db199SXin Li def __init__(self): 329*9c5db199SXin Li super(DhcpHandlingRule_RejectRequest, self).__init__( 330*9c5db199SXin Li dhcp_packet.MESSAGE_TYPE_REQUEST, {}, {}) 331*9c5db199SXin Li self._should_respond = True 332*9c5db199SXin Li 333*9c5db199SXin Li def handle_impl(self, query_packet): 334*9c5db199SXin Li if not self.is_our_message_type(query_packet): 335*9c5db199SXin Li return RESPONSE_NO_ACTION 336*9c5db199SXin Li 337*9c5db199SXin Li ret = RESPONSE_POP_HANDLER 338*9c5db199SXin Li if self.is_final_handler: 339*9c5db199SXin Li ret |= RESPONSE_TEST_SUCCEEDED 340*9c5db199SXin Li if self._should_respond: 341*9c5db199SXin Li ret |= RESPONSE_HAVE_RESPONSE 342*9c5db199SXin Li return ret 343*9c5db199SXin Li 344*9c5db199SXin Li def respond(self, query_packet): 345*9c5db199SXin Li if not self.is_our_message_type(query_packet): 346*9c5db199SXin Li return None 347*9c5db199SXin Li 348*9c5db199SXin Li self.logger.info("NAKing the REQUEST packet.") 349*9c5db199SXin Li response_packet = dhcp_packet.DhcpPacket.create_nak_packet( 350*9c5db199SXin Li query_packet.transaction_id, query_packet.client_hw_address) 351*9c5db199SXin Li return response_packet 352*9c5db199SXin Li 353*9c5db199SXin Li 354*9c5db199SXin Liclass DhcpHandlingRule_RespondToRequest(DhcpHandlingRule): 355*9c5db199SXin Li """ 356*9c5db199SXin Li This handler accepts any REQUEST packet that contains options for SERVER_ID 357*9c5db199SXin Li and REQUESTED_IP that match |expected_server_ip| and |expected_requested_ip| 358*9c5db199SXin Li respectively. It responds with an ACKNOWLEDGEMENT packet from a DHCP server 359*9c5db199SXin Li at |response_server_ip| granting |response_granted_ip| to a client at the 360*9c5db199SXin Li address given in the REQUEST packet. If |response_server_ip| or 361*9c5db199SXin Li |response_granted_ip| are not given, then they default to 362*9c5db199SXin Li |expected_server_ip| and |expected_requested_ip| respectively. 363*9c5db199SXin Li """ 364*9c5db199SXin Li def __init__(self, 365*9c5db199SXin Li expected_requested_ip, 366*9c5db199SXin Li expected_server_ip, 367*9c5db199SXin Li additional_options, 368*9c5db199SXin Li custom_fields, 369*9c5db199SXin Li should_respond=True, 370*9c5db199SXin Li response_server_ip=None, 371*9c5db199SXin Li response_granted_ip=None, 372*9c5db199SXin Li expect_server_ip_set=True): 373*9c5db199SXin Li """ 374*9c5db199SXin Li All *_ip arguments are IPv4 address strings like "192.168.1.101". 375*9c5db199SXin Li 376*9c5db199SXin Li |additional_options| is handled as explained by DhcpHandlingRule. 377*9c5db199SXin Li """ 378*9c5db199SXin Li super(DhcpHandlingRule_RespondToRequest, self).__init__( 379*9c5db199SXin Li dhcp_packet.MESSAGE_TYPE_REQUEST, additional_options, 380*9c5db199SXin Li custom_fields) 381*9c5db199SXin Li self._expected_requested_ip = expected_requested_ip 382*9c5db199SXin Li self._expected_server_ip = expected_server_ip 383*9c5db199SXin Li self._should_respond = should_respond 384*9c5db199SXin Li self._granted_ip = response_granted_ip 385*9c5db199SXin Li self._server_ip = response_server_ip 386*9c5db199SXin Li self._expect_server_ip_set = expect_server_ip_set 387*9c5db199SXin Li if self._granted_ip is None: 388*9c5db199SXin Li self._granted_ip = self._expected_requested_ip 389*9c5db199SXin Li if self._server_ip is None: 390*9c5db199SXin Li self._server_ip = self._expected_server_ip 391*9c5db199SXin Li 392*9c5db199SXin Li def handle_impl(self, query_packet): 393*9c5db199SXin Li if not self.is_our_message_type(query_packet): 394*9c5db199SXin Li return RESPONSE_NO_ACTION 395*9c5db199SXin Li 396*9c5db199SXin Li self.logger.info("Received REQUEST packet, checking fields...") 397*9c5db199SXin Li server_ip = query_packet.get_option(dhcp_packet.OPTION_SERVER_ID) 398*9c5db199SXin Li if dhcp_packet.OPTION_REQUESTED_IP in self.options: 399*9c5db199SXin Li requested_ip = query_packet.get_option( 400*9c5db199SXin Li dhcp_packet.OPTION_REQUESTED_IP) 401*9c5db199SXin Li else: 402*9c5db199SXin Li cli_ip = query_packet.get_field(dhcp_packet.FIELD_CLIENT_IP) 403*9c5db199SXin Li if cli_ip != dhcp_packet.IPV4_NULL_ADDRESS: 404*9c5db199SXin Li requested_ip = cli_ip 405*9c5db199SXin Li else: 406*9c5db199SXin Li requested_ip = None 407*9c5db199SXin Li server_ip_provided = server_ip is not None 408*9c5db199SXin Li if ((server_ip_provided != self._expect_server_ip_set) or 409*9c5db199SXin Li (requested_ip is None)): 410*9c5db199SXin Li self.logger.info("REQUEST packet did not have the expected " 411*9c5db199SXin Li "options, discarding.") 412*9c5db199SXin Li return RESPONSE_NO_ACTION 413*9c5db199SXin Li 414*9c5db199SXin Li if server_ip_provided and server_ip != self._expected_server_ip: 415*9c5db199SXin Li self.emit_warning("REQUEST packet's server ip did not match our " 416*9c5db199SXin Li "expectations; expected %s but got %s" % 417*9c5db199SXin Li (self._expected_server_ip, server_ip)) 418*9c5db199SXin Li return RESPONSE_NO_ACTION 419*9c5db199SXin Li 420*9c5db199SXin Li if requested_ip != self._expected_requested_ip: 421*9c5db199SXin Li self.emit_warning("REQUEST packet's requested IP did not match " 422*9c5db199SXin Li "our expectations; expected %s but got %s" % 423*9c5db199SXin Li (self._expected_requested_ip, requested_ip)) 424*9c5db199SXin Li return RESPONSE_NO_ACTION 425*9c5db199SXin Li 426*9c5db199SXin Li self.logger.info("Received valid REQUEST packet, processing") 427*9c5db199SXin Li ret = RESPONSE_POP_HANDLER 428*9c5db199SXin Li if self.is_final_handler: 429*9c5db199SXin Li ret |= RESPONSE_TEST_SUCCEEDED 430*9c5db199SXin Li if self._should_respond: 431*9c5db199SXin Li ret |= RESPONSE_HAVE_RESPONSE 432*9c5db199SXin Li return ret 433*9c5db199SXin Li 434*9c5db199SXin Li def respond(self, query_packet): 435*9c5db199SXin Li if not self.is_our_message_type(query_packet): 436*9c5db199SXin Li return None 437*9c5db199SXin Li 438*9c5db199SXin Li self.logger.info("Responding to REQUEST packet.") 439*9c5db199SXin Li response_packet = dhcp_packet.DhcpPacket.create_acknowledgement_packet( 440*9c5db199SXin Li query_packet.transaction_id, 441*9c5db199SXin Li query_packet.client_hw_address, 442*9c5db199SXin Li self._granted_ip, 443*9c5db199SXin Li self._server_ip) 444*9c5db199SXin Li requested_parameters = query_packet.get_option( 445*9c5db199SXin Li dhcp_packet.OPTION_PARAMETER_REQUEST_LIST) 446*9c5db199SXin Li if requested_parameters is not None: 447*9c5db199SXin Li self.inject_options(response_packet, requested_parameters) 448*9c5db199SXin Li self.inject_fields(response_packet) 449*9c5db199SXin Li return response_packet 450*9c5db199SXin Li 451*9c5db199SXin Li 452*9c5db199SXin Liclass DhcpHandlingRule_RespondToPostT2Request( 453*9c5db199SXin Li DhcpHandlingRule_RespondToRequest): 454*9c5db199SXin Li """ 455*9c5db199SXin Li This handler is a lot like DhcpHandlingRule_RespondToRequest except that it 456*9c5db199SXin Li expects request packets like those sent after the T2 deadline (see RFC 457*9c5db199SXin Li 2131). This is the time that you can find a request packet without the 458*9c5db199SXin Li SERVER_ID option. It responds to packets in exactly the same way. 459*9c5db199SXin Li """ 460*9c5db199SXin Li def __init__(self, 461*9c5db199SXin Li expected_requested_ip, 462*9c5db199SXin Li response_server_ip, 463*9c5db199SXin Li additional_options, 464*9c5db199SXin Li custom_fields, 465*9c5db199SXin Li should_respond=True, 466*9c5db199SXin Li response_granted_ip=None): 467*9c5db199SXin Li """ 468*9c5db199SXin Li All *_ip arguments are IPv4 address strings like "192.168.1.101". 469*9c5db199SXin Li 470*9c5db199SXin Li |additional_options| is handled as explained by DhcpHandlingRule. 471*9c5db199SXin Li """ 472*9c5db199SXin Li super(DhcpHandlingRule_RespondToPostT2Request, 473*9c5db199SXin Li self).__init__(expected_requested_ip, 474*9c5db199SXin Li None, 475*9c5db199SXin Li additional_options, 476*9c5db199SXin Li custom_fields, 477*9c5db199SXin Li should_respond=should_respond, 478*9c5db199SXin Li response_server_ip=response_server_ip, 479*9c5db199SXin Li response_granted_ip=response_granted_ip, 480*9c5db199SXin Li expect_server_ip_set=False) 481*9c5db199SXin Li 482*9c5db199SXin Li def handle_impl(self, query_packet): 483*9c5db199SXin Li if not self.is_our_message_type(query_packet): 484*9c5db199SXin Li return RESPONSE_NO_ACTION 485*9c5db199SXin Li 486*9c5db199SXin Li self.logger.info("Received REQUEST packet, checking fields...") 487*9c5db199SXin Li if query_packet.get_option(dhcp_packet.OPTION_SERVER_ID) is not None: 488*9c5db199SXin Li self.logger.info("REQUEST packet had a SERVER_ID option, which it " 489*9c5db199SXin Li "is not expected to have, discarding.") 490*9c5db199SXin Li return RESPONSE_NO_ACTION 491*9c5db199SXin Li 492*9c5db199SXin Li if query_packet.get_option( 493*9c5db199SXin Li dhcp_packet.OPTION_REQUESTED_IP) is not None: 494*9c5db199SXin Li self.logger.info("REQUEST packet had a REQUESTED_IP_ID option, " 495*9c5db199SXin Li "which it is not expected to have, discarding.") 496*9c5db199SXin Li return RESPONSE_NO_ACTION 497*9c5db199SXin Li 498*9c5db199SXin Li requested_ip = query_packet.get_field(dhcp_packet.FIELD_CLIENT_IP) 499*9c5db199SXin Li if requested_ip == dhcp_packet.IPV4_NULL_ADDRESS: 500*9c5db199SXin Li self.logger.info("REQUEST packet did not have the expected " 501*9c5db199SXin Li "request ip option at all, discarding.") 502*9c5db199SXin Li return RESPONSE_NO_ACTION 503*9c5db199SXin Li 504*9c5db199SXin Li if requested_ip != self._expected_requested_ip: 505*9c5db199SXin Li self.emit_warning("REQUEST packet's requested IP did not match " 506*9c5db199SXin Li "our expectations; expected %s but got %s" % 507*9c5db199SXin Li (self._expected_requested_ip, requested_ip)) 508*9c5db199SXin Li return RESPONSE_NO_ACTION 509*9c5db199SXin Li 510*9c5db199SXin Li self.logger.info("Received valid post T2 REQUEST packet, processing") 511*9c5db199SXin Li ret = RESPONSE_POP_HANDLER 512*9c5db199SXin Li if self.is_final_handler: 513*9c5db199SXin Li ret |= RESPONSE_TEST_SUCCEEDED 514*9c5db199SXin Li if self._should_respond: 515*9c5db199SXin Li ret |= RESPONSE_HAVE_RESPONSE 516*9c5db199SXin Li return ret 517*9c5db199SXin Li 518*9c5db199SXin Li 519*9c5db199SXin Liclass DhcpHandlingRule_AcceptRelease(DhcpHandlingRule): 520*9c5db199SXin Li """ 521*9c5db199SXin Li This handler accepts any RELEASE packet that contains an option for 522*9c5db199SXin Li SERVER_ID matches |expected_server_ip|. There is no response to this 523*9c5db199SXin Li packet. 524*9c5db199SXin Li """ 525*9c5db199SXin Li def __init__(self, 526*9c5db199SXin Li expected_server_ip, 527*9c5db199SXin Li additional_options, 528*9c5db199SXin Li custom_fields): 529*9c5db199SXin Li """ 530*9c5db199SXin Li All *_ip arguments are IPv4 address strings like "192.168.1.101". 531*9c5db199SXin Li 532*9c5db199SXin Li |additional_options| is handled as explained by DhcpHandlingRule. 533*9c5db199SXin Li """ 534*9c5db199SXin Li super(DhcpHandlingRule_AcceptRelease, self).__init__( 535*9c5db199SXin Li dhcp_packet.MESSAGE_TYPE_RELEASE, additional_options, 536*9c5db199SXin Li custom_fields) 537*9c5db199SXin Li self._expected_server_ip = expected_server_ip 538*9c5db199SXin Li 539*9c5db199SXin Li def handle_impl(self, query_packet): 540*9c5db199SXin Li if not self.is_our_message_type(query_packet): 541*9c5db199SXin Li return RESPONSE_NO_ACTION 542*9c5db199SXin Li 543*9c5db199SXin Li self.logger.info("Received RELEASE packet, checking fields...") 544*9c5db199SXin Li server_ip = query_packet.get_option(dhcp_packet.OPTION_SERVER_ID) 545*9c5db199SXin Li if server_ip is None: 546*9c5db199SXin Li self.logger.info("RELEASE packet did not have the expected " 547*9c5db199SXin Li "options, discarding.") 548*9c5db199SXin Li return RESPONSE_NO_ACTION 549*9c5db199SXin Li 550*9c5db199SXin Li if server_ip != self._expected_server_ip: 551*9c5db199SXin Li self.emit_warning("RELEASE packet's server ip did not match our " 552*9c5db199SXin Li "expectations; expected %s but got %s" % 553*9c5db199SXin Li (self._expected_server_ip, server_ip)) 554*9c5db199SXin Li return RESPONSE_NO_ACTION 555*9c5db199SXin Li 556*9c5db199SXin Li self.logger.info("Received valid RELEASE packet, processing") 557*9c5db199SXin Li ret = RESPONSE_POP_HANDLER 558*9c5db199SXin Li if self.is_final_handler: 559*9c5db199SXin Li ret |= RESPONSE_TEST_SUCCEEDED 560*9c5db199SXin Li return ret 561*9c5db199SXin Li 562*9c5db199SXin Li 563*9c5db199SXin Liclass DhcpHandlingRule_RejectAndRespondToRequest( 564*9c5db199SXin Li DhcpHandlingRule_RespondToRequest): 565*9c5db199SXin Li """ 566*9c5db199SXin Li This handler accepts any REQUEST packet that contains options for SERVER_ID 567*9c5db199SXin Li and REQUESTED_IP that match |expected_server_ip| and |expected_requested_ip| 568*9c5db199SXin Li respectively. It responds with both an ACKNOWLEDGEMENT packet from a DHCP 569*9c5db199SXin Li server as well as a NAK, in order to simulate a network with two conflicting 570*9c5db199SXin Li servers. 571*9c5db199SXin Li """ 572*9c5db199SXin Li def __init__(self, 573*9c5db199SXin Li expected_requested_ip, 574*9c5db199SXin Li expected_server_ip, 575*9c5db199SXin Li additional_options, 576*9c5db199SXin Li custom_fields, 577*9c5db199SXin Li send_nak_before_ack): 578*9c5db199SXin Li super(DhcpHandlingRule_RejectAndRespondToRequest, self).__init__( 579*9c5db199SXin Li expected_requested_ip, 580*9c5db199SXin Li expected_server_ip, 581*9c5db199SXin Li additional_options, 582*9c5db199SXin Li custom_fields) 583*9c5db199SXin Li self._send_nak_before_ack = send_nak_before_ack 584*9c5db199SXin Li self._response_counter = 0 585*9c5db199SXin Li 586*9c5db199SXin Li @property 587*9c5db199SXin Li def response_packet_count(self): 588*9c5db199SXin Li return 2 589*9c5db199SXin Li 590*9c5db199SXin Li def respond(self, query_packet): 591*9c5db199SXin Li """ Respond to |query_packet| with a NAK then ACK or ACK then NAK. """ 592*9c5db199SXin Li if ((self._response_counter == 0 and self._send_nak_before_ack) or 593*9c5db199SXin Li (self._response_counter != 0 and not self._send_nak_before_ack)): 594*9c5db199SXin Li response_packet = dhcp_packet.DhcpPacket.create_nak_packet( 595*9c5db199SXin Li query_packet.transaction_id, query_packet.client_hw_address) 596*9c5db199SXin Li else: 597*9c5db199SXin Li response_packet = super(DhcpHandlingRule_RejectAndRespondToRequest, 598*9c5db199SXin Li self).respond(query_packet) 599*9c5db199SXin Li self._response_counter += 1 600*9c5db199SXin Li return response_packet 601*9c5db199SXin Li 602*9c5db199SXin Li 603*9c5db199SXin Liclass DhcpHandlingRule_AcceptDecline(DhcpHandlingRule): 604*9c5db199SXin Li """ 605*9c5db199SXin Li This handler accepts any DECLINE packet that contains an option for 606*9c5db199SXin Li SERVER_ID matches |expected_server_ip|. There is no response to this 607*9c5db199SXin Li packet. 608*9c5db199SXin Li """ 609*9c5db199SXin Li def __init__(self, 610*9c5db199SXin Li expected_server_ip, 611*9c5db199SXin Li additional_options, 612*9c5db199SXin Li custom_fields): 613*9c5db199SXin Li """ 614*9c5db199SXin Li All *_ip arguments are IPv4 address strings like "192.168.1.101". 615*9c5db199SXin Li 616*9c5db199SXin Li |additional_options| is handled as explained by DhcpHandlingRule. 617*9c5db199SXin Li """ 618*9c5db199SXin Li super(DhcpHandlingRule_AcceptDecline, self).__init__( 619*9c5db199SXin Li dhcp_packet.MESSAGE_TYPE_DECLINE, additional_options, 620*9c5db199SXin Li custom_fields) 621*9c5db199SXin Li self._expected_server_ip = expected_server_ip 622*9c5db199SXin Li 623*9c5db199SXin Li def handle_impl(self, query_packet): 624*9c5db199SXin Li if not self.is_our_message_type(query_packet): 625*9c5db199SXin Li return RESPONSE_NO_ACTION 626*9c5db199SXin Li 627*9c5db199SXin Li self.logger.info("Received DECLINE packet, checking fields...") 628*9c5db199SXin Li server_ip = query_packet.get_option(dhcp_packet.OPTION_SERVER_ID) 629*9c5db199SXin Li if server_ip is None: 630*9c5db199SXin Li self.logger.info("DECLINE packet did not have the expected " 631*9c5db199SXin Li "options, discarding.") 632*9c5db199SXin Li return RESPONSE_NO_ACTION 633*9c5db199SXin Li 634*9c5db199SXin Li if server_ip != self._expected_server_ip: 635*9c5db199SXin Li self.emit_warning("DECLINE packet's server ip did not match our " 636*9c5db199SXin Li "expectations; expected %s but got %s" % 637*9c5db199SXin Li (self._expected_server_ip, server_ip)) 638*9c5db199SXin Li return RESPONSE_NO_ACTION 639*9c5db199SXin Li 640*9c5db199SXin Li self.logger.info("Received valid DECLINE packet, processing") 641*9c5db199SXin Li ret = RESPONSE_POP_HANDLER 642*9c5db199SXin Li if self.is_final_handler: 643*9c5db199SXin Li ret |= RESPONSE_TEST_SUCCEEDED 644*9c5db199SXin Li return ret 645