#include #include #include #include #include "packeteer/l7/dhcp.hpp" using namespace packeteer::net; namespace { // Builds a minimal BOOTP fixed header + magic cookie + a message-type // option (53), optionally with yiaddr set. std::vector dhcp_message(std::uint8_t op, std::uint8_t message_type, std::array yiaddr = {0, 0, 0, 0}) { std::vector bytes(240, 0); bytes[0] = op; std::copy(yiaddr.begin(), yiaddr.end(), bytes.begin() + 16); bytes[236] = 0x63; bytes[237] = 0x82; bytes[238] = 0x53; bytes[239] = 0x63; // Option 53 (message type), length 1, then End. bytes.push_back(53); bytes.push_back(1); bytes.push_back(message_type); bytes.push_back(0xFF); return bytes; } } // namespace TEST_CASE("parse_dhcp decodes a DISCOVER") { auto msg = parse_dhcp(dhcp_message(1, kDhcpDiscover)); REQUIRE(msg.has_value()); CHECK(msg->op == 1); REQUIRE(msg->message_type.has_value()); CHECK(*msg->message_type == kDhcpDiscover); } TEST_CASE("parse_dhcp decodes an OFFER with yiaddr set") { auto msg = parse_dhcp(dhcp_message(2, kDhcpOffer, {192, 168, 1, 50})); REQUIRE(msg.has_value()); REQUIRE(msg->message_type.has_value()); CHECK(*msg->message_type == kDhcpOffer); CHECK(msg->yiaddr.bytes == std::array{192, 168, 1, 50}); } TEST_CASE("parse_dhcp rejects a payload without the magic cookie") { std::vector bytes(240, 0); // right size, but cookie bytes are zero CHECK_FALSE(parse_dhcp(bytes).has_value()); } TEST_CASE("parse_dhcp rejects a payload shorter than the fixed header + cookie") { std::vector bytes(100, 0); CHECK_FALSE(parse_dhcp(bytes).has_value()); } TEST_CASE("parse_dhcp handles a message with no options gracefully") { std::vector bytes(240, 0); bytes[0] = 1; bytes[236] = 0x63; bytes[237] = 0x82; bytes[238] = 0x53; bytes[239] = 0x63; auto msg = parse_dhcp(bytes); REQUIRE(msg.has_value()); CHECK_FALSE(msg->message_type.has_value()); } TEST_CASE("parse_dhcp skips pad options while scanning for message type") { std::vector bytes(240, 0); bytes[236] = 0x63; bytes[237] = 0x82; bytes[238] = 0x53; bytes[239] = 0x63; bytes.push_back(0x00); // pad bytes.push_back(0x00); // pad bytes.push_back(53); bytes.push_back(1); bytes.push_back(kDhcpAck); bytes.push_back(0xFF); auto msg = parse_dhcp(bytes); REQUIRE(msg.has_value()); REQUIRE(msg->message_type.has_value()); CHECK(*msg->message_type == kDhcpAck); } TEST_CASE("DhcpDissector claims port 67 and names message types") { DhcpDissector dissector; CHECK(dissector.port() == kDhcpServerPort); auto summary = dissector.summarize(dhcp_message(2, kDhcpOffer, {192, 168, 1, 50})); REQUIRE(summary.has_value()); CHECK(*summary == "DHCP OFFER yiaddr=192.168.1.50"); } TEST_CASE("DhcpDissector omits yiaddr when it's all-zero") { DhcpDissector dissector; auto summary = dissector.summarize(dhcp_message(1, kDhcpDiscover)); REQUIRE(summary.has_value()); CHECK(*summary == "DHCP DISCOVER"); } TEST_CASE("DhcpDissector falls back to op when message type is absent") { DhcpDissector dissector; std::vector bytes(240, 0); bytes[0] = 1; bytes[236] = 0x63; bytes[237] = 0x82; bytes[238] = 0x53; bytes[239] = 0x63; auto summary = dissector.summarize(bytes); REQUIRE(summary.has_value()); CHECK(*summary == "DHCP request"); }