ncr_msg.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. // Copyright (C) 2013, 2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <dhcp_ddns/ncr_msg.h>
  15. #include <dns/name.h>
  16. #include <asiolink/io_address.h>
  17. #include <asiolink/io_error.h>
  18. #include <cryptolink/cryptolink.h>
  19. #include <cryptolink/crypto_hash.h>
  20. #include <boost/algorithm/string/predicate.hpp>
  21. #include <sstream>
  22. #include <limits>
  23. namespace isc {
  24. namespace dhcp_ddns {
  25. NameChangeFormat stringToNcrFormat(const std::string& fmt_str) {
  26. if (boost::iequals(fmt_str, "JSON")) {
  27. return FMT_JSON;
  28. }
  29. isc_throw(BadValue, "Invalid NameChangeRequest format: " << fmt_str);
  30. }
  31. std::string ncrFormatToString(NameChangeFormat format) {
  32. if (format == FMT_JSON) {
  33. return ("JSON");
  34. }
  35. std::ostringstream stream;
  36. stream << "UNKNOWN(" << format << ")";
  37. return (stream.str());
  38. }
  39. /********************************* D2Dhcid ************************************/
  40. namespace {
  41. ///
  42. /// @name Constants which define DHCID identifier-type
  43. //@{
  44. /// DHCID created from client's HW address.
  45. const uint8_t DHCID_ID_HWADDR = 0x0;
  46. /// DHCID created from client identifier.
  47. const uint8_t DHCID_ID_CLIENTID = 0x1;
  48. /// DHCID created from DUID.
  49. const uint8_t DHCID_ID_DUID = 0x2;
  50. }
  51. D2Dhcid::D2Dhcid() {
  52. }
  53. D2Dhcid::D2Dhcid(const std::string& data) {
  54. fromStr(data);
  55. }
  56. D2Dhcid::D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
  57. const std::vector<uint8_t>& wire_fqdn) {
  58. fromHWAddr(hwaddr, wire_fqdn);
  59. }
  60. D2Dhcid::D2Dhcid(const std::vector<uint8_t>& clientid_data,
  61. const std::vector<uint8_t>& wire_fqdn) {
  62. fromClientId(clientid_data, wire_fqdn);
  63. }
  64. D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
  65. const std::vector<uint8_t>& wire_fqdn) {
  66. fromDUID(duid, wire_fqdn);
  67. }
  68. void
  69. D2Dhcid::fromStr(const std::string& data) {
  70. bytes_.clear();
  71. try {
  72. isc::util::encode::decodeHex(data, bytes_);
  73. } catch (const isc::Exception& ex) {
  74. isc_throw(NcrMessageError, "Invalid data in Dhcid: " << ex.what());
  75. }
  76. }
  77. std::string
  78. D2Dhcid::toStr() const {
  79. return (isc::util::encode::encodeHex(bytes_));
  80. }
  81. void
  82. D2Dhcid::fromClientId(const std::vector<uint8_t>& clientid_data,
  83. const std::vector<uint8_t>& wire_fqdn) {
  84. createDigest(DHCID_ID_CLIENTID, clientid_data, wire_fqdn);
  85. }
  86. void
  87. D2Dhcid::fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
  88. const std::vector<uint8_t>& wire_fqdn) {
  89. if (!hwaddr) {
  90. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  91. "unable to compute DHCID from the HW address, "
  92. "NULL pointer has been specified");
  93. } else if (hwaddr->hwaddr_.empty()) {
  94. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  95. "unable to compute DHCID from the HW address, "
  96. "HW address is empty");
  97. }
  98. std::vector<uint8_t> hwaddr_data;
  99. hwaddr_data.push_back(hwaddr->htype_);
  100. hwaddr_data.insert(hwaddr_data.end(), hwaddr->hwaddr_.begin(),
  101. hwaddr->hwaddr_.end());
  102. createDigest(DHCID_ID_HWADDR, hwaddr_data, wire_fqdn);
  103. }
  104. void
  105. D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
  106. const std::vector<uint8_t>& wire_fqdn) {
  107. createDigest(DHCID_ID_DUID, duid.getDuid(), wire_fqdn);
  108. }
  109. void
  110. D2Dhcid::createDigest(const uint8_t identifier_type,
  111. const std::vector<uint8_t>& identifier_data,
  112. const std::vector<uint8_t>& wire_fqdn) {
  113. // We get FQDN in the wire format, so we don't know if it is
  114. // valid. It is caller's responsibility to make sure it is in
  115. // the valid format. Here we just make sure it is not empty.
  116. if (wire_fqdn.empty()) {
  117. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  118. "empty FQDN used to create DHCID");
  119. }
  120. // It is a responsibility of the classes which encapsulate client
  121. // identifiers, e.g. DUID, to validate the client identifier data.
  122. // But let's be on the safe side and at least check that it is not
  123. // empty.
  124. if (identifier_data.empty()) {
  125. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  126. "empty DUID used to create DHCID");
  127. }
  128. // A data buffer will be used to compute the digest.
  129. std::vector<uint8_t> data = identifier_data;
  130. // Append FQDN in the wire format.
  131. data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
  132. // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
  133. isc::util::OutputBuffer hash(0);
  134. try {
  135. // We have checked already that the DUID and FQDN aren't empty
  136. // so it is safe to assume that the data buffer is not empty.
  137. cryptolink::digest(&data[0], data.size(), cryptolink::SHA256, hash);
  138. } catch (const std::exception& ex) {
  139. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  140. "error while generating DHCID from DUID: "
  141. << ex.what());
  142. }
  143. // The DHCID RDATA has the following structure:
  144. //
  145. // < identifier-type > < digest-type > < digest >
  146. //
  147. // where identifier type
  148. // Let's allocate the space for the identifier-type (2 bytes) and
  149. // digest-type (1 byte). This is 3 bytes all together.
  150. bytes_.resize(3 + hash.getLength());
  151. // Leave first byte 0 and set the second byte. Those two bytes
  152. // form the identifier-type.
  153. bytes_[1] = identifier_type;
  154. // Third byte is always equal to 1, which specifies SHA-256 digest type.
  155. bytes_[2] = 1;
  156. // Now let's append the digest.
  157. std::memcpy(&bytes_[3], hash.getData(), hash.getLength());
  158. }
  159. std::ostream&
  160. operator<<(std::ostream& os, const D2Dhcid& dhcid) {
  161. os << dhcid.toStr();
  162. return (os);
  163. }
  164. /**************************** NameChangeRequest ******************************/
  165. NameChangeRequest::NameChangeRequest()
  166. : change_type_(CHG_ADD), forward_change_(false),
  167. reverse_change_(false), fqdn_(""), ip_io_address_("0.0.0.0"),
  168. dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
  169. }
  170. NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
  171. const bool forward_change, const bool reverse_change,
  172. const std::string& fqdn, const std::string& ip_address,
  173. const D2Dhcid& dhcid,
  174. const uint64_t lease_expires_on,
  175. const uint32_t lease_length)
  176. : change_type_(change_type), forward_change_(forward_change),
  177. reverse_change_(reverse_change), fqdn_(fqdn), ip_io_address_("0.0.0.0"),
  178. dhcid_(dhcid), lease_expires_on_(lease_expires_on),
  179. lease_length_(lease_length), status_(ST_NEW) {
  180. // User setter to validate fqdn.
  181. setFqdn(fqdn);
  182. // User setter to validate address.
  183. setIpAddress(ip_address);
  184. // Validate the contents. This will throw a NcrMessageError if anything
  185. // is invalid.
  186. validateContent();
  187. }
  188. NameChangeRequestPtr
  189. NameChangeRequest::fromFormat(const NameChangeFormat format,
  190. isc::util::InputBuffer& buffer) {
  191. // Based on the format requested, pull the marshalled request from
  192. // InputBuffer and pass it into the appropriate format-specific factory.
  193. NameChangeRequestPtr ncr;
  194. switch (format) {
  195. case FMT_JSON: {
  196. try {
  197. // Get the length of the JSON text.
  198. size_t len = buffer.readUint16();
  199. // Read the text from the buffer into a vector.
  200. std::vector<uint8_t> vec;
  201. buffer.readVector(vec, len);
  202. // Turn the vector into a string.
  203. std::string string_data(vec.begin(), vec.end());
  204. // Pass the string of JSON text into JSON factory to create the
  205. // NameChangeRequest instance. Note the factory may throw
  206. // NcrMessageError.
  207. ncr = NameChangeRequest::fromJSON(string_data);
  208. } catch (isc::util::InvalidBufferPosition& ex) {
  209. // Read error accessing data in InputBuffer.
  210. isc_throw(NcrMessageError, "fromFormat: buffer read error: "
  211. << ex.what());
  212. }
  213. break;
  214. }
  215. default:
  216. // Programmatic error, shouldn't happen.
  217. isc_throw(NcrMessageError, "fromFormat - invalid format");
  218. break;
  219. }
  220. return (ncr);
  221. }
  222. void
  223. NameChangeRequest::toFormat(const NameChangeFormat format,
  224. isc::util::OutputBuffer& buffer) const {
  225. // Based on the format requested, invoke the appropriate format handler
  226. // which will marshal this request's contents into the OutputBuffer.
  227. switch (format) {
  228. case FMT_JSON: {
  229. // Invoke toJSON to create a JSON text of this request's contents.
  230. std::string json = toJSON();
  231. uint16_t length = json.size();
  232. // Write the length of the JSON text to the OutputBuffer first, then
  233. // write the JSON text itself.
  234. buffer.writeUint16(length);
  235. buffer.writeData(json.c_str(), length);
  236. break;
  237. }
  238. default:
  239. // Programmatic error, shouldn't happen.
  240. isc_throw(NcrMessageError, "toFormat - invalid format");
  241. break;
  242. }
  243. }
  244. NameChangeRequestPtr
  245. NameChangeRequest::fromJSON(const std::string& json) {
  246. // This method leverages the existing JSON parsing provided by isc::data
  247. // library. Should this prove to be a performance issue, it may be that
  248. // lighter weight solution would be appropriate.
  249. // Turn the string of JSON text into an Element set.
  250. isc::data::ElementPtr elements;
  251. try {
  252. elements = isc::data::Element::fromJSON(json);
  253. } catch (isc::data::JSONError& ex) {
  254. isc_throw(NcrMessageError,
  255. "Malformed NameChangeRequest JSON: " << ex.what());
  256. }
  257. // Get a map of the Elements, keyed by element name.
  258. ElementMap element_map = elements->mapValue();
  259. isc::data::ConstElementPtr element;
  260. // Use default constructor to create a "blank" NameChangeRequest.
  261. NameChangeRequestPtr ncr(new NameChangeRequest());
  262. // For each member of NameChangeRequest, find its element in the map and
  263. // call the appropriate Element-based setter. These setters may throw
  264. // NcrMessageError if the given Element is the wrong type or its data
  265. // content is lexically invalid. If the element is NOT found in the
  266. // map, getElement will throw NcrMessageError indicating the missing
  267. // member. Currently there are no optional values.
  268. element = ncr->getElement("change-type", element_map);
  269. ncr->setChangeType(element);
  270. element = ncr->getElement("forward-change", element_map);
  271. ncr->setForwardChange(element);
  272. element = ncr->getElement("reverse-change", element_map);
  273. ncr->setReverseChange(element);
  274. element = ncr->getElement("fqdn", element_map);
  275. ncr->setFqdn(element);
  276. element = ncr->getElement("ip-address", element_map);
  277. ncr->setIpAddress(element);
  278. element = ncr->getElement("dhcid", element_map);
  279. ncr->setDhcid(element);
  280. element = ncr->getElement("lease-expires-on", element_map);
  281. ncr->setLeaseExpiresOn(element);
  282. element = ncr->getElement("lease-length", element_map);
  283. ncr->setLeaseLength(element);
  284. // All members were in the Element set and were correct lexically. Now
  285. // validate the overall content semantically. This will throw an
  286. // NcrMessageError if anything is amiss.
  287. ncr->validateContent();
  288. // Everything is valid, return the new instance.
  289. return (ncr);
  290. }
  291. std::string
  292. NameChangeRequest::toJSON() const {
  293. // Create a JSON string of this request's contents. Note that this method
  294. // does NOT use the isc::data library as generating the output is straight
  295. // forward.
  296. std::ostringstream stream;
  297. stream << "{\"change-type\":" << getChangeType() << ","
  298. << "\"forward-change\":"
  299. << (isForwardChange() ? "true" : "false") << ","
  300. << "\"reverse-change\":"
  301. << (isReverseChange() ? "true" : "false") << ","
  302. << "\"fqdn\":\"" << getFqdn() << "\","
  303. << "\"ip-address\":\"" << getIpAddress() << "\","
  304. << "\"dhcid\":\"" << getDhcid().toStr() << "\","
  305. << "\"lease-expires-on\":\"" << getLeaseExpiresOnStr() << "\","
  306. << "\"lease-length\":" << getLeaseLength() << "}";
  307. return (stream.str());
  308. }
  309. void
  310. NameChangeRequest::validateContent() {
  311. //@todo This is an initial implementation which provides a minimal amount
  312. // of validation. FQDN and DHCID members are all currently
  313. // strings, these may be replaced with richer classes.
  314. if (fqdn_ == "") {
  315. isc_throw(NcrMessageError, "FQDN cannot be blank");
  316. }
  317. // Validate the DHCID.
  318. if (dhcid_.getBytes().size() == 0) {
  319. isc_throw(NcrMessageError, "DHCID cannot be blank");
  320. }
  321. // Ensure the request specifies at least one direction to update.
  322. if (!forward_change_ && !reverse_change_) {
  323. isc_throw(NcrMessageError,
  324. "Invalid Request, forward and reverse flags are both false");
  325. }
  326. }
  327. isc::data::ConstElementPtr
  328. NameChangeRequest::getElement(const std::string& name,
  329. const ElementMap& element_map) const {
  330. // Look for "name" in the element map.
  331. ElementMap::const_iterator it = element_map.find(name);
  332. if (it == element_map.end()) {
  333. // Didn't find the element, so throw.
  334. isc_throw(NcrMessageError,
  335. "NameChangeRequest value missing for: " << name );
  336. }
  337. // Found the element, return it.
  338. return (it->second);
  339. }
  340. void
  341. NameChangeRequest::setChangeType(const NameChangeType value) {
  342. change_type_ = value;
  343. }
  344. void
  345. NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
  346. long raw_value = -1;
  347. try {
  348. // Get the element's integer value.
  349. raw_value = element->intValue();
  350. } catch (isc::data::TypeError& ex) {
  351. // We expect a integer Element type, don't have one.
  352. isc_throw(NcrMessageError,
  353. "Wrong data type for change_type: " << ex.what());
  354. }
  355. if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
  356. // Value is not a valid change type.
  357. isc_throw(NcrMessageError,
  358. "Invalid data value for change_type: " << raw_value);
  359. }
  360. // Good to go, make the assignment.
  361. setChangeType(static_cast<NameChangeType>(raw_value));
  362. }
  363. void
  364. NameChangeRequest::setForwardChange(const bool value) {
  365. forward_change_ = value;
  366. }
  367. void
  368. NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
  369. bool value;
  370. try {
  371. // Get the element's boolean value.
  372. value = element->boolValue();
  373. } catch (isc::data::TypeError& ex) {
  374. // We expect a boolean Element type, don't have one.
  375. isc_throw(NcrMessageError,
  376. "Wrong data type for forward-change: " << ex.what());
  377. }
  378. // Good to go, make the assignment.
  379. setForwardChange(value);
  380. }
  381. void
  382. NameChangeRequest::setReverseChange(const bool value) {
  383. reverse_change_ = value;
  384. }
  385. void
  386. NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
  387. bool value;
  388. try {
  389. // Get the element's boolean value.
  390. value = element->boolValue();
  391. } catch (isc::data::TypeError& ex) {
  392. // We expect a boolean Element type, don't have one.
  393. isc_throw(NcrMessageError,
  394. "Wrong data type for reverse_change: " << ex.what());
  395. }
  396. // Good to go, make the assignment.
  397. setReverseChange(value);
  398. }
  399. void
  400. NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
  401. setFqdn(element->stringValue());
  402. }
  403. void
  404. NameChangeRequest::setFqdn(const std::string& value) {
  405. try {
  406. dns::Name tmp(value);
  407. fqdn_ = tmp.toText();
  408. } catch (const std::exception& ex) {
  409. isc_throw(NcrMessageError,
  410. "Invalid FQDN value: " << value << ", reason: "
  411. << ex.what());
  412. }
  413. }
  414. void
  415. NameChangeRequest::setIpAddress(const std::string& value) {
  416. // Validate IP Address.
  417. try {
  418. ip_io_address_ = isc::asiolink::IOAddress(value);
  419. } catch (const isc::asiolink::IOError&) {
  420. isc_throw(NcrMessageError,
  421. "Invalid ip address string for ip_address: " << value);
  422. }
  423. }
  424. void
  425. NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
  426. setIpAddress(element->stringValue());
  427. }
  428. void
  429. NameChangeRequest::setDhcid(const std::string& value) {
  430. dhcid_.fromStr(value);
  431. }
  432. void
  433. NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
  434. setDhcid(element->stringValue());
  435. }
  436. std::string
  437. NameChangeRequest::getLeaseExpiresOnStr() const {
  438. return (isc::util::timeToText64(lease_expires_on_));
  439. }
  440. void
  441. NameChangeRequest::setLeaseExpiresOn(const std::string& value) {
  442. try {
  443. lease_expires_on_ = isc::util::timeFromText64(value);
  444. } catch(...) {
  445. // We were given an invalid string, so throw.
  446. isc_throw(NcrMessageError,
  447. "Invalid date-time string: [" << value << "]");
  448. }
  449. }
  450. void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
  451. // Pull out the string value and pass it into the string setter.
  452. setLeaseExpiresOn(element->stringValue());
  453. }
  454. void
  455. NameChangeRequest::setLeaseLength(const uint32_t value) {
  456. lease_length_ = value;
  457. }
  458. void
  459. NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
  460. long value = -1;
  461. try {
  462. // Get the element's integer value.
  463. value = element->intValue();
  464. } catch (isc::data::TypeError& ex) {
  465. // We expect a integer Element type, don't have one.
  466. isc_throw(NcrMessageError,
  467. "Wrong data type for lease_length: " << ex.what());
  468. }
  469. // Make sure we the range is correct and value is positive.
  470. if (value > std::numeric_limits<uint32_t>::max()) {
  471. isc_throw(NcrMessageError, "lease_length value " << value <<
  472. "is too large for unsigned 32-bit integer.");
  473. }
  474. if (value < 0) {
  475. isc_throw(NcrMessageError, "lease_length value " << value <<
  476. "is negative. It must greater than or equal to zero ");
  477. }
  478. // Good to go, make the assignment.
  479. setLeaseLength(static_cast<uint32_t>(value));
  480. }
  481. void
  482. NameChangeRequest::setStatus(const NameChangeStatus value) {
  483. status_ = value;
  484. }
  485. std::string
  486. NameChangeRequest::toText() const {
  487. std::ostringstream stream;
  488. stream << "Type: " << static_cast<int>(change_type_) << " (";
  489. switch (change_type_) {
  490. case CHG_ADD:
  491. stream << "CHG_ADD)\n";
  492. break;
  493. case CHG_REMOVE:
  494. stream << "CHG_REMOVE)\n";
  495. break;
  496. default:
  497. // Shouldn't be possible.
  498. stream << "Invalid Value\n";
  499. }
  500. stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
  501. << std::endl
  502. << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
  503. << std::endl
  504. << "FQDN: [" << fqdn_ << "]" << std::endl
  505. << "IP Address: [" << ip_io_address_ << "]" << std::endl
  506. << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
  507. << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
  508. << "Lease Length: " << lease_length_ << std::endl;
  509. return (stream.str());
  510. }
  511. bool
  512. NameChangeRequest::operator == (const NameChangeRequest& other) {
  513. return ((change_type_ == other.change_type_) &&
  514. (forward_change_ == other.forward_change_) &&
  515. (reverse_change_ == other.reverse_change_) &&
  516. (fqdn_ == other.fqdn_) &&
  517. (ip_io_address_ == other.ip_io_address_) &&
  518. (dhcid_ == other.dhcid_) &&
  519. (lease_expires_on_ == other.lease_expires_on_) &&
  520. (lease_length_ == other.lease_length_));
  521. }
  522. bool
  523. NameChangeRequest::operator != (const NameChangeRequest& other) {
  524. return (!(*this == other));
  525. }
  526. }; // end of isc::dhcp namespace
  527. }; // end of isc namespace