Browse Source

[master] Merge branch 'trac2984' (additional DHCPv6 hooks)

Conflicts:
	ChangeLog
	src/bin/dhcp6/dhcp6_srv.cc
Tomek Mrugalski 11 years ago
parent
commit
fef265b2e3

+ 9 - 4
ChangeLog

@@ -1,9 +1,14 @@
-655.	[func]	tmark
+656.	[func]		tomek
+	Additional hooks (buffer6_receive, lease6_renew,
+	lease6_release, buffer6_send) added to the DHCPv6 server.
+	(Trac #2984, git 540dd0449121094a56f294c500c2ed811f6016b6)
+
+655.	[func]		tmark
 	Added D2UpdateMgr class to b10-dhcp-ddns. This class is the b10-dhcp-ddns
 	task master, instantiating and supervising transactions that carry out the
 	DNS updates needed to fulfill the requests (NameChangeRequests) received
 	from b10-dhcp-ddns clients (e.g. DHCP servers).
-	(Trac #3059 git d72675617d6b60e3eb6160305738771f015849ba) 
+	(Trac #3059 git d72675617d6b60e3eb6160305738771f015849ba)
 
 654.	[bug]		stephen
 	Always clear "skip" flag before calling any callouts on a hook.
@@ -61,7 +66,7 @@
 	(Trac #3054, git 0f845ed94f462dee85b67f056656b2a197878b04)
 
 645.	[func]		tomek
-	Added initial set of hooks (pk4_receive, subnet4_select,
+	Added initial set of hooks (pkt4_receive, subnet4_select,
 	lease4_select, pkt4_send) to the DHCPv6 server.
 	(Trac #2994, git be65cfba939a6a7abd3c93931ce35c33d3e8247b)
 
@@ -82,7 +87,7 @@
 	(Trac #3056, git 92ebabdbcf6168666b03d7f7fbb31f899be39322)
 
 642.	[func]		tomek
-	Added initial set of hooks (pk6_receive, subnet6_select,
+	Added initial set of hooks (pkt6_receive, subnet6_select,
 	lease6_select, pkt6_send) to the DHCPv6 server.
 	(Trac #2995, git d6de376f97313ba40fef989e4a437d184fdf70cc)
 

+ 113 - 13
src/bin/dhcp6/dhcp6_hooks.dox

@@ -49,20 +49,46 @@ The following list is ordered by appearance of specific hook points during
 packet processing. Hook points that are not specific to packet processing
 (e.g. lease expiration) will be added to the end of this list.
 
+ @subsection dhcpv6HooksBuffer6Receive buffer6_receive
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when an incoming DHCPv6
+   packet is received and the data stored in a buffer. The sole argument -
+   query6 - contains a pointer to an isc::dhcp::Pkt6 object that contains
+   the received information stored in the data_ field. Basic information
+   like protocol, source/destination addresses and ports are set, but
+   the contents of the buffer have not yet been parsed.  That means that
+   the options_ field (that will eventually contain a list of objects
+   representing the received options) is empty, so none of the methods
+   that operate on it (e.g., getOption()) will work. The primary purpose
+   of this early call is to offer the ability to modify incoming packets
+   in their raw form. Unless you need to access to the raw data, it is
+   usually better to install your callout on the pkt6_receive hook point.
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the
+   server will assume that the callout parsed the buffer and added then
+   necessary option objects to the options_ field; the server will not
+   do any parsing. If the callout sets the skip flag but does not parse
+   the buffer, the server will most probably drop the packet due to
+   the absence of mandatory options. If you want to drop the packet,
+   see the description of the skip flag in the pkt6_receive hook point.
+
  @subsection dhcpv6HooksPkt6Receive pkt6_receive
 
  - @b Arguments:
    - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
 
- - @b Description: this callout is executed when an incoming DHCPv6
+ - @b Description: This callout is executed when an incoming DHCPv6
    packet is received and its content is parsed. The sole argument -
    query6 - contains a pointer to an isc::dhcp::Pkt6 object that contains
    all information regarding incoming packet, including its source and
-   destination addresses, interface over which it was received, a list
+   destination addresses, the interface over which it was received, a list
    of all options present within and relay information.  All fields of
    the Pkt6 object can be modified at this time, except data_. (data_
    contains the incoming packet as raw buffer. By the time this hook is
-   reached, that information has already parsed and is available though
+   reached, that information has already been parsed and is available though
    other fields in the Pkt6 object.  For this reason, it doesn't make
    sense to modify it.)
 
@@ -77,7 +103,7 @@ packet processing. Hook points that are not specific to packet processing
    - name: @b subnet6, type: isc::dhcp::Subnet6Ptr, direction: <b>in/out</b>
    - name: @b subnet6collection, type: const isc::dhcp::Subnet6Collection *, direction: <b>in</b>
 
- - @b Description: this callout is executed when a subnet is being
+ - @b Description: This callout is executed when a subnet is being
    selected for the incoming packet. All parameters, addresses and
    prefixes will be assigned from that subnet. A callout can select a
    different subnet if it wishes so, the list of all subnets currently
@@ -89,14 +115,14 @@ packet processing. Hook points that are not specific to packet processing
    will continue, but will be severely limited (i.e. only global options
    will be assigned).
 
-@subsection dhcpv6HooksLeaseSelect lease6_select
+@subsection dhcpv6HooksLease6Select lease6_select
 
  - @b Arguments:
    - name: @b subnet6, type: isc::dhcp::Subnet6Ptr, direction: <b>in</b>
    - name: @b fake_allocation, type: bool, direction: <b>in</b>
    - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
 
- - @b Description: this callout is executed after the server engine
+ - @b Description: This callout is executed after the server engine
    has selected a lease for client's request but before the lease
    has been inserted into the database. Any modifications made to the
    isc::dhcp::Lease6 object will be stored in the lease's record in the
@@ -115,24 +141,98 @@ packet processing. Hook points that are not specific to packet processing
    Packet processing will continue and the client may get other addresses
    or prefixes if it requested more than one address and/or prefix.
 
+@subsection dhcpv6HooksLease6Renew lease6_renew
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::PktPtr, direction: <b>in</b>
+   - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+   - name: @b ia_na, type: boost::shared_ptr<Option6IA>, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when the server engine is
+   about to renew an existing lease. The client's request is provided as
+   the query6 argument and the existing lease with the appropriate fields
+   already modified is given in the lease6 argument. The final argument,
+   ia_na, is the IA_NA option that will be sent back to the client.
+   Callouts installed on the lease6_renew may modify the content of
+   the lease6 object. Care should be taken however, as that modified
+   information will be written to the database without any further
+   checking. \n\n Although the envisaged usage assumes modification of T1,
+   T2, preferred and valid lifetimes only, other parameters associated
+   with the lease may be modified as well. The only exception is the addr_
+   field, which must not be modified as it is used by the database to
+   select the existing lease to be updated. Care should also be taken to
+   modify the ia_na argument to match any changes in the lease6 argument.
+   If a client sends more than one IA_NA option, callouts will be called
+   separately for each IA_NA instance. The callout will be called only
+   when the update is valid, i.e. conditions such as an invalid addresses
+   or invalid iaid renewal attempts will not trigger this hook point.
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease6_renew'
+   sets the skip flag, the server will not renew the lease. Under these
+   circumstances, the callout should modify the ia_na argument to reflect
+   this fact; otherwise the client will think the lease was renewed and continue
+   to operate under this assumption.
+
+@subsection dhcpv6HooksLease6Release lease6_release
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::PktPtr, direction: <b>in</b>
+   - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when the server engine is
+   about to release an existing lease. The client's request is provided
+   as the query6 argument and the existing lease is given in the lease6
+   argument.  Although the lease6 structure may be modified, it doesn't
+   make sense to do so as it will be destroyed immediately the callouts
+   finish execution.
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease6_release'
+   sets the skip flag, the server will not delete the lease, which will
+   remain in the database until it expires. However, the server will send out
+   the response back to the client as if it did.
+
 @subsection dhcpv6HooksPkt6Send pkt6_send
 
  - @b Arguments:
    - name: @b response6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
 
- - @b Description: this callout is executed when server's response
+ - @b Description: This callout is executed when server's response
    is about to be send back to the client. The sole argument - response6 -
    contains a pointer to an isc::dhcp::Pkt6 object that contains the
    packet, with set source and destination addresses, interface over which
    it will be send, list of all options and relay information.  All fields
-   of the Pkt6 object can be modified at this time, except bufferOut_.
-   (This is scratch space used for constructing the packet after all
-   pkt6_send callouts are complete, so any changes to that field will
-   be overwritten.)
+   of the Pkt6 object can be modified at this time.  It should be noted that
+   unless the callout sets the skip flag (see below), anything placed in the
+   bufferOut_ field will be overwritten when the callout returns.
+   (bufferOut_ is scratch space used for constructing the packet.)
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server
+   will assume that the callout did pack the transaction-id, message type and
+   option objects into the bufferOut_ field and will skip packing part.
+   Note that if the callout sets skip flag, but did not prepare the
+   output buffer, the server will send a zero sized message that will be
+   ignored by the client. If you want to drop the packet, please see
+   skip flag in the buffer6_send hook point.
+
+@subsection dhcpv6HooksBuffer6Send buffer6_send
+
+ - @b Arguments:
+   - name: @b response6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
 
- - <b>Skip flag action</b>: if any callout sets the skip flag, the server
+ - @b Description: This callout is executed when server's response is
+   assembled into binary form and is about to be send back to the
+   client. The sole argument - response6 - contains a pointer to an
+   isc::dhcp::Pkt6 object that contains the packet, with set source and
+   destination addresses, interface over which it will be sent, list of
+   all options and relay information. All options are already encoded
+   in bufferOut_ field. It doesn't make sense to modify anything but the
+   contents of bufferOut_ at this time (although if it is a requirement
+   to modify that data, it will probably be found easier to modify the
+   option objects in a callout attached to the pkt6_send hook).
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server
    will drop this response packet. However, the original request packet
-   from a client was processed, so server's state was most likely changed
+   from a client has been processed, so server's state has most likely changed
    (e.g. lease was allocated). Setting this flag merely stops the change
    being communicated to the client.
 

+ 45 - 6
src/bin/dhcp6/dhcp6_messages.mes

@@ -75,22 +75,50 @@ This message is printed when DHCPv6 server disables an interface from being
 used to receive DHCPv6 traffic. Sockets on this interface will not be opened
 by the Interface Manager until interface is enabled.
 
-% DHCP6_HOOK_PACKET_RCVD_SKIP received DHCPv6 packet was dropped, because a callout set skip flag.
+% DHCP6_HOOK_BUFFER_RCVD_SKIP received DHCPv6 buffer was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on buffer6_receive
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+
+% DHCP6_HOOK_BUFFER_SEND_SKIP prepared DHCPv6 response was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on buffer6_receive
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+Server completed all the processing (e.g. may have assigned, updated
+or released leases), but the response will not be send to the client.
+
+% DHCP6_HOOK_LEASE6_RENEW_SKIP DHCPv6 lease was not renewed because a callout set the skip flag.
+This debug message is printed when a callout installed on lease6_renew
+hook point set the skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to not renew a lease. If
+client requested renewal of multiples leases (by sending multiple IA
+options), the server will skip the renewal of the one in question and
+will proceed with other renewals as usual.
+
+% DHCP6_HOOK_LEASE6_RELEASE_SKIP DHCPv6 lease was not released because a callout set the skip flag.
+This debug message is printed when a callout installed on lease6_release
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to not release
+a lease. If client requested release of multiples leases (by sending
+multiple IA options), the server will retains this particular lease and
+will proceed with other renewals as usual.
+
+% DHCP6_HOOK_PACKET_RCVD_SKIP received DHCPv6 packet was dropped because a callout set the skip flag.
 This debug message is printed when a callout installed on the pkt6_receive
-hook point sets the skip flag. For this particular hook point, the
+hook point set the skip flag. For this particular hook point, the
 setting of the flag by a callout instructs the server to drop the packet.
 
-% DHCP6_HOOK_PACKET_SEND_SKIP prepared DHCPv6 response was not sent, because a callout set skip flag.
+% DHCP6_HOOK_PACKET_SEND_SKIP prepared DHCPv6 response was not sent because a callout set the skip flag.
 This debug message is printed when a callout installed on the pkt6_send
-hook point sets the skip flag. For this particular hook point, the setting
+hook point set the skip flag. For this particular hook point, the setting
 of the flag by a callout instructs the server to drop the packet. This
 effectively means that the client will not get any response, even though
 the server processed client's request and acted on it (e.g. possibly
 allocated a lease).
 
-% DHCP6_HOOK_SUBNET6_SELECT_SKIP no subnet was selected, because a callout set skip flag.
+% DHCP6_HOOK_SUBNET6_SELECT_SKIP no subnet was selected because a callout set the skip flag.
 This debug message is printed when a callout installed on the
-subnet6_select hook point sets the skip flag. For this particular hook
+subnet6_select hook point set the skip flag. For this particular hook
 point, the setting of the flag instructs the server not to choose a
 subnet, an action that severely limits further processing; the server
 will be only able to offer global options - no addresses or prefixes
@@ -165,6 +193,11 @@ This error is output if the IPv6 DHCP server fails to send an assembled
 DHCP message to a client. The reason for the error is included in the
 message.
 
+% DHCP6_PACK_FAIL failed to assemble response correctly
+This error is output if the server failed to assemble the data to be
+returned to the client into a valid packet.  The reason is most likely
+to be to a programming error: please raise a bug report.
+
 % DHCP6_PARSER_COMMIT_EXCEPTION parser failed to commit changes
 On receipt of message containing details to a change of the IPv6 DHCP
 server configuration, a set of parsers were successfully created, but one
@@ -339,6 +372,12 @@ to a misconfiguration of the server. The packet processing will continue, but
 the response will only contain generic configuration parameters and no
 addresses or prefixes.
 
+% DHCP6_UNKNOWN_MSG_RECEIVED received unknown message (type %d) on interface %2
+This debug message is printed when server receives a message of unknown type.
+That could either mean missing functionality or invalid or broken relay or client.
+The list of formally defined message types is available here:
+www.iana.org/assignments/dhcpv6-parameters.
+
 % DHCP6_UNKNOWN_RELEASE received RELEASE from unknown client (duid=%1, iaid=%2)
 This warning message is printed when client attempts to release a lease,
 but no such lease is known by the server. See DHCP6_UNKNOWN_RENEW for

+ 285 - 119
src/bin/dhcp6/dhcp6_srv.cc

@@ -60,15 +60,23 @@ namespace {
 
 /// Structure that holds registered hook indexes
 struct Dhcp6Hooks {
+    int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
     int hook_index_pkt6_receive_;   ///< index for "pkt6_receive" hook point
     int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
+    int hook_index_lease6_renew_;   ///< index for "lease6_renew" hook point
+    int hook_index_lease6_release_; ///< index for "lease6_release" hook point
     int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
+    int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
 
     /// Constructor that registers hook points for DHCPv6 engine
     Dhcp6Hooks() {
+        hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
         hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
         hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
+        hook_index_lease6_renew_   = HooksManager::registerHook("lease6_renew");
+        hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
         hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
+        hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
     }
 };
 
@@ -94,8 +102,7 @@ namespace dhcp {
 static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
-:alloc_engine_(), serverid_(), shutdown_(true), hook_index_pkt6_receive_(-1),
-    hook_index_subnet6_select_(-1), hook_index_pkt6_send_(-1), port_(port)
+:alloc_engine_(), serverid_(), shutdown_(true), port_(port)
 {
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
@@ -129,17 +136,11 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
                 LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
                     .arg(duid_file);
             }
-
         }
 
         // Instantiate allocation engine
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
 
-        // Register hook points
-        hook_index_pkt6_receive_   = Hooks.hook_index_pkt6_receive_;
-        hook_index_subnet6_select_ = Hooks.hook_index_subnet6_select_;
-        hook_index_pkt6_send_      = Hooks.hook_index_pkt6_send_;
-
         /// @todo call loadLibraries() when handling configuration changes
 
     } catch (const std::exception &e) {
@@ -191,146 +192,236 @@ bool Dhcpv6Srv::run() {
             LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
         }
 
-        if (query) {
-            if (!query->unpack()) {
-                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
-                          DHCP6_PACKET_PARSE_FAIL);
-                continue;
-            }
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
-                      .arg(query->getName());
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
-                      .arg(static_cast<int>(query->getType()))
-                      .arg(query->getBuffer().getLength())
-                      .arg(query->toText());
-
-            // Let's execute all callouts registered for packet_received
-            if (HooksManager::getHooksManager().calloutsPresent(hook_index_pkt6_receive_)) {
-                CalloutHandlePtr callout_handle = getCalloutHandle(query);
+        // Timeout may be reached or signal received, which breaks select() with no packet received
+        if (!query) {
+            continue;
+        }
 
-                // Delete previously set arguments
-                callout_handle->deleteAllArguments();
+        bool skip_unpack = false;
 
-                // Pass incoming packet as argument
-                callout_handle->setArgument("query6", query);
+        // The packet has just been received so contains the uninterpreted wire
+        // data; execute callouts registered for buffer6_receive.
+        if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
+            CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
-                // Call callouts
-                HooksManager::callCallouts(hook_index_pkt6_receive_, *callout_handle);
+            // Delete previously set arguments
+            callout_handle->deleteAllArguments();
 
-                // Callouts decided to skip the next processing step. The next
-                // processing step would to process the packet, so skip at this
-                // stage means drop.
-                if (callout_handle->getSkip()) {
-                    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP);
-                    continue;
-                }
+            // Pass incoming packet as argument
+            callout_handle->setArgument("query6", query);
 
-                callout_handle->getArgument("query6", query);
+            // Call callouts
+            HooksManager::callCallouts(Hooks.hook_index_buffer6_receive_, *callout_handle);
+
+            // Callouts decided to skip the next processing step. The next
+            // processing step would to parse the packet, so skip at this
+            // stage means that callouts did the parsing already, so server
+            // should skip parsing.
+            if (callout_handle->getSkip()) {
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_RCVD_SKIP);
+                skip_unpack = true;
             }
 
-            try {
-                switch (query->getType()) {
-                case DHCPV6_SOLICIT:
-                    rsp = processSolicit(query);
-                    break;
+            callout_handle->getArgument("query6", query);
+        }
 
-                case DHCPV6_REQUEST:
-                    rsp = processRequest(query);
-                    break;
+        // Unpack the packet information unless the buffer6_receive callouts
+        // indicated they did it
+        if (!skip_unpack) {
+            if (!query->unpack()) {
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                          DHCP6_PACKET_PARSE_FAIL);
+                continue;
+            }
+        }
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
+            .arg(query->getName());
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
+            .arg(static_cast<int>(query->getType()))
+            .arg(query->getBuffer().getLength())
+            .arg(query->toText());
+
+        // At this point the information in the packet has been unpacked into
+        // the various packet fields and option objects has been cretated.
+        // Execute callouts registered for packet6_receive.
+        if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
+            CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+            // Delete previously set arguments
+            callout_handle->deleteAllArguments();
+
+            // Pass incoming packet as argument
+            callout_handle->setArgument("query6", query);
+
+            // Call callouts
+            HooksManager::callCallouts(Hooks.hook_index_pkt6_receive_, *callout_handle);
+
+            // Callouts decided to skip the next processing step. The next
+            // processing step would to process the packet, so skip at this
+            // stage means drop.
+            if (callout_handle->getSkip()) {
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP);
+                continue;
+            }
 
-                case DHCPV6_RENEW:
-                    rsp = processRenew(query);
-                    break;
+            callout_handle->getArgument("query6", query);
+        }
 
-                case DHCPV6_REBIND:
-                    rsp = processRebind(query);
+        try {
+            switch (query->getType()) {
+            case DHCPV6_SOLICIT:
+                rsp = processSolicit(query);
                     break;
 
-                case DHCPV6_CONFIRM:
-                    rsp = processConfirm(query);
-                    break;
+            case DHCPV6_REQUEST:
+                rsp = processRequest(query);
+                break;
+
+            case DHCPV6_RENEW:
+                rsp = processRenew(query);
+                break;
+
+            case DHCPV6_REBIND:
+                rsp = processRebind(query);
+                break;
+
+            case DHCPV6_CONFIRM:
+                rsp = processConfirm(query);
+                break;
+
+            case DHCPV6_RELEASE:
+                rsp = processRelease(query);
+                break;
+
+            case DHCPV6_DECLINE:
+                rsp = processDecline(query);
+                break;
+
+            case DHCPV6_INFORMATION_REQUEST:
+                rsp = processInfRequest(query);
+                break;
+
+            default:
+                // We received a packet type that we do not recognize.
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED)
+                    .arg(static_cast<int>(query->getType()))
+                    .arg(query->getIface());
+                // Only action is to output a message if debug is enabled,
+                // and that will be covered by the debug statement before
+                // the "switch" statement.
+                ;
+            }
 
-                case DHCPV6_RELEASE:
-                    rsp = processRelease(query);
-                    break;
+        } catch (const RFCViolation& e) {
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
+                .arg(query->getName())
+                .arg(query->getRemoteAddr().toText())
+                .arg(e.what());
+
+        } catch (const isc::Exception& e) {
+
+            // Catch-all exception (at least for ones based on the isc
+            // Exception class, which covers more or less all that
+            // are explicitly raised in the BIND 10 code).  Just log
+            // the problem and ignore the packet. (The problem is logged
+            // as a debug message because debug is disabled by default -
+            // it prevents a DDOS attack based on the sending of problem
+            // packets.)
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
+                .arg(query->getName())
+                .arg(query->getRemoteAddr().toText())
+                .arg(e.what());
+        }
 
-                case DHCPV6_DECLINE:
-                    rsp = processDecline(query);
-                    break;
+        if (rsp) {
+            rsp->setRemoteAddr(query->getRemoteAddr());
+            rsp->setLocalAddr(query->getLocalAddr());
+            rsp->setRemotePort(DHCP6_CLIENT_PORT);
+            rsp->setLocalPort(DHCP6_SERVER_PORT);
+            rsp->setIndex(query->getIndex());
+            rsp->setIface(query->getIface());
+
+            // Specifies if server should do the packing
+            bool skip_pack = false;
+
+            // Server's reply packet now has all options and fields set.
+            // Options are represented by individual objects, but the
+            // output wire data has not been prepared yet.
+            // Execute all callouts registered for packet6_send
+            if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_pkt6_send_)) {
+                CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
-                case DHCPV6_INFORMATION_REQUEST:
-                    rsp = processInfRequest(query);
-                    break;
+                // Delete all previous arguments
+                callout_handle->deleteAllArguments();
+
+                // Set our response
+                callout_handle->setArgument("response6", rsp);
 
-                default:
-                    // Only action is to output a message if debug is enabled,
-                    // and that will be covered by the debug statement before
-                    // the "switch" statement.
-                    ;
+                // Call all installed callouts
+                HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);
+
+                // Callouts decided to skip the next processing step. The next
+                // processing step would to pack the packet (create wire data).
+                // That step will be skipped if any callout sets skip flag.
+                // It essentially means that the callout already did packing,
+                // so the server does not have to do it again.
+                if (callout_handle->getSkip()) {
+                    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
+                    skip_pack = true;
                 }
+            }
 
-            } catch (const RFCViolation& e) {
-                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
-                    .arg(query->getName())
-                    .arg(query->getRemoteAddr().toText())
-                    .arg(e.what());
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
+                      DHCP6_RESPONSE_DATA)
+                .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
 
-            } catch (const isc::Exception& e) {
-
-                // Catch-all exception (at least for ones based on the isc
-                // Exception class, which covers more or less all that
-                // are explicitly raised in the BIND 10 code).  Just log
-                // the problem and ignore the packet. (The problem is logged
-                // as a debug message because debug is disabled by default -
-                // it prevents a DDOS attack based on the sending of problem
-                // packets.)
-                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
-                    .arg(query->getName())
-                    .arg(query->getRemoteAddr().toText())
-                    .arg(e.what());
+            if (!skip_pack) {
+                try {
+                    rsp->pack();
+                } catch (const std::exception& e) {
+                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL)
+                        .arg(e.what());
+                    continue;
+                }
             }
 
-            if (rsp) {
-                rsp->setRemoteAddr(query->getRemoteAddr());
-                rsp->setLocalAddr(query->getLocalAddr());
-                rsp->setRemotePort(DHCP6_CLIENT_PORT);
-                rsp->setLocalPort(DHCP6_SERVER_PORT);
-                rsp->setIndex(query->getIndex());
-                rsp->setIface(query->getIface());
+            try {
 
-                // Execute all callouts registered for packet6_send
-                if (HooksManager::getHooksManager().calloutsPresent(hook_index_pkt6_send_)) {
+                // Now all fields and options are constructed into output wire buffer.
+                // Option objects modification does not make sense anymore. Hooks
+                // can only manipulate wire buffer at this stage.
+                // Let's execute all callouts registered for buffer6_send
+                if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_buffer6_send_)) {
                     CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
-                    // Delete all previous arguments
+                    // Delete previously set arguments
                     callout_handle->deleteAllArguments();
 
-                    // Set our response
+                    // Pass incoming packet as argument
                     callout_handle->setArgument("response6", rsp);
-
-                    // Call all installed callouts
-                    HooksManager::callCallouts(hook_index_pkt6_send_, *callout_handle);
-
+                    
+                    // Call callouts
+                    HooksManager::callCallouts(Hooks.hook_index_buffer6_send_, *callout_handle);
+                    
                     // Callouts decided to skip the next processing step. The next
-                    // processing step would to send the packet, so skip at this
-                    // stage means "drop response".
+                    // processing step would to parse the packet, so skip at this
+                    // stage means drop.
                     if (callout_handle->getSkip()) {
-                        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
+                        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP);
                         continue;
                     }
+                    
+                    callout_handle->getArgument("response6", rsp);
                 }
 
                 LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                           DHCP6_RESPONSE_DATA)
                     .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
 
