#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)
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
-// 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
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
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
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
-// 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
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
close(sock);
throw;
}
+
return (SocketInfo(addr, port, sock, fallback));
}
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);
}
<< strerror(errno));
}
+ pkt->addPktEvent(PktEvent::RESPONSE_SENT);
return (0);
}
-// 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
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,
<< " 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.
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
// 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);
}
(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);
}
pkt->updateTimestamp();
+ pkt->addPktEvent(PktEvent::RESPONSE_SENT);
+
int result = sendmsg(sockfd, &m, 0);
if (result < 0) {
isc_throw(SocketWriteError, "pkt4 send failed: sendmsg() returned "
-// 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
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,
}
#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,
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));
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) {
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,
pkt->updateTimestamp();
+ pkt->addPktEvent(PktEvent::RESPONSE_SENT);
+
int result = sendmsg(sockfd, &m, 0);
if (result < 0) {
isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned"
-// 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
#include <linux/if_ether.h>
#include <linux/if_packet.h>
+#define WITH_CMSG
+
namespace {
using namespace isc::dhcp;
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
<< " 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;
}
+#ifndef WITH_CMSG
Pkt4Ptr
PktFilterLPF::receive(Iface& iface, const SocketInfo& socket_info) {
uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
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) {
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));
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
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
#include <sys/socket.h>
using namespace isc::asiolink;
+using namespace boost::posix_time;
namespace isc {
namespace dhcp {
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_.
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) {
}
return (0);
}
-
} // end of isc::dhcp::test namespace
} // end of isc::dhcp namespace
} // end of isc namespace
-// 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
/// @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.
};
-// 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
#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>
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;
// 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;
// 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);
// 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_);
// 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_);
-// 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
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);
// Check if the received message is correct.
testRcvdMessage(rcvd_pkt);
-
}
// This test verifies that the DHCPv6 packet is correctly received via
// 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
-// 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
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);
// 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
-// 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
#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>
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.
// 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");
// 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);
// 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_);
// 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_);
-// 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
#include <dhcp/tests/pkt_filter_test_utils.h>
using namespace isc::asiolink;
+using namespace boost::posix_time;
namespace isc {
namespace dhcp {
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_.
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);
-// 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
/// @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.
};
// Change the scope of the protected function so as they can be unit tested.
using PktFilter::openFallbackSocket;
-
};