diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am index 4574bbbb5c..668f3d0c5e 100644 --- a/src/bin/dhcp6/Makefile.am +++ b/src/bin/dhcp6/Makefile.am @@ -34,6 +34,7 @@ libdhcp6_la_SOURCES += dhcp6_srv.cc dhcp6_srv.h libdhcp6_la_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h libdhcp6_la_SOURCES += json_config_parser.cc json_config_parser.h libdhcp6_la_SOURCES += dhcp6to4_ipc.cc dhcp6to4_ipc.h +libdhcp6_la_SOURCES += client_handler.cc client_handler.h libdhcp6_la_SOURCES += dhcp6_lexer.ll location.hh position.hh stack.hh libdhcp6_la_SOURCES += dhcp6_parser.cc dhcp6_parser.h libdhcp6_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h diff --git a/src/bin/dhcp6/client_handler.cc b/src/bin/dhcp6/client_handler.cc new file mode 100644 index 0000000000..9ca18e5306 --- /dev/null +++ b/src/bin/dhcp6/client_handler.cc @@ -0,0 +1,109 @@ +// Copyright (C) 2020 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/. + +#include + +#include +#include + +using namespace std; + +namespace isc { +namespace dhcp { + +mutex ClientHandler::mutex_; + +ClientHandler::ClientContainer ClientHandler::clients_; + +ClientHandler::ClientHandler() : locked_() { +} + +ClientHandler::~ClientHandler() { + if (locked_) { + lock_guard lock_(mutex_); + unLock(); + } + locked_.reset(); +} + +ClientHandler::Client::Client(Pkt6Ptr query) + : query_(query), thread_(this_thread::get_id()) { + if (!query) { + isc_throw(InvalidParameter, "null query in ClientHandler"); + } + if (!query->getClientId()) { + isc_throw(InvalidParameter, "query has no client Id in ClientHandler"); + } +} + +Pkt6Ptr +ClientHandler::lookup(const DuidPtr& duid) { + if (!duid) { + isc_throw(InvalidParameter, "duid is null in ClientHandler::lookup"); + } + auto it = clients_.find(duid->getDuid()); + if (it == clients_.end()) { + return (Pkt6Ptr()); + } + return (it->query_); +} + +void +ClientHandler::lock() { + if (!locked_) { + isc_throw(Unexpected, "nothing to lock in ClientHandler::lock"); + } + Client client(locked_); + clients_.insert(Client(locked_)); +} + +void +ClientHandler::unLock() { + if (!locked_) { + isc_throw(Unexpected, "nothing to unlock in ClientHandler::unLock"); + } + const DuidPtr& duid = locked_->getClientId(); + if (!duid) { + isc_throw(Unexpected, "no duid unlock in ClientHandler::unLock"); + } + auto it = clients_.find(duid->getDuid()); + if (it == clients_.end()) { + // Should not happen + return; + } + clients_.erase(it); +} + +bool +ClientHandler::tryLock(Pkt6Ptr query) { + if (!query) { + isc_throw(InvalidParameter, "null query in ClientHandler::tryLock"); + } + if (locked_) { + isc_throw(Unexpected, "already handling in ClientHandler::tryLock"); + } + const DuidPtr& duid = query->getClientId(); + if (!duid) { + // Can't do something useful: cross fingers. + return (false); + } + if (duid->getDuid().empty()) { + // A lot of code assumes this will never happen... + isc_throw(Unexpected, "empty DUID in ClientHandler::tryLock"); + } + lock_guard lock_(mutex_); + const Pkt6Ptr& duplicate = lookup(duid); + if (duplicate) { + // Should log. + return (true); + } + locked_ = query; + lock(); + return (false); +} + +} // namespace dhcp +} // namespace isc diff --git a/src/bin/dhcp6/client_handler.h b/src/bin/dhcp6/client_handler.h new file mode 100644 index 0000000000..2d1396a343 --- /dev/null +++ b/src/bin/dhcp6/client_handler.h @@ -0,0 +1,117 @@ +// Copyright (C) 2020 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 CLIENT_HANDLER_H +#define CLIENT_HANDLER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Client race avoidance RAII handler. +class ClientHandler : public boost::noncopyable { +public: + + /// @brief Constructor. + ClientHandler(); + + /// @brief Destructor. + /// + /// Releases the client if it was acquired. + virtual ~ClientHandler(); + + /// @brief Tries to acquires a client. + /// + /// @param query The query from the client. + /// @return true if the client was acquired, false if there is already + /// a query from the same client. + bool tryLock(Pkt6Ptr query); + +private: + + /// @brief Structure representing a client. + struct Client { + + /// @brief Constructor. + /// + /// @param query The query. + /// @throw if the query is null or has empty client ID. + Client(Pkt6Ptr query); + + /// @brief The query being processed. + Pkt6Ptr query_; + + /// @brief The ID of the thread processing the query. + std::thread::id thread_; + + /// @brief Key extractor. + /// + /// Returns the content of the Duid aka client ID. + const std::vector& getClientId() const { + return (query_->getClientId()->getDuid()); + } + }; + + /// @brief Query locked by this handler. + Pkt6Ptr locked_; + + /// @brief Mutex to protect the client container. + static std::mutex mutex_; + + /// @brief Lookup a client. + /// + /// The mutex must be held by the caller. + /// + /// @param duid The duid of the query from the client. + /// @return The query holding the client or null. + static Pkt6Ptr lookup(const DuidPtr& duid); + + /// @brief Acquire a client. + /// + /// The mutex must be held by the caller. + void lock(); + + /// @brief Release a client. + /// + /// The mutex must be held by the caller. + void unLock(); + + /// @brief The type of the client container. + typedef boost::multi_index_container< + + // This container stores clients. + Client, + + // Start specification of indexes here. + boost::multi_index::indexed_by< + + // First index is used to search by Duid. + boost::multi_index::hashed_unique< + + // Duid content is extracted by calling Client::getClientId() + boost::multi_index::const_mem_fun< + Client, const std::vector&, &Client::getClientId + > + > + > + > ClientContainer; + + /// @brief The client container. + static ClientContainer clients_; +}; + +} // namespace isc +} // namespace dhcp + +#endif // CLIENT_HANDLER_H diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index b2c744d17f..6338288c93 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -815,9 +816,28 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) { return; } + // Create a client race avoidance RAII handler. + ClientHandler client_handler; + bool drop = false; + + // Check for lease modifier queries from the same client being processed. + if (MultiThreadingMgr::instance().getMode() && + ((query->getType() == DHCPV6_SOLICIT) || + (query->getType() == DHCPV6_REQUEST) || + (query->getType() == DHCPV6_RENEW) || + (query->getType() == DHCPV6_REBIND) || + (query->getType() == DHCPV6_RELEASE) || + (query->getType() == DHCPV6_DECLINE))) { + drop = client_handler.tryLock(query); + } + + // Stop here if ClientHandler tryLock decided the packet is a duplicate. + if (drop) { + return; + } + // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; - bool drop = false; initContext(query, ctx, drop); // Stop here if initContext decided to drop the packet.