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
 731.	[func]		tmark
 	b10-dhcp4 now parses parameters which support DHCP-DDNS updates via
 	b10-dhcp4 now parses parameters which support DHCP-DDNS updates via
 	the DHCP-DDNS module, b10-dhcp-ddns.  These parameters are part of new
 	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 dhcpv4ConfigInherit
  *   - @subpage dhcpv4OptionsParse
  *   - @subpage dhcpv4OptionsParse
  *   - @subpage dhcpv4DDNSIntegration
  *   - @subpage dhcpv4DDNSIntegration
+ *   - @subpage dhcpv4Classifier
  *   - @subpage dhcpv4Other
  *   - @subpage dhcpv4Other
  * - @subpage dhcp6
  * - @subpage dhcp6
  *   - @subpage dhcpv6Session
  *   - @subpage dhcpv6Session
@@ -67,6 +68,7 @@
  *   - @subpage dhcpv6ConfigInherit
  *   - @subpage dhcpv6ConfigInherit
  *   - @subpage dhcpv6DDNSIntegration
  *   - @subpage dhcpv6DDNSIntegration
  *   - @subpage dhcpv6OptionsParse
  *   - @subpage dhcpv6OptionsParse
+ *   - @subpage dhcpv6Classifier
  *   - @subpage dhcpv6Other
  *   - @subpage dhcpv6Other
  * - @subpage libdhcp
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIntro

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

@@ -107,7 +107,7 @@ protected:
     /// various configuration values. Installing the dummy handler
     /// various configuration values. Installing the dummy handler
     /// that guarantees to return success causes initial configuration
     /// that guarantees to return success causes initial configuration
     /// to be stored for the session being created and that it can
     /// 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.
     /// @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,
 (upper part of) dhcp4_srv.cc file. Once the configuration is implemented,
 these constants will be removed.
 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
 @section dhcpv4Other Other DHCPv4 topics
 
 
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
  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
 FQDN or Hostname option sent by a client. This is likely because the client's
 name was malformed or due to internal server error.
 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
 % DHCP4_COMMAND_RECEIVED received command %1, arguments: %2
 A debug message listing the command (and possible arguments) received
 A debug message listing the command (and possible arguments) received
 from the BIND 10 control system by the IPv4 DHCP server.
 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.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/option_vendor.h>
+#include <dhcp/option_string.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_log.h>
@@ -312,6 +313,9 @@ Dhcpv4Srv::run() {
             callout_handle->getArgument("query4", query);
             callout_handle->getArgument("query4", query);
         }
         }
 
 
+        // Assign this packet to one or more classes if needed
+        classifyPacket(query);
+
         try {
         try {
             switch (query->getType()) {
             switch (query->getType()) {
             case DHCPDISCOVER:
             case DHCPDISCOVER:
@@ -368,6 +372,18 @@ Dhcpv4Srv::run() {
             continue;
             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
         // Specifies if server should do the packing
         bool skip_pack = false;
         bool skip_pack = false;
 
 
@@ -1766,5 +1782,86 @@ Dhcpv4Srv::ifaceMgrSocket4ErrorHandler(const std::string& errmsg) {
     LOG_WARN(dhcp4_logger, DHCP4_OPEN_SOCKET_FAIL).arg(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 dhcp
 }   // namespace isc
 }   // namespace isc

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

@@ -262,7 +262,7 @@ protected:
     /// using separate options within their respective vendor-option spaces.
     /// using separate options within their respective vendor-option spaces.
     ///
     ///
     /// @param question DISCOVER or REQUEST message from a client.
     /// @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);
     void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer);
 
 
     /// @brief Assigns a lease and appends corresponding options
     /// @brief Assigns a lease and appends corresponding options
@@ -559,6 +559,25 @@ protected:
                          const std::string& option_space,
                          const std::string& option_space,
                          isc::dhcp::OptionCollection& options);
                          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:
 private:
 
 
     /// @brief Constructs netmask option based on subnet4
     /// @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_);
     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
     /// @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
     /// @return relayed DISCOVER
     Pkt4Ptr captureRelayedDiscover();
     Pkt4Ptr captureRelayedDiscover();
@@ -253,6 +254,13 @@ public:
     createPacketFromBuffer(const Pkt4Ptr& src_pkt,
     createPacketFromBuffer(const Pkt4Ptr& src_pkt,
                            Pkt4Ptr& dst_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
     /// @brief generates a DHCPv4 packet based on provided hex string
     ///
     ///
@@ -443,6 +451,7 @@ public:
     using Dhcpv4Srv::srvidToString;
     using Dhcpv4Srv::srvidToString;
     using Dhcpv4Srv::unpackOptions;
     using Dhcpv4Srv::unpackOptions;
     using Dhcpv4Srv::name_change_reqs_;
     using Dhcpv4Srv::name_change_reqs_;
+    using Dhcpv4Srv::classifyPacket;
 };
 };
 
 
 }; // end of isc::dhcp::test namespace
 }; // 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() {
 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)
 User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
     Source port: bootps (67)
     Source port: bootps (67)
