Commit 33ffc9a7 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac991'

parents ff74508f 69bdbf32
......@@ -57,7 +57,7 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
......@@ -67,7 +67,7 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
if (port) {
// open sockets only if port is non-zero. Port 0 is used
// for non-socket related testing.
IfaceMgr::instance().openSockets4(port);
IfaceMgr::instance().openSockets4(port, use_bcast);
}
string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
......@@ -287,9 +287,9 @@ Dhcpv4Srv::generateServerID() {
continue;
}
const IfaceMgr::AddressCollection addrs = iface->getAddresses();
const Iface::AddressCollection addrs = iface->getAddresses();
for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
if (addr->getFamily() != AF_INET) {
continue;
......@@ -317,7 +317,7 @@ Dhcpv4Srv::writeServerID(const std::string& file_name) {
return (true);
}
string
string
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
if (!srvid) {
isc_throw(BadValue, "NULL pointer passed to srvidToString()");
......@@ -517,6 +517,28 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setYiaddr(lease->addr_);
// If remote address is not set, we are dealing with a directly
// connected client requesting new lease. We can send response to
// the address assigned in the lease, but first we have to make sure
// that IfaceMgr supports responding directly to the client when
// client doesn't have address assigned to its interface yet.
if (answer->getRemoteAddr().toText() == "0.0.0.0") {
if (IfaceMgr::instance().isDirectResponseSupported()) {
answer->setRemoteAddr(lease->addr_);
} else {
// Since IfaceMgr does not support direct responses to
// clients not having IP addresses, we have to send response
// to broadcast. We don't check whether the use_bcast flag
// was set in the constructor, because this flag is only used
// by unit tests to prevent opening broadcast sockets, as
// it requires root privileges. If this function is invoked by
// unit tests, we expect that it sets broadcast address if
// direct response is not supported, so as a test can verify
// function's behavior, regardless of the use_bcast flag's value.
answer->setRemoteAddr(IOAddress("255.255.255.255"));
}
}
// IP Address Lease time (type 51)
opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
opt->setUint32(lease->valid_lft_);
......
......@@ -66,8 +66,10 @@ class Dhcpv4Srv : public boost::noncopyable {
/// @param port specifies port number to listen on
/// @param dbconfig Lease manager configuration string. The default
/// of the "memfile" manager is used for testing.
/// @param use_bcast configure sockets to support broadcast messages.
Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
const char* dbconfig = "type=memfile");
const char* dbconfig = "type=memfile",
const bool use_bcast = true);
/// @brief Destructor. Used during DHCPv4 service shutdown.
~Dhcpv4Srv();
......
......@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
......@@ -44,7 +45,16 @@ namespace {
class NakedDhcpv4Srv: public Dhcpv4Srv {
// "Naked" DHCPv4 server, exposes internal fields
public:
NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
/// @brief Constructor.
///
/// It disables configuration of broadcast options on
/// sockets that are opened by the Dhcpv4Srv constructor.
/// Setting broadcast options requires root privileges
/// which is not the case when running unit tests.
NakedDhcpv4Srv(uint16_t port = 0)
: Dhcpv4Srv(port, "type=memfile", false) {
}
using Dhcpv4Srv::processDiscover;
using Dhcpv4Srv::processRequest;
......@@ -170,6 +180,8 @@ public:
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
EXPECT_TRUE(a->getOption(DHO_DHCP_LEASE_TIME));
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
// Check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
......@@ -345,6 +357,120 @@ public:
EXPECT_TRUE(expected_clientid->getData() == opt->getData());
}
/// @brief Tests if Discover or Request message is processed correctly
///
/// @param msg_type DHCPDISCOVER or DHCPREQUEST
/// @param client_addr client address
/// @param relay_addr relay address
void testDiscoverRequest(const uint8_t msg_type,
const IOAddress& client_addr,
const IOAddress& relay_addr) {
NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = i*10;
}
boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234));
boost::shared_ptr<Pkt4> rsp;
req->setIface("eth0");
req->setIndex(17);
req->setHWAddr(1, 6, mac);
req->setRemoteAddr(IOAddress(client_addr));
req->setGiaddr(relay_addr);
// We are going to test that certain options are returned
// in the response message when requested using 'Parameter
// Request List' option. Let's configure those options that
// are returned when requested.
configureRequestedOptions();
if (msg_type == DHCPDISCOVER) {
ASSERT_NO_THROW(
rsp = srv->processDiscover(req);
);
// Should return OFFER
ASSERT_TRUE(rsp);
EXPECT_EQ(DHCPOFFER, rsp->getType());
} else {
ASSERT_NO_THROW(
rsp = srv->processRequest(req);
);
// Should return ACK
ASSERT_TRUE(rsp);
EXPECT_EQ(DHCPACK, rsp->getType());
}
if (relay_addr.toText() != "0.0.0.0") {
// This is relayed message. It should be sent brsp to relay address.
EXPECT_EQ(req->getGiaddr().toText(),
rsp->getRemoteAddr().toText());
} else if (client_addr.toText() != "0.0.0.0") {
// This is a message from a client having an IP address.
EXPECT_EQ(req->getRemoteAddr().toText(),
rsp->getRemoteAddr().toText());
} else {
// This is a message from a client having no IP address yet.
// If IfaceMgr supports direct traffic the response should
// be sent to the new address assigned to the client.
if (IfaceMgr::instance().isDirectResponseSupported()) {
EXPECT_EQ(rsp->getYiaddr(),
rsp->getRemoteAddr().toText());
// If direct response to the client having no IP address is
// not supported, response should go to broadcast.
} else {
EXPECT_EQ("255.255.255.255", rsp->getRemoteAddr().toText());
}
}
messageCheck(req, rsp);
// We did not request any options so these should not be present
// in the RSP.
EXPECT_FALSE(rsp->getOption(DHO_LOG_SERVERS));
EXPECT_FALSE(rsp->getOption(DHO_COOKIE_SERVERS));
EXPECT_FALSE(rsp->getOption(DHO_LPR_SERVERS));
// Repeat the test but request some options.
// Add 'Parameter Request List' option.
addPrlOption(req);
if (msg_type == DHCPDISCOVER) {
ASSERT_NO_THROW(
rsp = srv->processDiscover(req);
);
// Should return non-NULL packet.
ASSERT_TRUE(rsp);
EXPECT_EQ(DHCPOFFER, rsp->getType());
} else {
ASSERT_NO_THROW(
rsp = srv->processRequest(req);
);
// Should return non-NULL packet.
ASSERT_TRUE(rsp);
EXPECT_EQ(DHCPACK, rsp->getType());
}
// Check that the requested options are returned.
optionsCheck(rsp);
}
~Dhcpv4SrvTest() {
CfgMgr::instance().deleteSubnets4();
......@@ -389,7 +515,7 @@ TEST_F(Dhcpv4SrvTest, basic) {
delete naked_srv;
}
// Verifies that received DISCOVER can be processed correctly,
// Verifies that DISCOVER received via relay can be processed correctly,
// that the OFFER message generated in response is valid and
// contains necessary options.
//
......@@ -397,203 +523,56 @@ TEST_F(Dhcpv4SrvTest, basic) {
// are other tests that verify correctness of the allocation
// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
// and DiscoverInvalidHint.
TEST_F(Dhcpv4SrvTest, processDiscover) {
NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = 255 - i;
}
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, 1234));
boost::shared_ptr<Pkt4> offer;
pkt->setIface("eth0");
pkt->setIndex(17);
pkt->setHWAddr(1, 6, mac);
pkt->setRemoteAddr(IOAddress("192.0.2.56"));
pkt->setGiaddr(IOAddress("192.0.2.67"));
// Let's make it a relayed message
pkt->setHops(3);
pkt->setRemotePort(DHCP4_SERVER_PORT);
// We are going to test that certain options are returned
// (or not returned) in the OFFER message when requested
// using 'Parameter Request List' option. Let's configure
// those options that are returned when requested.
configureRequestedOptions();
// Should not throw
EXPECT_NO_THROW(
offer = srv->processDiscover(pkt);
);
// Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
// This is relayed message. It should be sent back to relay address.
EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
messageCheck(pkt, offer);
// There are some options that are always present in the
// message, even if not requested.
EXPECT_TRUE(offer->getOption(DHO_DOMAIN_NAME));
EXPECT_TRUE(offer->getOption(DHO_DOMAIN_NAME_SERVERS));
// We did not request any options so they should not be present
// in the OFFER.
EXPECT_FALSE(offer->getOption(DHO_LOG_SERVERS));
EXPECT_FALSE(offer->getOption(DHO_COOKIE_SERVERS));
EXPECT_FALSE(offer->getOption(DHO_LPR_SERVERS));
// Add 'Parameter Request List' option.
addPrlOption(pkt);
// Now repeat the test but request some options.
EXPECT_NO_THROW(
offer = srv->processDiscover(pkt);
);
// Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
// This is relayed message. It should be sent back to relay address.
EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
messageCheck(pkt, offer);
// Check that the requested options are returned.
optionsCheck(offer);
// Now repeat the test for directly sent message
pkt->setHops(0);
pkt->setGiaddr(IOAddress("0.0.0.0"));
pkt->setRemotePort(DHCP4_CLIENT_PORT);
EXPECT_NO_THROW(
offer = srv->processDiscover(pkt);
);
// Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
// This is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
messageCheck(pkt, offer);
TEST_F(Dhcpv4SrvTest, processDiscoverRelay) {
testDiscoverRequest(DHCPDISCOVER,
IOAddress("192.0.2.56"),
IOAddress("192.0.2.67"));
}
// Check that the requested options are returned.
optionsCheck(offer);
// Verifies that the non-relayed DISCOVER is processed correctly when
// client source address is specified.
TEST_F(Dhcpv4SrvTest, processDiscoverNoRelay) {
testDiscoverRequest(DHCPDISCOVER,
IOAddress("0.0.0.0"),
IOAddress("192.0.2.67"));
}
delete srv;
// Verified that the non-relayed DISCOVER is processed correctly when
// client source address is not specified.
TEST_F(Dhcpv4SrvTest, processDiscoverNoClientAddr) {
testDiscoverRequest(DHCPDISCOVER,
IOAddress("0.0.0.0"),
IOAddress("0.0.0.0"));
}
// Verifies that received REQUEST can be processed correctly,
// that the ACK message generated in response is valid and
// Verifies that REQUEST received via relay can be processed correctly,
// that the OFFER message generated in response is valid and
// contains necessary options.
//
// Note: this test focuses on the packet correctness. There
// are other tests that verify correctness of the allocation
// engine. See RequestBasic.
TEST_F(Dhcpv4SrvTest, processRequest) {
NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = i*10;
}
boost::shared_ptr<Pkt4> req(new Pkt4(DHCPREQUEST, 1234));
boost::shared_ptr<Pkt4> ack;
req->setIface("eth0");
req->setIndex(17);
req->setHWAddr(1, 6, mac);
req->setRemoteAddr(IOAddress("192.0.2.56"));
req->setGiaddr(IOAddress("192.0.2.67"));
// We are going to test that certain options are returned
// in the ACK message when requested using 'Parameter
// Request List' option. Let's configure those options that
// are returned when requested.
configureRequestedOptions();
// Should not throw
ASSERT_NO_THROW(
ack = srv->processRequest(req);
);
// Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPACK, ack->getType());
// This is relayed message. It should be sent back to relay address.
EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
messageCheck(req, ack);
// There are some options that are always present in the
// message, even if not requested.
EXPECT_TRUE(ack->getOption(DHO_DOMAIN_NAME));
EXPECT_TRUE(ack->getOption(DHO_DOMAIN_NAME_SERVERS));
// We did not request any options so these should not be present
// in the ACK.
EXPECT_FALSE(ack->getOption(DHO_LOG_SERVERS));
EXPECT_FALSE(ack->getOption(DHO_COOKIE_SERVERS));
EXPECT_FALSE(ack->getOption(DHO_LPR_SERVERS));
// Add 'Parameter Request List' option.
addPrlOption(req);
// Repeat the test but request some options.
ASSERT_NO_THROW(
ack = srv->processRequest(req);
);
// Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPACK, ack->getType());
// This is relayed message. It should be sent back to relay address.
EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
// Check that the requested options are returned.
optionsCheck(ack);
// Now repeat the test for directly sent message
req->setHops(0);
req->setGiaddr(IOAddress("0.0.0.0"));
req->setRemotePort(DHCP4_CLIENT_PORT);
EXPECT_NO_THROW(
ack = srv->processDiscover(req);
);
// Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPOFFER, ack->getType());
// This is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
messageCheck(req, ack);
// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
// and DiscoverInvalidHint.
TEST_F(Dhcpv4SrvTest, processRequestRelay) {
testDiscoverRequest(DHCPREQUEST,
IOAddress("192.0.2.56"),
IOAddress("192.0.2.67"));
}
// Check that the requested options are returned.
optionsCheck(ack);
// Verifies that the non-relayed REQUEST is processed correctly when
// client source address is specified.
TEST_F(Dhcpv4SrvTest, processRequestNoRelay) {
testDiscoverRequest(DHCPREQUEST,
IOAddress("0.0.0.0"),
IOAddress("192.0.2.67"));
}
delete srv;
// Verified that the non-relayed REQUEST is processed correctly when
// client source address is not specified.
TEST_F(Dhcpv4SrvTest, processRequestNoClientAddr) {
testDiscoverRequest(DHCPREQUEST,
IOAddress("0.0.0.0"),
IOAddress("0.0.0.0"));
}
TEST_F(Dhcpv4SrvTest, processRelease) {
......
......@@ -35,6 +35,9 @@ libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
libb10_dhcp___la_SOURCES += option_space.cc option_space.h
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
libb10_dhcp___la_SOURCES += pkt_filter.h
libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
libb10_dhcp___la_SOURCES += std_option_defs.h
libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
......
// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2013 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
......@@ -22,6 +22,7 @@
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
#include <util/io/pktinfo_utilities.h>
......@@ -47,7 +48,7 @@ IfaceMgr::instance() {
return (iface_mgr);
}
IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
Iface::Iface(const std::string& name, int ifindex)
:name_(name), ifindex_(ifindex), mac_len_(0), hardware_type_(0),
flag_loopback_(false), flag_up_(false), flag_running_(false),
flag_multicast_(false), flag_broadcast_(false), flags_(0)
......@@ -56,7 +57,7 @@ IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
}
void
IfaceMgr::Iface::closeSockets() {
Iface::closeSockets() {
for (SocketCollection::iterator sock = sockets_.begin();
sock != sockets_.end(); ++sock) {
close(sock->sockfd_);
......@@ -65,14 +66,14 @@ IfaceMgr::Iface::closeSockets() {
}
std::string
IfaceMgr::Iface::getFullName() const {
Iface::getFullName() const {
ostringstream tmp;
tmp << name_ << "/" << ifindex_;
return (tmp.str());
}
std::string
IfaceMgr::Iface::getPlainMac() const {
Iface::getPlainMac() const {
ostringstream tmp;
tmp.fill('0');
tmp << hex;
......@@ -86,18 +87,18 @@ IfaceMgr::Iface::getPlainMac() const {
return (tmp.str());
}
void IfaceMgr::Iface::setMac(const uint8_t* mac, size_t len) {
if (len > IfaceMgr::MAX_MAC_LEN) {
void Iface::setMac(const uint8_t* mac, size_t len) {
if (len > MAX_MAC_LEN) {
isc_throw(OutOfRange, "Interface " << getFullName()
<< " was detected to have link address of length "
<< len << ", but maximum supported length is "
<< IfaceMgr::MAX_MAC_LEN);
<< MAX_MAC_LEN);
}
mac_len_ = len;
memcpy(mac_, mac, len);
}
bool IfaceMgr::Iface::delAddress(const isc::asiolink::IOAddress& addr) {
bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
for (AddressCollection::iterator a = addrs_.begin();
a!=addrs_.end(); ++a) {
if (*a==addr) {
......@@ -108,7 +109,7 @@ bool IfaceMgr::Iface::delAddress(const isc::asiolink::IOAddress& addr) {
return (false);
}
bool IfaceMgr::Iface::delSocket(uint16_t sockfd) {
bool Iface::delSocket(uint16_t sockfd) {
list<SocketInfo>::iterator sock = sockets_.begin();
while (sock!=sockets_.end()) {
if (sock->sockfd_ == sockfd) {
......@@ -124,7 +125,8 @@ bool IfaceMgr::Iface::delSocket(uint16_t sockfd) {
IfaceMgr::IfaceMgr()
:control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
control_buf_(new char[control_buf_len_]),
session_socket_(INVALID_SOCKET), session_callback_(NULL)
session_socket_(INVALID_SOCKET), session_callback_(NULL),
packet_filter_(new PktFilterInet())
{
try {
......@@ -193,10 +195,23 @@ void IfaceMgr::stubDetectIfaces() {
addInterface(iface);
}
bool IfaceMgr::openSockets4(const uint16_t port) {
bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
int sock;
int count = 0;
// This option is used to bind sockets to particular interfaces.
// This is currently the only way to discover on which interface
// the broadcast packet has been received. If this option is
// not supported then only one interface should be confugured
// to listen for broadcast traffic.
#ifdef SO_BINDTODEVICE
const bool bind_to_device = true;
#else
const bool bind_to_device = false;
#endif
int bcast_num = 0;
for (IfaceCollection::iterator iface = ifaces_.begin();
iface != ifaces_.end();
++iface) {
......@@ -207,8 +222,8 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
continue;
}
AddressCollection addrs = iface->getAddresses();