PktFilter6TestStub::openSocket(const Iface&,
const isc::asiolink::IOAddress& addr,
const uint16_t port, const bool) {
- if (open_socket_callback_) {
- open_socket_callback_(port);
+ int pipefd[2];
+
+ int ret = pipe(pipefd);
+ if (ret < 0) {
+ const char* errmsg = strerror(errno);
+ isc_throw(Unexpected,
+ "PktFilter6TestStub: cannot open pipe: " << errmsg);
+ }
+
+ try {
+ if (open_socket_callback_) {
+ open_socket_callback_(port);
+ }
+ } catch (...) {
+ // Don't leak fd on simulated errors.
+ close(pipefd[0]);
+ close(pipefd[1]);
+ throw;
}
- return (SocketInfo(addr, port, 0));
+ close(pipefd[1]);
+
+ return (SocketInfo(addr, port, pipefd[0]));
}
Pkt6Ptr
PktFilterTestStub::openSocket(Iface&,
const isc::asiolink::IOAddress& addr,
const uint16_t port, const bool, const bool) {
- int fd = open("/dev/null", O_RDONLY);
- if (fd < 0) {
+ int pipefd[2];
+
+ int ret = pipe(pipefd);
+ if (ret < 0) {
const char* errmsg = strerror(errno);
isc_throw(Unexpected,
- "PktFilterTestStub: cannot open /dev/null:" << errmsg);
+ "PktFilterTestStub: cannot open pipe: " << errmsg);
}
try {
if (open_socket_callback_) {
open_socket_callback_(port);
}
- } catch(...) {
+ } catch (...) {
// Don't leak fd on simulated errors.
- close(fd);
+ close(pipefd[0]);
+ close(pipefd[1]);
throw;
}
- return (SocketInfo(addr, port, fd));
+ close(pipefd[1]);
+
+ return (SocketInfo(addr, port, pipefd[0]));
}
Pkt4Ptr
--- /dev/null
+// 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/epoll_event_handler.h>
+
+#include <cstring>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+
+EPollEventHandler::EPollEventHandler() : FDEventHandler(TYPE_EPOLL), epollfd_(-1) {
+ epollfd_ = epoll_create1(EPOLL_CLOEXEC);
+ if (epollfd_ == -1) {
+ isc_throw(Unexpected, "error opening epoll: " << strerror(errno));
+ }
+ if (pipe(pipefd)) {
+ close(epollfd_);
+ isc_throw(Unexpected, "error opening internal epoll pipe: " << strerror(errno));
+ }
+ clear();
+}
+
+EPollEventHandler::~EPollEventHandler() {
+ close(epollfd_);
+ close(pipefd[1]);
+ close(pipefd[0]);
+}
+
+void EPollEventHandler::add(int fd, bool read /* = true */, bool write /* = false */) {
+ if (fd < 0) {
+ isc_throw(BadValue, "invalid negative value for fd");
+ }
+ struct epoll_event data;
+ memset(&data, 0, sizeof(data));
+ data.data.fd = fd;
+ if (read) {
+ // Add this socket to read events
+ data.events |= EPOLLIN;
+ }
+ if (write) {
+ // Add this socket to write events
+ data.events |= EPOLLOUT;
+ }
+ data_.push_back(data);
+}
+
+int EPollEventHandler::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");
+ }
+ int timeout = -1;
+ if (use_timeout) {
+ timeout = timeout_sec * 1000 + timeout_usec / 1000;
+ }
+ map_.clear();
+ used_data_.clear();
+ errors_.clear();
+ errno = 0;
+ int saved_errno = errno;
+ for (auto data : data_) {
+ if (epoll_ctl(epollfd_, EPOLL_CTL_ADD, data.data.fd, &data) == -1) {
+ saved_errno = errno;
+ errors_.insert(data.data.fd);
+ } else {
+ used_data_.push_back(data);
+ }
+ }
+ struct epoll_event dummy;
+ memset(&dummy, 0, sizeof(dummy));
+ dummy.data.fd = pipefd[0];
+ dummy.events |= EPOLLIN;
+ epoll_ctl(epollfd_, EPOLL_CTL_ADD, dummy.data.fd, &dummy);
+ used_data_.push_back(dummy);
+ int result = 0;
+ if (errors_.empty()) {
+ result = epoll_wait(epollfd_, used_data_.data(), used_data_.size(), timeout);
+ for (int i = 0; i < result; ++i) {
+ map_[used_data_[i].data.fd] = &used_data_[i];
+ }
+ }
+ for (auto data : data_) {
+ epoll_ctl(epollfd_, EPOLL_CTL_DEL, data.data.fd, &data);
+ }
+ epoll_ctl(epollfd_, EPOLL_CTL_DEL, dummy.data.fd, &dummy);
+ if (result != -1) {
+ errno = saved_errno;
+ }
+ if (errors_.size()) {
+ return (errors_.size());
+ }
+ return (result);
+}
+
+bool EPollEventHandler::readReady(int fd) {
+ if (map_.find(fd) == map_.end()) {
+ return (false);
+ }
+ return (map_[fd]->events & EPOLLIN);
+}
+
+bool EPollEventHandler::writeReady(int fd) {
+ if (map_.find(fd) == map_.end()) {
+ return (false);
+ }
+ return (map_[fd]->events & EPOLLOUT);
+}
+
+bool EPollEventHandler::hasError(int fd) {
+ if (errors_.count(fd)) {
+ return (true);
+ }
+ if (map_.find(fd) == map_.end()) {
+ return (false);
+ }
+ return (map_[fd]->events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP));
+}
+
+void EPollEventHandler::clear() {
+ data_.clear();
+ used_data_.clear();
+ errors_.clear();
+ map_.clear();
+ errno = 0;
+}
+
+} // end of namespace isc::util
+} // end of namespace isc
--- /dev/null
+// 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 EPOLL_EVENT_HANDLER_H
+#define EPOLL_EVENT_HANDLER_H
+
+#include <util/fd_event_handler.h>
+
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <sys/epoll.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 EPollEventHandler : public FDEventHandler {
+public:
+ /// @brief Constructor.
+ EPollEventHandler();
+
+ /// @brief Destructor.
+ virtual ~EPollEventHandler();
+
+ /// @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 epoll file descriptor.
+ int epollfd_;
+
+ /// @brief The epoll file descriptors data.
+ std::vector<struct epoll_event> data_;
+
+ /// @brief The epoll file descriptors data.
+ std::vector<struct epoll_event> 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_map<int, struct epoll_event*> map_;
+
+ /// @brief The pipe used to permit calling @ref waitEvent with no
+ /// registered file descriptors.
+ int pipefd[2];
+};
+
+} // namespace isc::util;
+} // namespace isc
+
+#endif // EPOLL_EVENT_HANDLER_H
#include <config.h>
+#include <util/epoll_event_handler.h>
#include <util/fd_event_handler_factory.h>
#include <util/poll_event_handler.h>
#include <util/select_event_handler.h>
if (string(env_type) == string("poll")) {
type = FDEventHandler::TYPE_POLL;
}
+ if (string(env_type) == string("epoll")) {
+ type = FDEventHandler::TYPE_EPOLL;
+ }
}
switch(type) {
case FDEventHandler::TYPE_SELECT:
return (FDEventHandlerPtr(new SelectEventHandler()));
+ case FDEventHandler::TYPE_EPOLL:
+ return (FDEventHandlerPtr(new EPollEventHandler()));
case FDEventHandler::TYPE_POLL:
return (FDEventHandlerPtr(new PollEventHandler()));
default:
'dhcp_space.cc',
'encode/encode.cc',
'encode/utf8.cc',
+ 'epoll_event_handler.cc',
'fd_event_handler.cc',
'fd_event_handler_factory.cc',
'filesystem.cc',
'doubles.h',
'encode/encode.h',
'encode/utf8.h',
+ 'epoll_event_handler.h',
'fd_event_handler.h',
'fd_event_handler_factory.h',
'filesystem.h',
#include <util/fd_event_handler.h>
#include <unordered_map>
+#include <unordered_set>
#include <vector>
#include <sys/poll.h>
--- /dev/null
+// 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 EPollEventHandler
+#define FDEventHandlerTest EPollEventHandlerTest
+
+#include <fd_event_handler_unittests.h>
#include <exceptions/exceptions.h>
#include <util/fd_event_handler.h>
+#include <util/epoll_event_handler.h>
#include <util/poll_event_handler.h>
#include <util/select_event_handler.h>
/// @brief Constructor.
FDEventHandlerTest() {
handler_.reset(new FDEventHandlerType);
- pipe(pipefd);
+ pipe(pipefd_);
}
/// @brief Destructor.
~FDEventHandlerTest() {
- close(pipefd[1]);
- close(pipefd[0]);
+ close(pipefd_[1]);
+ close(pipefd_[0]);
}
/// @brief The tested fd event handler.
FDEventHandlerPtr handler_;
/// @brief The pipe used for testing read and write operations.
- int pipefd[2];
+ int pipefd_[2];
};
TEST_F(FDEventHandlerTest, events) {
EXPECT_THROW(handler_->add(-1), BadValue);
- EXPECT_NO_THROW(handler_->add(pipefd[0], true, false));
- EXPECT_NO_THROW(handler_->add(pipefd[1], false, true));
+ EXPECT_NO_THROW(handler_->add(pipefd_[0], true, false));
+ EXPECT_NO_THROW(handler_->add(pipefd_[1], false, true));
- EXPECT_FALSE(handler_->readReady(pipefd[0]));
- EXPECT_FALSE(handler_->writeReady(pipefd[0]));
- EXPECT_FALSE(handler_->readReady(pipefd[1]));
- EXPECT_FALSE(handler_->writeReady(pipefd[1]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+ EXPECT_FALSE(handler_->writeReady(pipefd_[1]));
EXPECT_EQ(1, handler_->waitEvent(0, 1000));
- EXPECT_FALSE(handler_->readReady(pipefd[0]));
- EXPECT_FALSE(handler_->writeReady(pipefd[0]));
- EXPECT_FALSE(handler_->readReady(pipefd[1]));
- EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+ EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
- EXPECT_EQ(1, write(pipefd[1], &MARKER, sizeof(MARKER)));
+ EXPECT_EQ(1, write(pipefd_[1], &MARKER, sizeof(MARKER)));
EXPECT_EQ(2, handler_->waitEvent(0, 1000));
- EXPECT_TRUE(handler_->readReady(pipefd[0]));
- EXPECT_FALSE(handler_->writeReady(pipefd[0]));
- EXPECT_FALSE(handler_->readReady(pipefd[1]));
- EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+ EXPECT_TRUE(handler_->readReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+ EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
- EXPECT_EQ(1, write(pipefd[1], &MARKER, sizeof(MARKER)));
+ EXPECT_EQ(1, write(pipefd_[1], &MARKER, sizeof(MARKER)));
EXPECT_EQ(2, handler_->waitEvent(0, 1000));
- EXPECT_TRUE(handler_->readReady(pipefd[0]));
- EXPECT_FALSE(handler_->writeReady(pipefd[0]));
- EXPECT_FALSE(handler_->readReady(pipefd[1]));
- EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+ EXPECT_TRUE(handler_->readReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+ EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
unsigned char data;
- EXPECT_EQ(1, read(pipefd[0], &data, sizeof(data)));
+ EXPECT_EQ(1, read(pipefd_[0], &data, sizeof(data)));
EXPECT_EQ(2, handler_->waitEvent(0, 1000));
- EXPECT_TRUE(handler_->readReady(pipefd[0]));
- EXPECT_FALSE(handler_->writeReady(pipefd[0]));
- EXPECT_FALSE(handler_->readReady(pipefd[1]));
- EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+ EXPECT_TRUE(handler_->readReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+ EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
- EXPECT_EQ(1, read(pipefd[0], &data, sizeof(data)));
+ EXPECT_EQ(1, read(pipefd_[0], &data, sizeof(data)));
EXPECT_EQ(1, handler_->waitEvent(0, 1000));
- EXPECT_FALSE(handler_->readReady(pipefd[0]));
- EXPECT_FALSE(handler_->writeReady(pipefd[0]));
- EXPECT_FALSE(handler_->readReady(pipefd[1]));
- EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+ EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+ EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
EXPECT_NO_THROW(handler_->clear());
}
TEST_F(FDEventHandlerTest, badFD) {
- int fd = open("/dev/zero", O_RDONLY, 0);
+ errno = 0;
+ int fd;
+
+ if (handler_->type() == FDEventHandler::TYPE_EPOLL) {
+ // epoll does not allow add of /dev/zero to registered events.
+ fd = pipefd_[0];
+ EXPECT_EQ(1, write(pipefd_[1], &MARKER, sizeof(MARKER)));
+ } else {
+ fd = open("/dev/zero", O_RDONLY, 0);
+ }
ASSERT_GE(fd, 0);
EXPECT_TRUE(handler_->readReady(fd));
EXPECT_FALSE(handler_->hasError(fd));
EXPECT_EQ(EBADF, errno);
- } else {
+ } else if (handler_->type() == FDEventHandler::TYPE_POLL) {
EXPECT_EQ(1, handler_->waitEvent(0, 1000));
EXPECT_FALSE(handler_->readReady(fd));
EXPECT_TRUE(handler_->hasError(fd));
EXPECT_EQ(0, errno);
+ } else {
+ EXPECT_EQ(1, handler_->waitEvent(0, 1000));
+ EXPECT_FALSE(handler_->readReady(fd));
+ EXPECT_TRUE(handler_->hasError(fd));
+ EXPECT_EQ(EBADF, errno);
+ }
+
+ if (handler_->type() == FDEventHandler::TYPE_EPOLL) {
+ close(pipefd_[1]);
+ pipe(pipefd_);
}
}
'dhcp_space_unittest.cc',
'doubles_unittest.cc',
'encode_unittest.cc',
+ 'epoll_event_handler_unittests.cc',
'fd_event_handler_factory_unittests.cc',
'fd_tests.cc',
'filesystem_unittests.cc',
EXPECT_EQ(1, selectCheck(select_fd));
EXPECT_TRUE(watch->isReady());
+ // The epoll event handler must be created before closing the socket.
+ // It creates an internal pipe which will match the closed fd and the
+ // check for bad file descriptor will fail.
+ FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
+ bool use_select = FDEventHandlerFactory::factoryFDEventHandler()->type() == FDEventHandler::TYPE_SELECT;
+
// Interfere by closing the fd.
ASSERT_EQ(0, close(select_fd));
ASSERT_NO_THROW(watch->clearReady());
// Verify the select_fd fails as socket is invalid/closed.
- if (FDEventHandlerFactory::factoryFDEventHandler()->type() == FDEventHandler::TYPE_SELECT) {
- EXPECT_EQ(-1, selectCheck(select_fd));
+ if (use_select) {
+ ASSERT_EQ(-1, selectCheck(select_fd));
} else {
- FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
handler->add(select_fd);
EXPECT_EQ(1, handler->waitEvent(0, 0));
- EXPECT_TRUE(handler->hasError(select_fd));
+ ASSERT_TRUE(handler->hasError(select_fd));
}
// Verify that subsequent attempts to mark it will fail.
EXPECT_TRUE(watch->isReady());
EXPECT_EQ(1, selectCheck(select_fd));
+ // The epoll event handler must be created before closing the socket.
+ // It creates an internal pipe which will match the closed fd and the
+ // check for bad file descriptor will fail.
+ FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
+ bool use_select = FDEventHandlerFactory::factoryFDEventHandler()->type() == FDEventHandler::TYPE_SELECT;
+
// Interfere by reading the fd. This should empty the read pipe.
uint32_t buf = 0;
ASSERT_EQ((read (select_fd, &buf, 1)), 1);
// Verify the select_fd does not evaluate to ready.
EXPECT_FALSE(watch->isReady());
- if (FDEventHandlerFactory::factoryFDEventHandler()->type() == FDEventHandler::TYPE_SELECT) {
+ if (use_select) {
EXPECT_EQ(-1, selectCheck(select_fd));
} else {
- FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
handler->add(select_fd);
EXPECT_EQ(1, handler->waitEvent(0, 0));
EXPECT_TRUE(handler->hasError(select_fd));