Parcourir la source

[master] Merge branch 'trac3203' (DHCP client classification, part 1)

Conflicts:
	src/bin/dhcp4/dhcp4_srv.cc
	src/bin/dhcp4/tests/dhcp4_test_utils.h
	src/bin/dhcp6/dhcp6_srv.cc
	src/lib/dhcp/pkt4.cc
	src/lib/dhcp/tests/pkt4_unittest.cc
Tomek Mrugalski il y a 11 ans
Parent
commit
acc4ba2011

+ 10 - 0
ChangeLog

@@ -1,3 +1,13 @@
+732.	[func]		tomek
+	b10-dhcp4, b10-dhcp6: Support for simplified client classification
+        added. Incoming packets are now assigned to a client class based on
+	the content of the packet's user class option (DHCPv4) or vendor class
+	option (DHCPv6). Two classes (docsis3.0 and eRouter1.0) have class
+	specific behavior in b10-dhcp4. See DHCPv4 Client Classification and
+	DHCPv6 Client Classification in BIND10 Developer's Guide for details.
+	This is a first ticket in a series of planned at least three tickets.
+	(Trac #3203, git afea612c23143f81a4201e39ba793bc837c5c9f1)
+
 731.	[func]		tmark
 	b10-dhcp4 now parses parameters which support DHCP-DDNS updates via
 	the DHCP-DDNS module, b10-dhcp-ddns.  These parameters are part of new

+ 2 - 0
doc/devel/mainpage.dox

@@ -60,6 +60,7 @@
  *   - @subpage dhcpv4ConfigInherit
  *   - @subpage dhcpv4OptionsParse
  *   - @subpage dhcpv4DDNSIntegration
+ *   - @subpage dhcpv4Classifier
  *   - @subpage dhcpv4Other
  * - @subpage dhcp6
  *   - @subpage dhcpv6Session
@@ -67,6 +68,7 @@
  *   - @subpage dhcpv6ConfigInherit
  *   - @subpage dhcpv6DDNSIntegration
  *   - @subpage dhcpv6OptionsParse
+ *   - @subpage dhcpv6Classifier
  *   - @subpage dhcpv6Other
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro

+ 1 - 1
src/bin/dhcp4/ctrl_dhcp4_srv.h

@@ -107,7 +107,7 @@ protected:
     /// various configuration values. Installing the dummy handler
     /// that guarantees to return success causes initial configuration
     /// to be stored for the session being created and that it can
-    /// be later accessed with \ref isc::ConfigData::getFullConfig.
+    /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
     ///
     /// @param new_config new configuration.
     ///

+ 30 - 0
src/bin/dhcp4/dhcp4.dox

@@ -155,6 +155,36 @@ The default behaviour is constituted by the set of constants defined in the
 (upper part of) dhcp4_srv.cc file. Once the configuration is implemented,
 these constants will be removed.
 
+@section dhcpv4Classifier DHCPv4 Client Classification
+
+Kea DHCPv4 server currently supports simplified client classification. It is called
+"simplified", because the incoming packets are classified based on the content
+of the vendor class (60) option. More flexible classification is planned, but there
+are no specific development dates agreed.
+
+For each incoming packet, @ref isc::dhcp::Dhcpv4Srv::classifyPacket() method is called.
+It attempts to extract content of the vendor class option and interpret as a name
+of the class. For now, the code has been tested with two classes used in cable modem
+networks: eRouter1.0 and docsis3.0, but any other content of the vendor class option will
+be interpreted as a class name.
+
+In principle any given packet can belong to zero or more classes. As the current
+classifier is very modest, there's only one way to assign a class (based on vendor class
+option), the ability to assign more than one class to a packet is not yet exercised.
+Neverthless, there is such a possibility and it will be used in a near future. To
+check whether a packet belongs to given class, isc::dhcp::Pkt4::inClass method should
+be used.
+
+Currently there is a short code section that alternates packet processing depending on
+which class it belongs to. It is planned to move that capability to an external hook
+library. See ticket #3275. The class specific behavior is:
+
+- docsis3.0 packets have siaddr (next server) field set
+- docsis3.0 packets have file field set to the content of the boot-file-name option
+- eRouter1.0 packets have siaddr (next server) field cleared
+
+Aforementioned modifications are conducted in @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing.
+
 @section dhcpv4Other Other DHCPv4 topics
 
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.

+ 8 - 0
src/bin/dhcp4/dhcp4_messages.mes

@@ -32,6 +32,14 @@ This debug message is issued when the DHCP server was unable to process the
 FQDN or Hostname option sent by a client. This is likely because the client's
 name was malformed or due to internal server error.
 
+% DHCP4_CLASS_PROCESSING_FAILED client class specific processing failed
+This debug message means that the server processing that is unique for each
+client class has reported a failure. The response packet will not be sent.
+
+% DHCP4_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1
+This debug message informs that incoming packet has been assigned to specified
+class or classes. This is a norma
+
 % DHCP4_COMMAND_RECEIVED received command %1, arguments: %2
 A debug message listing the command (and possible arguments) received
 from the BIND 10 control system by the IPv4 DHCP server.

+ 97 - 0
src/bin/dhcp4/dhcp4_srv.cc

@@ -21,6 +21,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_vendor.h>
+#include <dhcp/option_string.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp4/dhcp4_log.h>
@@ -312,6 +313,9 @@ Dhcpv4Srv::run() {
             callout_handle->getArgument("query4", query);
         }
 
