diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 11:36:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 11:36:04 +0000 |
commit | 040eee1aa49b49df4698d83a05af57c220127fd1 (patch) | |
tree | f635435954e6ccde5eee9893889e24f30ca68346 /src/bin/dhcp6/dhcp6_srv.h | |
parent | Initial commit. (diff) | |
download | isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.tar.xz isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.zip |
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/dhcp6/dhcp6_srv.h')
-rw-r--r-- | src/bin/dhcp6/dhcp6_srv.h | 1201 |
1 files changed, 1201 insertions, 0 deletions
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h new file mode 100644 index 0000000..2f09515 --- /dev/null +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -0,0 +1,1201 @@ +// Copyright (C) 2011-2022 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 +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef DHCPV6_SRV_H +#define DHCPV6_SRV_H + +#include <asiolink/io_service.h> +#include <dhcp/dhcp6.h> +#include <dhcp/duid.h> +#include <dhcp/option.h> +#include <dhcp/option_string.h> +#include <dhcp/option6_client_fqdn.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option_custom.h> +#include <dhcp/option_definition.h> +#include <dhcp_ddns/ncr_msg.h> +#include <dhcp/pkt6.h> +#include <dhcpsrv/alloc_engine.h> +#include <dhcpsrv/callout_handle_store.h> +#include <dhcpsrv/cb_ctl_dhcp6.h> +#include <dhcpsrv/cfg_option.h> +#include <dhcpsrv/d2_client_mgr.h> +#include <dhcpsrv/network_state.h> +#include <dhcpsrv/subnet.h> +#include <hooks/callout_handle.h> +#include <process/daemon.h> + +#include <functional> +#include <iostream> +#include <queue> + +// Undefine the macro OPTIONAL which is defined in some operating +// systems but conflicts with a member of the RequirementLevel enum in +// the server class. + +#ifdef OPTIONAL +#undef OPTIONAL +#endif + +namespace isc { +namespace dhcp { + +/// @brief This exception is thrown when DHCP server hits the error which should +/// result in discarding the message being processed. +class DHCPv6DiscardMessageError : public Exception { +public: + DHCPv6DiscardMessageError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief DHCPv6 server service. +/// +/// This singleton class represents DHCPv6 server. It contains all +/// top-level methods and routines necessary for server operation. +/// In particular, it instantiates IfaceMgr, loads or generates DUID +/// that is going to be used as server-identifier, receives incoming +/// packets, processes them, manages leases assignment and generates +/// appropriate responses. +/// +/// This class does not support any controlling mechanisms directly. +/// See the derived \ref ControlledDhcpv6Srv class for support for +/// command and configuration updates over msgq. +class Dhcpv6Srv : public process::Daemon { +private: + + /// @brief Pointer to IO service used by the server. + asiolink::IOServicePtr io_service_; + +public: + /// @brief defines if certain option may, must or must not appear + typedef enum { + FORBIDDEN, + MANDATORY, + OPTIONAL + } RequirementLevel; + + /// @brief Minimum length of a MAC address to be used in DUID generation. + static const size_t MIN_MAC_LEN = 6; + + /// @brief Default constructor. + /// + /// Instantiates necessary services, required to run DHCPv6 server. + /// In particular, creates IfaceMgr that will be responsible for + /// network interaction. Will instantiate lease manager, and load + /// old or create new DUID. It is possible to specify alternate + /// port on which DHCPv6 server will listen on and alternate port + /// where DHCPv6 server sends all responses to. Those are mostly useful + /// for testing purposes. + /// + /// @param server_port specifies port number to listen on + /// @param client_port specifies port number to send to + Dhcpv6Srv(uint16_t server_port = DHCP6_SERVER_PORT, + uint16_t client_port = 0); + + /// @brief Destructor. Used during DHCPv6 service shutdown. + virtual ~Dhcpv6Srv(); + + /// @brief Checks if the server is running in unit test mode. + /// + /// @return true if the server is running in unit test mode, + /// false otherwise. + bool inTestMode() const { + return (server_port_ == 0); + } + + /// @brief Returns pointer to the IO service used by the server. + asiolink::IOServicePtr& getIOService() { + return (io_service_); + } + + /// @brief Returns pointer to the network state used by the server. + NetworkStatePtr& getNetworkState() { + return (network_state_); + } + + /// @brief Returns an object which controls access to the configuration + /// backends. + /// + /// @return Pointer to the instance of the object which controls + /// access to the configuration backends. + CBControlDHCPv6Ptr getCBControl() const { + return (cb_control_); + } + + /// @brief returns Kea version on stdout and exit. + /// redeclaration/redefinition. @ref isc::process::Daemon::getVersion() + static std::string getVersion(bool extended); + + /// @brief Returns server-identifier option. + /// + /// @return server-id option + OptionPtr getServerID() { return serverid_; } + + /// @brief Main server processing loop. + /// + /// Main server processing loop. Call the processing step routine + /// until shut down. + /// + /// @return The value returned by @c Daemon::getExitValue(). + int run(); + + /// @brief Main server processing step. + /// + /// Main server processing step. Receives one incoming packet, calls + /// the processing packet routing and (if necessary) transmits + /// a response. + void run_one(); + + /// @brief Process a single incoming DHCPv6 packet and sends the response. + /// + /// It verifies correctness of the passed packet, calls per-type processXXX + /// methods, generates appropriate answer, sends the answer to the client. + /// + /// @param query A pointer to the packet to be processed. + void processPacketAndSendResponse(Pkt6Ptr& query); + + /// @brief Process a single incoming DHCPv6 packet and sends the response. + /// + /// It verifies correctness of the passed packet, calls per-type processXXX + /// methods, generates appropriate answer, sends the answer to the client. + /// + /// @param query A pointer to the packet to be processed. + void processPacketAndSendResponseNoThrow(Pkt6Ptr& query); + + /// @brief Process an unparked DHCPv6 packet and sends the response. + /// + /// @param callout_handle pointer to the callout handle. + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response. + void sendResponseNoThrow(hooks::CalloutHandlePtr& callout_handle, + Pkt6Ptr& query, Pkt6Ptr& rsp); + + /// @brief Process a single incoming DHCPv6 packet. + /// + /// It verifies correctness of the passed packet, calls per-type processXXX + /// methods, generates appropriate answer. + /// + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response. + void processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp); + + /// @brief Process a single incoming DHCPv6 query. + /// + /// It calls per-type processXXX methods, generates appropriate answer. + /// + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response. + void processDhcp6Query(Pkt6Ptr& query, Pkt6Ptr& rsp); + + /// @brief Process a single incoming DHCPv6 query. + /// + /// It calls per-type processXXX methods, generates appropriate answer, + /// sends the answer to the client. + /// + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response. + void processDhcp6QueryAndSendResponse(Pkt6Ptr& query, Pkt6Ptr& rsp); + + /// @brief Instructs the server to shut down. + void shutdown() override; + + /// + /// @name Public accessors returning values required to (re)open sockets. + /// + //@{ + /// + /// @brief Get UDP port on which server should listen. + /// + /// Typically, server listens on UDP port number 547. Other ports are used + /// for testing purposes only. + /// + /// @return UDP port on which server should listen. + uint16_t getServerPort() const { + return (server_port_); + } + //@} + + /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled. + /// + /// If updates are enabled, it instructs the D2ClientMgr singleton to + /// enter send mode. If D2ClientMgr encounters errors it may throw + /// D2ClientError. This method does not catch exceptions. + void startD2(); + + /// @brief Stops DHCP_DDNS client IO if DDNS updates are enabled. + /// + /// If updates are enabled, it instructs the D2ClientMgr singleton to + /// leave send mode. If D2ClientMgr encounters errors it may throw + /// D2ClientError. This method does not catch exceptions. + void stopD2(); + + /// @brief Implements the error handler for DHCP_DDNS IO errors + /// + /// Invoked when a NameChangeRequest send to kea-dhcp-ddns completes with + /// a failed status. These are communications errors, not data related + /// failures. + /// + /// This method logs the failure and then suspends all further updates. + /// Updating can only be restored by reconfiguration or restarting the + /// server. There is currently no retry logic so the first IO error that + /// occurs will suspend updates. + /// @todo We may wish to make this more robust or sophisticated. + /// + /// @param result Result code of the send operation. + /// @param ncr NameChangeRequest which failed to send. + virtual void d2ClientErrorHandler(const dhcp_ddns:: + NameChangeSender::Result result, + dhcp_ddns::NameChangeRequestPtr& ncr); + + /// @brief Discards parked packets + /// Clears the packet parking lots of all packets. + /// Called during reconfigure and shutdown. + void discardPackets(); + + /// @brief Initialize client context and perform early global + /// reservations lookup. + /// + /// @param query The query message. + /// @param ctx Reference to client context. + /// @return true if processing can continue, false if the query must be + /// dropped. + bool earlyGHRLookup(const Pkt6Ptr& query, + AllocEngine::ClientContext6& ctx); + + /// @brief Set host identifiers within a context. + /// + /// This method sets an ordered list of host identifier types and + /// values which the server should use to find host reservations. + /// The order of the set is determined by the configuration parameter, + /// host-reservation-identifiers + /// + /// @param ctx reference to the context. + static void setHostIdentifiers(AllocEngine::ClientContext6& ctx); + +protected: + + /// @brief This function sets statistics related to DHCPv6 packets processing + /// to their initial values. + /// + /// All of the statistics observed by the DHCPv6 server and with the names + /// like "pkt6-" are reset to 0. This function must be invoked in the class + /// constructor. + void setPacketStatisticsDefaults(); + + /// @brief Compare received server id with our server id + /// + /// Checks if the server id carried in a query from a client matches + /// server identifier being used by the server. + /// + /// @param pkt DHCPv6 packet carrying server identifier to be checked. + /// @return true if server id carried in the query matches server id + /// used by the server; false otherwise. + bool testServerID(const Pkt6Ptr& pkt); + + /// @brief Check if the message can be sent to unicast. + /// + /// This function checks if the received message conforms to the section 16 + /// of RFC 8415 which says that: "A server MUST discard any Solicit, Confirm, + /// Rebind or Information-request messages it receives with a Layer 3 unicast + /// destination address. + /// + /// @param pkt DHCPv6 message to be checked. + /// @return false if the message has been sent to unicast address but it is + /// not allowed according to RFC3315, section 15; true otherwise. + bool testUnicast(const Pkt6Ptr& pkt) const; + + /// @brief Verifies if specified packet meets RFC requirements + /// + /// Checks if mandatory option is really there, that forbidden option + /// is not there, and that client-id or server-id appears only once. + /// + /// @param pkt packet to be checked + /// @return false if the message should be dropped as a result of the + /// sanity check. + bool sanityCheck(const Pkt6Ptr& pkt); + + /// @brief verifies if specified packet meets RFC requirements + /// + /// Checks if mandatory option is really there, that forbidden option + /// is not there, and that client-id or server-id appears only once. + /// + /// @param pkt packet to be checked + /// @param clientid expectation regarding client-id option + /// @param serverid expectation regarding server-id option + /// @throw RFCViolation if any issues are detected + void sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid, + RequirementLevel serverid); + + /// @brief verifies if received DUID option (client-id or server-id) is sane + /// + /// @param opt option to be checked + /// @param opt_name text name to be printed + /// @throw RFCViolation if any issues are detected + void sanityCheckDUID(const OptionPtr& opt, const std::string& opt_name); + + /// @brief Processes incoming Solicit and returns response. + /// + /// Processes received Solicit message and verifies that its sender + /// should be served. In particular IA, TA and PD options are populated + /// with to-be assigned addresses, temporary addresses and delegated + /// prefixes, respectively. In the usual 4 message exchange, server is + /// expected to respond with Advertise message. However, if client + /// requests rapid-commit and server supports it, Reply will be sent + /// instead of Advertise and requested leases will be assigned + /// immediately. + /// + /// @param ctx Reference to client context + /// + /// @return Advertise, Reply message or NULL. + Pkt6Ptr processSolicit(AllocEngine::ClientContext6& ctx); + + /// @brief Processes incoming Request and returns Reply response. + /// + /// Processes incoming Request message and verifies that its sender + /// should be served. In particular IA, TA and PD options are populated + /// with assigned addresses, temporary addresses and delegated + /// prefixes, respectively. Uses LeaseMgr to allocate or update existing + /// leases. + /// + /// @param ctx Reference to client context + /// + /// @return REPLY message or NULL + Pkt6Ptr processRequest(AllocEngine::ClientContext6& ctx); + + /// @brief Processes incoming Renew message. + /// + /// @param ctx Reference to client context + /// + /// @return Reply message to be sent to the client. + Pkt6Ptr processRenew(AllocEngine::ClientContext6& ctx); + + /// @brief Processes incoming Rebind message. + /// + /// @todo There are cases when the Rebind message should be discarded + /// by the DHCP server. One of those is when the server doesn't have a + /// record of the client and it is unable to determine whether the + /// client is on the appropriate link or not. We don't seem to do it + /// now. + /// + /// @param ctx Reference to client context + /// + /// @return Reply message to be sent to the client. + Pkt6Ptr processRebind(AllocEngine::ClientContext6& ctx); + + /// @brief Processes incoming Confirm message and returns Reply. + /// + /// This function processes Confirm message from the client according + /// to section 18.3.3. of RFC 8415. It discards the Confirm message if + /// the message sent by the client contains no addresses, i.e. it has + /// no IA_NA options or all IA_NA options contain no IAAddr options. + /// + /// If the Confirm message contains addresses this function will perform + /// the following checks: + /// - check if there is appropriate subnet configured for the client + /// (e.g. subnet from which addresses are assigned for requests + /// received on the particular interface). + /// - check if all addresses sent in the Confirm message belong to the + /// selected subnet. + /// + /// If any of the checks above fails, the Reply message with the status + /// code NotOnLink is returned. Otherwise, the Reply message with the + /// status code Success is returned. + /// + /// @param ctx Reference to client context + /// + /// @return Reply message from the server or NULL pointer if Confirm + /// message should be discarded by the server. + Pkt6Ptr processConfirm(AllocEngine::ClientContext6& ctx); + + /// @brief Process incoming Release message. + /// + /// @param ctx Reference to client context + /// + /// @return Reply message to be sent to the client. + Pkt6Ptr processRelease(AllocEngine::ClientContext6& ctx); + + /// @brief Process incoming Decline message. + /// + /// This method processes Decline message. It conducts standard sanity + /// checks, creates empty reply and copies the necessary options from + /// the client's message. Finally, it calls @ref declineLeases, where + /// the actual address processing takes place. + /// + /// @param ctx Reference to client context + /// + /// @return Reply message to be sent to the client. + Pkt6Ptr processDecline(AllocEngine::ClientContext6& ctx); + + /// @brief Processes incoming Information-request message. + /// + /// @param ctx Reference to client context + /// + /// @return Reply message to be sent to the client. + Pkt6Ptr processInfRequest(AllocEngine::ClientContext6& ctx); + + /// @brief Processes incoming DHCPv4-query message. + /// + /// It always returns NULL, as there is nothing to be sent back to the + /// client at this time. The message was sent to DHCPv4 server using + /// @ref isc::dhcp::Dhcp6to4Ipc::handler()). We will send back a response + /// to the client once we get back DHCP4-REPLY from the DHCPv4 server. + /// + /// @param dhcp4_query message received from client + /// Does not throw + void processDhcp4Query(const Pkt6Ptr& dhcp4_query); + + /// @brief Selects a subnet for a given client's packet. + /// + /// @param question client's message + /// @param drop if it is true the packet will be dropped + /// @return selected subnet (or NULL if no suitable subnet was found) + isc::dhcp::Subnet6Ptr selectSubnet(const Pkt6Ptr& question, bool& drop); + + /// @brief Processes IA_NA option (and assigns addresses if necessary). + /// + /// Generates response to IA_NA. This typically includes selecting (and + /// allocating a lease in case of REQUEST) an address lease and creating + /// IAADDR option. In case of allocation failure, it may contain + /// status code option with non-zero status, denoting cause of the + /// allocation failure. + /// + /// @param query client's message (typically SOLICIT or REQUEST) + /// to the client (if the client sent this option to the server). + /// @param ctx client context (contains subnet, duid and other parameters) + /// @param ia pointer to client's IA_NA option (client's request) + /// + /// @return IA_NA option (server's response) + OptionPtr assignIA_NA(const isc::dhcp::Pkt6Ptr& query, + AllocEngine::ClientContext6& ctx, + Option6IAPtr ia); + + /// @brief Processes IA_PD option (and assigns prefixes if necessary). + /// + /// Generates response to IA_PD. This typically includes selecting (and + /// allocating in the case of REQUEST) a prefix lease and creating an + /// IAPREFIX option. In case of an allocation failure, it may contain a + /// status code option with non-zero status denoting the cause of the + /// allocation failure. + /// + /// @param query client's message (typically SOLICIT or REQUEST) + /// @param ctx client context (contains subnet, duid and other parameters) + /// @param ia pointer to client's IA_PD option (client's request) + /// @return IA_PD option (server's response) + OptionPtr assignIA_PD(const Pkt6Ptr& query, + AllocEngine::ClientContext6& ctx, + boost::shared_ptr<Option6IA> ia); + + /// @brief Extends lifetime of the specific IA_NA option. + /// + /// Generates response to IA_NA in Renew or Rebind. This typically includes + /// finding a lease that corresponds to the received address. If no such + /// lease is found, an IA_NA response is generated with an appropriate + /// status code. + /// + /// @param query client's message (Renew or Rebind) + /// to the client (if the client sent this option to the server). + /// @param ctx client context (contains subnet, duid and other parameters) + /// @param ia IA_NA option which carries address for which lease lifetime + /// will be extended. + /// @return IA_NA option (server's response) + OptionPtr extendIA_NA(const Pkt6Ptr& query, + AllocEngine::ClientContext6& ctx, + Option6IAPtr ia); + + /// @brief Extends lifetime of the prefix. + /// + /// This function is called by the logic which processes Renew and Rebind + /// messages to extend the lifetime of the existing prefix. + /// + /// The behavior of this function is different than @c extendIA_NA in that + /// when there is no subnet found for the rebinding case, the Rebind message + /// is discarded by the server. That behavior is based on the following + /// statement from the RFC 8415, section 18.3.5: + /// + /// "If the server chooses to not include any IAs containing IA Address or + /// IA Prefix options with lifetimes of 0 and the server does not include + /// any other IAs with leases and/or status codes, the server does not send + /// a Reply message. In this situation, the server discards the Rebind + /// message". + /// + /// @todo We should consider unification of the server behavior for address + /// assignment and prefix delegation with respect to Rebind message + /// processing. The RFC 8415, section 18.3.5 doesn't really differentiate + /// between IA_NA and IA_PD in how they should be processed by the server. + /// The intention of the spec is as follows: + /// + /// - If the server finds a lease but addresses and/or prefixes are not + /// appropriate anymore, it sends them with zero lifetimes. + /// - If the server doesn't find a lease the server checks if the addresses + /// and/or prefixes the client sends are appropriate and sends them back + /// with zero lifetimes if they aren't. + /// - The server may choose to not respond at all, if it cannot determine + /// whether the addresses and/or prefixes are appropriate and it doesn't + /// allocate any other addresses and/or prefixes. + /// - If the server cannot find the leases included in the Rebind, the + /// server may either allocate the leases or simply return NoBinding. + /// + /// The @c extendIA_PD function drops the Rebind message if it cannot find + /// the client entry (as a result of not finding a subnet for the client), + /// the @c extendIA_NA function sends NoBinding status code in that case. + /// Perhaps we should introduce an "Authoritative" configuration flag which, + /// if enabled, would cause the server to always respond, either indicating + /// that the address/prefix is inappropriate (with zero lifetimes) or that + /// there is no binding (NoBinding status code) for both addresses and + /// prefixes. When the "Authoritative" flag is disabled the server would + /// drop the Rebind for which there is neither subnet selected nor client + /// entry found (as it could be handled by another DHCP server). If nothing + /// else we could consider unifying the behavior of @c extendIA_NA and + /// @c extendIA_PD with respect to Rebind processing. + /// + /// @param query client's message + /// @param ctx client context (contains subnet, duid and other parameters) + /// @param ia IA_PD option that is being renewed + /// @return IA_PD option (server's response) + /// @throw DHCPv6DiscardMessageError when the message being processed should + /// be discarded by the server, i.e. there is no binding for the client doing + /// Rebind. + OptionPtr extendIA_PD(const Pkt6Ptr& query, + AllocEngine::ClientContext6& ctx, + Option6IAPtr ia); + + /// @brief Releases specific IA_NA option + /// + /// Generates response to IA_NA in Release message. This covers finding and + /// removal of a lease that corresponds to the received address. If no such + /// lease is found, an IA_NA response is generated with an appropriate + /// status code. + /// + /// The server sends top-level Status Code option. This method may update the + /// passed value of that option, i.e. general_status. It is set to SUCCESS when + /// message processing begins, but may be updated to some error code if the + /// release process fails. + /// + /// @param duid client's duid + /// @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 released + /// @param old_lease a pointer to the lease being released + /// @return IA_NA option (server's response) + OptionPtr releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query, + int& general_status, + boost::shared_ptr<Option6IA> ia, + Lease6Ptr& old_lease); + + /// @brief Releases specific IA_PD option + /// + /// Generates response to IA_PD in Release message. This covers finding and + /// removal of a lease that corresponds to the received prefix(es). If no such + /// lease is found, an IA_PD response is generated with an appropriate + /// status code. + /// + /// @param duid client's duid + /// @param query client's message + /// @param general_status a global status (it may be updated in case of errors) + /// @param ia IA_PD option that is being released + /// @param old_lease a pointer to the lease being released + /// @return IA_PD option (server's response) + OptionPtr releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query, + int& general_status, + boost::shared_ptr<Option6IA> ia, + Lease6Ptr& old_lease); + + /// @brief Copies required options from client message to server answer. + /// + /// Copies options that must appear in any server response (ADVERTISE, REPLY) + /// to client's messages (SOLICIT, REQUEST, RENEW, REBIND, DECLINE, RELEASE). + /// One notable example is client-id. Other options may be copied as required. + /// Relay information details are also copied here. + /// + /// @param question client's message (options will be copied from here) + /// @param answer server's message (options will be copied here) + void copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer); + + /// @brief Build the configured option list + /// + /// @note The configured option list is an *ordered* list of + /// @c CfgOption objects used to append options to the response. + /// + /// @param question client's message + /// @param ctx client context (for the subnet) + /// @param co_list configured option list to build + void buildCfgOptionList(const Pkt6Ptr& question, + AllocEngine::ClientContext6& ctx, + CfgOptionList& co_list); + + /// @brief Appends default options to server's answer. + /// + /// Adds required options to server's answer. In particular, server-id + /// is added. Possibly other mandatory options will be added, depending + /// on type (or content) of client message. + /// + /// @param question client's message + /// @param answer server's message (options will be added here) + /// @param co_list configured option list (currently unused) + void appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, + const CfgOptionList& co_list); + + /// @brief Appends requested options to server's answer. + /// + /// Appends options requested by client to the server's answer. + /// + /// @param question client's message + /// @param answer server's message (options will be added here) + /// + /// @param co_list configured option list + void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, + const CfgOptionList& co_list); + + /// @brief Appends requested vendor options to server's answer. + /// + /// This is mostly useful for Cable Labs options for now, but the method + /// is easily extensible to other vendors. + /// + /// @param question client's message + /// @param answer server's message (vendor options will be added here) + /// @param ctx client context (contains subnet, duid and other parameters) + /// @param co_list configured option list + void appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx, + const CfgOptionList& co_list); + + /// @brief Assigns leases. + /// + /// It supports non-temporary addresses (IA_NA) and prefixes (IA_PD). It + /// does NOT support temporary addresses (IA_TA). + /// + /// @param question client's message (with requested IA options) + /// @param answer server's message (IA options will be added here). + /// This message should contain Client FQDN option being sent by the server + /// to the client (if the client sent this option to the server). + /// @param ctx client context (contains subnet, duid and other parameters) + void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx); + + /// @brief Processes Client FQDN Option. + /// + /// This function retrieves DHCPv6 Client FQDN %Option (if any) from the + /// packet sent by a client and takes necessary actions upon this option. + /// Received option comprises flags field which controls what DNS updates + /// server should do. Server may override client's preference based on + /// the current configuration. Server indicates that it has overridden + /// the preference by storing DHCPv6 Client FQDN option with the + /// appropriate flags in the response to a client. This option is also + /// used to communicate the client's domain-name which should be sent + /// to the DNS in the update. Again, server may act upon the received + /// domain-name, i.e. if the provided domain-name is partial it should + /// generate the fully qualified domain-name. + /// + /// This function takes into account the host reservation if one is matched + /// to this client when forming the FQDN to be used with DNS as well as the + /// lease name to be stored with the lease. In the following the term + /// "reserved hostname" means a host reservation which includes a + /// non-blank hostname. + /// + /// - If there is no Client FQDN and no reserved hostname then there + /// will no be DNS updates and the lease hostname will be empty. + /// + /// - If there is no Client FQDN but there is reserved hostname then + /// there will be no DNS updates and the lease hostname will be equal + /// to reserved hostname. + /// + /// - If there is a Client FQDN and a reserved hostname, then both the + /// FQDN and lease hostname will be equal to reserved hostname with + /// the qualifying suffix appended. + /// + /// - If there is a Client FQDN but no reserved hostname then both the + /// FQDN and lease hostname will be equal to the name provided in the + /// client FQDN adjusted according the DhcpDdns configuration + /// parameters (e.g.replace-client-name, qualifying suffix...). + /// + /// All the logic required to form appropriate answer to the client is + /// held in this function. + /// + /// @param question Client's message. + /// @param answer Server's response to a client. If server generated + /// Client FQDN option for the client, this option is stored in this + /// object. + /// @param ctx client context (includes subnet, client-id, hw-addr etc.) + void processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx); + + /// @brief Creates a number of @c isc::dhcp_ddns::NameChangeRequest objects + /// based on the DHCPv6 Client FQDN %Option. + /// + /// The @c isc::dhcp_ddns::NameChangeRequest class encapsulates the request + /// from the DHCPv6 server to the DHCP-DDNS module to perform DNS Update. + /// The FQDN option carries response to the client about DNS updates that + /// server intends to perform for the DNS client. Based on this, the + /// function will create zero or more @c isc::dhcp_ddns::NameChangeRequest + /// objects and store them in the internal queue. To catch lease renewals + /// that alter the FQDN, the function first looks at the context's changed + /// list of leases (if any) to determine if DNS entries need to be removed. + /// It then looks at the valid leases to determine if any DNS entries need + /// to be added. If DNS updates are disabled, this method returns immediately. + /// + /// @todo Add support for multiple IAADDR options in the IA_NA. + /// + /// @param answer A message begins sent to the Client. If it holds the + /// @param ctx client context (contains subnet, duid and other parameters) + /// Client FQDN option, this option is used to create NameChangeRequests. + void createNameChangeRequests(const Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx); + + /// @brief Attempts to extend the lifetime of IAs. + /// + /// This function is called when a client sends Renew or Rebind message. + /// It iterates through received IA options and attempts to extend + /// corresponding lease lifetimes. Internally, it calls + /// @c Dhcpv6Srv::extendIA_NA and @c Dhcpv6Srv::extendIA_PD to extend + /// the lifetime of IA_NA and IA_PD leases accordingly. + /// + /// @param query client's Renew or Rebind message + /// @param reply server's response + /// @param ctx client context (contains subnet, duid and other parameters) + void extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply, + AllocEngine::ClientContext6& ctx); + + /// @brief Sets the T1 and T2 timers in the outbound IA + /// + /// This method determines the values for both the T1 and T2 + /// timers for the given IA. It is influenced by the + /// lease's subnet's values for renew-timer, rebind-timer, + /// calculate-tee-times, t1-percent, and t2-percent as follows: + /// + /// T2: + /// + /// The value for T2 defaults to zero. If the rebind-timer value is + /// specified then use it. If not and calculate-tee-times is true, then + /// use the value given by: preferred lease time * t2-percent. + /// + /// T1: + /// + /// The candidate value for T1 defaults to zero. If the renew-timer value + /// is specified then use it. If not and calculate-tee-times is true, then + /// use the value given by: preferred lease time * t1-percent. + /// + /// The T1 candidate will be used provided it less than to T2, + /// otherwise it will be set T1 to zero. + /// + /// @param preferred_lft preferred lease time of the lease being assigned to the client + /// @param subnet the subnet to which the lease belongs + /// @param resp outbound IA option in which the timers are set. + void setTeeTimes(uint32_t preferred_lft, const Subnet6Ptr& subnet, Option6IAPtr& resp); + + /// @brief Attempts to release received addresses + /// + /// It iterates through received IA_NA options and attempts to release + /// received addresses. If no such leases are found, or the lease fails + /// proper checks (e.g. belongs to someone else), a proper status + /// code is added to reply message. Released addresses are not added + /// to REPLY packet, just its IA_NA containers. + /// @param release client's message asking to release + /// @param reply server's response + /// @param ctx client context (includes subnet, client-id, hw-addr etc.) + void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply, + AllocEngine::ClientContext6& ctx); + + /// @brief converts DUID to text + /// Converts content of DUID option to a text representation, e.g. + /// 01:ff:02:03:06:80:90:ab:cd:ef + /// + /// @param opt option that contains DUID + /// @return string representation + static std::string duidToString(const OptionPtr& opt); + + /// @brief dummy wrapper around IfaceMgr::receive6 + /// + /// This method is useful for testing purposes, where its replacement + /// simulates reception of a packet. For that purpose it is protected. + virtual Pkt6Ptr receivePacket(int timeout); + + /// @brief dummy wrapper around IfaceMgr::send() + /// + /// This method is useful for testing purposes, where its replacement + /// simulates transmission of a packet. For that purpose it is protected. + virtual void sendPacket(const Pkt6Ptr& pkt); + + /// @brief Assigns incoming packet to zero or more classes. + /// + /// @note This is done in two phases: first the content of the + /// vendor-class-identifier option is used as a class, by + /// calling @ref classifyByVendor(). Second, the classification match + /// expressions are evaluated. The resulting classes will be stored + /// in the packet (see @ref isc::dhcp::Pkt6::classes_ and + /// @ref isc::dhcp::Pkt6::inClass). + /// + /// @param pkt packet to be classified + void classifyPacket(const Pkt6Ptr& pkt); + + /// @brief Evaluate classes. + /// + /// @note Second part of the classification. + /// + /// Evaluate expressions of client classes: if it returns true the class + /// is added to the incoming packet. + /// + /// @param pkt packet to be classified. + /// @param depend_on_known if false classes depending on the KNOWN or + /// UNKNOWN classes are skipped, if true only these classes are evaluated. + void evaluateClasses(const Pkt6Ptr& pkt, bool depend_on_known); + + /// @brief Removed evaluated client classes. + /// + /// @todo: keep the list of dependent evaluated classes so + /// remove only them. + /// + /// @param pkt the packet. + static void removeDependentEvaluatedClasses(const Pkt6Ptr& pkt); + + /// @brief Assigns classes retrieved from host reservation database. + /// + /// @param pkt Pointer to the packet to which classes will be assigned. + /// @param ctx Reference to the client context. + void setReservedClientClasses(const Pkt6Ptr& pkt, + const AllocEngine::ClientContext6& ctx); + + /// @brief Assigns classes retrieved from host reservation database + /// if they haven't been yet set. + /// + /// This function sets reserved client classes in case they haven't + /// been set after fetching host reservations from the database. + /// This is the case when the client has non-global host reservation + /// and the selected subnet belongs to a shared network. + /// + /// @param pkt Pointer to the packet to which classes will be assigned. + /// @param ctx Reference to the client context. + void conditionallySetReservedClientClasses(const Pkt6Ptr& pkt, + const AllocEngine::ClientContext6& ctx); + + /// @brief Assigns incoming packet to zero or more classes (required pass). + /// + /// @note This required classification evaluates all classes which + /// were marked for required evaluation. Classes are collected so + /// evaluated in the reversed order than output option processing. + /// + /// @note The only-if-required flag is related because it avoids + /// double evaluation (which is not forbidden). + /// + /// @param pkt packet to be classified + /// @param ctx allocation context where to get information + void requiredClassify(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx); + + /// @brief Attempts to get a MAC/hardware address using configured sources + /// + /// Tries to extract MAC/hardware address information from the packet + /// using MAC sources configured in 'mac-sources' configuration parameter. + /// + /// @param pkt will try to exact MAC address from this packet + /// @return HWaddr pointer (or NULL if configured methods fail) + static HWAddrPtr getMAC(const Pkt6Ptr& pkt); + + /// @brief Processes Relay-supplied options, if present + /// + /// This method implements RFC6422. It checks if there are any RSOO options + /// inserted by the relay agents in the query message. If there are, they + /// are copied over to the response if they meet the following criteria: + /// - the option is marked as RSOO-enabled (see relay-supplied-options + /// configuration parameter) + /// - there is no such option provided by the server) + void processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp); + + /// @brief Initializes client context for specified packet + /// + /// This method: + /// - Performs the subnet selection and stores the result in context + /// - Extracts the duid from the packet and saves it to the context + /// - Extracts the hardware address from the packet and saves it to + /// the context + /// - Performs host reservation lookup and stores the result in the + /// context + /// + /// Even though the incoming packet type is known to this method, it + /// doesn't set the @c fake_allocation flag, because of a possibility + /// that the Rapid Commit option is in use. The @c fake_allocation + /// flag is set appropriately after it has been determined whether + /// the Rapid Commit option was included and that the server respects + /// it. + /// + /// @param pkt pointer to a packet for which context will be created. + /// @param [out] ctx reference to context object to be initialized. + /// @param [out] drop if it is true the packet will be dropped. + void initContext(const Pkt6Ptr& pkt, + AllocEngine::ClientContext6& ctx, + bool& drop); + + /// @brief this is a prefix added to the content of vendor-class option + /// + /// If incoming packet has a vendor class option, its content is + /// prepended with this prefix and then interpreted as a class. + /// For example, a packet that sends vendor class with value of "FOO" + /// will cause the packet to be assigned to class VENDOR_CLASS_FOO. + static const std::string VENDOR_CLASS_PREFIX; + + /// @brief Attempts to decline all leases in specified Decline message. + /// + /// This method iterates over all IA_NA options and calls @ref declineIA on + /// each of them. + /// + /// @param decline Decline message sent by a client + /// @param reply Server's response (IA_NA with status will be added here) + /// @param ctx context + /// @return true when expected to continue, false when hooks told us to drop + /// the packet + bool declineLeases(const Pkt6Ptr& decline, Pkt6Ptr& reply, + AllocEngine::ClientContext6& ctx); + + /// @brief Declines leases in a single IA_NA option + /// + /// This method iterates over all addresses in this IA_NA, verifies + /// whether they belong to the client and calls @ref declineLease. If there's + /// an error, general_status (a status put in the top level scope), will be + /// updated. + /// + /// @param decline client's Decline message + /// @param duid client's duid (used to verify if the client owns the lease) + /// @param general_status [out] status in top-level message (may be updated) + /// @param ia specific IA_NA option to process. + /// @param new_leases a collection of leases being declined. + /// @return IA_NA option with response (to be included in Reply message) + OptionPtr + declineIA(const Pkt6Ptr& decline, const DuidPtr& duid, int& general_status, + boost::shared_ptr<Option6IA> ia, Lease6Collection& new_leases); + + /// @brief Declines specific IPv6 lease. + /// + /// This method performs the actual decline and all necessary operations: + /// - cleans up DNS, if necessary + /// - updates subnet[X].declined-addresses (per subnet stat) + /// - updates declined-addresses (global stat) + /// - disassociates client information from the lease + /// - moves the lease to DECLINED state + /// - sets lease expiration time to decline-probation-period + /// - adds status-code success + /// + /// @param decline used for generating removal Name Change Request. + /// @param lease lease to be declined + /// @param ia_rsp response IA_NA. + /// @return true when expected to continue, false when hooks told us to drop + /// the packet + bool declineLease(const Pkt6Ptr& decline, const Lease6Ptr lease, + boost::shared_ptr<Option6IA> ia_rsp); + + /// @brief A simple utility method that sets the status code + /// + /// Removes old status code and sets a new one. + /// @param container status code will be added here + /// @param status status code option + void setStatusCode(boost::shared_ptr<Option6IA>& container, + const OptionPtr& status); + + /// @brief Iterates over new leases, update stale DNS entries + /// + /// Checks the context's current subnet (most recently selected) against + /// an original selected subnet. If they are the same the function + /// simply returns. + /// + /// If they differ, we treat this as a dynamic subnet change made by the + /// allocation engine. It is possible that DDNS subnet parameters for + /// the new subnet are different and this needs to handled. We first + /// save the current DNS-related values from the context and then + /// re-run processClientFqdn(). This will rebuild the FQDN option + /// to send back to the client based on the new subnet as well as + /// update the context. If the new values are different from the + /// previous values, we iterate over the leases and update the + /// DNS values. + /// + /// @param question Client's message. + /// @param answer Server's response to a client. If server generated + /// @param ctx client context (contains subnet, duid and other parameters) + /// @param orig_subnet the originally selected subnet + /// + /// @note + /// Subnet may be modified by the allocation engine, if the initial subnet + /// belongs to a shared network. Note that this will only handle cases + /// where all IA_xx's in a client request result in a subnet change. It is + /// possible, currently, for the last IA_xx in request to end up using the + /// same subnet as originally selected, and we will miss a change incurred + /// by preceding IA_xx's. In general users should be strongly encouraged to + /// avoid situations where all of the following are true: + /// + /// 1. clients send more than one IA_xx in a query + /// 2. subnets in the shared-network are equally eligible (i.e don't have + /// class guards etc) + /// 3. subnets have differing options or DDNS parameters + // + void checkDynamicSubnetChange(const Pkt6Ptr& question, Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx, + const Subnet6Ptr orig_subnet); +public: + + /// Used for DHCPv4-over-DHCPv6 too. + + /// @brief Check if the last relay added a relay-source-port option. + /// + /// @param query DHCPv6 message to be checked. + /// @return the port to use to join the relay or 0 for the default. + static uint16_t checkRelaySourcePort(const Pkt6Ptr& query); + +private: + + /// @public + /// @brief Assign class using vendor-class-identifier option + /// + /// @note This is the first part of @ref classifyPacket + /// + /// @param pkt packet to be classified + /// @param classes a reference to added class names for logging + void classifyByVendor(const Pkt6Ptr& pkt, std::string& classes); + + /// @brief Update FQDN based on the reservations in the current subnet. + /// + /// When shared networks are in use the allocation engine may switch to + /// a different subnet than originally selected. If this new subnet has + /// hostname reservations there is a need to update the FQDN option + /// value. + /// + /// This method should be called after lease assignments to perform + /// such update when required. + /// + /// @param ctx Client context. + /// @param answer Message being sent to a client, which may hold an FQDN + /// option to be updated. + /// + /// @throw isc::Unexpected if specified message is NULL. This is treated + /// as a programmatic error. + void updateReservedFqdn(AllocEngine::ClientContext6& ctx, + const Pkt6Ptr& answer); + + /// @private + /// @brief Generate FQDN to be sent to a client if none exists. + /// + /// This function is meant to be called by the functions which process + /// client's messages. The function should be called after a function + /// which creates FQDN option for the client. This option must exist + /// in the answer message specified as an argument. It must also be + /// called after functions which assign leases for a client. The + /// IA options being a result of lease acquisition must be appended + /// to the message specified as a parameter. + /// + /// If the Client FQDN option being present in the message carries empty + /// hostname, this function will attempt to generate hostname from the + /// IPv6 address being acquired by the client. The IPv6 address is retrieved + /// from the IA_NA option carried in the specified message. If multiple + /// addresses are present in the particular IA_NA option or multiple IA_NA + /// options exist, the first address found is selected. + /// + /// The IPv6 address is converted to the hostname using the following + /// pattern: + /// @code + /// prefix-converted-ip-address.domain-name-suffix. + /// @endcode + /// where: + /// - prefix is a configurable prefix string appended to all auto-generated + /// hostnames. + /// - converted-ip-address is created by replacing all colons from the IPv6 + /// address with hyphens. + /// - domain-name-suffix is a suffix for a domain name that, together with + /// the other parts, constitute the fully qualified domain name. + /// + /// When hostname is successfully generated, it is either used to update + /// FQDN-related fields in a lease database or to update the Client FQDN + /// option being sent back to the client. The lease database update is + /// NOT performed if Advertise message is being processed. + /// + /// @param answer Message being sent to a client, which may hold IA_NA + /// and Client FQDN options to be used to generate name for a client. + /// @param ctx Client context. + /// + /// @throw isc::Unexpected if specified message is NULL. This is treated + /// as a programmatic error. + void generateFqdn(const Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx); + + /// @brief Updates statistics for received packets + /// @param query packet received + static void processStatsReceived(const Pkt6Ptr& query); + + /// @brief Checks if the specified option code has been requested using + /// the Option Request option. + /// + /// @param query Pointer to the client's query. + /// @parma code Option code. + /// + /// @return true if option has been requested in the ORO. + bool requestedInORO(const Pkt6Ptr& query, const uint16_t code) const; + +protected: + /// UDP port number on which server listens. + uint16_t server_port_; + + /// UDP port number to which server sends all responses. + uint16_t client_port_; + +public: + + /// @note used by DHCPv4-over-DHCPv6 so must be public and static + + /// @brief Updates statistics for transmitted packets + /// @param response packet transmitted + static void processStatsSent(const Pkt6Ptr& response); + + /// @brief Returns the index of the buffer6_send hook + /// @return the index of the buffer6_send hook + static int getHookIndexBuffer6Send(); + + /// @brief Executes buffer6_send callout and sends the response. + /// + /// @param callout_handle pointer to the callout handle. + /// @param rsp pointer to a response. + void processPacketBufferSend(hooks::CalloutHandlePtr& callout_handle, + Pkt6Ptr& rsp); + + /// @brief Return a list of all paths that contain passwords or secrets for + /// kea-dhcp6. + /// + /// @return the list of lists of sequential JSON map keys needed to reach + /// the passwords and secrets. + std::list<std::list<std::string>> jsonPathsToRedact() const final override; + +protected: + + /// Server DUID (to be sent in server-identifier option) + OptionPtr serverid_; + + /// Indicates if shutdown is in progress. Setting it to true will + /// initiate server shutdown procedure. + volatile bool shutdown_; + + /// @brief Executes pkt6_send callout. + /// + /// @param callout_handle pointer to the callout handle. + /// @param query Pointer to a query. + /// @param rsp Pointer to a response. + void processPacketPktSend(hooks::CalloutHandlePtr& callout_handle, + Pkt6Ptr& query, Pkt6Ptr& rsp); + + /// @brief Allocation Engine. + /// Pointer to the allocation engine that we are currently using + /// It must be a pointer, because we will support changing engines + /// during normal operation (e.g. to use different allocators) + boost::shared_ptr<AllocEngine> alloc_engine_; + + /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects, which + /// are waiting for sending to kea-dhcp-ddns module. + std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_; + + /// @brief Holds information about disabled DHCP service and/or + /// disabled subnet/network scopes. + NetworkStatePtr network_state_; + + /// @brief Controls access to the configuration backends. + CBControlDHCPv6Ptr cb_control_; +}; + +} // namespace dhcp +} // namespace isc + +#endif // DHCP6_SRV_H |