2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 05:55:28 +00:00

[master] Merge branch 'trac3231'

Conflicts:
	src/lib/dhcp/iface_mgr.cc
This commit is contained in:
Marcin Siodelski
2014-01-10 19:47:09 +01:00
15 changed files with 856 additions and 605 deletions

View File

@@ -4378,20 +4378,15 @@ Dhcp4/subnet4 [] list (default)
<para>
The DHCPv4 protocol uses a "server identifier" for clients to be able
to discriminate between several servers present on the same link: this
value is an IPv4 address of the server. When started for the first time,
the DHCPv4 server will choose one of its IPv4 addresses as its server-id,
and store the chosen value to a file. That file will be read by the server
and the contained value used whenever the server is subsequently started.
value is an IPv4 address of the server. The server chooses the IPv4 address
of the interface on which the message from the client (or relay) has been
received. A single server instance will use multiple server identifiers
if it is receiving queries on multiple interfaces.
</para>
<para>
It is unlikely that this parameter should ever need to be changed.
However, if such a need arises, stop the server, edit the file and restart
the server. (The file is named b10-dhcp4-serverid and by default is
stored in the "var" subdirectory of the directory in which BIND 10 is installed.
This can be changed when BIND 10 is built by using "--localstatedir"
on the "configure" command line.) The file is a text file that should
contain an IPv4 address. Spaces are ignored, and no extra characters are allowed
in this file.
Currently there is no mechanism to override the default server identifiers
by an administrator. In the future, the configuration mechanism will be used
to specify the custom server identifier.
</para>
</section>

View File

@@ -289,26 +289,6 @@ both clones use the same client-id.
% DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
A debug message listing the data returned to the client.
% DHCP4_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
This informational messages indicates that the server was not able to
read its server identifier and has generated a new one. This server-id
will be stored in a file and will be read (and used) whenever the server
is restarted. This is normal behavior when the server is started for the
first time. If this message is printed every time the server is started,
please check that the server has sufficient permission to write its
server-id file and that the file is not corrupt.
% DHCP4_SERVERID_LOADED server-id %1 has been loaded from file %2
This debug message indicates that the server loaded its server identifier.
That value is sent in all server responses and clients use it to
discriminate between servers. This is a part of normal startup or
reconfiguration procedure.
% DHCP4_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
This warning message indicates that server was not able to write its
server identifier to a file. The most likely cause is is that the server
does not have permissions to write the server id file.
% DHCP4_SERVER_FAILED server failed: %1
The IPv4 DHCP server has encountered a fatal error and is terminating.
The reason for the failure is included in the message.

View File