+        // Assign this packet to one or more classes if needed
+        classifyPacket(query);
+
         try {
             switch (query->getType()) {
             case DHCPDISCOVER:
@@ -368,6 +372,18 @@ Dhcpv4Srv::run() {
             continue;
         }
 
+        // Let's do class specific processing. This is done before
+        // pkt4_send.
+        //
+        /// @todo: decide whether we want to add a new hook point for
+        /// doing class specific processing.
+        if (!classSpecificProcessing(query, rsp)) {
+            /// @todo add more verbosity here
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_PROCESSING_FAILED);
+
+            continue;
+        }
+
         // Specifies if server should do the packing
         bool skip_pack = false;
 
@@ -1766,5 +1782,86 @@ Dhcpv4Srv::ifaceMgrSocket4ErrorHandler(const std::string& errmsg) {
     LOG_WARN(dhcp4_logger, DHCP4_OPEN_SOCKET_FAIL).arg(errmsg);
 }
 
+void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
+    boost::shared_ptr<OptionString> vendor_class =
+        boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
+
+    string classes = "";
+
+    if (!vendor_class) {
+        return;
+    }
+
+    // DOCSIS specific section
+
+    // Let's keep this as a series of checks. So far we're supporting only
+    // docsis3.0, but there are also docsis2.0, docsis1.1 and docsis1.0. We
+    // may come up with adding several classes, e.g. for docsis2.0 we would
+    // add classes docsis2.0, docsis1.1 and docsis1.0.
+
+    // Also we are using find, because we have at least one traffic capture
+    // where the user class was followed by a space ("docsis3.0 ").
+
+    // For now, the code is very simple, but it is expected to get much more
+    // complex soon. One specific case is that the vendor class is an option
+    // sent by the client, so we should not trust it. To confirm that the device
+    // is indeed a modem, John B. suggested to check whether chaddr field
+    // quals subscriber-id option that was inserted by the relay (CMTS).
+    // This kind of logic will appear here soon.
+    if (vendor_class->getValue().find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
+        pkt->addClass(DOCSIS3_CLASS_MODEM);
+        classes += string(DOCSIS3_CLASS_MODEM) + " ";
+    } else
+    if (vendor_class->getValue().find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
+        pkt->addClass(DOCSIS3_CLASS_EROUTER);
+        classes += string(DOCSIS3_CLASS_EROUTER) + " ";
+    } else {
+        classes += vendor_class->getValue();
+        pkt->addClass(vendor_class->getValue());
+    }
+
+    if (!classes.empty()) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
+            .arg(classes);
+    }
+}
+
+bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp) {
+
+    Subnet4Ptr subnet = selectSubnet(query);
+    if (!subnet) {
+        return (true);
+    }
+
+    if (query->inClass(DOCSIS3_CLASS_MODEM)) {
+
+        // Set next-server. This is TFTP server address. Cable modems will
+        // download their configuration from that server.
+        rsp->setSiaddr(subnet->getSiaddr());
+
+        // Now try to set up file field in DHCPv4 packet. We will just copy
+        // content of the boot-file option, which contains the same information.
+        Subnet::OptionDescriptor desc =
+            subnet->getOptionDescriptor("dhcp4", DHO_BOOT_FILE_NAME);
+
+        if (desc.option) {
+            boost::shared_ptr<OptionString> boot =
+                boost::dynamic_pointer_cast<OptionString>(desc.option);
+            if (boot) {
+                std::string filename = boot->getValue();
+                rsp->setFile((const uint8_t*)filename.c_str(), filename.size());
+            }
+        }
+    }
+
+    if (query->inClass(DOCSIS3_CLASS_EROUTER)) {
+
+        // Do not set TFTP server address for eRouter devices.
+        rsp->setSiaddr(IOAddress("0.0.0.0"));
+    }
+
+    return (true);
+}
+
 }   // namespace dhcp
 }   // namespace isc

+ 20 - 1
src/bin/dhcp4/dhcp4_srv.h

@@ -262,7 +262,7 @@ protected:
     /// using separate options within their respective vendor-option spaces.
     ///
     /// @param question DISCOVER or REQUEST message from a client.
-    /// @param msg outgoing message (options will be added here)
+    /// @param answer outgoing message (options will be added here)
     void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer);
 
     /// @brief Assigns a lease and appends corresponding options
@@ -559,6 +559,25 @@ protected:
                          const std::string& option_space,
                          isc::dhcp::OptionCollection& options);
 
+    /// @brief Assigns incoming packet to zero or more classes.
+    ///
+    /// @note For now, the client classification is very simple. It just uses
+    /// content of the vendor-class-identifier option as a class. The resulting
+    /// class will be stored in packet (see @ref isc::dhcp::Pkt4::classes_ and
+    /// @ref isc::dhcp::Pkt4::inClass).
+    ///
+    /// @param pkt packet to be classified
+    void classifyPacket(const Pkt4Ptr& pkt);
+
+    /// @brief Performs packet processing specific to a class
+    ///
+    /// This processing is a likely candidate to be pushed into hooks.
+    ///
+    /// @param query incoming client's packet
+    /// @param rsp server's response
+    /// @return true if successful, false otherwise (will prevent sending response)
+    bool classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp);
+
 private:
 
     /// @brief Constructs netmask option based on subnet4

+ 27 - 3
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -3173,8 +3173,32 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, vendorOptionsDocsisDefinitions) {
     ASSERT_EQ(0, rcode_);
 }
 
