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

[3194] Merge branch 'master' into trac3194_1

parents 7f26bb20 2e283ec7
698. [bug] muks
A bug was fixed in the interaction between b10-init and b10-msgq
that caused BIND 10 failures after repeated start/stop of
components.
(Trac #3094, git ed672a898d28d6249ff0c96df12384b0aee403c8
697. [func] tmark
Implements "user_check" hooks shared library which supports subnet
selection based upon the contents of a list of known DHCP lease users
(i.e. clients). Adds the following subdirectories to the bind10 src
directory for maintaining hooks shared libraries:
-bind10/src/hooks - base directory for hooks shared libraries
-bind10/src/hooks/dhcp - base directory for all hooks libs pertaining
to DHCP(Kea)
-bind10/src/hooks/dhcp/user_check - directory containing the user_check
hooks library
(Trac #3186, git f36aab92c85498f8511fbbe19fad5e3f787aef68)
696. [func] tomek
b10-dhcp4: It is now possible to specify value of siaddr field
in DHCPv4 responses. It is used to point out to the next
server in the boot process (that typically is TFTP server).
(Trac #3191, git 541922b5300904a5de2eaeddc3666fc4b654ffba)
695. [func] tomek
b10-dhcp6 is now able to listen on global IPv6 unicast addresses.
(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
694. [bug] tomek
b10-dhcp6 now handles exceptions better when processing initial
configuration. In particular, errors with socket binding do not
prevent b10-dhcp6 from establishing configuration session anymore.
(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
693. [bug] tomek
b10-dhcp6 now handles IPv6 interface enabling correctly.
(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
692. [bug] marcin
b10-dhcp4: Fix a bug whereby the Parameter Request List was not parsed
by the server and requested DHCPv4 options were not returned to the
client. Options are not sent back to the client if server failed to
assign a lease.
(Trac #3200, git 50d91e4c069c6de13680bfaaee3c56b68d6e4ab1)
691. [bug] marcin
libdhcp++: Created definitions for standard DHCPv4 options:
tftp-server-name (66) and boot-file-name (67). Also, fixed definition
......
......@@ -1288,6 +1288,10 @@ AC_CONFIG_FILES([Makefile
src/bin/usermgr/Makefile
src/bin/usermgr/tests/Makefile
src/bin/tests/Makefile
src/hooks/Makefile
src/hooks/dhcp/Makefile
src/hooks/dhcp/user_chk/Makefile
src/hooks/dhcp/user_chk/tests/Makefile
src/lib/Makefile
src/lib/asiolink/Makefile
src/lib/asiolink/tests/Makefile
......@@ -1468,6 +1472,7 @@ AC_OUTPUT([doc/version.ent
src/bin/d2/spec_config.h.pre
src/bin/d2/tests/test_data_files_config.h
src/bin/tests/process_rename_test.py
src/hooks/dhcp/user_chk/tests/test_data_files_config.h
src/lib/config/tests/data_def_unittests_config.h
src/lib/dhcpsrv/tests/test_libraries.h
src/lib/python/isc/config/tests/config_test
......
......@@ -4390,6 +4390,30 @@ Dhcp4/subnet4 [] list (default)
</para>
</section>
<section id="dhcp4-next-server">
<title>Next server (siaddr)</title>
<para>In some cases, clients want to obtain configuration from the TFTP server.
Although there is a dedicated option for it, some devices may use siaddr field
in the DHCPv4 packet for that purpose. That specific field can be configured
using next-server directive. It is possible to define it in global scope or
for a given subnet only. If both are defined, subnet value takes precedence.
The value in subnet can be set to 0.0.0.0, which means that next-server should
not be sent. It may also be set to empty string, which means the same as if
it was not defined at all - use global value.
</para>
<screen>
&gt; <userinput>config add Dhcp4/next-server</userinput>
&gt; <userinput>config set Dhcp4/next-server "192.0.2.123"</userinput>
&gt; <userinput>config commit</userinput>
<userinput></userinput>
&gt; <userinput>config add Dhcp4/subnet[0]/next-server</userinput>
&gt; <userinput>config set Dhcp4/subnet[0]/next-server "192.0.2.234"</userinput>
&gt; <userinput>config commit</userinput>
</screen>
</section>
<section id="dhcp4-std">
<title>Supported Standards</title>
<para>The following standards and draft standards are currently
......@@ -4669,6 +4693,43 @@ Dhcp6/subnet6/ list
</para>
</section>
<section id="dhcp6-unicast">
<title>Unicast traffic support</title>
<para>
When DHCPv6 server starts up, by default it listens to the DHCP traffic
sent to multicast address ff02::1:2 on each interface that it is
configured to listen on (see <xref linkend="dhcp6-interface-selection"/>).
In some cases it is useful to configure a server to handle incoming
traffic sent to the global unicast addresses as well. The most common
reason for that is to have relays send their traffic to the server
directly. To configure server to listen on specific unicast address, a
notation to specify interfaces has been extended. Interface name can be
optionally followed by a slash, followed by global unicast address that
server should listen on. That will be done in addition to normal
link-local binding + listening on ff02::1:2 address. The sample commands
listed below show how to listen on 2001:db8::1 (a global address)
configured on the eth1 interface.
</para>
<para>
<screen>
&gt; <userinput>config set Dhcp6/interfaces[0] eth1/2001:db8::1</userinput>
&gt; <userinput>config commit</userinput></screen>
When configuration gets committed, the server will start to listen on
eth1 on link-local address, mutlicast group (ff02::1:2) and 2001:db8::1.
</para>
<para>
It is possible to mix interface names, wildcards and interface name/addresses
on the Dhcp6/interface list. It is not possible to specify more than one
unicast address on a given interface.
</para>
<para>
Care should be taken to specify proper unicast addresses. The server will
attempt to bind to those addresses specified, without any additional checks.
That approach is selected on purpose, so in the software can be used to
communicate over uncommon addresses if the administrator desires so.
</para>
</section>
<section>
<title>Subnet and Address Pool</title>
<para>
......
SUBDIRS = lib bin
# @todo hooks lib could be a configurable switch
SUBDIRS += hooks
EXTRA_DIST = \
cppcheck-suppress.lst \
......
......@@ -255,7 +255,7 @@ Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
if (nsec->getRdataCount() == 0) {
isc_throw(BadNSEC, "NSEC for WILDCARD_NXRRSET is empty");
}
ConstZoneFinderContextPtr fcontext =
finder.find(*qname_, RRType::NSEC(),
dnssec_opt_ | ZoneFinder::NO_WILDCARD);
......
......@@ -337,6 +337,7 @@ class Init:
self.__propagate_component_config(new_config['components'])
return isc.config.ccsession.create_answer(0)
except Exception as e:
logger.error(BIND10_RECONFIGURE_ERROR, e)
return isc.config.ccsession.create_answer(1, str(e))
def get_processes(self):
......@@ -597,6 +598,13 @@ class Init:
process, the log_starting/log_started methods are not used.
"""
logger.info(BIND10_STARTING_CC)
# Unsubscribe from the other CC session first, because we only
# monitor one and msgq expects all data sent to us to be read,
# or it will close its side of the socket.
if self.cc_session is not None:
self.cc_session.group_unsubscribe("Init")
self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
self.config_handler,
self.command_handler,
......@@ -764,9 +772,14 @@ class Init:
it might want to choose if it is for this one).
"""
logger.info(BIND10_STOP_PROCESS, process)
self.cc_session.group_sendmsg(isc.config.ccsession.
create_command('shutdown', {'pid': pid}),
recipient, recipient)
try:
self.cc_session.group_sendmsg(isc.config.ccsession.
create_command('shutdown',
{'pid': pid}),
recipient, recipient)
except:
logger.error(BIND10_COMPONENT_SHUTDOWN_ERROR, process)
raise
def component_shutdown(self, exitcode=0):
"""
......
......@@ -325,3 +325,10 @@ the configuration manager to start up. The total length of time Init
will wait for the configuration manager before reporting an error is
set with the command line --wait switch, which has a default value of
ten seconds.
% BIND10_RECONFIGURE_ERROR Error applying new config: %1
A new configuration was received, but there was an error doing the
re-configuration.
% BIND10_COMPONENT_SHUTDOWN_ERROR An error occured stopping component %1
An attempt to gracefully shutdown a component failed.
......@@ -199,7 +199,8 @@ protected:
(config_id.compare("rebind-timer") == 0)) {
parser = new Uint32Parser(config_id, uint32_values_);
} else if ((config_id.compare("subnet") == 0) ||
(config_id.compare("interface") == 0)) {
(config_id.compare("interface") == 0) ||
(config_id.compare("next-server") == 0)) {
parser = new StringParser(config_id, string_values_);
} else if (config_id.compare("pool") == 0) {
parser = new Pool4Parser(config_id, pools_);
......@@ -264,14 +265,34 @@ protected:
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> valid = getParam("valid-lifetime");
/// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
tmp << addr.toText() << "/" << (int)len
<< " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid));
subnet_ = subnet4;
// Try global value first
try {
string next_server = globalContext()->string_values_->getParam("next-server");
if (!next_server.empty()) {
subnet4->setSiaddr(IOAddress(next_server));
}
} catch (const DhcpConfigError&) {
// Don't care. next_server is optional. We can live without it
}
// Try subnet specific value if it's available
try {
string next_server = string_values_->getParam("next-server");
if (!next_server.empty()) {
subnet4->setSiaddr(IOAddress(next_server));
}
} catch (const DhcpConfigError&) {
// Don't care. next_server is optional. We can live without it
}
}
};
......@@ -366,7 +387,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
} else if (config_id.compare("option-def") == 0) {
parser = new OptionDefListParser(config_id,
globalContext()->option_defs_);
} else if (config_id.compare("version") == 0) {
} else if ((config_id.compare("version") == 0) ||
(config_id.compare("next-server") == 0)) {
parser = new StringParser(config_id,
globalContext()->string_values_);
} else if (config_id.compare("lease-database") == 0) {
......
......@@ -16,7 +16,7 @@
"item_default": ""
}
},
{ "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
......@@ -48,6 +48,12 @@
"item_default": 4000
},
{ "item_name": "next-server",
"item_type": "string",
"item_optional": true,
"item_default": ""
},
{ "item_name": "option-def",
"item_type": "list",
"item_optional": false,
......@@ -218,6 +224,13 @@
"item_optional": false,
"item_default": 7200
},
{ "item_name": "next-server",
"item_type": "string",
"item_optional": true,
"item_default": "0.0.0.0"
},
{ "item_name": "pool",
"item_type": "list",
"item_optional": false,
......@@ -290,7 +303,7 @@
{
"command_name": "libreload",
"command_description": "Reloads the current hooks libraries.",
"command_description": "Reloads the current hooks libraries.",
"command_args": []
}
......
......@@ -118,7 +118,7 @@ a lease. It is up to the client to choose one server out of othe advertised
and continue allocation with that server. This is a normal behavior and
indicates successful operation.
% DHCP4_LEASE_ADVERT_FAIL failed to advertise a lease for client client-id %1, hwaddr %2
% DHCP4_LEASE_ADVERT_FAIL failed to advertise a lease for client client-id %1, hwaddr %2, client sent yiaddr %3
This message indicates that the server has failed to offer a lease to
the specified client after receiving a DISCOVER message from it. There are
many possible reasons for such a failure.
......@@ -128,7 +128,7 @@ This debug message indicates that the server successfully granted a lease
in response to client's REQUEST message. This is a normal behavior and
indicates successful operation.
% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2
% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2, client sent yiaddr %3
This message indicates that the server failed to grant a lease to the
specified client after receiving a REQUEST message from it. There are many
possible reasons for such a failure. Additional messages will indicate the
......
......@@ -758,6 +758,13 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
return;
}
// Set up siaddr. Perhaps assignLease is not the best place to call this
// as siaddr has nothing to do with a lease, but otherwise we would have
// to select subnet twice (performance hit) or update too many functions
// at once.
// @todo: move subnet selection to a common code
answer->setSiaddr(subnet->getSiaddr());
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
.arg(subnet->toText());
......@@ -811,13 +818,6 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
opt->setUint32(lease->valid_lft_);
answer->addOption(opt);
// Router (type 3)
Subnet::OptionDescriptor opt_routers =
subnet->getOptionDescriptor("dhcp4", DHO_ROUTERS);
if (opt_routers.option) {
answer->addOption(opt_routers.option);
}
// Subnet mask (type 1)
answer->addOption(getNetmaskOption(subnet));
......@@ -914,15 +914,18 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
copyDefaultFields(discover, offer);
appendDefaultOptions(offer, DHCPOFFER);
appendRequestedOptions(discover, offer);
appendRequestedVendorOptions(discover, offer);
assignLease(discover, offer);
// There are a few basic options that we always want to
// include in the response. If client did not request
// them we append them for him.
appendBasicOptions(discover, offer);
// Adding any other options makes sense only when we got the lease.
if (offer->getYiaddr() != IOAddress("0.0.0.0")) {
appendRequestedOptions(discover, offer);
// There are a few basic options that we always want to
// include in the response. If client did not request
// them we append them for him.
appendBasicOptions(discover, offer);
}
return (offer);
}
......@@ -938,7 +941,6 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
copyDefaultFields(request, ack);
appendDefaultOptions(ack, DHCPACK);
appendRequestedOptions(request, ack);
appendRequestedVendorOptions(request, ack);
// Note that we treat REQUEST message uniformly, regardless if this is a
......@@ -946,10 +948,14 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
// or even rebinding.
assignLease(request, ack);
// There are a few basic options that we always want to
// include in the response. If client did not request
// them we append them for him.
appendBasicOptions(request, ack);
// Adding any other options makes sense only when we got the lease.
if (ack->getYiaddr() != IOAddress("0.0.0.0")) {
appendRequestedOptions(request, ack);
// There are a few basic options that we always want to
// include in the response. If client did not request
// them we append them for him.
appendBasicOptions(request, ack);
}
return (ack);
}
......
......@@ -412,6 +412,151 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
EXPECT_EQ(4000, subnet->getValid());
}
// Checks if the next-server defined as global parameter is taken into
// consideration.
TEST_F(Dhcp4ParserTest, nextServerGlobal) {
ConstElementPtr status;
string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"next-server\": \"1.2.3.4\", "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
// check if returned status is OK
checkResult(status, 0);
// Now check if the configuration was indeed handled and we have
// expected pool configured.
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
ASSERT_TRUE(subnet);
EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
}
// Checks if the next-server defined as subnet parameter is taken into
// consideration.
TEST_F(Dhcp4ParserTest, nextServerSubnet) {
ConstElementPtr status;
string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"next-server\": \"1.2.3.4\", "
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
// check if returned status is OK
checkResult(status, 0);
// Now check if the configuration was indeed handled and we have
// expected pool configured.
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
ASSERT_TRUE(subnet);
EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
}
// Test checks several negative scenarios for next-server configuration: bogus
// address, IPv6 adddress and empty string.
TEST_F(Dhcp4ParserTest, nextServerNegative) {
ConstElementPtr status;
// Config with junk instead of next-server address
string config_bogus1 = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"next-server\": \"a.b.c.d\", "
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
// Config with IPv6 next server address
string config_bogus2 = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"next-server\": \"2001:db8::1\", "
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
// Config with empty next server address
string config_bogus3 = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"next-server\": \"\", "
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json1 = Element::fromJSON(config_bogus1);
ElementPtr json2 = Element::fromJSON(config_bogus2);
ElementPtr json3 = Element::fromJSON(config_bogus3);
// check if returned status is always a failure
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json1));
checkResult(status, 1);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json2));
checkResult(status, 1);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json3));
checkResult(status, 0);
}
// Checks if the next-server defined as global value is overridden by subnet
// specific value.
TEST_F(Dhcp4ParserTest, nextServerOverride) {
ConstElementPtr status;
string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"next-server\": \"192.0.0.1\", "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"next-server\": \"1.2.3.4\", "
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
// check if returned status is OK
checkResult(status, 0);
// Now check if the configuration was indeed handled and we have
// expected pool configured.
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
ASSERT_TRUE(subnet);
EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
}
// This test checks if it is possible to override global values
// on a per subnet basis.
TEST_F(Dhcp4ParserTest, subnetLocal) {
......
......@@ -1377,6 +1377,136 @@ TEST_F(Dhcpv4SrvTest, unpackOptions) {
EXPECT_EQ(0x0, option_bar->getValue());
}
// Checks whether the server uses default (0.0.0.0) siaddr value, unless
// explicitly specified
TEST_F(Dhcpv4SrvTest, siaddrDefault) {
boost::scoped_ptr<NakedDhcpv4Srv> srv;
ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
IOAddress hint("192.0.2.107");
Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
dis->setRemoteAddr(IOAddress("192.0.2.1"));
OptionPtr clientid = generateClientId();
dis->addOption(clientid);
dis->setYiaddr(hint);
// Pass it to the server and get an offer
Pkt4Ptr offer = srv->processDiscover(dis);
ASSERT_TRUE(offer);
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Verify that it is 0.0.0.0
EXPECT_EQ("0.0.0.0", offer->getSiaddr().toText());
}
// Checks whether the server uses specified siaddr value
TEST_F(Dhcpv4SrvTest, siaddr) {
boost::scoped_ptr<NakedDhcpv4Srv> srv;
ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
subnet_->setSiaddr(IOAddress("192.0.2.123"));
Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
dis->setRemoteAddr(IOAddress("192.0.2.1"));
OptionPtr clientid = generateClientId();
dis->addOption(clientid);
// Pass it to the server and get an offer
Pkt4Ptr offer = srv->processDiscover(dis);
ASSERT_TRUE(offer);
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Verify that its value is proper
EXPECT_EQ("192.0.2.123", offer->getSiaddr().toText());
}
// Checks if the next-server defined as global value is overridden by subnet
// specific value and returned in server messages. There's also similar test for
// checking parser only configuration, see Dhcp4ParserTest.nextServerOverride in
// config_parser_unittest.cc.
TEST_F(Dhcpv4SrvTest, nextServerOverride) {