]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[606-drop-packets-in-drop-class] Added DROP class (including doc and unit tests)
authorFrancis Dupont <fdupont@isc.org>
Tue, 11 Jun 2019 13:45:50 +0000 (15:45 +0200)
committerFrancis Dupont <fdupont@isc.org>
Tue, 16 Jul 2019 09:11:19 +0000 (05:11 -0400)
17 files changed:
doc/guide/classify.xml
doc/guide/dhcp4-srv.xml
doc/guide/dhcp6-srv.xml
src/bin/dhcp4/dhcp4_messages.cc
src/bin/dhcp4/dhcp4_messages.h
src/bin/dhcp4/dhcp4_messages.mes
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/tests/classify_unittest.cc
src/bin/dhcp6/dhcp6_messages.cc
src/bin/dhcp6/dhcp6_messages.h
src/bin/dhcp6/dhcp6_messages.mes
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/tests/classify_unittests.cc
src/lib/dhcpsrv/client_class_def.cc
src/lib/dhcpsrv/client_class_def.h
src/lib/dhcpsrv/parsers/client_class_def_parser.cc
src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc

index 4755913c89ce7e7960e20c7bd06bd11101f984cd..4ca827d436b07845888c5cc7c10331333cf0df2c 100644 (file)
       resort) definition.
       </para></listitem>
       <listitem><para>
+      When the incoming packet belongs the special DROP class it is
+      dropped and an informational message is logged with the packet
+      information.
+      </para></listitem>
+      <listitem><para>
       A subnet is chosen, possibly based on the class information when
       some subnets are reserved. More precisely: when choosing a subnet,
       the server iterates over all of the subnets that are
       request") evaluation - are processed in the order they are defined
       in the configuration; the boolean expression is evaluated and,
       if it returns true ("match"), the incoming packet is associated
-      with the class. After a subnet is selected, the server determines whether there is a reservation
-      for a given client. Therefore, it
+      with the class. After a subnet is selected, the server determines
+      whether there is a reservation for a given client. Therefore, it
       is not possible to use KNOWN/UNKNOWN classes to select a shared
-      network or a subnet.
+      network or a subnet, nor to make the DROP class dependent of
+      KNOWN/UNKNOWN classes.
       </para></listitem>
       <listitem><para>
       If needed, addresses and prefixes from pools are assigned,
index 1e9b5384823436a31f60df3176efbc16fbe6a1ac..8b932ce243e88fc5424d10d342639db265a5a917 100644 (file)
@@ -2534,7 +2534,8 @@ It is merely echoed by the server.
       The first step is to assess an incoming packet and assign it to
       zero or more classes.
       The second step is to choose a subnet, possibly based on the
-      class information.
+      class information. When the incoming packet is in the "DROP"
+      special class it is dropped and an information message logged.
       The next step is to evaluate class expressions depending on the
       built-in "KNOWN"/"UNKNOWN" classes after host reservation lookup,
       using them for pool selection and assigning classes from host reservations.
index c17bd8859110112b3a8587cbf42a2570238fc7a8..78784183d1eb02d7d45e89cf876ded2facbac394 100644 (file)
@@ -2429,7 +2429,8 @@ should include options from the new option space:
       The first step is to assess an incoming packet and assign it to
       zero or more classes.
       Next, a subnet is chosen, possibly based on the
-      class information.
+      class information. When the incoming packet is inthe "DROP"
+      special class it is dropped and an information message logged.
       After that, class expressions are evaluated depending on the
       built-in "KNOWN"/"UNKNOWN" classes after host reservation lookup,
       using them for pool/pd-pool selection and assigning classes from host
index 44c0f7538a01b25a6939a6b73e565f7f04c68516..129aaddeb15f747fe58f276b4355d973f444b256 100644 (file)
@@ -93,6 +93,7 @@ extern const isc::log::MessageID DHCP4_PACKET_DROP_0006 = "DHCP4_PACKET_DROP_000
 extern const isc::log::MessageID DHCP4_PACKET_DROP_0007 = "DHCP4_PACKET_DROP_0007";
 extern const isc::log::MessageID DHCP4_PACKET_DROP_0008 = "DHCP4_PACKET_DROP_0008";
 extern const isc::log::MessageID DHCP4_PACKET_DROP_0009 = "DHCP4_PACKET_DROP_0009";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_DROP_CLASS = "DHCP4_PACKET_DROP_DROP_CLASS";
 extern const isc::log::MessageID DHCP4_PACKET_NAK_0001 = "DHCP4_PACKET_NAK_0001";
 extern const isc::log::MessageID DHCP4_PACKET_NAK_0002 = "DHCP4_PACKET_NAK_0002";
 extern const isc::log::MessageID DHCP4_PACKET_NAK_0003 = "DHCP4_PACKET_NAK_0003";