+// Checks if client packets are classified properly
+TEST_F(Dhcpv4SrvTest, clientClassification) {
+
+    NakedDhcpv4Srv srv(0);
+
+    // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+    // vendor-class set to docsis3.0
+    Pkt4Ptr dis1;
+    ASSERT_NO_THROW(dis1 = captureRelayedDiscover());
+    ASSERT_NO_THROW(dis1->unpack());
+
+    srv.classifyPacket(dis1);
+
+    EXPECT_TRUE(dis1->inClass("docsis3.0"));
+    EXPECT_FALSE(dis1->inClass("eRouter1.0"));
+
+    // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+    // vendor-class set to eRouter1.0
+    Pkt4Ptr dis2;
+    ASSERT_NO_THROW(dis2 = captureRelayedDiscover2());
+    ASSERT_NO_THROW(dis2->unpack());
+
+    srv.classifyPacket(dis2);
+
+    EXPECT_TRUE(dis2->inClass("eRouter1.0"));
+    EXPECT_FALSE(dis2->inClass("docsis3.0"));
 }
 
-    /*I}; // end of isc::dhcp::test namespace
-}; // end of isc::dhcp namespace
-}; // end of isc namespace */
+}; // end of anonymous namespace

+ 10 - 1
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -222,7 +222,8 @@ public:
 
     /// @brief returns captured DISCOVER that went through a relay
     ///
-    /// See method code for a detailed explanation.
+    /// See method code for a detailed explanation. This is a discover from
+    /// docsis3.0 device (Cable Modem)
     ///
     /// @return relayed DISCOVER
     Pkt4Ptr captureRelayedDiscover();
@@ -253,6 +254,13 @@ public:
     createPacketFromBuffer(const Pkt4Ptr& src_pkt,
                            Pkt4Ptr& dst_pkt);
 
+    /// @brief returns captured DISCOVER that went through a relay
+    ///
+    /// See method code for a detailed explanation. This is a discover from
+    /// eRouter1.0 device (CPE device integrated with cable modem)
+    ///
+    /// @return relayed DISCOVER
+    Pkt4Ptr captureRelayedDiscover2();
 
     /// @brief generates a DHCPv4 packet based on provided hex string
     ///
@@ -443,6 +451,7 @@ public:
     using Dhcpv4Srv::srvidToString;
     using Dhcpv4Srv::unpackOptions;
     using Dhcpv4Srv::name_change_reqs_;
+    using Dhcpv4Srv::classifyPacket;
 };
 
 }; // end of isc::dhcp::test namespace

+ 58 - 2
src/bin/dhcp4/tests/wireshark.cc

@@ -71,7 +71,10 @@ void Dhcpv4SrvTest::captureSetDefaultFields(const Pkt4Ptr& pkt) {
 
 Pkt4Ptr Dhcpv4SrvTest::captureRelayedDiscover() {
 
-/* string exported from Wireshark:
+/* This is packet 1 from capture
+   dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap
+
+string exported from Wireshark:
 
 User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
     Source port: bootps (67)
@@ -98,7 +101,7 @@ Bootstrap Protocol
     Magic cookie: DHCP
     Option: (53) DHCP Message Type
     Option: (55) Parameter Request List
-    Option: (60) Vendor class identifier
+    Option: (60) Vendor class identifier (docsis3.0)
     Option: (125) V-I Vendor-specific Information
       - suboption 1 (Option Request): requesting option 2
       - suboption 5 (Modem Caps): 117 bytes
@@ -129,6 +132,59 @@ Bootstrap Protocol
     return (packetFromCapture(hex_string));
 }
 
+Pkt4Ptr Dhcpv4SrvTest::captureRelayedDiscover2() {
+
+/* This is packet 5 from capture
+   dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap
+
+string exported from Wireshark:
+
+User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
+Bootstrap Protocol
+    Message type: Boot Request (1)
+    Hardware type: Ethernet (0x01)
+    Hardware address length: 6
+    Hops: 1
+    Transaction ID: 0x5d05478f
+    Seconds elapsed: 5
+    Bootp flags: 0x0000 (Unicast)
+    Client IP address: 0.0.0.0 (0.0.0.0)
+    Your (client) IP address: 0.0.0.0 (0.0.0.0)
+    Next server IP address: 0.0.0.0 (0.0.0.0)
+    Relay agent IP address: 10.254.226.1 (10.254.226.1)
+    Client MAC address: Netgear_b8:15:15 (20:e5:2a:b8:15:15)
+    Client hardware address padding: 00000000000000000000
+    Server host name not given
+    Boot file name not given
+    Magic cookie: DHCP
+    Option: (53) DHCP Message Type
+    Option: (55) Parameter Request List
+    Option: (43) Vendor-Specific Information
+    Option: (60) Vendor class identifier (eRouter1.0)
+    Option: (15) Domain Name
+    Option: (61) Client identifier
+    Option: (57) Maximum DHCP Message Size
+    Option: (82) Agent Information Option
+    Option: (255) End */
+
+    string hex_string =
+        "010106015d05478f000500000000000000000000000000000afee20120e52ab8151500"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "000000000000000000000000000000000000000000000000000063825363350101370e"
+        "480102030406070c0f171a36337a2b63020745524f55544552030b45434d3a45524f55"
+        "544552040d324252323239553430303434430504312e3034060856312e33332e303307"
+        "07322e332e305232080630303039354209094347333030304443520a074e6574676561"
+        "720f0745524f555445523c0a65526f75746572312e300f14687364312e70612e636f6d"
+        "636173742e6e65742e3d0fff2ab815150003000120e52ab81515390205dc5219010420"
+        "000002020620e52ab8151409090000118b0401020300ff";
+
+    return (packetFromCapture(hex_string));
+}
+
 }; // end of isc::dhcp::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 1 - 1
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -105,7 +105,7 @@ protected:
     /// various configuration values. Installing the dummy handler
     /// that guarantees to return success causes initial configuration
     /// to be stored for the session being created and that it can
