]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4141] implemented kqueue event handler
authorRazvan Becheriu <razvan@isc.org>
Wed, 5 Nov 2025 22:12:27 +0000 (00:12 +0200)
committerRazvan Becheriu <razvan@isc.org>
Fri, 21 Nov 2025 13:02:43 +0000 (13:02 +0000)
compiler-checks/have-kqueue.cc
src/lib/dhcp/testutils/pkt_filter6_test_stub.cc
src/lib/dhcp/testutils/pkt_filter_test_stub.cc
src/lib/util/epoll_event_handler.cc
src/lib/util/epoll_event_handler.h
src/lib/util/fd_event_handler_factory.cc
src/lib/util/kqueue_event_handler.cc [new file with mode: 0644]
src/lib/util/kqueue_event_handler.h [new file with mode: 0644]
src/lib/util/meson.build
src/lib/util/tests/fd_event_handler_unittests.h
src/lib/util/tests/kqueue_event_handler_unittests.cc [new file with mode: 0644]

index 05a0439b6c47bc7eb36785020575e08d567299ac..a5f6e2588718b61df1a190d2949f55039858bd56 100644 (file)
@@ -2,7 +2,7 @@
 #include <unistd.h>
 
 int main() {
-    int kq = kqueue();
-    close(kq);
+    int kqueuefd = kqueue();
+    close(kqueuefd);
     return 0;
 }
index 2dd0af5c70c37ef0197c830f8fdce34264b000c4..27384421360dd56491fb6fc12e271dc66472e07c 100644 (file)
@@ -20,13 +20,12 @@ SocketInfo
 PktFilter6TestStub::openSocket(const Iface&,
            const isc::asiolink::IOAddress& addr,
            const uint16_t port, const bool) {
-    int pipefd[2];
+    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
 
-    int ret = pipe(pipefd);
-    if (ret < 0) {
+    if (fd < 0) {
         const char* errmsg = strerror(errno);
         isc_throw(Unexpected,
-                  "PktFilter6TestStub: cannot open pipe: " << errmsg);
+                  "PktFilter6TestStub: cannot open socket: " << errmsg);
     }
 
     try {
@@ -35,14 +34,11 @@ PktFilter6TestStub::openSocket(const Iface&,
         }
     } catch (...) {
         // Don't leak fd on simulated errors.
-        close(pipefd[0]);
-        close(pipefd[1]);
+        close(fd);
         throw;
     }
 
-    close(pipefd[1]);
-
-    return (SocketInfo(addr, port, pipefd[0]));
+    return (SocketInfo(addr, port, fd));
 }
 
 Pkt6Ptr
index 5accf92bd6a2b51a497cce97e1d8ca4af9e19224..97f7f04d3aea79e8abc7cf37a072eb7263952e75 100644 (file)
@@ -37,13 +37,12 @@ SocketInfo
 PktFilterTestStub::openSocket(Iface&,
            const isc::asiolink::IOAddress& addr,
            const uint16_t port, const bool, const bool) {
-    int pipefd[2];
+    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
 
-    int ret = pipe(pipefd);
-    if (ret < 0) {
+    if (fd < 0) {
         const char* errmsg = strerror(errno);
         isc_throw(Unexpected,
-                  "PktFilterTestStub: cannot open pipe: " << errmsg);
+                  "PktFilterTestStub: cannot open socket: " << errmsg);
     }
 
     try {
@@ -52,14 +51,11 @@ PktFilterTestStub::openSocket(Iface&,
         }
     } catch (...) {
         // Don't leak fd on simulated errors.
-        close(pipefd[0]);
-        close(pipefd[1]);
+        close(fd);
         throw;
     }
 
-    close(pipefd[1]);
-
-    return (SocketInfo(addr, port, pipefd[0]));
+    return (SocketInfo(addr, port, fd));
 }
 
 Pkt4Ptr
index ce128062b741a8486fc9aec30fdc7183f4f29c94..b16715a344abcba31d39ea7d5034e721ac0f694b 100644 (file)
@@ -11,6 +11,8 @@
 
 #include <cstring>
 