@@ -37,12 +37,10 @@
#include <hooks/hooks_manager.h>
#include <util/strutil.h>
#include <boost/algorithm/string/erase.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <iomanip>
#include <fstream>
using namespace isc;
using namespace isc::asiolink;
@@ -111,38 +109,28 @@ const bool FQDN_REPLACE_CLIENT_NAME = false;
}
/// @brief file name of a server-id file
///
/// Server must store its server identifier in persistent storage that must not
/// change between restarts. This is name of the file that is created in dataDir
/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
/// regular IPv4 address, e.g. 192.0.2.1. Server will create it during
/// first run and then use it afterwards.
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, const bool use_bcast,
const bool direct_response_desired)
: serverid_(), shutdown_(true), alloc_engine_(), port_(port),
: shutdown_(true), alloc_engine_(), port_(port),
use_bcast_(use_bcast), hook_index_pkt4_receive_(-1),
hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong.
// The 'true' value of the call to setMatchingPacketFilter imposes
// that IfaceMgr will try to use the mechanism to respond directly
// to the client which doesn't have address assigned. This capability
// may be lacking on some OSes, so there is no guarantee that server
// will be able to respond directly.
IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
// Open sockets only if port is non-zero. Port 0 is used
// for non-socket related testing.
// Open sockets only if port is non-zero. Port 0 is used for testing
// purposes in two cases:
// - when non-socket related testing is performed
// - when the particular test supplies its own packet filtering class.
if (port) {
// First call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong.
// The 'true' value of the call to setMatchingPacketFilter imposes
// that IfaceMgr will try to use the mechanism to respond directly
// to the client which doesn't have address assigned. This capability
// may be lacking on some OSes, so there is no guarantee that server
// will be able to respond directly.
IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
// Create error handler. This handler will be called every time
// the socket opening operation fails. We use this handler to
// log a warning.
@@ -151,24 +139,6 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
IfaceMgr::instance().openSockets4(port_, use_bcast_, error_handler);
}
string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
if (loadServerID(srvid_file)) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_SERVERID_LOADED)
.arg(srvidToString(getServerID()))
.arg(srvid_file);
} else {
generateServerID();
LOG_INFO(dhcp4_logger, DHCP4_SERVERID_GENERATED)
.arg(srvidToString(getServerID()))
.arg(srvid_file);
if (!writeServerID(srvid_file)) {
LOG_WARN(dhcp4_logger, DHCP4_SERVERID_WRITE_FAIL)
.arg(srvid_file);
}
}
// Instantiate LeaseMgr
LeaseMgrFactory::create(dbconfig);
LOG_INFO(dhcp4_logger, DHCP4_DB_BACKEND_STARTED)
@@ -389,19 +359,6 @@ Dhcpv4Srv::run() {
continue;
}
adjustRemoteAddr(query, rsp);
if (!rsp->getHops()) {
rsp->setRemotePort(DHCP4_CLIENT_PORT);
} else {
rsp->setRemotePort(DHCP4_SERVER_PORT);
}
rsp->setLocalAddr(query->getLocalAddr());
rsp->setLocalPort(DHCP4_SERVER_PORT);
rsp->setIface(query->getIface());
rsp->setIndex(query->getIndex());
// Specifies if server should do the packing
bool skip_pack = false;
@@ -487,90 +444,6 @@ Dhcpv4Srv::run() {
return (true);
}
bool
Dhcpv4Srv::loadServerID(const std::string& file_name) {
// load content of the file into a string
fstream f(file_name.c_str(), ios::in);
if (!f.is_open()) {
return (false);
}
string hex_string;
f >> hex_string;
f.close();
// remove any spaces
boost::algorithm::erase_all(hex_string, " ");
try {
IOAddress addr(hex_string);
if (!addr.isV4()) {
return (false);
}
// Now create server-id option
serverid_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, addr));
} catch(...) {
// any kind of malformed input (empty string, IPv6 address, complete
// garbate etc.)
return (false);
}
return (true);
}
void
Dhcpv4Srv::generateServerID() {
const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
// Let's find suitable interface.
for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
iface != ifaces.end(); ++iface) {
// Let's don't use loopback.
if (iface->flag_loopback_) {
continue;
}
// Let's skip downed interfaces. It is better to use working ones.
if (!iface->flag_up_) {
continue;
}
const Iface::AddressCollection addrs = iface->getAddresses();
for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
if (addr->getFamily() != AF_INET) {
continue;
}
serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
*addr));
return;
}
}
isc_throw(BadValue, "No suitable interfaces for server-identifier found");
}
bool
Dhcpv4Srv::writeServerID(const std::string& file_name) {
fstream f(file_name.c_str(), ios::out | ios::trunc);
if (!f.good()) {
return (false);
}
f << srvidToString(getServerID());
f.close();
return (true);
}
string
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
if (!srvid) {
@@ -690,12 +563,21 @@ Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
// add Message Type Option (type 53)
msg->setType(msg_type);
// DHCP Server Identifier (type 54)
msg->addOption(getServerID());
// more options will be added here later
}
void
Dhcpv4Srv::appendServerID(const Pkt4Ptr& response) {
// The source address for the outbound message should have been set already.
// This is the address that to the best of the server's knowledge will be
// available from the client.
// @todo: perhaps we should consider some more sophisticated server id
// generation, but for the current use cases, it should be ok.
response->addOption(OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
response->getLocalAddr()))
);
}
void
Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
@@ -1269,7 +1151,36 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
}
void
Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg) {
Dhcpv4Srv::adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response) {
adjustRemoteAddr(query, response);
// For the non-relayed message, the destination port is the client's port.
// For the relayed message, the server/relay port is a destination.
// Note that the call to this function may throw if invalid combination
// of hops and giaddr is found (hops = 0 if giaddr = 0 and hops != 0 if
// giaddr != 0). The exception will propagate down and eventually cause the
// packet to be discarded.
response->setRemotePort(query->isRelayed() ? DHCP4_SERVER_PORT :
DHCP4_CLIENT_PORT);
// In many cases the query is sent to a broadcast address. This address
// appears as a local address in the query message. Therefore we can't
// simply copy local address from the query and use it as a source
// address for the response. Instead, we have to check what address our
// socket is bound to and use it as a source address. This operation
// may throw if for some reason the socket is closed.
// @todo Consider an optimization that we use local address from
// the query if this address is not broadcast.
SocketInfo sock_info = IfaceMgr::instance().getSocket(*query);
// Set local adddress, port and interface.
response->setLocalAddr(sock_info.addr_);
response->setLocalPort(DHCP4_SERVER_PORT);
response->setIface(query->getIface());
response->setIndex(query->getIndex());
}
void
Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, const Pkt4Ptr& response) {
// Let's create static objects representing zeroed and broadcast
// addresses. We will use them further in this function to test
// other addresses against them. Since they are static, they will
@@ -1278,22 +1189,22 @@ Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg) {
static const IOAddress bcast_addr("255.255.255.255");
// If received relayed message, server responds to the relay address.
if (question->getGiaddr() != zero_addr) {
msg->setRemoteAddr(question->getGiaddr());
if (question->isRelayed()) {
response->setRemoteAddr(question->getGiaddr());
// If giaddr is 0 but client set ciaddr, server should unicast the
// response to ciaddr.
} else if (question->getCiaddr() != zero_addr) {
msg->setRemoteAddr(question->getCiaddr());
response->setRemoteAddr(question->getCiaddr());
// We can't unicast the response to the client when sending NAK,
// because we haven't allocated address for him. Therefore,
// NAK is broadcast.
} else if (msg->getType() == DHCPNAK) {
msg->setRemoteAddr(bcast_addr);
} else if (response->getType() == DHCPNAK) {
response->setRemoteAddr(bcast_addr);
// If yiaddr is set it means that we have created a lease for a client.
} else if (msg->getYiaddr() != zero_addr) {
} else if (response->getYiaddr() != zero_addr) {
// If the broadcast bit is set in the flags field, we have to
// send the response to broadcast address. Client may have requested it
// because it doesn't support reception of messages on the interface
@@ -1302,13 +1213,13 @@ Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg) {
// directly to a client without address assigned.
const bool bcast_flag = ((question->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
msg->setRemoteAddr(bcast_addr);
response->setRemoteAddr(bcast_addr);
// Client cleared the broadcast bit and we support direct responses
// so we should unicast the response to a newly allocated address -
// yiaddr.
} else {
msg->setRemoteAddr(msg->getYiaddr());
response->setRemoteAddr(response ->getYiaddr());
}
@@ -1316,7 +1227,7 @@ Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg) {
// found ourselves at this point, the rational thing to do is to respond
// to the address we got the query from.
} else {
msg->setRemoteAddr(question->getRemoteAddr());
response->setRemoteAddr(question->getRemoteAddr());
}
}
@@ -1361,6 +1272,12 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
appendBasicOptions(discover, offer);
}
// Set the src/dest IP address, port and interface for the outgoing
// packet.
adjustIfaceData(discover, offer);
appendServerID(offer);
return (offer);
}
@@ -1397,6 +1314,12 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
appendBasicOptions(request, ack);
}
// Set the src/dest IP address, port and interface for the outgoing
// packet.
adjustIfaceData(request, ack);
appendServerID(ack);
return (ack);
}