-    /// be later accessed with \ref isc::ConfigData::getFullConfig.
+    /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
     ///
     /// @param new_config new configuration.
     ///

+ 27 - 0
src/bin/dhcp6/dhcp6.dox

@@ -190,6 +190,33 @@ implemented within the context of the server and it has access to all objects
 which define its configuration (including dynamically created option
 definitions).
 
+@section dhcpv6Classifier DHCPv6 Client Classification
+
+Kea DHCPv6 server currently supports simplified client classification. It is called
+"simplified", because the incoming packets are classified based on the content
+of the vendor class (16) option. More flexible classification is planned, but there
+are no specific development dates agreed.
+
+For each incoming packet, @ref isc::dhcp::Dhcpv6Srv::classifyPacket() method is
+called.  It attempts to extract content of the vendor class option and interprets
+as a name of the class. Although the RFC3315 says that the vendor class may
+contain more than one chunk of data, the existing code handles only one data
+block, because that is what actual devices use. For now, the code has been
+tested with two classes used in cable modem networks: eRouter1.0 and docsis3.0,
+but any other content of the vendor class option will be interpreted as a class
+name.
+
+In principle any given packet can belong to zero or more classes. As the current
+classifier is very modest, there's only one way to assign a class (based on vendor class
+option), the ability to assign more than one class to a packet is not yet exercised.
+Neverthless, there is such a possibility and it will be used in a near future. To
+check whether a packet belongs to given class, isc::dhcp::Pkt6::inClass method should
+be used.
+
+Currently there is no class behaviour coded in DHCPv6, hence no v6 equivalent of
+@ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing. Should any need for such a code arise,
+it will be conducted in an external hooks library.
+
  @section dhcpv6Other Other DHCPv6 topics
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.

+ 4 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -27,6 +27,10 @@ successfully established a session with the BIND 10 control channel.
 This debug message is issued just before the IPv6 DHCP server attempts
 to establish a session with the BIND 10 control channel.
 
+% DHCP6_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1
+This debug message informs that incoming packet has been assigned to specified
+class or classes. This is a norma
+
 % DHCP6_CLIENTID_MISSING mandatory client-id option is missing, message from %1 dropped
 This error message indicates that the received message is being dropped
 because it does not include the mandatory client-id option necessary for

+ 37 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -316,6 +316,9 @@ bool Dhcpv6Srv::run() {
             callout_handle->getArgument("query6", query);
         }
 
+        // Assign this packet to a class, if possible
+        classifyPacket(query);
+
         try {
                 NameChangeRequestPtr ncr;
             switch (query->getType()) {
@@ -2423,5 +2426,39 @@ Dhcpv6Srv::ifaceMgrSocket6ErrorHandler(const std::string& errmsg) {
     LOG_WARN(dhcp6_logger, DHCP6_OPEN_SOCKET_FAIL).arg(errmsg);
 }
 
+void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
+
+    boost::shared_ptr<OptionCustom> vclass =
+        boost::dynamic_pointer_cast<OptionCustom>(pkt->getOption(D6O_VENDOR_CLASS));
+
+    if (!vclass) {
+        return;
+    }
+
+    string classes = "";
+
+    // DOCSIS specific section
+    if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
+        .find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
+        pkt->addClass(DOCSIS3_CLASS_MODEM);
+        classes += string(DOCSIS3_CLASS_MODEM) + " ";
+    } else
+    if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
+        .find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
+        pkt->addClass(DOCSIS3_CLASS_EROUTER);
+        classes += string(DOCSIS3_CLASS_EROUTER) + " ";
+    }else
+    {
+        // Otherwise use the string as is
+        classes += vclass->readString(VENDOR_CLASS_STRING_INDEX);
+        pkt->addClass(vclass->readString(VENDOR_CLASS_STRING_INDEX));
+    }
+
+    if (!classes.empty()) {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
+            .arg(classes);
+    }
+}
+
 };
 };

+ 10 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -533,6 +533,16 @@ protected:
                          size_t* relay_msg_offset,
                          size_t* relay_msg_len);
 
+    /// @brief Assigns incoming packet to zero or more classes.
+    ///
+    /// @note For now, the client classification is very simple. It just uses
+    /// content of the vendor-class-identifier option as a class. The resulting
+    /// class will be stored in packet (see @ref isc::dhcp::Pkt6::classes_ and
+    /// @ref isc::dhcp::Pkt6::inClass).
+    ///
+    /// @param pkt packet to be classified
+    void classifyPacket(const Pkt6Ptr& pkt);
+
 private:
 
     /// @brief Implements the error handler for socket open failure.

+ 29 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -1673,6 +1673,35 @@ TEST_F(Dhcpv6SrvTest, unpackOptions) {
     EXPECT_EQ(0x0, option_bar->getValue());
 }
 
