summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/tests/pkt4_unittest.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/dhcp/tests/pkt4_unittest.cc
parentInitial commit. (diff)
downloadisc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz
isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcp/tests/pkt4_unittest.cc')
-rw-r--r--src/lib/dhcp/tests/pkt4_unittest.cc1529
1 files changed, 1529 insertions, 0 deletions
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
new file mode 100644
index 0000000..70bc624
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -0,0 +1,1529 @@
+// Copyright (C) 2011-2023 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/pkt4.h>
+#include <exceptions/exceptions.h>
+#include <testutils/gtest_utils.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <pkt_captures.h>
+
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/static_assert.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+// Don't import the entire boost namespace. It will unexpectedly hide uint8_t
+// for some systems.
+using boost::scoped_ptr;
+
+namespace {
+
+/// V4 Options being used for pack/unpack testing.
+/// For test simplicity, all selected options have
+/// variable length data so as there are no restrictions
+/// on a length of their data.
+static uint8_t v4_opts[] = {
+ 53, 1, 2, // Message Type (required to not throw exception during unpack)
+ 12, 3, 0, 1, 2, // Hostname
+ 14, 3, 10, 11, 12, // Merit Dump File
+ 60, 3, 20, 21, 22, // Class Id
+ 128, 3, 30, 31, 32, // Vendor specific
+ 254, 3, 40, 41, 42, // Reserved
+};
+
+// Sample data
+const uint8_t dummyOp = BOOTREQUEST;
+const uint8_t dummyHtype = 6;
+const uint8_t dummyHlen = 6;
+const uint8_t dummyHops = 13;
+const uint32_t dummyTransid = 0x12345678;
+const uint16_t dummySecs = 42;
+const uint16_t dummyFlags = BOOTP_BROADCAST;
+
+const IOAddress dummyCiaddr("192.0.2.1");
+const IOAddress dummyYiaddr("1.2.3.4");
+const IOAddress dummySiaddr("192.0.2.255");
+const IOAddress dummyGiaddr("255.255.255.255");
+
+// a dummy MAC address
+const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
+
+// A dummy MAC address, padded with 0s
+const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+// Let's use some creative test content here (128 chars + \0)
+const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit. Proin mollis placerat metus, at "
+ "lacinia orci ornare vitae. Mauris amet.";
+
+// Yet another type of test content (64 chars + \0)
+const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit posuere.";
+
+BOOST_STATIC_ASSERT(sizeof(dummyFile) == Pkt4::MAX_FILE_LEN + 1);
+BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1);
+
+
+class Pkt4Test : public ::testing::Test {
+public:
+ Pkt4Test() {
+ }
+
+ /// @brief Generates test packet.
+ ///
+ /// Allocates and generates test packet, with all fixed fields set to non-zero
+ /// values. Content is not always reasonable.
+ ///
+ /// See generateTestPacket2() function that returns exactly the same packet in
+ /// on-wire format.
+ ///
+ /// @return pointer to allocated Pkt4 object.
+ Pkt4Ptr generateTestPacket1() {
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, dummyTransid));
+
+ vector<uint8_t> vectorMacAddr(dummyMacAddr, dummyMacAddr
+ + sizeof(dummyMacAddr));
+
+ // hwType = 6(ETHERNET), hlen = 6(MAC address len)
+ pkt->setHWAddr(dummyHtype, dummyHlen, vectorMacAddr);
+ pkt->setHops(dummyHops); // 13 relays. Wow!
+ // Transaction-id is already set.
+ pkt->setSecs(dummySecs);
+ pkt->setFlags(dummyFlags); // all flags set
+ pkt->setCiaddr(dummyCiaddr);
+ pkt->setYiaddr(dummyYiaddr);
+ pkt->setSiaddr(dummySiaddr);
+ pkt->setGiaddr(dummyGiaddr);
+ // Chaddr already set with setHWAddr().
+ pkt->setSname(dummySname, 64);
+ pkt->setFile(dummyFile, 128);
+
+ return (pkt);
+ }
+
+ /// @brief Generates test packet.
+ ///
+ /// Allocates and generates on-wire buffer that represents test packet, with all
+ /// fixed fields set to non-zero values. Content is not always reasonable.
+ ///
+ /// See generateTestPacket1() function that returns exactly the same packet as
+ /// Pkt4 object.
+ ///
+ /// @return pointer to allocated Pkt4 object
+ // Returns a vector containing a DHCPv4 packet header.
+ vector<uint8_t> generateTestPacket2() {
+
+ // That is only part of the header. It contains all "short" fields,
+ // larger fields are constructed separately.
+ uint8_t hdr[] = {
+ 1, 6, 6, 13, // op, htype, hlen, hops,
+ 0x12, 0x34, 0x56, 0x78, // transaction-id
+ 0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
+ 192, 0, 2, 1, // ciaddr
+ 1, 2, 3, 4, // yiaddr
+ 192, 0, 2, 255, // siaddr
+ 255, 255, 255, 255, // giaddr
+ };
+
+ // Initialize the vector with the header fields defined above.
+ vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+
+ // Append the large header fields.
+ copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
+ copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
+ copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
+
+ // Should now have all the header, so check. The "static_cast" is used
+ // to get round an odd bug whereby the linker appears not to find the
+ // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
+
+ return (buf);
+ }
+
+ /// @brief Verify that the options are correct after parsing.
+ ///
+ /// @param pkt A packet holding parsed options.
+ void verifyParsedOptions(const Pkt4Ptr& pkt) {
+ EXPECT_TRUE(pkt->getOption(12));
+ EXPECT_TRUE(pkt->getOption(60));
+ EXPECT_TRUE(pkt->getOption(14));
+ EXPECT_TRUE(pkt->getOption(128));
+ EXPECT_TRUE(pkt->getOption(254));
+
+ // Verify the packet type is correct.
+ ASSERT_EQ(DHCPOFFER, pkt->getType());
+
+ // First option after message type starts at 3.
+ uint8_t *opt_data_ptr = v4_opts + 3;
+
+ // Option 12 is represented by the OptionString class so let's do
+ // the appropriate conversion.
+ boost::shared_ptr<Option> x = pkt->getOption(12);
+ ASSERT_TRUE(x); // option 1 should exist
+ OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
+
+ ASSERT_TRUE(option12);
+ EXPECT_EQ(12, option12->getType()); // this should be option 12
+ ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option12->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option12->getValue()[0], opt_data_ptr + 2, 2)); // data len=3
+ opt_data_ptr += x->len();
+
+ x = pkt->getOption(14);
+ ASSERT_TRUE(x); // option 14 should exist
+ // Option 14 is represented by the OptionString class so let's do
+ // the appropriate conversion.
+ OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x);
+ ASSERT_TRUE(option14);
+ EXPECT_EQ(14, option14->getType()); // this should be option 14
+ ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option14->len()); // total option length 5
+
+ EXPECT_EQ(0, memcmp(&option14->getValue()[0], opt_data_ptr + 2, 3)); // data len=3
+ opt_data_ptr += x->len();
+
+ x = pkt->getOption(60);
+ ASSERT_TRUE(x); // option 60 should exist
+ EXPECT_EQ(60, x->getType()); // this should be option 60
+ ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3
+ opt_data_ptr += x->len();
+
+ x = pkt->getOption(128);
+ ASSERT_TRUE(x); // option 3 should exist
+ EXPECT_EQ(128, x->getType()); // this should be option 254
+ ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3
+ opt_data_ptr += x->len();
+
+ x = pkt->getOption(254);
+ ASSERT_TRUE(x); // option 3 should exist
+ EXPECT_EQ(254, x->getType()); // this should be option 254
+ ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3
+ }
+
+};
+
+
+TEST_F(Pkt4Test, constructor) {
+
+ ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
+ scoped_ptr<Pkt4> pkt;
+
+ // Just some dummy payload.
+ uint8_t testData[250];
+ for (uint8_t i = 0; i < 250; i++) {
+ testData[i] = i;
+ }
+
+ // Positive case1. Normal received packet.
+ EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
+
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+
+ EXPECT_NO_THROW(pkt.reset());
+
+ // Positive case2. Normal outgoing packet.
+ EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
+
+ // DHCPv4 packet must be at least 236 bytes long, with Message Type
+ // Option taking extra 3 bytes it is 239
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+ EXPECT_EQ(0xffffffff, pkt->getTransid());
+ EXPECT_NO_THROW(pkt.reset());
+
+ // Negative case. Should drop truncated messages.
+ EXPECT_THROW(
+ pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
+ OutOfRange
+ );
+}
+
+
+TEST_F(Pkt4Test, fixedFields) {
+
+ boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
+
+ // OK, let's check packet values
+ EXPECT_EQ(dummyOp, pkt->getOp());
+ EXPECT_EQ(dummyHtype, pkt->getHtype());
+ EXPECT_EQ(dummyHlen, pkt->getHlen());
+ EXPECT_EQ(dummyHops, pkt->getHops());
+ EXPECT_EQ(dummyTransid, pkt->getTransid());
+ EXPECT_EQ(dummySecs, pkt->getSecs());
+ EXPECT_EQ(dummyFlags, pkt->getFlags());
+
+ EXPECT_EQ(dummyCiaddr, pkt->getCiaddr());
+ EXPECT_EQ(dummyYiaddr, pkt->getYiaddr());
+ EXPECT_EQ(dummySiaddr, pkt->getSiaddr());
+ EXPECT_EQ(dummyGiaddr, pkt->getGiaddr());
+
+ // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes
+ // long and its length depends on hlen value (it is up to 16 bytes now).
+ ASSERT_EQ(pkt->getHWAddr()->hwaddr_.size(), dummyHlen);
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
+
+ EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], 64));
+
+ EXPECT_EQ(0, memcmp(dummyFile, &pkt->getFile()[0], 128));
+
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+}
+
+TEST_F(Pkt4Test, fixedFieldsPack) {
+ boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
+ vector<uint8_t> expectedFormat = generateTestPacket2();
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ // Minimum packet size is 236 bytes + 3 bytes of mandatory
+ // DHCP Message Type Option
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
+
+ // Redundant but MUCH easier for debug in gdb
+ const uint8_t* exp = &expectedFormat[0];
+ const uint8_t* got = static_cast<const uint8_t*>(pkt->getBuffer().getData());
+
+ EXPECT_EQ(0, memcmp(exp, got, Pkt4::DHCPV4_PKT_HDR_LEN));
+}
+
+/// TODO Uncomment when ticket #1226 is implemented
+TEST_F(Pkt4Test, fixedFieldsUnpack) {
+ vector<uint8_t> expectedFormat = generateTestPacket2();
+
+ expectedFormat.push_back(0x63); // magic cookie
+ expectedFormat.push_back(0x82);
+ expectedFormat.push_back(0x53);
+ expectedFormat.push_back(0x63);
+
+ expectedFormat.push_back(0x35); // message-type
+ expectedFormat.push_back(0x1);
+ expectedFormat.push_back(0x1);
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0],
+ expectedFormat.size()));;
+
+
+ EXPECT_NO_THROW(
+ pkt->unpack()
+ );
+
+ // OK, let's check packet values
+ EXPECT_EQ(dummyOp, pkt->getOp());
+ EXPECT_EQ(dummyHtype, pkt->getHtype());
+ EXPECT_EQ(dummyHlen, pkt->getHlen());
+ EXPECT_EQ(dummyHops, pkt->getHops());
+ EXPECT_EQ(dummyTransid, pkt->getTransid());
+ EXPECT_EQ(dummySecs, pkt->getSecs());
+ EXPECT_EQ(dummyFlags, pkt->getFlags());
+
+ EXPECT_EQ(dummyCiaddr, pkt->getCiaddr());
+ EXPECT_EQ("1.2.3.4", pkt->getYiaddr().toText());
+ EXPECT_EQ("192.0.2.255", pkt->getSiaddr().toText());
+ EXPECT_EQ("255.255.255.255", pkt->getGiaddr().toText());
+
+ // chaddr is always 16 bytes long and contains link-layer addr (MAC)
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
+
+ ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_SNAME_LEN), pkt->getSname().size());
+ EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
+
+ ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_FILE_LEN), pkt->getFile().size());
+ EXPECT_EQ(0, memcmp(dummyFile, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN));
+
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+}
+
+// This test is for hardware addresses (htype, hlen and chaddr fields)
+TEST_F(Pkt4Test, hwAddr) {
+
+ vector<uint8_t> mac;
+ uint8_t expectedChaddr[Pkt4::MAX_CHADDR_LEN];
+
+ // We resize vector to specified length. It is more natural for fixed-length
+ // field, than clear it (shrink size to 0) and push_back each element
+ // (growing length back to MAX_CHADDR_LEN).
+ mac.resize(Pkt4::MAX_CHADDR_LEN);
+
+ scoped_ptr<Pkt4> pkt;
+ // let's test each hlen, from 0 till 16
+ for (size_t macLen = 0; macLen < Pkt4::MAX_CHADDR_LEN; macLen++) {
+ for (size_t i = 0; i < Pkt4::MAX_CHADDR_LEN; i++) {
+ mac[i] = 0;
+ expectedChaddr[i] = 0;
+ }
+ for (size_t i = 0; i < macLen; i++) {
+ mac[i] = 128 + i;
+ expectedChaddr[i] = 128 + i;
+ }
+
+ // type and transaction doesn't matter in this test
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
+ pkt->setHWAddr(255 - macLen * 10, // just weird htype
+ macLen,
+ mac);
+ EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
+ Pkt4::MAX_CHADDR_LEN));
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ // CHADDR starts at offset 28 in DHCP packet
+ const uint8_t* ptr =
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 28;
+
+ EXPECT_EQ(0, memcmp(ptr, expectedChaddr, Pkt4::MAX_CHADDR_LEN));
+
+ pkt.reset();
+ }
+
+ /// TODO: extend this test once options support is implemented. HW address
+ /// longer than 16 bytes should be stored in client-identifier option
+}
+
+TEST_F(Pkt4Test, msgTypes) {
+
+ struct msgType {
+ uint8_t dhcp;
+ uint8_t bootp;
+ };
+
+ msgType types[] = {
+ {DHCPDISCOVER, BOOTREQUEST},
+ {DHCPOFFER, BOOTREPLY},
+ {DHCPREQUEST, BOOTREQUEST},
+ {DHCPDECLINE, BOOTREQUEST},
+ {DHCPACK, BOOTREPLY},
+ {DHCPNAK, BOOTREPLY},
+ {DHCPRELEASE, BOOTREQUEST},
+ {DHCPINFORM, BOOTREQUEST},
+ {DHCPLEASEQUERY, BOOTREQUEST},
+ {DHCPLEASEUNASSIGNED, BOOTREPLY},
+ {DHCPLEASEUNKNOWN, BOOTREPLY},
+ {DHCPLEASEACTIVE, BOOTREPLY}
+ };
+
+ scoped_ptr<Pkt4> pkt;
+ for (size_t i = 0; i < sizeof(types) / sizeof(msgType); i++) {
+ pkt.reset(new Pkt4(types[i].dhcp, 0));
+ EXPECT_EQ(types[i].dhcp, pkt->getType());
+ EXPECT_EQ(types[i].bootp, pkt->getOp());
+ pkt.reset();
+ }
+
+ EXPECT_THROW(
+ pkt.reset(new Pkt4(100, 0)), // There's no message type 100
+ OutOfRange
+ );
+}
+
+// This test verifies handling of sname field
+TEST_F(Pkt4Test, sname) {
+
+ uint8_t sname[Pkt4::MAX_SNAME_LEN];
+
+ scoped_ptr<Pkt4> pkt;
+ // Let's test each sname length, from 0 till 64 (included)
+ for (size_t snameLen = 0; snameLen <= Pkt4::MAX_SNAME_LEN; ++snameLen) {
+ for (size_t i = 0; i < snameLen; ++i) {
+ sname[i] = i + 1;
+ }
+ if (snameLen < Pkt4::MAX_SNAME_LEN) {
+ for (size_t i = snameLen; i < Pkt4::MAX_SNAME_LEN; ++i) {
+ sname[i] = 0;
+ }
+ }
+
+ // Type and transaction doesn't matter in this test
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
+ pkt->setSname(sname, snameLen);
+
+ EXPECT_EQ(0, memcmp(sname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ // SNAME starts at offset 44 in DHCP packet
+ const uint8_t* ptr =
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 44;
+ EXPECT_EQ(0, memcmp(ptr, sname, Pkt4::MAX_SNAME_LEN));
+
+ pkt.reset();
+ }
+
+ // Check that a null argument generates an exception.
+ Pkt4 pkt4(DHCPOFFER, 1234);
+ EXPECT_THROW(pkt4.setSname(NULL, Pkt4::MAX_SNAME_LEN), InvalidParameter);
+ EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter);
+
+ // Check that a too long argument generates an exception
+ // (the actual content doesn't matter).
+ uint8_t bigsname[Pkt4::MAX_SNAME_LEN + 1];
+ EXPECT_THROW(pkt4.setSname(bigsname, Pkt4::MAX_SNAME_LEN + 1), OutOfRange);
+}
+
+TEST_F(Pkt4Test, file) {
+
+ uint8_t file[Pkt4::MAX_FILE_LEN];
+
+ scoped_ptr<Pkt4> pkt;
+ // Let's test each file length, from 0 till 128 (included).
+ for (size_t fileLen = 0; fileLen <= Pkt4::MAX_FILE_LEN; ++fileLen) {
+ for (size_t i = 0; i < fileLen; ++i) {
+ file[i] = i + 1;
+ }
+ if (fileLen < Pkt4::MAX_FILE_LEN) {
+ for (size_t i = fileLen; i < Pkt4::MAX_FILE_LEN; ++i) {
+ file[i] = 0;
+ }
+ }
+
+ // Type and transaction doesn't matter in this test.
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
+ pkt->setFile(file, fileLen);
+
+ EXPECT_EQ(0, memcmp(file, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN));
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ // FILE starts at offset 108 in DHCP packet.
+ const uint8_t* ptr =
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 108;
+ EXPECT_EQ(0, memcmp(ptr, file, Pkt4::MAX_FILE_LEN));
+
+ pkt.reset();
+ }
+
+ // Check that a null argument generates an exception.
+ Pkt4 pkt4(DHCPOFFER, 1234);
+ EXPECT_THROW(pkt4.setFile(NULL, Pkt4::MAX_FILE_LEN), InvalidParameter);
+ EXPECT_THROW(pkt4.setFile(NULL, 0), InvalidParameter);
+
+ // Check that a too long argument generates an exception
+ // (the actual content doesn't matter).
+ uint8_t bigfile[Pkt4::MAX_FILE_LEN + 1];
+ EXPECT_THROW(pkt4.setFile(bigfile, Pkt4::MAX_FILE_LEN + 1), OutOfRange);
+}
+
+TEST_F(Pkt4Test, options) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
+
+ vector<uint8_t> payload[5];
+ for (uint8_t i = 0; i < 5; i++) {
+ payload[i].push_back(i * 10);
+ payload[i].push_back(i * 10 + 1);
+ payload[i].push_back(i * 10 + 2);
+ }
+
+ boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
+ boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[1]));
+ boost::shared_ptr<Option> opt2(new Option(Option::V4, 60, payload[2]));
+ boost::shared_ptr<Option> opt5(new Option(Option::V4,128, payload[3]));
+ boost::shared_ptr<Option> opt4(new Option(Option::V4,254, payload[4]));
+
+ pkt->addOption(opt1);
+ pkt->addOption(opt2);
+ pkt->addOption(opt3);
+ pkt->addOption(opt4);
+ pkt->addOption(opt5);
+
+ EXPECT_TRUE(pkt->getOption(12));
+ EXPECT_TRUE(pkt->getOption(60));
+ EXPECT_TRUE(pkt->getOption(14));
+ EXPECT_TRUE(pkt->getOption(128));
+ EXPECT_TRUE(pkt->getOption(254));
+ EXPECT_FALSE(pkt->getOption(127)); // no such option
+
+ // Options are unique in DHCPv4. It should not be possible
+ // to add more than one option of the same type.
+ EXPECT_THROW(
+ pkt->addOption(opt1),
+ BadValue
+ );
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ const OutputBuffer& buf = pkt->getBuffer();
+ // Check that all options are stored, they should take sizeof(v4_opts),
+ // DHCP magic cookie (4 bytes), and OPTION_END added (just one byte)
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) +
+ sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4_opts) + 1,
+ buf.getLength());
+
+ // That that this extra data actually contain our options
+ const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
+
+ // Rewind to end of fixed part.
+ ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE);
+
+ EXPECT_EQ(0, memcmp(ptr, v4_opts, sizeof(v4_opts)));
+ EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4_opts))));
+
+ // delOption() checks
+ EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
+ EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+ EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
+ EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
+
+ EXPECT_NO_THROW(pkt.reset());
+}
+
+// Check that multiple options of the same type may be retrieved by
+// using getOptions, Also check that retrieved options are copied when
+// setCopyRetrievedOptions is enabled.
+TEST_F(Pkt4Test, getOptions) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
+ OptionPtr opt1(new Option(Option::V4, 1));
+ OptionPtr opt2(new Option(Option::V4, 1));
+ OptionPtr opt3(new Option(Option::V4, 2));
+ OptionPtr opt4(new Option(Option::V4, 2));
+
+ pkt->addOption(opt1);
+ pkt->Pkt::addOption(opt2);
+ pkt->Pkt::addOption(opt3);
+ pkt->Pkt::addOption(opt4);
+
+ // Retrieve options with option code 1.
+ OptionCollection options = pkt->getOptions(1);
+ ASSERT_EQ(2, options.size());
+
+ OptionCollection::const_iterator opt_it;
+
+ // Make sure that the first option is returned. We're using the pointer
+ // to opt1 to find the option.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt1));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Make sure that the second option is returned.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt2));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Retrieve options with option code 2.
+ options = pkt->getOptions(2);
+ ASSERT_EQ(2, options.size());
+
+ // opt3 and opt4 should exist.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt3));
+ EXPECT_TRUE(opt_it != options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt4));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Enable copying options when they are retrieved.
+ pkt->setCopyRetrievedOptions(true);
+
+ options = pkt->getOptions(1);
+ ASSERT_EQ(2, options.size());
+
+ // Both retrieved options should be copied so an attempt to find them
+ // using option pointer should fail. Original pointers should have
+ // been replaced with new instances.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt1));
+ EXPECT_TRUE(opt_it == options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt2));
+ EXPECT_TRUE(opt_it == options.end());
+
+ // Return instances of options with the option code 1 and make sure
+ // that copies of the options were used to replace original options
+ // in the packet.
+ pkt->setCopyRetrievedOptions(false);
+ OptionCollection options_modified = pkt->getOptions(1);
+ for (OptionCollection::const_iterator opt_it_modified = options_modified.begin();
+ opt_it_modified != options_modified.end(); ++opt_it_modified) {
+ opt_it = std::find(options.begin(), options.end(), *opt_it_modified);
+ ASSERT_TRUE(opt_it != options.end());
+ }
+
+ // Let's check that remaining two options haven't been affected by
+ // retrieving the options with option code 1.
+ options = pkt->getOptions(2);
+ ASSERT_EQ(2, options.size());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt3));
+ EXPECT_TRUE(opt_it != options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt4));
+ EXPECT_TRUE(opt_it != options.end());
+}
+
+// This test verifies that it is possible to control whether a pointer
+// to an option or a pointer to a copy of an option is returned by the
+// packet object.
+TEST_F(Pkt4Test, setCopyRetrievedOptions) {
+ // Create option 1 with two sub options.
+ OptionPtr option1(new Option(Option::V4, 1));
+ OptionPtr sub1(new Option(Option::V4, 1));
+ OptionPtr sub2(new Option(Option::V4, 2));
+
+ option1->addOption(sub1);
+ option1->addOption(sub2);
+
+ // Create option 2 with two sub options.
+ OptionPtr option2(new Option(Option::V4, 2));
+ OptionPtr sub3(new Option(Option::V4, 1));
+ OptionPtr sub4(new Option(Option::V4, 2));
+
+ option2->addOption(sub3);
+ option2->addOption(sub4);
+
+ // Add both options to a packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234));
+ pkt->addOption(option1);
+ pkt->addOption(option2);
+
+ // Retrieve options and make sure that the pointers to the original
+ // option instances are returned.
+ ASSERT_TRUE(option1 == pkt->getOption(1));
+ ASSERT_TRUE(option2 == pkt->getOption(2));
+
+ // Now force copying the options when they are retrieved.
+ pkt->setCopyRetrievedOptions(true);
+ EXPECT_TRUE(pkt->isCopyRetrievedOptions());
+
+ // Option pointer returned must point to a new instance of option 2.
+ OptionPtr option2_copy = pkt->getOption(2);
+ EXPECT_FALSE(option2 == option2_copy);
+
+ // Disable copying.
+ pkt->setCopyRetrievedOptions(false);
+ EXPECT_FALSE(pkt->isCopyRetrievedOptions());
+
+ // Expect that the original pointer is returned. This guarantees that
+ // option1 wasn't affected by copying option 2.
+ OptionPtr option1_copy = pkt->getOption(1);
+ EXPECT_TRUE(option1 == option1_copy);
+
+ // Again, enable copying options.
+ pkt->setCopyRetrievedOptions(true);
+
+ // This time a pointer to new option instance should be returned.
+ option1_copy = pkt->getOption(1);
+ EXPECT_FALSE(option1 == option1_copy);
+}
+
+// This test verifies that the options are unpacked from the packet correctly.
+TEST_F(Pkt4Test, unpackOptions) {
+
+ vector<uint8_t> expectedFormat = generateTestPacket2();
+
+ expectedFormat.push_back(0x63);
+ expectedFormat.push_back(0x82);
+ expectedFormat.push_back(0x53);
+ expectedFormat.push_back(0x63);
+
+ for (size_t i = 0; i < sizeof(v4_opts); i++) {
+ expectedFormat.push_back(v4_opts[i]);
+ }
+
+ // now expectedFormat contains fixed format and 5 options
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0],
+ expectedFormat.size()));
+
+ EXPECT_NO_THROW(
+ pkt->unpack()
+ );
+
+ verifyParsedOptions(pkt);
+}
+
+// Checks if the code is able to handle a malformed option
+TEST_F(Pkt4Test, unpackMalformed) {
+
+ vector<uint8_t> orig = generateTestPacket2();
+
+ orig.push_back(0x63);
+ orig.push_back(0x82);
+ orig.push_back(0x53);
+ orig.push_back(0x63);
+
+ orig.push_back(53); // Message Type
+ orig.push_back(1); // length=1
+ orig.push_back(2); // type=2
+
+ orig.push_back(12); // Hostname
+ orig.push_back(3); // length=3
+ orig.push_back(102); // data="foo"
+ orig.push_back(111);
+ orig.push_back(111);
+
+ // That's our original content. It should be sane.
+ Pkt4Ptr success(new Pkt4(&orig[0], orig.size()));
+ EXPECT_NO_THROW(success->unpack());
+
+ // With the exception of END and PAD an option must have a length byte
+ vector<uint8_t> nolength = orig;
+ nolength.resize(orig.size() - 4);
+ Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size()));
+ EXPECT_NO_THROW(no_length_pkt->unpack());
+
+ // The unpack() operation doesn't throw but there is no option 12
+ EXPECT_FALSE(no_length_pkt->getOption(12));
+
+ // Truncated data is not accepted too but doesn't throw
+ vector<uint8_t> shorty = orig;
+ shorty.resize(orig.size() - 1);
+ Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size()));
+ EXPECT_NO_THROW(too_short_pkt->unpack());
+
+ // The unpack() operation doesn't throw but there is no option 12
+ EXPECT_FALSE(no_length_pkt->getOption(12));
+}
+
+// Checks if the code is able to handle a malformed vendor option
+TEST_F(Pkt4Test, unpackVendorMalformed) {
+
+ vector<uint8_t> orig = generateTestPacket2();
+
+ orig.push_back(0x63);
+ orig.push_back(0x82);
+ orig.push_back(0x53);
+ orig.push_back(0x63);
+
+ orig.push_back(53); // Message Type
+ orig.push_back(1); // length=1
+ orig.push_back(2); // type=2
+
+ orig.push_back(125); // vivso suboptions
+ size_t full_len_index = orig.size();
+ orig.push_back(15); // length=15
+ orig.push_back(1); // vendor_id=0x1020304
+ orig.push_back(2);
+ orig.push_back(3);
+ orig.push_back(4);
+ size_t data_len_index = orig.size();
+ orig.push_back(10); // data-len=10
+ orig.push_back(128); // suboption type=128
+ orig.push_back(3); // suboption length=3
+ orig.push_back(102); // data="foo"
+ orig.push_back(111);
+ orig.push_back(111);
+ orig.push_back(129); // suboption type=129
+ orig.push_back(3); // suboption length=3
+ orig.push_back(99); // data="bar"
+ orig.push_back(98);
+ orig.push_back(114);
+
+ // That's our original content. It should be sane.
+ Pkt4Ptr success(new Pkt4(&orig[0], orig.size()));
+ EXPECT_NO_THROW(success->unpack());
+
+ // Data-len must match
+ vector<uint8_t> baddatalen = orig;
+ baddatalen.resize(orig.size() - 5);
+ baddatalen[full_len_index] = 10;
+ Pkt4Ptr bad_data_len_pkt(new Pkt4(&baddatalen[0], baddatalen.size()));
+ EXPECT_THROW(bad_data_len_pkt->unpack(), SkipRemainingOptionsError);
+
+ // A suboption must have a length byte
+ vector<uint8_t> nolength = orig;
+ nolength.resize(orig.size() - 4);
+ nolength[full_len_index] = 11;
+ nolength[data_len_index] = 6;
+ Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size()));
+ EXPECT_THROW(no_length_pkt->unpack(), SkipRemainingOptionsError);
+
+ // Truncated data is not accepted either
+ vector<uint8_t> shorty = orig;
+ shorty.resize(orig.size() - 1);
+ shorty[full_len_index] = 14;
+ shorty[data_len_index] = 9;
+ Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size()));
+ EXPECT_THROW(too_short_pkt->unpack(), SkipRemainingOptionsError);
+}
+
+// This test verifies methods that are used for manipulating meta fields
+// i.e. fields that are not part of DHCPv4 (e.g. interface name).
+TEST_F(Pkt4Test, metaFields) {
+
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ pkt->setIface("loooopback");
+ pkt->setIndex(42);
+ pkt->setRemoteAddr(IOAddress("1.2.3.4"));
+ pkt->setLocalAddr(IOAddress("4.3.2.1"));
+
+ EXPECT_EQ("loooopback", pkt->getIface());
+ EXPECT_EQ(42, pkt->getIndex());
+ EXPECT_EQ("1.2.3.4", pkt->getRemoteAddr().toText());
+ EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
+}
+
+TEST_F(Pkt4Test, Timestamp) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+
+ // Just after construction timestamp is invalid
+ ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time());
+
+ // Update packet time.
+ pkt->updateTimestamp();
+
+ // Get updated packet time.
+ boost::posix_time::ptime ts_packet = pkt->getTimestamp();
+
+ // After timestamp is updated it should be date-time.
+ ASSERT_FALSE(ts_packet.is_not_a_date_time());
+
+ // Check current time.
+ boost::posix_time::ptime ts_now =
+ boost::posix_time::microsec_clock::universal_time();
+
+ // Calculate period between packet time and now.
+ boost::posix_time::time_period ts_period(ts_packet, ts_now);
+
+ // Duration should be positive or zero.
+ EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
+}
+
+TEST_F(Pkt4Test, hwaddr) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+
+ HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+
+ // setting NULL hardware address is not allowed
+ EXPECT_THROW(pkt->setHWAddr(HWAddrPtr()), BadValue);
+
+ pkt->setHWAddr(hwaddr);
+
+ EXPECT_EQ(hw_type, pkt->getHtype());
+
+ EXPECT_EQ(sizeof(hw), pkt->getHlen());
+
+ EXPECT_TRUE(hwaddr == pkt->getHWAddr());
+}
+
+// This test verifies that the packet remote and local HW address can
+// be set and returned.
+TEST_F(Pkt4Test, hwaddrSrcRemote) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
+ const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };
+ const uint8_t hw_type = 123;
+
+ HWAddrPtr dst_hwaddr(new HWAddr(dst_hw, sizeof(src_hw), hw_type));
+ HWAddrPtr src_hwaddr(new HWAddr(src_hw, sizeof(src_hw), hw_type));
+
+ // Check that we can set the local address.
+ EXPECT_NO_THROW(pkt->setLocalHWAddr(dst_hwaddr));
+ EXPECT_TRUE(dst_hwaddr == pkt->getLocalHWAddr());
+
+ // Check that we can set the remote address.
+ EXPECT_NO_THROW(pkt->setRemoteHWAddr(src_hwaddr));
+ EXPECT_TRUE(src_hwaddr == pkt->getRemoteHWAddr());
+
+ // Can't set the NULL addres.
+ EXPECT_THROW(pkt->setRemoteHWAddr(HWAddrPtr()), BadValue);
+ EXPECT_THROW(pkt->setLocalHWAddr(HWAddrPtr()), BadValue);
+
+ // Test alternative way to set local address.
+ const uint8_t dst_hw2[] = { 19, 20, 21, 22, 23, 24 };
+ std::vector<uint8_t> dst_hw_vec(dst_hw2, dst_hw2 + sizeof(dst_hw2));
+ const uint8_t hw_type2 = 234;
+ EXPECT_NO_THROW(pkt->setLocalHWAddr(hw_type2, sizeof(dst_hw2), dst_hw_vec));
+ HWAddrPtr local_addr = pkt->getLocalHWAddr();
+ ASSERT_TRUE(local_addr);
+ EXPECT_EQ(hw_type2, local_addr->htype_);
+ EXPECT_TRUE(std::equal(dst_hw_vec.begin(), dst_hw_vec.end(),
+ local_addr->hwaddr_.begin()));
+
+ // Set remote address.
+ const uint8_t src_hw2[] = { 25, 26, 27, 28, 29, 30 };
+ std::vector<uint8_t> src_hw_vec(src_hw2, src_hw2 + sizeof(src_hw2));
+ EXPECT_NO_THROW(pkt->setRemoteHWAddr(hw_type2, sizeof(src_hw2), src_hw_vec));
+ HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+ ASSERT_TRUE(remote_addr);
+ EXPECT_EQ(hw_type2, remote_addr->htype_);
+ EXPECT_TRUE(std::equal(src_hw_vec.begin(), src_hw_vec.end(),
+ remote_addr->hwaddr_.begin()));
+}
+
+// This test verifies that the check for a message being relayed is correct.
+TEST_F(Pkt4Test, isRelayed) {
+ Pkt4 pkt(DHCPDISCOVER, 1234);
+ // By default, the hops and giaddr should be 0.
+ ASSERT_TRUE(pkt.getGiaddr().isV4Zero());
+ ASSERT_EQ(0, pkt.getHops());
+ // For zero giaddr the packet is non-relayed.
+ EXPECT_FALSE(pkt.isRelayed());
+ // Set giaddr but leave hops = 0.
+ pkt.setGiaddr(IOAddress("10.0.0.1"));
+ EXPECT_TRUE(pkt.isRelayed());
+ // After setting hops the message should still be relayed.
+ pkt.setHops(10);
+ EXPECT_TRUE(pkt.isRelayed());
+ // Set giaddr to 0. The message is now not-relayed.
+ pkt.setGiaddr(IOAddress(IOAddress::IPV4_ZERO_ADDRESS()));
+ EXPECT_FALSE(pkt.isRelayed());
+ // Setting the giaddr to 255.255.255.255 should not cause it to
+ // be relayed message.
+ pkt.setGiaddr(IOAddress(IOAddress::IPV4_BCAST_ADDRESS()));
+ EXPECT_FALSE(pkt.isRelayed());
+}
+
+// Tests whether a packet can be assigned to a class and later
+// checked if it belongs to a given class
+TEST_F(Pkt4Test, clientClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ EXPECT_TRUE(pkt.getClasses().empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ ASSERT_FALSE(pkt.getClasses().empty());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.inClass("foo"));
+}
+
+// Tests whether a packet can be marked to evaluate later a class and
+// after check if a given class is in the collection
+TEST_F(Pkt4Test, deferredClientClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_TRUE(pkt.getClasses(true).empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
+ EXPECT_EQ(1, pkt.getClasses(true).size());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM, true);
+ EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
+ EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
+}
+
+// Tests whether a packet can be assigned to a subclass and later
+// checked if it belongs to a given subclass
+TEST_F(Pkt4Test, templateClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any subclass)
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+ EXPECT_TRUE(pkt.getClasses().empty());
+
+ // Add to the first subclass
+ pkt.addSubClass("template-interface-name", "SPAWN_template-interface-name_eth0");
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+ ASSERT_FALSE(pkt.getClasses().empty());
+
+ // Add to a second subclass
+ pkt.addSubClass("template-interface-id", "SPAWN_template-interface-id_interface-id0");
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+
+ // Check that it's ok to add to the same subclass repeatedly
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-bar", "SPAWN_template-bar_bar"));
+
+ // Check that the packet belongs to 'SPAWN_template-foo_bar'
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-foo_bar"));
+
+ // Check that the packet belongs to 'SPAWN_template-bar_bar'
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-bar_bar"));
+}
+
+// Tests whether MAC can be obtained and that MAC sources are not
+// confused.
+TEST_F(Pkt4Test, getMAC) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // DHCPv4 packet by default doesn't have MAC address specified.
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+
+ // Let's invent a MAC
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+ HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+
+ // Now let's pretend that we obtained it from raw sockets
+ pkt.setRemoteHWAddr(dummy_hwaddr);
+
+ // Now we should be able to get something
+ ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+
+ // Check that the returned MAC is indeed the expected one
+ ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+}
+
+// Tests that getLabel/makeLabel methods produces the expected strings based on
+// packet content.
+TEST_F(Pkt4Test, getLabel) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Verify makeLabel() handles empty values
+ EXPECT_EQ ("[no hwaddr info], cid=[no info], tid=0x0",
+ Pkt4::makeLabel(HWAddrPtr(), ClientIdPtr(), 0));
+
+ // Verify an "empty" packet label is as we expect
+ EXPECT_EQ ("[hwtype=1 ], cid=[no info], tid=0x4d2",
+ pkt.getLabel());
+
+ // Set that packet hardware address, then verify getLabel
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+ HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+ pkt.setHWAddr(dummy_hwaddr);
+
+ EXPECT_EQ ("[hwtype=123 02:04:06:08:0a:0c],"
+ " cid=[no info], tid=0x4d2", pkt.getLabel());
+
+ // Add a client id to the packet then verify getLabel
+ OptionBuffer clnt_id(4);
+ for (uint8_t i = 0; i < 4; i++) {
+ clnt_id[i] = 100 + i;
+ }
+
+ OptionPtr opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+ clnt_id.begin(), clnt_id.begin() + 4));
+ pkt.addOption(opt);
+
+ EXPECT_EQ ("[hwtype=123 02:04:06:08:0a:0c],"
+ " cid=[64:65:66:67], tid=0x4d2",
+ pkt.getLabel());
+
+}
+
+// Test that empty client identifier option doesn't cause an exception from
+// Pkt4::getLabel.
+TEST_F(Pkt4Test, getLabelEmptyClientId) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Create empty client identifier option.
+ OptionPtr empty_opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER));
+ pkt.addOption(empty_opt);
+
+ EXPECT_EQ("[hwtype=1 ], cid=[no info], tid=0x4d2"
+ " (malformed client-id)", pkt.getLabel());
+}
+
+// Tests that the variant of makeLabel which doesn't include transaction
+// id produces expected output.
+TEST_F(Pkt4Test, makeLabelWithoutTransactionId) {
+ EXPECT_EQ("[no hwaddr info], cid=[no info]",
+ Pkt4::makeLabel(HWAddrPtr(), ClientIdPtr()));
+
+ // Test non-null hardware address.
+ HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06", 123)));
+ EXPECT_EQ("[hwtype=123 01:02:03:04:05:06], cid=[no info]",
+ Pkt4::makeLabel(hwaddr, ClientIdPtr()));
+
+ // Test non-null client identifier and non-null hardware address.
+ ClientIdPtr cid = ClientId::fromText("01:02:03:04");
+ EXPECT_EQ("[hwtype=123 01:02:03:04:05:06], cid=[01:02:03:04]",
+ Pkt4::makeLabel(hwaddr, cid));
+
+ // Test non-nnull client identifier and null hardware address.
+ EXPECT_EQ("[no hwaddr info], cid=[01:02:03:04]",
+ Pkt4::makeLabel(HWAddrPtr(), cid));
+}
+
+// Tests that the correct DHCPv4 message name is returned for various
+// message types.
+TEST_F(Pkt4Test, getName) {
+ // Check all possible packet types
+ for (int itype = 0; itype < 256; ++itype) {
+ uint8_t type = itype;
+
+ switch (type) {
+ case DHCPDISCOVER:
+ EXPECT_STREQ("DHCPDISCOVER", Pkt4::getName(type));
+ break;
+
+ case DHCPOFFER:
+ EXPECT_STREQ("DHCPOFFER", Pkt4::getName(type));
+ break;
+
+ case DHCPREQUEST:
+ EXPECT_STREQ("DHCPREQUEST", Pkt4::getName(type));
+ break;
+
+ case DHCPDECLINE:
+ EXPECT_STREQ("DHCPDECLINE", Pkt4::getName(type));
+ break;
+
+ case DHCPACK:
+ EXPECT_STREQ("DHCPACK", Pkt4::getName(type));
+ break;
+
+ case DHCPNAK:
+ EXPECT_STREQ("DHCPNAK", Pkt4::getName(type));
+ break;
+
+ case DHCPRELEASE:
+ EXPECT_STREQ("DHCPRELEASE", Pkt4::getName(type));
+ break;
+
+ case DHCPINFORM:
+ EXPECT_STREQ("DHCPINFORM", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEQUERY:
+ EXPECT_STREQ("DHCPLEASEQUERY", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEUNASSIGNED:
+ EXPECT_STREQ("DHCPLEASEUNASSIGNED", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEUNKNOWN:
+ EXPECT_STREQ("DHCPLEASEUNKNOWN", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEACTIVE:
+ EXPECT_STREQ("DHCPLEASEACTIVE", Pkt4::getName(type));
+ break;
+
+ case DHCPBULKLEASEQUERY:
+ EXPECT_STREQ("DHCPBULKLEASEQUERY", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEQUERYDONE:
+ EXPECT_STREQ("DHCPLEASEQUERYDONE", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEQUERYSTATUS:
+ EXPECT_STREQ("DHCPLEASEQUERYSTATUS", Pkt4::getName(type));
+ break;
+
+ case DHCPTLS:
+ EXPECT_STREQ("DHCPTLS", Pkt4::getName(type));
+ break;
+
+ default:
+ EXPECT_STREQ("UNKNOWN", Pkt4::getName(type));
+ }
+ }
+}
+
+// This test checks that the packet data are correctly converted to the
+// textual format.
+TEST_F(Pkt4Test, toText) {
+ 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")));
+
+ EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, "
+ "msg_type=DHCPDISCOVER (1), transid=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)",
+ pkt.toText());
+
+ // Now remove all options, including Message Type and check if the
+ // information about lack of any options is displayed properly.
+ pkt.delOption(123);
+ pkt.delOption(156);
+ pkt.delOption(87);
+ pkt.delOption(53);
+
+ EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, "
+ "msg_type=(missing), transid=0x9ef, "
+ "message contains no options",
+ pkt.toText());
+
+}
+
+// Sanity check. Verifies that the getName() and getType()
+// don't throw.
+TEST_F(Pkt4Test, getType) {
+
+ Pkt4 pkt(DHCPDISCOVER, 2543);
+ pkt.delOption(DHO_DHCP_MESSAGE_TYPE);
+
+ ASSERT_NO_THROW(pkt.getType());
+ ASSERT_NO_THROW(pkt.getName());
+
+ // The method has to return something that is not NULL,
+ // even if the packet doesn't have Message Type option.
+ EXPECT_TRUE(pkt.getName());
+}
+
+// Verifies that when the VIVSO option 125 has length that is too
+// short (i.e. less than sizeof(uint8_t), unpack throws a
+// SkipRemainingOptionsError exception
+TEST_F(Pkt4Test, truncatedVendorLength) {
+
+ // Build a good discover packet
+ Pkt4Ptr pkt = dhcp::test::PktCaptures::discoverWithValidVIVSO();
+
+ // Unpacking should not throw
+ ASSERT_NO_THROW(pkt->unpack());
+ ASSERT_EQ(DHCPDISCOVER, pkt->getType());
+
+ // VIVSO option should be there
+ OptionPtr x = pkt->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_TRUE(x);
+ ASSERT_EQ(DHO_VIVSO_SUBOPTIONS, x->getType());
+ OptionVendorPtr vivso = boost::dynamic_pointer_cast<OptionVendor>(x);
+ ASSERT_TRUE(vivso);
+ EXPECT_EQ(133+2, vivso->len()); // data + opt code + len
+
+ // Build a bad discover packet
+ pkt = dhcp::test::PktCaptures::discoverWithTruncatedVIVSO();
+
+ // Unpack should throw Skip exception
+ ASSERT_THROW(pkt->unpack(), SkipRemainingOptionsError);
+ ASSERT_EQ(DHCPDISCOVER, pkt->getType());
+
+ // VIVSO option should not be there
+ x = pkt->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_FALSE(x);
+}
+
+// Verifies that we handle text options that contain trailing
+// and embedded NULLs correctly. Per RFC 2132, Sec 2 we should
+// be stripping trailing NULLs. We've agreed to permit
+// embedded NULLs (for now).
+TEST_F(Pkt4Test, nullTerminatedOptions) {
+ // Construct the onwire packet.
+ vector<uint8_t> base_msg = generateTestPacket2();
+ base_msg.push_back(0x63); // magic cookie
+ base_msg.push_back(0x82);
+ base_msg.push_back(0x53);
+ base_msg.push_back(0x63);
+
+ base_msg.push_back(0x35); // message-type
+ base_msg.push_back(0x1);
+ base_msg.push_back(0x1);
+
+ int base_size = base_msg.size();
+
+ // We'll create four text options, with various combinations of NULLs.
+ vector<uint8_t> hostname = { DHO_HOST_NAME, 5, 't', 'w', 'o', 0, 0 };
+ vector<uint8_t> merit_dump = { DHO_MERIT_DUMP, 4, 'o', 'n', 'e', 0 };
+ vector<uint8_t> root_path = { DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e' };
+ vector<uint8_t> domain_name = { DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd' };
+
+ // Add the options to the onwire packet.
+ vector<uint8_t> test_msg = base_msg;
+ test_msg.insert(test_msg.end(), hostname.begin(), hostname.end());
+ test_msg.insert(test_msg.end(), root_path.begin(), root_path.end());
+ test_msg.insert(test_msg.end(), merit_dump.begin(), merit_dump.end());
+ test_msg.insert(test_msg.end(), domain_name.begin(), domain_name.end());
+ test_msg.push_back(DHO_END);
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(&test_msg[0], test_msg.size()));
+
+ // Unpack the onwire packet.
+ EXPECT_NO_THROW(
+ pkt->unpack()
+ );
+
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+
+ OptionPtr opt;
+ OptionStringPtr opstr;
+
+ // Now let's verify that each text option is as expected.
+ ASSERT_TRUE(opt = pkt->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("two", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_MERIT_DUMP));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("one", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_ROOT_PATH));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(4, opstr->getValue().length());
+ EXPECT_EQ("none", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_DOMAIN_NAME));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(6, opstr->getValue().length());
+ std::string embed{"em\0bed", 6};
+ EXPECT_EQ(embed, opstr->getValue());
+
+
+ // Next we pack the packet, to make sure trailing NULLs have
+ // been eliminated, embedded NULLs are intact.
+ EXPECT_NO_THROW(
+ pkt->pack()
+ );
+
+ // Create a vector of our expected packed option data.
+ vector<uint8_t> packed_opts =
+ {
+ DHO_HOST_NAME, 3, 't', 'w', 'o',
+ DHO_MERIT_DUMP, 3, 'o', 'n', 'e',
+ DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd',
+ DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e',
+ };
+
+ const uint8_t* packed = static_cast<const uint8_t*>(pkt->getBuffer().getData());
+ int packed_len = pkt->getBuffer().getLength();
+
+ // Packed message options should be 3 bytes smaller than original onwire data.
+ int dif = packed_len - test_msg.size();
+ ASSERT_EQ(-3, dif);
+
+ // Make sure the packed content is as expected.
+ EXPECT_EQ(0, memcmp(&packed[base_size], &packed_opts[0], packed_opts.size()));
+}
+
+// Checks that unpacking correctly handles SkipThisOptionError by
+// omitting the offending option from the unpacked options.
+TEST_F(Pkt4Test, testSkipThisOptionError) {
+ vector<uint8_t> orig = generateTestPacket2();
+
+ orig.push_back(0x63);
+ orig.push_back(0x82);
+ orig.push_back(0x53);
+ orig.push_back(0x63);
+
+ orig.push_back(53); // Message Type
+ orig.push_back(1); // length=1
+ orig.push_back(2); // type=2
+
+ orig.push_back(14); // merit-dump
+ orig.push_back(3); // length=3
+ orig.push_back(0x61); // data="abc"
+ orig.push_back(0x62);
+ orig.push_back(0x63);
+
+ orig.push_back(12); // Hostname
+ orig.push_back(3); // length=3
+ orig.push_back(0); // data= all nulls
+ orig.push_back(0);
+ orig.push_back(0);
+
+ orig.push_back(17); // root-path
+ orig.push_back(3); // length=3
+ orig.push_back(0x64); // data="def"
+ orig.push_back(0x65);
+ orig.push_back(0x66);
+
+ // Unpacking should not throw.
+ Pkt4Ptr pkt(new Pkt4(&orig[0], orig.size()));
+ ASSERT_NO_THROW_LOG(pkt->unpack());
+
+ // We should have option 14 = "abc".
+ OptionPtr opt;
+ OptionStringPtr opstr;
+ ASSERT_TRUE(opt = pkt->getOption(14));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("abc", opstr->getValue());
+
+ // We should not have option 12.
+ EXPECT_FALSE(opt = pkt->getOption(12));
+
+ // We should have option 17 = "def".
+ ASSERT_TRUE(opt = pkt->getOption(17));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("def", opstr->getValue());
+}
+
+// Tests that getHWAddrLabel method produces the expected strings based on
+// packet content.
+TEST_F(Pkt4Test, getHWAddrLabel) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Verify getHWAddrLabel() handles empty values
+ EXPECT_EQ ("hwaddr=", pkt.getHWAddrLabel());
+
+ // Testing undefined hwaddr case is not possible
+ EXPECT_THROW(pkt.setHWAddr(nullptr), BadValue);
+
+ // Set that packet hardware address, then verify getLabel
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+ HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+ pkt.setHWAddr(dummy_hwaddr);
+
+ EXPECT_EQ ("hwaddr=02:04:06:08:0a:0c", pkt.getHWAddrLabel());
+}
+
+} // end of anonymous namespace