-                try {
-                    rsp->pack();
-                    sendPacket(rsp);
-                } catch (const std::exception& e) {
-                    LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
-                              .arg(e.what());
-                }
+                sendPacket(rsp);
+            } catch (const std::exception& e) {
+                LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
+                    .arg(e.what());
             }
         }
     }
@@ -648,8 +739,8 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         }
     }
 
-    // Let's execute all callouts registered for packet_received
-    if (HooksManager::getHooksManager().calloutsPresent(hook_index_subnet6_select_)) {
+    // Let's execute all callouts registered for subnet6_receive
+    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_subnet6_select_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(question);
 
         // We're reusing callout_handle from previous calls
@@ -665,7 +756,7 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         callout_handle->setArgument("subnet6collection", CfgMgr::instance().getSubnets6());
 
         // Call user (and server-side) callouts
-        HooksManager::callCallouts(hook_index_subnet6_select_, *callout_handle);
+        HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
 
         // Callouts decided to skip this step. This means that no subnet will be
         // selected. Packet processing will continue, but it will be severly limited
@@ -852,7 +943,7 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
 
 OptionPtr
 Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                      Pkt6Ptr /* question */, boost::shared_ptr<Option6IA> ia) {
+                      const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) {
     if (!subnet) {
         // There's no subnet select for this client. There's nothing to renew.
         boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
@@ -889,14 +980,15 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         return (ia_rsp);
     }
 
+    // Keep the old data in case the callout tells us to skip update
+    Lease6 old_data = *lease;
+
     lease->preferred_lft_ = subnet->getPreferred();
     lease->valid_lft_ = subnet->getValid();
     lease->t1_ = subnet->getT1();
     lease->t2_ = subnet->getT2();
     lease->cltt_ = time(NULL);
 
-    LeaseMgrFactory::instance().updateLease6(lease);
-
     // Create empty IA_NA option with IAID matching the request.
     boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
 
@@ -907,6 +999,46 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                                           lease->addr_, lease->preferred_lft_,
                                           lease->valid_lft_));
     ia_rsp->addOption(addr);