+// Checks if client packets are classified properly
+TEST_F(Dhcpv6SrvTest, clientClassification) {
+
+    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 = 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("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 = captureeRouterRelayedSolicit());
+    ASSERT_NO_THROW(sol2->unpack());
+
+    srv.classifyPacket(sol2);
+
+    EXPECT_TRUE(sol2->inClass("eRouter1.0"));
+    EXPECT_FALSE(sol2->inClass("docsis3.0"));
+}
+
 
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.

+ 2 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -112,6 +112,7 @@ public:
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::sanityCheck;
+    using Dhcpv6Srv::classifyPacket;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::writeServerID;
     using Dhcpv6Srv::unpackOptions;
@@ -477,6 +478,7 @@ public:
     Pkt6Ptr captureSimpleSolicit();
     Pkt6Ptr captureRelayedSolicit();
     Pkt6Ptr captureDocsisRelayedSolicit();
+    Pkt6Ptr captureeRouterRelayedSolicit();
 
     /// @brief Auxiliary method that sets Pkt6 fields
     ///

+ 145 - 5
src/bin/dhcp6/tests/wireshark.cc

@@ -17,9 +17,9 @@
 #include <string>
 
 /// @file   wireshark.cc
-/// 
+///
 /// @brief  contains packet captures imported from Wireshark
-/// 
+///
 /// These are actual packets captured over wire. They are used in various
 /// tests.
 ///
@@ -33,6 +33,10 @@
 /// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
 /// 7. Make sure you decribe the capture appropriately
 /// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.)
+/// 9. To easily copy packet description, click File... -> Extract packet
+///    dissections -> as plain text file...
+///    (Make sure that the packet is expanded in the view. The text file will
+///    contain whatever expansion level you have in the graphical tree.)
 
 using namespace std;
 
@@ -81,7 +85,7 @@ Pkt6Ptr Dhcpv6SrvTest::captureRelayedSolicit() {
     //     - ORO (7)
 
     // string exported from Wireshark
-    string hex_string = 
+    string hex_string =
         "0c0500000000000000000000000000000000fc00000000000000000000000000000900"
         "12000231350009002c010517100001000e0001000151b5e46208002758f1e80003000c"
         "000000010000000000000000000600020007";
@@ -102,7 +106,7 @@ Pkt6Ptr Dhcpv6SrvTest::captureRelayedSolicit() {
 Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
 
     // This is an actual DOCSIS packet
-    // RELAY-FORW (12) 
+    // RELAY-FORW (12)
     //  - Relay Message
     //    - SOLICIT (1)
     //      - client-id
@@ -132,7 +136,7 @@ Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
     //    - Suboption 1026: Cable Modem MAC addr = 10:0d:7f:00:07:88
 
     // string exported from Wireshark
-    string hex_string = 
+    string hex_string =
         "0c002a0288fe00fe00015a8d09fffe7af955fe80000000000000120d7ffffe00078800"
         "090189010d397f0001000a00030001100d7f000788000300287f000788000000000000"
         "000000050018000000000000000000000000000000000000000000000000000e000000"
@@ -159,5 +163,141 @@ Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
     return (pkt);
 }
 
+/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 eRouter)
+Pkt6Ptr isc::test::Dhcpv6SrvTest::captureeRouterRelayedSolicit() {
+
+/* Packet description exported from wireshark:
+DHCPv6
+    Message type: Relay-forw (12)
+    Hopcount: 0
+    Link address: 2001:558:ffa8::1 (2001:558:ffa8::1)
+    Peer address: fe80::22e5:2aff:feb8:1515 (fe80::22e5:2aff:feb8:1515)
+    Relay Message
+        Option: Relay Message (9)
+        Length: 241
+        Value: 01a90044000e000000140000000600080011001700180019...
+        DHCPv6
+            Message type: Solicit (1)
+            Transaction ID: 0xa90044
+            Rapid Commit
+                Option: Rapid Commit (14)
+                Length: 0
+            Reconfigure Accept
+                Option: Reconfigure Accept (20)
+                Length: 0
+            Option Request
+                Option: Option Request (6)
+                Length: 8
+                Value: 0011001700180019
+                Requested Option code: Vendor-specific Information (17)
+                Requested Option code: DNS recursive name server (23)
+                Requested Option code: Domain Search List (24)
+                Requested Option code: Identity Association for Prefix Delegation (25)
+            Vendor Class
+                Option: Vendor Class (16)
+                Length: 16
+                Value: 0000118b000a65526f75746572312e30
+                Enterprise ID: Cable Television Laboratories, Inc. (4491)
+                vendor-class-data: eRouter1.0
+            Vendor-specific Information
+                Option: Vendor-specific Information (17)
+                Length: 112
+                Value: 0000118b0002000745524f555445520003000b45434d3a45...
+                Enterprise ID: Cable Television Laboratories, Inc. (4491)
+                Suboption: Device Type =  (2)"EROUTER"
+                Suboption: Embedded Components =  (3)"ECM:EROUTER"
+                Suboption: Serial Number =  (4)"2BR229U40044C"
+                Suboption: Hardware Version =  (5)"1.04"
+                Suboption: Software Version =  (6)"V1.33.03"
+                Suboption: Boot ROM Version =  (7)"2.3.0R2"
+                Suboption: Organization Unique Identifier =  (8)"00095B"
+                Suboption: Model Number =  (9)"CG3000DCR"
+                Suboption: Vendor Name =  (10)"Netgear"
+            Client Identifier
+                Option: Client Identifier (1)
+                Length: 10
+                Value: 0003000120e52ab81515
+                DUID: 0003000120e52ab81515
+                DUID Type: link-layer address (3)
+                Hardware type: Ethernet (1)
+                Link-layer address: 20:e5:2a:b8:15:15
+            Identity Association for Prefix Delegation
+                Option: Identity Association for Prefix Delegation (25)
+                Length: 41
+                Value: 2ab815150000000000000000001a00190000000000000000...
+                IAID: 2ab81515
+                T1: 0
+                T2: 0
+                IA Prefix
+                    Option: IA Prefix (26)
+                    Length: 25
+                    Value: 000000000000000038000000000000000000000000000000...
+                    Preferred lifetime: 0
+                    Valid lifetime: 0
+                    Prefix length: 56
+                    Prefix address: :: (::)
+            Identity Association for Non-temporary Address
+                Option: Identity Association for Non-temporary Address (3)
+                Length: 12
+                Value: 2ab815150000000000000000
+                IAID: 2ab81515
+                T1: 0
+                T2: 0
+            Elapsed time
+                Option: Elapsed time (8)
+                Length: 2
+                Value: 0000
+                Elapsed time: 0 ms
+    Vendor-specific Information
+        Option: Vendor-specific Information (17)
+        Length: 22
+        Value: 0000118b0402000620e52ab815140401000401020300
+        Enterprise ID: Cable Television Laboratories, Inc. (4491)
+        Suboption: CM MAC Address Option =  (1026)20:e5:2a:b8:15:14
+        Suboption: CMTS Capabilities Option :  (1025)
+    Interface-Id
+        Option: Interface-Id (18)
+        Length: 4
+        Value: 00000022
+        Interface-ID:
+    Remote Identifier
+        Option: Remote Identifier (37)
+        Length: 14
+        Value: 0000101300015c228d4110000122
+        Enterprise ID: Arris Interactive LLC (4115)
+        Remote-ID: 00015c228d4110000122
+    DHCP option 53
+        Option: Unknown (53)
+        Length: 10
+        Value: 0003000100015c228d3d
+        DUID: 0003000100015c228d3d
+        DUID Type: link-layer address (3)
+        Hardware type: Ethernet (1)
+        Link-layer address: 00:01:5c:22:8d:3d */
+
+    // string exported from Wireshark
+    string hex_string =
+        "0c0020010558ffa800000000000000000001fe8000000000000022e52afffeb8151500"
+        "0900f101a90044000e000000140000000600080011001700180019001000100000118b"
+        "000a65526f75746572312e30001100700000118b0002000745524f555445520003000b"
+        "45434d3a45524f555445520004000d3242523232395534303034344300050004312e30"
+        "340006000856312e33332e303300070007322e332e3052320008000630303039354200"
+        "090009434733303030444352000a00074e6574676561720001000a0003000120e52ab8"
+        "1515001900292ab815150000000000000000001a001900000000000000003800000000"
+        "0000000000000000000000000003000c2ab81515000000000000000000080002000000"
+        "1100160000118b0402000620e52ab81514040100040102030000120004000000220025"
+        "000e0000101300015c228d41100001220035000a0003000100015c228d3d";
+
+    std::vector<uint8_t> bin;
+
+    // Decode the hex string and store it in bin (which happens
+    // to be OptionBuffer format)
+    isc::util::encode::decodeHex(hex_string, bin);
+
+    Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+    captureSetDefaultFields(pkt);
+    return (pkt);
+}
+
 }; // end of isc::test namespace
 }; // end of isc namespace

