]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3230] Initial commit
authorThomas Markwalder <tmark@isc.org>
Thu, 1 Feb 2024 20:27:14 +0000 (15:27 -0500)
committerRazvan Becheriu <razvan@isc.org>
Thu, 15 Feb 2024 06:13:44 +0000 (08:13 +0200)
Adds dhcp::PktEvent, modifies packet filters and tests

src/lib/dhcp/pkt.*
    Add PktEvent class, add stack and methods to Pkt

src/lib/dhcp/pkt_filter_bpf.cc
    PktFilterBPF::receive() - fetch timestamp from bpfhdr,
    add SOCKET_RECEIVED and BUFFER_READ events

    PktFilterBPF::send() - add RESPONSE_SENT event

src/lib/dhcp/pkt_filter_inet.cc
    PktFilterInet::openSocket() - enable SO_TIMESTAMP socket option

    PktFilterInet::receive() - fetch SO_TIMESTAMP via CMSG,
    add SOCKET_RECEIVED and BUFFER_READ events

    PktFilterInet::send() - add RESPONSE_SENT event

src/lib/dhcp/pkt_filter_inet6.cc
    PktFilterInet6::openSocket() - enable SO_TIMESTAMP socket option

    PktFilterInet6::receive() - fetch SO_TIMESTAMP via CMSG,
    add SOCKET_RECEIVED and BUFFER_READ events

    PktFilterInet::send() - add RESPONSE_SENT event

src/lib/dhcp/pkt_filter_lpf.cc

    PktFilterLPF::openSocket() - enable SO_TIMESTAMP socket option
    PktFilterLPF::receive() - added conditonal compilation version based
    on WITH_CMSG macro that fetches SO_TIMESTAMP and add SOCKET_RECEIVED
    and BUFFER_READ events

    PktFilterLPF::send() - add RESPONSE_SENT event

src/lib/dhcp/tests/pkt4_unittest.cc
    TEST_F(Pkt4Test, PktEvents) - new test

src/lib/dhcp/tests/pkt6_unittest.cc
    TEST_F(Pkt6Test, PktEvents) - new test

src/lib/dhcp/tests/pkt_filter6_test_utils.cc
src/lib/dhcp/tests/pkt_filter6_test_utils.h
src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc
src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
src/lib/dhcp/tests/pkt_filter_test_utils.cc
src/lib/dhcp/tests/pkt_filter_test_utils.h
    Updated tests to check PktEvent stack contents

src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc
src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
    Updated tests to check PktEvent stack contents,
    Changed DISABLED_ tests to use SKIP_IF(notRoot)

16 files changed:
src/lib/dhcp/pkt.cc
src/lib/dhcp/pkt.h
src/lib/dhcp/pkt_filter_bpf.cc
src/lib/dhcp/pkt_filter_inet.cc
src/lib/dhcp/pkt_filter_inet6.cc
src/lib/dhcp/pkt_filter_lpf.cc
src/lib/dhcp/tests/pkt4_unittest.cc
src/lib/dhcp/tests/pkt6_unittest.cc
src/lib/dhcp/tests/pkt_filter6_test_utils.cc
src/lib/dhcp/tests/pkt_filter6_test_utils.h
src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc
src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc
src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
src/lib/dhcp/tests/pkt_filter_test_utils.cc
src/lib/dhcp/tests/pkt_filter_test_utils.h

index 938c7f1299658d7ab65675f45156e3eff3072f6e..4db0a359f4b4db7018d44b73731fb03726c2c9b2 100644 (file)
 #include <boost/foreach.hpp>
 #include <vector>
 
