11964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
21964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley# Use of this source code is governed by a BSD-style license that can be
31964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley# found in the LICENSE file.
41964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
51964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley"""
61964458a980b17c71ea10f9c00c59fe15bb83a96Christopher WileyDHCP handling rules are ways to record expectations for a DhcpTestServer.
71964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
81964458a980b17c71ea10f9c00c59fe15bb83a96Christopher WileyWhen a handling rule reaches the front of the DhcpTestServer handling rule
91964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileyqueue, the server begins to ask the rule what it should do with each incoming
107d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher WileyDHCP packet (in the form of a DhcpPacket).  The handle() method is expected to
111964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileyreturn a tuple (response, action) where response indicates whether the packet
121964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileyshould be ignored or responded to and whether the test failed, succeeded, or is
131964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileycontinuing.  The action part of the tuple refers to whether or not the rule
141964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileyshould be be removed from the test server's handling rule queue.
151964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley"""
161964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
171964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileyimport logging
189b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wileyimport time
191964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
201964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileyfrom autotest_lib.client.cros import dhcp_packet
211964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
227d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley# Drops the packet and acts like it never happened.
237d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher WileyRESPONSE_NO_ACTION = 0
247d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley# Signals that the handler wishes to send a packet.
257d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher WileyRESPONSE_HAVE_RESPONSE = 1 << 0
267d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley# Signals that the handler wishes to be removed from the handling queue.
277d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley# The handler will be asked to generate a packet first if the handler signalled
287d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley# that it wished to do so with RESPONSE_HAVE_RESPONSE.
297d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher WileyRESPONSE_POP_HANDLER = 1 << 1
307d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley# Signals that the handler wants to end the test on a failure.
317d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher WileyRESPONSE_TEST_FAILED = 1 << 2
327d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley# Signals that the handler wants to end the test because it succeeded.
337d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley# Note that the failure bit has precedence over the success bit.
347d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher WileyRESPONSE_TEST_SUCCEEDED = 1 << 3
351964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
361964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileyclass DhcpHandlingRule(object):
3730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    """
3830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    DhcpHandlingRule defines an interface between the DhcpTestServer and
3930b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    subclasses of DhcpHandlingRule.  A handling rule at the front of the
4030b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    DhcpTestServer rule queue is first asked what should be done with a packet
417d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley    via handle().  handle() returns a bitfield as described above.  If the
427d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley    response from handle() indicates that a packet should be sent in response,
437d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley    the server asks the handling rule to construct a response packet via
447d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley    respond().
4530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    """
467d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley
478962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal    def __init__(self, message_type, additional_options, custom_fields):
4830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
498962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        |message_type| should be a MessageType, from DhcpPacket.
5030b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        |additional_options| should be a dictionary that maps from
5130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        dhcp_packet.OPTION_* to values.  For instance:
5230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
5330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        {dhcp_packet.OPTION_SERVER_ID : "10.10.10.1"}
5430b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
5530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        These options are injected into response packets if the client requests
5630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        it.  See inject_options().
5730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
581964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        super(DhcpHandlingRule, self).__init__()
591964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        self._is_final_handler = False
601964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        self._logger = logging.getLogger("dhcp.handling_rule")
6130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        self._options = additional_options
623a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        self._fields = custom_fields
639b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        self._target_time_seconds = None
649b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        self._allowable_time_delta_seconds = 0.5
658d2348b6332131cf784075fb1882ed17e851d399Paul Stewart        self._force_reply_options = []
668962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        self._message_type = message_type
67f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        self._last_warning = None
68f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal
69f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal    def __str__(self):
70f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        if self._last_warning:
71f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal            return '%s (%s)' % (self.__class__.__name__, self._last_warning)
72f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        else:
73f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal            return self.__class__.__name__
741964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
751964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley    @property
761964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley    def logger(self):
771964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        return self._logger
781964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
791964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley    @property
801964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley    def is_final_handler(self):
811964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        return self._is_final_handler
821964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
831964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley    @is_final_handler.setter
841964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley    def is_final_handler(self, value):
851964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        self._is_final_handler = value
861964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
8730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    @property
8830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    def options(self):
8930b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
9030b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        Returns a dictionary that maps from DhcpPacket options to their values.
9130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
9230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        return self._options
931964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
949b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    @property
953a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart    def fields(self):
963a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        """
973a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        Returns a dictionary that maps from DhcpPacket fields to their values.
983a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        """
993a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        return self._fields
1003a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart
1013a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart    @property
1029b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def target_time_seconds(self):
1039b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        """
1049b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        If this is not None, packets will be rejected if they don't fall within
1059b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        |self.allowable_time_delta_seconds| seconds of
1069b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        |self.target_time_seconds|.  A value of None will cause this handler to
1079b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        ignore the target packet time.
1089b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1099b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        Defaults to None.
1109b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        """
1119b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        return self._target_time_seconds
1129b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1139b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    @target_time_seconds.setter
1149b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def target_time_seconds(self, value):
1159b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        self._target_time_seconds = value
1169b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1179b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    @property
1189b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def allowable_time_delta_seconds(self):
1199b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        """
1209b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        A configurable fudge factor for |self.target_time_seconds|.  If a packet
1219b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        comes in at time T and:
1229b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1239b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        delta = abs(T - |self.target_time_seconds|)
1249b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1259b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        Then if delta < |self.allowable_time_delta_seconds|, we accept the
1269b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        packet.  Otherwise we either fail the test or ignore the packet,
1279b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        depending on whether this packet is before or after the window.
1289b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1299b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        Defaults to 0.5 seconds.
1309b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        """
1319b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        return self._allowable_time_delta_seconds
1329b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1339b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    @allowable_time_delta_seconds.setter
1349b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def allowable_time_delta_seconds(self, value):
1359b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        self._allowable_time_delta_seconds = value
1369b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1379b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    @property
1389b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def packet_is_too_late(self):
1399b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        if self.target_time_seconds is None:
1409b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley            return False
1416e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley        delta = time.time() - self.target_time_seconds
1426e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley        logging.debug("Handler received packet %0.2f seconds from target time.",
1436e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley                      delta)
1446e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley        if delta > self._allowable_time_delta_seconds:
1456e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley            logging.info("Packet was too late for handling (+%0.2f seconds)",
1466e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley                         delta - self._allowable_time_delta_seconds)
1479b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley            return True
1489b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        logging.info("Packet was not too late for handling.")
1499b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        return False
1509b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1519b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    @property
1529b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def packet_is_too_soon(self):
1539b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        if self.target_time_seconds is None:
1549b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley            return False
1556e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley        delta = time.time() - self.target_time_seconds
1566e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley        logging.debug("Handler received packet %0.2f seconds from target time.",
1576e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley                      delta)
1586e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley        if -delta > self._allowable_time_delta_seconds:
1596e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley            logging.info("Packet arrived too soon for handling: "
1606e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley                         "(-%0.2f seconds)",
1616e9010b3ec8d09858be425292e4f83c9bb54dbf5Christopher Wiley                         -delta - self._allowable_time_delta_seconds)
1629b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley            return True
1639b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        logging.info("Packet was not too soon for handling.")
1649b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        return False
1659b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
1668d2348b6332131cf784075fb1882ed17e851d399Paul Stewart    @property
1678d2348b6332131cf784075fb1882ed17e851d399Paul Stewart    def force_reply_options(self):
1688d2348b6332131cf784075fb1882ed17e851d399Paul Stewart        return self._force_reply_options
1698d2348b6332131cf784075fb1882ed17e851d399Paul Stewart
1708d2348b6332131cf784075fb1882ed17e851d399Paul Stewart    @force_reply_options.setter
1718d2348b6332131cf784075fb1882ed17e851d399Paul Stewart    def force_reply_options(self, value):
1728d2348b6332131cf784075fb1882ed17e851d399Paul Stewart        self._force_reply_options = value
1738d2348b6332131cf784075fb1882ed17e851d399Paul Stewart
174e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    @property
175e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    def response_packet_count(self):
176e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        return 1
177e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart
178f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal    def emit_warning(self, warning):
179f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        """
180f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        Log a warning, and retain that warning as |_last_warning|.
181f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal
182f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        @param warning: The warning message
183f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        """
184f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        self.logger.warning(warning)
185f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal        self._last_warning = warning
186f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal
1879b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def handle(self, query_packet):
18830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
18930b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        The DhcpTestServer will call this method to ask a handling rule whether
1907d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        it wants to take some action in response to a packet.  The handler
1917d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        should return some combination of RESPONSE_* bits as described above.
19230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
19330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        |packet| is a valid DHCP packet, but the values of fields and presence
19430b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        of options is not guaranteed.
19530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
1969b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        if self.packet_is_too_late:
1979b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley            return RESPONSE_TEST_FAILED
1989b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        if self.packet_is_too_soon:
1999b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley            return RESPONSE_NO_ACTION
2009b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley        return self.handle_impl(query_packet)
2019b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley
2029b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def handle_impl(self, query_packet):
2034df7ac613759f0fd848619864e389454c70bc505Christopher Wiley        logging.error("DhcpHandlingRule.handle_impl() called.")
2047d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        return RESPONSE_TEST_FAILED
2051964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
20630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    def respond(self, query_packet):
20730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
2087d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        Called by the DhcpTestServer to generate a packet to send back to the
2097d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        client.  This method is called if and only if the response returned from
2107d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        handle() had RESPONSE_HAVE_RESPONSE set.
21130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
2121964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        return None
2131964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
21430b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    def inject_options(self, packet, requested_parameters):
21530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
21630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        Adds options listed in the intersection of |requested_parameters| and
2178d2348b6332131cf784075fb1882ed17e851d399Paul Stewart        |self.options| to |packet|.  Also include the options in the
2188d2348b6332131cf784075fb1882ed17e851d399Paul Stewart        intersection of |self.force_reply_options| and |self.options|.
21930b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
22030b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        |packet| is a DhcpPacket.
22130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
22230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        |requested_parameters| is a list of options numbers as you would find in
22330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        a DHCP_DISCOVER or DHCP_REQUEST packet after being parsed by DhcpPacket
22430b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        (e.g. [1, 121, 33, 3, 6, 12]).
22530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
22630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        Subclassed handling rules may call this to inject options into response
22730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        packets to the client.  This process emulates a real DHCP server which
22830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        would have a pool of configuration settings to hand out to DHCP clients
22930b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        upon request.
23030b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
23130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        for option, value in self.options.items():
2328d2348b6332131cf784075fb1882ed17e851d399Paul Stewart            if (option.number in requested_parameters or
2338d2348b6332131cf784075fb1882ed17e851d399Paul Stewart                option in self.force_reply_options):
23430b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                packet.set_option(option, value)
23530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
2363a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart    def inject_fields(self, packet):
2373a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        """
2383a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        Adds fields listed in |self.fields| to |packet|.
2393a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart
2403a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        |packet| is a DhcpPacket.
2413a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart
2423a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        Subclassed handling rules may call this to inject fields into response
2433a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        packets to the client.  This process emulates a real DHCP server which
2443a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        would have a pool of configuration settings to hand out to DHCP clients
2453a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        upon request.
2463a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        """
2473a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        for field, value in self.fields.items():
2483a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart            packet.set_field(field, value)
2493a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart
2508962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal    def is_our_message_type(self, packet):
2518962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        """
2528962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        Checks if the Message Type DHCP Option in |packet| matches the message
2538962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        type handled by this rule. Logs a warning if the types do not match.
2548962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal
2558962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        @param packet: a DhcpPacket
2568962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal
2578962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        @returns True or False
2588962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        """
2598962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        if packet.message_type == self._message_type:
2608962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal            return True
2618962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        else:
262f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal            self.emit_warning("Packet's message type was %s, not %s." % (
263f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal                              packet.message_type.name,
264f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal                              self._message_type.name))
2658962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal            return False
2668962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal
26730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
2681964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wileyclass DhcpHandlingRule_RespondToDiscovery(DhcpHandlingRule):
26930b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    """
27030b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    This handler will accept any DISCOVER packet received by the server. In
27130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    response to such a packet, the handler will construct an OFFER packet
27230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    offering |intended_ip| from a server at |server_ip| (from the constructor).
27330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    """
2741964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley    def __init__(self,
2751964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley                 intended_ip,
2761964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley                 server_ip,
2777d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley                 additional_options,
2783a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart                 custom_fields,
2797d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley                 should_respond=True):
28030b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
28130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        |intended_ip| is an IPv4 address string like "192.168.1.100".
28230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
28330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        |server_ip| is an IPv4 address string like "192.168.1.1".
28430b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
28530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        |additional_options| is handled as explained by DhcpHandlingRule.
28630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
28730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        super(DhcpHandlingRule_RespondToDiscovery, self).__init__(
2888962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal                dhcp_packet.MESSAGE_TYPE_DISCOVERY, additional_options,
2898962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal                custom_fields)
2901964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        self._intended_ip = intended_ip
2911964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        self._server_ip = server_ip
2927d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        self._should_respond = should_respond
2931964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
2949b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def handle_impl(self, query_packet):
2958962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        if not self.is_our_message_type(query_packet):
2967d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            return RESPONSE_NO_ACTION
2977d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley
2981964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        self.logger.info("Received valid DISCOVERY packet.  Processing.")
2997d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        ret = RESPONSE_POP_HANDLER
3001964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        if self.is_final_handler:
3017d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            ret |= RESPONSE_TEST_SUCCEEDED
3027d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        if self._should_respond:
3037d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            ret |= RESPONSE_HAVE_RESPONSE
3047d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        return ret
3051964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley
30630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    def respond(self, query_packet):
3078962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        if not self.is_our_message_type(query_packet):
3081964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley            return None
3098962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal
3101964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley        self.logger.info("Responding to DISCOVERY packet.")
31130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        response_packet = dhcp_packet.DhcpPacket.create_offer_packet(
31230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                query_packet.transaction_id,
31330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                query_packet.client_hw_address,
3141964458a980b17c71ea10f9c00c59fe15bb83a96Christopher Wiley                self._intended_ip,
31530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                self._server_ip)
31630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        requested_parameters = query_packet.get_option(
31730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                dhcp_packet.OPTION_PARAMETER_REQUEST_LIST)
31830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        if requested_parameters is not None:
31930b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley            self.inject_options(response_packet, requested_parameters)
3203a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        self.inject_fields(response_packet)
32130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        return response_packet
32230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
32319b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley
3242b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawalclass DhcpHandlingRule_RejectRequest(DhcpHandlingRule):
3252b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal    """
3262b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal    This handler receives a REQUEST packet, and responds with a NAK.
3272b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal    """
3282b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal    def __init__(self):
3292b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        super(DhcpHandlingRule_RejectRequest, self).__init__(
3302b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal                dhcp_packet.MESSAGE_TYPE_REQUEST, {}, {})
3312b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        self._should_respond = True
3322b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal
3332b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal    def handle_impl(self, query_packet):
3342b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        if not self.is_our_message_type(query_packet):
3352b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal            return RESPONSE_NO_ACTION
3362b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal
3372b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        ret = RESPONSE_POP_HANDLER
3382b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        if self.is_final_handler:
3392b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal            ret |= RESPONSE_TEST_SUCCEEDED
3402b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        if self._should_respond:
3412b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal            ret |= RESPONSE_HAVE_RESPONSE
3422b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        return ret
3432b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal
3442b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal    def respond(self, query_packet):
3452b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        if not self.is_our_message_type(query_packet):
3462b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal            return None
3472b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal
3482b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        self.logger.info("NAKing the REQUEST packet.")
3492b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        response_packet = dhcp_packet.DhcpPacket.create_nak_packet(
3502b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal            query_packet.transaction_id, query_packet.client_hw_address)
3512b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal        return response_packet
3522b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal
3532b680b292bdc3dcba5398563fb25b3613796039fmukesh agrawal
35419b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wileyclass DhcpHandlingRule_RespondToRequest(DhcpHandlingRule):
35530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    """
3566c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    This handler accepts any REQUEST packet that contains options for SERVER_ID
3576c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    and REQUESTED_IP that match |expected_server_ip| and |expected_requested_ip|
3586c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    respectively.  It responds with an ACKNOWLEDGEMENT packet from a DHCP server
3596c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    at |response_server_ip| granting |response_granted_ip| to a client at the
3606c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    address given in the REQUEST packet.  If |response_server_ip| or
3616c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    |response_granted_ip| are not given, then they default to
3626c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    |expected_server_ip| and |expected_requested_ip| respectively.
36330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    """
36419b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley    def __init__(self,
36519b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley                 expected_requested_ip,
36619b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley                 expected_server_ip,
36730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                 additional_options,
3683a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart                 custom_fields,
3697d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley                 should_respond=True,
3706c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                 response_server_ip=None,
37169769b6f92468592d0f22679ed0e683aa88505e8Paul Stewart                 response_granted_ip=None,
37269769b6f92468592d0f22679ed0e683aa88505e8Paul Stewart                 expect_server_ip_set=True):
37330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
37430b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        All *_ip arguments are IPv4 address strings like "192.168.1.101".
37530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley
37630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        |additional_options| is handled as explained by DhcpHandlingRule.
37730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        """
37830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        super(DhcpHandlingRule_RespondToRequest, self).__init__(
3798962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal                dhcp_packet.MESSAGE_TYPE_REQUEST, additional_options,
3808962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal                custom_fields)
38119b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley        self._expected_requested_ip = expected_requested_ip
38219b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley        self._expected_server_ip = expected_server_ip
3837d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        self._should_respond = should_respond
3846c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        self._granted_ip = response_granted_ip
3856c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        self._server_ip = response_server_ip
38669769b6f92468592d0f22679ed0e683aa88505e8Paul Stewart        self._expect_server_ip_set = expect_server_ip_set
3876c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        if self._granted_ip is None:
38819b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley            self._granted_ip = self._expected_requested_ip
38919b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley        if self._server_ip is None:
39019b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley            self._server_ip = self._expected_server_ip
39119b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley
3929b95bdd5945e23f48bbc5910f34a6a571444642aChristopher Wiley    def handle_impl(self, query_packet):
3938962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        if not self.is_our_message_type(query_packet):
3947d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            return RESPONSE_NO_ACTION
39519b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley
39619b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley        self.logger.info("Received REQUEST packet, checking fields...")
39730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        server_ip = query_packet.get_option(dhcp_packet.OPTION_SERVER_ID)
39830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        requested_ip = query_packet.get_option(dhcp_packet.OPTION_REQUESTED_IP)
39969769b6f92468592d0f22679ed0e683aa88505e8Paul Stewart        server_ip_provided = server_ip is not None
40069769b6f92468592d0f22679ed0e683aa88505e8Paul Stewart        if ((server_ip_provided != self._expect_server_ip_set) or
40169769b6f92468592d0f22679ed0e683aa88505e8Paul Stewart            (requested_ip is None)):
40219b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley            self.logger.info("REQUEST packet did not have the expected "
40319b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley                             "options, discarding.")
4047d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            return RESPONSE_NO_ACTION
40519b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley
40669769b6f92468592d0f22679ed0e683aa88505e8Paul Stewart        if server_ip_provided and server_ip != self._expected_server_ip:
407f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal            self.emit_warning("REQUEST packet's server ip did not match our "
408f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal                              "expectations; expected %s but got %s" %
409f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal                              (self._expected_server_ip, server_ip))
4107d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            return RESPONSE_NO_ACTION
41119b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley
41290c515dd484c31e2973142660a71bd596314a89bChristopher Wiley        if requested_ip != self._expected_requested_ip:
413f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal            self.emit_warning("REQUEST packet's requested IP did not match "
414f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal                              "our expectations; expected %s but got %s" %
415f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal                              (self._expected_requested_ip, requested_ip))
4167d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            return RESPONSE_NO_ACTION
41719b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley
41819b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley        self.logger.info("Received valid REQUEST packet, processing")
4197d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        ret = RESPONSE_POP_HANDLER
42019b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley        if self.is_final_handler:
4217d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            ret |= RESPONSE_TEST_SUCCEEDED
4227d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        if self._should_respond:
4237d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley            ret |= RESPONSE_HAVE_RESPONSE
4247d36aac2e68c1189b9c502cd095ed0edcef152c1Christopher Wiley        return ret
42519b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley
42630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley    def respond(self, query_packet):
4278962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        if not self.is_our_message_type(query_packet):
42819b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley            return None
42919b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley
43019b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley        self.logger.info("Responding to REQUEST packet.")
43130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        response_packet = dhcp_packet.DhcpPacket.create_acknowledgement_packet(
43230b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                query_packet.transaction_id,
43330b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                query_packet.client_hw_address,
43419b39f62edb37c2971b4a32fb9aad664ccd36c90Christopher Wiley                self._granted_ip,
43530b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                self._server_ip)
43630b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        requested_parameters = query_packet.get_option(
43730b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley                dhcp_packet.OPTION_PARAMETER_REQUEST_LIST)
43830b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        if requested_parameters is not None:
43930b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley            self.inject_options(response_packet, requested_parameters)
4403a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart        self.inject_fields(response_packet)
44130b095f46114605a4013a2840c02ea90cf76f9e9Christopher Wiley        return response_packet
4426c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley
4436c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley
4446c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wileyclass DhcpHandlingRule_RespondToPostT2Request(
4456c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        DhcpHandlingRule_RespondToRequest):
4466c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    """
4476c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    This handler is a lot like DhcpHandlingRule_RespondToRequest except that it
4486c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    expects request packets like those sent after the T2 deadline (see RFC
4496c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    2131).  This is the only time that you can find a request packet without the
4506c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    SERVER_ID option.  It responds to packets in exactly the same way.
4516c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    """
4526c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    def __init__(self,
4536c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                 expected_requested_ip,
4546c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                 response_server_ip,
4556c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                 additional_options,
4563a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart                 custom_fields,
4576c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                 should_respond=True,
4586c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                 response_granted_ip=None):
4596c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        """
4606c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        All *_ip arguments are IPv4 address strings like "192.168.1.101".
4616c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley
4626c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        |additional_options| is handled as explained by DhcpHandlingRule.
4636c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        """
4646c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        super(DhcpHandlingRule_RespondToPostT2Request, self).__init__(
4656c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                expected_requested_ip,
4666c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                None,
4676c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                additional_options,
4683a37ed1648539f17b30e0e1eabcfff11fed2a8f5Paul Stewart                custom_fields,
4696c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                should_respond=should_respond,
4706c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                response_server_ip=response_server_ip,
4716c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                response_granted_ip=response_granted_ip)
4726c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley
4736c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley    def handle_impl(self, query_packet):
4748962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        if not self.is_our_message_type(query_packet):
4756c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley            return RESPONSE_NO_ACTION
4766c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley
4776c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        self.logger.info("Received REQUEST packet, checking fields...")
4786c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        if query_packet.get_option(dhcp_packet.OPTION_SERVER_ID) is not None:
4796c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley            self.logger.info("REQUEST packet had a SERVER_ID option, which it "
4806c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                             "is not expected to have, discarding.")
4816c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley            return RESPONSE_NO_ACTION
4826c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley
4836c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        requested_ip = query_packet.get_option(dhcp_packet.OPTION_REQUESTED_IP)
4846c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        if requested_ip is None:
4856c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley            self.logger.info("REQUEST packet did not have the expected "
4866c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley                             "request ip option at all, discarding.")
4876c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley            return RESPONSE_NO_ACTION
4886c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley
4896c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        if requested_ip != self._expected_requested_ip:
490f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal            self.emit_warning("REQUEST packet's requested IP did not match "
491f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal                              "our expectations; expected %s but got %s" %
492f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal                              (self._expected_requested_ip, requested_ip))
4936c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley            return RESPONSE_NO_ACTION
4946c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley
4956c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        self.logger.info("Received valid post T2 REQUEST packet, processing")
4966c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        ret = RESPONSE_POP_HANDLER
4976c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        if self.is_final_handler:
4986c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley            ret |= RESPONSE_TEST_SUCCEEDED
4996c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        if self._should_respond:
5006c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley            ret |= RESPONSE_HAVE_RESPONSE
5016c921a7efb5c263d47322aaf77ae7adfa6c47752Christopher Wiley        return ret
5026c6a1425996beee22a4a53639197308e250b708cPaul Stewart
5036c6a1425996beee22a4a53639197308e250b708cPaul Stewart
5046c6a1425996beee22a4a53639197308e250b708cPaul Stewartclass DhcpHandlingRule_AcceptRelease(DhcpHandlingRule):
5056c6a1425996beee22a4a53639197308e250b708cPaul Stewart    """
5066c6a1425996beee22a4a53639197308e250b708cPaul Stewart    This handler accepts any RELEASE packet that contains an option for
5076c6a1425996beee22a4a53639197308e250b708cPaul Stewart    SERVER_ID matches |expected_server_ip|.  There is no response to this
5086c6a1425996beee22a4a53639197308e250b708cPaul Stewart    packet.
5096c6a1425996beee22a4a53639197308e250b708cPaul Stewart    """
5106c6a1425996beee22a4a53639197308e250b708cPaul Stewart    def __init__(self,
5116c6a1425996beee22a4a53639197308e250b708cPaul Stewart                 expected_server_ip,
5126c6a1425996beee22a4a53639197308e250b708cPaul Stewart                 additional_options,
5136c6a1425996beee22a4a53639197308e250b708cPaul Stewart                 custom_fields):
5146c6a1425996beee22a4a53639197308e250b708cPaul Stewart        """
5156c6a1425996beee22a4a53639197308e250b708cPaul Stewart        All *_ip arguments are IPv4 address strings like "192.168.1.101".
5166c6a1425996beee22a4a53639197308e250b708cPaul Stewart
5176c6a1425996beee22a4a53639197308e250b708cPaul Stewart        |additional_options| is handled as explained by DhcpHandlingRule.
5186c6a1425996beee22a4a53639197308e250b708cPaul Stewart        """
5196c6a1425996beee22a4a53639197308e250b708cPaul Stewart        super(DhcpHandlingRule_AcceptRelease, self).__init__(
5208962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal                dhcp_packet.MESSAGE_TYPE_RELEASE, additional_options,
5218962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal                custom_fields)
5226c6a1425996beee22a4a53639197308e250b708cPaul Stewart        self._expected_server_ip = expected_server_ip
5236c6a1425996beee22a4a53639197308e250b708cPaul Stewart
5246c6a1425996beee22a4a53639197308e250b708cPaul Stewart    def handle_impl(self, query_packet):
5258962b2d93ea3ac596b0ac46b1798c67bccd5e3d2mukesh agrawal        if not self.is_our_message_type(query_packet):
5266c6a1425996beee22a4a53639197308e250b708cPaul Stewart            return RESPONSE_NO_ACTION
5276c6a1425996beee22a4a53639197308e250b708cPaul Stewart
5286c6a1425996beee22a4a53639197308e250b708cPaul Stewart        self.logger.info("Received RELEASE packet, checking fields...")
5296c6a1425996beee22a4a53639197308e250b708cPaul Stewart        server_ip = query_packet.get_option(dhcp_packet.OPTION_SERVER_ID)
530f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        if server_ip is None:
5316c6a1425996beee22a4a53639197308e250b708cPaul Stewart            self.logger.info("RELEASE packet did not have the expected "
5326c6a1425996beee22a4a53639197308e250b708cPaul Stewart                             "options, discarding.")
5336c6a1425996beee22a4a53639197308e250b708cPaul Stewart            return RESPONSE_NO_ACTION
5346c6a1425996beee22a4a53639197308e250b708cPaul Stewart
5356c6a1425996beee22a4a53639197308e250b708cPaul Stewart        if server_ip != self._expected_server_ip:
536f56f7fde778822d17965bcd73c67dac1bb422829mukesh agrawal            self.emit_warning("RELEASE packet's server ip did not match our "
5376c6a1425996beee22a4a53639197308e250b708cPaul Stewart                                "expectations; expected %s but got %s" %
5386c6a1425996beee22a4a53639197308e250b708cPaul Stewart                                (self._expected_server_ip, server_ip))
5396c6a1425996beee22a4a53639197308e250b708cPaul Stewart            return RESPONSE_NO_ACTION
5406c6a1425996beee22a4a53639197308e250b708cPaul Stewart
5416c6a1425996beee22a4a53639197308e250b708cPaul Stewart        self.logger.info("Received valid RELEASE packet, processing")
5426c6a1425996beee22a4a53639197308e250b708cPaul Stewart        ret = RESPONSE_POP_HANDLER
5436c6a1425996beee22a4a53639197308e250b708cPaul Stewart        if self.is_final_handler:
5446c6a1425996beee22a4a53639197308e250b708cPaul Stewart            ret |= RESPONSE_TEST_SUCCEEDED
5456c6a1425996beee22a4a53639197308e250b708cPaul Stewart        return ret
546e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart
547e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart
548e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewartclass DhcpHandlingRule_RejectAndRespondToRequest(
549e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        DhcpHandlingRule_RespondToRequest):
550e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    """
551e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    This handler accepts any REQUEST packet that contains options for SERVER_ID
552e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    and REQUESTED_IP that match |expected_server_ip| and |expected_requested_ip|
553e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    respectively.  It responds with both an ACKNOWLEDGEMENT packet from a DHCP
554e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    server as well as a NAK, in order to simulate a network with two conflicting
555e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    servers.
556e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    """
557e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    def __init__(self,
558e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                 expected_requested_ip,
559e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                 expected_server_ip,
560e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                 additional_options,
561e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                 custom_fields,
562e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                 send_nak_before_ack):
563e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        super(DhcpHandlingRule_RejectAndRespondToRequest, self).__init__(
564e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                expected_requested_ip,
565e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                expected_server_ip,
566e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                additional_options,
567e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                custom_fields)
568e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        self._send_nak_before_ack = send_nak_before_ack
569e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        self._response_counter = 0
570e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart
571e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    @property
572e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    def response_packet_count(self):
573e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        return 2
574e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart
575e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart    def respond(self, query_packet):
576e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        """ Respond to |query_packet| with a NAK then ACK or ACK then NAK. """
577e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        if ((self._response_counter == 0 and self._send_nak_before_ack) or
578e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart            (self._response_counter != 0 and not self._send_nak_before_ack)):
579e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart            response_packet = dhcp_packet.DhcpPacket.create_nak_packet(
580e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                query_packet.transaction_id, query_packet.client_hw_address)
581e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        else:
582e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart            response_packet = super(DhcpHandlingRule_RejectAndRespondToRequest,
583e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart                                    self).respond(query_packet)
584e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        self._response_counter += 1
585e1d4fcb22f2f3f505a18e5078a73045872fb03ffPaul Stewart        return response_packet
586f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart
587f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart
588f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewartclass DhcpHandlingRule_AcceptDecline(DhcpHandlingRule):
589f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart    """
590f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart    This handler accepts any DECLINE packet that contains an option for
591f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart    SERVER_ID matches |expected_server_ip|.  There is no response to this
592f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart    packet.
593f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart    """
594f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart    def __init__(self,
595f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart                 expected_server_ip,
596f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart                 additional_options,
597f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart                 custom_fields):
598f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        """
599f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        All *_ip arguments are IPv4 address strings like "192.168.1.101".
600f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart
601f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        |additional_options| is handled as explained by DhcpHandlingRule.
602f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        """
603f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        super(DhcpHandlingRule_AcceptDecline, self).__init__(
604f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart                dhcp_packet.MESSAGE_TYPE_DECLINE, additional_options,
605f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart                custom_fields)
606f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        self._expected_server_ip = expected_server_ip
607f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart
608f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart    def handle_impl(self, query_packet):
609f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        if not self.is_our_message_type(query_packet):
610f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart            return RESPONSE_NO_ACTION
611f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart
612f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        self.logger.info("Received DECLINE packet, checking fields...")
613f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        server_ip = query_packet.get_option(dhcp_packet.OPTION_SERVER_ID)
614f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        if server_ip is None:
615f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart            self.logger.info("DECLINE packet did not have the expected "
616f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart                             "options, discarding.")
617f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart            return RESPONSE_NO_ACTION
618f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart
619f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        if server_ip != self._expected_server_ip:
620f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart            self.emit_warning("DECLINE packet's server ip did not match our "
621f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart                                "expectations; expected %s but got %s" %
622f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart                                (self._expected_server_ip, server_ip))
623f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart            return RESPONSE_NO_ACTION
624f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart
625f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        self.logger.info("Received valid DECLINE packet, processing")
626f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        ret = RESPONSE_POP_HANDLER
627f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        if self.is_final_handler:
628f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart            ret |= RESPONSE_TEST_SUCCEEDED
629f7efffe4912a75b932e8ff78b90bf1f5b697b4eaPaul Stewart        return ret
630