+ 8 - 3
src/lib/dhcp/docsis3_option_defs.h

@@ -18,8 +18,8 @@
 #include <dhcp/std_option_defs.h>
 #include <dhcp/option_data_types.h>
 
-
-namespace {
+namespace isc {
+namespace dhcp {
 
 #define VENDOR_ID_CABLE_LABS 4491
 
@@ -61,6 +61,11 @@ const OptionDefParams DOCSIS3_V6_DEFS[] = {
 /// Number of option definitions defined.
 const int DOCSIS3_V6_DEFS_SIZE  = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefParams);
 
-}; // anonymous namespace
+/// The class as specified in vendor-class option by the devices
+extern const char* DOCSIS3_CLASS_EROUTER;
+extern const char* DOCSIS3_CLASS_MODEM;
+
+}; // isc::dhcp namespace
+}; // isc namespace
 
 #endif // DOCSIS3_OPTION_DEFS_H

+ 8 - 0
src/lib/dhcp/libdhcp++.cc

@@ -52,6 +52,14 @@ VendorOptionDefContainers LibDHCP::vendor4_defs_;
 
 VendorOptionDefContainers LibDHCP::vendor6_defs_;
 
+// Those two vendor classes are used for cable modems:
+
+/// DOCSIS3.0 compatible cable modem
+const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0";
+
+/// DOCSIS3.0 cable modem that has router built-in
+const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0";
+
 // Let's keep it in .cc file. Moving it to .h would require including optionDefParams
 // definitions there
 void initOptionSpace(OptionDefContainer& defs,

+ 2 - 1
src/lib/dhcp/option6_iaprefix.h

@@ -90,7 +90,8 @@ public:
 
     /// sets address in this option.
     ///
-    /// @param addr address to be sent in this option
+    /// @param prefix prefix to be sent in this option
+    /// @param length prefix length
     void setPrefix(const isc::asiolink::IOAddress& prefix,
                    uint8_t length) { addr_ = prefix; prefix_len_ = length; }
 

+ 6 - 1
src/lib/dhcp/option_vendor.h

@@ -25,6 +25,11 @@
 namespace isc {
 namespace dhcp {
 
+/// Indexes for fields in vendor-class (17) DHCPv6 option
+const int VENDOR_CLASS_ENTERPRISE_ID_INDEX = 0;
+const int VENDOR_CLASS_DATA_LEN_INDEX = 1;
+const int VENDOR_CLASS_STRING_INDEX = 2;
+
 /// @brief This class represents vendor-specific information option.
 ///
 /// As specified in RFC3925, the option formatting is slightly different
@@ -72,7 +77,7 @@ public:
 
     /// @brief Sets enterprise identifier
     ///
-    /// @param value vendor identifier
+    /// @param vendor_id vendor identifier
     void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; }
 
     /// @brief Returns enterprise identifier

+ 10 - 0
src/lib/dhcp/pkt4.cc

@@ -480,7 +480,17 @@ Pkt4::isRelayed() const {
               << static_cast<int>(getHops()) << ". Valid values"
               " are: (giaddr = 0 and hops = 0) or (giaddr != 0 and"
               "hops != 0)");
+}
+
+bool Pkt4::inClass(const std::string& client_class) {
+    return (classes_.find(client_class) != classes_.end());
+}
 
+void
+Pkt4::addClass(const std::string& client_class) {
+    if (classes_.find(client_class) == classes_.end()) {
+        classes_.insert(client_class);
+    }
 }
 
 } // end of namespace isc::dhcp

