]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3531] Output fixed Pkt4 fields
authorThomas Markwalder <tmark@isc.org>
Tue, 19 Aug 2025 19:49:20 +0000 (15:49 -0400)
committerThomas Markwalder <tmark@isc.org>
Fri, 29 Aug 2025 12:58:42 +0000 (12:58 +0000)
/src/bin/dhcp4/dhcp4_messages.mes
    Updated mesage descriptions

/src/bin/dhcp4/dhcp4_srv.cc
    Add verbose=true to Pkt4::toText() calls

/src/lib/dhcp/pkt.h
    Pkt::toText() - added verbose flag

/src/lib/dhcp/pkt4.*
    Pkt4::toText()  - added verbose logic

/src/lib/dhcp/pkt6.*
    Pkt6::toText()  - added vebose parameter (unused)

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

/src/lib/util/str.*
    printOrDump() - new function

/src/lib/util/tests/str_unittests.cc
    TEST_F(StringUtilTest, printOrDump)  - new test

12 files changed:
changelog_unreleased/3531-how-can-i-log-the-pxe-boot-details-sent-to-my-clients [new file with mode: 0644]
src/bin/dhcp4/dhcp4_messages.mes
src/bin/dhcp4/dhcp4_srv.cc
src/lib/dhcp/pkt.h
src/lib/dhcp/pkt4.cc
src/lib/dhcp/pkt4.h
src/lib/dhcp/pkt6.cc
src/lib/dhcp/pkt6.h
src/lib/dhcp/tests/pkt4_unittest.cc
src/lib/util/str.cc
src/lib/util/str.h
src/lib/util/tests/str_unittests.cc

diff --git a/changelog_unreleased/3531-how-can-i-log-the-pxe-boot-details-sent-to-my-clients b/changelog_unreleased/3531-how-can-i-log-the-pxe-boot-details-sent-to-my-clients
new file mode 100644 (file)
index 0000000..48e7fb7
--- /dev/null
@@ -0,0 +1,5 @@
+[func]         tmark
+       Additional packet details are now emitted in
+       debug level logs by kea-dhcp4 for both inbound
+       and outbound packets.
+       (Gitlab #3531)
index a7b8c02d31816ec3c7330fcf0ea7bcbd620f6a1b..de0b4308e1c96a42a22e3f46fe630dad19617061 100644 (file)
@@ -906,7 +906,8 @@ initially selected for allocation) from the same shared network.
 Logged at debug log level 55.
 A debug message printing the details of the received packet. The first
 argument includes the client and the transaction identification
-information.
+information. Packet fields ciaddr, yiaddr, siaddr, giaddr, sname, and file
+will be included not empty.
 
 % DHCP4_QUERY_LABEL received query: %1
 This information message indicates that a query was received. It displays
@@ -1013,7 +1014,8 @@ A debug message including the detailed data about the packet being sent
 to the client. The first argument contains the client and the transaction
 identification information. The second and third argument contains the
 packet name and type respectively. The fourth argument contains detailed
-packet information.
+packet information. Packet fields ciaddr, yiaddr, siaddr, giaddr, sname,
+and file will be included not empty.
 
 % DHCP4_RESPONSE_FQDN_DATA %1: including FQDN option in the server's response: %2
 Logged at debug log level 55.