+
+    bool skip = false;
+    // Execute all callouts registered for packet6_send
+    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease6_renew_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass the original packet
+        callout_handle->setArgument("query6", query);
+
+        // Pass the lease to be updated
+        callout_handle->setArgument("lease6", lease);
+
+        // Pass the IA option to be sent in response
+        callout_handle->setArgument("ia_na", ia_rsp);
+
+        // Call all installed callouts
+        HooksManager::callCallouts(Hooks.hook_index_lease6_renew_, *callout_handle);
+
+        // Callouts decided to skip the next processing step. The next
+        // processing step would to actually renew the lease, so skip at this
+        // stage means "keep the old lease as it is".
+        if (callout_handle->getSkip()) {
+            skip = true;
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RENEW_SKIP);
+        }
+    }
+
+    if (!skip) {
+        LeaseMgrFactory::instance().updateLease6(lease);
+    } else {
+        // Copy back the original date to the lease. For MySQL it doesn't make
+        // much sense, but for memfile, the Lease6Ptr points to the actual lease
+        // in memfile, so the actual update is performed when we manipulate fields
+        // of returned Lease6Ptr, the actual updateLease6() is no-op.
+        *lease = old_data;
+    }
+
     return (ia_rsp);
 }
 
@@ -1025,7 +1157,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
 }
 
 OptionPtr
-Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */,
+Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
                         int& general_status, boost::shared_ptr<Option6IA> ia) {
     // Release can be done in one of two ways:
     // Approach 1: extract address from client's IA_NA and see if it belongs
@@ -1109,9 +1241,43 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */,
     // It is not necessary to check if the address matches as we used
     // getLease6(addr) method that is supposed to return a proper lease.
 
+    bool skip = false;
+    // Execute all callouts registered for packet6_send
+    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease6_release_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass the original packet
+        callout_handle->setArgument("query6", query);
+
+        // Pass the lease to be updated
+        callout_handle->setArgument("lease6", lease);
+
+        // Call all installed callouts
+        HooksManager::callCallouts(Hooks.hook_index_lease6_release_, *callout_handle);
+
+        // Callouts decided to skip the next processing step. The next
+        // processing step would to send the packet, so skip at this
+        // stage means "drop response".
+        if (callout_handle->getSkip()) {
+            skip = true;
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RELEASE_SKIP);
+        }
+    }
+
     // Ok, we've passed all checks. Let's release this address.
+    bool success = false; // was the removal operation succeessful?
+
+    if (!skip) {
+        success = LeaseMgrFactory::instance().deleteLease(lease->addr_);
+    }
+
+    // Here the success should be true if we removed lease successfully
+    // and false if skip flag was set or the removal failed for whatever reason
 
-    if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+    if (!success) {
         ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
                           "Server failed to release a lease"));
 

+ 4 - 9
src/bin/dhcp6/dhcp6_srv.h

@@ -227,11 +227,11 @@ protected:
     ///
     /// @param subnet subnet the sender belongs to
     /// @param duid client's duid
-    /// @param question client's message
+    /// @param query client's message
     /// @param ia IA_NA option that is being renewed
     /// @return IA_NA option (server's response)
     OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                         Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
+                         const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia);
 
     /// @brief Releases specific IA_NA option
     ///
@@ -246,11 +246,11 @@ protected:
     /// release process fails.
     ///
     /// @param duid client's duid
-    /// @param question client's message
+    /// @param query client's message
     /// @param general_status a global status (it may be updated in case of errors)
     /// @param ia IA_NA option that is being renewed
     /// @return IA_NA option (server's response)
-    OptionPtr releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+    OptionPtr releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
                            int& general_status,
                            boost::shared_ptr<Option6IA> ia);
 
@@ -383,11 +383,6 @@ private:
     /// @return a callout handle to be used in hooks related to said packet
     isc::hooks::CalloutHandlePtr getCalloutHandle(const Pkt6Ptr& pkt);
 
-    /// Indexes for registered hook points
-    int hook_index_pkt6_receive_;
-    int hook_index_subnet6_select_;
-    int hook_index_pkt6_send_;
-
     /// UDP port number on which server listens.
     uint16_t port_;
 };

