Commit b47533e9 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

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

parent f4c7155d
......@@ -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
......
......@@ -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
......
......@@ -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,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_);
};
}
......
// 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() {
}
// 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
......@@ -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) {