Browse Source

[1186] libdhcp now is able to parse and build packets and options.

Tomek Mrugalski 13 years ago
parent
commit
b47533e918

+ 55 - 0
src/bin/dhcp6/tests/iface_mgr_unittest.cc

@@ -54,6 +54,61 @@ public:
     }
 };
 
+// uncomment this test to create packet writer. It will
+// write incoming DHCPv6 packets as C arrays. That is useful
+// for generating test sequences based on actual traffic
+//
+// TODO: this potentially should be moved to a separate tool
+//
+
+#if 0
+TEST_F(IfaceMgrTest, dhcp6Sniffer) {
+    // testing socket operation in a portable way is tricky
+    // without interface detection implemented
+
+    unlink("interfaces.txt");
+
+    ofstream interfaces("interfaces.txt", ios::ate);
+    interfaces << "eth0 fe80::21e:8cff:fe9b:7349";
+    interfaces.close();
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    Pkt6 * pkt = 0;
+    int cnt = 0;
+    cout << "---8X-----------------------------------------" << endl;
+    while (true) {
+        pkt = ifacemgr->receive();
+        
+        cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl;
+        cout << "Pkt6 *capture" << cnt++ << "() {" << endl;
+        cout << "    Pkt6* pkt;" << endl;
+        cout << "    pkt = new Pkt6(" << pkt->data_len_ << ");" << endl;
+        cout << "    pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl;
+        cout << "    pkt->remote_addr_ = IOAddress(\"" << pkt->remote_addr_.toText() << "\");" << endl;
+        cout << "    pkt->local_port_ = " << pkt-> local_port_ << ";" << endl;
+        cout << "    pkt->local_addr_ = IOAddress(\"" << pkt->local_addr_.toText() << "\");" << endl;
+        cout << "    pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl;
+        cout << "    pkt->iface_ = \"" << pkt->iface_ << "\";" << endl;
+        for (int i=0; i< pkt->data_len_; i++) {
+            cout << "    pkt->data_[" << i << "]=" << (int)(unsigned char)pkt->data_[i] << "; ";
+            if (!(i%4))
+                cout << endl;
+        }
+        cout << endl;
+        cout << "    return (pkt);" << endl;
+        cout << "}" << endl << endl;
+
+        delete pkt;
+    }
+    cout << "---8X-----------------------------------------" << endl;
+        
+    // never happens. Infinite loop is infinite
+    delete pkt;
+    delete ifacemgr;
+}
+#endif
+
 TEST_F(IfaceMgrTest, basic) {
     // checks that IfaceManager can be instantiated
 

+ 1 - 0
src/lib/dhcp/Makefile.am

@@ -10,6 +10,7 @@ CLEANFILES = *.gcno *.gcda
 lib_LTLIBRARIES = libdhcp.la
 libdhcp_la_SOURCES  =
 libdhcp_la_SOURCES += libdhcp.cc libdhcp.h
+libdhcp_la_SOURCES += option.cc option.h
 libdhcp_la_SOURCES += dhcp6.h
 libdhcp_la_SOURCES += pkt6.cc pkt6.h
 

+ 89 - 2
src/lib/dhcp/libdhcp.cc

@@ -12,10 +12,97 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
 #include "dhcp/libdhcp.h"
+#include "config.h"
 
+using namespace std;
 using namespace isc::dhcp;
 
-std::string LibDHCP::version() {
-    return "0";
+std::string
+LibDHCP::version() {
+    return PACKAGE_VERSION;
+}
+
+/**
+ * Parses provided buffer and creates Option objects.
+ *
+ * Parses provided buf array and stores created Option objects
+ * in options container.
+ *
+ * @param buf Buffer to be parsed.
+ * @param offset Specifies offset for the first option.
+ * @param options Reference to option container. Options will be
+ *        put here.
+ *
+ * @return offset to first byte after last parsed option
+ */
+unsigned int
+LibDHCP::unpackOptions6(boost::shared_array<char>& buf,
+                        int buf_len,
+                        unsigned short offset,
+                        isc::dhcp::Option::Option6Lst& options) {
+    int len = buf_len - offset;
+    while (len>4) {
+        int opt_type = buf[offset]*256 + buf[offset+1];
+        offset += 2;
+        len -= 2;
+        int opt_len = buf[offset]*256 + buf[offset+1];
+        offset += 2;
+        len -= 2;
+
+        if (opt_len > len) {
+            cout << "Packet truncated. Unable to parse option " << opt_type
+                 << ". " << len << " bytes left in buffer, but option "
+                 << "len=" << opt_len << endl;
+            return (offset);
+        }
+
+        boost::shared_ptr<Option> opt(new Option(Option::V6,
+                                                 opt_type,
+                                                 buf,
+                                                 offset,
+                                                 opt_len));
+        // add option to options
+        options.insert(pair<int, boost::shared_ptr<Option> >(opt_type, opt));
+        offset += opt_len;
+        len -= opt_len;
+        cout << "Parse opt=" << opt_type << ", opt_len=" << opt_len << ", bytes left=" << len << endl;
+    }
+
+    if (len != 0) {
+        cout << "There are " << len << " bytes left to parse." << endl;
+    }
+
+    return (offset);
+}
+
+unsigned int
+LibDHCP::packOptions6(boost::shared_array<char>& data,
+                      int data_len,
+                      unsigned short offset,
+                      isc::dhcp::Option::Option6Lst& options) {
+    char* buf = &data[offset];
+    char* end = &data[data_len-1]; // last byte in shared array
+    try {
+        for (isc::dhcp::Option::Option6Lst::iterator it = options.begin();
+             it != options.end();
+             ++it) {
+            unsigned short opt_len = (*it).second->len();
+            if (buf+opt_len > end) {
+                isc_throw(OutOfRange, "Failed to build option" <<
+                          (*it).first << ": out of buffer");
+            }
+            buf = (*it).second->pack(buf, opt_len);
+            offset += opt_len;
+            data_len -= opt_len;
+        }
+    }
+    catch (Exception e) {
+        cout << "Packet build failed." << endl;
+        return (-1);
+    }
+    cout << "Packet built" << endl;
+    return (offset);
 }

+ 16 - 1
src/lib/dhcp/libdhcp.h

@@ -16,6 +16,7 @@
 #define LIBDHCP_H_
 
 #include <iostream>
+#include "dhcp/pkt6.h"
 
 namespace isc {
 namespace dhcp {
@@ -23,9 +24,23 @@ namespace dhcp {
 class LibDHCP {
 
 public:
+    LibDHCP();
     static std::string version();
-    
 
+    bool parsePkt6(Pkt6& pkt); 
+    bool builtPkt6(Pkt6& pkt);
+
+    
+    static unsigned int
+    packOptions6(boost::shared_array<char>& buf,
+                 int buf_len,
+                 unsigned short offset,
+                 isc::dhcp::Option::Option6Lst& options_);
+    static unsigned int
+    unpackOptions6(boost::shared_array<char>& buf,
+                   int buf_len,
+                   unsigned short offset,
+                   isc::dhcp::Option::Option6Lst& options_);
 };
 
 }

+ 192 - 0
src/lib/dhcp/option.cc

@@ -0,0 +1,192 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <sstream>
+#include <iomanip>
+#include <boost/shared_array.hpp>
+#include "exceptions/exceptions.h"
+
+#include "option.h"
+#include "libdhcp.h"
+
+using namespace std;
+using namespace isc::dhcp;
+
+Option::Option(Universe u, unsigned short type)
+    :universe_(u), type_(type) {
+
+
+}
+
+Option::Option(Universe u, unsigned short type, boost::shared_array<char> buf, 
+               unsigned int offset, unsigned int len)
+    :universe_(u), type_(type), data_(buf), 
+     offset_(offset),
+     len_(len) {
+
+    // sanity checks
+    // TODO: universe must be in V4 and V6
+}
+
+char* Option::pack(char* buf, unsigned int len) {
+    switch (universe_) {
+    case V4:
+        return pack4(buf, len);
+    case V6:
+        return pack6(buf, len);
+    default:
+        isc_throw(BadValue, "Unknown universe defined for Option " << type_);
+    }
+
+    return NULL; // should not happen
+}
+
+char* 
+Option::pack4(char* buf, unsigned short len) {
+    if (this->len()>len) {
+        isc_throw(OutOfRange, "Failed to pack v4 option=" << 
+                  type_ << ",len=" << len_ << ": too small buffer.");
+    }
+    buf[0] = type_;
+    buf[1] = len_;
+    buf += 2;
+    memcpy(buf, &data_[0], len_);
+
+    return buf + len_;
+}
+
+char* Option::pack6(char* buf, unsigned short len) {
+    if (this->len()>len) {
+        isc_throw(OutOfRange, "Failed to pack v6 option=" << 
+                  type_ << ",len=" << len_ << ": too small buffer.");
+    }
+    *(uint16_t*)buf = htons(type_);
+    buf += 2;
+    *(uint16_t*)buf = htons(len_);
+    buf += 2;
+    memcpy(buf, &data_[0], len_);
+
+    return buf + len_;
+}
+
+unsigned int 
+Option::unpack(boost::shared_array<char> buf, 
+               unsigned int buf_len,
+               unsigned int offset, 
+               unsigned int parse_len) {
+    switch (universe_) {
+    case V4:
+        return unpack4(buf, buf_len, offset, parse_len);
+    case V6:
+        return unpack6(buf, buf_len, offset, parse_len);
+    default:
+        isc_throw(BadValue, "Unknown universe defined for Option " << type_);
+    }
+
+    return 0; // should not happen
+}
+
+unsigned int 
+Option::unpack4(boost::shared_array<char>, 
+                unsigned int ,
+                unsigned int , 
+                unsigned int ) {
+    isc_throw(Unexpected, "IPv4 support not implemented yet.");
+    return 0;
+}
+/** 
+ * Parses buffer and creates collection of Option objects.
+ * 
+ * @param buf pointer to buffer
+ * @param buf_len length of buf
+ * @param offset offset, where start parsing option
+ * @param parse_len how many bytes should be parsed
+ * 
+ * @return offset after last parsed option
+ */
+unsigned int 
+Option::unpack6(boost::shared_array<char> buf, 
+                unsigned int buf_len,
+                unsigned int offset, 
+                unsigned int parse_len) {
+
+    if (buf_len < offset+parse_len) {
+        isc_throw(OutOfRange, "Failed to unpack DHCPv6 option len=" 
+                  << parse_len << " offset=" << offset << " from buffer (length=" 
+                  << buf_len << "): too small buffer.");
+    }
+    
+    data_ = buf;
+    offset_ = offset;
+    len_ = buf_len;
+
+    return LibDHCP::unpackOptions6(buf, buf_len, offset,
+                                   optionLst_);
+}
+
+unsigned short Option::len() {
+    switch (universe_) {
+    case V4:
+        return len_ + 2; // DHCPv4 option header length: 2 bytes
+    case V6:
+        return len_ + 4; // DHCPv6 option header length: 4 bytes
+    default:
+        isc_throw(BadValue, "Unknown universe defined for Option " << type_);
+    }
+
+    return 0; // should not happen
+}
+
+bool Option::valid() {
+    // total length of buffer is not stored. shared_array is not very useful.
+    // we should either add buf_len field or better replace shared_array
+    // with shared_ptr to array
+    if (universe_ != V4 && 
+        universe_ != V6) {
+        return (false);
+    }
+
+    return (true);
+}
+
+/** 
+ * Converts generic option to string.
+ * 
+ * @return string that represents option.
+ */
+std::string Option::toText() {
+    std::stringstream tmp;
+    tmp << type_ << "(len=" << len_ << "):";
+
+    for (unsigned int i=0; i<len_; i++) {
+        if (i) {
+            tmp << ":";
+        }
+        tmp << setfill('0') << setw(2) << hex << (unsigned short)data_[offset_+i];
+    }
+    return tmp.str();
+}
+
+unsigned short 
+Option::getType() {
+    return type_;
+}
+
+Option::~Option() {
+
+}
+

+ 109 - 0
src/lib/dhcp/option.h

@@ -0,0 +1,109 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_H_
+#define OPTION_H_
+
+#include <string>
+#include <map>
+#include <boost/shared_array.hpp>
+
+namespace isc {
+namespace dhcp {
+
+class Option {
+public:
+    typedef std::map<unsigned int, boost::shared_ptr<Option> > Option4Lst;
+    typedef std::multimap<unsigned int, boost::shared_ptr<Option> > Option6Lst;
+
+    enum Universe { V4, V6 };
+
+    // ctor, used for options constructed, usually during transmission
+    Option(Universe u, unsigned short type); 
+
+    // ctor, used for received options
+    // boost::shared_array allows sharing a buffer, but it requires that 
+    // different instances share pointer to the whole array, not point
+    // to different elements in shared array. Therefore we need to share
+    // pointer to the whole array and remember offset where data for
+    // this option begins
+    Option(Universe u, unsigned short type, boost::shared_array<char> buf, 
+           unsigned int offset, 
+           unsigned int len);
+
+    // writes option in wire-format to buf, returns pointer to first unused 
+    // byte after stored option
+    virtual char* pack(char* buf, unsigned int len);
+
+    // parses received buffer, returns pointer to first unused byte 
+    // after parsed option
+    // TODO: Do we need this overload? Commented out for now
+    // virtual const char* unpack(const char* buf, unsigned int len);
+
+    // parses received buffer, returns offset to the first unused byte after
+    // parsed option
+    virtual unsigned int 
+        unpack(boost::shared_array<char> buf, 
+               unsigned int buf_len,
+               unsigned int offset, 
+               unsigned int parse_len);
+
+    virtual std::string toText();
+
+    unsigned short getType();
+
+    // returns data length (data length + DHCPv4/DHCPv6 option header)
+    virtual unsigned short len();
+    
+    // returns if option is valid (e.g. option may be truncated)
+    virtual bool valid(); 
+
+    // just to force that every option has virtual dtor
+    virtual ~Option(); 
+
+protected:
+    virtual char* pack4(char* buf, unsigned short len);
+    virtual char* pack6(char* buf, unsigned short len);
+    virtual unsigned int unpack4(boost::shared_array<char> buf, 
+                                 unsigned int buf_len,
+                                 unsigned int offset, 
+                                 unsigned int parse_len);
+    virtual unsigned int unpack6(boost::shared_array<char> buf, 
+                                 unsigned int buf_len,
+                                 unsigned int offset, 
+                                 unsigned int parse_len);
+
+    Universe universe_;
+    unsigned short type_;
+
+    boost::shared_array<char> data_;
+    unsigned int data_len_;
+    unsigned int offset_; // data is a shared_pointer that points out to the
+                          // whole packet. offset_ specifies where data for
+                          // this option begins.
+    unsigned int len_; // length of data only. Use len() if you want to know 
+                       // proper length with option header overhead
+    char * value_;
+
+    // 2 different containers are used, because v4 options are unique
+    // and v6 allows multiple instances of the same option types
+    // originally 2 separate containers were planned. Let's try if we
+    // can use a single apporach
+    Option6Lst optionLst_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif

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

@@ -15,30 +15,243 @@
 
 #include "dhcp/dhcp6.h"
 #include "dhcp/pkt6.h"
+#include "dhcp/libdhcp.h"
+#include "exceptions/exceptions.h"
 #include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc::dhcp;
 
 namespace isc {
 
-///
-/// constructor
-///
-/// \param dataLen - length of the data to be allocated
-///
-Pkt6::Pkt6(int dataLen)
+/**
+ * Constructor.
+ *
+ * @param dataLen size of buffer to be allocated for this packet.
+ * @param proto protocol (usually UDP, but TCP will be supported eventually)
+ */
+Pkt6::Pkt6(unsigned int dataLen, DHCPv6Proto_ proto /* = UDP */)
     :local_addr_("::"),
-     remote_addr_("::") {
+     remote_addr_("::"),
+     proto_(proto)
+{
     try {
-	data_ = boost::shared_array<char>(new char[dataLen]);
-	data_len_ = dataLen;
+        data_ = boost::shared_array<char>(new char[dataLen]);
+        data_len_ = dataLen;
     } catch (const std::exception& ex) {
-	// TODO move to LOG_FATAL()
-	// let's continue with empty pkt for now
+        // TODO move to LOG_FATAL()
+        // let's continue with empty pkt for now
         std::cout << "Failed to allocate " << dataLen << " bytes."
                   << std::endl;
         data_len_ = 0;
     }
 }
 
+
+/**
+ * Returns calculated length of the packet.
+ *
+ * This function returns size of required buffer to buld this packet.
+ * To use that function, options_ field must be set.
+ *
+ * @return number of bytes required to build this packet
+ */
+unsigned short Pkt6::len() {
+    unsigned int length = 4; // DHCPv6 header
+
+    for (Option::Option6Lst::iterator it = options_.begin();
+         it != options_.end();
+         ++it) {
+        length += (*it).second->len();
+    }
+
+    return length;
+}
+
+
+/**
+ * Builds on wire packet.
+ *
+ * Prepares on wire packet format.
+ *
+ * @return true if preparation was successful
+ */
+bool
+Pkt6::pack() {
+    switch (proto_) {
+    case UDP:
+        return packUDP();
+    case TCP:
+        return packTCP();
+    default:
+        isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
+    }
+    return false; // never happens
+}
+
+
+/**
+ * Build on wire packet (in UDP format).
+ *
+ * @return true if packet build was successful, false otherwise
+ */
+bool
+Pkt6::packUDP() {
+    unsigned short length = len();
+    if (data_len_ < length) {
+        // we have too small buffer, let's allocate bigger one
+        data_ = boost::shared_array<char>(new char[length]);
+        data_len_ = length;
+    }
+
+    // DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
+    data_[0] = msg_type_;
+    data_[1] = (transid_ >> 16) & 0xff;
+    data_[2] = (transid_ >> 8) & 0xff;
+    data_[3] = (transid_) & 0xff;
+
+    try {
+        // the rest are options
+        unsigned short offset = LibDHCP::packOptions6(data_, length, 4/*offset*/, options_);
+
+        // sanity check
+        if (offset != length) {
+            isc_throw(OutOfRange, "Packet build failed: expected size=" << length
+                      << ", actual len=" << offset);
+        }
+    }
+    catch (Exception e) {
+        cout << "Packet build failed." << endl;
+        return false;
+    }
+    cout << "Packet built, len=" << len() << endl;
+    return true;
+}
+
+
+/**
+ * Builds on wire packet for TCP transmission.
+ *
+ * @note This function is not implemented yet.
+ *
+ * @return
+ */
+bool
+Pkt6::packTCP() {
+    /// TODO Implement this function.
+    isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) not implemented yet.");
+}
+
+/**
+ * Dispatch method that handles binary packet parsing.
+ *
+ * This method calls appropriate dispatch function (unpackUDP or unpackTCP)
+ *
+ * @return true, if parsing was successful, false otherwise
+ */
+bool
+Pkt6::unpack() {
+    switch (proto_) {
+    case UDP:
+        return unpackUDP();
+    case TCP:
+        return unpackTCP();
+    default:
+        isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
+    }
+    return false; // never happens
+}
+
+/**
+ * This method unpacks UDP packet.
+ *
+ * @return true, if parsing was successful, false otherwise
+ */
+bool
+Pkt6::unpackUDP() {
+    if (data_len_ < 4) {
+        std::cout << "DHCPv6 packet truncated. Only " << data_len_
+                  << " bytes. Need at least 4." << std::endl;
+        return false;
+    }
+    msg_type_ = data_[0];
+    transid_ = (data_[1] << 16) + (data_[2] << 8) + data_[3];
+
+    unsigned int offset = LibDHCP::unpackOptions6(data_,
+                                                  data_len_,
+                                                  4, //offset
+                                                  options_);
+    if (offset != data_len_) {
+        cout << "DHCPv6 packet contains trailing garbage. Parsed "
+             << offset << " bytes, packet is " << data_len_ << " bytes."
+             << endl;
+        // just a warning. Ignore trailing garbage and continue
+    }
+    return true;
+}
+
+/**
+ * This method unpacks TDP packet.
+ *
+ * @return true, if parsing was successful, false otherwise
+ */
+bool
+Pkt6::unpackTCP() {
+    isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) not implemented yet.");
+}
+
+
+/**
+ * Returns text representation of the packet.
+ *
+ * This function is useful mainly for debugging.
+ *
+ * @return string with text representation
+ */
+std::string
+Pkt6::toText() {
+    stringstream tmp;
+    tmp << "msgtype=" << msg_type_ << ", transid=0x" << hex << transid_
+        << dec << endl;
+    for (isc::dhcp::Option::Option6Lst::iterator opt=options_.begin();
+         opt != options_.end();
+         ++opt) {
+        tmp << opt->second->toText() << std::endl;
+    }
+    return tmp.str();
+}
+
+/**
+ * Returns the first option of specified type.
+ *
+ * Returns the first option of specified type. Note that in DHCPv6 several
+ * instances of the same option are allowed (and frequently used).
+ * See getOptions().
+ *
+ * @param opt_type option type we are looking for
+ *
+ * @return pointer to found option (or NULL)
+ */
+boost::shared_ptr<isc::dhcp::Option>
+Pkt6::getOption(unsigned short opt_type) {
+    isc::dhcp::Option::Option6Lst::const_iterator x = options_.find(opt_type);
+    if (x!=options_.end()) {
+        return (*x).second;
+    }
+    return boost::shared_ptr<isc::dhcp::Option>(); // NULL
+}
+
+/**
+ * Returns message type.
+ *
+ * @return message type.
+ */
+unsigned char
+Pkt6::getType() {
+    return msg_type_;
+}
+
 Pkt6::~Pkt6() {
     // no need to delete anything shared_ptr will take care of data_
 }

+ 51 - 9
src/lib/dhcp/pkt6.h

@@ -16,29 +16,58 @@
 #define PKT6_H
 
 #include <iostream>
+#include <boost/shared_ptr.hpp>
 #include <boost/shared_array.hpp>
 #include "io_address.h"
+#include "option.h"
 
 namespace isc {
 
+
     class Pkt6 {
     public:
-        Pkt6(int len);
+        enum DHCPv6Proto_ {
+            UDP = 0, // most packets are UDP
+            TCP = 1  // there are TCP DHCPv6 packet (bulk leasequery, failover)
+        };
+
+        Pkt6(unsigned char msg_type,
+             unsigned int transid,
+             DHCPv6Proto_ proto = UDP);
+        Pkt6(unsigned int len, DHCPv6Proto_ proto = UDP);
         ~Pkt6();
 
-        // XXX: probably need getter/setter wrappers
-        //      and hide fields as protected
-        // buffer that holds memory. It is shared_array as options may
-        // share pointer to this buffer
+        bool pack();
+        bool unpack();
+
+        DHCPv6Proto_ getProto();
+        void setProto(DHCPv6Proto_ proto = Pkt6::UDP);
+
+        // returns text representation, useful for debugging
+        std::string toText();
+
+        unsigned short len();
+
+        unsigned char getType();
+        unsigned int getTransid();
+
+        boost::shared_ptr<isc::dhcp::Option> getOption(unsigned short opt_type);
+
+        /// TODO Need to implement getOptions() as well
+
+        /// TODO need getter/setter wrappers
+        ///      and hide following fields as protected
+        /// buffer that holds memory. It is shared_array as options may
+        /// share pointer to this buffer
         boost::shared_array<char> data_;
 
         // length of the data
-        int data_len_;
+        unsigned int data_len_;
 
-        // local address (destination if receiving packet, source if sending packet)
+        // local address (dst if receiving packet, src if sending packet)
         isc::asiolink::IOAddress local_addr_;
 
-        // remote address (source if receiving packet, destination if sending packet)
+        // remote address (src if receiving packet, dst if sending packet)
         isc::asiolink::IOAddress remote_addr_;
 
         // name of the network interface the packet was received/to be sent over
@@ -46,7 +75,8 @@ namespace isc {
 
         // interface index (each network interface has assigned unique ifindex
         // it is functional equvalent of name, but sometimes more useful, e.g.
-        // when using crazy systems that allow spaces in interface names (Windows)
+        // when using crazy systems that allow spaces in interface names
+        // e.g. windows
         int ifindex_;
 
         // local TDP or UDP port
@@ -56,6 +86,18 @@ namespace isc {
         int remote_port_;
 
         // XXX: add *a lot* here
+        isc::dhcp::Option::Option6Lst options_;
+
+    protected:
+        bool packTCP();
+        bool packUDP();
+        bool unpackTCP();
+        bool unpackUDP();
+
+        DHCPv6Proto_ proto_; // UDP (most) or TCP (bulk leasequery or failover)
+        int msg_type_; // DHCPv6 message type
+        int transid_;  // DHCPv6 transaction-id
+
     };
 }
 

+ 1 - 0
src/lib/dhcp/tests/Makefile.am

@@ -17,6 +17,7 @@ if HAVE_GTEST
 TESTS += libdhcp_unittests
 libdhcp_unittests_SOURCES  = run_unittests.cc
 libdhcp_unittests_SOURCES += ../libdhcp.h ../libdhcp.cc libdhcp_unittest.cc
+libdhcp_unittests_SOURCES += ../option.h ../option.cc option_unittest.cc
 libdhcp_unittests_SOURCES += ../pkt6.h ../pkt6.cc pkt6_unittest.cc
 
 libdhcp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)

+ 0 - 1
src/lib/dhcp/tests/libdhcp_unittest.cc

@@ -35,7 +35,6 @@ public:
 TEST_F(LibDhcpTest, basic) {
     // dummy test
 
-    // an attempt to bind this socket will fail.
     EXPECT_EQ(LibDHCP::version(), "0");
 }
 

+ 81 - 0
src/lib/dhcp/tests/option_unittest.cc

@@ -0,0 +1,81 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include "dhcp/dhcp6.h"
+#include "dhcp/option.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+class OptionTest : public ::testing::Test {
+public:
+    OptionTest() {
+    }
+};
+
+TEST_F(OptionTest, basic) {
+
+    boost::shared_array<char> simple_buf(new char[128]);
+    for (int i=0; i<128; i++)
+        simple_buf[i] = 0;
+    simple_buf[0]=0xa1;
+    simple_buf[1]=0xa2;
+    simple_buf[2]=0xa3;
+    simple_buf[3]=0xa4;
+
+    // create an option (unpack content)
+    Option* opt = new Option(Option::V6, 
+                             D6O_CLIENTID,
+                             simple_buf,
+                             0, 
+                             4);
+    
+    // pack this option again in the same buffer, but in
+    // different place
+    char* offset18 = opt->pack(&simple_buf[10], 8);
+
+    // 4 bytes header + 4 bytes content
+    EXPECT_EQ(8, opt->len());
+    EXPECT_EQ(D6O_CLIENTID, opt->getType());
+
+    EXPECT_EQ(offset18, &simple_buf[0]+18);
+
+    // check if pack worked properly:
+    // if option type is correct
+    EXPECT_EQ(D6O_CLIENTID, simple_buf[10]*256 + simple_buf[11]);
+
+    // if option length is correct
+    EXPECT_EQ(4, simple_buf[12]*256 + simple_buf[13]);
+    
+    // if option content is correct
+    EXPECT_EQ(0, memcmp(&simple_buf[0], &simple_buf[14],4));
+
+    for (int i=0; i<20; i++) {
+        std::cout << i << ":" << (unsigned short) (unsigned char)simple_buf[i] << " ";
+    }
+    std::cout << std::endl;
+
+    delete opt;
+}
+
+}

+ 69 - 2
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -18,12 +18,14 @@
 
 #include <arpa/inet.h>
 #include <gtest/gtest.h>
-
+#include "io_address.h"
 
 #include "dhcp/pkt6.h"
+#include "dhcp/dhcp6.h"
 
 using namespace std;
 using namespace isc;
+using namespace isc::asiolink;
 
 namespace {
 // empty class for now, but may be extended once Addr6 becomes bigger
@@ -35,10 +37,75 @@ public:
 
 TEST_F(Pkt6Test, constructor) {
     Pkt6 * pkt1 = new Pkt6(17);
-    
+
     ASSERT_EQ(pkt1->data_len_, 17);
 
     delete pkt1;
 }
 
+// captured actual SOLICIT packet: transid=0x3d79fb
+// options: client-id, in_na, dns-server, elapsed-time, option-request
+// this code is autogenerated (see src/bin/dhcp6/tests/iface_mgr_unittest.c)
+Pkt6 *capture1() {
+    Pkt6* pkt;
+    pkt = new Pkt6(98);
+    pkt->remote_port_ = 546;
+    pkt->remote_addr_ = IOAddress("fe80::21e:8cff:fe9b:7349");
+    pkt->local_port_ = 0;
+    pkt->local_addr_ = IOAddress("ff02::1:2");
+    pkt->ifindex_ = 2;
+    pkt->iface_ = "eth0";
+    pkt->data_[0]=1;
+    pkt->data_[1]=192;     pkt->data_[2]=129;     pkt->data_[3]=6;     pkt->data_[4]=0;
+    pkt->data_[5]=1;     pkt->data_[6]=0;     pkt->data_[7]=14;     pkt->data_[8]=0;
+    pkt->data_[9]=1;     pkt->data_[10]=0;     pkt->data_[11]=1;     pkt->data_[12]=21;
+    pkt->data_[13]=158;     pkt->data_[14]=60;     pkt->data_[15]=22;     pkt->data_[16]=0;
+    pkt->data_[17]=30;     pkt->data_[18]=140;     pkt->data_[19]=155;     pkt->data_[20]=115;
+    pkt->data_[21]=73;     pkt->data_[22]=0;     pkt->data_[23]=3;     pkt->data_[24]=0;
+    pkt->data_[25]=40;     pkt->data_[26]=0;     pkt->data_[27]=0;     pkt->data_[28]=0;
+    pkt->data_[29]=1;     pkt->data_[30]=255;     pkt->data_[31]=255;     pkt->data_[32]=255;
+    pkt->data_[33]=255;     pkt->data_[34]=255;     pkt->data_[35]=255;     pkt->data_[36]=255;
+    pkt->data_[37]=255;     pkt->data_[38]=0;     pkt->data_[39]=5;     pkt->data_[40]=0;
+    pkt->data_[41]=24;     pkt->data_[42]=32;     pkt->data_[43]=1;     pkt->data_[44]=13;
+    pkt->data_[45]=184;     pkt->data_[46]=0;     pkt->data_[47]=1;     pkt->data_[48]=0;
+    pkt->data_[49]=0;     pkt->data_[50]=0;     pkt->data_[51]=0;     pkt->data_[52]=0;
+    pkt->data_[53]=0;     pkt->data_[54]=0;     pkt->data_[55]=0;     pkt->data_[56]=18;
+    pkt->data_[57]=52;     pkt->data_[58]=255;     pkt->data_[59]=255;     pkt->data_[60]=255;
+    pkt->data_[61]=255;     pkt->data_[62]=255;     pkt->data_[63]=255;     pkt->data_[64]=255;
+    pkt->data_[65]=255;     pkt->data_[66]=0;     pkt->data_[67]=23;     pkt->data_[68]=0;
+    pkt->data_[69]=16;     pkt->data_[70]=32;     pkt->data_[71]=1;     pkt->data_[72]=13;
+    pkt->data_[73]=184;     pkt->data_[74]=0;     pkt->data_[75]=1;     pkt->data_[76]=0;
+    pkt->data_[77]=0;     pkt->data_[78]=0;     pkt->data_[79]=0;     pkt->data_[80]=0;
+    pkt->data_[81]=0;     pkt->data_[82]=0;     pkt->data_[83]=0;     pkt->data_[84]=221;
+    pkt->data_[85]=221;     pkt->data_[86]=0;     pkt->data_[87]=8;     pkt->data_[88]=0;
+    pkt->data_[89]=2;     pkt->data_[90]=0;     pkt->data_[91]=100;     pkt->data_[92]=0;
+    pkt->data_[93]=6;     pkt->data_[94]=0;     pkt->data_[95]=2;     pkt->data_[96]=0;
+    pkt->data_[97]=23;
+    return (pkt);
+}
+
+TEST_F(Pkt6Test, parse_solicit1) {
+    Pkt6 * sol = capture1();
+
+    ASSERT_EQ(true, sol->unpack());
+
+    boost::shared_ptr<isc::dhcp::Option> null;
+
+    // check that all present options are returned
+    EXPECT_NE(null, sol->getOption(D6O_CLIENTID)); // client-id is present
+    EXPECT_NE(null, sol->getOption(D6O_IA_NA));    // IA_NA is present
+    EXPECT_NE(null, sol->getOption(D6O_ELAPSED_TIME));  // elapsed is present
+    EXPECT_NE(null, sol->getOption(D6O_NAME_SERVERS));
+    EXPECT_NE(null, sol->getOption(D6O_ORO));
+
+    // let's check that non-present options are not returned
+    EXPECT_EQ(null, sol->getOption(D6O_SERVERID)); // server-id is missing
+    EXPECT_EQ(null, sol->getOption(D6O_IA_TA));
+    EXPECT_EQ(null, sol->getOption(D6O_IAADDR));
+
+    std::cout << sol->toText();
+
+    delete sol;
+}
+
 }

+ 25 - 0
src/lib/dhcp/tests/run_unittests.cc

@@ -0,0 +1,25 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+    return (isc::util::unittests::run_all());
+}