+ 35 - 0
src/lib/dhcp/pkt4.h

@@ -26,6 +26,7 @@
 
 #include <iostream>
 #include <vector>
+#include <set>
 
 #include <time.h>
 
@@ -52,6 +53,9 @@ public:
     /// to check whether client requested broadcast response.
     const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
 
+    /// Container for storing client classes
+    typedef std::set<std::string> Classes;
+
     /// Constructor, used in replying to a message.
     ///
     /// @param msg_type type of message (e.g. DHCPDISOVER=1)
@@ -550,6 +554,37 @@ public:
     /// performance).
     std::vector<uint8_t> data_;
 
+    /// @brief Checks whether a client belongs to a given class
+    ///
+    /// @param client_class name of the class
+    /// @return true if belongs
+    bool inClass(const std::string& client_class);
+
+    /// @brief Adds packet to a specified class
+    ///
+    /// A packet can be added to the same class repeatedly. Any additional
+    /// attempts to add to a class the packet already belongs to, will be
+    /// ignored silently.
+    ///
+    /// @note It is a matter of naming convention. Conceptually, the server
+    /// processes a stream of packets, with some packets belonging to given
+    /// classes. From that perspective, this method adds a packet to specifed
+    /// class. Implementation wise, it looks the opposite - the class name
+    /// is added to the packet. Perhaps the most appropriate name for this
+    /// method would be associateWithClass()? But that seems overly long,
+    /// so I decided to stick with addClass().
+    ///
+    /// @param client_class name of the class to be added
+    void addClass(const std::string& client_class);
+
+    /// @brief Classes this packet belongs to.
+    ///
+    /// This field is public, so the code outside of Pkt4 class can iterate over
+    /// existing classes. Having it public also solves the problem of returned
+    /// reference lifetime. It is preferred to use @ref inClass and @ref addClass
+    /// should be used to operate on this field.
+    Classes classes_;
+
 private:
 
     /// @brief Generic method that validates and sets HW address.

+ 11 - 0
src/lib/dhcp/pkt6.cc

@@ -586,6 +586,17 @@ void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
     }
 }
 
+bool
+Pkt6::inClass(const std::string& client_class) {
+    return (classes_.find(client_class) != classes_.end());
+}
+
+void
+Pkt6::addClass(const std::string& client_class) {
+    if (classes_.find(client_class) == classes_.end()) {
+        classes_.insert(client_class);
+    }
+}
 
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 33 - 0
src/lib/dhcp/pkt6.h

@@ -23,6 +23,7 @@
 #include <boost/shared_ptr.hpp>
 
 #include <iostream>
+#include <set>
 
 #include <time.h>
 
@@ -47,6 +48,9 @@ public:
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
     };
 
+    /// Container for storing client classes
+    typedef std::set<std::string> Classes;
+
     /// @brief defines relay search pattern
     ///
     /// Defines order in which options are searched in a message that
@@ -426,6 +430,35 @@ public:
     /// data format change etc.
     OptionBuffer data_;
 
+    /// @brief Checks whether a client belongs to a given class
+    ///
+    /// @param client_class name of the class
+    /// @return true if belongs
+    bool inClass(const std::string& client_class);
+
+    /// @brief Adds packet to a specified class
+    ///
+    /// A packet can be added to the same class repeatedly. Any additional
+    /// attempts to add to a class the packet already belongs to, will be
+    /// ignored silently.
+    ///
+    /// @note It is a matter of naming convention. Conceptually, the server
+    /// processes a stream of packets, with some packets belonging to given
+    /// classes. From that perspective, this method adds a packet to specifed
+    /// class. Implementation wise, it looks the opposite - the class name
+    /// is added to the packet. Perhaps the most appropriate name for this
+    /// method would be associateWithClass()? But that seems overly long,
+    /// so I decided to stick with addClass().
+    ///
+    /// @param client_class name of the class to be added
+    void addClass(const std::string& client_class);
+
+    /// @brief Classes this packet belongs to.
+    ///
+    /// This field is public, so code can iterate over existing classes.
+    /// Having it public also solves the problem of returned reference lifetime.
+    Classes classes_;
+
 protected:
     /// Builds on wire packet for TCP transmission.
     ///

+ 3 - 2
src/lib/dhcp/std_option_defs.h

@@ -160,7 +160,7 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = {
     { "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME,
       OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER,
-      OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+      OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
     { "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER,
       OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
@@ -230,7 +230,8 @@ RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 // status-code
 RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
 // vendor-class
-RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_UINT16_TYPE,
+            OPT_STRING_TYPE);
 
 /// Standard DHCPv6 option definitions.
 ///

+ 45 - 1
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -30,6 +30,9 @@
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
 #include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <boost/pointer_cast.hpp>
 
 #include <gtest/gtest.h>
 
@@ -890,7 +893,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
                                     typeid(OptionInt<uint32_t>));
 
     LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end,
-                                    typeid(Option));
+                                    typeid(OptionString));
 
     LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end,
                                     typeid(Option));
@@ -1122,4 +1125,45 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
                                     typeid(Option6AddrLst));
 }
 
