Browse Source

[3221] Add new class dhcp_ddns::WatchSocket

Added a new to libdhcp_ddns, WatchSocket. This class provides an open
file descriptor whose read-readiness can be set or cleared and checked
via select() or poll() variants.
Thomas Markwalder 11 years ago
parent
commit
1ebd0b7870

+ 1 - 0
src/lib/dhcp_ddns/Makefile.am

@@ -34,6 +34,7 @@ libb10_dhcp_ddns_la_SOURCES += dhcp_ddns_log.cc dhcp_ddns_log.h
 libb10_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
 libb10_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
 libb10_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
 libb10_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
 libb10_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
 libb10_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
+libb10_dhcp_ddns_la_SOURCES += watch_socket.cc watch_socket.h
 
 
 nodist_libb10_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
 nodist_libb10_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
 
 

+ 12 - 0
src/lib/dhcp_ddns/dhcp_ddns_messages.mes

@@ -74,3 +74,15 @@ This is an error message that indicates that an exception was thrown but not
 caught in the application's send completion handler.  This is a programmatic
 caught in the application's send completion handler.  This is a programmatic
 error that needs to be reported.  Dependent upon the nature of the error the
 error that needs to be reported.  Dependent upon the nature of the error the
 client may or may not continue operating normally.
 client may or may not continue operating normally.
+
+% DHCP_DDNS_WATCH_SOURCE_CLOSE_ERROR Source-side watch socket failed to close: %1
+This is an error message that indicates the application was unable to close
+the outbound side of a NCR sender's watch socket.  While technically possible
+this error is highly unlikely to occur and should not impair the application's
+ability to process requests.
+
+% DHCP_DDNS_WATCH_SINK_CLOSE_ERROR Sink-side watch socket failed to close: %1
+This is an error message that indicates the application was unable to close
+the inbound side of a NCR sender's watch socket.  While technically possible
+this error is highly unlikely to occur and should not impair the application's
+ability to process requests.

+ 1 - 0
src/lib/dhcp_ddns/tests/Makefile.am

@@ -29,6 +29,7 @@ TESTS += libdhcp_ddns_unittests
 libdhcp_ddns_unittests_SOURCES  = run_unittests.cc
 libdhcp_ddns_unittests_SOURCES  = run_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
+libdhcp_ddns_unittests_SOURCES += watch_socket_unittests.cc
 
 
 libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 
 

+ 96 - 0
src/lib/dhcp_ddns/tests/watch_socket_unittests.cc

@@ -0,0 +1,96 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp_ddns/watch_socket.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <sys/ioctl.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Returns the result of select() given an fd to check for read status.
+///
+/// @param fd_to_check The file descriptor to test
+/// @return Returns less than one on an error, 0 if the fd is not ready to
+/// read, > 0 if it is ready to read. 
+int selectCheck(int fd_to_check). 
+
+int selectCheck(int fd_to_check) {
+    fd_set read_fds;
+    int maxfd = 0;
+
+    FD_ZERO(&read_fds);
+
+    // Add this socket to listening set
+    FD_SET(fd_to_check,  &read_fds);
+    maxfd = fd_to_check;
+
+    struct timeval select_timeout;
+    select_timeout.tv_sec = 0;
+    select_timeout.tv_usec = 0;
+
+    return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
+
+/// @brief Tests the basic functionality of WatchSocket.
+TEST(WatchSocketTest, basics) {
+    WatchSocketPtr watch;
+
+    /// Verify that we can construct a WatchSocket.
+    ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+    ASSERT_TRUE(watch);
+
+    /// Verify that post-construction the state the select-fd is valid.
+    int select_fd = watch->getSelectFd();
+    EXPECT_NE(select_fd, WatchSocket::INVALID_SOCKET);
+   
+    /// Verify that isReady() is false and that a call to select agrees. 
+    EXPECT_FALSE(watch->isReady());
+    EXPECT_EQ(0, selectCheck(select_fd));
+
+    /// Verify that the socket can be marked ready.
+    ASSERT_NO_THROW(watch->markReady());
+
+    /// Verify that we have exactly one marker waiting to be read.
+    int count = 0;
+    EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+    EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+    /// Verify that we can call markReady again without error.
+    ASSERT_NO_THROW(watch->markReady());
+
+    /// Verify that we STILL have exactly one marker waiting to be read.
+    EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+    EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+    /// Verify that isReady() is true and that a call to select agrees. 
+    EXPECT_TRUE(watch->isReady());
+    EXPECT_EQ(1, selectCheck(select_fd));
+
+    /// Verify that the socket can be cleared.
+    ASSERT_NO_THROW(watch->clearReady());
+
+    /// Verify that isReady() is false and that a call to select agrees. 
+    EXPECT_FALSE(watch->isReady());
+    EXPECT_EQ(0, selectCheck(select_fd));
+}
+
+} // end of anonymous namespace

+ 100 - 0
src/lib/dhcp_ddns/watch_socket.cc

