From d9bedcec1bce8d15de6403377702d51d1bcb862f Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:11:00 +0200 Subject: Add NTP and DHCP L7 dissectors NTP decodes the fixed header's version/mode/stratum - the timestamp fields need NTP era/fraction fixed-point math to render meaningfully and add nothing a one-line summary needs, so they're left alone. DHCP decodes RFC 2131's BOOTP fixed header + magic cookie, then walks the TLV options bounds-safely for option 53 (message type), plus yiaddr when the server has assigned one. It's the first dissector needing two well-known ports (67 server, 68 client) rather than one; registering at just 67 still matches both directions since l7_summarize() already falls back from dst_port to src_port. Verified live: NTP against a real pool.ntp.org query on wlp1s0 (a genuine stratum-2 reply came back). DHCP over loopback with a synthetic-but-wire-format-real DISCOVER/OFFER exchange rather than a real lease renewal, to avoid disrupting this machine's actual network state - caught a test-setup mistake in the process (both packets sent from the same ephemeral port instead of the OFFER actually originating from port 67), not a dissector bug. --- tests/test_ntp.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/test_ntp.cpp (limited to 'tests/test_ntp.cpp') diff --git a/tests/test_ntp.cpp b/tests/test_ntp.cpp new file mode 100644 index 0000000..2c93302 --- /dev/null +++ b/tests/test_ntp.cpp @@ -0,0 +1,64 @@ +#include + +#include + +#include "packeteer/l7/ntp.hpp" + +using namespace packeteer::net; + +namespace { + +std::vector ntp_client_request() { + std::vector bytes(48, 0); + bytes[0] = (4 << 3) | 3; // version 4, mode 3 (client) + bytes[1] = 0; // stratum: not applicable on a client request + return bytes; +} + +std::vector ntp_server_reply() { + std::vector bytes(48, 0); + bytes[0] = (4 << 3) | 4; // version 4, mode 4 (server) + bytes[1] = 2; // stratum 2 + return bytes; +} + +} // namespace + +TEST_CASE("parse_ntp decodes a client request") { + auto ntp = parse_ntp(ntp_client_request()); + REQUIRE(ntp.has_value()); + CHECK(ntp->version == 4); + CHECK(ntp->mode == 3); +} + +TEST_CASE("parse_ntp decodes a server reply with its stratum") { + auto ntp = parse_ntp(ntp_server_reply()); + REQUIRE(ntp.has_value()); + CHECK(ntp->mode == 4); + CHECK(ntp->stratum == 2); +} + +TEST_CASE("parse_ntp rejects a payload shorter than the fixed header") { + std::vector bytes(20, 0); + CHECK_FALSE(parse_ntp(bytes).has_value()); +} + +TEST_CASE("NtpDissector claims port 123 and includes stratum for client/server modes") { + NtpDissector dissector; + CHECK(dissector.port() == kNtpPort); + + auto summary = dissector.summarize(ntp_server_reply()); + REQUIRE(summary.has_value()); + CHECK(*summary == "NTP v4 server stratum=2"); +} + +TEST_CASE("NtpDissector omits stratum for non-client/server modes") { + NtpDissector dissector; + std::vector bytes(48, 0); + bytes[0] = (4 << 3) | 5; // mode 5: broadcast + bytes[1] = 1; + + auto summary = dissector.summarize(bytes); + REQUIRE(summary.has_value()); + CHECK(summary->find("stratum") == std::string::npos); +} -- cgit v1.2.3