Commit 3185d229 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[master] Merge branch 'trac2949' (inf-request in dhcpv6)

parents fc922d97 3739b530
......@@ -10,6 +10,7 @@ nobase_dist_doc_DATA += examples/kea6/simple.json
nobase_dist_doc_DATA += examples/kea6/several-subnets.json
nobase_dist_doc_DATA += examples/kea6/multiple-options.json
nobase_dist_doc_DATA += examples/kea6/advanced.json
nobase_dist_doc_DATA += examples/kea6/stateless.json
nobase_dist_doc_DATA += examples/ddns/sample1.json
nobase_dist_doc_DATA += examples/ddns/template.json
......
# A very simply stateless configuration that provides information about DNS
# servers to all clients, regardless of their point of attachment.
#
# It is also possible to specify options on a per subnet basis
# in the same way as in stateful mode.
#
{
"Dhcp6": {
"interfaces": [ "ethX" ],
# This is the list of options that will be granted to all clients that ask.
"option-data": [ {
"name": "dns-servers",
"data": "2001:db8::1, 2001:db8::2"
} ],
# Kea 0.9.1 requires lease-database to be specified, even it is not used.
# In stateless mode, only options are granted, not addresses or prefixes, so
# there will be no leases (unless stateless and stateful mode is used together).
"lease-database": { "type": "memfile" }
}
}
......@@ -1743,6 +1743,49 @@ should include options from the isc option space:
</section>
<section id="stateless-dhcp6">
<title>Stateless DHCPv6 (Information-Request Message)</title>
<para>Typically DHCPv6 is used to assign both addresses and options. These
assignments (leases) have state that changes over time, hence
their name, stateful. DHCPv6 also supports a stateless mode,
where clients request configuration options only. This mode is
considered lightweight from the server perspective, as it does not require
any state tracking; hence its name.</para>
<para>The Kea server supports stateless mode. Clients can send
Information-Request messages and the server will send back
answers with the requested options (providing the options are
available in the server configuration). The server will attempt to
use per-subnet options first. If that fails - for whatever reason - it
will then try to provide options defined in the global scope.</para>
<para>Stateless and stateful mode can be used together. No special
configuration directives are required to handle this. Simply use the
configuration for stateful clients and the stateless clients will get
just options they requested.</para>
<para>This usage of global options allows for an interesting case.
It is possible to run a server that provides just options and no
addresses or prefixes. If the options have the same value in each
subnet, the configuration can define required options in the global
scope and skip subnet definitions altogether. Here's a simple example of
such a configuration:
<screen>
"Dhcp6": {
"interfaces": [ "ethX" ],
<userinput>"option-data": [ {
"name": "dns-servers",
"data": "2001:db8::1, 2001:db8::2"
} ]</userinput>,
"lease-database": { "type": "memfile" }
}
</screen>
This very simple configuration will provide DNS server information
to all clients in the network, regardless of their location. Note the
specification of the memfile lease database: this is required since,
as of version 0.9.1, Kea requires a lease database to be specified
even if it is not used.</para>
</section>
<section id="dhcp6-relay-override">
<title>Using specific relay agent for a subnet</title>
<para>
......@@ -2051,8 +2094,7 @@ should include options from the isc option space:
<listitem>
<simpara>
Duplication report (DECLINE), stateless configuration
(INFORMATION-REQUEST) and client reconfiguration (RECONFIGURE) are
Duplication report (DECLINE) and client reconfiguration (RECONFIGURE) are
not yet supported.
</simpara>
</listitem>
......
......@@ -701,7 +701,7 @@ Dhcpv6Srv::generateServerID() {
}
void
Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Dhcpv6Srv::copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// Add client-id.
OptionPtr clientid = question->getOption(D6O_CLIENTID);
if (clientid) {
......@@ -724,29 +724,43 @@ Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer) {
void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// Get the configured subnet suitable for the incoming packet.
Subnet6Ptr subnet = selectSubnet(question);
// Leave if there is no subnet matching the incoming packet.
// There is no need to log the error message here because
// it will be logged in the assignLease() when it fails to
// pick the suitable subnet. We don't want to duplicate
// error messages in such case.
if (!subnet) {
return;
}
// Client requests some options using ORO option. Try to
// get this option from client's message.
boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(question->getOption(D6O_ORO));
// Option ORO not found. Don't do anything then.
boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
(question->getOption(D6O_ORO));
// Option ORO not found? We're done here then.
if (!option_oro) {
return;
}
// Get global option definitions (i.e. options defined to apply to all,
// unless overwritten on a subnet or host level)
ConstCfgOptionPtr global_opts = CfgMgr::instance().getCurrentCfg()->
getCfgOption();
// Get the configured subnet suitable for the incoming packet.
// It may be NULL (if server is misconfigured or the client was rejected
// using client classes).
Subnet6Ptr subnet = selectSubnet(question);
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
BOOST_FOREACH(uint16_t opt, requested_opts) {
OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", opt);
// If we found a subnet for this client, try subnet first.
if (subnet) {
OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", opt);
if (desc.option_) {
// Attempt to assign an option from subnet first.
answer->addOption(desc.option_);
continue;
}
}
// If subnet specific option is not there, try global.
OptionDescriptor desc = global_opts->get("dhcp6", opt);
if (desc.option_) {
answer->addOption(desc.option_);
}
......@@ -2272,7 +2286,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
copyDefaultOptions(solicit, advertise);
copyClientOptions(solicit, advertise);
appendDefaultOptions(solicit, advertise);
appendRequestedOptions(solicit, advertise);
appendRequestedVendorOptions(solicit, advertise);
......@@ -2295,7 +2309,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
copyDefaultOptions(request, reply);
copyClientOptions(request, reply);
appendDefaultOptions(request, reply);
appendRequestedOptions(request, reply);
appendRequestedVendorOptions(request, reply);
......@@ -2315,7 +2329,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
copyDefaultOptions(renew, reply);
copyClientOptions(renew, reply);
appendDefaultOptions(renew, reply);
appendRequestedOptions(renew, reply);
......@@ -2332,7 +2346,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
copyDefaultOptions(rebind, reply);
copyClientOptions(rebind, reply);
appendDefaultOptions(rebind, reply);
appendRequestedOptions(rebind, reply);
......@@ -2356,7 +2370,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
// The server sends Reply message in response to Confirm.
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, confirm->getTransid()));
// Make sure that the necessary options are included.
copyDefaultOptions(confirm, reply);
copyClientOptions(confirm, reply);
appendDefaultOptions(confirm, reply);
// Indicates if at least one address has been verified. If no addresses
// are verified it means that the client has sent no IA_NA options
......@@ -2430,7 +2444,7 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
copyDefaultOptions(release, reply);
copyClientOptions(release, reply);
appendDefaultOptions(release, reply);
releaseLeases(release, reply);
......@@ -2445,14 +2459,27 @@ Pkt6Ptr
Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) {
/// @todo: Implement this
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, decline->getTransid()));
return reply;
return (reply);
}
Pkt6Ptr
Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
/// @todo: Implement this
// Create a Reply packet, with the same trans-id as the client's.
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, infRequest->getTransid()));
return reply;
// Copy client options (client-id, also relay information if present)
copyClientOptions(infRequest, reply);
// Append default options, i.e. options that the server is supposed
// to put in all messages it sends (server-id for now, but possibly other
// options once we start supporting authentication)
appendDefaultOptions(infRequest, reply);
// Try to assign options that were requested by the client.
appendRequestedOptions(infRequest, reply);
return (reply);
}
size_t
......
......@@ -394,10 +394,11 @@ protected:
/// Copies options that must appear in any server response (ADVERTISE, REPLY)
/// to client's messages (SOLICIT, REQUEST, RENEW, REBIND, DECLINE, RELEASE).
/// One notable example is client-id. Other options may be copied as required.
/// Relay information details are also copied here.
///
/// @param question client's message (options will be copied from here)
/// @param answer server's message (options will be copied here)
void copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
void copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
/// @brief Appends default options to server's answer.
///
......
......@@ -25,7 +25,7 @@ AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
CLEANFILES = $(builddir)/logger_lockfile
CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
CLEANFILES += *.json *.log
......@@ -84,6 +84,7 @@ dhcp6_unittests_SOURCES += rebind_unittest.cc
dhcp6_unittests_SOURCES += sarr_unittest.cc
dhcp6_unittests_SOURCES += config_parser_unittest.cc
dhcp6_unittests_SOURCES += confirm_unittest.cc
dhcp6_unittests_SOURCES += infrequest_unittest.cc
dhcp6_unittests_SOURCES += dhcp6_message_test.cc dhcp6_message_test.h
if CONFIG_BACKEND_BUNDY
......
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2015 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
......@@ -16,6 +16,7 @@
#include <dhcp/option_custom.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
#include <dhcpsrv/lease.h>
#include <dhcp6/tests/dhcp6_client.h>
......@@ -40,6 +41,8 @@ Dhcp6Client::Dhcp6Client() :
use_na_(false),
use_pd_(false),
use_relay_(false),
use_oro_(false),
use_client_id_(true),
prefix_hint_() {
}
......@@ -52,7 +55,10 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
srv_(srv),
use_na_(false),
use_pd_(false),
use_relay_(false) {
use_relay_(false),
use_oro_(false),
use_client_id_(true),
prefix_hint_() {
}
void
......@@ -245,7 +251,17 @@ Dhcp6Client::createLease(const Lease6& lease) {
Pkt6Ptr
Dhcp6Client::createMsg(const uint8_t msg_type) {
Pkt6Ptr msg(new Pkt6(msg_type, curr_transid_++));
msg->addOption(getClientId());
if (use_client_id_) {
msg->addOption(getClientId());
}
if (use_oro_) {
OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
oro->setValues(oro_);
msg->addOption(oro);
};
return (msg);
}
......@@ -295,6 +311,32 @@ Dhcp6Client::doRequest() {
}
}
void
Dhcp6Client::doInfRequest() {
context_.query_ = createMsg(DHCPV6_INFORMATION_REQUEST);
// IA_NA, IA_TA and IA_PD options are not allowed in INF-REQUEST,
// but hey! Let's test it.
if (use_na_) {
// Insert IA_NA option with iaid=1234.
context_.query_->addOption(Option6IAPtr(new Option6IA(D6O_IA_NA,
1234)));
}
// IA-PD is also not allowed. So it may be useful in testing, too.
if (use_pd_) {
// Insert IA_PD option with iaid=5678
Option6IAPtr ia(new Option6IA(D6O_IA_PD, 5678));
if (prefix_hint_) {
ia->addOption(prefix_hint_);
}
context_.query_->addOption(ia);
}
sendMsg(context_.query_);
context_.response_ = receiveOneMsg();
}
void
Dhcp6Client::doRebind() {
Pkt6Ptr query = createMsg(DHCPV6_REBIND);
......
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -22,6 +22,7 @@
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <set>
#include <vector>
namespace isc {
namespace dhcp {
......@@ -217,6 +218,16 @@ public:
/// receiving server's response (if any).
void doConfirm();
/// @brief Performs stateless (inf-request / reply) exchange.
///
/// This function generates Information-request message, sends it
/// to the server and then receives the reply. Contents of the Inf-Request
/// are controlled by use_na_, use_pd_, use_client_id_ and use_oro_
/// fields. This method does not process the response in any specific
/// way, just stores it.
void doInfRequest();
/// @brief Removes the stateful configuration obtained from the server.
///
/// It removes all leases held by the client.
......@@ -365,12 +376,36 @@ public:
relay_link_addr_ = link_addr;
}
/// @brief Controls whether the client should send a client-id or not
/// @param send should the client-id be sent?
void useClientId(bool send) {
use_client_id_ = send;
}
/// @brief Lease configuration obtained by the client.
Configuration config_;
/// @brief Link address of the relay to be used for relayed messages.
asiolink::IOAddress relay_link_addr_;
/// @brief Controls whether the client will send ORO
///
/// The actual content of the ORO is specified in oro_.
/// It is useful to split the actual content and the ORO sending
/// decision, so we could test cases of sending empty ORO.
/// @param send controls whether ORO will be sent or not.
void useORO(bool send) {
use_oro_ = send;
}
/// @brief Instructs client to request specified option in ORO
///
/// @param option_code client will request this option code
void requestOption(uint16_t option_code) {
use_oro_ = true;
oro_.push_back(option_code);
}
private:
/// @brief Applies the new leases for the client.
......@@ -470,8 +505,17 @@ private:
bool use_pd_; ///< Enable prefix delegation.
bool use_relay_; ///< Enable relaying messages to the server.
bool use_oro_; ///< Conth
bool use_client_id_;
/// @brief Pointer to the option holding a prefix hint.
Option6IAPrefixPtr prefix_hint_;
/// @brief List of options to be requested
///
/// Content of this vector will be sent as ORO if use_oro_ is set
/// to true. See @ref sendORO for details.
std::vector<uint16_t> oro_;
};
} // end of namespace isc::dhcp::test
......
......@@ -186,15 +186,10 @@ TEST_F(NakedDhcpv6SrvTest, ReleaseNoSubnet) {
checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding);
}
// Test verifies that the Dhcpv6_srv class can be instantiated. It checks a mode
// without open sockets and with sockets opened on a high port (to not require
// root privileges).
TEST_F(Dhcpv6SrvTest, basic) {
// srv has stubbed interface detection. It will read
// interfaces.txt instead. It will pretend to have detected
// fe80::1234 link-local address on eth0 interface. Obviously
// an attempt to bind this socket will fail.
boost::scoped_ptr<Dhcpv6Srv> srv;
ASSERT_NO_THROW( {
......
// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2015 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
......@@ -16,6 +16,7 @@
#include <dhcp6/tests/dhcp6_test_utils.h>
#include <dhcp6/json_config_parser.h>
#include <config/ccsession.h>
#include <string.h>
using namespace isc::data;
using namespace isc::dhcp;
......@@ -621,5 +622,35 @@ NakedDhcpv6SrvTest::generateIA(uint16_t type, uint32_t iaid, uint32_t t1,
return (ia);
}
bool
Dhcpv6SrvTest::compareOptions(const isc::dhcp::OptionPtr& option1,
const isc::dhcp::OptionPtr& option2) {
if ((!option1 && option2) || (option1 && !option2)) {
return (false);
}
if (!option1 && !option2) {
return (true);
}
// We could start by comparing option codes and option lengths
// here, but it's just a waste of time. These are tests, so they
// don't have to be super performant. The pack+memcmp approach
// verifies all in one go.
isc::util::OutputBuffer buf1(0);
isc::util::OutputBuffer buf2(0);
option1->pack(buf1);
option2->pack(buf2);
if (buf1.getLength() != buf2.getLength()) {
return (false);
}
// memcmp returns 0 when equal.
return (!memcmp(buf1.getData(), buf2.getData(), buf1.getLength()));
}
}; // end of isc::test namespace
}; // end of isc namespace
// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2015 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
......@@ -462,6 +462,22 @@ public:
const isc::asiolink::IOAddress& addr,
const uint8_t prefix_len, const uint32_t iaid);
/// @brief Compare options
///
/// This method compares whether options content is identical. It writes
/// both options to a buffer and then compares the buffers. Comparing
/// two different instances of an option that has identical content
/// will return true.
///
/// It is safe to pass NULL pointers. Two NULL pointers are equal.
/// NULL pointer and non-NULL pointers are obviously non-equal.
///
/// @param option1 pointer to the first option
/// @param option2
/// @return true, if content is identical
bool compareOptions(const isc::dhcp::OptionPtr& option1,
const isc::dhcp::OptionPtr& option2);
/// @brief Performs basic (positive) RENEW test
///
/// See renewBasic and pdRenewBasic tests for detailed explanation.
......
// Copyright (C) 2015 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
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp6/tests/dhcp6_test_utils.h>
#include <dhcp6/tests/dhcp6_client.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_client_fqdn.h>
using namespace isc;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
using namespace isc::test;
namespace {
/// @brief Set of JSON configurations used by the Information-Request unit tests.
///
/// - Configuration 0:
/// - one subnet used on eth0 interface
/// - with address and prefix pools
/// - dns-servers option
/// - Configuation 1:
/// - one subnet used on eth0 interface
/// - no addresses or prefixes
/// - domain-search option
/// - Configuration 2:
/// - one subnet used on eth0 interface
/// - dns-servers option for subnet
/// - sip-servers defined in global scope
/// - Configuration 3:
/// - nis-server, nis-domain specified in global scope
/// - no subnets defined
const char* CONFIGS[] = {
// Configuration 0
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
" \"pd-pools\": ["
" { \"prefix\": \"2001:db8:3::\", "
" \"prefix-len\": 48, "
" \"delegated-len\": 64"
" } ],"
" \"option-data\": [ {"
" \"name\": \"dns-servers\","
" \"data\": \"2001:db8::1, 2001:db8::2\""
" } ],"
" \"subnet\": \"2001:db8::/32\", "
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000 }",
// Configuration 1
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"option-data\": [ {"
" \"name\": \"sip-server-addr\","
" \"data\": \"2001:db8::abcd\""
" } ],"
" \"subnet\": \"2001:db8::/32\", "
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000 }",
// Configuration 2
"{ \"interfaces\": [ \"*\" ],"