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