index 9224ccdfd473be3a74313ba8cb2563f155ed1036..aa6fe8d9bfc465d0efc15b8871cf2e0c31f3e26c 100644 (file)
@@ -1462,7 +1462,7 @@ Dhcpv4Srv::processPacket(Pkt4Ptr query, bool allow_answer_park) {
         .arg(query->getIface());
     LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
         .arg(query->getLabel())
-        .arg(query->toText());
+        .arg(query->toText(true));
 
     // Let's execute all callouts registered for pkt4_receive
     if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_receive_)) {
@@ -2017,7 +2017,7 @@ Dhcpv4Srv::processPacketBufferSend(CalloutHandlePtr& callout_handle,
             .arg(rsp->getLabel())
             .arg(rsp->getName())
             .arg(static_cast<int>(rsp->getType()))
-            .arg(rsp->toText());
+            .arg(rsp->toText(true));
         sendPacket(rsp);
 
         // Update statistics accordingly for sent packet.
index 2c698763cd4b08fd5a6882db4dd225f8e76034c3..d35666a0f6d06013e71d8fe8a5b9bd5557171460 100644 (file)
@@ -283,9 +283,10 @@ public:
     /// @note This is a pure virtual method and must be implemented in
     /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective
     /// implementations of this method.
+    /// @param verbose output most if not all members.
     ///
     /// @return string with text representation
-    virtual std::string toText() const = 0;
+    virtual std::string toText(bool verbose = false) const = 0;
 
     /// @brief Returns packet size in binary format.
     ///
index 88bbbce5d21187d1b73a3e9a5873304ee7e98510..131ad8c5a65ccf7463e65e9f195ebe9b98016a1d 100644 (file)
@@ -10,6 +10,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option_int.h>
 #include <dhcp/pkt4.h>
+#include <util/str.h>
 #include <exceptions/exceptions.h>
 
 #include <algorithm>
@@ -19,6 +20,7 @@
 using namespace std;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
+using namespace isc::util::str;
 
 namespace {
 
@@ -417,7 +419,7 @@ Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id) {
 }
 
 std::string
-Pkt4::toText() const {
+Pkt4::toText(bool verbose /* = false */) const {
     stringstream tmp;
 
     // First print the basics
@@ -437,6 +439,36 @@ Pkt4::toText() const {
 
     tmp << ", trans_id=0x" << hex << transid_ << dec;
 
+    if (verbose) {
+        tmp << ", secs=" << secs_;
+        tmp << ", flags=0x" << hex << flags_;
+        if (!ciaddr_.isV4Zero()) {
+            tmp << ", ciaddr=" << ciaddr_.toText();
+        }
+
+        if (!yiaddr_.isV4Zero()) {
+            tmp << ", yiaddr=" << yiaddr_.toText();
+        }
+
+        if (!siaddr_.isV4Zero()) {
+            tmp << ", siaddr=" << siaddr_.toText();
+        }
+
+        if (!giaddr_.isV4Zero()) {
+            tmp << ", giaddr=" << giaddr_.toText();
+        }
+
+        auto sname_dump = printOrDump(getSname(), 32);
+        if (!sname_dump.empty()) {
+            tmp << ", sname=[" << sname_dump << "]";
+        }
+
+        auto file_dump = printOrDump(getFile(), 32);
+        if (!file_dump.empty()) {
+            tmp << ", file=[" << file_dump << "]";
+        }
+    }
+
     if (!options_.empty()) {
         tmp << "," << endl << "options:";
         for (auto const& opt : options_) {
index 7ede3889ba1b6c2aa3a8c7e9ec234cc04c27421c..0b555706e1ba764a08c4c8d711eaad341cc8a15e 100644 (file)
@@ -125,9 +125,10 @@ public:
     /// @brief Returns text representation of the packet.
     ///
     /// This function is useful mainly for debugging.
-    ///
+    /// @param verbose output includes secs, flags and if they are populated: 
+    /// ciaddr, yiaddr, siaddr, giaddr, sname, and file
     /// @return string with text representation
-    std::string toText() const;
+    std::string toText(bool verbose = false) const;
 
     /// @brief Returns the size of the required buffer to build the packet.
     ///
index 286a27405d54d502954745cd78984aade0b483b0..999c4a5304c827afcc4cc7a50954f6ec5ca407ba 100644 (file)
@@ -725,7 +725,7 @@ Pkt6::getLabel() const {
     return (makeLabel(getClientId(), getTransid(), HWAddrPtr()));}
 
 std::string
-Pkt6::toText() const {
+Pkt6::toText(bool /* verbose = false */) const {
     stringstream tmp;
 
     // First print the basics
index a29ab2a63c930eefe8b1451c603547c6aa32b779..95c15c7d6c719ae7e4fcb4443a9cf2cf00d850d6 100644 (file)
@@ -199,8 +199,11 @@ public:
     ///
     /// This function is useful mainly for debugging.
     ///
+    /// @param verbose output most if not all members. Not currently
+    /// used for v6.
+    ///
     /// @return string with text representation
-    virtual std::string toText() const;
+    virtual std::string toText(bool verbose = false) const;
 
     /// @brief Returns length of the packet.
     ///
index 80920291b2a3cdd2ed9731fa506709d55779e20e..84f1e81160ad6e55cfbc8181bfc18bfabe5e1794 100644 (file)
@@ -19,6 +19,7 @@
 #include <exceptions/exceptions.h>
 #include <testutils/gtest_utils.h>
 #include <util/buffer.h>
+#include <util/str.h>
 #include <util/encode/encode.h>
 
 #include <boost/shared_array.hpp>
@@ -1336,6 +1337,83 @@ TEST_F(Pkt4Test, toText) {
 
 }
 
+// This test checks that the packet data are correctly converted to the
+// textual format.
+TEST_F(Pkt4Test, toTextVerbose) {
+    Pkt4 pkt(DHCPDISCOVER, 2543);
+    pkt.setLocalAddr(IOAddress("192.0.2.34"));
+    pkt.setRemoteAddr(IOAddress("192.10.33.4"));
+
+    pkt.addOption(OptionPtr(new Option4AddrLst(123, IOAddress("192.0.2.3"))));
+    pkt.addOption(OptionPtr(new OptionUint32(Option::V4, 156, 123456)));
+    pkt.addOption(OptionPtr(new OptionString(Option::V4, 87, "lorem ipsum")));
+    OptionBuffer data = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 };
+    OptionPtr opt(new Option(Option::V4, 231, data));
+    pkt.addOption(opt);
+    OptionBuffer data_sub = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 };
+    OptionPtr sub_opt(new Option(Option::V4, 1, data_sub));
+    opt->addOption(sub_opt);
+    data_sub.clear();
+    sub_opt.reset(new Option(Option::V4, 2, data_sub));
+    opt->addOption(sub_opt);
+
+    // Set verbose true but with verbose members not set.
+    EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68,\n"
+              "msg_type=DHCPDISCOVER (1), trans_id=0x9ef, secs=0, flags=0x0,\n"
+              "options:\n"
+              "  type=053, len=001: 1 (uint8)\n"
+              "  type=087, len=011: \"lorem ipsum\" (string)\n"
+              "  type=123, len=004: 192.0.2.3\n"
+              "  type=156, len=004: 123456 (uint32)\n"
+              "  type=231, len=020: 61:62:63:64:65:66 'abcdef',\n"
+              "options:\n"
+              "    type=001, len=010: 30:31:32:33:34:35:36:37:38:39 '0123456789'\n"
+              "    type=002, len=000: ''",
+              pkt.toText(true));
+
+    // Now populate verbose members.
+    pkt.setSecs(2);
+    pkt.setFlags(0x80);
+    pkt.setCiaddr(IOAddress("1.1.1.1"));
+    pkt.setYiaddr(IOAddress("2.2.2.2"));
+    pkt.setSiaddr(IOAddress("3.3.3.3"));
+    pkt.setGiaddr(IOAddress("4.4.4.4"));
+
+    std::vector<uint8_t> sname = { 'x', 'y', 'z' };
+    std::vector<uint8_t> file = { 'A', 'B', 'C' };
+    pkt.setSname(sname.data(), 3);
+    pkt.setFile(file.data(), 3);
+
+    // Verbose defaulting to false.
+    EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68,\n"
+              "msg_type=DHCPDISCOVER (1), trans_id=0x9ef,\n"
+              "options:\n"
+              "  type=053, len=001: 1 (uint8)\n"
+              "  type=087, len=011: \"lorem ipsum\" (string)\n"
+              "  type=123, len=004: 192.0.2.3\n"
+              "  type=156, len=004: 123456 (uint32)\n"
+              "  type=231, len=020: 61:62:63:64:65:66 'abcdef',\n"
+              "options:\n"
+              "    type=001, len=010: 30:31:32:33:34:35:36:37:38:39 '0123456789'\n"
+              "    type=002, len=000: ''",
+              pkt.toText());
+
+    // Set verbose true.
+    EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68,\n"
+              "msg_type=DHCPDISCOVER (1), trans_id=0x9ef, secs=2, flags=0x80, ciaddr=1.1.1.1,"
+              " yiaddr=2.2.2.2, siaddr=3.3.3.3, giaddr=4.4.4.4, sname=[xyz], file=[ABC],\n"
+              "options:\n"
+              "  type=053, len=001: 1 (uint8)\n"
+              "  type=087, len=011: \"lorem ipsum\" (string)\n"
+              "  type=123, len=004: 192.0.2.3\n"
+              "  type=156, len=004: 123456 (uint32)\n"
+              "  type=231, len=020: 61:62:63:64:65:66 'abcdef',\n"
+              "options:\n"
+              "    type=001, len=010: 30:31:32:33:34:35:36:37:38:39 '0123456789'\n"
+              "    type=002, len=000: ''",
+              pkt.toText(true));
+}
+
 // Sanity check. Verifies that the getName() and getType()
 // don't throw.
 TEST_F(Pkt4Test, getType) {
index 0bb7e8f67fd09f5e9368fb7ec5291234580d4e15..843c135fb3840eac3a58cd2a294761d20ec2f495 100644 (file)
@@ -347,6 +347,35 @@ dumpDouble(double val, size_t precision) {
     return (oss.str());
 }
 
+std::string 
+printOrDump(const std::vector<uint8_t>& data, size_t max_dump) {
+    auto it = data.begin();
+    bool print_it = true;
+    while (it != data.end() && *it != 0) {
+        if (!isprint(*it)) {
+            print_it = false;
+            break;
+        }
+
+        ++it;
+    }
+
+    if (print_it && it != data.begin()) {
+        return (std::string(data.begin(), it));
+    }
+
+    bool zeros = std::all_of(data.begin(), data.end(), [](int i) { return i==0; });
+    if (!zeros) {
+        if (data.size() > max_dump) {
+           return (std::string(dumpAsHex(&data[0], max_dump) + std::string("..")));
+        }
+
+        return (dumpAsHex(&data[0], data.size()));
+    }
+
+    return ("");
+}
+
 }  // namespace str
 }  // namespace util
 }  // namespace isc
index 4e817d502359275ddaa40c558a6d76fbe9ce73f8..0e2a8549ebf70872b290fade61dfa6f1ecccb581 100644 (file)
@@ -293,6 +293,21 @@ dumpAsHex(const uint8_t* data, size_t length);
 /// @return string representation of val
 std::string dumpDouble(double val, size_t precision = 5);
 
+/// @brief Outputs the contents of a vector as either a string printable
+/// characters or a hex dump.
+///
+/// Output of as string if all characters upto the first 0x0 are printable
+/// characters, otherwise as a string of hex digits limited to max_dump
+/// number of input bytes.  If the vector is empty or all zeros it returns
+/// an empty string. If the vector size exceeds max_dump when dumping as hex
+/// the output will be suffixed with "..".
+///
+/// @param data vector to be output
+/// @param max_dump maximum number of bytes to include when dumping as hex
+/// @return output string
+std::string
+printOrDump(const std::vector<uint8_t>& data, size_t max_dump);
+
 }  // namespace str
 }  // namespace util
 }  // namespace isc