+using namespace boost::posix_time;
+
 namespace isc {
 namespace dhcp {
 
+const std::string PktEvent::SOCKET_RECEIVED("socket_received");
+const std::string PktEvent::BUFFER_READ("buffer_read");
+const std::string PktEvent::RESPONSE_SENT("response_sent");
+
 Pkt::Pkt(uint32_t transid, const isc::asiolink::IOAddress& local_addr,
          const isc::asiolink::IOAddress& remote_addr, uint16_t local_port,
          uint16_t remote_port)
@@ -310,5 +316,83 @@ Pkt::getMACFromIPv6(const isc::asiolink::IOAddress& addr) {
     return (mac);
 }
 
+
+void
+Pkt::addPktEvent(const std::string& label, const ptime& timestamp) {
+    events_.push_back(PktEvent(label, timestamp));
+}
+
+void
+Pkt::setPktEvent(const std::string& label, const ptime& timestamp) {
+    for (auto& event : events_) {
+        if (event.label_ == label) {
+            event.timestamp_ = timestamp;
+            return;
+        }
+    }
+
+    events_.push_back(PktEvent(label, timestamp));
+}
+
+void
+Pkt::addPktEvent(const std::string& label, const struct timeval& tv) {
+    time_t time_t_secs = tv.tv_sec;
+    ptime timestamp = from_time_t(time_t_secs);
+    time_duration usecs(0, 0, 0, tv.tv_usec);
+    timestamp += usecs;
+    addPktEvent(label, timestamp);
+}
+
+ptime
+Pkt::getPktEventTime(const std::string& label) const {
+    for (const auto& event : events_) {
+        if (event.label_ == label) {
+            return(event.timestamp_);
+        }
+    }
+
+    return (PktEvent::EMPTY_TIME());
+}
+
+void
+Pkt::clearPktEvents() {
+    events_.clear();
+}
+
+std::string
+Pkt::dumpPktEvents(bool verbose /* = false */) const {
+    std::stringstream oss;
+    if (verbose) {
+        oss << "Event log: " << std::endl;
+    }
+
+    bool first_pass = true;
+    boost::posix_time::ptime beg_time;
+    boost::posix_time::ptime prev_time;
+    for (const auto& event : events_) {
+        if (!verbose) {
+            oss << (first_pass ? "" : ", ") <<  event.timestamp_ << " : " << event.label_;
+        } else {
+            oss << event.timestamp_ << " : " << event.label_;
+            if (first_pass) {
+                oss << std::endl;
+                beg_time = event.timestamp_;
+            } else {
+                oss << " elapsed: " << event.timestamp_ - prev_time << std::endl;
+            }
+
+            prev_time = event.timestamp_;
+        }
+
+        first_pass = false;
+    }
+
+    if (verbose) {
+        oss << "total elapsed: " << prev_time - beg_time;
+    }
+
+    return (oss.str());
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc
index f17f9f13a8660c9fdf58621d0613fd086c854474..a09e8754405b1acaed204925ca3c2ab4874c8277 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2024 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
@@ -84,6 +84,55 @@ private:
     std::pair<PktTypePtr, PktTypePtr> pkts_;
 };
 
+
+/// @brief Describes an event during the life cycle of a packet.
+class PktEvent {
+public:
+    /// @brief Event that marks when a packet is placed in the socket buffer
+    /// by the kernel.
+    static const std::string SOCKET_RECEIVED;
+
+    /// @brief Event that marks when a packet is read from the socket buffer
+    /// by application.
+    static const std::string BUFFER_READ;
+
+    /// @brief Event that marks when a packet is been written to the socket
+    /// by application.
+    static const std::string RESPONSE_SENT;
+
+    /// @brief Constructor
+    ///
+    /// @param label string identifying the event.
+    /// @param timestamp time at which the event occurred.
+    PktEvent(const std::string& label, boost::posix_time::ptime timestamp)
+        : label_(label), timestamp_(timestamp) {
+    }
+
+    /// @brief Destructor
+    ~PktEvent() = default;
+
+    /// @brief Fetch the current UTC system time, microsecond precision.
+    ///
+    /// @return ptime containing the microsecond system time.
+    static boost::posix_time::ptime now() {
+        return(boost::posix_time::microsec_clock::universal_time());
+    }
+
+    /// @brief Fetch an empty timestamp, used for logic comparisons
+    ///
+    /// @return an unset ptime.
+    static boost::posix_time::ptime EMPTY_TIME() {
+        static boost::posix_time::ptime empty_time;
+        return (empty_time);
+    }
+
+    /// @brief Label identifying this event.
+    std::string label_;
+
+    /// @brief Timestamp at which the event occurred.
+    boost::posix_time::ptime timestamp_;
+};
+
 /// @brief Base class for classes representing DHCP messages.
 ///
 /// This is a base class that holds common information (e.g. source
@@ -451,14 +500,65 @@ public:
         return timestamp_;
     }
 
-    /// @brief Set packet timestamp.
+    /// @brief Set socket receive timestamp.
     ///
-    /// Sets packet timestamp to arbitrary value.
-    /// It is used by perfdhcp tool and should not be used elsewhere.
+    /// Sets the socket receive timestamp to an arbitrary value.
     void setTimestamp(boost::posix_time::ptime& timestamp) {
         timestamp_ = timestamp;
     }
 
+    /// @brief Adds an event to the end of the event stack.
+    ///
+    /// @param label string identifying the event
+    /// @param timestamp time at which the event occurred. It is expected
+    /// to be in UTC/microseconds.  Defaults to the current time.
+    void addPktEvent(const std::string& label,
+                     const boost::posix_time::ptime& timestamp = PktEvent::now());
+
+    /// @brief Adds an event to the end of the event stack with the timestamp
+    /// specified as a struct timeval.
+    ///
+    /// @param label string identifying the event
+    /// @param timestamp time at which the event occurred. It is expected
+    /// to be in UTC/microseconds.
+    void addPktEvent(const std::string& label, const struct timeval& timestamp);
+
+    /// @brief Updates (or adds) an event in the event stack.
+    ///
+    /// Updates the timestamp of the event described by label if it exists in
+    /// the stack, otherwise it adds the event to the end of the stack.  This
+    /// is intended to be used for testing.
+    ///
+    /// @param label string identifying the event
+    /// @param timestamp time at which the event occurred. It is expected
+    /// to be in UTC/microseconds.
+    void setPktEvent(const std::string& label,
+                     const boost::posix_time::ptime& timestamp = PktEvent::now());
+
+    /// @brief Discards contents of the packet event stack.
+    ///
+    /// This is provided primarily for test purposes.
+    void clearPktEvents();
+
+    /// @brief Fetches the timestamp for a given event in the stack.
+    ///
+    /// @param label string identifying the event
+    /// @return timestamp of the event (UTC/microseconds)
+    boost::posix_time::ptime getPktEventTime(const std::string& label) const;
+
+    /// @brief Fetches the current event stack contents.
+    ///
+    /// @return reference to the list of events.
+    const std::list<PktEvent>& getPktEvents() {
+        return (events_);
+    }
+
+    /// @brief Creates a dump of the stack contents to a string for logging.
+    ///
+    /// @param verbose when true the dump is more verbose, includes durations
+    /// between events and spans multiple lines.  Defaults to false.
+    std::string dumpPktEvents(bool verbose = false) const;
+
     /// @brief Copies content of input buffer to output buffer.
     ///
     /// This is mostly a diagnostic function. It is being used for sending
@@ -858,6 +958,9 @@ private:
     virtual void setHWAddrMember(const uint8_t htype, const uint8_t hlen,
                                  const std::vector<uint8_t>& hw_addr,
                                  HWAddrPtr& storage);
+
+    /// @brief List of timestamped packet events.
+    std::list<PktEvent> events_;
 };
 
 /// @brief A pointer to either Pkt4 or Pkt6 packet
index 20d0098c68aec30564dc797aee2f8d3c6295fb56..340f0883f55eb655bc1dbff011cbb921887b36b5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2024 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
@@ -226,7 +226,6 @@ PktFilterBPF::openSocket(Iface& iface,
                          const isc::asiolink::IOAddress& addr,
                          const uint16_t port, const bool,
                          const bool) {
-
     // Open fallback socket first. If it fails, it will give us an indication
     // that there is another service (perhaps DHCP server) running.
     // The function will throw an exception and effectively cease opening
@@ -376,6 +375,7 @@ PktFilterBPF::openSocket(Iface& iface,
         close(sock);
         throw;
     }
+
     return (SocketInfo(addr, port, sock, fallback));
 }
 
@@ -537,6 +537,12 @@ PktFilterBPF::receive(Iface& iface, const SocketInfo& socket_info) {
     pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
     pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
 
+    // Set time the packet was stored in the buffer.
+    pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, bpfh.bh_tstamp);
+
+    // Set time packet was read from the buffer.
+    pkt->addPktEvent(PktEvent::BUFFER_READ);
+
     return (pkt);
 }
 
@@ -586,6 +592,7 @@ PktFilterBPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
                   << strerror(errno));
     }
 
