Commit 5eede0e0 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[master] Merge branch 'trac3184' (RAI option echoed back in DHCPv4)

parents b080f05c 287389c0
690. [bug] tomek
b10-dhcp4: Relay Agent Info option is now echoed back in
DHCPv4 responses.
(Trac #3184, git 287389c049518bff66bdf6a5a49bb8768be02d8e)
689. [func]* marcin
b10-dhcp4 and b10-dhcp6 install callback functions which parse options
in the received DHCP packets.
......@@ -584,6 +584,12 @@ Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
if (dst_hw_addr) {
// If this packet is relayed, we want to copy Relay Agent Info option
OptionPtr rai = question->getOption(DHO_DHCP_AGENT_OPTIONS);
if (rai) {
......@@ -77,6 +77,8 @@ dhcp4_unittests_SOURCES += ../dhcp4_log.h ../
dhcp4_unittests_SOURCES += ../ ../config_parser.h
dhcp4_unittests_SOURCES +=
dhcp4_unittests_SOURCES +=
dhcp4_unittests_SOURCES += dhcp4_test_utils.h
dhcp4_unittests_SOURCES +=
dhcp4_unittests_SOURCES +=
dhcp4_unittests_SOURCES +=
dhcp4_unittests_SOURCES +=
......@@ -17,12 +17,13 @@
#include <asiolink/io_address.h>
#include <config/ccsession.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option.h>
#include <dhcp/option_int.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt_filter.h>
#include <dhcp/pkt_filter_inet.h>
......@@ -52,6 +53,7 @@ using namespace isc::data;
using namespace isc::asiolink;
using namespace isc::hooks;
#if 0
namespace {
class NakedDhcpv4Srv: public Dhcpv4Srv {
......@@ -150,498 +152,14 @@ public:
using Dhcpv4Srv::srvidToString;
using Dhcpv4Srv::unpackOptions;
/// dummy server-id file location
static const char* SRVID_FILE = "server-id-test.txt";
/// @brief Dummy Packet Filtering class.
/// This class reports capability to respond directly to the client which
/// doesn't have address configured yet.
/// All packet and socket handling functions do nothing because they are not
/// used in unit tests.
class PktFilterTest : public PktFilter {
/// @brief Reports 'direct response' capability.
/// @return always true.
virtual bool isDirectResponseSupported() const {
return (true);
/// Does nothing.
virtual int openSocket(const Iface&, const IOAddress&, const uint16_t,
const bool, const bool) {
return (0);
/// Does nothing.
virtual Pkt4Ptr receive(const Iface&, const SocketInfo&) {
return Pkt4Ptr();
/// Does nothing.
virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
return (0);
class Dhcpv4SrvTest : public ::testing::Test {
/// @brief Constructor
/// Initializes common objects used in many tests.
/// Also sets up initial configuration in CfgMgr.
Dhcpv4SrvTest() :
subnet_ = Subnet4Ptr(new Subnet4(IOAddress(""), 24, 1000,
2000, 3000));
pool_ = Pool4Ptr(new Pool4(IOAddress(""), IOAddress("")));
// Add Router option.
Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
subnet_->addOption(opt_routers, false, "dhcp4");
// it's ok if that fails. There should not be such a file anyway
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();
virtual ~Dhcpv4SrvTest() {
/// @brief Add 'Parameter Request List' option to the packet.
/// This function PRL option comprising the following option codes:
/// - 5 - Name Server
/// - 15 - Domain Name
/// - 7 - Log Server
/// - 8 - Quotes Server
/// - 9 - LPR Server
/// @param pkt packet to add PRL option to.
void addPrlOption(Pkt4Ptr& pkt) {
OptionUint8ArrayPtr option_prl =
OptionUint8ArrayPtr(new OptionUint8Array(Option::V4,
// Let's request options that have been configured for the subnet.
// Let's also request the option that hasn't been configured. In such
// case server should ignore request for this particular option.
// And add 'Parameter Request List' option into the DISCOVER packet.
/// @brief Configures options being requested in the PRL option.
/// The lpr-servers option is NOT configured here although it is
/// added to the 'Parameter Request List' option in the
/// \ref addPrlOption. When requested option is not configured
/// the server should not return it in its response. The goal
/// of not configuring the requested option is to verify that
/// the server will not return it.
void configureRequestedOptions() {
// dns-servers
option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS));
ASSERT_NO_THROW(subnet_->addOption(option_dns_servers, false, "dhcp4"));
// domain-name
OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE);
OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
subnet_->addOption(option_domain_name, false, "dhcp4");
// log-servers
Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS));
ASSERT_NO_THROW(subnet_->addOption(option_log_servers, false, "dhcp4"));
// cookie-servers
Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS));
ASSERT_NO_THROW(subnet_->addOption(option_cookie_servers, false, "dhcp4"));
/// @brief checks that the response matches request
/// @param q query (client's message)
/// @param a answer (server's message)
void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
EXPECT_EQ(q->getHops(), a->getHops());
EXPECT_EQ(q->getIface(), a->getIface());
EXPECT_EQ(q->getIndex(), a->getIndex());
EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
// When processing an incoming packet the remote address
// is copied as a src address, and the source address is
// copied as a remote address to the response.
EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr());
EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr());
// Check that bare minimum of required options are there.
// We don't check options requested by a client. Those
// are checked elsewhere.
// Check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "");
/// @brief Check that requested options are present.
/// @param pkt packet to be checked.
void optionsCheck(const Pkt4Ptr& pkt) {
// Check that the requested and configured options are returned
// in the ACK message.
<< "domain-name not present in the response";
<< "dns-servers not present in the response";
<< "log-servers not present in the response";
<< "cookie-servers not present in the response";
// Check that the requested but not configured options are not
// returned in the ACK message.
<< "domain-name present in the response but it is"
<< " expected not to be present";
/// @brief generates client-id option
/// Generate client-id option of specified length
/// Ids with different lengths are sufficent to generate
/// unique ids. If more fine grained control is required,
/// tests generate client-ids on their own.
/// Sets client_id_ field.
/// @param size size of the client-id to be generated
OptionPtr generateClientId(size_t size = 4) {
OptionBuffer clnt_id(size);
for (int i = 0; i < size; i++) {
clnt_id[i] = 100 + i;
client_id_ = ClientIdPtr(new ClientId(clnt_id));
return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
clnt_id.begin() + size)));
/// @brief generate hardware address
/// @param size size of the generated MAC address
/// @param pointer to Hardware Address object
HWAddrPtr generateHWAddr(size_t size = 6) {
const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h)
OptionBuffer mac(size);
for (int i = 0; i < size; ++i) {
mac[i] = 50 + i;
return (HWAddrPtr(new HWAddr(mac, hw_type)));
/// Check that address was returned from proper range, that its lease
/// lifetime is correct, that T1 and T2 are returned properly
/// @param rsp response to be checked
/// @param subnet subnet that should be used to verify assigned address
/// and options
/// @param t1_mandatory is T1 mandatory?
/// @param t2_mandatory is T2 mandatory?
void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
bool t1_mandatory = false,
bool t2_mandatory = false) {
// Technically inPool implies inRange, but let's be on the safe
// side and check both.
EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, rsp->getYiaddr()));
// Check lease time
OptionPtr opt = rsp->getOption(DHO_DHCP_LEASE_TIME);
if (!opt) {
ADD_FAILURE() << "Lease time option missing in response";
} else {
EXPECT_EQ(opt->getUint32(), subnet->getValid());
// Check T1 timer
opt = rsp->getOption(DHO_DHCP_RENEWAL_TIME);
if (opt) {
EXPECT_EQ(opt->getUint32(), subnet->getT1());
} else {
if (t1_mandatory) {
ADD_FAILURE() << "Required T1 option missing";
// Check T2 timer
opt = rsp->getOption(DHO_DHCP_REBINDING_TIME);
if (opt) {
EXPECT_EQ(opt->getUint32(), subnet->getT2());
} else {
if (t2_mandatory) {
ADD_FAILURE() << "Required T2 option missing";
/// @brief Basic checks for generated response (message type and trans-id).
/// @param rsp response packet to be validated
/// @param expected_message_type expected message type
/// @param expected_transid expected transaction-id
void checkResponse(const Pkt4Ptr& rsp, uint8_t expected_message_type,
uint32_t expected_transid) {
EXPECT_EQ(expected_message_type, rsp->getType());
EXPECT_EQ(expected_transid, rsp->getTransid());
/// @brief Checks if the lease sent to client is present in the database
/// @param rsp response packet to be validated
/// @param client_id expected client-identifier (or NULL)
/// @param HWAddr expected hardware address (not used now)
/// @param expected_addr expected address
Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
const HWAddrPtr&, const IOAddress& expected_addr) {
ClientIdPtr id;
if (client_id) {
OptionBuffer data = client_id->getData();
id.reset(new ClientId(data));
Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
if (!lease) {
cout << "Lease for " << expected_addr.toText()
<< " not found in the database backend.";
return (Lease4Ptr());
EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText());
EXPECT_EQ(expected_addr.toText(), lease->addr_.toText());
if (client_id) {
EXPECT_TRUE(*lease->client_id_ == *id);
EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
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 checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) {
// Check that server included its server-id
OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER);
EXPECT_EQ(opt->getType(), expected_srvid->getType() );
EXPECT_EQ(opt->len(), expected_srvid->len() );
EXPECT_TRUE(opt->getData() == expected_srvid->getData());
/// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id
/// @param rsp response packet to be validated
/// @param expected_clientid expected value of client-id
void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) {
// check that server included our own client-id
OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
EXPECT_EQ(expected_clientid->getType(), opt->getType());
EXPECT_EQ(expected_clientid->len(), opt->len());
EXPECT_TRUE(expected_clientid->getData() == opt->getData());
/// @brief Tests if Discover or Request message is processed correctly
/// @param msg_type DHCPDISCOVER or DHCPREQUEST
void testDiscoverRequest(const uint8_t msg_type) {
// Create an instance of the tested class.
boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
// Initialize the source HW address.
vector<uint8_t> mac(6);
for (int i = 0; i < 6; ++i) {
mac[i] = i * 10;
// Initialized the destination HW address.
vector<uint8_t> dst_mac(6);
for (int i = 0; i < 6; ++i) {
dst_mac[i] = i * 20;
// Create a DHCP message. It will be used to simulate the
// incoming message.
boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234));
// Create a response message. It will hold a reponse packet.
// Initially, set it to NULL.
boost::shared_ptr<Pkt4> rsp;
// Set the name of the interface on which packet is received.
// Set the interface index. It is just a dummy value and will
// not be interpreted.
// Set the target HW address. This value is normally used to
// construct the data link layer header.
req->setRemoteHWAddr(1, 6, dst_mac);
// Set the HW address. This value is set on DHCP level (in chaddr).
req->setHWAddr(1, 6, mac);
// Set local HW address. It is used to construct the data link layer
// header.
req->setLocalHWAddr(1, 6, mac);
// Set target IP address.
// Set relay address.
// 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.
if (msg_type == DHCPDISCOVER) {
rsp = srv->processDiscover(req);
// Should return OFFER
EXPECT_EQ(DHCPOFFER, rsp->getType());
} else {
rsp = srv->processRequest(req);
// Should return ACK
EXPECT_EQ(DHCPACK, rsp->getType());
messageCheck(req, rsp);
// We did not request any options so these should not be present
// in the RSP.
// Repeat the test but request some options.
// Add 'Parameter Request List' option.
if (msg_type == DHCPDISCOVER) {
rsp = srv->processDiscover(req);
// Should return non-NULL packet.
EXPECT_EQ(DHCPOFFER, rsp->getType());
} else {
rsp = srv->processRequest(req);
// Should return non-NULL packet.
EXPECT_EQ(DHCPACK, rsp->getType());
// Check that the requested options are returned.
/// @brief This function cleans up after the test.
virtual void TearDown() {
// Let's clean up if there is such a file.
// Close all open sockets.
// 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();
/// @brief A subnet used in most tests
Subnet4Ptr subnet_;
/// @brief A pool used in most tests
Pool4Ptr pool_;
/// @brief A client-id used in most tests
ClientIdPtr client_id_;
int rcode_;
ConstElementPtr comment_;
// Name of a valid network interface
string valid_iface_;
namespace isc {
namespace dhcp {
namespace test {
// Sanity check. Verifies that both Dhcpv4Srv and its derived
// class NakedDhcpv4Srv can be instantiated and destroyed.
......@@ -1620,6 +1138,48 @@ TEST_F(Dhcpv4SrvTest, ServerID) {
EXPECT_EQ(srvid_text, text);
// Checks if callouts installed on pkt4_receive are indeed called and the
// all necessary parameters are passed.
// Note that the test name does not follow test naming convention,
// but the proper hook name is "buffer4_receive".
TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
NakedDhcpv4Srv srv(0);
// Let's create a relayed DISCOVER. This particular relayed DISCOVER has
// added option 82 (relay agent info) with 3 suboptions. The server
// is supposed to echo it back in its response.
Pkt4Ptr dis;
ASSERT_NO_THROW(dis = captureRelayedDiscover());
// Simulate that we have received that traffic
// Server will now process to run its normal loop, but instead of calling
// IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered buffer4_receive callback.;
// Check that the server did send a reposonse
ASSERT_EQ(1, srv.fake_sent_.size());
// Make sure that we received a response
Pkt4Ptr offer = srv.fake_sent_.front();
// Get Relay Agent Info from query...
OptionPtr rai_query = dis->getOption(DHO_DHCP_AGENT_OPTIONS);
// Get Relay Agent Info from response...
OptionPtr rai_response = offer->getOption(DHO_DHCP_AGENT_OPTIONS);
/// @todo Implement tests for subnetSelect See tests in
/// selectSubnetAddr, selectSubnetIface, selectSubnetRelayLinkaddr,
/// selectSubnetRelayInterfaceId. Note that the concept of interface-id is not
......@@ -2213,7 +1773,7 @@ vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
// but the proper hook name is "buffer4_receive".