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

Merge branch 'trac1239' into trac1230

Conflicts:
	ChangeLog
	src/bin/dhcp4/dhcp4_srv.cc
	src/bin/dhcp6/dhcp6_srv.cc
	src/lib/dhcp/iface_mgr.cc
	src/lib/dhcp/iface_mgr.h
parents e945e578 3e3ba930
......@@ -4,6 +4,14 @@
addresses, flags and configured IPv4 and IPv6 addresses.
(Trac #1237, git TBD)
3XX. [func] tomek
libdhcp++: Transmission and reception of DHCPv4 packets is now
implemented. Low-level hacks are not implemented for transmission
to hosts that don't have IPv4 address yet, so currently the code
is usable for communication with relays only, not hosts on the
same link.
(Trac #1239, #1240, git TBD)
349. [bug] dvv
resolver: If an upstream server responds with FORMERR to an EDNS query,
try querying it without EDNS.
......
......@@ -23,6 +23,8 @@ using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
// #define ECHO_SERVER
Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
cout << "Initialization: opening sockets on port " << port << endl;
......@@ -33,10 +35,8 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
/// @todo: instantiate LeaseMgr here once it is imlpemented.
IfaceMgr::instance().printIfaces();
#if 0
// uncomment this once #1238, #992 and #1239 are merged
IfaceMgr::instance().openSockets4(port);
#endif
setServerID();
......@@ -45,6 +45,7 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
Dhcpv4Srv::~Dhcpv4Srv() {
cout << "DHCPv4 server shutdown." << endl;
IfaceMgr::instance().closeSockets();
}
bool
......@@ -53,16 +54,23 @@ Dhcpv4Srv::run() {
boost::shared_ptr<Pkt4> query; // client's message
boost::shared_ptr<Pkt4> rsp; // server's response
#if 0
// uncomment this once ticket 1239 is merged.
query = IfaceMgr::instance().receive4();
#if defined(ECHO_SERVER)
query->repack();
IfaceMgr::instance().send(query);
continue;
#endif
if (query) {
if (!query->unpack()) {
cout << "Failed to parse incoming packet" << endl;
try {
query->unpack();
} catch (const std::exception& e) {
/// TODO: Printout reasons of failed parsing
cout << "Failed to parse incoming packet " << endl;
continue;
}
switch (query->getType()) {
case DHCPDISCOVER:
rsp = processDiscover(query);
......@@ -84,8 +92,7 @@ Dhcpv4Srv::run() {
<< query->getType() << endl;
}
cout << "Received " << query->len() << " bytes packet type="
<< query->getType() << endl;
cout << "Received message type " << int(query->getType()) << endl;
// TODO: print out received packets only if verbose (or debug)
// mode is enabled
......@@ -99,15 +106,16 @@ Dhcpv4Srv::run() {
rsp->setIface(query->getIface());
rsp->setIndex(query->getIndex());
cout << "Replying with:" << rsp->getType() << endl;
cout << "Replying with message type "
<< static_cast<int>(rsp->getType()) << ":" << endl;
cout << rsp->toText();
cout << "----" << endl;
if (rsp->pack()) {
cout << "Packet assembled correctly." << endl;
}
#if 0
#if 1
// uncomment this once ticket 1240 is merged.
IfaceMgr::instance().send4(rsp);
IfaceMgr::instance().send(rsp);
#endif
}
}
......
......@@ -41,6 +41,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
endif
noinst_PROGRAMS = $(TESTS)
......@@ -14,6 +14,7 @@
#include <config.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <arpa/inet.h>
......@@ -28,11 +29,12 @@ using namespace isc;
using namespace isc::dhcp;
namespace {
const char* const INTERFACE_FILE = "interfaces.txt";
class NakedDhcpv4Srv: public Dhcpv4Srv {
// "naked" DHCPv4 server, exposes internal fields
public:
NakedDhcpv4Srv() { }
NakedDhcpv4Srv():Dhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
boost::shared_ptr<Pkt4> processDiscover(boost::shared_ptr<Pkt4>& discover) {
return Dhcpv4Srv::processDiscover(discover);
......@@ -54,9 +56,18 @@ public:
class Dhcpv4SrvTest : public ::testing::Test {
public:
Dhcpv4SrvTest() {
unlink(INTERFACE_FILE);
fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
if (if_nametoindex("lo") > 0) {
fakeifaces << "lo 127.0.0.1";
} else if (if_nametoindex("lo0") > 0) {
fakeifaces << "lo0 127.0.0.1";
}
fakeifaces.close();
}
~Dhcpv4SrvTest() {
unlink(INTERFACE_FILE);
};
};
......@@ -66,7 +77,7 @@ TEST_F(Dhcpv4SrvTest, basic) {
Dhcpv4Srv* srv = NULL;
ASSERT_NO_THROW({
srv = new Dhcpv4Srv();
srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
});
delete srv;
......
......@@ -44,11 +44,7 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
}
// Now try to open IPv6 sockets on detected interfaces.
cout << "Opening sockets on port " << port << endl;
#if 0
// uncomment this once #1238, #992 and #1239 are merged
IfaceMgr::instance().openSockets6(port);
#endif
/// @todo: instantiate LeaseMgr here once it is imlpemented.
......
......@@ -14,6 +14,7 @@
#include <config.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <arpa/inet.h>
......@@ -29,7 +30,8 @@ using namespace isc::dhcp;
// namespace has to be named, because friends are defined in Dhcpv6Srv class
// Maybe it should be isc::test?
namespace test {
namespace {
const char* const INTERFACE_FILE = "interfaces.txt";
class NakedDhcpv6Srv: public Dhcpv6Srv {
// "naked" Interface Manager, exposes internal fields
......@@ -49,7 +51,18 @@ public:
class Dhcpv6SrvTest : public ::testing::Test {
public:
Dhcpv6SrvTest() {
unlink(INTERFACE_FILE);
fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
if (if_nametoindex("lo") > 0) {
fakeifaces << "lo ::1";
} else if (if_nametoindex("lo0") > 0) {
fakeifaces << "lo0 ::1";
}
fakeifaces.close();
}
~Dhcpv6SrvTest() {
unlink(INTERFACE_FILE);
};
};
TEST_F(Dhcpv6SrvTest, basic) {
......@@ -116,7 +129,7 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
boost::shared_ptr<Option> tmp = reply->getOption(D6O_IA_NA);
ASSERT_TRUE( tmp );
Option6IA* reply_ia = dynamic_cast<Option6IA*> ( tmp.get() );
Option6IA* reply_ia = dynamic_cast<Option6IA*>(tmp.get());
EXPECT_EQ( 234, reply_ia->getIAID() );
// check that there's an address included
......
......@@ -157,7 +157,7 @@ static const uint16_t DHCP4_SERVER_PORT = 67;
/// Magic cookie validating dhcp options field (and bootp vendor
/// extensions field).
///static const char* DHCP_OPTIONS_COOKIE = "\143\202\123\143";
static const uint32_t DHCP_OPTIONS_COOKIE = 0x63825363;
// TODO: Following are leftovers from dhcp.h import from ISC DHCP
// They will be converted to C++-style defines once they will start
......
This diff is collapsed.
......@@ -20,6 +20,8 @@
#include <boost/scoped_array.hpp>
#include <boost/noncopyable.hpp>
#include <asiolink/io_address.h>
#include <dhcp/dhcp6.h>
#include <dhcp/dhcp4.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
......@@ -325,13 +327,10 @@ public:
int openSocket(const std::string& ifname,
const isc::asiolink::IOAddress& addr, int port);
/// Opens IPv4 sockets on detected interfaces.
///
/// Will throw exception if socket creation fails.
/// @param port specifies port number (usually DHCP6_SERVER_PORT)
///
/// @param port specifies port number (usually DHCP4_SERVER_PORT)
void openSockets4(uint16_t port);
/// @return true if any sockets were open
bool openSockets6(uint16_t port = DHCP6_SERVER_PORT);
/// @brief Closes all open sockets.
/// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.
......@@ -342,6 +341,14 @@ public:
/// @return number of detected interfaces
uint16_t countIfaces() { return ifaces_.size(); }
/// Opens IPv4 sockets on detected interfaces.
/// Will throw exception if socket creation fails.
///
/// @param port specifies port number (usually DHCP4_SERVER_PORT)
///
/// @return true if any sockets were open
bool openSockets4(uint16_t port = DHCP4_SERVER_PORT);
// don't use private, we need derived classes in tests
protected:
......@@ -351,7 +358,7 @@ protected:
/// anyone to create instances of IfaceMgr. Use instance() method instead.
IfaceMgr();
~IfaceMgr();
virtual ~IfaceMgr();
/// @brief Opens IPv4 socket.
///
......@@ -429,10 +436,6 @@ protected:
boost::scoped_array<char> control_buf_;
private:
/// Opens IPv6 sockets on detected interfaces.
///
/// @param port specifies port on which sockets will be open
void openSockets6(uint16_t port);
/// creates a single instance of this class (a singleton implementation)
static void
......
......@@ -93,13 +93,13 @@ LibDHCP::unpackOptions4(const std::vector<uint8_t>& buf,
// 2 - header of DHCPv4 option
while (offset + 1 <= buf.size()) {
uint8_t opt_type = buf[offset++];
if (offset + 1 == buf.size()) {
if (opt_type == DHO_END)
return; // just return. Don't need to add DHO_END option
else {
isc_throw(OutOfRange, "Attempt to parse truncated option "
<< opt_type);
}
if (opt_type == DHO_END)
return; // just return. Don't need to add DHO_END option
if (offset + 1 >= buf.size()) {
isc_throw(OutOfRange, "Attempt to parse truncated option "
<< opt_type);
}
uint8_t opt_len = buf[offset++];
......
......@@ -301,6 +301,31 @@ Option::addOption(boost::shared_ptr<Option> opt) {
options_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(), opt));
}
uint8_t Option::getUint8() {
if (data_.size() < sizeof(uint8_t) ) {
isc_throw(OutOfRange, "Attempt to read uint8 from option " << type_
<< " that has size " << data_.size());
}
return (data_[0]);
}
uint16_t Option::getUint16() {
if (data_.size() < sizeof(uint16_t) ) {
isc_throw(OutOfRange, "Attempt to read uint16 from option " << type_
<< " that has size " << data_.size());
}
return ( readUint16(&data_[0]) );
}
uint32_t Option::getUint32() {
if (data_.size() < sizeof(uint32_t) ) {
isc_throw(OutOfRange, "Attempt to read uint32 from option " << type_
<< " that has size " << data_.size());
}
return ( readUint32(&data_[0]) );
}
Option::~Option() {
}
......@@ -236,9 +236,29 @@ public:
bool
delOption(unsigned short type);
/// @brief Returns content of first byte.
///
/// @exception OutOfRange Thrown if the option has a length of 0.
///
/// @return value of the first byte
uint8_t getUint8();
/// @brief Returns content of first word.
///
/// @exception OutOfRange Thrown if the option has a length less than 2.
///
/// @return uint16_t value stored on first two bytes
uint16_t getUint16();
/// @brief Returns content of first double word.
///
/// @exception OutOfRange Thrown if the option has a length less than 4.
///
/// @return uint32_t value stored on first four bytes
uint32_t getUint32();
/// just to force that every option has virtual dtor
virtual
~Option();
virtual ~Option();
protected:
/// Builds raw (over-wire) buffer of this option, including all
......
......@@ -75,8 +75,8 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
{
if (len < DHCPV4_PKT_HDR_LEN) {
isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len
<< " received, at least " << DHCPV4_PKT_HDR_LEN
<< "is expected");
<< ") received, at least " << DHCPV4_PKT_HDR_LEN
<< " is expected.");
}
data_.resize(len);
......@@ -114,6 +114,9 @@ Pkt4::pack() {
bufferOut_.writeData(sname_, MAX_SNAME_LEN);
bufferOut_.writeData(file_, MAX_FILE_LEN);
// write DHCP magic cookie
bufferOut_.writeUint32(DHCP_OPTIONS_COOKIE);
LibDHCP::packOptions(bufferOut_, options_);
// add END option that indicates end of options
......@@ -128,7 +131,7 @@ Pkt4::unpack() {
// input buffer (used during message reception)
isc::util::InputBuffer bufferIn(&data_[0], data_.size());
if (bufferIn.getLength()<DHCPV4_PKT_HDR_LEN) {
if (bufferIn.getLength() < DHCPV4_PKT_HDR_LEN) {
isc_throw(OutOfRange, "Received truncated DHCPv4 packet (len="
<< bufferIn.getLength() << " received, at least "
<< DHCPV4_PKT_HDR_LEN << "is expected");
......@@ -149,8 +152,26 @@ Pkt4::unpack() {
bufferIn.readData(sname_, MAX_SNAME_LEN);
bufferIn.readData(file_, MAX_FILE_LEN);
if (bufferIn.getLength() == bufferIn.getPosition()) {
// this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
// particular, it does not have magic cookie, a 4 byte sequence that
// differentiates between DHCP and BOOTP packets.
return (true);
}
if (bufferIn.getLength() - bufferIn.getPosition() < 4) {
// there is not enough data to hold magic DHCP cookie
isc_throw(Unexpected, "Truncated or no DHCP packet.");
}
uint32_t magic = bufferIn.readUint32();
if (magic != DHCP_OPTIONS_COOKIE) {
isc_throw(Unexpected, "Invalid or missing DHCP magic cookie");
}
size_t opts_len = bufferIn.getLength() - bufferIn.getPosition();
vector<uint8_t> optsBuffer;
// fist use of readVector
bufferIn.readVector(optsBuffer, opts_len);
LibDHCP::unpackOptions4(optsBuffer, options_);
......@@ -158,15 +179,40 @@ Pkt4::unpack() {
return (true);
}
void Pkt4::check() {
boost::shared_ptr<Option> typeOpt = getOption(DHO_DHCP_MESSAGE_TYPE);
if (typeOpt) {
uint8_t msg_type = typeOpt->getUint8();
if (msg_type>DHCPLEASEACTIVE) {
isc_throw(BadValue, "Invalid DHCP message type received:" << msg_type);
}
msg_type_ = msg_type;
} else {
isc_throw(Unexpected, "Missing DHCP Message Type option");
}
}
void Pkt4::repack() {
cout << "Convering RX packet to TX packet: " << data_.size() << " bytes." << endl;
bufferOut_.writeData(&data_[0], data_.size());
}
std::string
Pkt4::toText() {
stringstream tmp;
tmp << "localAddr=[" << local_addr_.toText() << "]:" << local_port_
<< " remoteAddr=[" << remote_addr_.toText()
<< "]:" << remote_port_ << endl;
tmp << "msgtype=" << msg_type_
<< ", transid=0x" << hex << transid_ << dec
<< endl;
tmp << "localAddr=" << local_addr_.toText() << ":" << local_port_
<< " remoteAddr=" << remote_addr_.toText()
<< ":" << remote_port_ << ", msgtype=" << int(msg_type_)
<< ", transid=0x" << hex << transid_ << dec << endl;
for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
opt != options_.end();
++opt) {
tmp << " " << opt->second->toText() << std::endl;
}
return tmp.str();
}
......@@ -256,7 +302,6 @@ Pkt4::getOption(uint8_t type) {
return boost::shared_ptr<isc::dhcp::Option>(); // NULL
}
} // end of namespace isc::dhcp
} // end of namespace isc
......@@ -78,6 +78,25 @@ public:
bool
unpack();
/// @brief performs sanity check on a packet.
///
/// This is usually performed after unpack(). It checks if packet is sane:
/// required options are present, fields have sane content etc.
/// For example verifies that DHCP_MESSAGE_TYPE is present and have
/// reasonable value. This method is expected to grow significantly.
/// It makes sense to separate unpack() and check() for testing purposes.
///
/// Method will throw exception if anomaly is found.
void check();
/// @brief Copies content of input buffer to output buffer.
///
/// This is mostly a diagnostic function. It is being used for sending
/// received packet. Received packet is stored in bufferIn_, but
/// transmitted data is stored in bufferOut_. If we want to send packet
/// that we just received, a copy between those two buffers is necessary.
void repack();
/// @brief Returns text representation of the packet.
///
/// This function is useful mainly for debugging.
......
......@@ -56,6 +56,10 @@ public:
fakeifaces << LOOPBACK << " ::1";
fakeifaces.close();
}
~IfaceMgrTest() {
unlink(INTERFACE_FILE);
}
};
// We need some known interface to work reliably. Loopback interface
......@@ -148,6 +152,8 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
TEST_F(IfaceMgrTest, basic) {
// checks that IfaceManager can be instantiated
createLoInterfacesTxt();
IfaceMgr & ifacemgr = IfaceMgr::instance();
ASSERT_TRUE(&ifacemgr != 0);
}
......@@ -160,13 +166,14 @@ TEST_F(IfaceMgrTest, ifaceClass) {
EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
delete iface;
}
// TODO: Implement getPlainMac() test as soon as interface detection
// is implemented.
TEST_F(IfaceMgrTest, getIface) {
createLoInterfacesTxt();
cout << "Interface checks. Please ignore socket binding errors." << endl;
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
......@@ -210,6 +217,7 @@ TEST_F(IfaceMgrTest, getIface) {
EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi0") );
delete ifacemgr;
}
#if !defined(OS_LINUX)
......@@ -365,6 +373,90 @@ TEST_F(IfaceMgrTest, sendReceive6) {
unlink(INTERFACE_FILE);
}
TEST_F(IfaceMgrTest, sendReceive4) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
// let's assume that every supported OS have lo interface
IOAddress loAddr("127.0.0.1");
int socket1 = 0, socket2 = 0;
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000 + 1);
);
boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
sendPkt->setLocalAddr(IOAddress("127.0.0.1"));
sendPkt->setLocalPort(DHCP4_SERVER_PORT + 10000 + 1);
sendPkt->setRemotePort(DHCP4_SERVER_PORT + 10000);
sendPkt->setRemoteAddr(IOAddress("127.0.0.1"));
sendPkt->setIndex(1);
sendPkt->setIface(string(LOOPBACK));
sendPkt->setHops(6);
sendPkt->setSecs(42);
sendPkt->setCiaddr(IOAddress("192.0.2.1"));
sendPkt->setSiaddr(IOAddress("192.0.2.2"));
sendPkt->setYiaddr(IOAddress("192.0.2.3"));
sendPkt->setGiaddr(IOAddress("192.0.2.4"));
uint8_t sname[] = "That's just a string that will act as SNAME";
sendPkt->setSname(sname, strlen((const char*)sname));
uint8_t file[] = "/another/string/that/acts/as/a/file_name.txt";
sendPkt->setFile(file, strlen((const char*)file));
ASSERT_NO_THROW(
sendPkt->pack();
);
boost::shared_ptr<Pkt4> rcvPkt;
EXPECT_EQ(true, ifacemgr->send(sendPkt));
rcvPkt = ifacemgr->receive4();
ASSERT_TRUE( rcvPkt ); // received our own packet
ASSERT_NO_THROW(
rcvPkt->unpack();
);
// let's check that we received what was sent
EXPECT_EQ(sendPkt->len(), rcvPkt->len());
EXPECT_EQ("127.0.0.1", rcvPkt->getRemoteAddr().toText());
EXPECT_EQ(sendPkt->getRemotePort(), rcvPkt->getLocalPort());
// now let's check content
EXPECT_EQ(sendPkt->getHops(), rcvPkt->getHops());
EXPECT_EQ(sendPkt->getOp(), rcvPkt->getOp());
EXPECT_EQ(sendPkt->getSecs(), rcvPkt->getSecs());
EXPECT_EQ(sendPkt->getFlags(), rcvPkt->getFlags());
EXPECT_EQ(sendPkt->getCiaddr(), rcvPkt->getCiaddr());
EXPECT_EQ(sendPkt->getSiaddr(), rcvPkt->getSiaddr());