2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 05:55:28 +00:00

[#2974] Checkpoint: did v4 code, need UTs, v6 and doc

This commit is contained in:
Francis Dupont
2024-06-13 21:50:09 +02:00
committed by Razvan Becheriu
parent a740d0bfe5
commit cadf332033
9 changed files with 470 additions and 0 deletions

View File

@@ -69,6 +69,9 @@ src/share/api/lease6-wipe.json
src/share/api/lease6-write.json
src/share/api/leases-reclaim.json
src/share/api/list-commands.json
src/share/api/localize4.json
src/share/api/localize4o6.json
src/share/api/localize6.json
src/share/api/network4-add.json
src/share/api/network4-del.json
src/share/api/network4-get.json

View File

@@ -864,6 +864,37 @@ command-line argument. This command does not take any parameters.
"command": "version-get"
}
Commands Supported by the DHCPv4 Server
=======================================
.. isccmd:: localize4
.. _command-localize4:
The ``localize4`` Command
-------------------------
The :isccmd:`localize4` command returns the result of DHCPv4 subnet selection.
.. isccmd:: localize4o6
.. _command-localize4o6:
The ``localize4o6`` Command
---------------------------
The :isccmd:`localize4o6` command returns the result of DHCPv4-over-DHCPv6
subnet selection.
Commands Supported by the DHCPv6 Server
=======================================
.. isccmd:: localize6
.. _command-localize6:
The ``localize6`` Command
-------------------------
The :isccmd:`localize6` command returns the result of DHCPv6 subnet selection.
Commands Supported by the D2 Server
===================================

View File