+    pkt->addPktEvent(PktEvent::RESPONSE_SENT);
     return (0);
 }
 
index 5fedd300013d4ecde39d51281afc7fc6bb80bf43..ac21c2821bbd5c90341a095140a0c3d1f91bb020 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2024 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
@@ -17,8 +17,7 @@ using namespace isc::asiolink;
 namespace isc {
 namespace dhcp {
 
-const size_t
-PktFilterInet::CONTROL_BUF_LEN = CMSG_SPACE(sizeof(struct in6_pktinfo));
+const size_t PktFilterInet::CONTROL_BUF_LEN = 512;
 
 SocketInfo
 PktFilterInet::openSocket(Iface& iface,
@@ -51,6 +50,15 @@ PktFilterInet::openSocket(Iface& iface,
                   << " on socket " << sock);
     }
 
+#ifdef SO_TIMESTAMP
+    int enable = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable))) {
+        const char* errmsg = strerror(errno);
+        isc_throw(SocketConfigError, "Could not enable SO_TIMESTAMP for " << addr.toText()
+                  << ", error: " << errmsg);
+    }
+#endif
+
 #ifdef SO_BINDTODEVICE
     if (receive_bcast && iface.flag_broadcast_) {
         // Bind to device so as we receive traffic on a specific interface.
@@ -173,7 +181,6 @@ PktFilterInet::receive(Iface& iface, const SocketInfo& socket_info) {
 
             pkt->setIndex(pktinfo->ipi_ifindex);
             pkt->setLocalAddr(IOAddress(htonl(pktinfo->ipi_addr.s_addr)));
-            break;
 
             // This field is useful, when we are bound to unicast
             // address e.g. 192.0.2.1 and the packet was sent to
@@ -182,7 +189,19 @@ PktFilterInet::receive(Iface& iface, const SocketInfo& socket_info) {
 
             // XXX: Perhaps we should uncomment this:
             // to_addr = pktinfo->ipi_spec_dst;
+#ifndef SO_TIMESTAMP
+            break;
+        }
+#else
+        } else if ((cmsg->cmsg_level == SOL_SOCKET) &&
+                   (cmsg->cmsg_type  == SCM_TIMESTAMP)) {
+
+            struct timeval cmsg_time;
+            memcpy(&cmsg_time, CMSG_DATA(cmsg), sizeof(cmsg_time));
+            pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, cmsg_time);
         }
+#endif
+
         cmsg = CMSG_NXTHDR(&m, cmsg);
     }
 
@@ -195,12 +214,23 @@ PktFilterInet::receive(Iface& iface, const SocketInfo& socket_info) {
             (cmsg->cmsg_type == IP_RECVDSTADDR)) {
             to_addr = reinterpret_cast<struct in_addr*>(CMSG_DATA(cmsg));
             pkt->setLocalAddr(IOAddress(htonl(to_addr->s_addr)));
+#ifndef SO_TIMESTAMP
             break;
         }
+#else
+        } else if ((cmsg->cmsg_level == SOL_SOCKET) &&
+                   (cmsg->cmsg_type  == SCM_TIMESTAMP)) {
+
+            struct timeval cmsg_time;
+            memcpy(&cmsg_time, CMSG_DATA(cmsg), sizeof(cmsg_time));
+            pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, cmsg_time);
+        }
+#endif
         cmsg = CMSG_NXTHDR(&m, cmsg);
     }
 
 #endif
+    pkt->addPktEvent(PktEvent::BUFFER_READ);
 
     return (pkt);
 }
@@ -272,6 +302,8 @@ PktFilterInet::send(const Iface&, uint16_t sockfd, const Pkt4Ptr& pkt) {
 
     pkt->updateTimestamp();
 
+    pkt->addPktEvent(PktEvent::RESPONSE_SENT);
+
     int result = sendmsg(sockfd, &m, 0);
     if (result < 0) {
         isc_throw(SocketWriteError, "pkt4 send failed: sendmsg() returned "
index b7c05af5bb8b38732e23f3fd9b17aa401dc4ca11..af8987b80bee85300ed832d11a531972f158d033 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2024 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
@@ -20,8 +20,7 @@ using namespace isc::asiolink;
 namespace isc {
 namespace dhcp {
 
-const size_t
-PktFilterInet6::CONTROL_BUF_LEN = CMSG_SPACE(sizeof(struct in6_pktinfo));
+const size_t PktFilterInet6::CONTROL_BUF_LEN = 512;
 
 SocketInfo
 PktFilterInet6::openSocket(const Iface& iface,
@@ -91,6 +90,14 @@ PktFilterInet6::openSocket(const Iface& iface,
     }
 #endif
 
+#ifdef SO_TIMESTAMP
+    int enable = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable))) {
+        const char* errmsg = strerror(errno);
+        isc_throw(SocketConfigError, "Can't set SO_TIMESTAMP for " << addr.toText());
+    }
+#endif
+
 #ifdef IPV6_V6ONLY
     // Set IPV6_V6ONLY to get only IPv6 packets.
     if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
@@ -150,6 +157,11 @@ PktFilterInet6::receive(const SocketInfo& socket_info) {
     struct sockaddr_in6 from;
     memset(&from, 0, sizeof(from));
 
+#ifdef SO_TIMESTAMP
+    struct timeval so_rcv_timestamp;
+    memset(&so_rcv_timestamp, 0, sizeof(so_rcv_timestamp));
+#endif
+
     // Initialize our message header structure.
     struct msghdr m;
     memset(&m, 0, sizeof(m));
@@ -202,8 +214,15 @@ PktFilterInet6::receive(const SocketInfo& socket_info) {
                 to_addr = pktinfo->ipi6_addr;
                 ifindex = pktinfo->ipi6_ifindex;
                 found_pktinfo = true;
+#ifndef SO_TIMESTAMP
                 break;
             }
+#else
+            } else if ((cmsg->cmsg_level == SOL_SOCKET) &&
+                       (cmsg->cmsg_type  == SCM_TIMESTAMP)) {
+                memcpy(&so_rcv_timestamp, CMSG_DATA(cmsg), sizeof(so_rcv_timestamp));
+            }
+#endif
             cmsg = CMSG_NXTHDR(&m, cmsg);
         }
         if (!found_pktinfo) {
@@ -234,6 +253,12 @@ PktFilterInet6::receive(const SocketInfo& socket_info) {
 
     pkt->updateTimestamp();
 
+#ifdef SO_TIMESTAMP
+    pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, so_rcv_timestamp);
+#endif
+
+    pkt->addPktEvent(PktEvent::BUFFER_READ);
+
     pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6,
                       reinterpret_cast<const uint8_t*>(&to_addr)));
     pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6,
@@ -326,6 +351,8 @@ PktFilterInet6::send(const Iface&, uint16_t sockfd, const Pkt6Ptr& pkt) {
 
     pkt->updateTimestamp();
 
+    pkt->addPktEvent(PktEvent::RESPONSE_SENT);
+
     int result = sendmsg(sockfd, &m, 0);
     if (result < 0) {
         isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned"
index 791e8635d27fb657b172194620d5218f36dcc2d7..7b36dc12fef464d40009a9aa076afad5aa2ea7b4 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2024 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
@@ -17,6 +17,8 @@
 #include <linux/if_ether.h>
 #include <linux/if_packet.h>
 
+#define WITH_CMSG
+
 namespace {
 
 using namespace isc::dhcp;
@@ -133,7 +135,6 @@ PktFilterLPF::openSocket(Iface& iface,
                          const isc::asiolink::IOAddress& addr,
                          const uint16_t port, const bool,
                          const bool) {
-
     // Open fallback socket first. If it fails, it will give us an indication
     // that there is another service (perhaps DHCP server) running.
     // The function will throw an exception and effectively cease opening
@@ -180,6 +181,15 @@ PktFilterLPF::openSocket(Iface& iface,
                   << " on the socket " << sock);
     }
 
+#ifdef SO_TIMESTAMP
+    int enable = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable))) {
+        const char* errmsg = strerror(errno);
+        isc_throw(SocketConfigError, "Can't enable SO_TIMESTAMP for " << addr.toText()
+                  << ", error: " << errmsg);
+    }
+#endif
+
     struct sockaddr_ll sa;
     memset(&sa, 0, sizeof(sockaddr_ll));
     sa.sll_family = AF_PACKET;
@@ -213,6 +223,7 @@ PktFilterLPF::openSocket(Iface& iface,
 
 }
 
+#ifndef WITH_CMSG
 Pkt4Ptr
 PktFilterLPF::receive(Iface& iface, const SocketInfo& socket_info) {
     uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
@@ -285,8 +296,123 @@ PktFilterLPF::receive(Iface& iface, const SocketInfo& socket_info) {
     pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
     pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
 
+    // Set time packet was read from the buffer.
+    pkt->addPktEvent(PktEvent::BUFFER_READ);
+
+    return (pkt);
+}
+#else
+Pkt4Ptr
+PktFilterLPF::receive(Iface& iface, const SocketInfo& socket_info) {
+    uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
+    // First let's get some data from the fallback socket. The data will be
+    // discarded but we don't want the socket buffer to bloat. We get the
+    // packets from the socket in loop but most of the time the loop will
+    // end after receiving one packet. The call to recv returns immediately
+    // when there is no data left on the socket because the socket is
+    // non-blocking.
+    // @todo In the normal conditions, both the primary socket and the fallback
+    // socket are in sync as they are set to receive packets on the same
+    // address and port. The reception of packets on the fallback socket
+    // shouldn't cause significant lags in packet reception. If we find in the
+    // future that it does, the sort of threshold could be set for the maximum
+    // bytes received on the fallback socket in a single round. Further
+    // optimizations would include an asynchronous read from the fallback socket
+    // when the DHCP server is idle.
+    int datalen;
+    do {
+        datalen = recv(socket_info.fallbackfd_, raw_buf, sizeof(raw_buf), 0);
+    } while (datalen > 0);
+
+    const size_t CONTROL_BUF_LEN = 512;
+
+    uint8_t msg_buf[IfaceMgr::RCVBUFSIZE];
+    uint8_t control_buf[CONTROL_BUF_LEN];
+
+    memset(&control_buf[0], 0, CONTROL_BUF_LEN);
+
+    // Initialize our message header structure.
+    struct msghdr m;
+    memset(&m, 0, sizeof(m));
+
+    struct iovec v;
+    v.iov_base = static_cast<void*>(msg_buf);
+    v.iov_len = IfaceMgr::RCVBUFSIZE;
+    m.msg_iov = &v;
+    m.msg_iovlen = 1;
+
+    // Getting the interface is a bit more involved.
+    //
+    // We set up some space for a "control message". We have
+    // previously asked the kernel to give us packet
+    // information (when we initialized the interface), so we
+    // should get the destination address from that.
+    m.msg_control = &control_buf[0];
+    m.msg_controllen = CONTROL_BUF_LEN;
+
+    int result = recvmsg(socket_info.sockfd_, &m, 0);
+    if (result < 0) {
+        isc_throw(SocketReadError, "Pkt4FilterLpf to receive UDP4 data");
+    }
+
+    InputBuffer buf(msg_buf, result);
+
+    // @todo: This is awkward way to solve the chicken and egg problem
+    // whereby we don't know the offset where DHCP data start in the
+    // received buffer when we create the packet object. In general case,
+    // the IP header has variable length. The information about its length
+    // is stored in one of its fields. Therefore, we have to decode the
+    // packet to get the offset of the DHCP data. The dummy object is
+    // created so as we can pass it to the functions which decode IP stack
+    // and find actual offset of the DHCP data.
+    // Once we find the offset we can create another Pkt4 object from
+    // the reminder of the input buffer and set the IP addresses and
+    // ports from the dummy packet. We should consider doing it
+    // in some more elegant way.
+    Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+    // Decode ethernet, ip and udp headers.
+    decodeEthernetHeader(buf, dummy_pkt);
+    decodeIpUdpHeader(buf, dummy_pkt);
+
+    // Read the DHCP data.
+    std::vector<uint8_t> dhcp_buf;
+    buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+
+    // Decode DHCP data into the Pkt4 object.
+    Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+
+    // Set the appropriate packet members using data collected from
+    // the decoded headers.
+    pkt->setIndex(iface.getIndex());
+    pkt->setIface(iface.getName());
+    pkt->setLocalAddr(dummy_pkt->getLocalAddr());
+    pkt->setRemoteAddr(dummy_pkt->getRemoteAddr());
+    pkt->setLocalPort(dummy_pkt->getLocalPort());
+    pkt->setRemotePort(dummy_pkt->getRemotePort());
+    pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
+    pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
+
+    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
+    while (cmsg != NULL) {
+        if ((cmsg->cmsg_level == SOL_SOCKET) &&
+                   (cmsg->cmsg_type  == SCM_TIMESTAMP)) {
+
+            struct timeval cmsg_time;
+            memcpy(&cmsg_time, CMSG_DATA(cmsg), sizeof(cmsg_time));
+            pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, cmsg_time);
+            break;
+        }
+
+        cmsg = CMSG_NXTHDR(&m, cmsg);
+    }
+
+    // Set time packet was read from the buffer.
+    pkt->addPktEvent(PktEvent::BUFFER_READ);
+
     return (pkt);
 }
+#endif
 
 int
 PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
@@ -323,6 +449,7 @@ PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
     sa.sll_protocol = htons(ETH_P_IP);
     sa.sll_halen = 6;
 
+    pkt->addPktEvent(PktEvent::RESPONSE_SENT);
     int result = sendto(sockfd, buf.getData(), buf.getLength(), 0,
                         reinterpret_cast<const struct sockaddr*>(&sa),
                         sizeof(sockaddr_ll));
index db07f2bd5845a6c39fc947afe34838ff3ba8f0bc..c9f120eeef9a9fe9c96bfbf4d7b1519156f47a29 100644 (file)
@@ -1525,4 +1525,71 @@ TEST_F(Pkt4Test, getHWAddrLabel) {
     EXPECT_EQ ("hwaddr=02:04:06:08:0a:0c", pkt.getHWAddrLabel());
 }
 
+// Exercises packet event stack and helper functions.
+TEST_F(Pkt4Test, PktEvents) {
+    // Get current time.
+    auto start_time = PktEvent::now();
+
+    // Verify that a set time is not equal to an EMPTY_TIME.
+    ASSERT_NE(start_time, PktEvent::EMPTY_TIME());
+
+    // Create a test packet.
+    scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+
+    // Upon creation, the events table should be empty.
+    ASSERT_TRUE(pkt->getPktEvents().empty());
+
+    // An non-existant event should return an empty time.
+    auto event_time = pkt->getPktEventTime(PktEvent::BUFFER_READ);
+    ASSERT_EQ(event_time, PktEvent::EMPTY_TIME());
+
+    // Sleep for 200 microseconds to put some distance between now and start_time.
+    usleep(200);
+
+    // Should be able to add an event, defaulting the event time to current time.
+    pkt->addPktEvent(PktEvent::BUFFER_READ);
+    event_time = pkt->getPktEventTime(PktEvent::BUFFER_READ);
+    ASSERT_GT(event_time, start_time);
+
+    // Should be able to overwrite an existing event's time.
+    pkt->setPktEvent(PktEvent::BUFFER_READ, start_time);
+    event_time = pkt->getPktEventTime(PktEvent::BUFFER_READ);
+    ASSERT_EQ(event_time, start_time);
+
+    // Should be able to add an event with an explicit time.
+    pkt->addPktEvent(PktEvent::RESPONSE_SENT, start_time);
+    event_time = pkt->getPktEventTime(PktEvent::RESPONSE_SENT);
+    ASSERT_EQ(event_time, start_time);
+
+    // Should be able to fetch the list of events.
+    const auto& events = pkt->getPktEvents();
+    ASSERT_FALSE(events.empty());
+    auto event = events.begin();
+    ASSERT_EQ((*event).label_, PktEvent::BUFFER_READ);
+    ++event;
+    ASSERT_EQ((*event).label_, PktEvent::RESPONSE_SENT);
+
+    // Discard the event stack contents.
+    pkt->clearPktEvents();
+    ASSERT_TRUE(pkt->getPktEvents().empty());
+
+    // Verify dumpPktEvent terse output. Also serves to
+    // verify adding events using struct timeval.
+    struct timeval log_time = {1706802676, 100};
+    struct timeval log_time_plus = {1706802676, 250};
+    pkt->addPktEvent("first-event", log_time);
+    pkt->addPktEvent("second-event", log_time_plus);
+    std::string log = pkt->dumpPktEvents();
+    EXPECT_EQ(log, "2024-Feb-01 15:51:16.000100 : first-event, 2024-Feb-01 15:51:16.000250 : second-event");
+
+    // Verify dumpPktEvent verbose output.
+    log = pkt->dumpPktEvents(true);
+    EXPECT_EQ(log,
+              "Event log: \n"
+              "2024-Feb-01 15:51:16.000100 : first-event\n"
+              "2024-Feb-01 15:51:16.000250 : second-event elapsed: 00:00:00.000150\n"
+              "total elapsed: 00:00:00.000150");
+}
+
+
 } // end of anonymous namespace