View File

@@ -175,7 +175,7 @@ protected:
/// @param pkt packet to be checked
/// @param serverid expectation regarding server-id option
/// @throw RFCViolation if any issues are detected
void sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid);
static void sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid);
/// @brief Processes incoming DISCOVER and returns response.
///
@@ -396,10 +396,68 @@ protected:
/// @brief Appends default options to a message
///
/// Currently it is only a Message Type option. This function does not add
/// the Server Identifier option as this option must be added using
/// @c Dhcpv4Srv::appendServerID.
///
///
/// @param msg message object (options will be added to it)
/// @param msg_type specifies message type
void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
/// @brief Adds server identifier option to the server's response.
///
/// This method adds a server identifier to the DHCPv4 message. It epxects
/// that the local (source) address is set for this message. If address is
/// not set, it will throw an exception. This method also expects that the
/// server identifier option is not present in the specified message.
/// Otherwise, it will throw an exception on attempt to add a duplicate
/// server identifier option.
///
/// @note This method doesn't throw exceptions by itself but the underlying
/// classes being used my throw. The reason for this method to not sanity
/// check the specified message is that it is meant to be called internally
/// by the @c Dhcpv4Srv class.
///
/// @note This method is static because it is not dependent on the class
/// state.
///
/// @param [out] response DHCPv4 message to which the server identifier
/// option should be added.
static void appendServerID(const Pkt4Ptr& response);
/// @brief Set IP/UDP and interface parameters for the DHCPv4 response.
///
/// This method sets the following parameters for the DHCPv4 message being
/// sent to a client:
/// - client unicast or a broadcast address,
/// - client or relay port,
/// - server address,
/// - server port,
/// - name and index of the interface which is to be used to send the
/// message.
///
/// Internally it calls the @c Dhcpv4Srv::adjustRemoteAddr to figure
/// out the destination address (client unicast address or broadcast
/// address).
///
/// The destination port is always DHCPv4 client (68) or relay (67) port,
/// depending if the response will be sent directly to a client.
///
/// The source port is always set to DHCPv4 server port (67).
///
/// The interface selected for the response is always the same as the
/// one through which the query has been received.
///
/// The source address for the response is the IPv4 address assigned to
/// the interface being used to send the response. This function uses
/// @c IfaceMgr to get the socket bound to the IPv4 address on the
/// particular interface.
///
/// @note This method is static because it is not dependent on the class
/// state.
static void adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response);
/// @brief Sets remote addresses for outgoing packet.
///
/// This method sets the local and remote addresses on outgoing packet.
@@ -413,42 +471,14 @@ protected:
/// are valid. Make sure that pointers are correct before calling this
/// function.
///
/// @note This method is static because it is not dependent on the class
/// state.
///
/// @param question instance of a packet received by a server.
/// @param [out] msg response packet which addresses are to be adjusted.
void adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg);
/// @brief Returns server-identifier option
///
/// @return server-id option
OptionPtr getServerID() { return serverid_; }
/// @brief Sets server-identifier.
///
/// This method attempts to set server-identifier DUID. It tries to
/// load previously stored IP from configuration. If there is no previously
/// stored server identifier, it will pick up one address from configured
/// and supported network interfaces.
///
/// @throws isc::Unexpected Failed to obtain server identifier (i.e. no
// previously stored configuration and no network interfaces available)
void generateServerID();
/// @brief attempts to load server-id from a file
///
/// Tries to load duid from a text file. If the load is successful,
/// it creates server-id option and stores it in serverid_ (to be used
/// later by getServerID()).
///
/// @param file_name name of the server-id file to load
/// @return true if load was successful, false otherwise
bool loadServerID(const std::string& file_name);
/// @brief attempts to write server-id to a file
/// Tries to write server-id content (stored in serverid_) to a text file.
///
/// @param file_name name of the server-id file to write
/// @return true if write was successful, false otherwise
bool writeServerID(const std::string& file_name);
/// @param [out] response response packet which addresses are to be
/// adjusted.
static void adjustRemoteAddr(const Pkt4Ptr& question,
const Pkt4Ptr& response);
/// @brief converts server-id to text
/// Converts content of server-id option to a text representation, e.g.
@@ -486,9 +516,6 @@ protected:
/// @return selected subnet (or NULL if no suitable subnet was found)
isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
/// server DUID (to be sent in server-identifier option)
OptionPtr serverid_;
/// indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
volatile bool shutdown_;