@@ -695,6 +695,337 @@ ControlledDhcpv4Srv::commandLeasesReclaimHandler(const string&,
return (answer);
}
ConstElementPtr
ControlledDhcpv4Srv::commandLocalize4Handler(const string&,
ConstElementPtr args) {
if (!args) {
return (createAnswer(CONTROL_RESULT_ERROR, "empty arguments"));
}
if (args->getType() != Element::map) {
return (createAnswer(CONTROL_RESULT_ERROR, "arguments must be a map"));
}
bool ignore_link_sel =
CfgMgr::instance().getCurrentCfg()->getIgnoreRAILinkSelection();
SubnetSelector selector;
for (auto const& entry : args->mapValue()) {
ostringstream errmsg;
if (entry.first == "interface") {
if (entry.second->getType() != Element::string) {
errmsg << "'interface' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.iface_name_ = entry.second->stringValue();
continue;
} else if (entry.first == "address") {
if (entry.second->getType() != Element::string) {
errmsg << "'address' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'address' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.ciaddr_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'address' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "relay") {
if (entry.second->getType() != Element::string) {
errmsg << "'relay' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'relay' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.giaddr_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'relay' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "local") {
if (entry.second->getType() != Element::string) {
errmsg << "'local' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'local' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.local_address_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'local' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "remote") {
if (entry.second->getType() != Element::string) {
errmsg << "'remote' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'remote' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.remote_address_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'remote' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "link") {
if (entry.second->getType() != Element::string) {
errmsg << "'link' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'link' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
if (!ignore_link_sel) {
selector.option_select_ = addr;
}
continue;
} catch (const exception& ex) {
errmsg << "bad 'link' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "subnet") {
// RAI link-selection has precedence over subnet-selection.
if (args->contains("link") && !ignore_link_sel) {
continue;
}
if (entry.second->getType() != Element::string) {
errmsg << "'subnet' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'subnet' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.option_select_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'subnet' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "classes") {
if (entry.second->getType() != Element::list) {
return (createAnswer(CONTROL_RESULT_ERROR,
"'classes' entry must be a list"));
}
for (auto const& item : entry.second->listValue()) {
if (!item || (item->getType() != Element::string)) {
errmsg << "'classes' entry must be a list of strings";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
// Skip empty client classes.
if (!item->stringValue().empty()) {
selector.client_classes_.insert(item->stringValue());
}
}
continue;
} else {
errmsg << "unknown entry '" << entry.first << "'";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
}
ConstSubnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
getCfgSubnets4()->selectSubnet(selector);
if (!subnet) {
return (createAnswer(CONTROL_RESULT_EMPTY, "no selected subnet"));
}
SharedNetwork4Ptr network;
subnet->getSharedNetwork(subnet);
ostringstream msg;
if (network) {
msg << "selected shared network '" << network->getName()
<< "' starting with subnet '" << subnet->toText()
<< "' id " << subnet->getID();
} else {
msg << "selected subnet '" << subnet->toText()
<< "' id " << subnet->getID();
}
return (createAnswer(CONTROL_RESULT_SUCCESS, msg.str()));
}
ConstElementPtr
ControlledDhcpv4Srv::commandLocalize4o6Handler(const string&,
ConstElementPtr args) {
if (!args) {
return (createAnswer(CONTROL_RESULT_ERROR, "empty arguments"));
}
if (args->getType() != Element::map) {
return (createAnswer(CONTROL_RESULT_ERROR, "arguments must be a map"));
}
SubnetSelector selector;
selector.dhcp4o6_ = true;
for (auto const& entry : args->mapValue()) {
ostringstream errmsg;
if (entry.first == "interface") {
if (entry.second->getType() != Element::string) {
errmsg << "'interface' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.iface_name_ = entry.second->stringValue();
continue;
} else if (entry.first == "address") {
if (entry.second->getType() != Element::string) {
errmsg << "'address' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'address' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.ciaddr_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'address' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "relay") {
if (entry.second->getType() != Element::string) {
errmsg << "'relay' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'relay' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.giaddr_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'relay' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "local") {
if (entry.second->getType() != Element::string) {
errmsg << "'local' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'local' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.local_address_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'local' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "remote") {
if (entry.second->getType() != Element::string) {
errmsg << "'remote' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'remote' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.remote_address_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'remote' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "link") {
if (entry.second->getType() != Element::string) {
errmsg << "'link' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV6()) {
errmsg << "bad 'link' entry: not IPv6";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.first_relay_linkaddr_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'link' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "subnet") {
if (entry.second->getType() != Element::string) {
errmsg << "'subnet' entry must be a string";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
try {
IOAddress addr(entry.second->stringValue());
if (!addr.isV4()) {
errmsg << "bad 'subnet' entry: not IPv4";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
selector.option_select_ = addr;
continue;
} catch (const exception& ex) {
errmsg << "bad 'subnet' entry: " << ex.what();
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
} else if (entry.first == "classes") {
if (entry.second->getType() != Element::list) {
return (createAnswer(CONTROL_RESULT_ERROR,
"'classes' entry must be a list"));
}
for (auto const& item : entry.second->listValue()) {
if (!item || (item->getType() != Element::string)) {
errmsg << "'classes' entry must be a list of strings";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
// Skip empty client classes.
if (!item->stringValue().empty()) {
selector.client_classes_.insert(item->stringValue());
}
}
continue;
} else {
errmsg << "unknown entry '" << entry.first << "'";
return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
}
}
ConstSubnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
getCfgSubnets4()->selectSubnet4o6(selector);
if (!subnet) {
return (createAnswer(CONTROL_RESULT_EMPTY, "no selected subnet"));
}
SharedNetwork4Ptr network;
subnet->getSharedNetwork(subnet);
ostringstream msg;
if (network) {
msg << "selected shared network '" << network->getName()
<< "' starting with subnet '" << subnet->toText()
<< "' id " << subnet->getID();
} else {
msg << "selected subnet '" << subnet->toText()
<< "' id " << subnet->getID();
}
return (createAnswer(CONTROL_RESULT_SUCCESS, msg.str()));
}
ConstElementPtr
ControlledDhcpv4Srv::commandServerTagGetHandler(const std::string&,
ConstElementPtr) {
@@ -1133,6 +1464,12 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_P
CommandMgr::instance().registerCommand("leases-reclaim",
std::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, ph::_1, ph::_2));
CommandMgr::instance().registerCommand("localize4",
std::bind(&ControlledDhcpv4Srv::commandLocalize4Handler, this, ph::_1, ph::_2));
CommandMgr::instance().registerCommand("localize4o6",
std::bind(&ControlledDhcpv4Srv::commandLocalize4o6Handler, this, ph::_1, ph::_2));
CommandMgr::instance().registerCommand("server-tag-get",
std::bind(&ControlledDhcpv4Srv::commandServerTagGetHandler, this, ph::_1, ph::_2));
@@ -1216,6 +1553,8 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
CommandMgr::instance().deregisterCommand("dhcp-enable");
CommandMgr::instance().deregisterCommand("leases-reclaim");
CommandMgr::instance().deregisterCommand("server-tag-get");
CommandMgr::instance().deregisterCommand("localize4");
CommandMgr::instance().deregisterCommand("localize4o6");
CommandMgr::instance().deregisterCommand("shutdown");
CommandMgr::instance().deregisterCommand("statistic-get");
CommandMgr::instance().deregisterCommand("statistic-get-all");

View File

@@ -279,6 +279,32 @@ private:
commandLeasesReclaimHandler(const std::string& command,
isc::data::ConstElementPtr args);
/// @brief Handler for processing 'localize4' command
///
/// This handler processes localize4 command, which returns
/// the result of DHCPv4 subnet selected.
///
/// @param command (parameter ignored)
/// @param args arguments map { <selector>: <value> }
///
/// @return status of the command with the selection result
isc::data::ConstElementPtr
commandLocalize4Handler(const std::string& command,
isc::data::ConstElementPtr args);
/// @brief Handler for processing 'localize4o6' command
///
/// This handler processes localize4o6 command, which returns
/// the result of DHCP4o6 subnet selected.
///
/// @param command (parameter ignored)
/// @param args arguments map { <selector>: <value> }
///
/// @return status of the command with the selection result
isc::data::ConstElementPtr
commandLocalize4o6Handler(const std::string& command,
isc::data::ConstElementPtr args);
/// @brief handler for server-tag-get command
///
/// This method handles the server-tag-get command, which retrieves

View File

@@ -482,6 +482,8 @@ TEST_F(CtrlChannelDhcpv4SrvTest, commandsRegistration) {
EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos);
EXPECT_TRUE(command_list.find("\"localize4\"") != string::npos);
EXPECT_TRUE(command_list.find("\"localize4o6\"") != string::npos);
EXPECT_TRUE(command_list.find("\"server-tag-get\"") != string::npos);
EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);

View File

@@ -69,6 +69,9 @@ api_files += $(top_srcdir)/src/share/api/lease6-wipe.json
api_files += $(top_srcdir)/src/share/api/lease6-write.json
api_files += $(top_srcdir)/src/share/api/leases-reclaim.json
api_files += $(top_srcdir)/src/share/api/list-commands.json
api_files += $(top_srcdir)/src/share/api/localize4.json
api_files += $(top_srcdir)/src/share/api/localize4o6.json
api_files += $(top_srcdir)/src/share/api/localize6.json
api_files += $(top_srcdir)/src/share/api/network4-add.json
api_files += $(top_srcdir)/src/share/api/network4-del.json
api_files += $(top_srcdir)/src/share/api/network4-get.json

View File

@@ -0,0 +1,22 @@
{
"access": "read",
"avail": "2.7.0",
"brief": [ "This command returns the result of DHCPv4 subnet selection" ],
"cmd-comment": [ "Possible parameters are the incoming interface name, client address, relay address, client classes, etc." ],
"cmd-syntax": [
"{",
" \"command\": \"localize4\",",
" \"arguments\": {",
" \"interface\": <string>,",
" \"address\": <string>,",
" \"relay\": <string>,",
" \"classes\": [ <strings> ]",
" }",
"}"
],
"description": [ "See <xref linkend=\"command-localize4\"/>" ],
"name": "localize4",
"resp-comment": [ "3 forms for selected shared network, subnet or nothing.." ],
"resp-syntax": [ "{ \"result\": 1, \"text\": <localization result> }" ],
"support": [ "kea-dhcp4" ]
}

View File

@@ -0,0 +1,22 @@
{
"access": "read",
"avail": "2.7.0",
"brief": [ "This command returns the result of DHCPv4o6 subnet selection" ],
"cmd-comment": [ "Possible parameters are the incoming interface name, client address, relay address, client classes, etc." ],
"cmd-syntax": [
"{",
" \"command\": \"localize4o6\",",
" \"arguments\": {",
" \"interface\": <string>,",
" \"address\": <string>,",
" \"relay\": <string>,",
" \"classes\": [ <strings> ]",
" }",
"}"
],
"description": [ "See <xref linkend=\"command-localize4o6\"/>" ],
"name": "localize4o6",
"resp-comment": [ "3 forms for selected shared network, subnet or nothing.." ],
"resp-syntax": [ "{ \"result\": 1, \"text\": <localization result> }" ],
"support": [ "kea-dhcp4" ]
}

View File

@@ -0,0 +1,22 @@
{
"access": "read",
"avail": "2.7.0",
"brief": [ "This command returns the result of DHCPv6 subnet selection" ],
"cmd-comment": [ "Possible parameters are the incoming interface name, client address, relay address, client classes, etc." ],
"cmd-syntax": [
"{",
" \"command\": \"localize6\",",
" \"arguments\": {",
" \"interface\": <string>,",
" \"address\": <string>,",
" \"relay\": <string>,",
" \"classes\": [ <strings> ]",
" }",
"}"
],
"description": [ "See <xref linkend=\"command-localize6\"/>" ],
"name": "localize6",
"resp-comment": [ "3 forms for selected shared network, subnet or nothing.." ],
"resp-syntax": [ "{ \"result\": 1, \"text\": <localization result> }" ],
"support": [ "kea-dhcp6" ]
}