diff --git a/doc/guide/classify.xml b/doc/guide/classify.xml index bd5ab03907..49fd96d01f 100644 --- a/doc/guide/classify.xml +++ b/doc/guide/classify.xml @@ -61,6 +61,13 @@ + + + Beginning with 1.4 client classes follow now the insertion order + (vs. alphabetical order in previous versions). + + + When determining which options to include in the response the server will examine the union of options from all of the assigned classes. In the case two or more @@ -223,6 +230,13 @@ If the option with given code is present in the packet "true" else "false" + + Client class membership + member('foobar') + 'true' + If the packet belongs to the given client class + "true" else "false" + DHCPv4 relay agent sub-option relay4[123].hex @@ -472,6 +486,15 @@ in the incoming packet. It can be used with empty options. + + "member('foobar')" checks if the packet belongs to the client + class "foobar". To avoid dependency loops the configuration file + parser checks if client classes were already defined or are + built-in, i.e., beginning by "VENDOR_CLASS_", + "AFTER__" (for the to come "after" hook) and + "EXTERNAL_". + + "relay4[code].hex" attempts to extract the value of the sub-option "code" from the option inserted as the DHCPv4 Relay Agent Information diff --git a/src/bin/dhcp4/tests/get_config_unittest.cc b/src/bin/dhcp4/tests/get_config_unittest.cc index 95e2c00464..16f9473a4d 100644 --- a/src/bin/dhcp4/tests/get_config_unittest.cc +++ b/src/bin/dhcp4/tests/get_config_unittest.cc @@ -6171,7 +6171,7 @@ const char* UNPARSED_CONFIGS[] = { " },\n" " {\n" " \"boot-file-name\": \"\",\n" -" \"name\": \"three\",\n" +" \"name\": \"two\",\n" " \"next-server\": \"0.0.0.0\",\n" " \"option-data\": [ ],\n" " \"option-def\": [ ],\n" @@ -6179,7 +6179,7 @@ const char* UNPARSED_CONFIGS[] = { " },\n" " {\n" " \"boot-file-name\": \"\",\n" -" \"name\": \"two\",\n" +" \"name\": \"three\",\n" " \"next-server\": \"0.0.0.0\",\n" " \"option-data\": [ ],\n" " \"option-def\": [ ],\n" diff --git a/src/bin/dhcp6/tests/classify_unittests.cc b/src/bin/dhcp6/tests/classify_unittests.cc index d8967e8ffe..99058ac014 100644 --- a/src/bin/dhcp6/tests/classify_unittests.cc +++ b/src/bin/dhcp6/tests/classify_unittests.cc @@ -992,5 +992,137 @@ TEST_F(ClassifyTest, clientClassesInHostReservations) { "2001:db8:1::100")); } +// Check classification using membership expressions. +TEST_F(ClassifyTest, member) { + 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\": \"not-foo\", " + " \"test\": \"not (option[host-name].text == 'foo')\"" + "}," + "{ \"name\": \"foo\", " + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"true\" } ], " + " \"test\": \"not member('not-foo')\"" + "}," + "{ \"name\": \"bar\", " + " \"test\": \"option[host-name].text == 'bar'\"" + "}," + "{ \"name\": \"baz\", " + " \"test\": \"option[host-name].text == 'baz'\"" + "}," + "{ \"name\": \"barz\", " + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"false\" } ], " + " \"test\": \"member('bar') or member('baz')\" } ] }"; + + 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 queries + OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(oro); + oro->addValue(2345); + query1->addOption(oro); + query2->addOption(oro); + query3->addOption(oro); + + // Create and add a host-name option to the first and last queries + OptionStringPtr hostname1(new OptionString(Option::V6, 1234, "foo")); + ASSERT_TRUE(hostname1); + query1->addOption(hostname1); + OptionStringPtr hostname3(new OptionString(Option::V6, 1234, "baz")); + ASSERT_TRUE(hostname3); + query3->addOption(hostname3); + + // Classify packets + srv.classifyPacket(query1); + srv.classifyPacket(query2); + srv.classifyPacket(query3); + + // Check classes + EXPECT_FALSE(query1->inClass("not-foo")); + EXPECT_TRUE(query1->inClass("foo")); + EXPECT_FALSE(query1->inClass("bar")); + EXPECT_FALSE(query1->inClass("baz")); + EXPECT_FALSE(query1->inClass("barz")); + + EXPECT_TRUE(query2->inClass("not-foo")); + EXPECT_FALSE(query2->inClass("foo")); + EXPECT_FALSE(query2->inClass("bar")); + EXPECT_FALSE(query2->inClass("baz")); + EXPECT_FALSE(query2->inClass("barz")); + + EXPECT_TRUE(query3->inClass("not-foo")); + EXPECT_FALSE(query3->inClass("foo")); + EXPECT_FALSE(query3->inClass("bar")); + EXPECT_TRUE(query3->inClass("baz")); + EXPECT_TRUE(query3->inClass("barz")); + + // 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); + OptionCustomPtr ipf1 = + boost::dynamic_pointer_cast(opt1); + ASSERT_TRUE(ipf1); + EXPECT_TRUE(ipf1->readBoolean()); + + // But not the second query which was not classified + OptionPtr opt2 = response2->getOption(2345); + EXPECT_FALSE(opt2); + + // The third has the option but with another value + OptionPtr opt3 = response3->getOption(2345); + EXPECT_TRUE(opt3); + OptionCustomPtr ipf3 = + boost::dynamic_pointer_cast(opt3); + ASSERT_TRUE(ipf3); + EXPECT_FALSE(ipf3->readBoolean()); +} } // end of anonymous namespace diff --git a/src/bin/dhcp6/tests/get_config_unittest.cc b/src/bin/dhcp6/tests/get_config_unittest.cc index 05996adee3..403bbb79d8 100644 --- a/src/bin/dhcp6/tests/get_config_unittest.cc +++ b/src/bin/dhcp6/tests/get_config_unittest.cc @@ -5467,11 +5467,11 @@ const char* UNPARSED_CONFIGS[] = { " \"option-data\": [ ]\n" " },\n" " {\n" -" \"name\": \"three\",\n" +" \"name\": \"two\",\n" " \"option-data\": [ ]\n" " },\n" " {\n" -" \"name\": \"two\",\n" +" \"name\": \"three\",\n" " \"option-data\": [ ]\n" " }\n" " ],\n"