index eee590883c4d37956702c141ec9e52724c24d7e1..562db62077fc671acb557dfd5a51526d77162b8a 100644 (file)
@@ -2371,4 +2371,70 @@ TEST_F(Pkt6Test, relayDataOption) {
     EXPECT_EQ(orig_data, clone_data);
 }
 
+// Exercises packet event stack and helper functions.
+TEST_F(Pkt6Test, PktEvents) {
+    // Get current time.
+    auto start_time = PktEvent::now();
+
+    // Verify that a set time is not equal to an EMPTY_TIME.
+    ASSERT_NE(start_time, PktEvent::EMPTY_TIME());
+
+    // Create a test packet.
+    scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304));
+
+    // Upon creation, the events table should be empty.
+    ASSERT_TRUE(pkt->getPktEvents().empty());
+
+    // An non-existant event should return an empty time.
+    auto event_time = pkt->getPktEventTime(PktEvent::BUFFER_READ);
+    ASSERT_EQ(event_time, PktEvent::EMPTY_TIME());
+
+    // Sleep for 200 microseconds to put some distance between now and start_time.
+    usleep(200);
+
+    // Should be able to add an event, defaulting the event time to current time.
+    pkt->addPktEvent(PktEvent::BUFFER_READ);
+    event_time = pkt->getPktEventTime(PktEvent::BUFFER_READ);
+    ASSERT_GT(event_time, start_time);
+
+    // Should be able to overwrite an existing event's time.
+    pkt->setPktEvent(PktEvent::BUFFER_READ, start_time);
+    event_time = pkt->getPktEventTime(PktEvent::BUFFER_READ);
+    ASSERT_EQ(event_time, start_time);
+
+    // Should be able to add an event with an explicit time.
+    pkt->addPktEvent(PktEvent::RESPONSE_SENT, start_time);
+    event_time = pkt->getPktEventTime(PktEvent::RESPONSE_SENT);
+    ASSERT_EQ(event_time, start_time);
+
+    // Should be able to fetch the list of events.
+    const auto& events = pkt->getPktEvents();
+    ASSERT_FALSE(events.empty());
+    auto event = events.begin();
+    ASSERT_EQ((*event).label_, PktEvent::BUFFER_READ);
+    ++event;
+    ASSERT_EQ((*event).label_, PktEvent::RESPONSE_SENT);
+
+    // Discard the event stack contents.
+    pkt->clearPktEvents();
+    ASSERT_TRUE(pkt->getPktEvents().empty());
+
+    // Verify dumpPktEvent terse output. Also serves to
+    // verify adding events using struct timeval.
+    struct timeval log_time = {1706802676, 100};
+    struct timeval log_time_plus = {1706802676, 250};
+    pkt->addPktEvent("first-event", log_time);
+    pkt->addPktEvent("second-event", log_time_plus);
+    std::string log = pkt->dumpPktEvents();
+    EXPECT_EQ(log, "2024-Feb-01 15:51:16.000100 : first-event, 2024-Feb-01 15:51:16.000250 : second-event");
+
+    // Verify dumpPktEvent verbose output.
+    log = pkt->dumpPktEvents(true);
+    EXPECT_EQ(log,
+              "Event log: \n"
+              "2024-Feb-01 15:51:16.000100 : first-event\n"
+              "2024-Feb-01 15:51:16.000250 : second-event elapsed: 00:00:00.000150\n"
+              "total elapsed: 00:00:00.000150");
+}
+
 }  // namespace
index 6fb2651acc1cbff6589e1a6d6bd34dcda8748406..a86fc739f937bd23f59f174ee8d63b6a5932e57d 100644 (file)
@@ -15,6 +15,7 @@
 #include <sys/socket.h>
 
 using namespace isc::asiolink;
+using namespace boost::posix_time;
 
 namespace isc {
 namespace dhcp {
@@ -23,7 +24,8 @@ namespace test {
 PktFilter6Test::PktFilter6Test(const uint16_t port)
     : port_(port),
       sock_info_(isc::asiolink::IOAddress("::1"), port, -1, -1),
-      send_msg_sock_(-1) {
+      send_msg_sock_(-1),
+      start_time_(PktEvent::now()) {
     // Initialize ifname_ and ifindex_.
     loInit();
     // Initialize test_message_.
@@ -170,6 +172,21 @@ PktFilter6Test::testRcvdMessage(const Pkt6Ptr& rcvd_msg) const {
     EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid());
 }
 
+void
+PktFilter6Test::testPktEvents(const PktPtr& msg, ptime start_time,
+                             std::list<std::string> expected_events) const {
+    ASSERT_NE(start_time, PktEvent::EMPTY_TIME());
+    auto events = msg->getPktEvents();
+    ASSERT_EQ(events.size(), expected_events.size());
+    ptime prev_time = start_time;
+    auto expected_event = expected_events.begin();
+    for (const auto& event : events) {
+        ASSERT_EQ(event.label_, *expected_event);
+        EXPECT_GE(event.timestamp_, prev_time);
+        ++expected_event;
+    }
+}
+
 PktFilter6Stub::PktFilter6Stub()
     : open_socket_count_ (0) {
 }
@@ -198,7 +215,6 @@ PktFilter6Stub::send(const Iface&, uint16_t, const Pkt6Ptr&) {
     return (0);
 }
 
-
 } // end of isc::dhcp::test namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
index c005322ed4ee0780a5c12b776dc22e71f414a78f..ba4ecc68a14e873178812ebf89f6b81772a8f12c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2024 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
@@ -73,12 +73,24 @@ public:
     /// @param rcvd_msg An instance of the message to be tested.
     void testRcvdMessage(const Pkt6Ptr& rcvd_msg) const;
 
+    /// @brief Checks the contents of a packet's event stack agains a list
+    /// of expected events.
+    ///
+    /// @param msg pointer to the packet under test.
+    /// @param start_time system time prior to or equal to the timestamp
+    /// of the stack's first event (i.e. before packet was sent or received)
+    /// @param expected_events a list of the event labels in the order they
+    /// are expected to occur in the stack.
+    void testPktEvents(const PktPtr& msg, boost::posix_time::ptime start_time,
+                       std::list<std::string> expected_events) const;
+
     std::string ifname_;   ///< Loopback interface name.
     unsigned int ifindex_; ///< Loopback interface index.
     uint16_t port_;        ///< A port number used for the test.
     isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info.
     int send_msg_sock_;    ///< Holds a descriptor of the socket used by
                            ///< sendMessage function.
+    boost::posix_time::ptime start_time_; ///< System time at the start of the test.
     Pkt6Ptr test_message_; ///< A DHCPv6 message used by tests.
 
 };
index 164b0571f7a58652ef4d239c0a84b9a33a354067..0ecf70b5673b267d98331e17e79810a2ba6c5eb7 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2024 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
@@ -12,6 +12,7 @@
 #include <dhcp/protocol_util.h>
 #include <dhcp/tests/pkt_filter_test_utils.h>
 #include <util/buffer.h>
+#include <testutils/gtest_utils.h>
 
 #include <gtest/gtest.h>
 
@@ -46,19 +47,14 @@ TEST_F(PktFilterBPFTest, isDirectResponseSupported) {
     EXPECT_TRUE(pkt_filter.isDirectResponseSupported());
 }
 
-// All tests below require root privileges to execute successfully. If
-// they are run as non-root user they will fail due to insufficient privileges
-// to open raw network sockets. Therefore, they should remain disabled by default
-// and "DISABLED_" tags should not be removed. If one is willing to run these
-// tests please run "make check" as root and enable execution of disabled tests
-// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order
-// to run tests from this particular file, set the GTEST_FILTER environmental
-// variable to "PktFilterBPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS
-// setting.
+// All tests below require root privileges to execute successfully. If they
+// are run as non-root they will be skipped via SKIP_IF(notRoot()).
 
 // This test verifies that the raw AF_PACKET family socket can
 // be opened and bound to the specific interface.
-TEST_F(PktFilterBPFTest, DISABLED_openSocket) {
+TEST_F(PktFilterBPFTest, openSocket) {
+    SKIP_IF(notRoot());
+
     // Create object representing loopback interface.
     Iface iface(ifname_, ifindex_);
     iface.flag_loopback_ = true;
@@ -86,7 +82,9 @@ TEST_F(PktFilterBPFTest, DISABLED_openSocket) {
 // test over the real interface but since we don't know what interfaces
 // are present in the particular system we have to stick to local loopback
 // interface as this one is almost always present.
-TEST_F(PktFilterBPFTest, DISABLED_send) {
+TEST_F(PktFilterBPFTest, send) {
+    SKIP_IF(notRoot());
+
     // Packet will be sent over loopback interface.
     Iface iface(ifname_, ifindex_);
     iface.flag_loopback_ = true;
@@ -103,6 +101,9 @@ TEST_F(PktFilterBPFTest, DISABLED_send) {
     // Send the packet over the socket.
     ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
 
+    // Verify that we have only the RESPONSE_SENT event with a good timestamp.
+    testPktEvents(test_message_, start_time_, std::list<std::string>{PktEvent::RESPONSE_SENT});
+
     // Read the data from socket.
     fd_set readfds;
     FD_ZERO(&readfds);
@@ -163,7 +164,8 @@ TEST_F(PktFilterBPFTest, DISABLED_send) {
 
 // This test verifies correctness of reception of the DHCP packet over
 // raw socket, whereby all IP stack headers are hand-crafted.
-TEST_F(PktFilterBPFTest, DISABLED_receive) {
+TEST_F(PktFilterBPFTest, receive) {
+    SKIP_IF(notRoot());
 
     // Packet will be received over loopback interface.
     Iface iface(ifname_, ifindex_);
@@ -193,12 +195,18 @@ TEST_F(PktFilterBPFTest, DISABLED_receive) {
     // Check if the received message is correct.
     testRcvdMessage(rcvd_pkt);
     testRcvdMessageAddressPort(rcvd_pkt);
+
+    // Verify that the packet event stack has SOCKET_RECEIVED and BUFFER_READ events.
+    testPktEvents(rcvd_pkt, start_time_,
+                  std::list<std::string>{PktEvent::SOCKET_RECEIVED,
+                                         PktEvent::BUFFER_READ});
 }
 
 // This test verifies that if the packet is received over the raw
 // socket and its destination address doesn't match the address
 // to which the socket is "bound", the packet is dropped.
-TEST_F(PktFilterBPFTest, DISABLED_filterOutUnicast) {
+TEST_F(PktFilterBPFTest, filterOutUnicast) {
+    SKIP_IF(notRoot());
 
     // Packet will be received over loopback interface.
     Iface iface(ifname_, ifindex_);
index 77104dcaaed2657878872a4866b8aadb9785169c..17730ee6b99fa26365d3f81e019d98bc98f6b707 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2024 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
@@ -70,6 +70,9 @@ TEST_F(PktFilterInet6Test, send) {
     ASSERT_NO_THROW(result = pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
     ASSERT_EQ(0, result);
 
+    // Verify that we have only the RESPONSE_SENT event with a good timestamp.
+    testPktEvents(test_message_, start_time_, std::list<std::string>{PktEvent::RESPONSE_SENT});
+
     // Read the data from socket.
     fd_set readfds;
     FD_ZERO(&readfds);
@@ -96,7 +99,6 @@ TEST_F(PktFilterInet6Test, send) {
 
     // Check if the received message is correct.
     testRcvdMessage(rcvd_pkt);
-
 }
 
 // This test verifies that the DHCPv6 packet is correctly received via
@@ -129,6 +131,11 @@ TEST_F(PktFilterInet6Test, receive) {
 
     // Check if the received message is correct.
     testRcvdMessage(rcvd_pkt);
-    }
+
+    // Verify that the packet event stack has SOCKET_RECEIVED and BUFFER_READ events.
+    testPktEvents(rcvd_pkt, start_time_,
+                  std::list<std::string>{PktEvent::SOCKET_RECEIVED,
+                                         PktEvent::BUFFER_READ});
+}
 
 } // anonymous namespace
index a8d495ca8eeffd3dc9cc2f81680f3e4daab60266..9bd1c9b0e1a74c9e24844bfcd4789d6d08528167 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2024 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
@@ -84,6 +84,9 @@ TEST_F(PktFilterInetTest, send) {
     ASSERT_NO_THROW(result = pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
     ASSERT_EQ(0, result);
 
+    // Verify that we have only the RESPONSE_SENT event with a good timestamp.
+    testPktEvents(test_message_, start_time_, std::list<std::string>{PktEvent::RESPONSE_SENT});
+
     // Read the data from socket.
     fd_set readfds;
     FD_ZERO(&readfds);
@@ -143,6 +146,11 @@ TEST_F(PktFilterInetTest, receive) {
     // Check if the received message is correct.
     testRcvdMessage(rcvd_pkt);
     testRcvdMessageAddressPort(rcvd_pkt);
+
+    // Verify that the packet event stack has SOCKET_RECEIVED and BUFFER_READ events.
+    testPktEvents(rcvd_pkt, start_time_,
+                  std::list<std::string>{PktEvent::SOCKET_RECEIVED,
+                                         PktEvent::BUFFER_READ});
 }
 
 } // anonymous namespace
index 9471b336664138a8c7a23dc07af19db5c741d3db..39fd0a1ef031de21b323826d8ff26d07d0373285 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2024 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
@@ -12,6 +12,7 @@
 #include <dhcp/protocol_util.h>
 #include <dhcp/tests/pkt_filter_test_utils.h>
 #include <util/buffer.h>
+#include <testutils/gtest_utils.h>
 
 #include <gtest/gtest.h>
 
@@ -46,19 +47,14 @@ TEST_F(PktFilterLPFTest, isDirectResponseSupported) {
     EXPECT_TRUE(pkt_filter.isDirectResponseSupported());
 }
 
-// All tests below require root privileges to execute successfully. If
-// they are run as non-root user they will fail due to insufficient privileges
-// to open raw network sockets. Therefore, they should remain disabled by default
-// and "DISABLED_" tags should not be removed. If one is willing to run these
-// tests please run "make check" as root and enable execution of disabled tests
-// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order
-// to run tests from this particular file, set the GTEST_FILTER environmental
-// variable to "PktFilterLPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS
-// setting.
+// All tests below require root privileges to execute successfully. If they
+// are run as non-root they will be skipped via SKIP_IF(notRoot()).
 
 // This test verifies that the raw AF_PACKET family socket can
 // be opened and bound to the specific interface.
-TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
+TEST_F(PktFilterLPFTest, openSocket) {
+    SKIP_IF(notRoot());
+
     // Create object representing loopback interface.
     Iface iface(ifname_, ifindex_);
     // Set loopback address.
@@ -94,7 +90,9 @@ TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
 
 // This test verifies correctness of sending DHCP packet through the raw
 // socket, whereby all IP stack headers are hand-crafted.
-TEST_F(PktFilterLPFTest, DISABLED_send) {
+TEST_F(PktFilterLPFTest, send) {
+    SKIP_IF(notRoot());
+
     // Packet will be sent over loopback interface.
     Iface iface(ifname_, ifindex_);
     IOAddress addr("127.0.0.1");
@@ -112,6 +110,9 @@ TEST_F(PktFilterLPFTest, DISABLED_send) {
     // Send the packet over the socket.
     ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
 
+    // Verify that we have only the RESPONSE_SENT event with a good timestamp.
+    testPktEvents(test_message_, start_time_, std::list<std::string>{PktEvent::RESPONSE_SENT});
+
     // Read the data from socket.
     fd_set readfds;
     FD_ZERO(&readfds);
@@ -152,7 +153,8 @@ TEST_F(PktFilterLPFTest, DISABLED_send) {
 
 // This test verifies correctness of reception of the DHCP packet over
 // raw socket, whereby all IP stack headers are hand-crafted.
-TEST_F(PktFilterLPFTest, DISABLED_receive) {
+TEST_F(PktFilterLPFTest, receive) {
+    SKIP_IF(notRoot());
 
     // Packet will be received over loopback interface.
     Iface iface(ifname_, ifindex_);
@@ -180,12 +182,18 @@ TEST_F(PktFilterLPFTest, DISABLED_receive) {
     // Check if the received message is correct.
     testRcvdMessage(rcvd_pkt);
     testRcvdMessageAddressPort(rcvd_pkt);
+
+    // Verify that the packet event stack has SOCKET_RECEIVED and BUFFER_READ events.
+    testPktEvents(rcvd_pkt, start_time_,
+                  std::list<std::string>{PktEvent::SOCKET_RECEIVED,
+                                         PktEvent::BUFFER_READ});
 }
 
 // This test verifies that if the packet is received over the raw
 // socket and its destination address doesn't match the address
 // to which the socket is "bound", the packet is dropped.
-TEST_F(PktFilterLPFTest, DISABLED_filterOutUnicast) {
+TEST_F(PktFilterLPFTest, filterOutUnicast) {
+    SKIP_IF(notRoot());
 
     // Packet will be received over loopback interface.
     Iface iface(ifname_, ifindex_);
index 7b5a79ec561b483d231785e8ca5df896de58e2e7..8c29b0b604f7b523b8e1524f8ea95a05f66119db 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2024 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
@@ -10,6 +10,7 @@
 #include <dhcp/tests/pkt_filter_test_utils.h>
 
 using namespace isc::asiolink;
+using namespace boost::posix_time;
 
 namespace isc {
 namespace dhcp {
@@ -18,7 +19,8 @@ namespace test {
 PktFilterTest::PktFilterTest(const uint16_t port)
     : port_(port),
       sock_info_(isc::asiolink::IOAddress("127.0.0.1"), port, -1, -1),
-      send_msg_sock_(-1) {
+      send_msg_sock_(-1),
+      start_time_(PktEvent::now()) {
     // Initialize ifname_ and ifindex_.
     loInit();
     // Initialize test_message_.
@@ -168,6 +170,21 @@ PktFilterTest::testRcvdMessageAddressPort(const Pkt4Ptr& rcvd_msg) const {
     EXPECT_EQ(test_message_->getLocalPort(), rcvd_msg->getRemotePort());
 }
 
+void
+PktFilterTest::testPktEvents(const PktPtr& msg, ptime start_time,
+                             std::list<std::string> expected_events) const {
+    ASSERT_NE(start_time, PktEvent::EMPTY_TIME());
+    auto events = msg->getPktEvents();
+    ASSERT_EQ(events.size(), expected_events.size());
+    ptime prev_time = start_time;
+    auto expected_event = expected_events.begin();
+    for (const auto& event : events) {
+        ASSERT_EQ(event.label_, *expected_event);
+        EXPECT_GE(event.timestamp_, prev_time);
+        ++expected_event;
+    }
+}
+
 bool
 PktFilterStub::isDirectResponseSupported() const {
     return (true);
index 4be1d58df00b1072b6f434a61b820e7c6b56a265..a5d8838bb977e336683e871c4eb36aa1acdb610c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2024 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
@@ -82,12 +82,32 @@ public:
     /// @param rcvd_msg An instance of the message to be tested.
     void testRcvdMessageAddressPort(const Pkt4Ptr& rcvd_msg) const;
 
+    /// @brief Checks the contents of a packet's event stack agains a list
+    /// of expected events.
+    ///
+    /// @param msg pointer to the packet under test.
+    /// @param start_time system time prior to or equal to the timestamp
+    /// of the stack's first event (i.e. before packet was sent or received)
+    /// @param expected_events a list of the event labels in the order they
+    /// are expected to occur in the stack.
+    void testPktEvents(const PktPtr& msg, boost::posix_time::ptime start_time,
+                       std::list<std::string> expected_events) const;
+
+    /// @brief Indicates if current user is not root
+    ///
+    /// @return True if neither the uid or the effective
+    /// uid is root.
+    static bool notRoot() {
+        return (getuid() != 0 && geteuid() != 0);
+    }
+
     std::string ifname_;   ///< Loopback interface name
     unsigned int ifindex_; ///< Loopback interface index.
     uint16_t port_;        ///< A port number used for the test.
     isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info.
     int send_msg_sock_;    ///< Holds a descriptor of the socket used by
                            ///< sendMessage function.
+    boost::posix_time::ptime start_time_; ///< Test start time.
     Pkt4Ptr test_message_; ///< A DHCPv4 message used by tests.
 
 };
@@ -159,7 +179,6 @@ public:
 
     // Change the scope of the protected function so as they can be unit tested.
     using PktFilter::openFallbackSocket;
-
 };