+ 2 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -49,6 +49,8 @@ TESTS += dhcp6_unittests
 
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += hooks_unittest.cc
+dhcp6_unittests_SOURCES += dhcp6_test_utils.h
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc

File diff suppressed because it is too large
+ 2 - 1036
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc


+ 403 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -0,0 +1,403 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file   dhcp6_test_utils.h
+///
+/// @brief  This file contains utility classes used for DHCPv6 server testing
+
+#include <gtest/gtest.h>
+
+#include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <hooks/hooks_manager.h>
+#include <config/ccsession.h>
+
+#include <list>
+
+using namespace isc::dhcp;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace isc::hooks;
+
+namespace isc {
+namespace test {
+
+/// @brief "naked" Dhcpv6Srv class that exposes internal members
+class NakedDhcpv6Srv: public isc::dhcp::Dhcpv6Srv {
+public:
+    NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
+        // Open the "memfile" database for leases
+        std::string memfile = "type=memfile";
+        LeaseMgrFactory::create(memfile);
+    }
+
+    /// @brief fakes packet reception
+    /// @param timeout ignored
+    ///
+    /// The method receives all packets queued in receive
+    /// queue, one after another. Once the queue is empty,
+    /// it initiates the shutdown procedure.
+    ///
+    /// See fake_received_ field for description
+    virtual isc::dhcp::Pkt6Ptr receivePacket(int /*timeout*/) {
+
+        // If there is anything prepared as fake incoming
+        // traffic, use it
+        if (!fake_received_.empty()) {
+            Pkt6Ptr pkt = fake_received_.front();
+            fake_received_.pop_front();
+            return (pkt);
+        }
+
+        // If not, just trigger shutdown and
+        // return immediately
+        shutdown();
+        return (Pkt6Ptr());
+    }
+
+    /// @brief fake packet sending
+    ///
+    /// Pretend to send a packet, but instead just store
+    /// it in fake_send_ list where test can later inspect
+    /// server's response.
+    virtual void sendPacket(const Pkt6Ptr& pkt) {
+        fake_sent_.push_back(pkt);
+    }
+
+    /// @brief adds a packet to fake receive queue
+    ///
+    /// See fake_received_ field for description
+    void fakeReceive(const Pkt6Ptr& pkt) {
+        fake_received_.push_back(pkt);
+    }
+
+    virtual ~NakedDhcpv6Srv() {
+        // Close the lease database
+        LeaseMgrFactory::destroy();
+    }
+
+    using Dhcpv6Srv::processSolicit;
+    using Dhcpv6Srv::processRequest;
+    using Dhcpv6Srv::processRenew;
+    using Dhcpv6Srv::processRelease;
+    using Dhcpv6Srv::createStatusCode;
+    using Dhcpv6Srv::selectSubnet;
+    using Dhcpv6Srv::sanityCheck;
+    using Dhcpv6Srv::loadServerID;
+    using Dhcpv6Srv::writeServerID;
+
+    /// @brief packets we pretend to receive
+    ///
+    /// Instead of setting up sockets on interfaces that change between OSes, it
+    /// is much easier to fake packet reception. This is a list of packets that
+    /// we pretend to have received. You can schedule new packets to be received
+    /// using fakeReceive() and NakedDhcpv6Srv::receivePacket() methods.
+    std::list<Pkt6Ptr> fake_received_;
+
+    std::list<Pkt6Ptr> fake_sent_;
+};
+
+static const char* DUID_FILE = "server-id-test.txt";
+
+// test fixture for any tests requiring blank/empty configuration
+// serves as base class for additional tests
+class NakedDhcpv6SrvTest : public ::testing::Test {
+public:
+
+    NakedDhcpv6SrvTest() : rcode_(-1) {
+        // it's ok if that fails. There should not be such a file anyway
+        unlink(DUID_FILE);
+
+        const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+        // There must be some interface detected
+        if (ifaces.empty()) {
+            // We can't use ASSERT in constructor
+            ADD_FAILURE() << "No interfaces detected.";
+        }
+
+        valid_iface_ = ifaces.begin()->getName();
+    }
+
+    // Generate IA_NA option with specified parameters
+    boost::shared_ptr<Option6IA> generateIA(uint32_t iaid, uint32_t t1, uint32_t t2) {
+        boost::shared_ptr<Option6IA> ia =
+            boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, iaid));
+        ia->setT1(t1);
+        ia->setT2(t2);
+        return (ia);
+    }
+
+    /// @brief generates interface-id option, based on text
+    ///
+    /// @param iface_id textual representation of the interface-id content
+    ///
+    /// @return pointer to the option object
+    OptionPtr generateInterfaceId(const std::string& iface_id) {
+        OptionBuffer tmp(iface_id.begin(), iface_id.end());
+        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    }
+
+    // Generate client-id option
+    OptionPtr generateClientId(size_t duid_size = 32) {
+
+        OptionBuffer clnt_duid(duid_size);
+        for (int i = 0; i < duid_size; i++) {
+            clnt_duid[i] = 100 + i;
+        }
+
+        duid_ = DuidPtr(new DUID(clnt_duid));
+
+        return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+                                     clnt_duid.begin(),
+                                     clnt_duid.begin() + duid_size)));
+    }
+
+    // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
+    void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
+        // check that server included its server-id
+        OptionPtr tmp = rsp->getOption(D6O_SERVERID);
+        EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
+        ASSERT_EQ(tmp->len(), expected_srvid->len() );
+        EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
+    }
+
+    // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
+    void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
+        // check that server included our own client-id
+        OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
+        ASSERT_TRUE(tmp);
+        EXPECT_EQ(expected_clientid->getType(), tmp->getType());
+        ASSERT_EQ(expected_clientid->len(), tmp->len());
+
+        // check that returned client-id is valid
+        EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
+    }
+
+    // Checks if server response is a NAK
+    void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+                          uint32_t expected_transid,
+                          uint16_t expected_status_code) {
+        // Check if we get response at all
+        checkResponse(rsp, expected_message_type, expected_transid);
+
+        // Check that IA_NA was returned
+        OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
+        ASSERT_TRUE(option_ia_na);
+
+        // check that the status is no address available
+        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(option_ia_na);
+        ASSERT_TRUE(ia);
+
+        checkIA_NAStatusCode(ia, expected_status_code);
+    }
+
+    // Checks that server rejected IA_NA, i.e. that it has no addresses and
+    // that expected status code really appears there. In some limited cases
+    // (reply to RELEASE) it may be used to verify positive case, where
+    // IA_NA response is expected to not include address.
+    //
+    // Status code indicates type of error encountered (in theory it can also
+    // indicate success, but servers typically don't send success status
+    // as this is the default result and it saves bandwidth)
+    void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
+                            uint16_t expected_status_code) {
+        // Make sure there is no address assigned.
+        EXPECT_FALSE(ia->getOption(D6O_IAADDR));
+
+        // T1, T2 should be zeroed
+        EXPECT_EQ(0, ia->getT1());
+        EXPECT_EQ(0, ia->getT2());
+
+        OptionCustomPtr status =
+            boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+
+        // It is ok to not include status success as this is the default behavior
+        if (expected_status_code == STATUS_Success && !status) {
+            return;
+        }
+
+        EXPECT_TRUE(status);
+
+        if (status) {
+            // We don't have dedicated class for status code, so let's just interpret
+            // first 2 bytes as status. Remainder of the status code option content is
+            // just a text explanation what went wrong.
+            EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
+                      status->readInteger<uint16_t>(0));
+        }
+    }
+
+    void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
+        OptionCustomPtr status =
+            boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+
+        // It is ok to not include status success as this is the default behavior
+        if (expected_status == STATUS_Success && !status) {
+            return;
+        }
+
+        EXPECT_TRUE(status);
+        if (status) {
+            // We don't have dedicated class for status code, so let's just interpret
+            // first 2 bytes as status. Remainder of the status code option content is
+            // just a text explanation what went wrong.
+            EXPECT_EQ(static_cast<uint16_t>(expected_status),
+                      status->readInteger<uint16_t>(0));
+        }
+    }
+
+    // Basic checks for generated response (message type and transaction-id).
+    void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+                       uint32_t expected_transid) {
+        ASSERT_TRUE(rsp);
+        EXPECT_EQ(expected_message_type, rsp->getType());
+        EXPECT_EQ(expected_transid, rsp->getTransid());
+    }
+
+    virtual ~NakedDhcpv6SrvTest() {
+        // Let's clean up if there is such a file.
+        unlink(DUID_FILE);
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "buffer6_receive");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "buffer6_send");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "lease6_renew");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "lease6_release");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "pkt6_receive");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "pkt6_send");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "subnet6_select");
+
+    };
+
+    // A DUID used in most tests (typically as client-id)
+    DuidPtr duid_;
+
+    int rcode_;
+    ConstElementPtr comment_;
+
+    // Name of a valid network interface
+    std::string valid_iface_;
+};
+
+// Provides suport for tests against a preconfigured subnet6
+// extends upon NakedDhcp6SrvTest
+class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
+public:
+    /// Name of the server-id file (used in server-id tests)
+
+    // these are empty for now, but let's keep them around
+    Dhcpv6SrvTest() {
+        subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
+                                         2000, 3000, 4000));
+        pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+        subnet_->addPool(pool_);
+
+        CfgMgr::instance().deleteSubnets6();
+        CfgMgr::instance().addSubnet6(subnet_);
+    }
+
+    // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
+    // It returns IAADDR option for each chaining with checkIAAddr method.
+    boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+                                            uint32_t expected_t1, uint32_t expected_t2) {
+        OptionPtr tmp = rsp->getOption(D6O_IA_NA);
+        // Can't use ASSERT_TRUE() in method that returns something
+        if (!tmp) {
+            ADD_FAILURE() << "IA_NA option not present in response";
+            return (boost::shared_ptr<Option6IAAddr>());
+        }
+
+        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+        if (!ia) {
+            ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
+            return (boost::shared_ptr<Option6IAAddr>());
+        }
+
+        EXPECT_EQ(expected_iaid, ia->getIAID());
+        EXPECT_EQ(expected_t1, ia->getT1());
+        EXPECT_EQ(expected_t2, ia->getT2());
+
+        tmp = ia->getOption(D6O_IAADDR);
+        boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+        return (addr);
+    }
+
+    // Check that generated IAADDR option contains expected address
+    // and lifetime values match the configured subnet
+    void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
+                     const IOAddress& expected_addr,
+                     uint32_t /* expected_preferred */,
+                     uint32_t /* expected_valid */) {
+
+        // Check that the assigned address is indeed from the configured pool.
+        // Note that when comparing addresses, we compare the textual
+        // representation. IOAddress does not support being streamed to
+        // an ostream, which means it can't be used in EXPECT_EQ.
+        EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
+        EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
+        EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
+        EXPECT_EQ(addr->getValid(), subnet_->getValid());
+    }
+
+    // Checks if the lease sent to client is present in the database
+    // and is valid when checked agasint the configured subnet
+    Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
+                         boost::shared_ptr<Option6IAAddr> addr) {
+        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
+
+        Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(addr->getAddress());
+        if (!lease) {
+            std::cout << "Lease for " << addr->getAddress().toText()
+                      << " not found in the database backend.";
+            return (Lease6Ptr());
+        }
+
+        EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
+        EXPECT_TRUE(*lease->duid_ == *duid);
+        EXPECT_EQ(ia->getIAID(), lease->iaid_);
+        EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+        return (lease);
+    }
+
+    ~Dhcpv6SrvTest() {
+        CfgMgr::instance().deleteSubnets6();
+    };
+
+    /// A subnet used in most tests
+    Subnet6Ptr subnet_;
+
+    /// A pool used in most tests
+    Pool6Ptr pool_;
+};
+
+}; // end of isc::test namespace
+}; // end of isc namespace

