From: Marcin Siodelski Date: Fri, 2 Sep 2016 15:48:58 +0000 (+0200) Subject: [4765] Moved classification specific unit tests to a separate file. X-Git-Tag: trac5006_base~14^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9c956ea57868b5fd9b2a4f1cfd4922c27cc103fd;p=thirdparty%2Fkea.git [4765] Moved classification specific unit tests to a separate file. --- diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am index a85119cc9a..87c619761e 100644 --- a/src/bin/dhcp6/tests/Makefile.am +++ b/src/bin/dhcp6/tests/Makefile.am @@ -92,6 +92,7 @@ dhcp6_unittests_SOURCES += decline_unittest.cc dhcp6_unittests_SOURCES += dhcp6_message_test.cc dhcp6_message_test.h dhcp6_unittests_SOURCES += kea_controller_unittest.cc dhcp6_unittests_SOURCES += dhcp6to4_ipc_unittest.cc +dhcp6_unittests_SOURCES += classify_unittests.cc nodist_dhcp6_unittests_SOURCES = marker_file.h test_libraries.h diff --git a/src/bin/dhcp6/tests/classify_unittests.cc b/src/bin/dhcp6/tests/classify_unittests.cc new file mode 100644 index 0000000000..64197a8a54 --- /dev/null +++ b/src/bin/dhcp6/tests/classify_unittests.cc @@ -0,0 +1,605 @@ +// Copyright (C) 2016 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief Set of JSON configurations used by the classification unit tests. +/// +/// - Configuration 0: +/// - one subnet 3000::/32 used on eth0 interface +/// - prefixes of length 64, delegated from the pool: 2001:db8:3::/48 +/// - the delegated prefix was intentionally selected to not match the +/// subnet prefix, to test that the delegated prefix doesn't need to +/// match the subnet prefix +/// +/// - Configuration 1: +/// - two subnets 2001:db8:1::/48 and 2001:db8:2::/48 +/// - first subnet assigned to interface eth0, another one assigned to eth1 +/// - one pool for subnet in a range of 2001:db8:X::1 - 2001:db8:X::10, +/// where X is 1 or 2 +/// - enables Rapid Commit for the first subnet and disables for the second +/// one +/// - DNS updates enabled +/// +const char* CONFIGS[] = { + // Configuration 0 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pd-pools\": [" + " { \"prefix\": \"2001:db8:3::\", " + " \"prefix-len\": 48, " + " \"delegated-len\": 64" + " } ]," + " \"subnet\": \"3000::/32\", " + " \"interface-id\": \"\"," + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 }", + +// Configuration 1 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth0\"," + " \"rapid-commit\": True" + " }," + " {" + " \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ]," + " \"subnet\": \"2001:db8:2::/48\", " + " \"interface\": \"eth1\"," + " \"rapid-commit\": False" + " } ]," + "\"valid-lifetime\": 4000," + " \"dhcp-ddns\" : {" + " \"enable-updates\" : True, " + " \"qualifying-suffix\" : \"example.com\" }" + "}" +}; + +/// @brief Test fixture class for testing client classification by the +/// DHCPv6 server. +class ClassifyTest : public Dhcpv6SrvTest { +public: + /// @brief Constructor. + /// + /// Sets up fake interfaces. + ClassifyTest() + : Dhcpv6SrvTest(), + iface_mgr_test_config_(true) { + } + + /// @brief Interface Manager's fake configuration control. + IfaceMgrTestConfig iface_mgr_test_config_; +}; + +// Checks if DOCSIS client packets are classified properly +TEST_F(ClassifyTest, docsisClientClassification) { + + NakedDhcpv6Srv srv(0); + + // Let's create a relayed SOLICIT. This particular relayed SOLICIT has + // vendor-class set to docsis3.0 + Pkt6Ptr sol1; + ASSERT_NO_THROW(sol1 = PktCaptures::captureDocsisRelayedSolicit()); + ASSERT_NO_THROW(sol1->unpack()); + + srv.classifyPacket(sol1); + + // It should belong to docsis3.0 class. It should not belong to eRouter1.0 + EXPECT_TRUE(sol1->inClass("VENDOR_CLASS_docsis3.0")); + EXPECT_FALSE(sol1->inClass("eRouter1.0")); + + // Let's get a relayed SOLICIT. This particular relayed SOLICIT has + // vendor-class set to eRouter1.0 + Pkt6Ptr sol2; + ASSERT_NO_THROW(sol2 = PktCaptures::captureeRouterRelayedSolicit()); + ASSERT_NO_THROW(sol2->unpack()); + + srv.classifyPacket(sol2); + + EXPECT_TRUE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0")); + EXPECT_FALSE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0")); +} + +// Checks if client packets are classified properly using match expressions. +// Note option names and definitions are used. +TEST_F(ClassifyTest, matchClassification) { + IfaceMgrTestConfig test_config(true); + + NakedDhcpv6Srv srv(0); + + // The router class matches incoming packets with foo in a host-name + // option (code 1234) and sets an ipv6-forwarding option in the response. + std::string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"name\": \"host-name\"," + " \"code\": 1234," + " \"type\": \"string\" }," + "{ \"name\": \"ipv6-forwarding\"," + " \"code\": 2345," + " \"type\": \"boolean\" }]," + "\"subnet6\": [ " + "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth1\" } ]," + "\"client-classes\": [ " + "{ \"name\": \"router\", " + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"true\" } ], " + " \"test\": \"option[host-name].text == 'foo'\" } ] }"; + ASSERT_NO_THROW(configure(config)); + + // Create packets with enough to select the subnet + OptionPtr clientid = generateClientId(); + Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234)); + query1->setRemoteAddr(IOAddress("fe80::abcd")); + query1->addOption(clientid); + query1->setIface("eth1"); + query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); + Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234)); + query2->setRemoteAddr(IOAddress("fe80::abcd")); + query2->addOption(clientid); + query2->setIface("eth1"); + query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); + Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234)); + query3->setRemoteAddr(IOAddress("fe80::abcd")); + query3->addOption(clientid); + query3->setIface("eth1"); + query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000)); + + // Create and add an ORO option to the first 2 queries + OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(oro); + oro->addValue(2345); + query1->addOption(oro); + query2->addOption(oro); + + // Create and add a host-name option to the first and last queries + OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); + ASSERT_TRUE(hostname); + query1->addOption(hostname); + query3->addOption(hostname); + + // Classify packets + srv.classifyPacket(query1); + srv.classifyPacket(query2); + srv.classifyPacket(query3); + + // Packets with the exception of the second should be in the router class + EXPECT_TRUE(query1->inClass("router")); + EXPECT_FALSE(query2->inClass("router")); + EXPECT_TRUE(query3->inClass("router")); + + // Process queries + Pkt6Ptr response1 = srv.processSolicit(query1); + Pkt6Ptr response2 = srv.processSolicit(query2); + Pkt6Ptr response3 = srv.processSolicit(query3); + + // Classification processing should add an ip-forwarding option + OptionPtr opt1 = response1->getOption(2345); + EXPECT_TRUE(opt1); + + // But only for the first query: second was not classified + OptionPtr opt2 = response2->getOption(2345); + EXPECT_FALSE(opt2); + + // But only for the first query: third has no ORO + OptionPtr opt3 = response3->getOption(2345); + EXPECT_FALSE(opt3); +} + +// Checks subnet options have the priority over class options +TEST_F(ClassifyTest, subnetClassPriority) { + IfaceMgrTestConfig test_config(true); + + NakedDhcpv6Srv srv(0); + + // Subnet sets an ipv6-forwarding option in the response. + // The router class matches incoming packets with foo in a host-name + // option (code 1234) and sets an ipv6-forwarding option in the response. + std::string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"name\": \"host-name\"," + " \"code\": 1234," + " \"type\": \"string\" }," + "{ \"name\": \"ipv6-forwarding\"," + " \"code\": 2345," + " \"type\": \"boolean\" }]," + "\"subnet6\": [ " + "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth1\", " + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"false\" } ] } ], " + "\"client-classes\": [ " + "{ \"name\": \"router\"," + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"true\" } ], " + " \"test\": \"option[1234].text == 'foo'\" } ] }"; + ASSERT_NO_THROW(configure(config)); + + // Create a packet with enough to select the subnet and go through + // the SOLICIT processing + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); + query->setRemoteAddr(IOAddress("fe80::abcd")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); + + // Create and add an ORO option to the query + OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(oro); + oro->addValue(2345); + query->addOption(oro); + + // Create and add a host-name option to the query + OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); + ASSERT_TRUE(hostname); + query->addOption(hostname); + + // Classify the packet + srv.classifyPacket(query); + + // The packet should be in the router class + EXPECT_TRUE(query->inClass("router")); + + // Process the query + Pkt6Ptr response = srv.processSolicit(query); + + // Processing should add an ip-forwarding option + OptionPtr opt = response->getOption(2345); + ASSERT_TRUE(opt); + ASSERT_GT(opt->len(), opt->getHeaderLen()); + // Classification sets the value to true/1, subnet to false/0 + // Here subnet has the priority + EXPECT_EQ(0, opt->getUint8()); +} + +// Checks subnet options have the priority over global options +TEST_F(ClassifyTest, subnetGlobalPriority) { + IfaceMgrTestConfig test_config(true); + + NakedDhcpv6Srv srv(0); + + // Subnet sets an ipv6-forwarding option in the response. + // The router class matches incoming packets with foo in a host-name + // option (code 1234) and sets an ipv6-forwarding option in the response. + std::string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"name\": \"host-name\"," + " \"code\": 1234," + " \"type\": \"string\" }," + "{ \"name\": \"ipv6-forwarding\"," + " \"code\": 2345," + " \"type\": \"boolean\" }]," + "\"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"false\" } ], " + "\"subnet6\": [ " + "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth1\", " + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"false\" } ] } ] }"; + ASSERT_NO_THROW(configure(config)); + + // Create a packet with enough to select the subnet and go through + // the SOLICIT processing + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); + query->setRemoteAddr(IOAddress("fe80::abcd")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); + + // Create and add an ORO option to the query + OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(oro); + oro->addValue(2345); + query->addOption(oro); + + // Create and add a host-name option to the query + OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); + ASSERT_TRUE(hostname); + query->addOption(hostname); + + // Process the query + Pkt6Ptr response = srv.processSolicit(query); + + // Processing should add an ip-forwarding option + OptionPtr opt = response->getOption(2345); + ASSERT_TRUE(opt); + ASSERT_GT(opt->len(), opt->getHeaderLen()); + // Global sets the value to true/1, subnet to false/0 + // Here subnet has the priority + EXPECT_EQ(0, opt->getUint8()); +} + +// Checks class options have the priority over global options +TEST_F(ClassifyTest, classGlobalPriority) { + IfaceMgrTestConfig test_config(true); + + NakedDhcpv6Srv srv(0); + + // A global ipv6-forwarding option is set in the response. + // The router class matches incoming packets with foo in a host-name + // option (code 1234) and sets an ipv6-forwarding option in the response. + std::string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"name\": \"host-name\"," + " \"code\": 1234," + " \"type\": \"string\" }," + "{ \"name\": \"ipv6-forwarding\"," + " \"code\": 2345," + " \"type\": \"boolean\" }]," + "\"subnet6\": [ " + "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth1\" } ]," + "\"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"false\" } ], " + "\"client-classes\": [ " + "{ \"name\": \"router\"," + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"true\" } ], " + " \"test\": \"option[1234].text == 'foo'\" } ] }"; + ASSERT_NO_THROW(configure(config)); + + // Create a packet with enough to select the subnet and go through + // the SOLICIT processing + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); + query->setRemoteAddr(IOAddress("fe80::abcd")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); + + // Create and add an ORO option to the query + OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(oro); + oro->addValue(2345); + query->addOption(oro); + + // Create and add a host-name option to the query + OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); + ASSERT_TRUE(hostname); + query->addOption(hostname); + + // Classify the packet + srv.classifyPacket(query); + + // The packet should be in the router class + EXPECT_TRUE(query->inClass("router")); + + // Process the query + Pkt6Ptr response = srv.processSolicit(query); + + // Processing should add an ip-forwarding option + OptionPtr opt = response->getOption(2345); + ASSERT_TRUE(opt); + ASSERT_GT(opt->len(), opt->getHeaderLen()); + // Classification sets the value to true/1, global to false/0 + // Here class has the priority + EXPECT_NE(0, opt->getUint8()); +} + +// Checks if the client-class field is indeed used for subnet selection. +// Note that packet classification is already checked in ClassifyTest +// .*Classification above. +TEST_F(ClassifyTest, clientClassifySubnet) { + + // This test configures 2 subnets. We actually only need the + // first one, but since there's still this ugly hack that picks + // the pool if there is only one, we must use more than one + // subnet. That ugly hack will be removed in #3242, currently + // under review. + + // The second subnet does not play any role here. The client's + // IP address belongs to the first subnet, so only that first + // subnet is being tested. + std::string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ " + " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"client-class\": \"foo\" " + " }, " + " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ]," + " \"subnet\": \"2001:db8:2::/48\", " + " \"client-class\": \"xyzzy\" " + " } " + "]," + "\"valid-lifetime\": 4000 }"; + + ASSERT_NO_THROW(configure(config)); + + Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)); + sol->setRemoteAddr(IOAddress("2001:db8:1::3")); + sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); + OptionPtr clientid = generateClientId(); + sol->addOption(clientid); + + // This discover does not belong to foo class, so it will not + // be serviced + EXPECT_FALSE(srv_.selectSubnet(sol)); + + // Let's add the packet to bar class and try again. + sol->addClass("bar"); + + // Still not supported, because it belongs to wrong class. + EXPECT_FALSE(srv_.selectSubnet(sol)); + + // Let's add it to matching class. + sol->addClass("foo"); + + // This time it should work + EXPECT_TRUE(srv_.selectSubnet(sol)); +} + +// Tests whether a packet with custom vendor-class (not erouter or docsis) +// is classified properly. +TEST_F(ClassifyTest, vendorClientClassification2) { + NakedDhcpv6Srv srv(0); + + // Let's create a SOLICIT. + Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)); + sol->setRemoteAddr(IOAddress("2001:db8:1::3")); + sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); + OptionPtr clientid = generateClientId(); + sol->addOption(clientid); + + // Now let's add a vendor-class with id=1234 and content "foo" + OptionVendorClassPtr vendor_class(new OptionVendorClass(Option::V6, 1234)); + OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); + tuple = "foo"; + vendor_class->addTuple(tuple); + sol->addOption(vendor_class); + + // Now the server classifies the packet. + srv.classifyPacket(sol); + + // The packet should now belong to VENDOR_CLASS_foo. + EXPECT_TRUE(sol->inClass(srv.VENDOR_CLASS_PREFIX + "foo")); + + // It should not belong to "foo" + EXPECT_FALSE(sol->inClass("foo")); +} + +// Checks if relay IP address specified in the relay-info structure can be +// used together with client-classification. +TEST_F(ClassifyTest, relayOverrideAndClientClass) { + + // This test configures 2 subnets. They both are on the same link, so they + // have the same relay-ip address. Furthermore, the first subnet is + // reserved for clients that belong to class "foo". + std::string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ " + " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"client-class\": \"foo\", " + " \"relay\": { " + " \"ip-address\": \"2001:db8:3::1\"" + " }" + " }, " + " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ]," + " \"subnet\": \"2001:db8:2::/48\", " + " \"relay\": { " + " \"ip-address\": \"2001:db8:3::1\"" + " }" + " } " + "]," + "\"valid-lifetime\": 4000 }"; + + // Use this config to set up the server + ASSERT_NO_THROW(configure(config)); + + // Let's get the subnet configuration objects + const Subnet6Collection* subnets = + CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll(); + ASSERT_EQ(2, subnets->size()); + + // Let's get them for easy reference + Subnet6Ptr subnet1 = (*subnets)[0]; + Subnet6Ptr subnet2 = (*subnets)[1]; + ASSERT_TRUE(subnet1); + ASSERT_TRUE(subnet2); + + Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)); + sol->setRemoteAddr(IOAddress("2001:db8:1::3")); + sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); + OptionPtr clientid = generateClientId(); + sol->addOption(clientid); + + // Now pretend the packet came via one relay. + Pkt6::RelayInfo relay; + relay.linkaddr_ = IOAddress("2001:db8:3::1"); + relay.peeraddr_ = IOAddress("fe80::1"); + + sol->relay_info_.push_back(relay); + + // This packet does not belong to class foo, so it should be rejected in + // subnet[0], even though the relay-ip matches. It should be accepted in + // subnet[1], because the subnet matches and there are no class + // requirements. + EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol)); + + // Now let's add this packet to class foo and recheck. This time it should + // be accepted in the first subnet, because both class and relay-ip match. + sol->addClass("foo"); + EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol)); +} + + +} // end of anonymous namespace diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 82fcaf6163..99e83b774b 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -1721,432 +1721,6 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) { ASSERT_EQ(0, rcode_); } -// Checks if DOCSIS client packets are classified properly -TEST_F(Dhcpv6SrvTest, docsisClientClassification) { - - NakedDhcpv6Srv srv(0); - - // Let's create a relayed SOLICIT. This particular relayed SOLICIT has - // vendor-class set to docsis3.0 - Pkt6Ptr sol1; - ASSERT_NO_THROW(sol1 = PktCaptures::captureDocsisRelayedSolicit()); - ASSERT_NO_THROW(sol1->unpack()); - - srv.classifyPacket(sol1); - - // It should belong to docsis3.0 class. It should not belong to eRouter1.0 - EXPECT_TRUE(sol1->inClass("VENDOR_CLASS_docsis3.0")); - EXPECT_FALSE(sol1->inClass("eRouter1.0")); - - // Let's get a relayed SOLICIT. This particular relayed SOLICIT has - // vendor-class set to eRouter1.0 - Pkt6Ptr sol2; - ASSERT_NO_THROW(sol2 = PktCaptures::captureeRouterRelayedSolicit()); - ASSERT_NO_THROW(sol2->unpack()); - - srv.classifyPacket(sol2); - - EXPECT_TRUE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0")); - EXPECT_FALSE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0")); -} - -// Checks if client packets are classified properly using match expressions. -// Note option names and definitions are used. -TEST_F(Dhcpv6SrvTest, matchClassification) { - IfaceMgrTestConfig test_config(true); - - NakedDhcpv6Srv srv(0); - - // The router class matches incoming packets with foo in a host-name - // option (code 1234) and sets an ipv6-forwarding option in the response. - string config = "{ \"interfaces-config\": {" - " \"interfaces\": [ \"*\" ] }, " - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"valid-lifetime\": 4000, " - "\"option-def\": [ " - "{ \"name\": \"host-name\"," - " \"code\": 1234," - " \"type\": \"string\" }," - "{ \"name\": \"ipv6-forwarding\"," - " \"code\": 2345," - " \"type\": \"boolean\" }]," - "\"subnet6\": [ " - "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " - " \"subnet\": \"2001:db8:1::/48\", " - " \"interface\": \"eth1\" } ]," - "\"client-classes\": [ " - "{ \"name\": \"router\", " - " \"option-data\": [" - " { \"name\": \"ipv6-forwarding\", " - " \"data\": \"true\" } ], " - " \"test\": \"option[host-name].text == 'foo'\" } ] }"; - ASSERT_NO_THROW(configure(config)); - - // Create packets with enough to select the subnet - OptionPtr clientid = generateClientId(); - Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234)); - query1->setRemoteAddr(IOAddress("fe80::abcd")); - query1->addOption(clientid); - query1->setIface("eth1"); - query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); - Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234)); - query2->setRemoteAddr(IOAddress("fe80::abcd")); - query2->addOption(clientid); - query2->setIface("eth1"); - query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); - Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234)); - query3->setRemoteAddr(IOAddress("fe80::abcd")); - query3->addOption(clientid); - query3->setIface("eth1"); - query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000)); - - // Create and add an ORO option to the first 2 queries - OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); - ASSERT_TRUE(oro); - oro->addValue(2345); - query1->addOption(oro); - query2->addOption(oro); - - // Create and add a host-name option to the first and last queries - OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); - ASSERT_TRUE(hostname); - query1->addOption(hostname); - query3->addOption(hostname); - - // Classify packets - srv.classifyPacket(query1); - srv.classifyPacket(query2); - srv.classifyPacket(query3); - - // Packets with the exception of the second should be in the router class - EXPECT_TRUE(query1->inClass("router")); - EXPECT_FALSE(query2->inClass("router")); - EXPECT_TRUE(query3->inClass("router")); - - // Process queries - Pkt6Ptr response1 = srv.processSolicit(query1); - Pkt6Ptr response2 = srv.processSolicit(query2); - Pkt6Ptr response3 = srv.processSolicit(query3); - - // Classification processing should add an ip-forwarding option - OptionPtr opt1 = response1->getOption(2345); - EXPECT_TRUE(opt1); - - // But only for the first query: second was not classified - OptionPtr opt2 = response2->getOption(2345); - EXPECT_FALSE(opt2); - - // But only for the first query: third has no ORO - OptionPtr opt3 = response3->getOption(2345); - EXPECT_FALSE(opt3); -} - -// Checks subnet options have the priority over class options -TEST_F(Dhcpv6SrvTest, subnetClassPriority) { - IfaceMgrTestConfig test_config(true); - - NakedDhcpv6Srv srv(0); - - // Subnet sets an ipv6-forwarding option in the response. - // The router class matches incoming packets with foo in a host-name - // option (code 1234) and sets an ipv6-forwarding option in the response. - string config = "{ \"interfaces-config\": {" - " \"interfaces\": [ \"*\" ] }, " - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"valid-lifetime\": 4000, " - "\"option-def\": [ " - "{ \"name\": \"host-name\"," - " \"code\": 1234," - " \"type\": \"string\" }," - "{ \"name\": \"ipv6-forwarding\"," - " \"code\": 2345," - " \"type\": \"boolean\" }]," - "\"subnet6\": [ " - "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " - " \"subnet\": \"2001:db8:1::/48\", " - " \"interface\": \"eth1\", " - " \"option-data\": [" - " { \"name\": \"ipv6-forwarding\", " - " \"data\": \"false\" } ] } ], " - "\"client-classes\": [ " - "{ \"name\": \"router\"," - " \"option-data\": [" - " { \"name\": \"ipv6-forwarding\", " - " \"data\": \"true\" } ], " - " \"test\": \"option[1234].text == 'foo'\" } ] }"; - ASSERT_NO_THROW(configure(config)); - - // Create a packet with enough to select the subnet and go through - // the SOLICIT processing - Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); - query->setRemoteAddr(IOAddress("fe80::abcd")); - OptionPtr clientid = generateClientId(); - query->addOption(clientid); - query->setIface("eth1"); - query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); - - // Create and add an ORO option to the query - OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); - ASSERT_TRUE(oro); - oro->addValue(2345); - query->addOption(oro); - - // Create and add a host-name option to the query - OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); - ASSERT_TRUE(hostname); - query->addOption(hostname); - - // Classify the packet - srv.classifyPacket(query); - - // The packet should be in the router class - EXPECT_TRUE(query->inClass("router")); - - // Process the query - Pkt6Ptr response = srv.processSolicit(query); - - // Processing should add an ip-forwarding option - OptionPtr opt = response->getOption(2345); - ASSERT_TRUE(opt); - ASSERT_GT(opt->len(), opt->getHeaderLen()); - // Classification sets the value to true/1, subnet to false/0 - // Here subnet has the priority - EXPECT_EQ(0, opt->getUint8()); -} - -// Checks subnet options have the priority over global options -TEST_F(Dhcpv6SrvTest, subnetGlobalPriority) { - IfaceMgrTestConfig test_config(true); - - NakedDhcpv6Srv srv(0); - - // Subnet sets an ipv6-forwarding option in the response. - // The router class matches incoming packets with foo in a host-name - // option (code 1234) and sets an ipv6-forwarding option in the response. - string config = "{ \"interfaces-config\": {" - " \"interfaces\": [ \"*\" ] }, " - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"valid-lifetime\": 4000, " - "\"option-def\": [ " - "{ \"name\": \"host-name\"," - " \"code\": 1234," - " \"type\": \"string\" }," - "{ \"name\": \"ipv6-forwarding\"," - " \"code\": 2345," - " \"type\": \"boolean\" }]," - "\"option-data\": [" - " { \"name\": \"ipv6-forwarding\", " - " \"data\": \"false\" } ], " - "\"subnet6\": [ " - "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " - " \"subnet\": \"2001:db8:1::/48\", " - " \"interface\": \"eth1\", " - " \"option-data\": [" - " { \"name\": \"ipv6-forwarding\", " - " \"data\": \"false\" } ] } ] }"; - ASSERT_NO_THROW(configure(config)); - - // Create a packet with enough to select the subnet and go through - // the SOLICIT processing - Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); - query->setRemoteAddr(IOAddress("fe80::abcd")); - OptionPtr clientid = generateClientId(); - query->addOption(clientid); - query->setIface("eth1"); - query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); - - // Create and add an ORO option to the query - OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); - ASSERT_TRUE(oro); - oro->addValue(2345); - query->addOption(oro); - - // Create and add a host-name option to the query - OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); - ASSERT_TRUE(hostname); - query->addOption(hostname); - - // Process the query - Pkt6Ptr response = srv.processSolicit(query); - - // Processing should add an ip-forwarding option - OptionPtr opt = response->getOption(2345); - ASSERT_TRUE(opt); - ASSERT_GT(opt->len(), opt->getHeaderLen()); - // Global sets the value to true/1, subnet to false/0 - // Here subnet has the priority - EXPECT_EQ(0, opt->getUint8()); -} - -// Checks class options have the priority over global options -TEST_F(Dhcpv6SrvTest, classGlobalPriority) { - IfaceMgrTestConfig test_config(true); - - NakedDhcpv6Srv srv(0); - - // A global ipv6-forwarding option is set in the response. - // The router class matches incoming packets with foo in a host-name - // option (code 1234) and sets an ipv6-forwarding option in the response. - string config = "{ \"interfaces-config\": {" - " \"interfaces\": [ \"*\" ] }, " - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"valid-lifetime\": 4000, " - "\"option-def\": [ " - "{ \"name\": \"host-name\"," - " \"code\": 1234," - " \"type\": \"string\" }," - "{ \"name\": \"ipv6-forwarding\"," - " \"code\": 2345," - " \"type\": \"boolean\" }]," - "\"subnet6\": [ " - "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " - " \"subnet\": \"2001:db8:1::/48\", " - " \"interface\": \"eth1\" } ]," - "\"option-data\": [" - " { \"name\": \"ipv6-forwarding\", " - " \"data\": \"false\" } ], " - "\"client-classes\": [ " - "{ \"name\": \"router\"," - " \"option-data\": [" - " { \"name\": \"ipv6-forwarding\", " - " \"data\": \"true\" } ], " - " \"test\": \"option[1234].text == 'foo'\" } ] }"; - ASSERT_NO_THROW(configure(config)); - - // Create a packet with enough to select the subnet and go through - // the SOLICIT processing - Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); - query->setRemoteAddr(IOAddress("fe80::abcd")); - OptionPtr clientid = generateClientId(); - query->addOption(clientid); - query->setIface("eth1"); - query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); - - // Create and add an ORO option to the query - OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); - ASSERT_TRUE(oro); - oro->addValue(2345); - query->addOption(oro); - - // Create and add a host-name option to the query - OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); - ASSERT_TRUE(hostname); - query->addOption(hostname); - - // Classify the packet - srv.classifyPacket(query); - - // The packet should be in the router class - EXPECT_TRUE(query->inClass("router")); - - // Process the query - Pkt6Ptr response = srv.processSolicit(query); - - // Processing should add an ip-forwarding option - OptionPtr opt = response->getOption(2345); - ASSERT_TRUE(opt); - ASSERT_GT(opt->len(), opt->getHeaderLen()); - // Classification sets the value to true/1, global to false/0 - // Here class has the priority - EXPECT_NE(0, opt->getUint8()); -} - -// Checks if the client-class field is indeed used for subnet selection. -// Note that packet classification is already checked in Dhcpv6SrvTest -// .*Classification above. -TEST_F(Dhcpv6SrvTest, clientClassifySubnet) { - - // This test configures 2 subnets. We actually only need the - // first one, but since there's still this ugly hack that picks - // the pool if there is only one, we must use more than one - // subnet. That ugly hack will be removed in #3242, currently - // under review. - - // The second subnet does not play any role here. The client's - // IP address belongs to the first subnet, so only that first - // subnet is being tested. - string config = "{ \"interfaces-config\": {" - " \"interfaces\": [ \"*\" ]" - "}," - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"subnet6\": [ " - " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," - " \"subnet\": \"2001:db8:1::/48\", " - " \"client-class\": \"foo\" " - " }, " - " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ]," - " \"subnet\": \"2001:db8:2::/48\", " - " \"client-class\": \"xyzzy\" " - " } " - "]," - "\"valid-lifetime\": 4000 }"; - - ASSERT_NO_THROW(configure(config)); - - Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)); - sol->setRemoteAddr(IOAddress("2001:db8:1::3")); - sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); - OptionPtr clientid = generateClientId(); - sol->addOption(clientid); - - // This discover does not belong to foo class, so it will not - // be serviced - EXPECT_FALSE(srv_.selectSubnet(sol)); - - // Let's add the packet to bar class and try again. - sol->addClass("bar"); - - // Still not supported, because it belongs to wrong class. - EXPECT_FALSE(srv_.selectSubnet(sol)); - - // Let's add it to matching class. - sol->addClass("foo"); - - // This time it should work - EXPECT_TRUE(srv_.selectSubnet(sol)); -} - -// Tests whether a packet with custom vendor-class (not erouter or docsis) -// is classified properly. -TEST_F(Dhcpv6SrvTest, vendorClientClassification2) { - NakedDhcpv6Srv srv(0); - - // Let's create a SOLICIT. - Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)); - sol->setRemoteAddr(IOAddress("2001:db8:1::3")); - sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); - OptionPtr clientid = generateClientId(); - sol->addOption(clientid); - - // Now let's add a vendor-class with id=1234 and content "foo" - OptionVendorClassPtr vendor_class(new OptionVendorClass(Option::V6, 1234)); - OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES); - tuple = "foo"; - vendor_class->addTuple(tuple); - sol->addOption(vendor_class); - - // Now the server classifies the packet. - srv.classifyPacket(sol); - - // The packet should now belong to VENDOR_CLASS_foo. - EXPECT_TRUE(sol->inClass(srv.VENDOR_CLASS_PREFIX + "foo")); - - // It should not belong to "foo" - EXPECT_FALSE(sol->inClass("foo")); - -} - - // This test checks that the server will handle a Solicit with the Vendor Class // having a length of 4 (enterprise-id only). TEST_F(Dhcpv6SrvTest, cableLabsShortVendorClass) { @@ -2251,75 +1825,6 @@ TEST_F(Dhcpv6SrvTest, relayOverride) { EXPECT_FALSE(srv_.selectSubnet(sol)); } -// Checks if relay IP address specified in the relay-info structure can be -// used together with client-classification. -TEST_F(Dhcpv6SrvTest, relayOverrideAndClientClass) { - - // This test configures 2 subnets. They both are on the same link, so they - // have the same relay-ip address. Furthermore, the first subnet is - // reserved for clients that belong to class "foo". - string config = "{ \"interfaces-config\": {" - " \"interfaces\": [ \"*\" ]" - "}," - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"subnet6\": [ " - " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," - " \"subnet\": \"2001:db8:1::/48\", " - " \"client-class\": \"foo\", " - " \"relay\": { " - " \"ip-address\": \"2001:db8:3::1\"" - " }" - " }, " - " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ]," - " \"subnet\": \"2001:db8:2::/48\", " - " \"relay\": { " - " \"ip-address\": \"2001:db8:3::1\"" - " }" - " } " - "]," - "\"valid-lifetime\": 4000 }"; - - // Use this config to set up the server - ASSERT_NO_THROW(configure(config)); - - // Let's get the subnet configuration objects - const Subnet6Collection* subnets = - CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll(); - ASSERT_EQ(2, subnets->size()); - - // Let's get them for easy reference - Subnet6Ptr subnet1 = (*subnets)[0]; - Subnet6Ptr subnet2 = (*subnets)[1]; - ASSERT_TRUE(subnet1); - ASSERT_TRUE(subnet2); - - Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)); - sol->setRemoteAddr(IOAddress("2001:db8:1::3")); - sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); - OptionPtr clientid = generateClientId(); - sol->addOption(clientid); - - // Now pretend the packet came via one relay. - Pkt6::RelayInfo relay; - relay.linkaddr_ = IOAddress("2001:db8:3::1"); - relay.peeraddr_ = IOAddress("fe80::1"); - - sol->relay_info_.push_back(relay); - - // This packet does not belong to class foo, so it should be rejected in - // subnet[0], even though the relay-ip matches. It should be accepted in - // subnet[1], because the subnet matches and there are no class - // requirements. - EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol)); - - // Now let's add this packet to class foo and recheck. This time it should - // be accepted in the first subnet, because both class and relay-ip match. - sol->addClass("foo"); - EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol)); -} - /// @brief Creates RSOO option with suboptions /// /// Creates Relay-Supplied Options option that includes nested options. The