mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-01 14:35:29 +00:00
[2325] Sanity checks for DHCPv6 server implemented.
This commit is contained in:
@@ -81,6 +81,12 @@ This message indicates that the server failed to grant (in response to
|
|||||||
received REQUEST) a lease for a given client. There may be many reasons for
|
received REQUEST) a lease for a given client. There may be many reasons for
|
||||||
such failure. Each specific failure is logged in a separate log entry.
|
such failure. Each specific failure is logged in a separate log entry.
|
||||||
|
|
||||||
|
% DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
|
||||||
|
This message indicates that received message has invalid number of options:
|
||||||
|
mandatory client-id option is missing, server-id forbidden in that particular
|
||||||
|
type of message is present, there is more than one instance of client-id
|
||||||
|
or server-id etc. Exact reason for rejecting the packed is printed.
|
||||||
|
|
||||||
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
|
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
|
||||||
A warning message is issued when an attempt is made to shut down the
|
A warning message is issued when an attempt is made to shut down the
|
||||||
IPv6 DHCP server but it is not running.
|
IPv6 DHCP server but it is not running.
|
||||||
|
@@ -132,44 +132,51 @@ bool Dhcpv6Srv::run() {
|
|||||||
.arg(query->getBuffer().getLength())
|
.arg(query->getBuffer().getLength())
|
||||||
.arg(query->toText());
|
.arg(query->toText());
|
||||||
|
|
||||||
switch (query->getType()) {
|
try {
|
||||||
case DHCPV6_SOLICIT:
|
switch (query->getType()) {
|
||||||
rsp = processSolicit(query);
|
case DHCPV6_SOLICIT:
|
||||||
break;
|
rsp = processSolicit(query);
|
||||||
|
break;
|
||||||
|
|
||||||
case DHCPV6_REQUEST:
|
case DHCPV6_REQUEST:
|
||||||
rsp = processRequest(query);
|
rsp = processRequest(query);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DHCPV6_RENEW:
|
case DHCPV6_RENEW:
|
||||||
rsp = processRenew(query);
|
rsp = processRenew(query);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DHCPV6_REBIND:
|
case DHCPV6_REBIND:
|
||||||
rsp = processRebind(query);
|
rsp = processRebind(query);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DHCPV6_CONFIRM:
|
case DHCPV6_CONFIRM:
|
||||||
rsp = processConfirm(query);
|
rsp = processConfirm(query);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DHCPV6_RELEASE:
|
case DHCPV6_RELEASE:
|
||||||
rsp = processRelease(query);
|
rsp = processRelease(query);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DHCPV6_DECLINE:
|
case DHCPV6_DECLINE:
|
||||||
rsp = processDecline(query);
|
rsp = processDecline(query);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DHCPV6_INFORMATION_REQUEST:
|
case DHCPV6_INFORMATION_REQUEST:
|
||||||
rsp = processInfRequest(query);
|
rsp = processInfRequest(query);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Only action is to output a message if debug is enabled,
|
// Only action is to output a message if debug is enabled,
|
||||||
// and that will be covered by the debug statement before
|
// and that will be covered by the debug statement before
|
||||||
// the "switch" statement.
|
// the "switch" statement.
|
||||||
;
|
;
|
||||||
|
}
|
||||||
|
} catch (const RFCViolation& e) {
|
||||||
|
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
|
||||||
|
.arg(query->getName())
|
||||||
|
.arg(query->getRemoteAddr())
|
||||||
|
.arg(e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rsp) {
|
if (rsp) {
|
||||||
@@ -195,9 +202,6 @@ bool Dhcpv6Srv::run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add support for config session (see src/bin/auth/main.cc)
|
|
||||||
// so this daemon can be controlled from bob
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (true);
|
return (true);
|
||||||
@@ -357,6 +361,54 @@ OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
|
|||||||
return (status);
|
return (status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
|
||||||
|
RequirementLevel serverid) {
|
||||||
|
Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
|
||||||
|
switch (clientid) {
|
||||||
|
case MANDATORY:
|
||||||
|
if (client_ids.size() != 1) {
|
||||||
|
isc_throw(RFCViolation, "Exactly 1 client-id option expected in "
|
||||||
|
<< pkt->getName() << ", but " << client_ids.size()
|
||||||
|
<< " received");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPTIONAL:
|
||||||
|
if (client_ids.size() > 1) {
|
||||||
|
isc_throw(RFCViolation, "Too many (" << client_ids.size()
|
||||||
|
<< ") client-id options received in " << pkt->getName());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FORBIDDEN:
|
||||||
|
// doesn't make sense - client-id is always allowed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
|
||||||
|
switch (serverid) {
|
||||||
|
case FORBIDDEN:
|
||||||
|
if (server_ids.size() > 0) {
|
||||||
|
isc_throw(RFCViolation, "Exactly 1 server-id option expected, but "
|
||||||
|
<< server_ids.size() << " received in " << pkt->getName());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MANDATORY:
|
||||||
|
if (server_ids.size() != 1) {
|
||||||
|
isc_throw(RFCViolation, "Invalid number of server-id options received ("
|
||||||
|
<< server_ids.size() << "), exactly 1 expected in message "
|
||||||
|
<< pkt->getName());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPTIONAL:
|
||||||
|
if (server_ids.size() > 1) {
|
||||||
|
isc_throw(RFCViolation, "Too many (" << server_ids.size()
|
||||||
|
<< ") client-id options received in " << pkt->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
|
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
|
||||||
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
|
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
|
||||||
|
|
||||||
@@ -370,7 +422,7 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
|
|||||||
|
|
||||||
// We need to select a subnet the client is connected in.
|
// We need to select a subnet the client is connected in.
|
||||||
Subnet6Ptr subnet = selectSubnet(question);
|
Subnet6Ptr subnet = selectSubnet(question);
|
||||||
if (subnet) {
|
if (!subnet) {
|
||||||
// This particular client is out of luck today. We do not have
|
// This particular client is out of luck today. We do not have
|
||||||
// information about the subnet he is connected to. This likely means
|
// information about the subnet he is connected to. This likely means
|
||||||
// misconfiguration of the server (or some relays). We will continue to
|
// misconfiguration of the server (or some relays). We will continue to
|
||||||
@@ -378,12 +430,13 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
|
|||||||
// addresses or prefixes, no subnet specific configuration etc. The only
|
// addresses or prefixes, no subnet specific configuration etc. The only
|
||||||
// thing this client can get is some global information (like DNS
|
// thing this client can get is some global information (like DNS
|
||||||
// servers).
|
// servers).
|
||||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
|
|
||||||
.arg(subnet->toText());
|
|
||||||
} else {
|
|
||||||
// perhaps this should be logged on some higher level? This is most likely
|
// perhaps this should be logged on some higher level? This is most likely
|
||||||
// configuration bug.
|
// configuration bug.
|
||||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SUBNET_SELECTION_FAILED);
|
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SUBNET_SELECTION_FAILED);
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
|
||||||
|
.arg(subnet->toText());
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo: We should implement Option6Duid some day, but we can do without it
|
// @todo: We should implement Option6Duid some day, but we can do without it
|
||||||
@@ -514,6 +567,14 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
|
|||||||
|
|
||||||
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
|
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
|
||||||
|
|
||||||
|
if (!sanityCheck(solicit, MANDATORY, FORBIDDEN)) {
|
||||||
|
return (Pkt6Ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
|
||||||
|
|
||||||
|
sanityCheck(solicit, MANDATORY, FORBIDDEN);
|
||||||
|
|
||||||
Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
|
Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
|
||||||
|
|
||||||
copyDefaultOptions(solicit, advertise);
|
copyDefaultOptions(solicit, advertise);
|
||||||
@@ -526,6 +587,9 @@ Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
|
Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
|
||||||
|
|
||||||
|
sanityCheck(request, MANDATORY, MANDATORY);
|
||||||
|
|
||||||
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
|
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
|
||||||
|
|
||||||
copyDefaultOptions(request, reply);
|
copyDefaultOptions(request, reply);
|
||||||
|
@@ -30,6 +30,23 @@
|
|||||||
|
|
||||||
namespace isc {
|
namespace isc {
|
||||||
namespace dhcp {
|
namespace dhcp {
|
||||||
|
|
||||||
|
/// An exception that is thrown if a DHCPv6 protocol violation occurs while
|
||||||
|
/// processing a message (e.g. a mandatory option is missing)
|
||||||
|
class RFCViolation : public isc::Exception {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// @brief constructor
|
||||||
|
///
|
||||||
|
/// @param file name of the file, where exception occurred
|
||||||
|
/// @param line line of the file, where exception occurred
|
||||||
|
/// @param what text description of the issue that caused exception
|
||||||
|
RFCViolation(const char* file, size_t line, const char* what) :
|
||||||
|
isc::Exception(file, line, what) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// @brief DHCPv6 server service.
|
/// @brief DHCPv6 server service.
|
||||||
///
|
///
|
||||||
/// This class represents DHCPv6 server. It contains all
|
/// This class represents DHCPv6 server. It contains all
|
||||||
@@ -45,6 +62,12 @@ namespace dhcp {
|
|||||||
class Dhcpv6Srv : public boost::noncopyable {
|
class Dhcpv6Srv : public boost::noncopyable {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/// @brief defines if certain option may, must or must not appear
|
||||||
|
typedef enum {
|
||||||
|
FORBIDDEN,
|
||||||
|
MANDATORY,
|
||||||
|
OPTIONAL
|
||||||
|
} RequirementLevel;
|
||||||
|
|
||||||
/// @brief Minimum length of a MAC address to be used in DUID generation.
|
/// @brief Minimum length of a MAC address to be used in DUID generation.
|
||||||
static const size_t MIN_MAC_LEN = 6;
|
static const size_t MIN_MAC_LEN = 6;
|
||||||
@@ -84,6 +107,19 @@ public:
|
|||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
/// @brief verifies if specified packet meets RFC requirements
|
||||||
|
///
|
||||||
|
/// Checks if mandatory option is really there, that forbidden option
|
||||||
|
/// is not there, and that client-id or server-id appears only once.
|
||||||
|
///
|
||||||
|
/// @param pkt packet to be checked
|
||||||
|
/// @param clientid expectation regarding client-id option
|
||||||
|
/// @param serverid expectation regarding server-id option
|
||||||
|
/// @throw RFCViolation if any issues are detected
|
||||||
|
void sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
|
||||||
|
RequirementLevel serverid);
|
||||||
|
|
||||||
/// @brief Processes incoming SOLICIT and returns response.
|
/// @brief Processes incoming SOLICIT and returns response.
|
||||||
///
|
///
|
||||||
/// Processes received SOLICIT message and verifies that its sender
|
/// Processes received SOLICIT message and verifies that its sender
|
||||||
|
@@ -59,6 +59,7 @@ public:
|
|||||||
using Dhcpv6Srv::processRequest;
|
using Dhcpv6Srv::processRequest;
|
||||||
using Dhcpv6Srv::createStatusCode;
|
using Dhcpv6Srv::createStatusCode;
|
||||||
using Dhcpv6Srv::selectSubnet;
|
using Dhcpv6Srv::selectSubnet;
|
||||||
|
using Dhcpv6Srv::sanityCheck;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Dhcpv6SrvTest : public ::testing::Test {
|
class Dhcpv6SrvTest : public ::testing::Test {
|
||||||
@@ -651,6 +652,9 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
|
|||||||
OptionPtr clientid = generateClientId();
|
OptionPtr clientid = generateClientId();
|
||||||
req->addOption(clientid);
|
req->addOption(clientid);
|
||||||
|
|
||||||
|
// server-id is mandatory in REQUEST
|
||||||
|
req->addOption(srv->getServerID());
|
||||||
|
|
||||||
// Pass it to the server and hope for a REPLY
|
// Pass it to the server and hope for a REPLY
|
||||||
Pkt6Ptr reply = srv->processRequest(req);
|
Pkt6Ptr reply = srv->processRequest(req);
|
||||||
|
|
||||||
@@ -709,6 +713,11 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
|
|||||||
req2->addOption(clientid2);
|
req2->addOption(clientid2);
|
||||||
req3->addOption(clientid3);
|
req3->addOption(clientid3);
|
||||||
|
|
||||||
|
// server-id is mandatory in REQUEST
|
||||||
|
req1->addOption(srv->getServerID());
|
||||||
|
req2->addOption(srv->getServerID());
|
||||||
|
req3->addOption(srv->getServerID());
|
||||||
|
|
||||||
// Pass it to the server and get an advertise
|
// Pass it to the server and get an advertise
|
||||||
Pkt6Ptr reply1 = srv->processRequest(req1);
|
Pkt6Ptr reply1 = srv->processRequest(req1);
|
||||||
Pkt6Ptr reply2 = srv->processRequest(req2);
|
Pkt6Ptr reply2 = srv->processRequest(req2);
|
||||||
@@ -763,8 +772,8 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
|
|||||||
EXPECT_TRUE(status->getData() == exp);
|
EXPECT_TRUE(status->getData() == exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test verifies if the selectSubnet() method works as expected.
|
// This test verifies if the sanityCheck() really checks options presence.
|
||||||
TEST_F(Dhcpv6SrvTest, SelectSubnet) {
|
TEST_F(Dhcpv6SrvTest, sanityCheck) {
|
||||||
boost::scoped_ptr<NakedDhcpv6Srv> srv;
|
boost::scoped_ptr<NakedDhcpv6Srv> srv;
|
||||||
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
|
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
|
||||||
|
|
||||||
@@ -772,18 +781,63 @@ TEST_F(Dhcpv6SrvTest, SelectSubnet) {
|
|||||||
|
|
||||||
// check that the packets originating from local addresses can be
|
// check that the packets originating from local addresses can be
|
||||||
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
|
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
|
||||||
EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
|
|
||||||
|
|
||||||
// packets originating from subnet A will select subnet A
|
// client-id is optional for information-request, so
|
||||||
pkt->setRemoteAddr(IOAddress("2001:db8:1::6789"));
|
EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
|
||||||
EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
|
|
||||||
|
// empty packet, no client-id, no server-id
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
|
||||||
|
RFCViolation);
|
||||||
|
|
||||||
|
// This doesn't make much sense, but let's check it for completeness
|
||||||
|
EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
|
||||||
|
|
||||||
|
OptionPtr clientid = generateClientId();
|
||||||
|
pkt->addOption(clientid);
|
||||||
|
|
||||||
|
// client-id is mandatory, server-id is forbidden (as in SOLICIT or REBIND)
|
||||||
|
EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
|
||||||
|
|
||||||
|
pkt->addOption(srv->getServerID());
|
||||||
|
|
||||||
|
// both client-id and server-id are mandatory (as in REQUEST, RENEW, RELEASE, DECLINE)
|
||||||
|
EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
|
||||||
|
|
||||||
|
// sane section ends here, let's do some negative tests as well
|
||||||
|
|
||||||
|
pkt->addOption(clientid);
|
||||||
|
pkt->addOption(clientid);
|
||||||
|
|
||||||
|
// with more than one client-id it should throw, no matter what
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
|
||||||
|
RFCViolation);
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
|
||||||
|
RFCViolation);
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
|
||||||
|
RFCViolation);
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
|
||||||
|
RFCViolation);
|
||||||
|
|
||||||
|
pkt->delOption(D6O_CLIENTID);
|
||||||
|
pkt->delOption(D6O_CLIENTID);
|
||||||
|
|
||||||
|
// again we have only one client-id
|
||||||
|
|
||||||
|
// let's try different type of insanity - several server-ids
|
||||||
|
pkt->addOption(srv->getServerID());
|
||||||
|
pkt->addOption(srv->getServerID());
|
||||||
|
|
||||||
|
// with more than one server-id it should throw, no matter what
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
|
||||||
|
RFCViolation);
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
|
||||||
|
RFCViolation);
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
|
||||||
|
RFCViolation);
|
||||||
|
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
|
||||||
|
RFCViolation);
|
||||||
|
|
||||||
// packets from a subnet that is not supported will not get
|
|
||||||
// a subnet
|
|
||||||
pkt->setRemoteAddr(IOAddress("3000::faf"));
|
|
||||||
EXPECT_FALSE(srv->selectSubnet(pkt));
|
|
||||||
|
|
||||||
/// @todo: expand this test once support for relays is implemented
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end of anonymous namespace
|
} // end of anonymous namespace
|
||||||
|
Reference in New Issue
Block a user