]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1147] Checkpoint: began v6 client
authorFrancis Dupont <fdupont@isc.org>
Tue, 5 May 2020 16:21:52 +0000 (18:21 +0200)
committerFrancis Dupont <fdupont@isc.org>
Tue, 26 May 2020 09:51:57 +0000 (11:51 +0200)
src/bin/dhcp6/Makefile.am
src/bin/dhcp6/client_handler.cc [new file with mode: 0644]
src/bin/dhcp6/client_handler.h [new file with mode: 0644]
src/bin/dhcp6/dhcp6_srv.cc

index 4574bbbb5ce122bbbe6f773c8c3f6ddc5819e2a0..668f3d0c5e3e4d0e7080ad24465663f524e25130 100644 (file)
@@ -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 (file)
index 0000000..9ca18e5
--- /dev/null
@@ -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 <config.h>
+
+#include <dhcp6/client_handler.h>
+#include <exceptions/exceptions.h>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+mutex ClientHandler::mutex_;
+
+ClientHandler::ClientContainer ClientHandler::clients_;
+
+ClientHandler::ClientHandler() : locked_() {
+}
+
+ClientHandler::~ClientHandler() {
+    if (locked_) {
+        lock_guard<mutex> 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<mutex> 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 (file)
index 0000000..2d1396a
--- /dev/null
@@ -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 <dhcp/pkt6.h>
+#include <boost/noncopyable.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <mutex>
+#include <thread>
+
+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<uint8_t>& 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<uint8_t>&, &Client::getClientId
+                >
+            >
+        >
+    > ClientContainer;
+
+    /// @brief The client container.
+    static ClientContainer clients_;
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif // CLIENT_HANDLER_H
index b2c744d17f02f5a4565814ef42ee527369dc4f35..6338288c939e7abc86e4c2e3f8ef27bc8129a3e6 100644 (file)
@@ -28,6 +28,7 @@
 #include <dhcp/option_vendor_class.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
+#include <dhcp6/client_handler.h>
 #include <dhcp6/dhcp6to4_ipc.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_srv.h>
@@ -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.