Browse Source

[4765] Moved classification specific unit tests to a separate file.

Marcin Siodelski 8 years ago
parent
commit
9c956ea578

+ 1 - 0
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
 

+ 605 - 0
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 <config.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <asiolink/io_address.h>
+#include <string>
+
+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

+ 0 - 495
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