@@ -98,7 +101,7 @@ Bootstrap Protocol
     Magic cookie: DHCP
     Magic cookie: DHCP
     Option: (53) DHCP Message Type
     Option: (53) DHCP Message Type
     Option: (55) Parameter Request List
     Option: (55) Parameter Request List
-    Option: (60) Vendor class identifier
+    Option: (60) Vendor class identifier (docsis3.0)
     Option: (125) V-I Vendor-specific Information
     Option: (125) V-I Vendor-specific Information
       - suboption 1 (Option Request): requesting option 2
       - suboption 1 (Option Request): requesting option 2
       - suboption 5 (Modem Caps): 117 bytes
       - suboption 5 (Modem Caps): 117 bytes
@@ -129,6 +132,59 @@ Bootstrap Protocol
     return (packetFromCapture(hex_string));
     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::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace
 }; // end of isc 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
     /// various configuration values. Installing the dummy handler
     /// that guarantees to return success causes initial configuration
     /// that guarantees to return success causes initial configuration
     /// to be stored for the session being created and that it can
     /// 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.
     /// @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
 which define its configuration (including dynamically created option
 definitions).
 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
  @section dhcpv6Other Other DHCPv6 topics
 
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
  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
 This debug message is issued just before the IPv6 DHCP server attempts
 to establish a session with the BIND 10 control channel.
 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
 % DHCP6_CLIENTID_MISSING mandatory client-id option is missing, message from %1 dropped
 This error message indicates that the received message is being dropped
 This error message indicates that the received message is being dropped
 because it does not include the mandatory client-id option necessary for
 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);
             callout_handle->getArgument("query6", query);
         }
         }
 
 
