From: Francis Dupont Date: Thu, 21 Sep 2017 13:44:43 +0000 (+0200) Subject: [5073a] Added DHCPv4 unit tests (still doc to update) X-Git-Tag: trac5363_base~6^2~5 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=271896c41821602fac75301b13be4786ec574fc7;p=thirdparty%2Fkea.git [5073a] Added DHCPv4 unit tests (still doc to update) --- diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index 04113409c2..d6a3ff8e25 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -2210,6 +2210,858 @@ TEST_F(Dhcpv4SrvTest, clientClassify) { EXPECT_TRUE(srv_.selectSubnet(dis)); } +// Verifies last resort option 43 is backward compatible +TEST_F(Dhcpv4SrvTest, option43LastResort) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + NakedDhcpv4Srv srv(0); + + // If there is no definition for option 43 a last resort + // one is applied. This definition was used by Kea <= 1.2 + // so should be backward compatible. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"subnet4\": [ " + "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], " + " \"subnet\": \"192.0.2.0/24\" } ]," + "\"option-def\": [ " + "{ \"code\": 1, " + " \"name\": \"foo\", " + " \"space\": \"vendor-encapsulated-options-space\", " + " \"type\": \"uint32\" } ]," + "\"option-data\": [ " + "{ \"name\": \"foo\", " + " \"space\": \"vendor-encapsulated-options-space\", " + " \"data\": \"12345678\" }, " + "{ \"name\": \"vendor-class-identifier\", " + " \"data\": \"bar\" }, " + "{ \"name\": \"vendor-encapsulated-options\" } ] }"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + CfgMgr::instance().commit(); + + // Create a packet with enough to select the subnet and go through + // the DISCOVER processing + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234)); + query->setRemoteAddr(IOAddress("192.0.2.1")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + + // Create and add a PRL option to the query + OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + ASSERT_TRUE(prl); + prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS); + prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER); + query->addOption(prl); + + srv.classifyPacket(query); + srv.deferredUnpack(query); + + // Pass it to the server and get an offer + Pkt4Ptr offer = srv.processDiscover(query); + + // Check if we get response at all + checkResponse(offer, DHCPOFFER, 1234); + + // Processing should add a vendor-class-identifier (code 60) + OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER); + EXPECT_TRUE(opt); + + // And a vendor-encapsulated-options (code 43) + opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(opt); + const OptionCollection& opts = opt->getOptions(); + ASSERT_EQ(1, opts.size()); + OptionPtr sopt = opts.begin()->second; + ASSERT_TRUE(sopt); + EXPECT_EQ(1, sopt->getType()); +} + +// Checks effect of raw not compatible option 43 +TEST_F(Dhcpv4SrvTest, option43BadRaw) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + NakedDhcpv4Srv srv(0); + + // The vendor-encapsulated-options has an incompatible data + // so won't have the expected content. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"subnet4\": [ " + "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], " + " \"subnet\": \"192.0.2.0/24\" } ]," + "\"option-data\": [ " + "{ \"name\": \"vendor-class-identifier\", " + " \"data\": \"bar\" }, " + "{ \"name\": \"vendor-encapsulated-options\", " + " \"csv-format\": false, " + " \"data\": \"0102\" } ] }"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + CfgMgr::instance().commit(); + + // Create a packet with enough to select the subnet and go through + // the DISCOVER processing + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234)); + query->setRemoteAddr(IOAddress("192.0.2.1")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + + // Create and add a vendor-encapsulated-options (code 43) + // with not compatible (not parsable as suboptions) content + OptionBuffer buf; + buf.push_back(0x01); + buf.push_back(0x02); + OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf)); + query->addOption(vopt); + query->deferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS); + + // Create and add a PRL option to the query + OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + ASSERT_TRUE(prl); + prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS); + prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER); + query->addOption(prl); + + srv.classifyPacket(query); + srv.deferredUnpack(query); + + // Check if the option was (uncorrectly) re-unpacked + vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + OptionCustomPtr custom = boost::dynamic_pointer_cast(vopt); + EXPECT_TRUE(custom); + + // Pass it to the server and get an offer + Pkt4Ptr offer = srv.processDiscover(query); + + // Check if we get response at all + checkResponse(offer, DHCPOFFER, 1234); + + // Processing should add a vendor-class-identifier (code 60) + OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER); + EXPECT_TRUE(opt); + + // And a vendor-encapsulated-options (code 43) + opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(opt); + // But truncated. + EXPECT_EQ(0, opt->len() - opt->getHeaderLen()); +} + +// Verifies raw option 43 can be handled (global) +TEST_F(Dhcpv4SrvTest, option43RawGlobal) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + NakedDhcpv4Srv srv(0); + + // The vendor-encapsulated-options is redefined as raw binary + // in a global definition. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"subnet4\": [ " + "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], " + " \"subnet\": \"192.0.2.0/24\" } ]," + "\"option-def\": [ " + "{ \"code\": 43, " + " \"name\": \"vendor-encapsulated-options\", " + " \"type\": \"binary\" } ]," + "\"option-data\": [ " + "{ \"name\": \"vendor-class-identifier\", " + " \"data\": \"bar\" }, " + "{ \"name\": \"vendor-encapsulated-options\", " + " \"csv-format\": false, " + " \"data\": \"0102\" } ] }"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + CfgMgr::instance().commit(); + + // Create a packet with enough to select the subnet and go through + // the DISCOVER processing + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234)); + query->setRemoteAddr(IOAddress("192.0.2.1")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + + // Create and add a vendor-encapsulated-options (code 43) + // with not compatible (not parsable as suboptions) content + OptionBuffer buf; + buf.push_back(0x02); + buf.push_back(0x03); + OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf)); + query->addOption(vopt); + query->deferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS); + + // Create and add a PRL option to the query + OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + ASSERT_TRUE(prl); + prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS); + prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER); + query->addOption(prl); + + srv.classifyPacket(query); + srv.deferredUnpack(query); + + // Check if the option was (correctly) re-unpacked + vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + OptionCustomPtr custom = boost::dynamic_pointer_cast(vopt); + EXPECT_FALSE(custom); + + // Pass it to the server and get an offer + Pkt4Ptr offer = srv.processDiscover(query); + + // Check if we get response at all + checkResponse(offer, DHCPOFFER, 1234); + + // Processing should add a vendor-class-identifier (code 60) + OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER); + EXPECT_TRUE(opt); + + // And a vendor-encapsulated-options (code 43) + opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(opt); + // Verifies the content + ASSERT_EQ(2, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(0x01, opt->getData()[0]); + EXPECT_EQ(0x02, opt->getData()[1]); +} + +// Verifies raw option 43 can be handled (catch-all class) +TEST_F(Dhcpv4SrvTest, option43RawClass) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + NakedDhcpv4Srv srv(0); + + // The vendor-encapsulated-options is redefined as raw binary + // in a class definition. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"subnet4\": [ " + "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], " + " \"subnet\": \"192.0.2.0/24\" } ]," + "\"client-classes\": [ " + "{ \"name\": \"vendor\", " + " \"test\": \"option[vendor-encapsulated-options].exists\", " + " \"option-def\": [ " + " { \"code\": 43, " + " \"name\": \"vendor-encapsulated-options\", " + " \"type\": \"binary\" } ]," + " \"option-data\": [ " + " { \"name\": \"vendor-class-identifier\", " + " \"data\": \"bar\" }, " + " { \"name\": \"vendor-encapsulated-options\", " + " \"csv-format\": false, " + " \"data\": \"0102\" } ] } ] }"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + CfgMgr::instance().commit(); + + // Create a packet with enough to select the subnet and go through + // the DISCOVER processing + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234)); + query->setRemoteAddr(IOAddress("192.0.2.1")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + + // Create and add a vendor-encapsulated-options (code 43) + // with not compatible (not parsable as suboptions) content + OptionBuffer buf; + buf.push_back(0x02); + buf.push_back(0x03); + OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf)); + query->addOption(vopt); + query->deferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS); + + // Create and add a PRL option to the query + OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + ASSERT_TRUE(prl); + prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS); + prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER); + query->addOption(prl); + + srv.classifyPacket(query); + srv.deferredUnpack(query); + + // Check if the option was (correctly) re-unpacked + vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + OptionCustomPtr custom = boost::dynamic_pointer_cast(vopt); + EXPECT_FALSE(custom); + + // Pass it to the server and get an offer + Pkt4Ptr offer = srv.processDiscover(query); + + // Check if we get response at all + checkResponse(offer, DHCPOFFER, 1234); + + // Processing should add a vendor-class-identifier (code 60) + OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER); + EXPECT_TRUE(opt); + + // And a vendor-encapsulated-options (code 43) + opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(opt); + // Verifies the content + ASSERT_EQ(2, opt->len() - opt->getHeaderLen()); + EXPECT_EQ(0x01, opt->getData()[0]); + EXPECT_EQ(0x02, opt->getData()[1]); +} + +// Verifies option 43 deferred processing (one class) +TEST_F(Dhcpv4SrvTest, option43Class) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + NakedDhcpv4Srv srv(0); + + // A client class defines vendor-encapsulated-options (code 43) + // and data for it and its sub-option. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"code\": 1, " + " \"name\": \"foo\", " + " \"space\": \"alpha\", " + " \"type\": \"uint32\" } ]," + "\"subnet4\": [ " + "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], " + " \"subnet\": \"192.0.2.0/24\" } ]," + "\"client-classes\": [ " + "{ \"name\": \"alpha\", " + " \"test\": \"option[vendor-class-identifier].text == 'alpha'\", " + " \"option-def\": [ " + " { \"code\": 43, " + " \"name\": \"vendor-encapsulated-options\", " + " \"type\": \"empty\", " + " \"encapsulate\": \"alpha\" } ]," + " \"option-data\": [ " + " { \"name\": \"vendor-class-identifier\", " + " \"data\": \"alpha\" }, " + " { \"name\": \"vendor-encapsulated-options\" }, " + " { \"name\": \"foo\", " + " \"space\": \"alpha\", " + " \"data\": \"12345678\" } ] } ] }"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + CfgMgr::instance().commit(); + + // Create a packet with enough to select the subnet and go through + // the DISCOVER processing + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234)); + query->setRemoteAddr(IOAddress("192.0.2.1")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + + // Create and add a vendor-encapsulated-options (code 43) + OptionBuffer buf; + buf.push_back(0x01); + buf.push_back(0x04); + buf.push_back(0x87); + buf.push_back(0x65); + buf.push_back(0x43); + buf.push_back(0x21); + OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf)); + query->addOption(vopt); + query->deferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS); + + // Create and add a vendor-class-identifier (code 60) + OptionStringPtr iopt(new OptionString(Option::V4, + DHO_VENDOR_CLASS_IDENTIFIER, + "alpha")); + query->addOption(iopt); + + // Create and add a PRL option to the query + OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + ASSERT_TRUE(prl); + prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS); + prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER); + query->addOption(prl); + + srv.classifyPacket(query); + srv.deferredUnpack(query); + + // Check if the option was (correctly) re-unpacked + vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + OptionCustomPtr custom = boost::dynamic_pointer_cast(vopt); + EXPECT_TRUE(custom); + EXPECT_EQ(1, vopt->getOptions().size()); + + // Pass it to the server and get an offer + Pkt4Ptr offer = srv.processDiscover(query); + + // Check if we get response at all + checkResponse(offer, DHCPOFFER, 1234); + + // Processing should add a vendor-class-identifier (code 60) + OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER); + EXPECT_TRUE(opt); + + // And a vendor-encapsulated-options (code 43) + opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(opt); + // Verifies the content + const OptionCollection& opts = opt->getOptions(); + ASSERT_EQ(1, opts.size()); + OptionPtr sopt = opts.begin()->second; + ASSERT_TRUE(sopt); + EXPECT_EQ(1, sopt->getType()); + OptionUint32Ptr sopt32 = boost::dynamic_pointer_cast(sopt); + ASSERT_TRUE(sopt32); + EXPECT_EQ(12345678, sopt32->getValue()); +} + +// Verifies option 43 priority +TEST_F(Dhcpv4SrvTest, option43ClassPriority) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + NakedDhcpv4Srv srv(0); + + // Both global and client-class scopes get vendor-encapsulated-options + // (code 43) definition and data. The client-class has precedence. + // Note it does not work without the vendor-encapsulated-options + // option-data in the client-class. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"code\": 1, " + " \"name\": \"foo\", " + " \"space\": \"alpha\", " + " \"type\": \"uint32\" }," + "{ \"code\": 1, " + " \"name\": \"bar\", " + " \"space\": \"beta\", " + " \"type\": \"uint8\" }, " + "{ \"code\": 43, " + " \"name\": \"vendor-encapsulated-options\", " + " \"type\": \"empty\", " + " \"encapsulate\": \"beta\" } ]," + "\"option-data\": [ " + "{ \"name\": \"vendor-encapsulated-options\" }, " + "{ \"name\": \"vendor-class-identifier\", " + " \"data\": \"beta\" }, " + "{ \"name\": \"bar\", " + " \"space\": \"beta\", " + " \"data\": \"33\" } ]," + "\"subnet4\": [ " + "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], " + " \"subnet\": \"192.0.2.0/24\" } ]," + "\"client-classes\": [ " + "{ \"name\": \"alpha\", " + " \"test\": \"option[vendor-class-identifier].text == 'alpha'\", " + " \"option-def\": [ " + " { \"code\": 43, " + " \"name\": \"vendor-encapsulated-options\", " + " \"type\": \"empty\", " + " \"encapsulate\": \"alpha\" } ]," + " \"option-data\": [ " + "{ \"name\": \"vendor-encapsulated-options\" }, " + " { \"name\": \"vendor-class-identifier\", " + " \"data\": \"alpha\" }, " + " { \"name\": \"foo\", " + " \"space\": \"alpha\", " + " \"data\": \"12345678\" } ] } ] }"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + CfgMgr::instance().commit(); + + // Create a packet with enough to select the subnet and go through + // the DISCOVER processing + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234)); + query->setRemoteAddr(IOAddress("192.0.2.1")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + + // Create and add a vendor-encapsulated-options (code 43) + OptionBuffer buf; + buf.push_back(0x01); + buf.push_back(0x04); + buf.push_back(0x87); + buf.push_back(0x65); + buf.push_back(0x43); + buf.push_back(0x21); + OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf)); + query->addOption(vopt); + query->deferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS); + + // Create and add a vendor-class-identifier (code 60) + OptionStringPtr iopt(new OptionString(Option::V4, + DHO_VENDOR_CLASS_IDENTIFIER, + "alpha")); + query->addOption(iopt); + + // Create and add a PRL option to the query + OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + ASSERT_TRUE(prl); + prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS); + prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER); + query->addOption(prl); + + srv.classifyPacket(query); + srv.deferredUnpack(query); + + // Check if the option was (correctly) re-unpacked + vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + OptionCustomPtr custom = boost::dynamic_pointer_cast(vopt); + EXPECT_TRUE(custom); + EXPECT_EQ(1, vopt->getOptions().size()); + + // Pass it to the server and get an offer + Pkt4Ptr offer = srv.processDiscover(query); + + // Check if we get response at all + checkResponse(offer, DHCPOFFER, 1234); + + // Processing should add a vendor-class-identifier (code 60) + OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER); + EXPECT_TRUE(opt); + OptionStringPtr id = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(id); + EXPECT_EQ("alpha", id->getValue()); + + // And a vendor-encapsulated-options (code 43) + opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(opt); + // Verifies the content + const OptionCollection& opts = opt->getOptions(); + ASSERT_EQ(1, opts.size()); + OptionPtr sopt = opts.begin()->second; + ASSERT_TRUE(sopt); + EXPECT_EQ(1, sopt->getType()); + EXPECT_EQ(2 + 4, sopt->len()); + OptionUint32Ptr sopt32 = boost::dynamic_pointer_cast(sopt); + ASSERT_TRUE(sopt32); + EXPECT_EQ(12345678, sopt32->getValue()); +} + +// Verifies option 43 deferred processing (two classes) +TEST_F(Dhcpv4SrvTest, option43Classes) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + NakedDhcpv4Srv srv(0); + + // Two client-class scopes get vendor-encapsulated-options + // (code 43) definition and data. The first matching client-class + // (from a set?) applies. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"code\": 1, " + " \"name\": \"foo\", " + " \"space\": \"alpha\", " + " \"type\": \"uint32\" }," + "{ \"code\": 1, " + " \"name\": \"bar\", " + " \"space\": \"beta\", " + " \"type\": \"uint8\" } ]," + "\"subnet4\": [ " + "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], " + " \"subnet\": \"192.0.2.0/24\" } ]," + "\"client-classes\": [ " + "{ \"name\": \"alpha\", " + " \"test\": \"option[vendor-class-identifier].text == 'alpha'\", " + " \"option-def\": [ " + " { \"code\": 43, " + " \"name\": \"vendor-encapsulated-options\", " + " \"type\": \"empty\", " + " \"encapsulate\": \"alpha\" } ]," + " \"option-data\": [ " + "{ \"name\": \"vendor-encapsulated-options\" }, " + " { \"name\": \"vendor-class-identifier\", " + " \"data\": \"alpha\" }, " + " { \"name\": \"foo\", " + " \"space\": \"alpha\", " + " \"data\": \"12345678\" } ] }," + "{ \"name\": \"beta\", " + " \"test\": \"option[vendor-class-identifier].text == 'beta'\", " + " \"option-def\": [ " + " { \"code\": 43, " + " \"name\": \"vendor-encapsulated-options\", " + " \"type\": \"empty\", " + " \"encapsulate\": \"beta\" } ]," + " \"option-data\": [ " + "{ \"name\": \"vendor-encapsulated-options\" }, " + " { \"name\": \"vendor-class-identifier\", " + " \"data\": \"beta\" }, " + " { \"name\": \"bar\", " + " \"space\": \"beta\", " + " \"data\": \"33\" } ] } ] }"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + CfgMgr::instance().commit(); + + // Create a packet with enough to select the subnet and go through + // the DISCOVER processing + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234)); + query->setRemoteAddr(IOAddress("192.0.2.1")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + + // Create and add a vendor-encapsulated-options (code 43) + OptionBuffer buf; + buf.push_back(0x01); + buf.push_back(0x04); + buf.push_back(0x87); + buf.push_back(0x65); + buf.push_back(0x43); + buf.push_back(0x21); + OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf)); + query->addOption(vopt); + query->deferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS); + + // Create and add a vendor-class-identifier (code 60) + OptionStringPtr iopt(new OptionString(Option::V4, + DHO_VENDOR_CLASS_IDENTIFIER, + "alpha")); + query->addOption(iopt); + + // Create and add a PRL option to the query + OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + ASSERT_TRUE(prl); + prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS); + prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER); + query->addOption(prl); + + srv.classifyPacket(query); + srv.deferredUnpack(query); + + // Check if the option was (correctly) re-unpacked + vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + OptionCustomPtr custom = boost::dynamic_pointer_cast(vopt); + EXPECT_TRUE(custom); + EXPECT_EQ(1, vopt->getOptions().size()); + + // Pass it to the server and get an offer + Pkt4Ptr offer = srv.processDiscover(query); + + // Check if we get response at all + checkResponse(offer, DHCPOFFER, 1234); + + // Processing should add a vendor-class-identifier (code 60) + OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER); + EXPECT_TRUE(opt); + OptionStringPtr id = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(id); + EXPECT_EQ("alpha", id->getValue()); + + // And a vendor-encapsulated-options (code 43) + opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(opt); + // Verifies the content + const OptionCollection& opts = opt->getOptions(); + ASSERT_EQ(1, opts.size()); + OptionPtr sopt = opts.begin()->second; + ASSERT_TRUE(sopt); + EXPECT_EQ(1, sopt->getType()); + EXPECT_EQ(2 + 4, sopt->len()); + OptionUint32Ptr sopt32 = boost::dynamic_pointer_cast(sopt); + ASSERT_TRUE(sopt32); + EXPECT_EQ(12345678, sopt32->getValue()); +} + +// Verifies private option deferred processing +TEST_F(Dhcpv4SrvTest, privateOption) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + NakedDhcpv4Srv srv(0); + + // Same than option43Class but with private options + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"subnet4\": [ " + "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], " + " \"subnet\": \"192.0.2.0/24\" } ]," + "\"client-classes\": [ " + "{ \"name\": \"private\", " + " \"test\": \"option[234].exists\", " + " \"option-def\": [ " + " { \"code\": 245, " + " \"name\": \"privint\", " + " \"type\": \"uint32\" } ]," + " \"option-data\": [ " + " { \"code\": 234, " + " \"data\": \"01\" }, " + " { \"name\": \"privint\", " + " \"data\": \"12345678\" } ] } ] }"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + CfgMgr::instance().commit(); + + // Create a packet with enough to select the subnet and go through + // the DISCOVER processing + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234)); + query->setRemoteAddr(IOAddress("192.0.2.1")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + + // Create and add a private option with code 234 + OptionBuffer buf; + buf.push_back(0x01); + OptionPtr opt1(new Option(Option::V4, 234, buf)); + query->addOption(opt1); + query->deferredOptions().push_back(234); + + // Create and add a private option with code 245 + buf.clear(); + buf.push_back(0x87); + buf.push_back(0x65); + buf.push_back(0x43); + buf.push_back(0x21); + OptionPtr opt2(new Option(Option::V4, 245, buf)); + query->addOption(opt2); + query->deferredOptions().push_back(245); + + + // Create and add a PRL option to the query + OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + ASSERT_TRUE(prl); + prl->addValue(234); + prl->addValue(245); + query->addOption(prl); + + srv.classifyPacket(query); + srv.deferredUnpack(query); + + // Check if the option 245 was re-unpacked + opt2 = query->getOption(245); + OptionUint32Ptr opt32 = boost::dynamic_pointer_cast(opt2); + EXPECT_TRUE(opt32); + + // Pass it to the server and get an offer + Pkt4Ptr offer = srv.processDiscover(query); + + // Check if we get response at all + checkResponse(offer, DHCPOFFER, 1234); + + // Processing should add an option with code 234 + OptionPtr opt = offer->getOption(234); + EXPECT_TRUE(opt); + + // And an option with code 245 + opt = offer->getOption(245); + ASSERT_TRUE(opt); + // Verifies the content + opt32 = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(opt32); + EXPECT_EQ(12345678, opt32->getValue()); +} + // Checks effect of persistency (aka always-true) flag on the PRL TEST_F(Dhcpv4SrvTest, prlPersistency) { IfaceMgrTestConfig test_config(true); diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h index 560d7b1ae1..48e571a447 100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.h +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h @@ -196,6 +196,7 @@ public: using Dhcpv4Srv::sanityCheck; using Dhcpv4Srv::srvidToString; using Dhcpv4Srv::classifyPacket; + using Dhcpv4Srv::deferredUnpack; using Dhcpv4Srv::accept; using Dhcpv4Srv::acceptMessageType; using Dhcpv4Srv::selectSubnet;