@@ -228,6 +229,7 @@ const char* values[] = {
     "DHCP4_PACKET_DROP_0007", "%1: failed to process packet: %2",
     "DHCP4_PACKET_DROP_0008", "%1: DHCP service is globally disabled",
     "DHCP4_PACKET_DROP_0009", "%1: Option 53 missing (no DHCP message type), is this a BOOTP packet?",
+    "DHCP4_PACKET_DROP_DROP_CLASS", "dropped as member of the special DROP class: %1",
     "DHCP4_PACKET_NAK_0001", "%1: failed to select a subnet for incoming packet, src %2, type %3",
     "DHCP4_PACKET_NAK_0002", "%1: invalid address %2 requested by INIT-REBOOT",
     "DHCP4_PACKET_NAK_0003", "%1: failed to advertise a lease, client sent ciaddr %2, requested-ip-address %3",
index b8c7ff185c1f236eaf1d4ba8218fa6aea7181699..5d4480cc3d2311316a6b29e5590639080cd34407 100644 (file)
@@ -94,6 +94,7 @@ extern const isc::log::MessageID DHCP4_PACKET_DROP_0006;
 extern const isc::log::MessageID DHCP4_PACKET_DROP_0007;
 extern const isc::log::MessageID DHCP4_PACKET_DROP_0008;
 extern const isc::log::MessageID DHCP4_PACKET_DROP_0009;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_DROP_CLASS;
 extern const isc::log::MessageID DHCP4_PACKET_NAK_0001;
 extern const isc::log::MessageID DHCP4_PACKET_NAK_0002;
 extern const isc::log::MessageID DHCP4_PACKET_NAK_0003;
index 40a02f109b8208299b560097fd247ab258397c62..4bf6ea1bf51f51d662b54a9752b61a655c706444 100644 (file)
@@ -509,6 +509,10 @@ This debug message is issued when a packet is dropped because it did contain
 option 53 and thus has no DHCP message type. The most likely explanation is
 that it was BOOTP packet.
 
+% DHCP4_PACKET_DROP_DROP_CLASS dropped as member of the special DROP class: %1
+This informational message is emitted when an incoming packet was classified
+into the special DROP class and dropped. The packet details are displayed.
+
 % DHCP4_PACKET_NAK_0001 %1: failed to select a subnet for incoming packet, src %2, type %3
 This error message is output when a packet was received from a subnet
 for which the DHCPv4 server has not been configured. The most probable
index d434691c732c5a7dc98d4e414e11c552c14bb0f1..27c32ff4b76929488fd2948c5f8426ea93b81ecc 100644 (file)
@@ -1006,6 +1006,14 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp, bool allow_packet_park) {
         callout_handle->getArgument("query4", query);
     }
 