File diff suppressed because it is too large
+ 1457 - 0
src/bin/dhcp6/tests/hooks_unittest.cc


+ 13 - 18
src/lib/dhcp/pkt6.h

@@ -141,11 +141,6 @@ public:
     /// @return reference to output buffer
     const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
 
-    /// @brief Returns reference to input buffer.
-    ///
-    /// @return reference to input buffer
-    const OptionBuffer& getData() const { return(data_); }
-
     /// @brief Returns protocol of this packet (UDP or TCP).
     ///
     /// @return protocol type
@@ -349,9 +344,7 @@ public:
 
     /// collection of options present in this message
     ///
-    /// @todo: Text mentions protected, but this is really public
-    ///
-    /// @warning This protected member is accessed by derived
+    /// @warning This public member is accessed by derived
     /// classes directly. One of such derived classes is
     /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
     /// behavior must be taken into consideration before making
@@ -412,6 +405,18 @@ public:
     /// to be impossible). Therefore public field is considered the best
     /// (or least bad) solution.
     std::vector<RelayInfo> relay_info_;
+
+
+    /// unparsed data (in received packets)
+    ///
+    /// @warning This public member is accessed by derived
+    /// classes directly. One of such derived classes is
+    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+    /// behavior must be taken into consideration before making
+    /// changes to this member such as access scope restriction or
+    /// data format change etc.
+    OptionBuffer data_;
+
 protected:
     /// Builds on wire packet for TCP transmission.
     ///