@@ -0,0 +1,100 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file watch_socket.cc
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/watch_socket.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+const int WatchSocket::INVALID_SOCKET;
+const uint32_t WatchSocket::MARKER;
+
+WatchSocket::WatchSocket() 
+    : source_(INVALID_SOCKET), sink_(INVALID_SOCKET), ready_flag_(false) {
+    // Open the pipe.
+    int fds[2];
+    if (pipe(fds)) {
+        const char* errstr = strerror(errno);
+        isc_throw(WatchSocketError, "Cannot construct pipe: " << errstr);
+    }
+
+    source_ = fds[1];
+    sink_ = fds[0];
+}
+
+WatchSocket::~WatchSocket() {
+    // Close the pipe fds.  Technically a close can fail (hugely unlikely)
+    // but there's no recovery for it either.  If one does fail we log it 
+    // and go on. Plus no likes destructors that throw.
+    if (source_ != INVALID_SOCKET) {
+        if (close(source_)) {
+            const char* errstr = strerror(errno);
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_WATCH_SOURCE_CLOSE_ERROR)
+                      .arg(errstr);
+        }
+    }
+
+    if (sink_ != INVALID_SOCKET) {
+        if (close(sink_)) {
+            const char* errstr = strerror(errno);
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_WATCH_SINK_CLOSE_ERROR)
+                      .arg(errstr);
+        }
+    }
+}
+
+void 
+WatchSocket::markReady() {
+    if (!isReady()) {
+        int nbytes = write (source_, &MARKER, sizeof(MARKER));
+        if (nbytes != sizeof(MARKER)) {
+            const char* errstr = strerror(errno);
+            isc_throw(WatchSocketError, "WatchSocket markReady failed:"
+                      << " bytes written: " << nbytes << " : " << errstr);
+        }
+
+        ready_flag_ = true;
+    } 
+}
+
+bool 
+WatchSocket::isReady() {
+    return (ready_flag_);
+}
+
+void 
+WatchSocket::clearReady() {
+    if (isReady()) {
+        uint32_t buf;
+        int nbytes = read (sink_, &buf, sizeof(buf));
+        if (nbytes != sizeof(MARKER)) { 
+            const char* errstr = strerror(errno);
+            isc_throw(WatchSocketError, "WatchSocket clearReady failed:"
+                      << " bytes read: " << nbytes << " : " << errstr);
+        }
+
+        ready_flag_ = false;
+    }
+}
+
+int 
+WatchSocket::getSelectFd() {
+    return (sink_);
+}
+
+} // namespace isc::dhcp_ddns
+} // namespace isc

+ 108 - 0
src/lib/dhcp_ddns/watch_socket.h

@@ -0,0 +1,108 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef WATCH_SOCKET_H
+#define WATCH_SOCKET_H
+
+/// @file watch_socket.h Defines the class, WatchSocket.
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class WatchSocketError : public isc::Exception {
+public:
+    WatchSocketError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Provides an IO "ready" semaphore for use with select() or poll()
+/// WatchSocket exposes a single open file descriptor, the "select-fd" which
+/// can be marked as being ready to read (i.e. !EWOULDBLOCK) and cleared
+/// (i.e. EWOULDBLOCK).  The select-fd can be used with select(), poll(), or
+/// their variants alongside other file descriptors.
+///
+/// Internally, WatchSocket uses a pipe.  The select-fd is the "read" end of
+/// pipe.  To mark the socket as ready to read, an integer marker is written
+/// to the pipe.  To clear the socket, the marker is read from the pipe.  Note
+/// that WatchSocket will only write the marker if it is not already marked.
+/// This prevents the socket's pipe from filling endlessly.
+class WatchSocket {
+public:
+    /// @brief Value used to signify an invalid descriptor.
+    static const int INVALID_SOCKET = -1;
+    /// @brief Value written to the source when marking the socket as ready.
+    static const uint32_t MARKER = 0xDEADBEEF;
+
+    /// @brief Constructor
+    ///
+    /// Constructs an instance of the WatchSocket in the cleared (EWOULDBLOCK)
+    /// state.
+    WatchSocket();
+
+    /// @brief Destructor
+    ///
+    /// Closes all internal resources, including the select-fd.
+    virtual ~WatchSocket();
+
+    /// @brief Marks the select-fd as ready to read.
+    ///
+    /// Marks the socket as ready to read, if is not already so marked.
+    ///
+    /// @throw WatchSocketError if an error occurs marking the socket.
+    void markReady();
+
+    /// @brief Returns true the if socket is marked as ready.
+    bool isReady();
+
+    /// @brief Clears the socket's ready to read marker.
+    ///
+    /// Clears the socket if it is currently marked as ready to read.
+    ///
+    /// @throw WatchSocketError if an error occurs clearing the socket
+    /// marker.
+    void clearReady();
+
+    /// @brief Returns the file descriptor to use to monitor the socket.
+    ///
+    /// @note Using this file descriptor as anything other than an argument
+    /// to select() or similar methods can have unpredictable results.
+    ///
+    /// @return The file descriptor associated with read end of the socket's
+    /// pipe.
+    int getSelectFd();
+
+private:
+    /// @brief The end of the pipe to which the marker is written
+    int source_;
+
+    /// @brief The end of the pipe from which the marker is read.
+    /// This is the value returned as the select-fd.
+    int sink_;
+
+    /// @brief True the socket is currently marked ready to read.
+    bool ready_flag_;
+};
+
+/// @brief Defines a smart pointer to an instance of a WatchSocket.
+typedef boost::shared_ptr<WatchSocket> WatchSocketPtr;
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif