Commit a61f40c4 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[master] Merge branch 'trac3794' (packet statistics in DHCPv4)

parents 82eddae8 d82978c9
......@@ -7,7 +7,7 @@ dist_html_DATA = $(HTMLDOCS) kea-guide.css
DOCBOOK = kea-guide.xml intro.xml quickstart.xml install.xml admin.xml config.xml
DOCBOOK += keactrl.xml dhcp4-srv.xml dhcp6-srv.xml logging.xml ddns.xml hooks.xml
DOCBOOK += libdhcp.xml lfc.xml
DOCBOOK += libdhcp.xml lfc.xml stats.xml
EXTRA_DIST = $(DOCBOOK)
DISTCLEANFILES = $(HTMLDOCS) $(DOCS) kea-messages.xml
......
......@@ -2582,6 +2582,216 @@ temporarily override a list of interface names and listen on all interfaces.
</section>
<section id="dhcp4-stats">
<title>Statistics in DHCPv4 server</title>
<note>
<para>This section describes DHCPv4-specific statistics. For a general
overview and usage of statistics, see <xref linkend="stats" />.</para>
</note>
<para>
The DHCPv4 server supports the following statistics:
</para>
<table frame="all" id="dhcp4-statistics">
<title>DHCPv4 Statistics</title>
<tgroup cols='3'>
<colspec colname='statistic' align='center'/>
<colspec colname='type' align='center'/>
<colspec colname='description' align='left'/>
<thead>
<row>
<entry>Statistic</entry>
<entry>Data Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>pkt4-received</entry>
<entry>integer</entry>
<entry>
Number of packets received. This includes all packets: valid, bogus, corrupted,
rejected etc. This statistic is expected to grow rapidly.
</entry>
</row>
<row>
<entry>pkt4-discover-received</entry>
<entry>integer</entry>
<entry>
Number of DHCPDISCOVER packets received. This statistic is expected to grow.
Its increase means that clients that just booted started their configuration process
and their initial packets reached your server.
</entry>
</row>
<row>
<entry>pkt4-offer-received</entry>
<entry>integer</entry>
<entry>
Number of DHCPOFFER packets received. This statistic
is expected to remain zero at all times, as DHCPOFFER packets are sent
by the server and the server is never expected to receive them. Non-zero
value indicates an error. One likely cause would be a misbehaving relay
agent that incorrectly forwards DHCPOFFER messages towards the server,
rather back to the clients.
</entry>
</row>
<row>
<entry>pkt4-request-received</entry>
<entry>integer</entry>
<entry>
Number of DHCPREQUEST packets received. This statistic
is expected to grow. Its increase means that clients that just booted
received server's response (DHCPOFFER), accepted it and now requesting
an address (DHCPREQUEST).
</entry>
</row>
<row>
<entry>pkt4-ack-received</entry>
<entry>integer</entry>
<entry>
Number of DHCPACK packets received. This statistic
is expected to remain zero at all times, as DHCPACK packets are sent
by the server and the server is never expected to receive them. Non-zero
value indicates an error. One likely cause would be a misbehaving relay
agent that incorrectly forwards DHCPACK messages towards the server,
rather back to the clients.
</entry>
</row>
<row>
<entry>pkt4-nak-received</entry>
<entry>integer</entry>
<entry>
Number of DHCPNAK packets received. This statistic
is expected to remain zero at all times, as DHCPNAK packets are sent
by the server and the server is never expected to receive them. Non-zero
value indicates an error. One likely cause would be a misbehaving relay
agent that incorrectly forwards DHCPNAK messages towards the server,
rather back to the clients.
</entry>
</row>
<row>
<entry>pkt4-release-received</entry>
<entry>integer</entry>
<entry>
Number of DHCPRELEASE packets received. This statistic
is expected to grow. Its increase means that clients that had an address
are shutting down or stop using their addresses.
</entry>
</row>
<row>
<entry>pkt4-decline-received</entry>
<entry>integer</entry>
<entry>
Number of DHCPDECLINE packets received. This statistic
is expected to remain close to zero. Its increase means that a client
that leased an address, but discovered that the address is currently
used by an unknown device in your network.
</entry>
</row>
<row>
<entry>pkt4-inform-received</entry>
<entry>integer</entry>
<entry>
Number of DHCPINFORM packets received. This statistic
is expected to grow. Its increase means that there are clients that
either do not need an address or already have an address and are
interested only in getting additional configuration parameters.
</entry>
</row>
<row>
<entry>pkt4-unknown-received</entry>
<entry>integer</entry>
<entry>
Number of packets received of an unknown type. Non-zero
value of this statistic indicates that the server received a packet
that it wasn't able to recognize: either with unsupported type
or possibly malformed (without message type option).
</entry>
</row>
<row>
<entry>pkt4-sent</entry>
<entry>integer</entry>
<entry>
Number of DHCPv4 packets sent. This statistic is expected to grow
every time the server transmits a packet. In general, it should
roughly match pkt4-received, as most incoming packets cause
server to respond. There are exceptions (e.g. DHCPRELEASE), so
do not worry, if it is lesser than pkt4-received.
</entry>
</row>
<row>
<entry>pkt4-offer-sent</entry>
<entry>integer</entry>
<entry>
Number of DHCPOFFER packets sent. This statistic is expected to
grow in most cases after a DHCPDISCOVER is processed. There are
certain uncommon, but valid cases where incoming DHCPDISCOVER is
dropped, but in general this statistic is expected to be close to
pkt4-discover-received.
</entry>
</row>
<row>
<entry>pkt4-ack-sent</entry>
<entry>integer</entry>
<entry>
Number of DHCPACK packets sent. This statistic is expected to
grow in most cases after a DHCPREQUEST is processed. There are
certain cases where DHCPNAK is sent instead. In general, the sum of
pkt4-ack-sent and pkt4-nak-sent should be close to
pkt4-request-received.
</entry>
</row>
<row>
<entry>pkt4-nak-sent</entry>
<entry>integer</entry>
<entry>
Number of DHCPNAK packets sent. This statistic is expected to
grow when the server choses to not honor the address requested by a
client. In general, the sum of pkt4-ack-sent and pkt4-nak-sent
should be close to pkt4-request-received.
</entry>
</row>
<row>
<entry>pkt4-parse-failed</entry>
<entry>integer</entry>
<entry>
Number of incoming packets that could not be parsed. Non-zero value of
this statistic indicates that the server received malformed or truncated packet.
This may indicate problems in your network, faulty clients or server code bug.
</entry>
</row>
<row>
<entry>pkt4-receive-drop</entry>
<entry>integer</entry>
<entry>
Number of incoming packets that were dropped.
Exact reason for dropping packets is logged, but the most common
reasons may be: an unacceptable packet type, direct responses are
forbidden, or the server-id sent by the client does not match
the server's server-id.
</entry>
</row>
</tbody>
</tgroup>
</table>
</section>
<section id="dhcp4-std">
<title>Supported DHCP Standards</title>
......
......@@ -73,6 +73,8 @@
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="hooks.xml" />
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="stats.xml" />
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="libdhcp.xml" />
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="logging.xml" />
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
<!ENTITY mdash "&#x2014;" >
]>
<!-- Note: Please use the following terminology:
- daemon - one process (e.g. kea-dhcp4)
- component - one piece of code within a daemon (e.g. libdhcp or hooks)
- server - currently equal to daemon, but the difference will be more
prominent once we add client or relay support
- logger - one instance of isc::log::Logger
- structure - an element in config file (e.g. "Dhcp4")
Do not use:
- module => daemon
-->
<chapter id="stats">
<title>Statistics</title>
<section>
<title>Statistics Overview</title>
<para>
TODO: Describe statistics collection here as part of ticket #3800.
For DHCPv4 specific statistics, see <xref linkend="dhcp4-stats"/>.
For DHCPv6 specific statistics, see TODO.
For DDNS specific statistics, see TODO.
</para>
</section>
</chapter>
......@@ -82,6 +82,7 @@ kea_dhcp4_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
kea_dhcp4_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
kea_dhcp4_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
kea_dhcp4_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
kea_dhcp4_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
kea_dhcp4dir = $(pkgdatadir)
kea_dhcp4_DATA = dhcp4.spec
......@@ -41,6 +41,7 @@
#include <hooks/hooks_log.h>
#include <hooks/hooks_manager.h>
#include <util/strutil.h>
#include <stats/stats_mgr.h>
#include <asio.hpp>
#include <boost/bind.hpp>
......@@ -412,6 +413,13 @@ Dhcpv4Srv::run() {
continue;
}
// Log reception of the packet. We need to increase it early, as any
// failures in unpacking will cause the packet to be dropped. We
// will increase type specific packets further down the road.
// See processStatsReceived().
isc::stats::StatsMgr::instance().addValue("pkt4-received",
static_cast<uint64_t>(1));
// In order to parse the DHCP options, the server needs to use some
// configuration information such as: existing option spaces, option
// definitions etc. This is the kind of information which is not
......@@ -470,10 +478,19 @@ Dhcpv4Srv::run() {
.arg(query->getLocalAddr().toText())
.arg(query->getIface())
.arg(e.what());
// Increase the statistics of parse failues and dropped packets.
isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
static_cast<uint64_t>(1));
isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
static_cast<uint64_t>(1));
continue;
}
}
// Update statistics accordingly for received packet.
processStatsReceived(query);
// Assign this packet to one or more classes if needed. We need to do
// this before calling accept(), because getSubnet4() may need client
// class information.
......@@ -482,6 +499,9 @@ Dhcpv4Srv::run() {
// Check whether the message should be further processed or discarded.
// There is no need to log anything here. This function logs by itself.
if (!accept(query)) {
// Increase the statistic of dropped packets.
isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
static_cast<uint64_t>(1));
continue;
}
......@@ -568,6 +588,10 @@ Dhcpv4Srv::run() {
DHCP4_PACKET_DROP_0007)
.arg(query->getLabel())
.arg(e.what());
// Increase the statistic of dropped packets.
isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
static_cast<uint64_t>(1));
}
if (!rsp) {
......@@ -665,6 +689,10 @@ Dhcpv4Srv::run() {
.arg(static_cast<int>(rsp->getType()))
.arg(rsp->toText());
sendPacket(rsp);
// Update statistics accordingly for sent packet.
processStatsSent(rsp);
} catch (const std::exception& e) {
LOG_ERROR(packet_logger, DHCP4_PACKET_SEND_FAIL)
.arg(rsp->getLabel())
......@@ -1045,7 +1073,7 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
// clients do not handle the hostnames with the trailing dot.
opt_hostname_resp->setValue(d2_mgr.qualifyName(hostname, false));
}
LOG_DEBUG(ddns_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_RESPONSE_HOSTNAME_DATA)
.arg(ex.getQuery()->getLabel())
.arg(opt_hostname_resp->getValue());
......@@ -2235,5 +2263,80 @@ Daemon::getVersion(bool extended) {
return (tmp.str());
}
void Dhcpv4Srv::processStatsReceived(const Pkt4Ptr& query) {
// Note that we're not bumping pkt4-received statistic as it was
// increased early in the packet reception code.
string stat_name = "pkt4-unknown-received";
try {
switch (query->getType()) {
case DHCPDISCOVER:
stat_name = "pkt4-discover-received";
break;
case DHCPOFFER:
// Should not happen, but let's keep a counter for it
stat_name = "pkt4-offer-received";
break;
case DHCPREQUEST:
stat_name = "pkt4-request-received";
break;
case DHCPACK:
// Should not happen, but let's keep a counter for it
stat_name = "pkt4-ack-received";
break;
case DHCPNAK:
// Should not happen, but let's keep a counter for it
stat_name = "pkt4-nak-received";
break;
case DHCPRELEASE:
stat_name = "pkt4-release-received";
break;
case DHCPDECLINE:
stat_name = "pkt4-decline-received";
break;
case DHCPINFORM:
stat_name = "pkt4-inform-received";
break;
default:
; // do nothing
}
}
catch (...) {
// If the incoming packet doesn't have option 53 (message type)
// or a hook set pkt4_receive_skip, then Pkt4::getType() may
// throw an exception. That's ok, we'll then use the default
// name of pkt4-unknown-received.
}
isc::stats::StatsMgr::instance().addValue(stat_name,
static_cast<uint64_t>(1));
}
void Dhcpv4Srv::processStatsSent(const Pkt4Ptr& response) {
// Increase generic counter for sent packets.
isc::stats::StatsMgr::instance().addValue("pkt4-sent",
static_cast<uint64_t>(1));
// Increase packet type specific counter for packets sent.
string stat_name;
switch (response->getType()) {
case DHCPOFFER:
stat_name = "pkt4-offer-sent";
break;
case DHCPACK:
stat_name = "pkt4-ack-sent";
break;
case DHCPNAK:
stat_name = "pkt4-nak-sent";
break;
default:
// That should never happen
return;
}
isc::stats::StatsMgr::instance().addValue(stat_name,
static_cast<uint64_t>(1));
}
} // namespace dhcp
} // namespace isc
......@@ -738,6 +738,13 @@ private:
/// @return Option that contains netmask information
static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
/// @brief Updates statistics for received packets
/// @param query packet received
static void processStatsReceived(const Pkt4Ptr& query);
/// @brief Updates statistics for transmitted packets
/// @param query packet transmitted
static void processStatsSent(const Pkt4Ptr& response);
uint16_t port_; ///< UDP port number on which server listens.
bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).
......
......@@ -116,6 +116,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
endif
......
......@@ -142,6 +142,8 @@ Dhcp4Client::applyConfiguration() {
return;
}
// Let's keep the old lease in case this is a response to Inform.
Lease4 old_lease = config_.lease_;
config_.reset();
// Routers
......@@ -175,11 +177,17 @@ Dhcp4Client::applyConfiguration() {
config_.serverid_ = opt_serverid->readAddress();
}
/// @todo Set the valid lifetime, t1, t2 etc.
config_.lease_ = Lease4(IOAddress(context_.response_->getYiaddr()),
context_.response_->getHWAddr(),
0, 0, 0, 0, 0, time(NULL), 0, false, false,
"");
// If the message sent was Inform, we don't want to throw
// away the old lease info, just the bits about options.
if (context_.query_->getType() == DHCPINFORM) {
config_.lease_ = old_lease;
} else {
/// @todo Set the valid lifetime, t1, t2 etc.
config_.lease_ = Lease4(IOAddress(context_.response_->getYiaddr()),
context_.response_->getHWAddr(),
0, 0, 0, 0, 0, time(NULL), 0, false, false,
"");
}
}
void
......
......@@ -43,6 +43,7 @@
#include <hooks/server_hooks.h>
#include <hooks/hooks_manager.h>
#include <config/ccsession.h>
#include <stats/stats_mgr.h>
#include <boost/scoped_ptr.hpp>
......@@ -61,6 +62,26 @@ using namespace isc::test;
namespace {
const char* CONFIGS[] = {
// Configuration 0:
// - 1 subnet: 10.254.226.0/25
// - used for recorded traffic (see PktCaptures::captureRelayedDiscover)
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pools\": [ { \"pool\": \"10.254.226.0/25\" } ],"
" \"subnet\": \"10.254.226.0/24\", "
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"valid-lifetime\": 4000,"
" \"interface\": \"eth0\" "
" } ],"
"\"valid-lifetime\": 4000 }"
};
// This test verifies that the destination address of the response
// message is set to giaddr, when giaddr is set to non-zero address
// in the received message.
......@@ -922,22 +943,7 @@ TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
// subnet 10.254.226.0/24 is in use, because this packet
// contains the giaddr which belongs to this subnet and
// this giaddr is used to select the subnet
std::string config = "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pools\": [ { \"pool\": \"10.254.226.0/25\" } ],"
" \"subnet\": \"10.254.226.0/24\", "
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"valid-lifetime\": 4000,"
" \"interface\": \"eth0\" "
" } ],"
"\"valid-lifetime\": 4000 }";
configure(config);
configure(CONFIGS[0]);
// Let's create a relayed DISCOVER. This particular relayed DISCOVER has
// added option 82 (relay agent info) with 3 suboptions. The server
......@@ -3345,4 +3351,61 @@ TEST_F(Dhcpv4SrvTest, acceptMessageType) {
}
}
// Test checks whether statistic is bumped up appropriately when Decline
// message is received.
TEST_F(Dhcpv4SrvTest, statisticsDecline) {
NakedDhcpv4Srv srv(0);
pretendReceivingPkt(srv, CONFIGS[0], DHCPDECLINE, "pkt4-decline-received");
}
// Test checks whether statistic is bumped up appropriately when Offer
// message is received (this should never happen in a sane metwork).
TEST_F(Dhcpv4SrvTest, statisticsOfferRcvd) {
NakedDhcpv4Srv srv(0);
pretendReceivingPkt(srv, CONFIGS[0], DHCPOFFER, "pkt4-offer-received");
}
// Test checks whether statistic is bumped up appropriately when Ack
// message is received (this should never happen in a sane metwork).
TEST_F(Dhcpv4SrvTest, statisticsAckRcvd) {
NakedDhcpv4Srv srv(0);
pretendReceivingPkt(srv, CONFIGS[0], DHCPACK, "pkt4-ack-received");
}
// Test checks whether statistic is bumped up appropriately when Nak
// message is received (this should never happen in a sane metwork).
TEST_F(Dhcpv4SrvTest, statisticsNakRcvd) {
NakedDhcpv4Srv srv(0);
pretendReceivingPkt(srv, CONFIGS[0], DHCPNAK, "pkt4-nak-received");
}
// Test checks whether statistic is bumped up appropriately when Release
// message is received.
TEST_F(Dhcpv4SrvTest, statisticsReleaseRcvd) {
NakedDhcpv4Srv srv(0);
pretendReceivingPkt(srv, CONFIGS[0], DHCPRELEASE, "pkt4-release-received");
}
// Test checks whether statistic is bumped up appropriately when unknown
// message is received.
TEST_F(Dhcpv4SrvTest, statisticsUnknownRcvd) {
NakedDhcpv4Srv srv(0);
pretendReceivingPkt(srv, CONFIGS[0], 200, "pkt4-unknown-received");
// There should also be pkt4-receive-drop stat bumped up
using namespace isc::stats;
StatsMgr& mgr = StatsMgr::instance();
ObservationPtr drop_stat = mgr.getObservation("pkt4-receive-drop");
// This statistic must be present and must be set to 1.
ASSERT_TRUE(drop_stat);
EXPECT_EQ(1, drop_stat->getInteger().first);
}
}; // end of anonymous namespace
......@@ -25,10 +25,12 @@
#include <dhcp/option_custom.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp/tests/pkt_captures.h>