Commit 9e571cc2 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac3242'

Conflicts:
	src/bin/dhcp4/tests/fqdn_unittest.cc
parents 49d0ce18 b6c929fa
......@@ -4475,6 +4475,40 @@ Dhcp4/subnet4 [] list (default)
</section>
<section id="dhcp4-subnet-selection">
<title>How DHCPv4 server selects subnet for a client</title>
<para>
The DHCPv4 server differentiates between the directly connected clients,
clients trying to renew leases and clients sending their messages through
relays. For the directly connected clients the server will check the
configuration of the interface on which the message has been received, and
if the server configuration doesn't match any configured subnet the
message is discarded.</para>
<para>Assuming that the server's interface is configured with the 192.0.2.3
IPv4 address, the server will only process messages received through
this interface from the directly connected client, if there is a subnet
configured, to which this IPv4 address belongs, e.g. 192.0.2.0/24.
The server will use this subnet to assign IPv4 address for the client.
</para>
<para>
The rule above does not apply when the client unicasts its message, i.e.
is trying to renew its lease. Such message is accepted through any
interface. The renewing client sets ciaddr to the currently used IPv4
address. The server uses this address to select the subnet for the client
(in particular, to extend the lease using this address).
</para>
<para>
If the message is relayed it is accepted through any interface. The giaddr
set by the relay agent is used to select the subnet for the client.
</para>
<note>
<para>The subnet selection mechanism described in this section is based
on the assumption that client classification is not used. The classification
mechanism alters the way in which subnet is selected for the client,
depending on the clasess that the client belongs to.</para>
</note>
</section>
<section id="dhcp4-std">
<title>Supported Standards</title>
<para>The following standards and draft standards are currently
......
......@@ -135,6 +135,18 @@ A "libreload" command was issued to reload the hooks libraries but for
some reason the reload failed. Other error messages issued from the
hooks framework will indicate the nature of the problem.
% DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE received message (transaction id %1) has unrecognized type %2 in option 53
This debug message indicates that the message type carried in DHCPv4 option
53 is unrecognized by the server. The valid message types are listed
on the IANA website: http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53.
The message will not be processed by the server.
% DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE received message (transaction id %1), having type %2 is not supported
This debug message indicates that the message type carried in DHCPv4 option
53 is valid but the message will not be processed by the server. This includes
messages being normally sent by the server to the client, such as Offer, ACK,
NAK etc.
% DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
This debug message indicates that the server successfully advertised
a lease. It is up to the client to choose one server out of othe advertised
......@@ -175,6 +187,13 @@ This warning message is issued when current server configuration specifies
no interfaces that server should listen on, or specified interfaces are not
configured to receive the traffic.
% DHCP4_NO_SUBNET_FOR_DIRECT_CLIENT no suitable subnet configured for a direct client sending packet with transaction id %1, on interface %2, received message is dropped
This info messsage is logged when received a message from a directly connected
client but there is no suitable subnet configured for the interface on
which this message has been received. The IPv4 address assigned on this
interface must belong to one of the configured subnets. Otherwise
received message is dropped.
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv4 DHCP server but it is not running.
......
......@@ -230,26 +230,15 @@ Dhcpv4Srv::run() {
}
}
// Check if the DHCPv4 packet has been sent to us or to someone else.
// If it hasn't been sent to us, drop it!
if (!acceptServerId(query)) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
.arg(query->getTransid())
.arg(query->getIface());
continue;
}
// When receiving a packet without message type option, getType() will
// throw. Let's set type to -1 as default error indicator.
int type = -1;
try {
type = query->getType();
} catch (...) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
.arg(query->getIface());
// Check whether the message should be further processed or discarded.
// There is no need to log anything here. This function logs by itself.
if (!accept(query)) {
continue;
}
// We have sanity checked (in accept() that the Message Type option
// exists, so we can safely get it here.
int type = query->getType();
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
.arg(serverReceivedPacketName(type))
.arg(type)
......@@ -1413,24 +1402,38 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
}
Subnet4Ptr
Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
Subnet4Ptr subnet;
// Is this relayed message?
IOAddress relay = question->getGiaddr();
static const IOAddress notset("0.0.0.0");
static const IOAddress bcast("255.255.255.255");
if (relay != notset) {
// Yes: Use relay address to select subnet
subnet = CfgMgr::instance().getSubnet4(relay);
// If a message is relayed, use the relay (giaddr) address to select subnet
// for the client. Note that this may result in exception if the value
// of hops does not correspond with the Giaddr. Such message is considered
// to be malformed anyway and the message will be dropped by the higher
// level functions.
if (question->isRelayed()) {
subnet = CfgMgr::instance().getSubnet4(question->getGiaddr());
// The message is not relayed so it is sent directly by a client. But
// the client may be renewing its lease and in such case it unicasts
// its message to the server. Note that the unicast Request bypasses
// relays and the client may be in a different network, so we can't
// use IP address on the local interface to get the subnet. Instead,
// we rely on the client's address to get the subnet.
} else if ((question->getLocalAddr() != bcast) &&
(question->getCiaddr() != notset)) {
subnet = CfgMgr::instance().getSubnet4(question->getCiaddr());
// The message has been received from a directly connected client
// and this client appears to have no address. The IPv4 address
// assigned to the interface on which this message has been received,
// will be used to determine the subnet suitable for the client.
} else {
// No: Use client's address to select subnet
subnet = CfgMgr::instance().getSubnet4(question->getRemoteAddr());
subnet = CfgMgr::instance().getSubnet4(question->getIface());
}
/// @todo Implement getSubnet4(interface-name)
// Let's execute all callouts registered for subnet4_select
if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
CalloutHandlePtr callout_handle = getCalloutHandle(question);
......@@ -1465,7 +1468,93 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
}
bool
Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
// Check if the message from directly connected client (if directly
// connected) should be dropped or processed.
if (!acceptDirectRequest(query)) {
LOG_INFO(dhcp4_logger, DHCP4_NO_SUBNET_FOR_DIRECT_CLIENT)
.arg(query->getTransid())
.arg(query->getIface());
return (false);
}
// Check if the DHCPv4 packet has been sent to us or to someone else.
// If it hasn't been sent to us, drop it!
if (!acceptServerId(query)) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
.arg(query->getTransid())
.arg(query->getIface());
return (false);
}
// Check that the message type is accepted by the server. We rely on the
// function called to log a message if needed.
if (!acceptMessageType(query)) {
return (false);
}
return (true);
}
bool
Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
try {
if (pkt->isRelayed()) {
return (true);
}
} catch (const Exception& ex) {
return (false);
}
static const IOAddress bcast("255.255.255.255");
return ((pkt->getLocalAddr() != bcast || selectSubnet(pkt)));
}
bool
Dhcpv4Srv::acceptMessageType(const Pkt4Ptr& query) const {
// When receiving a packet without message type option, getType() will
// throw.
int type;
try {
type = query->getType();
} catch (...) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
.arg(query->getIface());
return (false);
}
// If we receive a message with a non-existing type, we are logging it.
if (type > DHCPLEASEQUERYDONE) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE)
.arg(type)
.arg(query->getTransid());
return (false);
}
// Once we know that the message type is within a range of defined DHCPv4
// messages, we do a detailed check to make sure that the received message
// is targeted at server. Note that we could have received some Offer
// message broadcasted by the other server to a relay. Even though, the
// server would rather unicast its response to a relay, let's be on the
// safe side. Also, we want to drop other messages which we don't support.
// All these valid messages that we are not going to process are dropped
// silently.
if ((type != DHCPDISCOVER) && (type != DHCPREQUEST) &&
(type != DHCPRELEASE) && (type != DHCPDECLINE) &&
(type != DHCPINFORM)) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE)
.arg(type)
.arg(query->getTransid());
return (false);
}
return (true);
}
bool
Dhcpv4Srv::acceptServerId(const Pkt4Ptr& query) const {
// This function is meant to be called internally by the server class, so
// we rely on the caller to sanity check the pointer and we don't check
// it here.
......@@ -1475,7 +1564,7 @@ Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
// Note that we don't check cases that server identifier is mandatory
// but not present. This is meant to be sanity checked in other
// functions.
OptionPtr option = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
if (!option) {
return (true);
}
......@@ -1603,8 +1692,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
size_t
Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options) {
const std::string& option_space,
isc::dhcp::OptionCollection& options) {
size_t offset = 0;
OptionDefContainer option_defs;
......
......@@ -167,6 +167,83 @@ public:
protected:
/// @name Functions filtering and sanity-checking received messages.
///
/// @todo These functions are supposed to be moved to a new class which
/// will manage different rules for accepting and rejecting messages.
/// Perhaps ticket #3116 is a good opportunity to do it.
///
//@{
/// @brief Checks whether received message should be processed or discarded.
///
/// This function checks whether received message should be processed or
/// discarded. It should be called on the beginning of message processing
/// (just after the message has been decoded). This message calls a number
/// of other functions which check whether message should be processed,
/// using different criteria.
///
/// This function should be extended when new criteria for accepting
/// received message have to be implemented. This function is meant to
/// aggregate all early filtering checks on the received message. By having
/// a single function like this, we are avoiding bloat of the server's main
/// loop.
///
/// @warning This function should remain exception safe.
///
/// @param query Received message.
///
/// @return true if the message should be further processed, or false if
/// the message should be discarded.
bool accept(const Pkt4Ptr& query) const;
/// @brief Check if a message sent by directly connected client should be
/// accepted or discared.
///
/// This function checks if the received message is from directly connected
/// client. If it is, it checks that it should be processed or discarded.
///
/// Note that this function doesn't validate all addresses being carried in
/// the message. The primary purpose of this function is to filter out
/// direct messages in the local network for which there is no suitable
/// subnet configured. For example, this function accepts unicast messages
/// because unicasts may be used by clients located in remote networks to
/// to renew existing leases. If their notion of address is wrong, the
/// server will have to sent a NAK, instead of dropping the message.
/// Detailed validation of such messages is performed at later stage of
/// processing.
///
/// This function accepts the following messages:
/// - all valid relayed messages,
/// - all unicast messages,
/// - all broadcast messages received on the interface for which the
/// suitable subnet exists (is configured).
///
/// @param query Message sent by a client.
///
/// @return true if message is accepted for further processing, false
/// otherwise.
bool acceptDirectRequest(const Pkt4Ptr& query) const;
/// @brief Check if received message type is valid for the server to
/// process.
///
/// This function checks that the received message type belongs to the range
/// of types regonized by the server and that the message of this type
/// should be processed by the server.
///
/// The messages types accepted for processing are:
/// - Discover
/// - Request
/// - Release
/// - Decline
/// - Inform
///
/// @param query Message sent by a client.
///
/// @return true if message is accepted for further processing, false
/// otherwise.
bool acceptMessageType(const Pkt4Ptr& query) const;
/// @brief Verifies if the server id belongs to our server.
///
/// This function checks if the server identifier carried in the specified
......@@ -181,6 +258,7 @@ protected:
/// @return true, if the server identifier is absent or matches one of the
/// server identifiers that the server is using; false otherwise.
bool acceptServerId(const Pkt4Ptr& pkt) const;
//@}
/// @brief verifies if specified packet meets RFC requirements
///
......@@ -529,7 +607,7 @@ protected:
///
/// @param question client's message
/// @return selected subnet (or NULL if no suitable subnet was found)
isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question) const;
/// indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
......
......@@ -80,6 +80,7 @@ dhcp4_unittests_SOURCES += dhcp4_test_utils.h
dhcp4_unittests_SOURCES += dhcp4_unittests.cc
dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += dhcp4_test_utils.cc dhcp4_test_utils.h
dhcp4_unittests_SOURCES += direct_client_unittest.cc
dhcp4_unittests_SOURCES += wireshark.cc
dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += config_parser_unittest.cc
......@@ -95,6 +96,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
......
This diff is collapsed.
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2014 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
......@@ -15,12 +15,15 @@
#include <config.h>
#include <asiolink/io_address.h>
#include <cc/data.h>
#include <config/ccsession.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_custom.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/lease_mgr.h>
......@@ -28,17 +31,14 @@
using namespace std;
using namespace isc::asiolink;
using namespace isc::data;
namespace isc {
namespace dhcp {
namespace test {
/// dummy server-id file location
static const char* SRVID_FILE = "server-id-test.txt";
Dhcpv4SrvTest::Dhcpv4SrvTest()
:rcode_(-1) {
:rcode_(-1), srv_(0) {
subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
2000, 3000));
pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
......@@ -52,9 +52,6 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
opt_routers->setAddress(IOAddress("192.0.2.2"));
subnet_->addOption(opt_routers, false, "dhcp4");
// it's ok if that fails. There should not be such a file anyway
unlink(SRVID_FILE);
}
Dhcpv4SrvTest::~Dhcpv4SrvTest() {
......@@ -387,9 +384,6 @@ void Dhcpv4SrvTest::TearDown() {
CfgMgr::instance().deleteSubnets4();
// Let's clean up if there is such a file.
unlink(SRVID_FILE);
// Close all open sockets.
IfaceMgr::instance().closeSockets();
......@@ -413,58 +407,11 @@ void Dhcpv4SrvTest::TearDown() {
}
Dhcpv4SrvFakeIfaceTest::Dhcpv4SrvFakeIfaceTest()
: Dhcpv4SrvTest(), current_pkt_filter_() {
// Remove current interface configuration. Instead we want to add
// a couple of fake interfaces.
IfaceMgr& ifacemgr = IfaceMgr::instance();
ifacemgr.closeSockets();
ifacemgr.clearIfaces();
// Add fake interfaces.
ifacemgr.addInterface(createIface("lo", 0, "127.0.0.1"));
ifacemgr.addInterface(createIface("eth0", 1, "192.0.3.1"));
ifacemgr.addInterface(createIface("eth1", 2, "10.0.0.1"));
// In order to use fake interfaces we have to supply the custom
// packet filtering class, which can mimic opening sockets on
// fake interafaces.
current_pkt_filter_.reset(new PktFilterTest());
ifacemgr.setPacketFilter(current_pkt_filter_);
ifacemgr.openSockets4();
}
void
Dhcpv4SrvFakeIfaceTest::TearDown() {
// The base class function restores the original packet filtering class.
Dhcpv4SrvTest::TearDown();
// The base class however, doesn't re-detect real interfaces.
try {
IfaceMgr::instance().clearIfaces();
IfaceMgr::instance().detectIfaces();
Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
} catch (const Exception& ex) {
FAIL() << "Failed to restore interface configuration after using"
" fake interfaces";
}
}
Iface
Dhcpv4SrvFakeIfaceTest::createIface(const std::string& name, const int ifindex,
const std::string& addr) {
Iface iface(name, ifindex);
iface.addAddress(IOAddress(addr));
if (name == "lo") {
iface.flag_loopback_ = true;
}
iface.flag_up_ = true;
iface.flag_running_ = true;
iface.inactive4_ = false;
return (iface);
}
void
Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
// Create an instance of the tested class.
boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
......@@ -608,6 +555,20 @@ Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
EXPECT_TRUE(noBasicOptions(rsp));
}
void
Dhcpv4SrvTest::configure(const std::string& config) {
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
// Configure the server and make sure the config is accepted
EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
ASSERT_TRUE(status);
int rcode;
ConstElementPtr comment = config::parseAnswer(rcode, status);
ASSERT_EQ(0, rcode);
}
}; // end of isc::dhcp::test namespace
}; // end of isc::dhcp namespace
}; // end of isc namespace
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2014 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
......@@ -87,6 +87,120 @@ public:
typedef boost::shared_ptr<PktFilterTest> PktFilterTestPtr;
/// @brief "Naked" DHCPv4 server, exposes internal fields
class NakedDhcpv4Srv: public Dhcpv4Srv {
public:
/// @brief Constructor.
///
/// This constructor disables default modes of operation used by the
/// Dhcpv4Srv class:
/// - Send/receive broadcast messages through sockets on interfaces
/// which support broadcast traffic.
/// - Direct DHCPv4 traffic - communication with clients which do not
/// have IP address assigned yet.
///
/// Enabling these modes requires root privilges so they must be
/// disabled for unit testing.
///
/// Note, that disabling broadcast options on sockets does not impact
/// the operation of these tests because they use local loopback
/// interface which doesn't have broadcast capability anyway. It rather
/// prevents setting broadcast options on other (broadcast capable)
/// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
///
/// The Direct DHCPv4 Traffic capability can be disabled here because
/// it is tested with PktFilterLPFTest unittest. The tests which belong
/// to PktFilterLPFTest can be enabled on demand when root privileges can
/// be guaranteed.
///
/// @param port port number to listen on; the default value 0 indicates
/// that sockets should not be opened.
NakedDhcpv4Srv(uint16_t port = 0)
: Dhcpv4Srv(port, "type=memfile", false, false) {
// Create fixed server id.
server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
asiolink::IOAddress("192.0.3.1")));
}
/// @brief Returns fixed server identifier assigned to the naked server
/// instance.
OptionPtr getServerID() const {
return (server_id_);
}
/// @brief fakes packet reception
/// @param timeout ignored
///
/// The method receives all packets queued in receive queue, one after
/// another. Once the queue is empty, it initiates the shutdown procedure.
///
/// See fake_received_ field for description
virtual Pkt4Ptr receivePacket(int /*timeout*/) {
// If there is anything prepared as fake incoming traffic, use it
if (!fake_received_.empty()) {
Pkt4Ptr pkt = fake_received_.front();
fake_received_.pop_front();
return (pkt);
}
// If not, just trigger shutdown and return immediately
shutdown();
return (Pkt4Ptr());
}
/// @brief fake packet sending
///
/// Pretend to send a packet, but instead just store it in fake_send_ list
/// where test can later inspect server's response.
virtual void sendPacket(const Pkt4Ptr& pkt) {
fake_sent_.push_back(pkt);
}
/// @brief adds a packet to fake receive queue
///
/// See fake_received_ field for description
void fakeReceive(const Pkt4Ptr& pkt) {
fake_received_.push_back(pkt);