File diff suppressed because it is too large Load Diff

View File

@@ -55,16 +55,6 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
// it's ok if that fails. There should not be such a file anyway
unlink(SRVID_FILE);
const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
// There must be some interface detected
if (ifaces.empty()) {
// We can't use ASSERT in constructor
ADD_FAILURE() << "No interfaces detected.";
}
valid_iface_ = ifaces.begin()->getName();
}
Dhcpv4SrvTest::~Dhcpv4SrvTest() {
@@ -325,9 +315,6 @@ Lease4Ptr Dhcpv4SrvTest::checkLease(const Pkt4Ptr& rsp,
return (lease);
}
/// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id
/// @param rsp response packet to be validated
/// @param expected_srvid expected value of server-id
void Dhcpv4SrvTest::checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) {
// Check that server included its server-id
OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER);
@@ -396,8 +383,88 @@ Dhcpv4SrvTest::createPacketFromBuffer(const Pkt4Ptr& src_pkt,
return (::testing::AssertionSuccess());
}
void Dhcpv4SrvTest::TearDown() {
void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
CfgMgr::instance().deleteSubnets4();
// Let's clean up if there is such a file.
unlink(SRVID_FILE);
// Close all open sockets.
IfaceMgr::instance().closeSockets();
// Some unit tests override the default packet filtering class, used
// by the IfaceMgr. The dummy class, called PktFilterTest, reports the
// capability to directly respond to the clients without IP address
// assigned. This capability is not supported by the default packet
// filtering class: PktFilterInet. Therefore setting the dummy class
// allows to test scenarios, when server responds to the broadcast address
// on client's request, despite having support for direct response.
// The following call restores the use of original packet filtering class
// after the test.
try {
IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
} catch (const Exception& ex) {
FAIL() << "Failed to restore the default (PktFilterInet) packet filtering"
<< " class after the test. Exception has been caught: "
<< ex.what();
}
}
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();
} 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));
@@ -432,8 +499,9 @@ void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
req->setLocalHWAddr(1, 6, mac);
// Set target IP address.
req->setRemoteAddr(IOAddress("192.0.2.55"));
// Set relay address.
// Set relay address and hops.
req->setGiaddr(IOAddress("192.0.2.10"));
req->setHops(1);
// We are going to test that certain options are returned
// in the response message when requested using 'Parameter
@@ -446,6 +514,8 @@ void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
// which was parsed from its wire format.
Pkt4Ptr received;
ASSERT_TRUE(createPacketFromBuffer(req, received));
// Set interface. It is required for the server to generate server id.
received->setIface("eth0");
if (msg_type == DHCPDISCOVER) {
ASSERT_NO_THROW(
rsp = srv->processDiscover(received);
@@ -479,6 +549,9 @@ void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
ASSERT_TRUE(createPacketFromBuffer(req, received));
ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
// Set interface. It is required for the server to generate server id.
received->setIface("eth0");
if (msg_type == DHCPDISCOVER) {
ASSERT_NO_THROW(rsp = srv->processDiscover(received));
@@ -512,6 +585,9 @@ void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
ASSERT_TRUE(createPacketFromBuffer(req, received));
ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
// Set interface. It is required for the server to generate server id.
received->setIface("eth0");
if (msg_type == DHCPDISCOVER) {
ASSERT_NO_THROW(rsp = srv->processDiscover(received));
// Should return non-NULL packet.
@@ -532,36 +608,6 @@ void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
EXPECT_TRUE(noBasicOptions(rsp));
}
/// @brief This function cleans up after the test.
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();
// Some unit tests override the default packet filtering class, used
// by the IfaceMgr. The dummy class, called PktFilterTest, reports the
// capability to directly respond to the clients without IP address
// assigned. This capability is not supported by the default packet
// filtering class: PktFilterInet. Therefore setting the dummy class
// allows to test scenarios, when server responds to the broadcast address
// on client's request, despite having support for direct response.
// The following call restores the use of original packet filtering class
// after the test.
try {
IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
} catch (const Exception& ex) {
FAIL() << "Failed to restore the default (PktFilterInet) packet filtering"
<< " class after the test. Exception has been caught: "
<< ex.what();
}
}
}; // end of isc::dhcp::test namespace
}; // end of isc::dhcp namespace
}; // end of isc namespace

View File

@@ -21,6 +21,7 @@
#include <gtest/gtest.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt_filter.h>
#include <dhcp/pkt_filter_inet.h>
@@ -31,6 +32,8 @@
#include <config/ccsession.h>
#include <list>
#include <boost/shared_ptr.hpp>
namespace isc {
namespace dhcp {
namespace test {
@@ -45,11 +48,18 @@ namespace test {
class PktFilterTest : public PktFilter {
public:
/// @brief Constructor.
///
/// Sets the 'direct response' capability to true.
PktFilterTest()
: direct_resp_supported_(true) {
}
/// @brief Reports 'direct response' capability.
///
/// @return always true.
virtual bool isDirectResponseSupported() const {
return (true);
return (direct_resp_supported_);
}
/// Does nothing.
@@ -69,8 +79,14 @@ public:
return (0);
}
/// @brief Holds a boolean value which indicates whether direct response
/// capability is supported (true) or not (false).
bool direct_resp_supported_;
};
typedef boost::shared_ptr<PktFilterTest> PktFilterTestPtr;
class Dhcpv4SrvTest : public ::testing::Test {
public:
@@ -237,24 +253,12 @@ public:
createPacketFromBuffer(const Pkt4Ptr& src_pkt,
Pkt4Ptr& dst_pkt);
/// @brief generates a DHCPv4 packet based on provided hex string
///
/// @return created packet
Pkt4Ptr packetFromCapture(const std::string& hex_string);
/// @brief Tests if Discover or Request message is processed correctly
///
/// This test verifies that the Parameter Request List option is handled
/// correctly, i.e. it checks that certain options are present in the
/// server's response when they are requested and that they are not present
/// when they are not requested or NAK occurs.
///
/// @todo We need an additional test for PRL option using real traffic
/// capture.
///
/// @param msg_type DHCPDISCOVER or DHCPREQUEST
void testDiscoverRequest(const uint8_t msg_type);
/// @brief This function cleans up after the test.
virtual void TearDown();
@@ -271,8 +275,62 @@ public:
isc::data::ConstElementPtr comment_;
// Name of a valid network interface
std::string valid_iface_;
};
/// @brief Test fixture class to be used for tests which require fake
/// interfaces.
///
/// The DHCPv4 server must always append the server identifier to its response.
/// The server identifier is typically an IP address assigned to the interface
/// on which the query has been received. The DHCPv4 server uses IfaceMgr to
/// check this address. In order to test this functionality, a set of interfaces
/// must be known to the test. This test fixture class creates a set of well
/// known (fake) interfaces which can be assigned to the test DHCPv4 messages
/// so as the response (including server identifier) can be validated.
/// The real interfaces are removed from the IfaceMgr in the constructor and
/// they are re-assigned in the destructor.
class Dhcpv4SrvFakeIfaceTest : public Dhcpv4SrvTest {
public:
/// @brief Constructor.
///
/// Creates a set of fake interfaces:
/// - lo, index: 0, address: 127.0.0.1
/// - eth0, index: 1, address: 192.0.3.1
/// - eth1, index: 2, address: 10.0.0.1
///
/// These interfaces replace the real interfaces detected by the IfaceMgr.
Dhcpv4SrvFakeIfaceTest();
/// @brief Restores the original interface configuration.
virtual void TearDown();
/// @brief Creates an instance of the interface.
///
/// @param name Name of the interface.
/// @param ifindex Index of the interface.
/// @param addr IP address assigned to the interface, represented as string.
///
/// @return Iface Instance of the interface.
static Iface createIface(const std::string& name, const int ifindex,
const std::string& addr);
/// @brief Tests if Discover or Request message is processed correctly
///
/// This test verifies that the Parameter Request List option is handled
/// correctly, i.e. it checks that certain options are present in the
/// server's response when they are requested and that they are not present
/// when they are not requested or NAK occurs.
///
/// @todo We need an additional test for PRL option using real traffic
/// capture.
///
/// @param msg_type DHCPDISCOVER or DHCPREQUEST
void testDiscoverRequest(const uint8_t msg_type);
/// @brief Holds a pointer to the packet filter object currently used
/// by the IfaceMgr.
PktFilterTestPtr current_pkt_filter_;
};
/// @brief "Naked" DHCPv4 server, exposes internal fields
@@ -306,6 +364,15 @@ public:
/// 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
@@ -341,12 +408,16 @@ public:
///
/// See fake_received_ field for description
void fakeReceive(const Pkt4Ptr& pkt) {
pkt->setIface("eth0");
fake_received_.push_back(pkt);
}
virtual ~NakedDhcpv4Srv() {
}
/// @brief Dummy server identifier option used by various tests.
OptionPtr server_id_;
/// @brief packets we pretend to receive
///
/// Instead of setting up sockets on interfaces that change between OSes, it
@@ -357,7 +428,8 @@ public:
std::list<Pkt4Ptr> fake_sent_;
using Dhcpv4Srv::adjustRemoteAddr;
using Dhcpv4Srv::adjustIfaceData;
using Dhcpv4Srv::appendServerID;
using Dhcpv4Srv::processDiscover;
using Dhcpv4Srv::processRequest;
using Dhcpv4Srv::processRelease;
@@ -366,10 +438,6 @@ public:
using Dhcpv4Srv::processClientName;
using Dhcpv4Srv::computeDhcid;
using Dhcpv4Srv::createNameChangeRequests;
using Dhcpv4Srv::getServerID;
using Dhcpv4Srv::loadServerID;
using Dhcpv4Srv::generateServerID;
using Dhcpv4Srv::writeServerID;
using Dhcpv4Srv::sanityCheck;
using Dhcpv4Srv::srvidToString;
using Dhcpv4Srv::unpackOptions;

View File

@@ -30,9 +30,9 @@ using namespace isc::dhcp_ddns;
namespace {
class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
public:
NameDhcpv4SrvTest() : Dhcpv4SrvTest() {
NameDhcpv4SrvTest() : Dhcpv4SrvFakeIfaceTest() {
srv_ = new NakedDhcpv4Srv(0);
}
virtual ~NameDhcpv4SrvTest() {
@@ -110,6 +110,7 @@ public:
const bool include_clientid = true) {
Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
pkt->setRemoteAddr(IOAddress("192.0.2.3"));
pkt->setIface("eth0");
// For DISCOVER we don't include server id, because client broadcasts
// the message to all servers.
if (msg_type != DHCPDISCOVER) {
@@ -580,7 +581,6 @@ TEST_F(NameDhcpv4SrvTest, processDiscover) {
Pkt4Ptr reply;
ASSERT_NO_THROW(reply = srv_->processDiscover(req));
checkResponse(reply, DHCPOFFER, 1234);
EXPECT_TRUE(srv_->name_change_reqs_.empty());
@@ -615,6 +615,9 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
// to it when Hostname option carries the top level domain-name.
TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
// Set interface for the incoming packet. The server requires it to
// generate client id.
req->setIface("eth0");
Pkt4Ptr reply;
ASSERT_NO_THROW(reply = srv_->processRequest(req));
@@ -691,6 +694,11 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "myhost.example.com.");
// Set interface for the incoming packet. The server requires it to
// generate client id.
req1->setIface("eth0");
Pkt4Ptr reply;
ASSERT_NO_THROW(reply = srv_->processRequest(req1));
@@ -709,6 +717,10 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
// another one to add new entry with updated domain-name.
Pkt4Ptr req2 = generatePktWithHostname(DHCPREQUEST, "otherhost");
// Set interface for the incoming packet. The server requires it to
// generate client id.
req2->setIface("eth0");
ASSERT_NO_THROW(reply = srv_->processRequest(req2));
checkResponse(reply, DHCPACK, 1234);

View File

@@ -325,6 +325,8 @@ void IfaceMgr::stubDetectIfaces() {
addInterface(iface);
}
bool
IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
IfaceMgrErrorMsgCallback error_handler) {
@@ -585,6 +587,11 @@ IfaceMgr::getIface(const std::string& ifname) {
return (NULL); // not found
}
void
IfaceMgr::clearIfaces() {
ifaces_.clear();
}
int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
const uint16_t port, const bool receive_bcast,
const bool send_bcast) {
@@ -794,7 +801,7 @@ IfaceMgr::send(const Pkt4Ptr& pkt) {
}
// Assuming that packet filter is not NULL, because its modifier checks it.
return (packet_filter_->send(*iface, getSocket(*pkt), pkt));
return (packet_filter_->send(*iface, getSocket(*pkt).sockfd_, pkt));
}
@@ -994,8 +1001,7 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
Iface* iface = getIface(pkt.getIface());
if (iface == NULL) {
isc_throw(BadValue, "Tried to find socket for non-existent interface "
<< pkt.getIface());
isc_throw(BadValue, "Tried to find socket for non-existent interface");
}
@@ -1048,18 +1054,18 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
<< " does not have any suitable IPv6 sockets open.");
}
uint16_t IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
SocketInfo
IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
Iface* iface = getIface(pkt.getIface());
if (iface == NULL) {
isc_throw(BadValue, "Tried to find socket for non-existent interface "
<< pkt.getIface());
isc_throw(BadValue, "Tried to find socket for non-existent interface");
}
const Iface::SocketCollection& socket_collection = iface->getSockets();
Iface::SocketCollection::const_iterator s;
for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
if (s->family_ == AF_INET) {
return (s->sockfd_);
return (*s);
}
/// TODO: Add more checks here later. If remote address is
/// not link-local, we can't use link local bound socket

View File

@@ -444,8 +444,7 @@ public:
/// @return interface with requested name (or NULL if no such
/// interface is present)
///
Iface*
getIface(const std::string& ifname);
Iface* getIface(const std::string& ifname);
/// @brief Returns container with all interfaces.
///
@@ -454,7 +453,22 @@ public:
/// main() function completes, you should not worry much about this.
///
/// @return container with all interfaces.
const IfaceCollection& getIfaces() { return ifaces_; }
const IfaceCollection& getIfaces() { return (ifaces_); }
/// @brief Removes detected interfaces.
///
/// This method removes all detected interfaces. This method should be
/// used by unit tests to supply a custom set of interfaces. For example:
/// a unit test may create a pool of fake interfaces and use the custom
/// @c PktFilter class to mimic socket operation on these interfaces.
void clearIfaces();
/// @brief Detects network interfaces.
///
/// This method will eventually detect available interfaces. For now
/// it offers stub implementation. First interface name and link-local
/// IPv6 address is read from interfaces.txt file.
void detectIfaces();
/// @brief Return most suitable socket for transmitting specified IPv6 packet.
///
@@ -469,7 +483,7 @@ public:
/// @return a socket descriptor
uint16_t getSocket(const isc::dhcp::Pkt6& pkt);
/// @brief Return most suitable socket for transmitting specified IPv6 packet.
/// @brief Return most suitable socket for transmitting specified IPv4 packet.
///
/// This method takes Pkt4 (see overloaded implementation that takes
/// Pkt6) and chooses appropriate socket to send it. This method
@@ -479,8 +493,8 @@ public:
///
/// @param pkt a packet to be transmitted
///
/// @return a socket descriptor
uint16_t getSocket(const isc::dhcp::Pkt4& pkt);
/// @return A structure describing a socket.
SocketInfo getSocket(const isc::dhcp::Pkt4& pkt);
/// Debugging method that prints out all available interfaces.
///
@@ -870,14 +884,6 @@ protected:
int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr,
uint16_t port, const bool join_multicast);
/// @brief Detects network interfaces.
///
/// This method will eventually detect available interfaces. For now
/// it offers stub implementation. First interface name and link-local
/// IPv6 address is read from interfaces.txt file.
void
detectIfaces();
/// @brief Stub implementation of network interface detection.
///
/// This implementations reads a single line from interfaces.txt file

View File

@@ -463,6 +463,26 @@ Pkt4::updateTimestamp() {
timestamp_ = boost::posix_time::microsec_clock::universal_time();
}
bool
Pkt4::isRelayed() const {
static const IOAddress zero_addr("0.0.0.0");
// For non-relayed message both Giaddr and Hops are zero.
if (getGiaddr() == zero_addr && getHops() == 0) {
return (false);
// For relayed message, both Giaddr and Hops are non-zero.
} else if (getGiaddr() != zero_addr && getHops() > 0) {
return (true);
}
// In any other case, the packet is considered malformed.
isc_throw(isc::BadValue, "invalid combination of giaddr = "
<< getGiaddr().toText() << " and hops = "
<< static_cast<int>(getHops()) << ". Valid values"
" are: (giaddr = 0 and hops = 0) or (giaddr != 0 and"
"hops != 0)");
}
} // end of namespace isc::dhcp
} // end of namespace isc

View File

@@ -484,6 +484,24 @@ public:
/// @return remote port
uint16_t getRemotePort() const { return (remote_port_); }
/// @brief Checks if a DHCPv4 message has been relayed.
///
/// This function returns a boolean value which indicates whether a DHCPv4
/// message has been relayed (if true is returned) or not (if false).
///
/// This function uses a combination of Giaddr and Hops. It is expected that
/// if Giaddr is not 0, the Hops is greater than 0. In this case the message
/// is considered relayed. If Giaddr is 0, the Hops value must also be 0. In
/// this case the message is considered non-relayed. For any other
/// combination of Giaddr and Hops, an exception is thrown to indicate that
/// the message is malformed.
///
/// @return Boolean value which indicates whether the message is relayed
/// (true) or non-relayed (false).
/// @throw isc::BadValue if invalid combination of Giaddr and Hops values is
/// found.
bool isRelayed() const;
/// @brief Set callback function to be used to parse options.
///
/// @param callback An instance of the callback function or NULL to

View File

@@ -258,26 +258,6 @@ PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
// are valid because they are checked by the function called.
writeEthernetHeader(pkt, buf);
// This object represents broadcast address. We will compare the
// local packet address with it a few lines below. Having static
// variable guarantees that this object is created only once, not
// every time this function is called.
static const isc::asiolink::IOAddress bcast_addr("255.255.255.255");
// It is likely that the local address in pkt object is set to
// broadcast address. This is the case if server received the
// client's packet on broadcast address. Therefore, we need to
// correct it here and assign the actual source address.
if (pkt->getLocalAddr() == bcast_addr) {
const Iface::SocketCollection& sockets = iface.getSockets();
for (Iface::SocketCollection::const_iterator it = sockets.begin();
it != sockets.end(); ++it) {
if (sockfd == it->sockfd_) {
pkt->setLocalAddr(it->addr_);
}
}
}
// IP and UDP header
writeIpUdpHeader(pkt, buf);

View File

@@ -577,6 +577,25 @@ TEST_F(IfaceMgrTest, getIface) {
EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi15") );
}
TEST_F(IfaceMgrTest, clearIfaces) {
NakedIfaceMgr ifacemgr;
// Create a set of fake interfaces. At the same time, remove the actual
// interfaces that have been detected by the IfaceMgr.
ifacemgr.createIfaces();
ASSERT_GT(ifacemgr.countIfaces(), 0);
boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
ASSERT_TRUE(custom_packet_filter);
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
ASSERT_NO_THROW(ifacemgr.openSockets4());
ifacemgr.clearIfaces();
EXPECT_EQ(0, ifacemgr.countIfaces());
}
TEST_F(IfaceMgrTest, receiveTimeout6) {
using namespace boost::posix_time;
std::cout << "Testing DHCPv6 packet reception timeouts."
@@ -1328,7 +1347,7 @@ TEST_F(IfaceMgrTest, socket4) {
pkt.setIface(LOOPBACK);
// Expect that we get the socket that we just opened.
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt));
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt).sockfd_);
close(socket1);
}
@@ -1963,7 +1982,7 @@ TEST_F(IfaceMgrTest, socketInfo) {
// Socket info is set, packet has well defined interface. It should work.
pkt4.setIface(LOOPBACK);
EXPECT_EQ(7, ifacemgr->getSocket(pkt4));
EXPECT_EQ(7, ifacemgr->getSocket(pkt4).sockfd_);
EXPECT_NO_THROW(
ifacemgr->getIface(LOOPBACK)->delSocket(7);

View File

@@ -798,4 +798,27 @@ TEST_F(Pkt4Test, hwaddrSrcRemote) {
remote_addr->hwaddr_.begin()));
}
// This test verifies that the check for a message being relayed is correct.
// It also checks that the exception is thrown if the combination of hops and
// giaddr is invalid.
TEST_F(Pkt4Test, isRelayed) {
Pkt4 pkt(DHCPDISCOVER, 1234);
// By default, the hops and giaddr should be 0.
ASSERT_EQ("0.0.0.0", pkt.getGiaddr().toText());
ASSERT_EQ(0, pkt.getHops());
// For hops = 0 and giaddr = 0, the message is non-relayed.
EXPECT_FALSE(pkt.isRelayed());
// Set giaddr but leave hops = 0. This should result in exception.
pkt.setGiaddr(IOAddress("10.0.0.1"));
EXPECT_THROW(pkt.isRelayed(), isc::BadValue);
// Set hops. Now both hops and giaddr is set. The message is relayed.
pkt.setHops(10);
EXPECT_TRUE(pkt.isRelayed());
// Set giaddr to 0. For hops being set to non-zero value the function
// should throw an exception.
pkt.setGiaddr(IOAddress("0.0.0.0"));
EXPECT_THROW(pkt.isRelayed(), isc::BadValue);
}
} // end of anonymous namespace