option4_client_fqdn.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. // Copyright (C) 2013-2014 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/dhcp4.h>
  15. #include <dhcp/option4_client_fqdn.h>
  16. #include <dns/labelsequence.h>
  17. #include <util/buffer.h>
  18. #include <util/io_utilities.h>
  19. #include <util/strutil.h>
  20. #include <sstream>
  21. namespace isc {
  22. namespace dhcp {
  23. /// @brief Implements the logic for the Option6ClientFqdn class.
  24. ///
  25. /// The purpose of the class is to separate the implementation details
  26. /// of the Option4ClientFqdn class from the interface. This implementation
  27. /// uses libdns classes to process FQDNs. At some point it may be
  28. /// desired to split libdhcp++ from libdns. In such case the
  29. /// implementation of this class may be changed. The declaration of the
  30. /// Option6ClientFqdn class holds the pointer to implementation, so
  31. /// the transition to a different implementation would not affect the
  32. /// header file.
  33. class Option4ClientFqdnImpl {
  34. public:
  35. /// Holds flags carried by the option.
  36. uint8_t flags_;
  37. /// Holds RCODE1 and RCODE2 values.
  38. Option4ClientFqdn::Rcode rcode1_;
  39. Option4ClientFqdn::Rcode rcode2_;
  40. /// Holds the pointer to a domain name carried in the option.
  41. boost::shared_ptr<isc::dns::Name> domain_name_;
  42. /// Indicates whether domain name is partial or fully qualified.
  43. Option4ClientFqdn::DomainNameType domain_name_type_;
  44. /// @brief Constructor, from domain name.
  45. ///
  46. /// @param flags A value of the flags option field.
  47. /// @param rcode An object representing the RCODE1 and RCODE2 values.
  48. /// @param domain_name A domain name carried by the option given in the
  49. /// textual format.
  50. /// @param name_type A value which indicates whether domain-name is partial
  51. /// or fully qualified.
  52. Option4ClientFqdnImpl(const uint8_t flags,
  53. const Option4ClientFqdn::Rcode& rcode,
  54. const std::string& domain_name,
  55. const Option4ClientFqdn::DomainNameType name_type);
  56. /// @brief Constructor, from wire data.
  57. ///
  58. /// @param first An iterator pointing to the begining of the option data
  59. /// in the wire format.
  60. /// @param last An iterator poiting to the end of the option data in the
  61. /// wire format.
  62. Option4ClientFqdnImpl(OptionBufferConstIter first,
  63. OptionBufferConstIter last);
  64. /// @brief Copy constructor.
  65. ///
  66. /// @param source An object being copied.
  67. Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source);
  68. /// @brief Assignment operator.
  69. ///
  70. /// @param source An object which is being assigned.
  71. Option4ClientFqdnImpl& operator=(const Option4ClientFqdnImpl& source);
  72. /// @brief Set a new domain name for the option.
  73. ///
  74. /// @param domain_name A new domain name to be assigned.
  75. /// @param name_type A value which indicates whether the domain-name is
  76. /// partial or fully qualified.
  77. void setDomainName(const std::string& domain_name,
  78. const Option4ClientFqdn::DomainNameType name_type);
  79. /// @brief Check if flags are valid.
  80. ///
  81. /// In particular, this function checks if the N and S bits are not
  82. /// set to 1 in the same time.
  83. ///
  84. /// @param flags A value carried by the flags field of the option.
  85. /// @param check_mbz A boolean value which indicates if this function should
  86. /// check if the MBZ bits are set (if true). This parameter should be set
  87. /// to false when validating flags in the received message. This is because
  88. /// server should ignore MBZ bits in received messages.
  89. /// @throw InvalidOption6FqdnFlags if flags are invalid.
  90. static void checkFlags(const uint8_t flags, const bool check_mbz);
  91. /// @brief Parse the Option provided in the wire format.
  92. ///
  93. /// @param first An iterator pointing to the begining of the option data
  94. /// in the wire format.
  95. /// @param last An iterator poiting to the end of the option data in the
  96. /// wire format.
  97. void parseWireData(OptionBufferConstIter first,
  98. OptionBufferConstIter last);
  99. /// @brief Parse domain-name encoded in the canonical format.
  100. ///
  101. void parseCanonicalDomainName(OptionBufferConstIter first,
  102. OptionBufferConstIter last);
  103. /// @brief Parse domain-name emcoded in the deprecated ASCII format.
  104. ///
  105. /// @param first An iterator pointing to the begining of the option data
  106. /// where domain-name is stored.
  107. /// @param last An iterator poiting to the end of the option data where
  108. /// domain-name is stored.
  109. void parseASCIIDomainName(OptionBufferConstIter first,
  110. OptionBufferConstIter last);
  111. };
  112. Option4ClientFqdnImpl::
  113. Option4ClientFqdnImpl(const uint8_t flags,
  114. const Option4ClientFqdn::Rcode& rcode,
  115. const std::string& domain_name,
  116. // cppcheck 1.57 complains that const enum value is not passed
  117. // by reference. Note that, it accepts the non-const enum value
  118. // to be passed by value. In both cases it is unneccessary to
  119. // pass the enum by reference.
  120. // cppcheck-suppress passedByValue
  121. const Option4ClientFqdn::DomainNameType name_type)
  122. : flags_(flags),
  123. rcode1_(rcode),
  124. rcode2_(rcode),
  125. domain_name_(),
  126. domain_name_type_(name_type) {
  127. // Check if flags are correct. Also, check that MBZ bits are not set. If
  128. // they are, throw exception.
  129. checkFlags(flags_, true);
  130. // Set domain name. It may throw an exception if domain name has wrong
  131. // format.
  132. setDomainName(domain_name, name_type);
  133. }
  134. Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first,
  135. OptionBufferConstIter last)
  136. : rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
  137. rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
  138. parseWireData(first, last);
  139. // Verify that flags value was correct. This constructor is used to parse
  140. // incoming packet, so don't check MBZ bits. They are ignored because we
  141. // don't want to discard the whole option because MBZ bits are set.
  142. checkFlags(flags_, false);
  143. }
  144. Option4ClientFqdnImpl::
  145. Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source)
  146. : flags_(source.flags_),
  147. rcode1_(source.rcode1_),
  148. rcode2_(source.rcode2_),
  149. domain_name_(),
  150. domain_name_type_(source.domain_name_type_) {
  151. if (source.domain_name_) {
  152. domain_name_.reset(new isc::dns::Name(*source.domain_name_));
  153. }
  154. }
  155. Option4ClientFqdnImpl&
  156. // This assignment operator handles assignment to self, it copies all
  157. // required values.
  158. // cppcheck-suppress operatorEqToSelf
  159. Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) {
  160. if (source.domain_name_) {
  161. domain_name_.reset(new isc::dns::Name(*source.domain_name_));
  162. } else {
  163. domain_name_.reset();
  164. }
  165. // Assignment is exception safe.
  166. flags_ = source.flags_;
  167. rcode1_ = source.rcode1_;
  168. rcode2_ = source.rcode2_;
  169. domain_name_type_ = source.domain_name_type_;
  170. return (*this);
  171. }
  172. void
  173. Option4ClientFqdnImpl::
  174. setDomainName(const std::string& domain_name,
  175. // cppcheck 1.57 complains that const enum value is not passed
  176. // by reference. Note that, it accepts the non-const enum
  177. // to be passed by value. In both cases it is unneccessary to
  178. // pass the enum by reference.
  179. // cppcheck-suppress passedByValue
  180. const Option4ClientFqdn::DomainNameType name_type) {
  181. // domain-name must be trimmed. Otherwise, string comprising spaces only
  182. // would be treated as a fully qualified name.
  183. std::string name = isc::util::str::trim(domain_name);
  184. if (name.empty()) {
  185. if (name_type == Option4ClientFqdn::FULL) {
  186. isc_throw(InvalidOption4FqdnDomainName,
  187. "fully qualified domain-name must not be empty"
  188. << " when setting new domain-name for DHCPv4 Client"
  189. << " FQDN Option");
  190. }
  191. // The special case when domain-name is empty is marked by setting the
  192. // pointer to the domain-name object to NULL.
  193. domain_name_.reset();
  194. } else {
  195. try {
  196. domain_name_.reset(new isc::dns::Name(name));
  197. } catch (const Exception& ex) {
  198. isc_throw(InvalidOption4FqdnDomainName,
  199. "invalid domain-name value '"
  200. << domain_name << "' when setting new domain-name for"
  201. << " DHCPv4 Client FQDN Option");
  202. }
  203. }
  204. domain_name_type_ = name_type;
  205. }
  206. void
  207. Option4ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
  208. // The Must Be Zero (MBZ) bits must not be set.
  209. if (check_mbz && ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0)) {
  210. isc_throw(InvalidOption4FqdnFlags,
  211. "invalid DHCPv4 Client FQDN Option flags: 0x"
  212. << std::hex << static_cast<int>(flags) << std::dec);
  213. }
  214. // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
  215. // MUST be 0. Checking it here.
  216. if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S))
  217. == (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) {
  218. isc_throw(InvalidOption4FqdnFlags,
  219. "both N and S flag of the DHCPv4 Client FQDN Option are set."
  220. << " According to RFC 4702, if the N bit is 1 the S bit"
  221. << " MUST be 0");
  222. }
  223. }
  224. void
  225. Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
  226. OptionBufferConstIter last) {
  227. // Buffer must comprise at least one byte with the flags.
  228. // The domain-name may be empty.
  229. if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
  230. isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
  231. << DHO_FQDN << ") is truncated");
  232. }
  233. // Parse flags
  234. flags_ = *(first++);
  235. // Parse RCODE1 and RCODE2.
  236. rcode1_ = Option4ClientFqdn::Rcode(*(first++));
  237. rcode2_ = Option4ClientFqdn::Rcode(*(first++));
  238. try {
  239. if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) {
  240. parseCanonicalDomainName(first, last);
  241. } else {
  242. parseASCIIDomainName(first, last);
  243. }
  244. } catch (const Exception& ex) {
  245. isc_throw(InvalidOption4FqdnDomainName,
  246. "failed to parse the domain-name in DHCPv4 Client FQDN"
  247. << " Option: " << ex.what());
  248. }
  249. }
  250. void
  251. Option4ClientFqdnImpl::parseCanonicalDomainName(OptionBufferConstIter first,
  252. OptionBufferConstIter last) {
  253. // Parse domain-name if any.
  254. if (std::distance(first, last) > 0) {
  255. // The FQDN may comprise a partial domain-name. In this case it lacks
  256. // terminating 0. If this is the case, we will need to add zero at
  257. // the end because Name object constructor requires it.
  258. if (*(last - 1) != 0) {
  259. // Create temporary buffer and add terminating zero.
  260. OptionBuffer buf(first, last);
  261. buf.push_back(0);
  262. // Reset domain name.
  263. isc::util::InputBuffer name_buf(&buf[0], buf.size());
  264. domain_name_.reset(new isc::dns::Name(name_buf));
  265. // Terminating zero was missing, so set the domain-name type
  266. // to partial.
  267. domain_name_type_ = Option4ClientFqdn::PARTIAL;
  268. } else {
  269. // We are dealing with fully qualified domain name so there is
  270. // no need to add terminating zero. Simply pass the buffer to
  271. // Name object constructor.
  272. isc::util::InputBuffer name_buf(&(*first),
  273. std::distance(first, last));
  274. domain_name_.reset(new isc::dns::Name(name_buf));
  275. // Set the domain-type to fully qualified domain name.
  276. domain_name_type_ = Option4ClientFqdn::FULL;
  277. }
  278. }
  279. }
  280. void
  281. Option4ClientFqdnImpl::parseASCIIDomainName(OptionBufferConstIter first,
  282. OptionBufferConstIter last) {
  283. if (std::distance(first, last) > 0) {
  284. std::string domain_name(first, last);
  285. domain_name_.reset(new isc::dns::Name(domain_name));
  286. domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ?
  287. Option4ClientFqdn::FULL : Option4ClientFqdn::PARTIAL;
  288. }
  289. }
  290. Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
  291. : Option(Option::V4, DHO_FQDN),
  292. impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
  293. }
  294. Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag,
  295. const Rcode& rcode,
  296. const std::string& domain_name,
  297. const DomainNameType domain_name_type)
  298. : Option(Option::V4, DHO_FQDN),
  299. impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
  300. domain_name_type)) {
  301. }
  302. Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first,
  303. OptionBufferConstIter last)
  304. : Option(Option::V4, DHO_FQDN, first, last),
  305. impl_(new Option4ClientFqdnImpl(first, last)) {
  306. }
  307. Option4ClientFqdn::~Option4ClientFqdn() {
  308. delete(impl_);
  309. }
  310. Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
  311. : Option(source),
  312. impl_(new Option4ClientFqdnImpl(*source.impl_)) {
  313. }
  314. Option4ClientFqdn&
  315. // This assignment operator handles assignment to self, it uses copy
  316. // constructor of Option4ClientFqdnImpl to copy all required values.
  317. // cppcheck-suppress operatorEqToSelf
  318. Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
  319. Option4ClientFqdnImpl* old_impl = impl_;
  320. impl_ = new Option4ClientFqdnImpl(*source.impl_);
  321. delete(old_impl);
  322. return (*this);
  323. }
  324. bool
  325. Option4ClientFqdn::getFlag(const uint8_t flag) const {
  326. // Caller should query for one of the: E, N, S or O flags. Any other value
  327. /// is invalid and results in the exception.
  328. if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
  329. isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
  330. << " Option flag specified, expected E, N, S or O");
  331. }
  332. return ((impl_->flags_ & flag) != 0);
  333. }
  334. void
  335. Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
  336. // Check that flag is in range between 0x1 and 0x7. Although it is
  337. // discouraged this check doesn't preclude the caller from setting
  338. // multiple flags concurrently.
  339. if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
  340. isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
  341. << " Option flag " << std::hex
  342. << static_cast<int>(flag) << std::dec
  343. << "is being set. Expected combination of E, N, S and O");
  344. }
  345. // Copy the current flags into local variable. That way we will be able
  346. // to test new flags settings before applying them.
  347. uint8_t new_flag = impl_->flags_;
  348. if (set_flag) {
  349. new_flag |= flag;
  350. } else {
  351. new_flag &= ~flag;
  352. }
  353. // Check new flags. If they are valid, apply them. Also, check that MBZ
  354. // bits are not set.
  355. Option4ClientFqdnImpl::checkFlags(new_flag, true);
  356. impl_->flags_ = new_flag;
  357. }
  358. void
  359. Option4ClientFqdn::setRcode(const Rcode& rcode) {
  360. impl_->rcode1_ = rcode;
  361. impl_->rcode2_ = rcode;
  362. }
  363. void
  364. Option4ClientFqdn::resetFlags() {
  365. impl_->flags_ = 0;
  366. }
  367. std::string
  368. Option4ClientFqdn::getDomainName() const {
  369. if (impl_->domain_name_) {
  370. return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
  371. PARTIAL));
  372. }
  373. // If an object holding domain-name is NULL it means that the domain-name
  374. // is empty.
  375. return ("");
  376. }
  377. void
  378. Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
  379. // If domain-name is empty, do nothing.
  380. if (!impl_->domain_name_) {
  381. return;
  382. }
  383. if (getFlag(FLAG_E)) {
  384. // Domain name, encoded as a set of labels.
  385. isc::dns::LabelSequence labels(*impl_->domain_name_);
  386. if (labels.getDataLength() > 0) {
  387. size_t read_len = 0;
  388. const uint8_t* data = labels.getData(&read_len);
  389. if (impl_->domain_name_type_ == PARTIAL) {
  390. --read_len;
  391. }
  392. buf.writeData(data, read_len);
  393. }
  394. } else {
  395. std::string domain_name = getDomainName();
  396. if (!domain_name.empty()) {
  397. buf.writeData(&domain_name[0], domain_name.size());
  398. }
  399. }
  400. }
  401. void
  402. Option4ClientFqdn::setDomainName(const std::string& domain_name,
  403. const DomainNameType domain_name_type) {
  404. impl_->setDomainName(domain_name, domain_name_type);
  405. }
  406. void
  407. Option4ClientFqdn::resetDomainName() {
  408. setDomainName("", PARTIAL);
  409. }
  410. Option4ClientFqdn::DomainNameType
  411. Option4ClientFqdn::getDomainNameType() const {
  412. return (impl_->domain_name_type_);
  413. }
  414. void
  415. Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) {
  416. // Header = option code and length.
  417. packHeader(buf);
  418. // Flags field.
  419. buf.writeUint8(impl_->flags_);
  420. // RCODE1 and RCODE2
  421. buf.writeUint8(impl_->rcode1_.getCode());
  422. buf.writeUint8(impl_->rcode2_.getCode());
  423. // Domain name.
  424. packDomainName(buf);
  425. }
  426. void
  427. Option4ClientFqdn::unpack(OptionBufferConstIter first,
  428. OptionBufferConstIter last) {
  429. setData(first, last);
  430. impl_->parseWireData(first, last);
  431. // Check that the flags in the received option are valid. Ignore MBZ bits,
  432. // because we don't want to discard the whole option because of MBZ bits
  433. // being set.
  434. impl_->checkFlags(impl_->flags_, false);
  435. }
  436. std::string
  437. Option4ClientFqdn::toText(int indent) {
  438. std::ostringstream stream;
  439. std::string in(indent, ' '); // base indentation
  440. stream << in << "type=" << type_ << " (CLIENT_FQDN), "
  441. << "flags: ("
  442. << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
  443. << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", "
  444. << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
  445. << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
  446. << "domain-name='" << getDomainName() << "' ("
  447. << (getDomainNameType() == PARTIAL ? "partial" : "full")
  448. << ")";
  449. return (stream.str());
  450. }
  451. uint16_t
  452. Option4ClientFqdn::len() {
  453. uint16_t domain_name_length = 0;
  454. // Try to calculate the length of the domain name only if there is
  455. // any domain name specified.
  456. if (impl_->domain_name_) {
  457. // If the E flag is specified, the domain name is encoded in the
  458. // canonical format. The length of the domain name depends on
  459. // whether it is a partial or fully qualified names. For the
  460. // former the length is 1 octet lesser because it lacks terminating
  461. // zero.
  462. if (getFlag(FLAG_E)) {
  463. domain_name_length = impl_->domain_name_type_ == FULL ?
  464. impl_->domain_name_->getLength() :
  465. impl_->domain_name_->getLength() - 1;
  466. // ASCII encoded domain name. Its length is just a length of the
  467. // string holding the name.
  468. } else {
  469. domain_name_length = getDomainName().length();
  470. }
  471. }
  472. return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
  473. }
  474. } // end of isc::dhcp namespace
  475. } // end of isc namespace