Commit c7a229f1 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac3231'

Conflicts:
	src/lib/dhcp/iface_mgr.cc
parents 8e2ea006 861a9b6f
......@@ -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>
......
......@@ -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.
......
......@@ -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);
}
......
......@@ -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.
///
/// @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
/// @note This method is static because it is not dependent on the class
/// state.
///
/// @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 question instance of a packet received by a server.
/// @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_;
......
This diff is collapsed.
......@@ -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() {
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();