--- /dev/null
+/lease_cmds_messages.cc
+/lease_cmds_messages.h
+/s-messages
+/html
TYPE_HWADDR, ///< query by hardware address (v4 only)
TYPE_DUID ///< query by DUID (v6 only)
} Type;
-
+
/// @brief Specifies subnet-id (always used)
SubnetID subnet_id;
/// @brief Specifies identifier value (used when query_by_addr is false)
isc::dhcp::DuidPtr duid;
+ static Type txtToType(const std::string& txt) {
+ if (txt == "address") {
+ return (Parameters::TYPE_ADDR);
+ } else if (txt == "hw-address") {
+ return (Parameters::TYPE_HWADDR);
+ } else if (txt == "duid") {
+ return (Parameters::TYPE_DUID);
+ } else {
+ isc_throw(BadValue, "Incorrect identifier type: "
+ << txt << ", the only supported values are: "
+ "address, hw-address, duid");
+ }
+ }
+
/// @brief specifies parameter types (true = query by address, false =
/// query by indetifier-type,identifier)
Type query_type;
+ Lease::Type lease_type;
+
+ uint32_t iaid;
+
/// @brief Default contstructor.
Parameters()
- :addr("::"), query_type(TYPE_ADDR) {
+ :addr("::"), query_type(TYPE_ADDR), lease_type(Lease::TYPE_NA), iaid(0) {
}
};
/// "valid-lft": 3600,
/// "expire": 1499282530,
/// "subnet-id": 1,
- /// "fdqn_fwd": true,
- /// "fqdn_rev": true,
+ /// "fdqn-fwd": true,
+ /// "fqdn-rev": true,
/// "hostname": "myhost.example.org",
/// "state": 0
/// }
};
LeaseCmdsImpl::LeaseCmdsImpl() {
+ /// @todo: Remove family_
family_ = CfgMgr::instance().getFamily();
registerCommands();
isc_throw(isc::BadValue, "'lease' parameters must be specified");
}
+ ConstSrvConfigPtr config = CfgMgr::instance().getCurrentCfg();
+
Lease4Ptr lease4;
Lease6Ptr lease6;
if (v4) {
- LeaseDataParser4 parser;
- lease4 = parser.parse(lease_data);
+ Lease4Parser parser;
+ lease4 = parser.parse(config, lease_data);
- checkLeaseIntegrity(lease4);
+ // checkLeaseIntegrity(config, lease4);
if (lease4) {
- LeaseMgrFactory::instance().add(lease4);
+ LeaseMgrFactory::instance().addLease(lease4);
}
} else {
- LeaseDataParser6 parser;
- lease6 = parser.parse(lease_data);
+ Lease6Parser parser;
+ lease6 = parser.parse(config, lease_data);
- checkLeaseIntegrity(lease6);
+ // checkLeaseIntegrity(config, lease6);
if (lease6) {
- LeaseMgrFactory::instance().add(lease6);
+ LeaseMgrFactory::instance().addLease(lease6);
}
}
} catch (const std::exception& ex) {
- LOG_ERROR(lease_cmds_logger, LEASE_CMDS_RESERV_ADD_FAILED)
+ LOG_ERROR(lease_cmds_logger, v4 ? LEASE_CMDS_ADD4_FAILED : LEASE_CMDS_ADD6_FAILED)
.arg(txt)
.arg(ex.what());
return (createAnswer(CONTROL_RESULT_ERROR, ex.what()));
}
x.subnet_id = tmp->intValue();
- tmp = params->get("ip-address");
- ConstLeasePtr host;
+ tmp = params->get("address");
if (tmp) {
if (tmp->getType() != Element::string) {
- isc_throw(BadValue, "'ip-address' is not a string.");
+ isc_throw(BadValue, "'address' is not a string.");
}
x.addr = IOAddress(tmp->stringValue());
- x.query_by_addr = true;
+ x.query_type = Parameters::TYPE_ADDR;
return (x);
}
}
// Got the parameters. Let's see if their values make sense.
-
- // Try to parse the identifier value first.
- try {
- x.ident = util::str::quotedStringToBinary(ident->stringValue());
- if (x.ident.empty()) {
- util::str::decodeFormattedHexString(ident->stringValue(),
- x.ident);
- }
- } catch (...) {
- // The string doesn't match any known pattern, so we have to
- // report an error at this point.
- isc_throw(BadValue, "Unable to parse 'identifier' value.");
+ // Try to convert identifier-type
+ x.query_type = Parameters::txtToType(type->stringValue());
+
+ switch (x.query_type) {
+ case Parameters::TYPE_HWADDR: {
+ HWAddr hw = HWAddr::fromText(ident->stringValue());
+ x.hwaddr = HWAddrPtr(new HWAddr(hw));
+ break;
}
-
- if (x.ident.empty()) {
- isc_throw(BadValue, "Unable to query for empty 'identifier'.");
+ case Parameters::TYPE_DUID: {
+ DUID duid = DUID::fromText(ident->stringValue());
+ x.duid = DuidPtr(new DUID(duid));
+ }
+ case Parameters::TYPE_ADDR: {
+ // We should never get here. The address clause should have been caught
+ // earlier.
+ return (x);
+ }
+ default: {
+ isc_throw(BadValue, "Identifier type " << type->stringValue() <<
+ " is not supported.");
}
-
- // Next, try to convert identifier-type
- try {
- x.type = Lease::getIdentifierType(type->stringValue());
- } catch (const std::exception& ex) {
- isc_throw(BadValue, "Value of 'identifier-type' was not recognized.");
}
-
- x.query_by_addr = false;
return (x);
}
p = getParameters(params);
switch (p.query_type) {
- case TYPE_ADDR: {
+ case Parameters::TYPE_ADDR: {
// Query by address
if (v4) {
- lease4 = LeaseMgr::instance().getLease4(p.subnet_id, p.addr);
+ lease4 = LeaseMgrFactory::instance().getLease4(p.addr);
} else {
- lease6 = LeaseMgr::instance().getLease6(p.subnet_id, p.addr);
+ lease6 = LeaseMgrFactory::instance().getLease6(p.lease_type, p.addr);
}
break;
}
- case TYPE_HWADDR:
+ case Parameters::TYPE_HWADDR:
if (v4) {
- lease4 = LeaseMgr::instance().getLease4(p.subnet_id, p.hwaddr);
+ if (!p.hwaddr) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "Program error: Query by hw-address "
+ "requires hwaddr to be specified"));
+ }
+ lease4 = LeaseMgrFactory::instance().getLease4(*p.hwaddr, p.subnet_id);
} else {
return (createAnswer(CONTROL_RESULT_ERROR,
"Query by hw-address is not allowed in v6."));
}
- case TYPE_DUID:
- if (v6) {
- lease6 = LeaseMgr::instance().getLease6(p.subnet_id, p.duid);
+ case Parameters::TYPE_DUID:
+ if (!v4) {
+ if (!p.duid) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "Program error: Query by duid "
+ "requires duid to be specified"));
+ }
+ lease6 = LeaseMgrFactory::instance().getLease6(p.lease_type, *p.duid,
+ p.iaid, p.subnet_id);
} else {
return (createAnswer(CONTROL_RESULT_ERROR,
"Query by duid is not allowed in v4."));
}
- default:
- return (createAnswer(CONTROL_RESULT_ERROR,
- "Unknown query type: " << static_cast<int>(p.query_type)));
+ default: {
+ stringstream tmp;
+ tmp << "Unknown query type: " << static_cast<int>(p.query_type);
+ return (createAnswer(CONTROL_RESULT_ERROR, tmp.str()));
+ }
}
} catch (const std::exception& ex) {
- return (createAnswer(CONTROL_RESULT_ERROR,
- "Failure during leaseX-get: " << ex.what()));
+ stringstream tmp;
+ tmp << "Failure during leaseX-get: " << ex.what();
+ return (createAnswer(CONTROL_RESULT_ERROR, tmp.str()));
}
ElementPtr lease_json;
if (v4 && lease4) {
- lease_json = lease4->toElement4();
+ lease_json = lease4->toElement();
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "DHCPv4 lease found.", lease_json));
}
if (!v4 && lease6) {
- lease_json = lease6->toElement6();
+ lease_json = lease6->toElement();
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "DHCPv6 lease found.", lease_json));
}
- if (lease) {
- return (createAnswer(CONTROL_RESULT_SUCCESS, "Lease found.", lease_json));
- } else {
- return (createAnswer(CONTROL_RESULT_SUCCESS, "Lease not found."));
- }
+
+ // If we got here, the lease has not been found.
+ return (createAnswer(CONTROL_RESULT_EMPTY, "Lease not found."));
}
ConstElementPtr
-LeaseCmdsImpl::reservationDelHandler(const std::string& /*name*/,
- ConstElementPtr params) {
+LeaseCmdsImpl::leaseDelHandler(const std::string& name,
+ ConstElementPtr params) {
Parameters p;
- bool deleted;
+ bool deleted = false;
+#if 0
try {
p = getParameters(params);
if (p.query_by_addr) {
// try to delete by address
- deleted = LeaseMgr::instance().del(p.subnet_id, p.addr);
+ deleted = LeaseMgrFactory::instance().del(p.subnet_id, p.addr);
} else {
// try to delete by identifier
if (family_ == AF_INET) {
- deleted = LeaseMgr::instance().del4(p.subnet_id, p.type,
- &p.ident[0], p.ident.size());
+ deleted = LeaseMgrFactory::instance().del4(p.subnet_id, p.type,
+ &p.ident[0], p.ident.size());
} else {
- deleted = LeaseMgr::instance().del6(p.subnet_id, p.type,
- &p.ident[0], p.ident.size());
+ deleted = LeaseMgrFactory::instance().del6(p.subnet_id, p.type,
+ &p.ident[0], p.ident.size());
}
}
} catch (const std::exception& ex) {
return (createAnswer(CONTROL_RESULT_ERROR, ex.what()));
}
+#endif
if (deleted) {
return (createAnswer(CONTROL_RESULT_SUCCESS, "Lease deleted."));
}
}
+ConstElementPtr
+LeaseCmdsImpl::leaseUpdateHandler(const string& command, ConstElementPtr args) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "not implemented yet."));
+}
+
+ConstElementPtr
+LeaseCmdsImpl::leaseWipeHandler(const string& command, ConstElementPtr args) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "not implemented yet."));
+}
+
+
uint16_t LeaseCmdsImpl::family_ = AF_INET;
-LeaseDataSourcePtr LeaseCmdsImpl::db_storage_;
LeaseCmds::LeaseCmds()
:impl_(new LeaseCmdsImpl()) {
This info message indicates that the Lease Commands hooks library has been
removed successfully.
-% LEASE_CMDS_ADD4_FAILED Lease4-add command failed (parameters: %1, reason: %2)
+% LEASE_CMDS_ADD4_FAILED lease4-add command failed (parameters: %1, reason: %2)
The lease4-add command has failed. Both the reason as well as the
parameters passed are logged.
-% LEASE_CMDS_ADD6_FAILED Lease4-add command failed (parameters: %1, reason: %2)
+% LEASE_CMDS_ADD6_FAILED Lease6-add command failed (parameters: %1, reason: %2)
The lease6-add command has failed. Both the reason as well as the
parameters passed are logged.
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <cc/data.h>
+#include <dhcp/hwaddr.h>
+#include <asiolink/io_address.h>
+#include <dhcpsrv/lease.h>
+#include <lease_parser.h>
-// V4:
-// address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state
+#include <config.h>
-// V6:
-// address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
-#include <config.h>
+// Can't use a constructor as a function
+namespace {
+IOAddress buildIOAddress(const std::string& str) { return (IOAddress(str)); }
+};
+
+namespace isc {
+namespace lease_cmds {
+
+IOAddress
+LeaseParser::getIOAddress(const ConstElementPtr& scope,
+ const std::string& name) {
+ return (getAndConvert<IOAddress,
+ buildIOAddress>(scope, name, "address"));
+}
+
+Lease4Ptr
+Lease4Parser::parse(ConstSrvConfigPtr& cfg,
+ const ConstElementPtr& lease_info) {
+ if (!lease_info) {
+ isc_throw(BadValue, "lease information missing");
+ }
+
+ // These are mandatory parameters.
+ IOAddress addr = getIOAddress(lease_info, "ip-address");
+ SubnetID subnet_id = getUint32(lease_info, "subnet-id");
+
+ if (!addr.isV4()) {
+ isc_throw(BadValue, "Non-IPv4 address specified: " << addr);
+ }
+
+ // Not a most straightforward conversion, but it works.
+ string hwaddr_txt = getString(lease_info, "hw-address");
+ HWAddr hwaddr = HWAddr::fromText(hwaddr_txt);
+ HWAddrPtr hwaddr_ptr = HWAddrPtr(new HWAddr(hwaddr));
+
+ Subnet4Ptr subnet = cfg->getCfgSubnets4()->getSubnet(subnet_id);
+ if (!subnet) {
+ isc_throw(BadValue, "Invalid subnet-id: No IPv4 subnet with subnet-id="
+ << subnet_id << " currently configured.");
+ }
+
+ // Client-id is optional.
+ ClientIdPtr client_id;
+ if (lease_info->contains("client-id")) {
+ string txt = getString(lease_info, "client-id");
+ client_id = ClientId::fromText(txt);
+ }
+
+ // These parameters are optional. If not specified, we'll derive them from
+ // the current subnet configuration, if possible.
+ uint32_t valid_lft = 0;
+ if (lease_info->contains("valid-lft")) {
+ valid_lft = getUint32(lease_info, "valid-lft");
+ } else {
+ valid_lft = subnet->getValid();
+ }
+
+ /// Let's calculate client last transmission time (cltt). If expiration
+ /// timestamp is specified explicitly, we will use that. Note there are no
+ /// checks whether this is in the past. There may be valid cases when user
+ /// wants to insert expired leases, e.g. when migrating from one DHCP server
+ /// to another and wants to migrate the database as is, without disarding
+ /// any leases.
+ time_t cltt;
+ if (lease_info->contains("expire")) {
+ int64_t tmp = getUint32(lease_info, "expire");
+ cltt = static_cast<time_t>(tmp - valid_lft);
+ } else {
+ cltt = time(NULL);
+ }
+
+ bool fqdn_fwd = false;
+ if (lease_info->contains("fqdn-fwd")) {
+ fqdn_fwd = getBoolean(lease_info, "fqdn-fwd");
+ }
+ bool fqdn_rev = false;
+ if (lease_info->contains("fqdn-rev")) {
+ fqdn_rev = getBoolean(lease_info, "fqdn-rev");
+ }
+ string hostname;
+ if (lease_info->contains("hostname")) {
+ hostname = getString(lease_info, "hostname");
+ }
+ if (hostname.empty() && (fqdn_fwd || fqdn_rev)) {
+ isc_throw(BadValue, "No hostname specified and either forward or reverse"
+ " fqdn was set to true.");
+ }
+
+ uint32_t state = 0;
+ if (lease_info->contains("state")) {
+ state = getUint8(lease_info, "state");
+ }
+
+ // Let's fabricate some data and we're ready to go.
+ uint32_t t1 = subnet->getT1();
+ uint32_t t2 = subnet->getT2();
+
+ Lease4Ptr l(new Lease4(addr, hwaddr_ptr, client_id, valid_lft, t1, t2,
+ cltt, subnet_id,
+ fqdn_fwd, fqdn_rev, hostname));
+ l->state_ = state;
+ return (l);
+}
+
+Lease6Ptr
+Lease6Parser::parse(ConstSrvConfigPtr& cfg,
+ const ConstElementPtr& lease_info) {
+ if (!lease_info) {
+ isc_throw(BadValue, "lease information missing");
+ }
+
+ // These are mandatory parameters.
+ IOAddress addr = getIOAddress(lease_info, "ip-address");
+ SubnetID subnet_id = getUint32(lease_info, "subnet-id");
+
+ if (addr.isV4()) {
+ isc_throw(BadValue, "Non-IPv6 address specified: " << addr);
+ }
+
+ // Not a most straightforward conversion, but it works.
+ string duid_txt = getString(lease_info, "duid");
+ DUID duid = DUID::fromText(duid_txt);
+ DuidPtr duid_ptr = DuidPtr(new DUID(duid));
+
+ Subnet6Ptr subnet = cfg->getCfgSubnets6()->getSubnet(subnet_id);
+ if (!subnet) {
+ isc_throw(BadValue, "Invalid subnet-id: No IPv6 subnet with subnet-id="
+ << subnet_id << " currently configured.");
+ }
+
+ Lease::Type type = Lease::TYPE_NA;
+ uint8_t prefix_len = 128;
+ if (lease_info->contains("type")) {
+ string txt = getString(lease_info, "type");
+ if (txt == "IA_NA") {
+ type = Lease::TYPE_NA;
+ } else if (txt == "IA_TA") {
+ type = Lease::TYPE_TA;
+ } else if (txt == "IA_PD") {
+ type = Lease::TYPE_PD;
+
+ prefix_len = getUint8(lease_info, "prefix-len");
+ } else {
+ isc_throw(BadValue, "Incorrect lease type: " << txt << ", the only "
+ "supported values are: na, ta and pd");
+ }
+ }
+
+ uint32_t iaid = getUint32(lease_info, "iaid");
+
+ // Hw-address is optional in v6 leases.
+ HWAddrPtr hwaddr_ptr;
+ if (lease_info->contains("hw-address")) {
+ string hwaddr_txt = getString(lease_info, "hw-address");
+ HWAddr hwaddr = HWAddr::fromText(hwaddr_txt);
+ hwaddr_ptr = HWAddrPtr(new HWAddr(hwaddr));
+ }
+
+ // These parameters are optional. If not specified, we'll derive them
+ // from the current subnet configuration, if possible.
+ uint32_t valid_lft = 0;
+ if (lease_info->contains("valid-lft")) {
+ valid_lft = getUint32(lease_info, "valid-lft");
+ } else {
+ valid_lft = subnet->getValid();
+ }
+
+ // These parameters are optional. If not specified, we'll derive them
+ // from the current subnet configuration, if possible.
+ uint32_t pref_lft = 0;
+ if (lease_info->contains("preferred-lft")) {
+ pref_lft = getUint32(lease_info, "preferred-lft");
+ } else {
+ pref_lft = subnet->getValid();
+ }
+
+ /// Let's calculate client last transmission time (cltt). If expiration
+ /// timestamp is specified explicitly, we will use that. Note there are
+ /// no checks whether this is in the past. There may be valid cases when
+ /// user wants to insert expired leases, e.g. when migrating from one
+ /// DHCP server to another and wants to migrate the database as is, without
+ /// disarding any leases.
+ time_t cltt;
+ if (lease_info->contains("expire")) {
+ int64_t tmp = getUint32(lease_info, "expire");
+ cltt = static_cast<time_t>(tmp - valid_lft);
+ } else {
+ cltt = time(NULL);
+ }
+
+ bool fqdn_fwd = false;
+ if (lease_info->contains("fqdn-fwd")) {
+ fqdn_fwd = getBoolean(lease_info, "fqdn-fwd");
+ }
+ bool fqdn_rev = false;
+ if (lease_info->contains("fqdn-rev")) {
+ fqdn_rev = getBoolean(lease_info, "fqdn-rev");
+ }
+ string hostname;
+ if (lease_info->contains("hostname")) {
+ hostname = getString(lease_info, "hostname");
+ }
+ if (hostname.empty() && (fqdn_fwd || fqdn_rev)) {
+ isc_throw(BadValue, "No hostname specified and either forward or reverse"
+ " fqdn was set to true.");
+ }
+
+ uint32_t state = 0;
+ if (lease_info->contains("state")) {
+ state = getUint8(lease_info, "state");
+ }
+
+ // Let's fabricate some data and we're ready to go.
+ uint32_t t1 = subnet->getT1();
+ uint32_t t2 = subnet->getT2();
+ Lease6Ptr l(new Lease6(type, addr, duid_ptr, iaid, pref_lft, valid_lft, t1, t2,
+ subnet_id, fqdn_fwd, fqdn_rev, hostname,
+ hwaddr_ptr, prefix_len));
+ l->cltt_ = cltt;
+ l->state_ = state;
+ return (l);
+}
-
+};
+};
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
#include <cc/data.h>
#include <cc/simple_parser.h>
#include <dhcpsrv/lease.h>
+#include <dhcpsrv/srv_config.h>
namespace isc {
namespace lease_cmds {
-/// @brief Parser for a single Lease information.
+/// @brief Base class for Lease4 and Lease6 parsers
class LeaseParser : public isc::data::SimpleParser {
+protected:
+
+ /// @brief Returns an address from JSON structure
+ ///
+ /// @param scope a map the element will be searched at
+ /// @param name key name to be searched for
+ /// @return IOAddress representation
+ isc::asiolink::IOAddress getIOAddress(const isc::data::ConstElementPtr& scope,
+ const std::string& name);
};
+/// @brief Parser for Lease4 structure
+///
+/// It expects the data in the following format:
+/// {
+/// "ip-address": "192.0.2.1",
+/// "hw-address": "00:01:02:03:04:05",
+/// "client-id": "this-is-a-client",
+/// "valid-lft": 3600,
+/// "cltt": 12345678,
+/// "expire": 1499282530,
+/// "subnet-id": 1,
+/// "fdqn-fwd": true,
+/// "fqdn-rev": true,
+/// "hostname": "myhost.example.org",
+/// "state": 0
+/// }
class Lease4Parser : public LeaseParser {
public:
- virtual isc::dhcp::Lease4Ptr parse(const isc::data::ConstElementPtr& lease_info);
+
+ /// @brief Parses Element tree and tries to convert to Lease4
+ ///
+ /// See @ref Lease6Parser class description for expected format.
+ ///
+ /// @param cfg Currently running config (used for sanity checks and defaults)
+ /// @param lease_info structure to be parsed
+ /// @return A pointer to Lease4
+ /// @throw BadValue if any of the parameters is invalid
+ /// @throw DhcpConfigError if mandatory parameter is missing
+ virtual isc::dhcp::Lease4Ptr parse(isc::dhcp::ConstSrvConfigPtr& cfg,
+ const isc::data::ConstElementPtr& lease_info);
+
+ /// @brief virtual dtor (does nothing)
virtual ~Lease4Parser() {}
};
+/// @brief Parser for Lease6 structure
+///
+/// {
+/// "address": "2001:db8::1",
+/// "duid": "00:01:02:03:04:05",
+/// "type": "IA_NA",
+/// "cltt": 12345678,
+/// "preferred-lft": 3600,
+/// "valid-lft": 3600,
+/// "expire": 1499282530,
+/// "subnet-id": 1,
+/// "fdqn-fwd": true,
+/// "fqdn-rev": true,
+/// "hostname": "myhost.example.org",
+/// "state": 0
+/// }
+
+/// It expects the input data to use the following format:
class Lease6Parser : public LeaseParser {
public:
- virtual isc::dhcp::Lease6Ptr parse(const isc::data::ConstElementPtr& lease_info);
+ /// @brief Parses Element tree and tries to convert to Lease4
+ ///
+ /// See @ref Lease6Parser class description for expected format.
+ ///
+ /// @param cfg Currently running config (used for sanity checks and defaults)
+ /// @param lease_info structure to be parsed
+ /// @return A pointer to Lease4
+ /// @throw BadValue if any of the parameters is invalid
+ /// @throw DhcpConfigError if mandatory parameter is missing
+ virtual isc::dhcp::Lease6Ptr parse(isc::dhcp::ConstSrvConfigPtr& cfg,
+ const isc::data::ConstElementPtr& lease_info);
+
+ /// @brief virtual dtor (does nothing)
virtual ~Lease6Parser() {}
};
-
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
#endif
-
--- /dev/null
+host_cmds_unittests
+host_cmds_unittests.log
+host_cmds_unittests.trs
+test-suite.log
+*~
\ No newline at end of file
--- /dev/null
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <hooks/hooks.h>
+
+extern "C" {
+
+/// @brief returns Kea hooks version.
+int version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+}