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