+        // Assign this packet to a class, if possible
+        classifyPacket(query);
+
         try {
         try {
                 NameChangeRequestPtr ncr;
                 NameChangeRequestPtr ncr;
             switch (query->getType()) {
             switch (query->getType()) {
@@ -2423,5 +2426,39 @@ Dhcpv6Srv::ifaceMgrSocket6ErrorHandler(const std::string& errmsg) {
     LOG_WARN(dhcp6_logger, DHCP6_OPEN_SOCKET_FAIL).arg(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_offset,
                          size_t* relay_msg_len);
                          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:
 private:
 
 
     /// @brief Implements the error handler for socket open failure.
     /// @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());
     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
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 /// to call processX() methods.

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

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

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

@@ -17,9 +17,9 @@
 #include <string>
 #include <string>
 
 
 /// @file   wireshark.cc
 /// @file   wireshark.cc
-/// 
+///
 /// @brief  contains packet captures imported from Wireshark
 /// @brief  contains packet captures imported from Wireshark
-/// 
+///
 /// These are actual packets captured over wire. They are used in various
 /// These are actual packets captured over wire. They are used in various
 /// tests.
 /// tests.
 ///
 ///
@@ -33,6 +33,10 @@
 /// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
 /// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
 /// 7. Make sure you decribe the capture appropriately
 /// 7. Make sure you decribe the capture appropriately
 /// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.)
 /// 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;
 using namespace std;
 
 
@@ -81,7 +85,7 @@ Pkt6Ptr Dhcpv6SrvTest::captureRelayedSolicit() {
     //     - ORO (7)
     //     - ORO (7)
 
 
     // string exported from Wireshark
     // string exported from Wireshark
-    string hex_string = 
+    string hex_string =
         "0c0500000000000000000000000000000000fc00000000000000000000000000000900"
         "0c0500000000000000000000000000000000fc00000000000000000000000000000900"
         "12000231350009002c010517100001000e0001000151b5e46208002758f1e80003000c"
         "12000231350009002c010517100001000e0001000151b5e46208002758f1e80003000c"
         "000000010000000000000000000600020007";
         "000000010000000000000000000600020007";
@@ -102,7 +106,7 @@ Pkt6Ptr Dhcpv6SrvTest::captureRelayedSolicit() {
 Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
 Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
 
 
     // This is an actual DOCSIS packet
     // This is an actual DOCSIS packet
-    // RELAY-FORW (12) 
+    // RELAY-FORW (12)
     //  - Relay Message
     //  - Relay Message
     //    - SOLICIT (1)
     //    - SOLICIT (1)
     //      - client-id
     //      - client-id
@@ -132,7 +136,7 @@ Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
     //    - Suboption 1026: Cable Modem MAC addr = 10:0d:7f:00:07:88
     //    - Suboption 1026: Cable Modem MAC addr = 10:0d:7f:00:07:88
 
 
     // string exported from Wireshark
     // string exported from Wireshark
-    string hex_string = 
+    string hex_string =
         "0c002a0288fe00fe00015a8d09fffe7af955fe80000000000000120d7ffffe00078800"
         "0c002a0288fe00fe00015a8d09fffe7af955fe80000000000000120d7ffffe00078800"
         "090189010d397f0001000a00030001100d7f000788000300287f000788000000000000"
         "090189010d397f0001000a00030001100d7f000788000300287f000788000000000000"
         "000000050018000000000000000000000000000000000000000000000000000e000000"
         "000000050018000000000000000000000000000000000000000000000000000e000000"
@@ -159,5 +163,141 @@ Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
     return (pkt);
     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::test namespace
 }; // end of isc 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/std_option_defs.h>
 #include <dhcp/option_data_types.h>
 #include <dhcp/option_data_types.h>
 
 
-
-namespace {
+namespace isc {
+namespace dhcp {
 
 
 #define VENDOR_ID_CABLE_LABS 4491
 #define VENDOR_ID_CABLE_LABS 4491
 
 
@@ -61,6 +61,11 @@ const OptionDefParams DOCSIS3_V6_DEFS[] = {
 /// Number of option definitions defined.
 /// Number of option definitions defined.
 const int DOCSIS3_V6_DEFS_SIZE  = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefParams);
 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
 #endif // DOCSIS3_OPTION_DEFS_H

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

@@ -52,6 +52,14 @@ VendorOptionDefContainers LibDHCP::vendor4_defs_;
 
 
 VendorOptionDefContainers LibDHCP::vendor6_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
 // Let's keep it in .cc file. Moving it to .h would require including optionDefParams
 // definitions there
 // definitions there
 void initOptionSpace(OptionDefContainer& defs,
 void initOptionSpace(OptionDefContainer& defs,

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

@@ -90,7 +90,8 @@ public:
 
 
     /// sets address in this option.
     /// 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,
     void setPrefix(const isc::asiolink::IOAddress& prefix,
                    uint8_t length) { addr_ = prefix; prefix_len_ = length; }
                    uint8_t length) { addr_ = prefix; prefix_len_ = length; }
 
 

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

@@ -25,6 +25,11 @@
 namespace isc {
 namespace isc {
 namespace dhcp {
 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.
 /// @brief This class represents vendor-specific information option.
 ///
 ///
 /// As specified in RFC3925, the option formatting is slightly different
 /// As specified in RFC3925, the option formatting is slightly different
@@ -72,7 +77,7 @@ public:
 
 
     /// @brief Sets enterprise identifier
     /// @brief Sets enterprise identifier
     ///
     ///
-    /// @param value vendor identifier
+    /// @param vendor_id vendor identifier
     void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; }
     void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; }
 
 
     /// @brief Returns enterprise identifier
     /// @brief Returns enterprise identifier

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

@@ -480,7 +480,17 @@ Pkt4::isRelayed() const {
               << static_cast<int>(getHops()) << ". Valid values"
               << static_cast<int>(getHops()) << ". Valid values"
               " are: (giaddr = 0 and hops = 0) or (giaddr != 0 and"
               " are: (giaddr = 0 and hops = 0) or (giaddr != 0 and"
               "hops != 0)");
               "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
 } // end of namespace isc::dhcp

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

@@ -26,6 +26,7 @@
 
 
 #include <iostream>
 #include <iostream>
 #include <vector>
 #include <vector>
+#include <set>
 
 
 #include <time.h>
 #include <time.h>
 
 
@@ -52,6 +53,9 @@ public:
     /// to check whether client requested broadcast response.
     /// to check whether client requested broadcast response.
     const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
     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.
     /// Constructor, used in replying to a message.
     ///
     ///
     /// @param msg_type type of message (e.g. DHCPDISOVER=1)
     /// @param msg_type type of message (e.g. DHCPDISOVER=1)
@@ -550,6 +554,37 @@ public:
     /// performance).
     /// performance).
     std::vector<uint8_t> data_;
     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:
 private:
 
 
     /// @brief Generic method that validates and sets HW address.
     /// @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::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace

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

@@ -23,6 +23,7 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
 #include <iostream>
 #include <iostream>
+#include <set>
 
 
 #include <time.h>
 #include <time.h>
 
 
@@ -47,6 +48,9 @@ public:
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
         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
     /// @brief defines relay search pattern
     ///
     ///
     /// Defines order in which options are searched in a message that
     /// Defines order in which options are searched in a message that
@@ -426,6 +430,35 @@ public:
     /// data format change etc.
     /// data format change etc.
     OptionBuffer data_;
     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:
 protected:
     /// Builds on wire packet for TCP transmission.
     /// 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,
     { "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME,
       OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
       OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER,
     { "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,
     { "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER,
       OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
       OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_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
 // status-code
 RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
 RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
 // vendor-class
 // 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.
 /// 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_string.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/option_vendor.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <boost/pointer_cast.hpp>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -890,7 +893,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
                                     typeid(OptionInt<uint32_t>));
                                     typeid(OptionInt<uint32_t>));
 
 
     LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end,
     LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end,
-                                    typeid(Option));
+                                    typeid(OptionString));
 
 
     LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end,
     LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end,
                                     typeid(Option));
                                     typeid(Option));
@@ -1122,4 +1125,45 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
                                     typeid(Option6AddrLst));
                                     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 <asiolink/io_address.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/libdhcp++.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_string.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
@@ -818,7 +819,36 @@ TEST_F(Pkt4Test, isRelayed) {
     // should throw an exception.
     // should throw an exception.
     pkt.setGiaddr(IOAddress("0.0.0.0"));
     pkt.setGiaddr(IOAddress("0.0.0.0"));
     EXPECT_THROW(pkt.isRelayed(), isc::BadValue);
     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
 } // 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.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <util/range_utilities.h>
 #include <util/range_utilities.h>
 
 
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
@@ -779,4 +780,34 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
     EXPECT_FALSE(opt);
     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.
     /// 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
     /// @return Pointer to a DbAccessParser.  The caller is responsible for
     ///         destroying the parser after use.
     ///         destroying the parser after use.

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

@@ -214,7 +214,7 @@ public:
     /// @param subnet_id A subnet identifier.
     /// @param subnet_id A subnet identifier.
     ///
     ///
     /// @return A pointer to the lease or NULL if the lease is not found.
     /// @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;
                                 SubnetID subnet_id) const = 0;
 
 
     /// @brief Returns existing IPv4 lease for specified client-id
     /// @brief Returns existing IPv4 lease for specified client-id