diff options
Diffstat (limited to 'src/bin/dhcp6/dhcp6_srv.cc')
-rw-r--r-- | src/bin/dhcp6/dhcp6_srv.cc | 1639 |
1 files changed, 1511 insertions, 128 deletions
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 75a5337a17..3dffa37992 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2014 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 @@ -15,29 +15,37 @@ #include <config.h> #include <asiolink/io_address.h> +#include <dhcp_ddns/ncr_msg.h> #include <dhcp/dhcp6.h> +#include <dhcp/docsis3_option_defs.h> #include <dhcp/duid.h> #include <dhcp/iface_mgr.h> #include <dhcp/libdhcp++.h> #include <dhcp/option6_addrlst.h> +#include <dhcp/option6_client_fqdn.h> #include <dhcp/option6_ia.h> #include <dhcp/option6_iaaddr.h> -#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> #include <dhcp/option_custom.h> +#include <dhcp/option_vendor.h> #include <dhcp/option_int_array.h> #include <dhcp/pkt6.h> #include <dhcp6/dhcp6_log.h> #include <dhcp6/dhcp6_srv.h> +#include <dhcpsrv/callout_handle_store.h> #include <dhcpsrv/cfgmgr.h> #include <dhcpsrv/lease_mgr.h> #include <dhcpsrv/lease_mgr_factory.h> #include <dhcpsrv/subnet.h> #include <dhcpsrv/utils.h> #include <exceptions/exceptions.h> +#include <hooks/callout_handle.h> +#include <hooks/hooks_manager.h> +#include <util/encode/hex.h> #include <util/io_utilities.h> #include <util/range_utilities.h> -#include <util/encode/hex.h> +#include <boost/bind.hpp> #include <boost/foreach.hpp> #include <boost/tokenizer.hpp> #include <boost/algorithm/string/erase.hpp> @@ -49,13 +57,75 @@ using namespace isc; using namespace isc::asiolink; +using namespace isc::dhcp_ddns; using namespace isc::dhcp; +using namespace isc::hooks; using namespace isc::util; using namespace std; +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"); + } +}; + +// Declare a Hooks object. As this is outside any function or method, it +// will be instantiated (and the constructor run) when the module is loaded. +// As a result, the hook indexes will be defined before any method in this +// module is called. +Dhcp6Hooks Hooks; + +}; // anonymous namespace + namespace isc { namespace dhcp { +namespace { + +// The following constants describe server's behavior with respect to the +// DHCPv6 Client FQDN Option sent by a client. They will be removed +// when DDNS parameters for DHCPv6 are implemented with the ticket #3034. + +// Should server always include the FQDN option in its response, regardless +// if it has been requested in ORO (Disabled). +const bool FQDN_ALWAYS_INCLUDE = false; +// Enable AAAA RR update delegation to the client (Disabled). +const bool FQDN_ALLOW_CLIENT_UPDATE = false; +// Globally enable updates (Enabled). +const bool FQDN_ENABLE_UPDATE = true; +// The partial name generated for the client if empty name has been +// supplied. +const char* FQDN_GENERATED_PARTIAL_NAME = "myhost"; +// Do update, even if client requested no updates with N flag (Disabled). +const bool FQDN_OVERRIDE_NO_UPDATE = false; +// Server performs an update when client requested delegation (Enabled). +const bool FQDN_OVERRIDE_CLIENT_UPDATE = true; +// The fully qualified domain-name suffix if partial name provided by +// a client. +const char* FQDN_PARTIAL_SUFFIX = "example.com"; +// Should server replace the domain-name supplied by the client (Disabled). +const bool FQDN_REPLACE_CLIENT_NAME = false; + +} + /// @brief file name of a server-id file /// /// Server must store its duid in persistent storage that must not change @@ -67,7 +137,8 @@ namespace dhcp { static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid"; Dhcpv6Srv::Dhcpv6Srv(uint16_t port) - : alloc_engine_(), serverid_(), shutdown_(true) { +:alloc_engine_(), serverid_(), shutdown_(true), port_(port) +{ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port); @@ -82,7 +153,12 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES); return; } - IfaceMgr::instance().openSockets6(port); + // Create error handler. This handler will be called every time + // the socket opening operation fails. We use this handler to + // log a warning. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + boost::bind(&Dhcpv6Srv::ifaceMgrSocket6ErrorHandler, _1); + IfaceMgr::instance().openSockets6(port_, error_handler); } string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE); @@ -100,12 +176,13 @@ 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)); + /// @todo call loadLibraries() when handling configuration changes + } catch (const std::exception &e) { LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what()); return; @@ -126,123 +203,291 @@ void Dhcpv6Srv::shutdown() { shutdown_ = true; } +Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) { + return (IfaceMgr::instance().receive6(timeout)); +} + +void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) { + IfaceMgr::instance().send(packet); +} + bool Dhcpv6Srv::run() { while (!shutdown_) { - /// @todo: calculate actual timeout to the next event (e.g. lease + /// @todo Calculate actual timeout to the next event (e.g. lease /// expiration) once we have lease database. The idea here is that /// it is possible to do everything in a single process/thread. /// For now, we are just calling select for 1000 seconds. There /// were some issues reported on some systems when calling select() /// with too large values. Unfortunately, I don't recall the details. - int timeout = 1000; + //cppcheck-suppress variableScope This is temporary anyway + const int timeout = 1000; // client's message and server's response Pkt6Ptr query; Pkt6Ptr rsp; try { - query = IfaceMgr::instance().receive6(timeout); + query = receivePacket(timeout); } catch (const std::exception& e) { LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what()); } - if (query) { + // Timeout may be reached or signal received, which breaks select() with no packet received + if (!query) { + continue; + } + + // In order to parse the DHCP options, the server needs to use some + // configuration information such as: existing option spaces, option + // definitions etc. This is the kind of information which is not + // available in the libdhcp, so we need to supply our own implementation + // of the option parsing function here, which would rely on the + // configuration data. + query->setCallback(boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2, + _3, _4, _5)); + + bool skip_unpack = false; + + // The packet has just been received so contains the uninterpreted wire + // data; execute callouts registered for buffer6_receive. + if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_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_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; + } + + callout_handle->getArgument("query6", query); + } + + // 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()); + } + 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::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; + } - try { - switch (query->getType()) { - case DHCPV6_SOLICIT: - rsp = processSolicit(query); - break; + callout_handle->getArgument("query6", query); + } - case DHCPV6_REQUEST: - rsp = processRequest(query); - break; + // Assign this packet to a class, if possible + classifyPacket(query); - case DHCPV6_RENEW: - rsp = processRenew(query); - break; - - case DHCPV6_REBIND: - rsp = processRebind(query); + try { + NameChangeRequestPtr ncr; + 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()); - case DHCPV6_INFORMATION_REQUEST: - rsp = processInfRequest(query); - break; + if (rsp->relay_info_.empty()) { + // Direct traffic, send back to the client directly + rsp->setRemotePort(DHCP6_CLIENT_PORT); + } else { + // Relayed traffic, send back to the relay agent + rsp->setRemotePort(DHCP6_SERVER_PORT); + } - 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. - ; + 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::calloutsPresent(Hooks.hook_index_pkt6_send_)) { + CalloutHandlePtr callout_handle = getCalloutHandle(query); + + // Delete all previous arguments + callout_handle->deleteAllArguments(); + + // Set our response + callout_handle->setArgument("response6", rsp); + + // 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()) - .arg(e.what()); + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, + DHCP6_RESPONSE_DATA) + .arg(static_cast<int>(rsp->getType())).arg(rsp->toText()); + + if (!skip_pack) { + try { + rsp->pack(); + } catch (const std::exception& e) { + LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL) + .arg(e.what()); + continue; + } - } 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()) - .arg(e.what()); } - 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 { + + // 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::calloutsPresent(Hooks.hook_index_buffer6_send_)) { + CalloutHandlePtr callout_handle = getCalloutHandle(query); + + // Delete previously set arguments + callout_handle->deleteAllArguments(); + + // Pass incoming packet as argument + callout_handle->setArgument("response6", rsp); + + // 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 parse the packet, so skip at this + // stage means drop. + if (callout_handle->getSkip()) { + 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()); - if (rsp->pack()) { - try { - IfaceMgr::instance().send(rsp); - } catch (const std::exception& e) { - LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL).arg(e.what()); - } - } else { - LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL); - } + sendPacket(rsp); + } catch (const std::exception& e) { + LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL) + .arg(e.what()); } + + // Send NameChangeRequests to the b10-dhcp-ddns module. + sendNameChangeRequests(); } } @@ -403,8 +648,13 @@ Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) { if (clientid) { answer->addOption(clientid); } + /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST) + + // If this is a relayed message, we need to copy relay information + if (!question->relay_info_.empty()) { + answer->copyRelayInfo(question); + } - // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST) } void @@ -444,6 +694,57 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) { } } +void +Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) { + // Get the configured subnet suitable for the incoming packet. + Subnet6Ptr subnet = selectSubnet(question); + // Leave if there is no subnet matching the incoming packet. + // There is no need to log the error message here because + // it will be logged in the assignLease() when it fails to + // pick the suitable subnet. We don't want to duplicate + // error messages in such case. + if (!subnet) { + return; + } + + // Try to get the vendor option + boost::shared_ptr<OptionVendor> vendor_req = + boost::dynamic_pointer_cast<OptionVendor>(question->getOption(D6O_VENDOR_OPTS)); + if (!vendor_req) { + return; + } + + // Let's try to get ORO within that vendor-option + /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors + /// may have different policies. + boost::shared_ptr<OptionUint16Array> oro = + boost::dynamic_pointer_cast<OptionUint16Array>(vendor_req->getOption(DOCSIS3_V6_ORO)); + + // Option ORO not found. Don't do anything then. + if (!oro) { + return; + } + + uint32_t vendor_id = vendor_req->getVendorId(); + + boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id)); + + // Get the list of options that client requested. + bool added = false; + const std::vector<uint16_t>& requested_opts = oro->getValues(); + BOOST_FOREACH(uint16_t opt, requested_opts) { + Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, opt); + if (desc.option) { + vendor_rsp->addOption(desc.option); + added = true; + } + } + + if (added) { + answer->addOption(vendor_rsp); + } +} + OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) { // @todo This function uses OptionCustom class to manage contents @@ -458,10 +759,9 @@ Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) { assert(status_code_def); // As there is no dedicated class to represent Status Code - // the OptionCustom class should be returned here. - boost::shared_ptr<OptionCustom> option_status = - boost::dynamic_pointer_cast< - OptionCustom>(status_code_def->optionFactory(Option::V6, D6O_STATUS_CODE)); + // the OptionCustom class is used here instead. + OptionCustomPtr option_status = + OptionCustomPtr(new OptionCustom(*status_code_def, Option::V6)); assert(option_status); // Set status code to 'code' (0 - means data field #0). @@ -474,7 +774,7 @@ Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) { void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid, RequirementLevel serverid) { - Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID); + OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID); switch (clientid) { case MANDATORY: if (client_ids.size() != 1) { @@ -495,7 +795,7 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid, break; } - Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID); + OptionCollection server_ids = pkt->getOptions(D6O_SERVERID); switch (serverid) { case FORBIDDEN: if (!server_ids.empty()) { @@ -523,27 +823,79 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid, Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) { - /// @todo: pass interface information only if received direct (non-relayed) message + Subnet6Ptr subnet; + + if (question->relay_info_.empty()) { + // This is a direct (non-relayed) message - // Try to find a subnet if received packet from a directly connected client - Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface()); - if (subnet) { - return (subnet); + // Try to find a subnet if received packet from a directly connected client + subnet = CfgMgr::instance().getSubnet6(question->getIface()); + if (!subnet) { + // If no subnet was found, try to find it based on remote address + subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr()); + } + } else { + + // This is a relayed message + OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID, + Pkt6::RELAY_GET_FIRST); + if (interface_id) { + subnet = CfgMgr::instance().getSubnet6(interface_id); + } + + if (!subnet) { + // If no interface-id was specified (or not configured on server), let's + // try address matching + IOAddress link_addr = question->relay_info_.back().linkaddr_; + + // if relay filled in link_addr field, then let's use it + if (link_addr != IOAddress("::")) { + subnet = CfgMgr::instance().getSubnet6(link_addr); + } + } } - // If no subnet was found, try to find it based on remote address - subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr()); + // Let's execute all callouts registered for subnet6_receive + if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) { + CalloutHandlePtr callout_handle = getCalloutHandle(question); + + // We're reusing callout_handle from previous calls + callout_handle->deleteAllArguments(); + + // Set new arguments + callout_handle->setArgument("query6", question); + callout_handle->setArgument("subnet6", subnet); + + // We pass pointer to const collection for performance reasons. + // Otherwise we would get a non-trivial performance penalty each + // time subnet6_select is called. + callout_handle->setArgument("subnet6collection", CfgMgr::instance().getSubnets6()); + + // Call user (and server-side) callouts + 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 + // (i.e. only global options will be assigned) + if (callout_handle->getSkip()) { + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_SUBNET6_SELECT_SKIP); + return (Subnet6Ptr()); + } + + // Use whatever subnet was specified by the callout + callout_handle->getArgument("subnet6", subnet); + } return (subnet); } void -Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) { +Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer, + const Option6ClientFqdnPtr& fqdn) { // We need to allocate addresses for all IA_NA options in the client's // question (i.e. SOLICIT or REQUEST) message. // @todo add support for IA_TA - // @todo add support for IA_PD // We need to select a subnet the client is connected in. Subnet6Ptr subnet = selectSubnet(question); @@ -588,26 +940,301 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) { // // @todo: expand this to cover IA_PD and IA_TA once we implement support for // prefix delegation and temporary addresses. - for (Option::OptionCollection::iterator opt = question->options_.begin(); + for (OptionCollection::iterator opt = question->options_.begin(); opt != question->options_.end(); ++opt) { switch (opt->second->getType()) { case D6O_IA_NA: { OptionPtr answer_opt = assignIA_NA(subnet, duid, question, - boost::dynamic_pointer_cast<Option6IA>(opt->second)); + boost::dynamic_pointer_cast< + Option6IA>(opt->second), + fqdn); if (answer_opt) { answer->addOption(answer_opt); } break; } + case D6O_IA_PD: { + OptionPtr answer_opt = assignIA_PD(subnet, duid, question, + boost::dynamic_pointer_cast< + Option6IA>(opt->second)); + if (answer_opt) { + answer->addOption(answer_opt); + } + } default: break; } } } +Option6ClientFqdnPtr +Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) { + // Get Client FQDN Option from the client's message. If this option hasn't + // been included, do nothing. + Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast< + Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN)); + if (!fqdn) { + return (fqdn); + } + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, + DHCP6_DDNS_RECEIVE_FQDN).arg(fqdn->toText()); + + + // Prepare the FQDN option which will be included in the response to + // the client. + Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn)); + // RFC 4704, section 6. - all flags set to 0. + fqdn_resp->resetFlags(); + + // Conditions when N flag has to be set to indicate that server will not + // perform DNS updates: + // 1. Updates are globally disabled, + // 2. Client requested no update and server respects it, + // 3. Client requested that the AAAA update is delegated to the client but + // server neither respects delegation of updates nor it is configured + // to send update on its own when client requested delegation. + if (!FQDN_ENABLE_UPDATE || + (fqdn->getFlag(Option6ClientFqdn::FLAG_N) && + !FQDN_OVERRIDE_NO_UPDATE) || + (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) && + !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) { + fqdn_resp->setFlag(Option6ClientFqdn::FLAG_N, true); + + // Conditions when S flag is set to indicate that server will perform + // DNS update on its own: + // 1. Client requested that server performs DNS update and DNS updates are + // globally enabled + // 2. Client requested that server delegates AAAA update to the client but + // server doesn't respect delegation and it is configured to perform + // an update on its own when client requested delegation. + } else if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) || + (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) && + !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) { + fqdn_resp->setFlag(Option6ClientFqdn::FLAG_S, true); + } + + // Server MUST set the O flag if it has overridden the client's setting + // of S flag. + if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) != + fqdn_resp->getFlag(Option6ClientFqdn::FLAG_S)) { + fqdn_resp->setFlag(Option6ClientFqdn::FLAG_O, true); + } + + // If client supplied partial or empty domain-name, server should + // generate one. + if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) { + std::ostringstream name; + if (fqdn->getDomainName().empty()) { + name << FQDN_GENERATED_PARTIAL_NAME; + } else { + name << fqdn->getDomainName(); + } + name << "." << FQDN_PARTIAL_SUFFIX; + fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL); + + // Server may be configured to replace a name supplied by a client, + // even if client supplied fully qualified domain-name. + } else if (FQDN_REPLACE_CLIENT_NAME) { + std::ostringstream name; + name << FQDN_GENERATED_PARTIAL_NAME << "." << FQDN_PARTIAL_SUFFIX; + fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL); + + } + + // Return the FQDN option which can be included in the server's response. + // Note that it doesn't have to be included, if client didn't request + // it using ORO and server is not configured to always include it. + return (fqdn_resp); +} + + +void +Dhcpv6Srv::appendClientFqdn(const Pkt6Ptr& question, + Pkt6Ptr& answer, + const Option6ClientFqdnPtr& fqdn) { + + // If FQDN is NULL, it means that client did not request DNS Update, plus + // server doesn't force updates. + if (!fqdn) { + return; + } + + // Server sends back the FQDN option to the client if client has requested + // it using Option Request Option. However, server may be configured to + // send the FQDN option in its response, regardless whether client requested + // it or not. + bool include_fqdn = FQDN_ALWAYS_INCLUDE; + if (!include_fqdn) { + OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast< + OptionUint16Array>(question->getOption(D6O_ORO)); + if (oro) { + const std::vector<uint16_t>& values = oro->getValues(); + for (int i = 0; i < values.size(); ++i) { + if (values[i] == D6O_CLIENT_FQDN) { + include_fqdn = true; + } + } + } + } + + if (include_fqdn) { + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, + DHCP6_DDNS_SEND_FQDN).arg(fqdn->toText()); + answer->addOption(fqdn); + } + +} + +void +Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer, + const Option6ClientFqdnPtr& opt_fqdn) { + + // It is likely that client haven't included the FQDN option in the message + // and server is not configured to always update DNS. In such cases, + // FQDN option will be NULL. This is valid state, so we simply return. + if (!opt_fqdn) { + return; + } + + // The response message instance is always required. For instance it + // holds the Client Identifier. It is a programming error if supplied + // message is NULL. + if (!answer) { + isc_throw(isc::Unexpected, "an instance of the object" + << " encapsulating server's message must not be" + << " NULL when creating DNS NameChangeRequest"); + } + + // Get the Client Id. It is mandatory and a function creating a response + // would have thrown an exception if it was missing. Thus throwning + // Unexpected if it is missing as it is a programming error. + OptionPtr opt_duid = answer->getOption(D6O_CLIENTID); + if (!opt_duid) { + isc_throw(isc::Unexpected, + "client identifier is required when creating a new" + " DNS NameChangeRequest"); + } + DuidPtr duid = DuidPtr(new DUID(opt_duid->getData())); + + // Get the FQDN in the on-wire format. It will be needed to compute + // DHCID. + OutputBuffer name_buf(1); + opt_fqdn->packDomainName(name_buf); + const uint8_t* name_data = static_cast<const uint8_t*>(name_buf.getData()); + // @todo currently D2Dhcid accepts a vector holding FQDN. + // However, it will be faster if we used a pointer name_data. + std::vector<uint8_t> buf_vec(name_data, name_data + name_buf.getLength()); + // Compute DHCID from Client Identifier and FQDN. + isc::dhcp_ddns::D2Dhcid dhcid(*duid, buf_vec); + + // Get all IAs from the answer. For each IA, holding an address we will + // create a corresponding NameChangeRequest. + OptionCollection answer_ias = answer->getOptions(D6O_IA_NA); + for (OptionCollection::const_iterator answer_ia = + answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) { + // @todo IA_NA may contain multiple addresses. We should process + // each address individually. Currently we get only one. + Option6IAAddrPtr iaaddr = boost::static_pointer_cast< + Option6IAAddr>(answer_ia->second->getOption(D6O_IAADDR)); + // We need an address to create a name-to-address mapping. + // If address is missing for any reason, go to the next IA. + if (!iaaddr) { + continue; + } + // Create new NameChangeRequest. Use the domain name from the FQDN. + // This is an FQDN included in the response to the client, so it + // holds a fully qualified domain-name already (not partial). + // Get the IP address from the lease. Also, use the S flag to determine + // if forward change should be performed. This flag will always be + // set if server has taken responsibility for the forward update. + NameChangeRequest ncr(isc::dhcp_ddns::CHG_ADD, + opt_fqdn->getFlag(Option6ClientFqdn::FLAG_S), + true, opt_fqdn->getDomainName(), + iaaddr->getAddress().toText(), + dhcid, 0, iaaddr->getValid()); + // Add the request to the queue. This queue will be read elsewhere in + // the code and all requests from this queue will be sent to the + // D2 module. + name_change_reqs_.push(ncr); + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, + DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr.toText()); + } +} + +void +Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) { + // If we haven't performed a DNS Update when lease was acquired, + // there is nothing to do here. + if (!lease->fqdn_fwd_ && !lease->fqdn_rev_) { + return; + } + + // When lease was added into a database the host name should have + // been added. The hostname can be empty if someone messed up in the + // lease data base and removed the hostname. + if (lease->hostname_.empty()) { + LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME) + .arg(lease->addr_.toText()); + return; + } + + // If hostname is non-empty, try to convert it to wire format so as + // DHCID can be computed from it. This may throw an exception if hostname + // has invalid format. Again, this should be only possible in case of + // manual intervention in the database. Note that the last parameter + // passed to the writeFqdn function forces conversion of the FQDN + // to lower case. This is required by the RFC4701, section 3.5. + // The DHCID computation is further in this function. + std::vector<uint8_t> hostname_wire; + try { + OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true); + } catch (const Exception& ex) { + LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_INVALID_HOSTNAME) + .arg(lease->hostname_); + return; + } + + // DUID must have been checked already by the caller of this function. + // Let's be on the safe side and make sure it is non-NULL and throw + // an exception if it is NULL. + if (!lease->duid_) { + isc_throw(isc::Unexpected, "DUID must be set when creating" + << " NameChangeRequest for DNS records removal for " + << lease->addr_); + + } + isc::dhcp_ddns::D2Dhcid dhcid(*lease->duid_, hostname_wire); + + // Create a NameChangeRequest to remove the entry. + NameChangeRequest ncr(isc::dhcp_ddns::CHG_REMOVE, + lease->fqdn_fwd_, lease->fqdn_rev_, + lease->hostname_, + lease->addr_.toText(), + dhcid, 0, lease->valid_lft_); + name_change_reqs_.push(ncr); + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, + DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST).arg(ncr.toText()); + +} + +void +Dhcpv6Srv::sendNameChangeRequests() { + while (!name_change_reqs_.empty()) { + // @todo Once next NameChangeRequest is picked from the queue + // we should send it to the b10-dhcp_ddns module. Currently we + // just drop it. + name_change_reqs_.pop(); + } +} + + OptionPtr Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, - Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) { + const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia, + const Option6ClientFqdnPtr& fqdn) { // If there is no subnet selected for handling this IA_NA, the only thing to do left is // to say that we are sorry, but the user won't get an address. As a convenience, we // use a different status text to indicate that (compare to the same status code, @@ -646,17 +1273,54 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, // it should include this hint. That will help us during the actual lease // allocation. bool fake_allocation = false; - if (question->getType() == DHCPV6_SOLICIT) { + if (query->getType() == DHCPV6_SOLICIT) { /// @todo: Check if we support rapid commit fake_allocation = true; } + CalloutHandlePtr callout_handle = getCalloutHandle(query); + + // At this point, we have to make make some decisions with respect to the + // FQDN option that we have generated as a result of receiving client's + // FQDN. In particular, we have to get to know if the DNS update will be + // performed or not. It is possible that option is NULL, which is valid + // condition if client didn't request DNS updates and server didn't force + // the update. + bool do_fwd = false; + bool do_rev = false; + if (fqdn) { + // Flag S must not coexist with flag N being set to 1, so if S=1 + // server takes responsibility for both reverse and forward updates. + // Otherwise, we have to check N. + if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) { + do_fwd = true; + do_rev = true; + } else if (!fqdn->getFlag(Option6ClientFqdn::FLAG_N)) { + do_rev = true; + } + } + // Set hostname only in case any of the updates is being performed. + std::string hostname; + if (do_fwd || do_rev) { + hostname = fqdn->getDomainName(); + } + // Use allocation engine to pick a lease for this client. Allocation engine // will try to honour the hint, but it is just a hint - some other address // may be used instead. If fake_allocation is set to false, the lease will // be inserted into the LeaseMgr as well. - Lease6Ptr lease = alloc_engine_->allocateAddress6(subnet, duid, ia->getIAID(), - hint, fake_allocation); + Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid, + ia->getIAID(), + hint, Lease::TYPE_NA, + do_fwd, do_rev, + hostname, + fake_allocation, + callout_handle); + /// @todo: Handle more than one lease + Lease6Ptr lease; + if (!leases.empty()) { + lease = *leases.begin(); + } // Create IA_NA that we will put in the response. // Do not use OptionDefinition to create option's instance so @@ -685,6 +1349,29 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, // It would be possible to insert status code=0(success) as well, // but this is considered waste of bandwidth as absence of status // code is considered a success. + + // Allocation engine may have returned an existing lease. If so, we + // have to check that the FQDN settings we provided are the same + // that were set. If they aren't, we will have to remove existing + // DNS records and update the lease with the new settings. + if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) || + (lease->fqdn_rev_ != do_rev)) { + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, + DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE) + .arg(lease->toText()) + .arg(hostname) + .arg(do_rev ? "true" : "false") + .arg(do_fwd ? "true" : "false"); + + // Schedule removal of the existing lease. + createRemovalNameChangeRequest(lease); + // Set the new lease properties and update. + lease->hostname_ = hostname; + lease->fqdn_fwd_ = do_fwd; + lease->fqdn_rev_ = do_rev; + LeaseMgrFactory::instance().updateLease6(lease); + } + } else { // Allocation engine did not allocate a lease. The engine logged // cause of that failure. The only thing left is to insert @@ -702,8 +1389,110 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, } OptionPtr +Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid, + const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) { + + // Create IA_PD that we will put in the response. + // Do not use OptionDefinition to create option's instance so + // as we can initialize IAID using a constructor. + boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID())); + + // If there is no subnet selected for handling this IA_PD, the only thing to + // do left is to say that we are sorry, but the user won't get an address. + // As a convenience, we use a different status text to indicate that + // (compare to the same status code, but different wording below) + if (!subnet) { + + // Insert status code NoAddrsAvail. + ia_rsp->addOption(createStatusCode(STATUS_NoPrefixAvail, + "Sorry, no subnet available.")); + return (ia_rsp); + } + + // Check if the client sent us a hint in his IA_PD. Clients may send an + // address in their IA_NA options as a suggestion (e.g. the last address + // they used before). + boost::shared_ptr<Option6IAPrefix> hintOpt = + boost::dynamic_pointer_cast<Option6IAPrefix>(ia->getOption(D6O_IAPREFIX)); + IOAddress hint("::"); + if (hintOpt) { + hint = hintOpt->getAddress(); + } + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_PD_REQUEST) + .arg(duid ? duid->toText() : "(no-duid)").arg(ia->getIAID()) + .arg(hintOpt ? hint.toText() : "(no hint)"); + + // "Fake" allocation is processing of SOLICIT message. We pretend to do an + // allocation, but we do not put the lease in the database. That is ok, + // because we do not guarantee that the user will get that exact lease. If + // the user selects this server to do actual allocation (i.e. sends REQUEST) + // it should include this hint. That will help us during the actual lease + // allocation. + bool fake_allocation = (query->getType() == DHCPV6_SOLICIT); + + CalloutHandlePtr callout_handle = getCalloutHandle(query); + + // Use allocation engine to pick a lease for this client. Allocation engine + // will try to honour the hint, but it is just a hint - some other address + // may be used instead. If fake_allocation is set to false, the lease will + // be inserted into the LeaseMgr as well. + Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid, + ia->getIAID(), + hint, Lease::TYPE_PD, + false, false, + string(), + fake_allocation, + callout_handle); + + if (!leases.empty()) { + + ia_rsp->setT1(subnet->getT1()); + ia_rsp->setT2(subnet->getT2()); + + for (Lease6Collection::iterator l = leases.begin(); + l != leases.end(); ++l) { + + // We have a lease! Let's wrap its content into IA_PD option + // with IAADDR suboption. + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, fake_allocation ? + DHCP6_PD_LEASE_ADVERT : DHCP6_PD_LEASE_ALLOC) + .arg((*l)->addr_.toText()) + .arg(static_cast<int>((*l)->prefixlen_)) + .arg(duid ? duid->toText() : "(no-duid)") + .arg(ia->getIAID()); + + boost::shared_ptr<Option6IAPrefix> + addr(new Option6IAPrefix(D6O_IAPREFIX, (*l)->addr_, + (*l)->prefixlen_, (*l)->preferred_lft_, + (*l)->valid_lft_)); + ia_rsp->addOption(addr); + } + + // It would be possible to insert status code=0(success) as well, + // but this is considered waste of bandwidth as absence of status + // code is considered a success. + + } else { + // Allocation engine did not allocate a lease. The engine logged + // cause of that failure. The only thing left is to insert + // status code to pass the sad news to the client. + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, fake_allocation ? + DHCP6_PD_LEASE_ADVERT_FAIL : DHCP6_PD_LEASE_ALLOC_FAIL) + .arg(duid ? duid->toText() : "(no-duid)") + .arg(ia->getIAID()); + + ia_rsp->addOption(createStatusCode(STATUS_NoPrefixAvail, + "Sorry, no prefixes could be allocated.")); + } + return (ia_rsp); +} + +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, + const Option6ClientFqdnPtr& fqdn) { 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())); @@ -719,7 +1508,8 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, return (ia_rsp); } - Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(*duid, ia->getIAID(), + Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, + *duid, ia->getIAID(), subnet->getID()); if (!lease) { @@ -732,7 +1522,7 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, ia_rsp->addOption(createStatusCode(STATUS_NoBinding, "Sorry, no known leases for this duid/iaid.")); - LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW) + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW_NA) .arg(duid->toText()) .arg(ia->getIAID()) .arg(subnet->toText()); @@ -740,13 +1530,53 @@ 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; + + // At this point, we have to make make some decisions with respect to the + // FQDN option that we have generated as a result of receiving client's + // FQDN. In particular, we have to get to know if the DNS update will be + // performed or not. It is possible that option is NULL, which is valid + // condition if client didn't request DNS updates and server didn't force + // the update. + bool do_fwd = false; + bool do_rev = false; + if (fqdn) { + if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) { + do_fwd = true; + do_rev = true; + } else if (!fqdn->getFlag(Option6ClientFqdn::FLAG_N)) { + do_rev = true; + } + } + + std::string hostname; + if (do_fwd || do_rev) { + hostname = fqdn->getDomainName(); + } + + // If the new FQDN settings have changed for the lease, we need to + // delete any existing FQDN records for this lease. + if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) || + (lease->fqdn_rev_ != do_rev)) { + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, + DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE) + .arg(lease->toText()) + .arg(hostname) + .arg(do_rev ? "true" : "false") + .arg(do_fwd ? "true" : "false"); + + createRemovalNameChangeRequest(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); + lease->hostname_ = hostname; + lease->fqdn_fwd_ = do_fwd; + lease->fqdn_rev_ = do_rev; // Create empty IA_NA option with IAID matching the request. boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID())); @@ -758,11 +1588,153 @@ 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::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); +} + +OptionPtr +Dhcpv6Srv::renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid, + const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) { + + // Let's create a IA_NA response and fill it in later + boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID())); + + if (!subnet) { + // Insert status code NoBinding + ia_rsp->addOption(createStatusCode(STATUS_NoBinding, + "Sorry, no known leases for this duid/iaid.")); + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RENEW_UNKNOWN_SUBNET) + .arg(duid->toText()) + .arg(ia->getIAID()); + + return (ia_rsp); + } + + Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, + *duid, ia->getIAID(), + subnet->getID()); + + if (!lease) { + // Client is renewing a lease that we don't know about. + + // Insert status code NoBinding + ia_rsp->addOption(createStatusCode(STATUS_NoBinding, + "Sorry, no known leases for this duid/iaid.")); + + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW_PD) + .arg(duid->toText()) + .arg(ia->getIAID()) + .arg(subnet->toText()); + + return (ia_rsp); + } + + // Keep the old data in case the callout tells us to skip update. + Lease6 old_data = *lease; + + // Do the actual lease update + lease->preferred_lft_ = subnet->getPreferred(); + lease->valid_lft_ = subnet->getValid(); + lease->t1_ = subnet->getT1(); + lease->t2_ = subnet->getT2(); + lease->cltt_ = time(NULL); + + // Also update IA_PD container with proper T1, T2 values + ia_rsp->setT1(subnet->getT1()); + ia_rsp->setT2(subnet->getT2()); + + boost::shared_ptr<Option6IAPrefix> + prefix(new Option6IAPrefix(D6O_IAPREFIX, lease->addr_, + lease->prefixlen_, lease->preferred_lft_, + lease->valid_lft_)); + ia_rsp->addOption(prefix); + + bool skip = false; + // Execute all callouts registered for packet6_send + if (HooksManager::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_pd", ia_rsp); + + // Call all installed callouts + HooksManager::callCallouts(Hooks.hook_index_lease6_renew_, + *callout_handle); + + // Remember hook's instruction whether we want to skip update or not + skip = callout_handle->getSkip(); + } + + if (!skip) { + LeaseMgrFactory::instance().updateLease6(lease); + } else { + // 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". + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RENEW_SKIP); + + // 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); } void -Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) { +Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply, + const Option6ClientFqdnPtr& fqdn) { // We need to renew addresses for all IA_NA options in the client's // RENEW message. @@ -801,17 +1773,31 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) { } DuidPtr duid(new DUID(opt_duid->getData())); - for (Option::OptionCollection::iterator opt = renew->options_.begin(); + for (OptionCollection::iterator opt = renew->options_.begin(); opt != renew->options_.end(); ++opt) { switch (opt->second->getType()) { + case D6O_IA_NA: { OptionPtr answer_opt = renewIA_NA(subnet, duid, renew, - boost::dynamic_pointer_cast<Option6IA>(opt->second)); + boost::dynamic_pointer_cast< + Option6IA>(opt->second), + fqdn); + if (answer_opt) { + reply->addOption(answer_opt); + } + break; + } + + case D6O_IA_PD: { + OptionPtr answer_opt = renewIA_PD(subnet, duid, renew, + boost::dynamic_pointer_cast< + Option6IA>(opt->second)); if (answer_opt) { reply->addOption(answer_opt); } break; } + default: break; } @@ -848,8 +1834,13 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) { } DuidPtr duid(new DUID(opt_duid->getData())); + // Let's set the status to be success by default. We can override it with + // error status if needed. The important thing to understand here is that + // the global status code may be set to success only if all IA options were + // handled properly. Therefore the releaseIA_NA and releaseIA_PD options + // may turn the status code to some error, but can't turn it back to success. int general_status = STATUS_Success; - for (Option::OptionCollection::iterator opt = release->options_.begin(); + for (OptionCollection::iterator opt = release->options_.begin(); opt != release->options_.end(); ++opt) { switch (opt->second->getType()) { case D6O_IA_NA: { @@ -860,7 +1851,14 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) { } break; } - // @todo: add support for IA_PD + case D6O_IA_PD: { + OptionPtr answer_opt = releaseIA_PD(duid, release, general_status, + boost::dynamic_pointer_cast<Option6IA>(opt->second)); + if (answer_opt) { + reply->addOption(answer_opt); + } + break; + } // @todo: add support for IA_TA default: // remaining options are stateless and thus ignored in this context @@ -876,7 +1874,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 @@ -894,12 +1892,13 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */, (ia->getOption(D6O_IAADDR)); if (!release_addr) { ia_rsp->addOption(createStatusCode(STATUS_NoBinding, - "You did not include address in your RELEASE")); + "You did not include an address in your RELEASE")); general_status = STATUS_NoBinding; return (ia_rsp); } - Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(release_addr->getAddress()); + Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, + release_addr->getAddress()); if (!lease) { // client releasing a lease that we don't know about. @@ -909,7 +1908,7 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */, "Sorry, no known leases for this duid/iaid, can't release.")); general_status = STATUS_NoBinding; - LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE) + LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE_NA) .arg(duid->toText()) .arg(ia->getIAID()); @@ -921,7 +1920,7 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */, // have mandatory DUID information attached. Someone was messing with our // database. - LOG_ERROR(dhcp6_logger, DHCP6_LEASE_WITHOUT_DUID) + LOG_ERROR(dhcp6_logger, DHCP6_LEASE_NA_WITHOUT_DUID) .arg(release_addr->getAddress().toText()); general_status = STATUS_UnspecFail; @@ -931,9 +1930,9 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */, } if (*duid != *(lease->duid_)) { - // Sorry, it's not your address. You can't release it. - LOG_INFO(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_DUID) + // Sorry, it's not your address. You can't release it. + LOG_INFO(dhcp6_logger, DHCP6_RELEASE_NA_FAIL_WRONG_DUID) .arg(duid->toText()) .arg(release_addr->getAddress().toText()) .arg(lease->duid_->toText()); @@ -946,7 +1945,7 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */, if (ia->getIAID() != lease->iaid_) { // This address belongs to this client, but to a different IA - LOG_WARN(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_IAID) + LOG_WARN(dhcp6_logger, DHCP6_RELEASE_PD_FAIL_WRONG_IAID) .arg(duid->toText()) .arg(release_addr->getAddress().toText()) .arg(lease->iaid_) @@ -960,13 +1959,47 @@ 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::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_NA_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")); - LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_FAIL) + LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_NA_FAIL) .arg(lease->addr_.toText()) .arg(duid->toText()) .arg(lease->iaid_); @@ -974,7 +2007,7 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */, return (ia_rsp); } else { - LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE) + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE_NA) .arg(lease->addr_.toText()) .arg(duid->toText()) .arg(lease->iaid_); @@ -982,10 +2015,157 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */, ia_rsp->addOption(createStatusCode(STATUS_Success, "Lease released. Thank you, please come again.")); + // Check if a lease has flags indicating that the FQDN update has + // been performed. If so, create NameChangeRequest which removes + // the entries. + createRemovalNameChangeRequest(lease); + return (ia_rsp); } } +OptionPtr +Dhcpv6Srv::releaseIA_PD(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 + // to this particular client. + // Approach 2: find a subnet for this client, get a lease for + // this subnet/duid/iaid and check if its content matches to what the + // client is asking us to release. + // + // This method implements approach 1. + + // That's our response. We will fill it in as we check the lease to be + // released. + boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID())); + + boost::shared_ptr<Option6IAPrefix> release_prefix = + boost::dynamic_pointer_cast<Option6IAPrefix>(ia->getOption(D6O_IAPREFIX)); + if (!release_prefix) { + ia_rsp->addOption(createStatusCode(STATUS_NoBinding, + "You did not include a prefix in your RELEASE")); + general_status = STATUS_NoBinding; + return (ia_rsp); + } + + Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, + release_prefix->getAddress()); + + if (!lease) { + // Client releasing a lease that we don't know about. + + // Insert status code NoBinding. + ia_rsp->addOption(createStatusCode(STATUS_NoBinding, + "Sorry, no known leases for this duid/iaid, can't release.")); + general_status = STATUS_NoBinding; + + LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE_PD) + .arg(duid->toText()) + .arg(ia->getIAID()); + + return (ia_rsp); + } + + if (!lease->duid_) { + // Something is gravely wrong here. We do have a lease, but it does not + // have mandatory DUID information attached. Someone was messing with our + // database. + LOG_ERROR(dhcp6_logger, DHCP6_LEASE_PD_WITHOUT_DUID) + .arg(release_prefix->getAddress().toText()); + + general_status = STATUS_UnspecFail; + ia_rsp->addOption(createStatusCode(STATUS_UnspecFail, + "Database consistency check failed when trying to RELEASE")); + return (ia_rsp); + } + + if (*duid != *(lease->duid_)) { + // Sorry, it's not your address. You can't release it. + LOG_INFO(dhcp6_logger, DHCP6_RELEASE_PD_FAIL_WRONG_DUID) + .arg(duid->toText()) + .arg(release_prefix->getAddress().toText()) + .arg(lease->duid_->toText()); + + general_status = STATUS_NoBinding; + ia_rsp->addOption(createStatusCode(STATUS_NoBinding, + "This address does not belong to you, you can't release it")); + return (ia_rsp); + } + + if (ia->getIAID() != lease->iaid_) { + // This address belongs to this client, but to a different IA + LOG_WARN(dhcp6_logger, DHCP6_RELEASE_PD_FAIL_WRONG_IAID) + .arg(duid->toText()) + .arg(release_prefix->getAddress().toText()) + .arg(lease->iaid_) + .arg(ia->getIAID()); + ia_rsp->addOption(createStatusCode(STATUS_NoBinding, + "This is your address, but you used wrong IAID")); + general_status = STATUS_NoBinding; + return (ia_rsp); + } + + // 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::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); + + skip = callout_handle->getSkip(); + } + + // Ok, we've passed all checks. Let's release this prefix. + bool success = false; // was the removal operation succeessful? + + if (!skip) { + success = LeaseMgrFactory::instance().deleteLease(lease->addr_); + } else { + // 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". + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RELEASE_PD_SKIP); + } + + // 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 (!success) { + ia_rsp->addOption(createStatusCode(STATUS_UnspecFail, + "Server failed to release a lease")); + + LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_PD_FAIL) + .arg(lease->addr_.toText()) + .arg(duid->toText()) + .arg(lease->iaid_); + general_status = STATUS_UnspecFail; + } else { + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE_PD) + .arg(lease->addr_.toText()) + .arg(duid->toText()) + .arg(lease->iaid_); + + ia_rsp->addOption(createStatusCode(STATUS_Success, + "Lease released. Thank you, please come again.")); + } + + return (ia_rsp); +} + Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { @@ -996,8 +2176,14 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { copyDefaultOptions(solicit, advertise); appendDefaultOptions(solicit, advertise); appendRequestedOptions(solicit, advertise); + appendRequestedVendorOptions(solicit, advertise); - assignLeases(solicit, advertise); + Option6ClientFqdnPtr fqdn = processClientFqdn(solicit); + assignLeases(solicit, advertise, fqdn); + appendClientFqdn(solicit, advertise, fqdn); + // Note, that we don't create NameChangeRequests here because we don't + // perform DNS Updates for Solicit. Client must send Request to update + // DNS. return (advertise); } @@ -1012,8 +2198,12 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) { copyDefaultOptions(request, reply); appendDefaultOptions(request, reply); appendRequestedOptions(request, reply); + appendRequestedVendorOptions(request, reply); - assignLeases(request, reply); + Option6ClientFqdnPtr fqdn = processClientFqdn(request); + assignLeases(request, reply, fqdn); + appendClientFqdn(request, reply, fqdn); + createNameChangeRequests(reply, fqdn); return (reply); } @@ -1029,13 +2219,17 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { appendDefaultOptions(renew, reply); appendRequestedOptions(renew, reply); - renewLeases(renew, reply); + Option6ClientFqdnPtr fqdn = processClientFqdn(renew); + renewLeases(renew, reply, fqdn); + appendClientFqdn(renew, reply, fqdn); + createNameChangeRequests(reply, fqdn); return reply; } Pkt6Ptr Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { + /// @todo: Implement this Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid())); return reply; @@ -1060,7 +2254,10 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) { releaseLeases(release, reply); - return reply; + // @todo If client sent a release and we should remove outstanding + // DNS records. + + return (reply); } Pkt6Ptr @@ -1077,5 +2274,191 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) { return reply; } +void +Dhcpv6Srv::openActiveSockets(const uint16_t port) { + IfaceMgr::instance().closeSockets(); + + // Get the reference to the collection of interfaces. This reference should be + // valid as long as the program is run because IfaceMgr is a singleton. + // Therefore we can safely iterate over instances of all interfaces and modify + // their flags. Here we modify flags which indicate wheter socket should be + // open for a particular interface or not. + const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces(); + for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin(); + iface != ifaces.end(); ++iface) { + Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName()); + if (iface_ptr == NULL) { + isc_throw(isc::Unexpected, "Interface Manager returned NULL" + << " instance of the interface when DHCPv6 server was" + << " trying to reopen sockets after reconfiguration"); + } + if (CfgMgr::instance().isActiveIface(iface->getName())) { + iface_ptr->inactive6_ = false; + LOG_INFO(dhcp6_logger, DHCP6_ACTIVATE_INTERFACE) + .arg(iface->getFullName()); + + } else { + // For deactivating interface, it should be sufficient to log it + // on the debug level because it is more useful to know what + // interface is activated which is logged on the info level. + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, + DHCP6_DEACTIVATE_INTERFACE).arg(iface->getName()); + iface_ptr->inactive6_ = true; + + } + + iface_ptr->clearUnicasts(); + + const IOAddress* unicast = CfgMgr::instance().getUnicast(iface->getName()); + if (unicast) { + LOG_INFO(dhcp6_logger, DHCP6_SOCKET_UNICAST).arg(unicast->toText()) + .arg(iface->getName()); + iface_ptr->addUnicast(*unicast); + } + } + // Let's reopen active sockets. openSockets6 will check internally whether + // sockets are marked active or inactive. + // @todo Optimization: we should not reopen all sockets but rather select + // those that have been affected by the new configuration. + isc::dhcp::IfaceMgrErrorMsgCallback error_handler = + boost::bind(&Dhcpv6Srv::ifaceMgrSocket6ErrorHandler, _1); + if (!IfaceMgr::instance().openSockets6(port, error_handler)) { + LOG_WARN(dhcp6_logger, DHCP6_NO_SOCKETS_OPEN); + } +} + +size_t +Dhcpv6Srv::unpackOptions(const OptionBuffer& buf, + const std::string& option_space, + isc::dhcp::OptionCollection& options, + size_t* relay_msg_offset, + size_t* relay_msg_len) { + size_t offset = 0; + size_t length = buf.size(); + + OptionDefContainer option_defs; + if (option_space == "dhcp6") { + // Get the list of stdandard option definitions. + option_defs = LibDHCP::getOptionDefs(Option::V6); + } else if (!option_space.empty()) { + OptionDefContainerPtr option_defs_ptr = + CfgMgr::instance().getOptionDefs(option_space); + if (option_defs_ptr != NULL) { + option_defs = *option_defs_ptr; + } + } + + // Get the search index #1. It allows to search for option definitions + // using option code. + const OptionDefContainerTypeIndex& idx = option_defs.get<1>(); + + // The buffer being read comprises a set of options, each starting with + // a two-byte type code and a two-byte length field. + while (offset + 4 <= length) { + uint16_t opt_type = isc::util::readUint16(&buf[offset]); + offset += 2; + + uint16_t opt_len = isc::util::readUint16(&buf[offset]); + offset += 2; + + if (offset + opt_len > length) { + // @todo: consider throwing exception here. + return (offset); + } + + if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) { + // remember offset of the beginning of the relay-msg option + *relay_msg_offset = offset; + *relay_msg_len = opt_len; + + // do not create that relay-msg option + offset += opt_len; + continue; + } + + // Get all definitions with the particular option code. Note that option + // code is non-unique within this container however at this point we + // expect to get one option definition with the particular code. If more + // are returned we report an error. + const OptionDefContainerTypeRange& range = idx.equal_range(opt_type); + // Get the number of returned option definitions for the option code. + size_t num_defs = distance(range.first, range.second); + + OptionPtr opt; + if (num_defs > 1) { + // Multiple options of the same code are not supported right now! + isc_throw(isc::Unexpected, "Internal error: multiple option definitions" + " for option type " << opt_type << " returned. Currently it is not" + " supported to initialize multiple option definitions" + " for the same option code. This will be supported once" + " support for option spaces is implemented"); + } else if (num_defs == 0) { + // @todo Don't crash if definition does not exist because only a few + // option definitions are initialized right now. In the future + // we will initialize definitions for all options and we will + // remove this elseif. For now, return generic option. + opt = OptionPtr(new Option(Option::V6, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len)); + opt->setEncapsulatedSpace("dhcp6"); + } else { + // The option definition has been found. Use it to create + // the option instance from the provided buffer chunk. + const OptionDefinitionPtr& def = *(range.first); + assert(def); + opt = def->optionFactory(Option::V6, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len, + boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2, + _3, _4, _5)); + } + // add option to options + options.insert(std::make_pair(opt_type, opt)); + offset += opt_len; + } + + return (offset); +} + +void +Dhcpv6Srv::ifaceMgrSocket6ErrorHandler(const std::string& errmsg) { + // Log the reason for socket opening failure and return. + LOG_WARN(dhcp6_logger, DHCP6_OPEN_SOCKET_FAIL).arg(errmsg); +} + +void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) { + + boost::shared_ptr<OptionCustom> vclass = + boost::dynamic_pointer_cast<OptionCustom>(pkt->getOption(D6O_VENDOR_CLASS)); + + if (!vclass) { + return; + } + + string classes = ""; + + // DOCSIS specific section + if (vclass->readString(VENDOR_CLASS_STRING_INDEX) + .find(DOCSIS3_CLASS_MODEM) != std::string::npos) { + pkt->addClass(DOCSIS3_CLASS_MODEM); + classes += string(DOCSIS3_CLASS_MODEM) + " "; + } else + if (vclass->readString(VENDOR_CLASS_STRING_INDEX) + .find(DOCSIS3_CLASS_EROUTER) != std::string::npos) { + pkt->addClass(DOCSIS3_CLASS_EROUTER); + classes += string(DOCSIS3_CLASS_EROUTER) + " "; + }else + { + // Otherwise use the string as is + classes += vclass->readString(VENDOR_CLASS_STRING_INDEX); + pkt->addClass(vclass->readString(VENDOR_CLASS_STRING_INDEX)); + } + + if (!classes.empty()) { + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED) + .arg(classes); + } +} + }; }; |