diff --git a/src/bin/dhcp4/dhcp4_messages.cc b/src/bin/dhcp4/dhcp4_messages.cc index 5e7f9bf8c1..b31f22c7d2 100644 --- a/src/bin/dhcp4/dhcp4_messages.cc +++ b/src/bin/dhcp4/dhcp4_messages.cc @@ -95,6 +95,7 @@ extern const isc::log::MessageID DHCP4_HOOK_PACKET_RCVD_SKIP = "DHCP4_HOOK_PACKE extern const isc::log::MessageID DHCP4_HOOK_PACKET_SEND_DROP = "DHCP4_HOOK_PACKET_SEND_DROP"; extern const isc::log::MessageID DHCP4_HOOK_PACKET_SEND_SKIP = "DHCP4_HOOK_PACKET_SEND_SKIP"; extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_DROP = "DHCP4_HOOK_SUBNET4_SELECT_DROP"; +extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_PARK = "DHCP4_HOOK_SUBNET4_SELECT_PARK"; extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_SKIP = "DHCP4_HOOK_SUBNET4_SELECT_SKIP"; extern const isc::log::MessageID DHCP4_INFORM_DIRECT_REPLY = "DHCP4_INFORM_DIRECT_REPLY"; extern const isc::log::MessageID DHCP4_INIT_FAIL = "DHCP4_INIT_FAIL"; @@ -279,6 +280,7 @@ const char* values[] = { "DHCP4_HOOK_PACKET_SEND_DROP", "%1: prepared DHCPv4 response was not sent because a callout set the next ste to DROP", "DHCP4_HOOK_PACKET_SEND_SKIP", "%1: prepared response is not sent, because a callout set the next stp to SKIP", "DHCP4_HOOK_SUBNET4_SELECT_DROP", "%1: packet was dropped, because a callout set the next step to 'drop'", + "DHCP4_HOOK_SUBNET4_SELECT_PARK", "%1: packet was parked", "DHCP4_HOOK_SUBNET4_SELECT_SKIP", "%1: no subnet was selected, because a callout set the next skip flag", "DHCP4_INFORM_DIRECT_REPLY", "%1: DHCPACK in reply to the DHCPINFORM will be sent directly to %2 over %3", "DHCP4_INIT_FAIL", "failed to initialize Kea server: %1", diff --git a/src/bin/dhcp4/dhcp4_messages.h b/src/bin/dhcp4/dhcp4_messages.h index a5baf1e75f..8427698ed1 100644 --- a/src/bin/dhcp4/dhcp4_messages.h +++ b/src/bin/dhcp4/dhcp4_messages.h @@ -96,6 +96,7 @@ extern const isc::log::MessageID DHCP4_HOOK_PACKET_RCVD_SKIP; extern const isc::log::MessageID DHCP4_HOOK_PACKET_SEND_DROP; extern const isc::log::MessageID DHCP4_HOOK_PACKET_SEND_SKIP; extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_DROP; +extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_PARK; extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_SKIP; extern const isc::log::MessageID DHCP4_INFORM_DIRECT_REPLY; extern const isc::log::MessageID DHCP4_INIT_FAIL; diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index 5818788a2f..a439bbdb32 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2023 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -523,6 +523,11 @@ point, the setting to that value instructs the server to drop the received packet. The argument specifies the client and transaction identification information. +% DHCP4_HOOK_SUBNET4_SELECT_PARK %1: packet was parked +This debug message is printed when a callout installed on the +subnet4_select hook point set the park flag. The argument holds the +client and transaction identification information. + % DHCP4_HOOK_SUBNET4_SELECT_SKIP %1: no subnet was selected, because a callout set the next skip flag This debug message is printed when a callout installed on the subnet4_select hook point sets the next step to SKIP value. For this particular hook diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 30c26bfb6b..5359f44cb4 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -715,11 +715,11 @@ Dhcpv4Srv::shutdown() { isc::dhcp::Subnet4Ptr Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query, bool& drop, - bool sanity_only) const { + bool sanity_only, bool allow_answer_park) { // DHCPv4-over-DHCPv6 is a special (and complex) case if (query->isDhcp4o6()) { - return (selectSubnet4o6(query, drop, sanity_only)); + return (selectSubnet4o6(query, drop, sanity_only, allow_answer_park)); } Subnet4Ptr subnet; @@ -751,9 +751,34 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query, bool& drop, cfgmgr.getCurrentCfg()-> getCfgSubnets4()->getAll()); + // We proactively park the packet. + HooksManager::park("subnet4_select", query, + [this, query, allow_answer_park] () { + processLocalizedQuery4AndSendResponse(query, + allow_answer_park); + }); + // Call user (and server-side) callouts - HooksManager::callCallouts(Hooks.hook_index_subnet4_select_, - *callout_handle); + try { + HooksManager::callCallouts(Hooks.hook_index_subnet4_select_, + *callout_handle); + } catch (...) { + // Make sure we don't orphan a parked packet. + HooksManager::drop("subnet4_select", query); + throw; + } + + // Callouts parked the packet. Same as drop but callouts will resume + // processing or drop the packet later. + if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) { + LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, + DHCP4_HOOK_SUBNET4_SELECT_PARK) + .arg(query->getLabel()); + drop = true; + return (Subnet4Ptr()); + } else { + HooksManager::drop("subnet4_select", query); + } // Callouts decided to skip this step. This means that no subnet // will be selected. Packet processing will continue, but it will @@ -801,7 +826,7 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query, bool& drop, isc::dhcp::Subnet4Ptr Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop, - bool sanity_only) const { + bool sanity_only, bool allow_answer_park) { Subnet4Ptr subnet; SubnetSelector selector; @@ -861,6 +886,9 @@ Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop, // handle and its arguments. ScopedCalloutHandleState callout_handle_state(callout_handle); + // Enable copying options from the packet within hook library. + ScopedEnableOptionsCopy query4_options_copy(query); + // Set new arguments callout_handle->setArgument("query4", query); callout_handle->setArgument("subnet4", subnet); @@ -868,9 +896,34 @@ Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop, cfgmgr.getCurrentCfg()-> getCfgSubnets4()->getAll()); + // We proactively park the packet. + HooksManager::park("subnet4_select", query, + [this, query, allow_answer_park] () { + processLocalizedQuery4AndSendResponse(query, + allow_answer_park); + }); + // Call user (and server-side) callouts - HooksManager::callCallouts(Hooks.hook_index_subnet4_select_, - *callout_handle); + try { + HooksManager::callCallouts(Hooks.hook_index_subnet4_select_, + *callout_handle); + } catch (...) { + // Make sure we don't orphan a parked packet. + HooksManager::drop("subnet4_select", query); + throw; + } + + // Callouts parked the packet. Same as drop but callouts will resume + // processing or drop the packet later. + if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) { + LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, + DHCP4_HOOK_SUBNET4_SELECT_PARK) + .arg(query->getLabel()); + drop = true; + return (Subnet4Ptr()); + } else { + HooksManager::drop("subnet4_select", query); + } // Callouts decided to skip this step. This means that no subnet // will be selected. Packet processing will continue, but it will @@ -1147,7 +1200,7 @@ Dhcpv4Srv::processPacketAndSendResponse(Pkt4Ptr query) { } Pkt4Ptr -Dhcpv4Srv::processPacket(Pkt4Ptr query, bool allow_packet_park) { +Dhcpv4Srv::processPacket(Pkt4Ptr query, bool allow_answer_park) { query->addPktEvent("process_started"); // All packets belong to ALL. @@ -1326,14 +1379,14 @@ Dhcpv4Srv::processPacket(Pkt4Ptr query, bool allow_packet_park) { return (Pkt4Ptr()); } - return (processDhcp4Query(query, allow_packet_park)); + return (processDhcp4Query(query, allow_answer_park)); } void Dhcpv4Srv::processDhcp4QueryAndSendResponse(Pkt4Ptr query, - bool allow_packet_park) { + bool allow_answer_park) { try { - Pkt4Ptr rsp = processDhcp4Query(query, allow_packet_park); + Pkt4Ptr rsp = processDhcp4Query(query, allow_answer_park); if (!rsp) { return; } @@ -1349,7 +1402,7 @@ Dhcpv4Srv::processDhcp4QueryAndSendResponse(Pkt4Ptr query, } Pkt4Ptr -Dhcpv4Srv::processDhcp4Query(Pkt4Ptr query, bool allow_packet_park) { +Dhcpv4Srv::processDhcp4Query(Pkt4Ptr query, bool allow_answer_park) { // Create a client race avoidance RAII handler. ClientHandler client_handler; @@ -1361,7 +1414,7 @@ Dhcpv4Srv::processDhcp4Query(Pkt4Ptr query, bool allow_packet_park) { (query->getType() == DHCPDECLINE))) { ContinuationPtr cont = makeContinuation(std::bind(&Dhcpv4Srv::processDhcp4QueryAndSendResponse, - this, query, allow_packet_park)); + this, query, allow_answer_park)); if (!client_handler.tryLock(query, cont)) { return (Pkt4Ptr()); } @@ -1378,7 +1431,7 @@ Dhcpv4Srv::processDhcp4Query(Pkt4Ptr query, bool allow_packet_park) { (query->getType() == DHCPREQUEST) || (query->getType() == DHCPINFORM)) { bool drop = false; - ctx->subnet_ = selectSubnet(query, drop); + ctx->subnet_ = selectSubnet(query, drop, false, allow_answer_park); // Stop here if selectSubnet decided to drop the packet if (drop) { return (Pkt4Ptr()); @@ -1402,14 +1455,15 @@ Dhcpv4Srv::processDhcp4Query(Pkt4Ptr query, bool allow_packet_park) { static_cast(1)); } - return (processLocalizedQuery4(ctx, allow_packet_park)); + return (processLocalizedQuery4(ctx, allow_answer_park)); } void Dhcpv4Srv::processLocalizedQuery4AndSendResponse(Pkt4Ptr query, - AllocEngine::ClientContext4Ptr& ctx) { + AllocEngine::ClientContext4Ptr& ctx, + bool allow_answer_park) { try { - Pkt4Ptr rsp = processLocalizedQuery4(ctx, true); + Pkt4Ptr rsp = processLocalizedQuery4(ctx, allow_answer_park); if (!rsp) { return; } @@ -1425,9 +1479,27 @@ Dhcpv4Srv::processLocalizedQuery4AndSendResponse(Pkt4Ptr query, } } +void +Dhcpv4Srv::processLocalizedQuery4AndSendResponse(Pkt4Ptr query, + bool allow_answer_park) { + // Initialize context. + AllocEngine::ClientContext4Ptr ctx(new AllocEngine::ClientContext4()); + initContext0(query, ctx); + + // Subnet is cached in the callout context associated to the query. + try { + CalloutHandlePtr callout_handle = getCalloutHandle(query); + callout_handle->getContext("subnet4", ctx->subnet_); + } catch (const Exception&) { + // No subnet, leave it to null... + } + + processLocalizedQuery4AndSendResponse(query, ctx, allow_answer_park); +} + Pkt4Ptr Dhcpv4Srv::processLocalizedQuery4(AllocEngine::ClientContext4Ptr& ctx, - bool allow_packet_park) { + bool allow_answer_park) { if (!ctx) { isc_throw(Unexpected, "null context"); } @@ -1553,7 +1625,7 @@ Dhcpv4Srv::processLocalizedQuery4(AllocEngine::ClientContext4Ptr& ctx, callout_handle->setArgument("deleted_leases4", deleted_leases); } - if (allow_packet_park) { + if (allow_answer_park) { // Get the parking limit. Parsing should ensure the value is present. uint32_t parked_packet_limit = 0; data::ConstElementPtr ppl = CfgMgr::instance().getCurrentCfg()-> @@ -1636,7 +1708,7 @@ Dhcpv4Srv::processLocalizedQuery4(AllocEngine::ClientContext4Ptr& ctx, HooksManager::callCallouts(hook_idx, *callout_handle); } catch (...) { // Make sure we don't orphan a parked packet. - if (allow_packet_park) { + if (allow_answer_park) { HooksManager::drop(hook_label, query); } @@ -1644,7 +1716,7 @@ Dhcpv4Srv::processLocalizedQuery4(AllocEngine::ClientContext4Ptr& ctx, } if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) && - allow_packet_park) { + allow_answer_park) { LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, pkt_park_msg) .arg(query->getLabel()); // Since the hook library(ies) are going to do the unparking, then @@ -4172,7 +4244,7 @@ Dhcpv4Srv::processInform(Pkt4Ptr& inform, AllocEngine::ClientContext4Ptr& contex } bool -Dhcpv4Srv::accept(const Pkt4Ptr& query) const { +Dhcpv4Srv::accept(const Pkt4Ptr& query) { // Check that the message type is accepted by the server. We rely on the // function called to log a message if needed. if (!acceptMessageType(query)) { @@ -4200,7 +4272,7 @@ Dhcpv4Srv::accept(const Pkt4Ptr& query) const { } bool -Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const { +Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) { // Accept all relayed messages. if (pkt->isRelayed()) { return (true); diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index a38c152866..eb530280ec 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -369,9 +369,9 @@ class Dhcpv4Srv : public process::Daemon { /// methods, generates appropriate answer. /// /// @param query A pointer to the packet to be processed. - /// @param allow_packet_park Indicates if parking a packet is allowed. + /// @param allow_answer_park Indicates if parking a packet is allowed. /// @return A pointer to the response. - Pkt4Ptr processPacket(Pkt4Ptr query, bool allow_packet_park = true); + Pkt4Ptr processPacket(Pkt4Ptr query, bool allow_answer_park = true); /// @brief Process a single incoming DHCPv4 query. /// @@ -379,9 +379,9 @@ class Dhcpv4Srv : public process::Daemon { /// generates appropriate answer. /// /// @param query A pointer to the packet to be processed. - /// @param allow_packet_park Indicates if parking a packet is allowed. + /// @param allow_answer_park Indicates if parking a packet is allowed. /// @return A pointer to the response. - Pkt4Ptr processDhcp4Query(Pkt4Ptr query, bool allow_packet_park); + Pkt4Ptr processDhcp4Query(Pkt4Ptr query, bool allow_answer_park); /// @brief Process a single incoming DHCPv4 query. /// @@ -389,19 +389,32 @@ class Dhcpv4Srv : public process::Daemon { /// generates appropriate answer, sends the answer to the client. /// /// @param query A pointer to the packet to be processed. - /// @param allow_packet_park Indicates if parking a packet is allowed. + /// @param allow_answer_park Indicates if parking a packet is allowed. void processDhcp4QueryAndSendResponse(Pkt4Ptr query, - bool allow_packet_park); + bool allow_answer_park); /// @brief Process a localized incoming DHCPv4 query. /// /// It calls per-type processXXX methods, generates appropriate answer. /// /// @param ctx Pointer to The client context. - /// @param allow_packet_park Indicates if parking a packet is allowed. + /// @param allow_answer_park Indicates if parking a packet is allowed. /// @return A pointer to the response. Pkt4Ptr processLocalizedQuery4(AllocEngine::ClientContext4Ptr& ctx, - bool allow_packet_park); + bool allow_answer_park); + + /// @brief Process a localized incoming DHCPv4 query. + /// + /// It calls per-type processXXX methods, generates appropriate answer, + /// sends the answer to the client. + /// + /// @param query A pointer to the unparked packet. + /// @param ctx Pointer to The client context. + /// @param allow_answer_park Indicates if parking a packet is allowed. + /// @return A pointer to the response. + void processLocalizedQuery4AndSendResponse(Pkt4Ptr query, + AllocEngine::ClientContext4Ptr& ctx, + bool allow_answer_park); /// @brief Process a localized incoming DHCPv4 query. /// @@ -409,10 +422,10 @@ class Dhcpv4Srv : public process::Daemon { /// for packets parked in the subnet4_select callout. /// /// @param query A pointer to the unparked packet. - /// @param ctx Pointer to The client context. + /// @param allow_answer_park Indicates if parking a packet is allowed. /// @return A pointer to the response. void processLocalizedQuery4AndSendResponse(Pkt4Ptr query, - AllocEngine::ClientContext4Ptr& ctx); + bool allow_answer_park); /// @brief Instructs the server to shut down. void shutdown() override; @@ -531,7 +544,7 @@ class Dhcpv4Srv : public process::Daemon { /// /// @return true if the message should be further processed, or false if /// the message should be discarded. - bool accept(const Pkt4Ptr& query) const; + bool accept(const Pkt4Ptr& query); /// @brief Check if a message sent by directly connected client should be /// accepted or discarded. @@ -560,7 +573,7 @@ class Dhcpv4Srv : public process::Daemon { /// /// @return true if message is accepted for further processing, false /// otherwise. - bool acceptDirectRequest(const Pkt4Ptr& query) const; + bool acceptDirectRequest(const Pkt4Ptr& query); /// @brief Check if received message type is valid for the server to /// process. @@ -1079,10 +1092,12 @@ class Dhcpv4Srv : public process::Daemon { /// @param query client's message /// @param drop if it is true the packet will be dropped /// @param sanity_only if it is true the callout won't be called + /// @param allow_answer_park Indicates if parking a packet is allowed /// @return selected subnet (or NULL if no suitable subnet was found) isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& query, bool& drop, - bool sanity_only = false) const; + bool sanity_only = false, + bool allow_answer_park = true); /// @brief Selects a subnet for a given client's DHCP4o6 packet. /// @@ -1094,10 +1109,12 @@ class Dhcpv4Srv : public process::Daemon { /// @param query client's message /// @param drop if it is true the packet will be dropped /// @param sanity_only if it is true the callout won't be called + /// @param allow_answer_park Indicates if parking a packet is allowed /// @return selected subnet (or NULL if no suitable subnet was found) isc::dhcp::Subnet4Ptr selectSubnet4o6(const Pkt4Ptr& query, bool& drop, - bool sanity_only = false) const; + bool sanity_only = false, + bool allow_answer_park = true); /// @brief dummy wrapper around IfaceMgr::receive4 ///