index 78d28ce569795108f19af7f1d5a9c8aae099f002..e1a87bb8f9dcb278703339d3f9792ff012449979 100644 (file)
@@ -16,6 +16,7 @@
 #include <string>
 #include <unordered_set>
 #include <vector>
+#include <list>
 
 #include <gtest/gtest.h>
 
@@ -526,4 +527,52 @@ TEST_F(StringUtilTest, vectorIsPrintable) {
     EXPECT_FALSE(isPrintable(content));
 }
 
+// Verifies the printOrDUmp operates correctly.
+TEST_F(StringUtilTest, printOrDump) {
+    struct Scenario {
+        size_t line_;
+        std::vector<uint8_t> input_;
+        size_t max_length_;
+        std::string exp_output_;
+    };
+
+    std::list<Scenario> scenarios {
+        {
+            __LINE__,
+            { '1', '2', '3', 0, 0 },
+            5,
+            "123",
+        },
+        {
+            __LINE__,
+            { '4', '5', '6' },
+            3,
+            "456"
+        },
+        {
+            __LINE__,
+            { 0 , 0, 0, 0},
+            4,
+            ""
+        },
+        {
+            __LINE__,
+            { 0 , 1, 2, 3, 0},
+            5,
+            "00:01:02:03:00"
+        },
+        {
+            __LINE__,
+            { 0 , 1, 2, 3, 0},
+            3,
+            "00:01:02.."
+        }
+    };
+
+    for ( auto const& scenario : scenarios ) {
+        EXPECT_EQ(printOrDump(scenario.input_, scenario.max_length_),
+                              scenario.exp_output_);
+    }
+}
+
 }  // namespace