+// tests whether v6 vendor-class option can be parsed properly.
+TEST_F(LibDhcpTest, vendorClass6) {
+
+    isc::dhcp::OptionCollection options; // Will store parsed option here
+
+    // Exported from wireshark: vendor-class option with enterprise-id = 4491
+    // and a single data entry containing "eRouter1.0"
+    string vendor_class_hex = "001000100000118b000a65526f75746572312e30";
+    OptionBuffer bin;
+
+    // Decode the hex string and store it in bin (which happens
+    // to be OptionBuffer format)
+    isc::util::encode::decodeHex(vendor_class_hex, bin);
+
+    ASSERT_NO_THROW ({
+            LibDHCP::unpackOptions6(bin, "dhcp6", options);
+        });
+
+    EXPECT_EQ(options.size(), 1); // There should be 1 option.
+
+    // Option vendor-class should be there
+    ASSERT_FALSE(options.find(D6O_VENDOR_CLASS) == options.end());
+
+    // It should be of type OptionCustom
+    boost::shared_ptr<OptionCustom> vclass =
+        boost::dynamic_pointer_cast<OptionCustom> (options.begin()->second);
+    ASSERT_TRUE(vclass);
+
+    // Let's investigate if the option content is correct
+
+    // 3 fields expected: vendor-id, data-len and data
+    ASSERT_EQ(3, vclass->getDataFieldsNum());
+
+    EXPECT_EQ(4491, vclass->readInteger<uint32_t>
+              (VENDOR_CLASS_ENTERPRISE_ID_INDEX)); // vendor-id=4491
+    EXPECT_EQ(10, vclass->readInteger<uint16_t>
+              (VENDOR_CLASS_DATA_LEN_INDEX)); // data len = 10
+    EXPECT_EQ("eRouter1.0", vclass->readString
+              (VENDOR_CLASS_STRING_INDEX)); // data="eRouter1.0"
 }
+
+} // end of anonymous space

+ 30 - 0
src/lib/dhcp/tests/pkt4_unittest.cc

@@ -17,6 +17,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/libdhcp++.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp/option_string.h>
 #include <dhcp/pkt4.h>
 #include <exceptions/exceptions.h>
@@ -818,7 +819,36 @@ TEST_F(Pkt4Test, isRelayed) {
     // should throw an exception.
     pkt.setGiaddr(IOAddress("0.0.0.0"));
     EXPECT_THROW(pkt.isRelayed(), isc::BadValue);
+}
 
+// Tests whether a packet can be assigned to a class and later
+// checked if it belongs to a given class
+TEST_F(Pkt4Test, clientClasses) {
+    Pkt4 pkt(DHCPOFFER, 1234);
+
+    // Default values (do not belong to any class)
+    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+    EXPECT_TRUE(pkt.classes_.empty());
+
+    // Add to the first class
+    pkt.addClass(DOCSIS3_CLASS_EROUTER);
+    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+    ASSERT_FALSE(pkt.classes_.empty());
+
+    // Add to a second class
+    pkt.addClass(DOCSIS3_CLASS_MODEM);
+    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+
+    // Check that it's ok to add to the same class repeatedly
+    EXPECT_NO_THROW(pkt.addClass("foo"));
+    EXPECT_NO_THROW(pkt.addClass("foo"));
+    EXPECT_NO_THROW(pkt.addClass("foo"));
+
+    // Check that the packet belongs to 'foo'
+    EXPECT_TRUE(pkt.inClass("foo"));
 }
 
 } // end of anonymous namespace

+ 31 - 0
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -22,6 +22,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <util/range_utilities.h>
 
 #include <boost/bind.hpp>
@@ -779,4 +780,34 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
     EXPECT_FALSE(opt);
 }
 
+// Tests whether a packet can be assigned to a class and later
+// checked if it belongs to a given class
+TEST_F(Pkt6Test, clientClasses) {
+    Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+    // Default values (do not belong to any class)
+    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+    EXPECT_TRUE(pkt.classes_.empty());
+
+    // Add to the first class
+    pkt.addClass(DOCSIS3_CLASS_EROUTER);
+    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+    ASSERT_FALSE(pkt.classes_.empty());
+
+    // Add to a second class
+    pkt.addClass(DOCSIS3_CLASS_MODEM);
+    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+
+    // Check that it's ok to add to the same class repeatedly
+    EXPECT_NO_THROW(pkt.addClass("foo"));
+    EXPECT_NO_THROW(pkt.addClass("foo"));
+    EXPECT_NO_THROW(pkt.addClass("foo"));
+
+    // Check that the packet belongs to 'foo'
+    EXPECT_TRUE(pkt.inClass("foo"));
+}
+
 }

+ 1 - 1
src/lib/dhcpsrv/dbaccess_parser.h

@@ -94,7 +94,7 @@ public:
     ///
     /// Creates an instance of this parser.
     ///
-    /// @param name Name of the parameter used to access the configuration.
+    /// @param param_name Name of the parameter used to access the configuration.
     ///
     /// @return Pointer to a DbAccessParser.  The caller is responsible for
     ///         destroying the parser after use.

+ 1 - 1
src/lib/dhcpsrv/lease_mgr.h

@@ -214,7 +214,7 @@ public:
     /// @param subnet_id A subnet identifier.
     ///
     /// @return A pointer to the lease or NULL if the lease is not found.
-    virtual Lease4Ptr getLease4(const ClientId& clientid, const HWAddr& hwaddr,
+    virtual Lease4Ptr getLease4(const ClientId& client_id, const HWAddr& hwaddr,
                                 SubnetID subnet_id) const = 0;
 
     /// @brief Returns existing IPv4 lease for specified client-id