+#include <unistd.h>
+
 using namespace std;
 
 namespace isc {
@@ -21,7 +23,7 @@ EPollEventHandler::EPollEventHandler() : FDEventHandler(TYPE_EPOLL), epollfd_(-1
     if (epollfd_ == -1) {
         isc_throw(Unexpected, "error opening epoll: " << strerror(errno));
     }
-    if (pipe(pipefd)) {
+    if (pipe(pipefd_)) {
         close(epollfd_);
         isc_throw(Unexpected, "error opening internal epoll pipe: " << strerror(errno));
     }
@@ -30,8 +32,8 @@ EPollEventHandler::EPollEventHandler() : FDEventHandler(TYPE_EPOLL), epollfd_(-1
 
 EPollEventHandler::~EPollEventHandler() {
     close(epollfd_);
-    close(pipefd[1]);
-    close(pipefd[0]);
+    close(pipefd_[1]);
+    close(pipefd_[0]);
 }
 
 void EPollEventHandler::add(int fd, bool read /* = true */, bool write /* = false */) {
@@ -78,7 +80,7 @@ int EPollEventHandler::waitEvent(uint32_t timeout_sec, uint32_t timeout_usec /*
     }
     struct epoll_event dummy;
     memset(&dummy, 0, sizeof(dummy));
-    dummy.data.fd = pipefd[0];
+    dummy.data.fd = pipefd_[0];
     dummy.events |= EPOLLIN;
     epoll_ctl(epollfd_, EPOLL_CTL_ADD, dummy.data.fd, &dummy);
     used_data_.push_back(dummy);
index fdb96bc83000be6f9e37c9107b80762541ca9eb4..69c28faf9045474493e57ffe891eb96609207152 100644 (file)
@@ -90,7 +90,7 @@ private:
 
     /// @brief The pipe used to permit calling @ref waitEvent with no
     /// registered file descriptors.
-    int pipefd[2];
+    int pipefd_[2];
 };
 
 }  // namespace isc::util;
index 64bd05c2a2cc542904c3aa9cfe28e7e8f1021f5d..e381612b7a3e14238ac151763be0c1f696e54dac 100644 (file)
@@ -33,12 +33,15 @@ FDEventHandlerPtr FDEventHandlerFactory::factoryFDEventHandler() {
         if (string(env_type) == string("select")) {
             type = FDEventHandler::TYPE_SELECT;
         }
-        if (string(env_type) == string("poll")) {
-            type = FDEventHandler::TYPE_POLL;
-        }
         if (string(env_type) == string("epoll")) {
             type = FDEventHandler::TYPE_EPOLL;
         }
+        if (string(env_type) == string("kqueue")) {
+            type = FDEventHandler::TYPE_KQUEUE;
+        }
+        if (string(env_type) == string("poll")) {
+            type = FDEventHandler::TYPE_POLL;
+        }
     }
     switch(type) {
     case FDEventHandler::TYPE_SELECT:
diff --git a/src/lib/util/kqueue_event_handler.cc b/src/lib/util/kqueue_event_handler.cc
new file mode 100644 (file)
index 0000000..3a97aee
--- /dev/null
@@ -0,0 +1,155 @@
+// Copyright (C) 2025 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 <exceptions/exceptions.h>
+#include <util/kqueue_event_handler.h>
+
+#include <cstring>
+
+#include <unistd.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+
+KQueueEventHandler::KQueueEventHandler() : FDEventHandler(TYPE_KQUEUE), kqueuefd_(-1) {
+    kqueuefd_ = kqueue();
+    if (kqueuefd_ == -1) {
+        isc_throw(Unexpected, "error opening kqueue: " << strerror(errno));
+    }
+    if (pipe(pipefd_)) {
+        close(kqueuefd_);
+        isc_throw(Unexpected, "error opening internal kqueue pipe: " << strerror(errno));
+    }
+    clear();
+}
+
+KQueueEventHandler::~KQueueEventHandler() {
+    close(kqueuefd_);
+    close(pipefd_[1]);
+    close(pipefd_[0]);
+}
+
+void KQueueEventHandler::add(int fd, bool read /* = true */, bool write /* = false */) {
+    if (fd < 0) {
+        isc_throw(BadValue, "invalid negative value for fd");
+    }
+    if (read) {
+        // Add this socket to read events
+        struct kevent data;
+        memset(&data, 0, sizeof(data));
+        EV_SET(&data, fd, EVFILT_READ, EV_ADD, 0, 0, 0);
+        data_.push_back(data);
+    }
+    if (write) {
+        // Add this socket to write events
+        struct kevent data;
+        memset(&data, 0, sizeof(data));
+        EV_SET(&data, fd, EVFILT_WRITE, EV_ADD, 0, 0, 0);
+        data_.push_back(data);
+    }
+}
+
+int KQueueEventHandler::waitEvent(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */,
+                                  bool use_timeout /* = true */) {
+    // Sanity check for microsecond timeout.
+    if (timeout_usec >= 1000000) {
+        isc_throw(BadValue, "fractional timeout must be shorter than"
+                  " one million microseconds");
+    }
+    struct timespec select_timeout;
+    struct timespec* select_timeout_p = 0;
+    if (use_timeout) {
+        select_timeout.tv_sec = timeout_sec;
+        select_timeout.tv_nsec = timeout_usec * 1000;
+        select_timeout_p = &select_timeout;
+    }
+    map_.clear();
+    used_data_.clear();
+    errors_.clear();
+    errno = 0;
+    int saved_errno = errno;
+    for (auto data : data_) {
+        if (kevent(kqueuefd_, &data, 1, 0, 0, 0) == -1) {
+            saved_errno = errno;
+            errors_.insert(data.ident);
+        } else {
+            used_data_.push_back(data);
+        }
+    }
+    struct kevent dummy;
+    memset(&dummy, 0, sizeof(dummy));
+    EV_SET(&dummy, pipefd_[0], EVFILT_READ, EV_ADD, 0, 0, 0);
+    kevent(kqueuefd_, &dummy, 1, 0, 0, 0);
+    used_data_.push_back(dummy);
+    int result = 0;
+    if (errors_.empty()) {
+        result = kevent(kqueuefd_, 0, 0, used_data_.data(), used_data_.size(), select_timeout_p);
+        for (int i = 0; i < result; ++i) {
+            map_.emplace(used_data_[i].ident, &used_data_[i]);
+        }
+    }
+    for (auto data : data_) {
+        EV_SET(&data, data.ident, data.filter, EV_DELETE, 0, 0, 0);
+        kevent(kqueuefd_, &data, 1, 0, 0, 0);
+    }
+    EV_SET(&dummy, dummy.ident, dummy.filter, EV_DELETE, 0, 0, 0);
+    kevent(kqueuefd_, &dummy, 1, 0, 0, 0);
+    if (result != -1) {
+        errno = saved_errno;
+    }
+    if (errors_.size()) {
+        return (errors_.size());
+    }
+    return (result);
+}
+
+bool KQueueEventHandler::readReady(int fd) {
+    auto range = map_.equal_range(fd);
+    for (auto it = range.first; it != range.second; ++it) {
+        if (it->second->filter == EVFILT_READ) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+bool KQueueEventHandler::writeReady(int fd) {
+    auto range = map_.equal_range(fd);
+    for (auto it = range.first; it != range.second; ++it) {
+        if (it->second->filter == EVFILT_WRITE) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+bool KQueueEventHandler::hasError(int fd) {
+    if (errors_.count(fd)) {
+        return (true);
+    }
+    auto range = map_.equal_range(fd);
+    for (auto it = range.first; it != range.second; ++it) {
+        if ((it->second->flags & EV_EOF) || (it->second->filter == EV_ERROR)) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+void KQueueEventHandler::clear() {
+    data_.clear();
+    used_data_.clear();
+    errors_.clear();
+    map_.clear();
+    errno = 0;
+}
+
+} // end of namespace isc::util
+} // end of namespace isc
diff --git a/src/lib/util/kqueue_event_handler.h b/src/lib/util/kqueue_event_handler.h
new file mode 100644 (file)
index 0000000..1123c0b
--- /dev/null
@@ -0,0 +1,101 @@
+// Copyright (C) 2025 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 KQUEUE_EVENT_HANDLER_H
+#define KQUEUE_EVENT_HANDLER_H
+
+#include <util/fd_event_handler.h>
+
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+namespace isc {
+namespace util {
+
+/// @brief File descriptor event handler class handles events for registered
+/// file descriptors. This class uses the OS select syscall for event handling.
+class KQueueEventHandler : public FDEventHandler {
+public:
+    /// @brief Constructor.
+    KQueueEventHandler();
+
+    /// @brief Destructor.
+    virtual ~KQueueEventHandler();
+
+    /// @brief Add file descriptor to watch for events.
+    ///
+    /// @param fd The file descriptor.
+    /// @param read The flag indicating if the file descriptor should be
+    /// registered for read ready events.
+    /// @param write The flag indicating if the file descriptor should be
+    /// registered for write ready events.
+    void add(int fd, bool read = true, bool write = false);
+
+    /// @brief Wait for events on registered file descriptors.
+    ///
+    /// @param timeout_sec The wait timeout in seconds.
+    /// @param timeout_usec The wait timeout in micro seconds.
+    /// @param use_timeout Flag which indicates if function should wait
+    /// with no timeout (wait forever).
+    /// @return -1 on error, 0 if no data is available (timeout expired),
+    /// 1 if data is ready.
+    int waitEvent(uint32_t timeout_sec, uint32_t timeout_usec = 0,
+                  bool use_timeout = true);
+
+    /// @brief Check if file descriptor is ready for read operation.
+    ///
+    /// @param fd The file descriptor.
+    ///
+    /// @return True if file descriptor is ready for reading.
+    bool readReady(int fd);
+
+    /// @brief Check if file descriptor is ready for write operation.
+    ///
+    /// @param fd The file descriptor.
+    ///
+    /// @return True if file descriptor is ready for writing.
+    bool writeReady(int fd);
+
+    /// @brief Check if file descriptor has error.
+    ///
+    /// @param fd The file descriptor.
+    ///
+    /// @return True if file descriptor has error.
+    virtual bool hasError(int fd);
+
+    /// @brief Clear registered file descriptors.
+    void clear();
+
+private:
+    /// @brief The kqueue file descriptor.
+    int kqueuefd_;
+
+    /// @brief The kqueue file descriptors data.
+    std::vector<struct kevent> data_;
+
+    /// @brief The kqueue file descriptors data.
+    std::vector<struct kevent> used_data_;
+
+    /// @brief The set of file descriptors with errors.
+    std::unordered_set<int> errors_;
+
+    /// @brief The map with file descriptor to data reference.
+    std::unordered_multimap<int, struct kevent*> map_;
+
+    /// @brief The pipe used to permit calling @ref waitEvent with no
+    /// registered file descriptors.
+    int pipefd_[2];
+};
+
+}  // namespace isc::util;
+}  // namespace isc
+
+#endif  // KQUEUE_EVENT_HANDLER_H
index 2aaf341c17d91e459f75cab2919352c2ea6a2fc7..1a83773c387e5a5c3ca7609f02f96c7b5b60623e 100644 (file)
@@ -1,15 +1,11 @@
 epoll_event_handler_cc = []
-epoll_event_handler_h = []
 if HAVE_EPOLL
     epoll_event_handler_cc = 'epoll_event_handler.cc'
-    epoll_event_handler_h = 'epoll_event_handler.h'
 endif
 
 kqueue_event_handler_cc = []
-kqueue_event_handler_h = []
 if HAVE_KQUEUE
     kqueue_event_handler_cc = 'kqueue_event_handler.cc'
-    kqueue_event_handler_h = 'kqueue_event_handler.h'
 endif
 
 kea_util_lib = shared_library(
@@ -63,7 +59,7 @@ kea_util_headers = [
     'doubles.h',
     'encode/encode.h',
     'encode/utf8.h',
-    epoll_event_handler_h,
+    'epoll_event_handler.h',
     'fd_event_handler.h',
     'fd_event_handler_factory.h',
     'filesystem.h',
@@ -72,7 +68,7 @@ kea_util_headers = [
     'io/sockaddr_util.h',
     'hash.h',
     'io.h',
-    kqueue_event_handler_h,
+    'kqueue_event_handler.h',
     'labeled_value.h',
     'memory_segment.h',
     'memory_segment_local.h',
index fc9bc5d6f5c2e103473553063b20ed4aee7d66ef..2916a0ebe6149e6bd25fb1682a543641357fac0a 100644 (file)
@@ -152,7 +152,7 @@ TEST_F(FDEventHandlerTest, badFD) {
     errno = 0;
     int fd;
 
-    if (handler_->type() == FDEventHandler::TYPE_EPOLL) {
+    if (handler_->type() != FDEventHandler::TYPE_SELECT) {
         // epoll does not allow add of /dev/zero to registered events.
         fd = pipefd_[0];
         EXPECT_EQ(1, write(pipefd_[1], &MARKER, sizeof(MARKER)));
@@ -198,7 +198,7 @@ TEST_F(FDEventHandlerTest, badFD) {
         EXPECT_EQ(EBADF, errno);
     }
 
-    if (handler_->type() == FDEventHandler::TYPE_EPOLL) {
+    if (handler_->type() != FDEventHandler::TYPE_SELECT) {
         close(pipefd_[1]);
         pipe(pipefd_);
     }
diff --git a/src/lib/util/tests/kqueue_event_handler_unittests.cc b/src/lib/util/tests/kqueue_event_handler_unittests.cc
new file mode 100644 (file)
index 0000000..aec6145
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright (C) 2025 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>
+
+#define FDEventHandlerType KQueueEventHandler
+#define FDEventHandlerTest KQueueEventHandlerTest
+
+#include <fd_event_handler_unittests.h>