+    // Check the DROP special class.
+    if (query->inClass("DROP")) {
+        LOG_INFO(packet4_logger, DHCP4_PACKET_DROP_DROP_CLASS)
+            .arg(query->toText());
+        // increase pkt4-receive-drop stats?
+        return;
+    }
+
     AllocEngine::ClientContext4Ptr ctx;
 
     try {
index 713fd8f4dfb4fc1325aec5920d6bf63bbd9447bd..5c1f1047c7dd8fa208e9d218da0df683953d1622 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2019 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
@@ -75,6 +75,12 @@ namespace {
 ///      option[93].hex == 0x0009 aka telephones
 ///      option[93].hex == 0x0007 aka computers
 ///
+/// - Configuration 5:
+///   - Used for the DROP class
+///   - 1 subnet: 10.0.0.0/24
+///   - 1 pool: 10.0.0.10-10.0.0.100
+///   - the following class defined: option[93].hex == 0x0009, DROP
+///
 const char* CONFIGS[] = {
     // Configuration 0
     "{ \"interfaces-config\": {"
@@ -275,6 +281,22 @@ const char* CONFIGS[] = {
         " } ]"
     "}",
 
+    // Configuration 5
+    "{ \"interfaces-config\": {"
+        "   \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 600,"
+        "\"client-classes\": ["
+        "{"
+        "   \"name\": \"DROP\","
+        "   \"test\": \"option[93].hex == 0x0009\""
+        "}],"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"id\": 1,"
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
+        " } ]"
+    "}"
 };
 
 /// @brief Test fixture class for testing classification.
@@ -1107,4 +1129,31 @@ TEST_F(ClassifyTest, precedenceNetwork) {
     EXPECT_EQ("10.0.0.3", addrs[0].toText());
 }
 
+// This test checks the handling for the DROP special class.
+TEST_F(ClassifyTest, dropClass) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+
+    // Configure DHCP server.
+    configure(CONFIGS[5], *client.getServer());
+
+    // Send the discover.
+    client.doDiscover();
+
+    // No option: no drop.
+    EXPECT_TRUE(client.getContext().response_);
+
+    // Retry with an option matching the DROP class.
+    Dhcp4Client client2(Dhcp4Client::SELECTING);
+
+    // Add the pxe option.
+    OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+    client2.addExtraOption(pxe);
+
+    // Send the discover.
+    client2.doDiscover();
+
+    // Option, dropped.
+    EXPECT_FALSE(client2.getContext().response_);
+}
+
 } // end of anonymous namespace
index eaab2c015f3ffd11b1acd69d80f40e45d2403c5b..712b076481bbbc6a368efec9cf17fcd257a67dee 100644 (file)
@@ -88,6 +88,7 @@ extern const isc::log::MessageID DHCP6_NO_SOCKETS_OPEN = "DHCP6_NO_SOCKETS_OPEN"
 extern const isc::log::MessageID DHCP6_OPEN_SOCKET = "DHCP6_OPEN_SOCKET";
 extern const isc::log::MessageID DHCP6_OPEN_SOCKET_FAIL = "DHCP6_OPEN_SOCKET_FAIL";
 extern const isc::log::MessageID DHCP6_PACKET_DROP_DHCP_DISABLED = "DHCP6_PACKET_DROP_DHCP_DISABLED";
+extern const isc::log::MessageID DHCP6_PACKET_DROP_DROP_CLASS = "DHCP6_PACKET_DROP_DROP_CLASS";
 extern const isc::log::MessageID DHCP6_PACKET_DROP_PARSE_FAIL = "DHCP6_PACKET_DROP_PARSE_FAIL";
 extern const isc::log::MessageID DHCP6_PACKET_DROP_SERVERID_MISMATCH = "DHCP6_PACKET_DROP_SERVERID_MISMATCH";
 extern const isc::log::MessageID DHCP6_PACKET_DROP_UNICAST = "DHCP6_PACKET_DROP_UNICAST";
@@ -230,6 +231,7 @@ const char* values[] = {
     "DHCP6_OPEN_SOCKET", "opening service sockets on port %1",
     "DHCP6_OPEN_SOCKET_FAIL", "failed to open socket: %1",
     "DHCP6_PACKET_DROP_DHCP_DISABLED", "%1: DHCP service is globally disabled",
+    "DHCP6_PACKET_DROP_DROP_CLASS", "dropped as member of the special DROP class: %1",
     "DHCP6_PACKET_DROP_PARSE_FAIL", "failed to parse packet from %1 to %2, received over interface %3, reason: %4",
     "DHCP6_PACKET_DROP_SERVERID_MISMATCH", "%1: dropping packet with server identifier: %2, server is using: %3",
     "DHCP6_PACKET_DROP_UNICAST", "%1: dropping unicast %2 packet as this packet should be sent to multicast",
index cc328c9d6a798add3a652a5bfa3e94506d65b1e8..f5580fd590067cc1131d8caaafd73f021913de69 100644 (file)
@@ -89,6 +89,7 @@ extern const isc::log::MessageID DHCP6_NO_SOCKETS_OPEN;
 extern const isc::log::MessageID DHCP6_OPEN_SOCKET;
 extern const isc::log::MessageID DHCP6_OPEN_SOCKET_FAIL;
 extern const isc::log::MessageID DHCP6_PACKET_DROP_DHCP_DISABLED;
+extern const isc::log::MessageID DHCP6_PACKET_DROP_DROP_CLASS;
 extern const isc::log::MessageID DHCP6_PACKET_DROP_PARSE_FAIL;
 extern const isc::log::MessageID DHCP6_PACKET_DROP_SERVERID_MISMATCH;
 extern const isc::log::MessageID DHCP6_PACKET_DROP_UNICAST;
index 0ba507f94b09a6a1f81f70f0eb8c779307ab3568..119ef9bfe325d3b95f3cf21dde03f689a7267a9b 100644 (file)
@@ -505,6 +505,10 @@ has been temporarily disabled. This affects all received DHCP packets. The
 service may be enabled by the "dhcp-enable" control command or automatically
 after a specified amount of time since receiving "dhcp-disable" command.
 
+% DHCP6_PACKET_DROP_DROP_CLASS dropped as member of the special DROP class: %1
+This informational message is emitted when an incoming packet was classified
+into the special DROP class and dropped. The packet details are displayed.
+
 % DHCP6_PACKET_DROP_PARSE_FAIL failed to parse packet from %1 to %2, received over interface %3, reason: %4
 The DHCPv6 server has received a packet that it is unable to
 interpret. The reason why the packet is invalid is included in the message.
index 64bb286dd0d94c8770e451b22fdb99e0d406eeb6..5006977a054295434440759887fc5d97da3bfcb6 100644 (file)
@@ -683,6 +683,14 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
         return;
     }
 
+    // Check the DROP special class.
+    if (query->inClass("DROP")) {
+        LOG_INFO(packet6_logger, DHCP6_PACKET_DROP_DROP_CLASS)
+            .arg(query->toText());
+        // increase pkt6-receive-drop stats?
+        return;
+    }
+
     if (query->getType() == DHCPV6_DHCPV4_QUERY) {
         // This call never throws. Should this change, this section must be
         // enclosed in try-catch.
index 9620874853349d116198018c320e05c1eebae131..b37ce46ecd7a5da7e093aee1383ba172657a09a9 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2019 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
@@ -67,6 +67,12 @@ namespace {
 ///      option 1234 'foo' aka telephones
 ///      option 1234 'bar' aka computers
 ///
+/// - Configuration 3:
+///   - Used for the DROP class
+///   - 1 subnet: 2001:db8:1::/48
+///   - 2 pool: 2001:db8:1:1::/64
+///   - the following class defined: option 1234 'foo', DROP
+///
 const char* CONFIGS[] = {
     // Configuration 0
     "{ \"interfaces-config\": {"
@@ -249,8 +255,38 @@ const char* CONFIGS[] = {
         "          \"prefix-len\": 48, \"delegated-len\": 64,"
         "          \"client-class\": \"server2_and_computers\" } ]"
         " } ],"
-        "\"valid-lifetime\": 4000 }"
+        "\"valid-lifetime\": 4000 }",
 
+    // Configuration 3
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-def\": [ "
+        "{"
+        "    \"name\": \"host-name\","
+        "    \"code\": 1234,"
+        "    \"type\": \"string\""
+        "},"
+        "{"
+        "    \"name\": \"ipv6-forwarding\","
+        "    \"code\": 2345,"
+        "    \"type\": \"boolean\""
+        "} ],"
+        "\"client-classes\": ["
+        "{"
+        "   \"name\": \"DROP\","
+        "   \"test\": \"option[host-name].text == 'foo'\""
+        "}"
+        "],"
+        "\"subnet6\": [ "
+        "{   \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
 };
 
 /// @brief Test fixture class for testing client classification by the
@@ -2042,4 +2078,35 @@ TEST_F(ClassifyTest, pDserver2Computer) {
     EXPECT_EQ("2001:db8:4::", lease_client.addr_.toText());
 }
 
+// This test checks the handling for the DROP special class.
+TEST_F(ClassifyTest, dropClass) {
+    Dhcp6Client client;
+    client.setDUID("01:02:03:05");
+    client.setInterface("eth1");
+    client.requestAddress();
+
+    // Configure DHCP server.
+    ASSERT_NO_THROW(configure(CONFIGS[3], *client.getServer()));
+
+    // Send a message to the server.
+    ASSERT_NO_THROW(client.doSolicit(true));
+
+    // No option: no drop.
+    EXPECT_TRUE(client.getContext().response_);
+
+    // Retry with an option matching the DROP class.
+    Dhcp6Client client2;
+
+    // Add the host-name option.
+    OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+    ASSERT_TRUE(hostname);
+    client2.addExtraOption(hostname);
+
+    // Send a message to the server.
+    ASSERT_NO_THROW(client2.doSolicit(true));
+
+    // Option, dropped.
+    EXPECT_FALSE(client2.getContext().response_);
+}
+
 } // end of anonymous namespace
index 2a9a52098a74e949a5f29e96b2f6dd340cb0e656..bb18d8c328584c5aa42163fee8ce23e77b095412 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2019 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
@@ -341,6 +341,10 @@ ClientClassDictionary::toElement() const {
 
 std::list<std::string>
 builtinNames = {
+    // DROP is not in this list because it is special but not built-in.
+    // In fact DROP is set from an expression as callouts can drop
+    // directly the incoming packet. The expression must not depend on
+    // KNOWN/UNKNOWN which are set far after the drop point.
     "ALL", "KNOWN", "UNKNOWN"
 };
 
index 94e03c2c56af907fc173b296c8bbae13c804ba02..88425d0a97b69aca149e47571fcaf7506e682532 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2019 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
@@ -382,7 +382,7 @@ private:
 typedef boost::shared_ptr<ClientClassDictionary> ClientClassDictionaryPtr;
 
 /// @brief List of built-in client class names.
-/// i.e. ALL, KNOWN and UNKNOWN.
+/// i.e. ALL, KNOWN and UNKNOWN but not DROP.
 extern std::list<std::string> builtinNames;
 
 /// @brief List of built-in client class prefixes
index 7177a4b7c6dc5fbcc94d8ce5640d697d70f9d510..4be5e85dcf99c07b962894b6b8ac2c845c0b0ee4 100644 (file)
@@ -203,6 +203,32 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
 
     }
 
+    // Sanity checks on built-in classes
+    for (auto bn : builtinNames) {
+        if (name == bn) {
+            if (required) {
+                isc_throw(DhcpConfigError, "built-in class '" << name
+                          << "' only-if-required flag must be false");
+            }
+            if (!test.empty()) {
+                isc_throw(DhcpConfigError, "built-in class '" << name
+                          << "' test expression must be empty");
+            }
+        }
+    }
+
+    // Sanity checks on DROP
+    if (name == "DROP") {
+        if (required) {
+            isc_throw(DhcpConfigError, "special class '" << name
+                      << "' only-if-required flag must be false");
+        }
+        if (depend_on_known) {
+            isc_throw(DhcpConfigError, "special class '" << name
+                      << "' must not depend on 'KNOWN'/'UNKNOWN' classes");
+        }
+    }
+
     // Add the client class definition
     try {
         class_dictionary->addClass(name, match_expr, test, required,
index 0f249311500b9546587458a6c3855f96183c0dd0..813d46d75aee3fa76939fe634ac5b50d73caffd1 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2019 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
@@ -1185,4 +1185,110 @@ TEST_F(ClientClassDefListParserTest, dependOnKnown) {
     EXPECT_TRUE(cclass->getDependOnKnown());
 }
 
+// Verifies that a built-in class can't be required or evaluated.
+TEST_F(ClientClassDefListParserTest, builtinCheckError) {
+    std::string cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"ALL\" \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
+
+    cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"ALL\", \n"
+        "       \"only-if-required\": true \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET), DhcpConfigError);
+
+    cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"ALL\", \n"
+        "       \"test\": \"'aa' == 'aa'\" \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+
+    cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"KNOWN\", \n"
+        "       \"only-if-required\": true \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET), DhcpConfigError);
+
+    cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"KNOWN\", \n"
+        "       \"test\": \"'aa' == 'aa'\" \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+
+    cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"UNKNOWN\", \n"
+        "       \"only-if-required\": true \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET), DhcpConfigError);
+
+    cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"UNKNOWN\", \n"
+        "       \"test\": \"'aa' == 'aa'\" \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+}
+
+// Verifies that the special DROP class can't be required or
+// dependent on KNOWN/UNKNOWN
+TEST_F(ClientClassDefListParserTest, dropCheckError) {
+    std::string cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"DROP\", \n"
+        "       \"test\": \"option[123].text == 'abc'\" \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
+
+    cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"DROP\", \n"
+        "       \"only-if-required\": true \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET), DhcpConfigError);
+
+    cfg_text =
+        "[ \n"
+        "   { \n"
+        "       \"name\": \"DROP\", \n"
+        "       \"test\": \"member('KNOWN')\" \n"
+        "   } \n"
+        "] \n";
+
+    EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+}
+
 } // end of anonymous namespace