@@ -494,16 +499,6 @@ protected:
     /// DHCPv6 transaction-id
     uint32_t transid_;
 
-    /// unparsed data (in received packets)
-    ///
-    /// @warning This protected member is accessed by derived
-    /// classes directly. One of such derived classes is
-    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
-    /// behavior must be taken into consideration before making
-    /// changes to this member such as access scope restriction or
-    /// data format change etc.
-    OptionBuffer data_;
-
     /// name of the network interface the packet was received/to be sent over
     std::string iface_;
 

+ 3 - 3
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -766,9 +766,9 @@ TEST_F(IfaceMgrTest, sendReceive6) {
     ASSERT_TRUE(rcvPkt); // received our own packet
 
     // let's check that we received what was sent
-    ASSERT_EQ(sendPkt->getData().size(), rcvPkt->getData().size());
-    EXPECT_EQ(0, memcmp(&sendPkt->getData()[0], &rcvPkt->getData()[0],
-                        rcvPkt->getData().size()));
+    ASSERT_EQ(sendPkt->data_.size(), rcvPkt->data_.size());
+    EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0],
+                        rcvPkt->data_.size()));
 
     EXPECT_EQ(sendPkt->getRemoteAddr().toText(), rcvPkt->getRemoteAddr().toText());
 

+ 2 - 2
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -64,8 +64,8 @@ TEST_F(Pkt6Test, constructor) {
     uint8_t data[] = { 0, 1, 2, 3, 4, 5 };
     scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data)));
 
-    EXPECT_EQ(6, pkt1->getData().size());
-    EXPECT_EQ(0, memcmp( &pkt1->getData()[0], data, sizeof(data)));
+    EXPECT_EQ(6, pkt1->data_.size());
+    EXPECT_EQ(0, memcmp( &pkt1->data_[0], data, sizeof(data)));
 }
 
 /// @brief returns captured actual SOLICIT packet

+ 5 - 5
tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc

@@ -111,8 +111,8 @@ TEST_F(PerfPkt6Test, Constructor) {
     // Test constructor to be used for incoming messages.
     // Use default (1) offset value and don't specify transaction id.
     boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data, sizeof(data)));
-    EXPECT_EQ(sizeof(data), pkt1->getData().size());
-    EXPECT_EQ(0, memcmp(&pkt1->getData()[0], data, sizeof(data)));
+    EXPECT_EQ(sizeof(data), pkt1->data_.size());
+    EXPECT_EQ(0, memcmp(&pkt1->data_[0], data, sizeof(data)));
     EXPECT_EQ(1, pkt1->getTransidOffset());
 
     // Test constructor to be used for outgoing messages.
@@ -121,8 +121,8 @@ TEST_F(PerfPkt6Test, Constructor) {
     const uint32_t transid = 0x010203;
     boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data, sizeof(data),
                                                   offset_transid, transid));
-    EXPECT_EQ(sizeof(data), pkt2->getData().size());
-    EXPECT_EQ(0, memcmp(&pkt2->getData()[0], data, sizeof(data)));
+    EXPECT_EQ(sizeof(data), pkt2->data_.size());
+    EXPECT_EQ(0, memcmp(&pkt2->data_[0], data, sizeof(data)));
     EXPECT_EQ(0x010203, pkt2->getTransid());
     EXPECT_EQ(10, pkt2->getTransidOffset());
 }
@@ -163,7 +163,7 @@ TEST_F(PerfPkt6Test, RawPackUnpack) {
     // Get output buffer from packet 1 to create new packet
     // that will be later validated.
     util::OutputBuffer pkt1_output = pkt1->getBuffer();
-    ASSERT_EQ(pkt1_output.getLength(), pkt1->getData().size());
+    ASSERT_EQ(pkt1_output.getLength(), pkt1->data_.size());
     const uint8_t* pkt1_output_data = static_cast<const uint8_t*>
         (pkt1_output.getData());
     boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(pkt1_output_data,