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

[master] Merge branch 'trac3747'

parents c817b5d1 a6a4cbf0
......@@ -1950,6 +1950,170 @@ temporarily override a list of interface names and listen on all interfaces.
</screen>
</section>
<section id="dhcp4-match-client-id">
<title>Using Client Identifier and Hardware Address</title>
<para>DHCP server must be able to identify the client (distinguish it from
other clients) from which it receives the message. There are many reasons
why this identification is required and the most important ones are listed
below.
<itemizedlist>
<listitem><simpara>When the client contacts the server to allocate a new
lease, the server must store the client identification information in
the lease database as a search key.</simpara></listitem>
<listitem><simpara>When the client is trying to renew or release the existing
lease, the server must be able to find the existing lease entry in the
database for this client, using the client identification information as a
search key.</simpara></listitem>
<listitem><simpara>Some configurations use static reservations for the IP
addreses and other configuration information. The server's administrator
uses client identification information to create these static assignments.
</simpara></listitem>
<listitem><simpara>In the dual stack networks there is often a need to
correlate the lease information stored in DHCPv4 and DHCPv6 server for
a particular host. Using common identification information by the DHCPv4
and DHCPv6 client allows the network administrator to achieve this
correlation and better administer the network.</simpara></listitem>
</itemizedlist>
</para>
<para>DHCPv4 makes use of two distinct identifiers which are placed
by the client in the queries sent to the server and copied by the server
to its responses to the client: 'chaddr' and 'client identifier'. The
former was introduced as a part of the BOOTP specification and it is also
used by DHCP to carry the hardware address of the interface used to send
the query to the server (MAC address for the Ethernet). The latter is
carried in the Client-identifier option, introduced in the
<ulink url="http://tools.ietf.org/html/rfc2132">RFC 2132</ulink>.
</para>
<para>The <ulink url="http://tools.ietf.org/html/rfc2131">RFC 2131</ulink>
indicates that the server may use both of these identifiers to identify
the client but the 'client identifier', if present, takes precedence
over 'chaddr'. One of the reasons for this is that 'client identifier'
is independent from the hardware used by the client to communicate with
the server. For example, if the client obtained the lease using one
network card and then the network card is moved to another host, the
server will wrongly identify this host is the one which has obtained
the lease. Moreover, the
<ulink url="https://tools.ietf.org/html/rfc4361">RFC 4361</ulink> gives
the recommendation to use DUID
(see <ulink url="https://tools.ietf.org/html/rfc3315">DHCPv6 specification</ulink>)
carried as 'client identifier' when dual stack networks are in use,
to provide consistent identification information of the client, regardless
of the protocol type it is using. Kea adheres to these specifications and
the 'client identifier' by default takes precedence over the value carried
in 'chaddr' field when the server searches, creates, updates or removes
the client's lease.
</para>
<para>When the server receives a DHCPDISCOVER or DHCPREQUEST message from the
client, it will try to find out if the client already has a lease in the
database and will hand out the existing lease rather than allocate
a new one. Each lease in the lease database is associated with the
'client identifier' and/or 'chaddr'. The server will first use the
'client identifer' (if present) to search the lease. If the lease is
found, the server will treat this lease as belonging to the client
even if the current 'chaddr' and the 'chaddr' associated with
the lease do not match. This facilitates the scenario when the network card
on the client system has been replaced and thus the new MAC address
appears in the messages sent by the DHCP client. If the server fails
to find the lease using the 'client identifier' it will perform another lookup
using the 'chaddr'. If this lookup returns no result, the client is
considered as not having a lease and the new lease will be created.
</para>
<para>A common problem reported by network operators is that bogus
client implementations do not use stable client identifiers such as
generating a new 'client identifier' each time the client connects
to the network. Another well known case is when the client changes its
'client identifier' during the multi-stage boot process (PXE). In such
cases, the MAC address of the client's interface remains stable and
using 'chaddr' field to identify the client guarantees that the
particular system is considered to be the same client, even though its
'client identifier' changes.
</para>
<para>To address this problem, Kea includes a configuration option
which enables client identification using 'chaddr' only by instructing
the server to disregard server to "ignore" the 'client identifier' during
lease lookups and allocations for a particular subnet. Consider the following
simplified server configuration:</para>
<screen>
"Dhcp4": {
...
<userinput>"match-client-id": true,</userinput>
...
"subnet4": [
{
"subnet": "192.0.10.0/24",
"pools": [ { "pool": "192.0.2.23-192.0.2.87" } ],
<userinput>"match-client-id": false</userinput>
},
{
"subnet": "10.0.0.0/8",
"pools": [ { "pool": "10.0.0.23-10.0.2.99" } ],
}
]
}
</screen>
<para>The <command>match-client-id</command> is a boolean value which
controls this behavior. The default value of <userinput>true</userinput>
indicates that the server will use the 'client identifier' for lease
lookups and 'chaddr' if the first lookup returns no results. The
<command>false</command> means that the server will only
use the 'chaddr' to search for client's lease. Whether the DHCID for
DNS updates is generated from the 'client identifier' or 'chaddr' is
controlled through the same parameter accordingly.</para>
<para>The <command>match-client-id</command> parameter may appear
both in the global configuration scope and/or under any subnet
declaration. In the example shown above, the effective value of the
<command>match-client-id</command> will be <userinput>false</userinput>
for the subnet 192.0.10.0/24, because the subnet specific setting
of the parameter overrides the global value of the parameter. The
effective value of the <command>match-client-id</command> for the subnet
10.0.0.0/8 will be set to <userinput>true</userinput> because the
subnet declaration lacks this parameter and the global setting is
by default used for this subnet. In fact, the global entry for this
parameter could be omitted in this case, because
<userinput>true</userinput> is the default value.
</para>
<para>It is important to explain what happens when the client obtains
its lease for one setting of the <command>match-client-id</command>
and then renews when the setting has been changed. Let's first consider
the case when the client obtains the lease when the
<command>match-client-id</command> is set to <userinput>true</userinput>.
The server will store the lease information including 'client identifier'
(if supplied) and 'chaddr' in the lease database. When the setting is
changed and the client renews the lease the server will determine that
it should use the 'chaddr' to search for the existing lease. If the
client hasn't changed its MAC address the server should successfully
find the existing lease. The 'client identifier' associated with the
returned lease is ignored and the client is allowed to use this lease.
When the lease is renewed only the 'chaddr' is recorded for this
lease according to the new server setting.
</para>
<para>In the second case the client has the lease with only a 'chaddr'
value recorded. When the setting is changed to
<command>match-client-id</command> set to <userinput>true</userinput>
the server will first try to use the 'client identifier' to find the
existing client's lease. This will return no results because the
'client identifier' was not recorded for this lease. The server will
then use the 'chaddr' and the lease will be found. If the lease appears
to have no 'client identifier' recorded, the server will assume that
this lease belongs to the client and that it was created with the previous
setting of the <command>match-client-id</command>.
However, if the lease contains 'client identifier' which is different
from the 'client identifier' used by the client the lease will be
assumed to belong to another client and the new lease will be
allocated.
</para>
</section>
</section> <!-- end of configuring kea-dhcp4 server section with many subsections -->
<!-- Host reservation is a large topic. There will be many subsections,
......@@ -2255,18 +2419,18 @@ temporarily override a list of interface names and listen on all interfaces.
<para>
An example configuration that disables reservation looks like follows:
<screen>
<screen>
"Dhcp4": {
"subnet4": [
{
{
"subnet": "192.0.2.0/24",
<userinput>"reservation-mode": "disabled"</userinput>,
...
}
}
]
}
</screen>
</para>
</para>
</section>
</section>
......
/upgrade_1.0_to_2.0.sh
/upgrade_2.0_to_3.0.sh
......@@ -74,6 +74,12 @@
"item_default": true
},
{ "item_name": "match-client-id",
"item_type": "boolean",
"item_optional": true,
"item_default": true
},
{ "item_name": "option-def",
"item_type": "list",
"item_optional": false,
......@@ -269,6 +275,12 @@
"item_default": "0.0.0.0"
},
{ "item_name": "match-client-id",
"item_type": "boolean",
"item_optional": true,
"item_default": true
},
{ "item_name": "pool",
"item_type": "list",
"item_optional": false,
......
......@@ -71,6 +71,20 @@ The first argument specifies the client and transaction identification
information. The second argument includes all classes to which the
packet has been assigned.
% DHCP4_CLIENTID_IGNORED_FOR_LEASES %1: not using client identifier for lease allocation for subnet %2
This debug message is issued when the server is processing the DHCPv4 message
for which client identifier will not be used when allocating new lease or
renewing existing lease. The server is explicitly configured to not use
client identifier to lookup existing leases for the client and will not
record client identifier in the lease database. This mode of operation
is useful when clients don't use stable client identifiers, e.g. multi
stage booting. Note that the client identifier may be used for other
operations than lease allocation, e.g. identifying host reservations
for the client using client identifier. The first argument includes the
client and transaction identification information. The second argument
specifies the identifier of the subnet where the client is connected
and for which this mode of operation is configured on the server.
% DHCP4_CLIENT_FQDN_PROCESS %1: processing Client FQDN option
This debug message is issued when the server starts processing the Client
FQDN option sent in the client's query. The argument includes the
......@@ -496,22 +510,13 @@ but no such lease is known to the server. The first argument contains
the client and transaction identification information. The second
argument contains the address which the client is trying to release.
% DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID %1: client tried to release address %2, but this address is leased to different client using client id %3
This warning message indicates that client tried to release an address
that belongs to a different client. This should not happen in normal
circumstances and may indicate a misconfiguration of the client. However,
since the client releasing the address will stop using it anyway, there
is a good chance that the situation will correct itself.
% DHCP4_RELEASE_FAIL_WRONG_HWADDR %1: client tried to release address %2, but this address is leased to different client using HW address %3
This warning message indicates that client tried to release an address
that does belong to it, but the lease information was associated with
a different hardware address. One possible reason for using different
hardware address is that a cloned virtual machine was not updated and
both clones use the same client-id. The first argument includes the
client and the transaction identification information. The second
argument includes the address which release was attempted. The
third argumnet includes the HW address of the lease owner.
% DHCP4_RELEASE_FAIL_WRONG_CLIENT %1: client is trying to release the lease %2 which belongs to a different client
This debug message is issued when a client is trying to release the
lease for the address which is currently used by another client, i.e.
the 'client identifier' or 'chaddr' doesn't match between the client
and the lease. The first argument includes the client and the
transaction identification information. The second argument specifies
the leased address.
% DHCP4_RESPONSE_FQDN_DATA %1: including FQDN option in the server's response: %2
This debug message is issued when the server is adding the Client FQDN
......
......@@ -108,10 +108,22 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
context_->subnet_ = subnet;
// Hardware address.
context_->hwaddr_ = query->getHWAddr();
// Client Identifier
OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
if (opt_clientid) {
context_->clientid_.reset(new ClientId(opt_clientid->getData()));
// Set client identifier if the match-client-id flag is enabled (default).
// If the subnet wasn't found it doesn't matter because we will not be
// able to allocate a lease anyway so this context will not be used.
if (subnet) {
if (subnet->getMatchClientId()) {
OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
if (opt_clientid) {
context_->clientid_.reset(new ClientId(opt_clientid->getData()));
}
} else {
/// @todo When merging with #3806 use different logger.
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENTID_IGNORED_FOR_LEASES)
.arg(query->getLabel())
.arg(subnet->getID());
}
}
// Check for static reservations.
alloc_engine->findReservation(*context_);
......@@ -1055,29 +1067,21 @@ Dhcpv4Srv::createNameChangeRequests(const Lease4Ptr& lease,
// We may also decide not to generate any requests at all. This is when
// we discover that nothing has changed in the client's FQDN data.
if (old_lease) {
if (!lease->matches(*old_lease)) {
isc_throw(isc::Unexpected,
"there is no match between the current instance of the"
" lease: " << lease->toText() << ", and the previous"
" instance: " << lease->toText());
} else {
// There will be a NameChangeRequest generated to remove existing
// DNS entries if the following conditions are met:
// - The hostname is set for the existing lease, we can't generate
// removal request for non-existent hostname.
// - A server has performed reverse, forward or both updates.
// - FQDN data between the new and old lease do not match.
if (!lease->hasIdenticalFqdn(*old_lease)) {
queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE,
old_lease);
// There will be a NameChangeRequest generated to remove existing
// DNS entries if the following conditions are met:
// - The hostname is set for the existing lease, we can't generate
// removal request for non-existent hostname.
// - A server has performed reverse, forward or both updates.
// - FQDN data between the new and old lease do not match.
if (!lease->hasIdenticalFqdn(*old_lease)) {
queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, old_lease);
// If FQDN data from both leases match, there is no need to update.
} else if (lease->hasIdenticalFqdn(*old_lease)) {
return;
}
} else if (lease->hasIdenticalFqdn(*old_lease)) {
return;
}
}
// We may need to generate the NameChangeRequest for the new lease. It
......@@ -1159,9 +1163,6 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
/// @todo: move subnet selection to a common code
resp->setSiaddr(subnet->getSiaddr());
// Get client-id. It is not mandatory in DHCPv4.
ClientIdPtr client_id = ex.getContext()->clientid_;
// Get the server identifier. It will be used to determine the state
// of the client.
OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
......@@ -1190,6 +1191,9 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
// allocation.
bool fake_allocation = (query->getType() == DHCPDISCOVER);
// Get client-id. It is not mandatory in DHCPv4.
ClientIdPtr client_id = ex.getContext()->clientid_;
// If there is no server id and there is a Requested IP Address option
// the client is in the INIT-REBOOT state in which the server has to
// determine whether the client's notion of the address is correct
......@@ -1201,33 +1205,37 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
.arg(hint.toText());
Lease4Ptr lease;
if (hwaddr) {
lease = LeaseMgrFactory::instance().getLease4(*hwaddr,
subnet->getID());
if (client_id) {
lease = LeaseMgrFactory::instance().getLease4(*client_id, subnet->getID());
}
if (!lease && client_id) {
lease = LeaseMgrFactory::instance().getLease4(*client_id,
subnet->getID());
if (!lease && hwaddr) {
lease = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
}
// Got a lease so we can check the address.
if (lease && (lease->addr_ != hint)) {
// Check the first error case: unknown client. We check this before
// validating the address sent because we don't want to respond if
// we don't know this client.
if (!lease || !lease->belongsToClient(hwaddr, client_id)) {
LOG_DEBUG(bad_packet_logger, DBG_DHCP4_DETAIL,
DHCP4_PACKET_NAK_0002)
DHCP4_NO_LEASE_INIT_REBOOT)
.arg(query->getLabel())
.arg(hint.toText());
resp->setType(DHCPNAK);
resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
ex.deleteResponse();
return;
}
// Now check the second error case: unknown client.
if (!lease) {
// We know this client so we can now check if his notion of the
// IP address is correct.
if (lease && (lease->addr_ != hint)) {
LOG_DEBUG(bad_packet_logger, DBG_DHCP4_DETAIL,
DHCP4_NO_LEASE_INIT_REBOOT)
DHCP4_PACKET_NAK_0002)
.arg(query->getLabel())
.arg(hint.toText());
ex.deleteResponse();
resp->setType(DHCPNAK);
resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
return;
}
}
......@@ -1661,7 +1669,16 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
/// @todo Uncomment this (see ticket #3116)
/// sanityCheck(release, MANDATORY);
// Try to find client-id
// Try to find client-id. Note that for the DHCPRELEASE we don't check if the
// match-client-id configuration parameter is disabled because this parameter
// is configured for subnets and we don't select subnet for the DHCPRELEASE.
// Bogus clients usually generate new client identifiers when they first
// connect to the network, so whatever client identifier has been used to
// acquire the lease, the client identifier carried in the DHCPRELEASE is
// likely to be the same and the lease will be correctly identified in the
// lease database. If supplied client identifier differs from the one used
// to acquire the lease then the lease will remain in the database and
// simply expire.
ClientIdPtr client_id;
OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
if (opt) {
......@@ -1680,25 +1697,10 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
return;
}
// Does the hardware address match? We don't want one client releasing
// another client's leases. Note that we're comparing the hardware
// addresses only, not hardware types or sources of the hardware
// addresses. Thus we don't use HWAddr::equals().
if (lease->hwaddr_->hwaddr_ != release->getHWAddr()->hwaddr_) {
LOG_DEBUG(lease_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
if (!lease->belongsToClient(release->getHWAddr(), client_id)) {
LOG_DEBUG(lease_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT)
.arg(release->getLabel())
.arg(release->getCiaddr().toText())
.arg(lease->hwaddr_->toText(false));
return;
}
// Does the lease have client-id info? If it has, then check it with what
// the client sent us.
if (lease->client_id_ && client_id && *lease->client_id_ != *client_id) {
LOG_DEBUG(lease_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID)
.arg(release->getLabel())
.arg(release->getCiaddr().toText())
.arg(lease->client_id_->toText());
.arg(release->getCiaddr().toText());
return;
}
......
......@@ -378,7 +378,7 @@ protected:
/// @return ACK or NAK message
Pkt4Ptr processRequest(Pkt4Ptr& request);
/// @brief Stub function that will handle incoming RELEASE messages.
/// @brief Processes incoming DHCPRELEASE messages.
///
/// In DHCPv4, server does not respond to RELEASE messages, therefore
/// this function does not return anything.
......@@ -391,9 +391,11 @@ protected:
/// @param decline message received from client
void processDecline(Pkt4Ptr& decline);
/// @brief Stub function that will handle incoming INFORM messages.
/// @brief Processes incoming DHCPINFORM messages.
///
/// @param inform message received from client
///
/// @return DHCPACK to be sent to the client.
Pkt4Ptr processInform(Pkt4Ptr& inform);
/// @brief Appends options requested by client.
......
......@@ -194,6 +194,8 @@ protected:
parser = new RelayInfoParser(config_id, relay_info_, Option::V4);
} else if (config_id.compare("option-data") == 0) {
parser = new OptionDataListParser(config_id, options_, AF_INET);
} else if (config_id.compare("match-client-id") == 0) {
parser = new BooleanParser(config_id, boolean_values_);
} else {
isc_throw(NotImplemented, "unsupported parameter: " << config_id);
}
......@@ -250,7 +252,28 @@ protected:
Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid, subnet_id));
subnet_ = subnet4;
// Try global value first
// match-client-id
isc::util::OptionalValue<bool> match_client_id;
try {
match_client_id = boolean_values_->getParam("match-client-id");
} catch (...) {
// Ignore because this parameter is optional and it may be specified
// in the global scope.
}
// If the match-client-id wasn't specified as a subnet specific parameter
// check if there is global value specified.
if (!match_client_id.isSpecified()) {
// If not specified, use false.
match_client_id.specify(globalContext()->boolean_values_->
getOptionalParam("match-client-id", true));
}
// Set the match-client-id value for the subnet.
subnet4->setMatchClientId(match_client_id.get());
// next-server
try {
string next_server = globalContext()->string_values_->getParam("next-server");
if (!next_server.empty()) {
......@@ -374,6 +397,8 @@ namespace dhcp {
parser = new BooleanParser(config_id, globalContext()->boolean_values_);
} else if (config_id.compare("dhcp-ddns") == 0) {
parser = new D2ClientConfigParser(config_id);
} else if (config_id.compare("match-client-id") == 0) {
parser = new BooleanParser(config_id, globalContext()->boolean_values_);
} else {
isc_throw(DhcpConfigError,
"unsupported global configuration parameter: "
......@@ -408,6 +433,9 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
DHCP4_CONFIG_START).arg(config_set->str());
// Reset global context.
globalContext().reset(new ParserContext(Option::V4));
// Before starting any subnet operations, let's reset the subnet-id counter,
// so newly recreated configuration starts with first subnet-id equal 1.
Subnet::resetSubnetID();
......
......@@ -87,6 +87,7 @@ dhcp4_unittests_SOURCES += marker_file.cc
dhcp4_unittests_SOURCES += dhcp4_client.cc dhcp4_client.h
dhcp4_unittests_SOURCES += inform_unittest.cc
dhcp4_unittests_SOURCES += dora_unittest.cc
dhcp4_unittests_SOURCES += release_unittest.cc
if CONFIG_BACKEND_BUNDY
# For Bundy backend, we only need to run the usual tests. There are no
......
......@@ -1102,6 +1102,77 @@ TEST_F(Dhcp4ParserTest, echoClientId) {
CfgMgr::instance().echoClientId(true);
}
// This test checks that the global match-client-id parameter is optional
// and that values under the subnet are used.
TEST_F(Dhcp4ParserTest, matchClientIdNoGlobal) {
ConstElementPtr status;
std::string config = "{ " + genIfaceConfig() + "," +
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ "
"{"
" \"match-client-id\": true,"
" \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
" \"subnet\": \"192.0.2.0/24\""
"},"
"{"
" \"match-client-id\": false,"
" \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
" \"subnet\": \"192.0.3.0/24\""
"} ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
checkResult(status, 0);
CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
Subnet4Ptr subnet1 = cfg->selectSubnet(IOAddress("192.0.2.1"));
ASSERT_TRUE(subnet1);
EXPECT_TRUE(subnet1->getMatchClientId());
Subnet4Ptr subnet2 = cfg->selectSubnet(IOAddress("192.0.3.1"));
ASSERT_TRUE(subnet2);
EXPECT_FALSE(subnet2->getMatchClientId());
}
// This test checks that the global match-client-id parameter is used
// when there is no such parameter under subnet and that the parameter
// specified for a subnet overrides the global setting.
TEST_F(Dhcp4ParserTest, matchClientIdGlobal) {
ConstElementPtr status;
std::string config = "{ " + genIfaceConfig() + "," +
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"match-client-id\": true,"
"\"subnet4\": [ "
"{"
" \"match-client-id\": false,"
" \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
" \"subnet\": \"192.0.2.0/24\""
"},"
"{"
" \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
" \"subnet\": \"192.0.3.0/24\""
"} ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
checkResult(status, 0);
CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();