option4_client_fqdn.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. // Copyright (C) 2013 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. const Option4ClientFqdn::DomainNameType name_type)
  117. : flags_(flags),
  118. rcode1_(rcode),
  119. rcode2_(rcode),
  120. domain_name_(),
  121. domain_name_type_(name_type) {
  122. // Check if flags are correct. Also, check that MBZ bits are not set. If
  123. // they are, throw exception.
  124. checkFlags(flags_, true);
  125. // Set domain name. It may throw an exception if domain name has wrong
  126. // format.
  127. setDomainName(domain_name, name_type);
  128. }
  129. Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first,
  130. OptionBufferConstIter last)
  131. : rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
  132. rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
  133. parseWireData(first, last);
  134. // Verify that flags value was correct. This constructor is used to parse
  135. // incoming packet, so don't check MBZ bits. They are ignored because we
  136. // don't want to discard the whole option because MBZ bits are set.
  137. checkFlags(flags_, false);
  138. }
  139. Option4ClientFqdnImpl::
  140. Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source)
  141. : flags_(source.flags_),
  142. rcode1_(source.rcode1_),
  143. rcode2_(source.rcode2_),
  144. domain_name_(),
  145. domain_name_type_(source.domain_name_type_) {
  146. if (source.domain_name_) {
  147. domain_name_.reset(new isc::dns::Name(*source.domain_name_));
  148. }
  149. }
  150. Option4ClientFqdnImpl&
  151. // This assignment operator handles assignment to self, it copies all
  152. // required values.
  153. // cppcheck-suppress operatorEqToSelf
  154. Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) {
  155. if (source.domain_name_) {
  156. domain_name_.reset(new isc::dns::Name(*source.domain_name_));
  157. } else {
  158. domain_name_.reset();
  159. }
  160. // Assignment is exception safe.
  161. flags_ = source.flags_;
  162. rcode1_ = source.rcode1_;
  163. rcode2_ = source.rcode2_;
  164. domain_name_type_ = source.domain_name_type_;
  165. return (*this);
  166. }
  167. void
  168. Option4ClientFqdnImpl::
  169. setDomainName(const std::string& domain_name,
  170. const Option4ClientFqdn::DomainNameType name_type) {
  171. // domain-name must be trimmed. Otherwise, string comprising spaces only
  172. // would be treated as a fully qualified name.
  173. std::string name = isc::util::str::trim(domain_name);
  174. if (name.empty()) {
  175. if (name_type == Option4ClientFqdn::FULL) {
  176. isc_throw(InvalidOption4FqdnDomainName,
  177. "fully qualified domain-name must not be empty"
  178. << " when setting new domain-name for DHCPv4 Client"
  179. << " FQDN Option");
  180. }
  181. // The special case when domain-name is empty is marked by setting the
  182. // pointer to the domain-name object to NULL.
  183. domain_name_.reset();
  184. } else {
  185. try {
  186. domain_name_.reset(new isc::dns::Name(name));
  187. domain_name_type_ = name_type;
  188. } catch (const Exception& ex) {
  189. isc_throw(InvalidOption4FqdnDomainName,
  190. "invalid domain-name value '"
  191. << domain_name << "' when setting new domain-name for"
  192. << " DHCPv4 Client FQDN Option");
  193. }
  194. }
  195. }
  196. void
  197. Option4ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
  198. // The Must Be Zero (MBZ) bits must not be set.
  199. if (check_mbz && ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0)) {
  200. isc_throw(InvalidOption4FqdnFlags,
  201. "invalid DHCPv4 Client FQDN Option flags: 0x"
  202. << std::hex << static_cast<int>(flags) << std::dec);
  203. }
  204. // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
  205. // MUST be 0. Checking it here.
  206. if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S))
  207. == (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) {
  208. isc_throw(InvalidOption4FqdnFlags,
  209. "both N and S flag of the DHCPv4 Client FQDN Option are set."
  210. << " According to RFC 4702, if the N bit is 1 the S bit"
  211. << " MUST be 0");
  212. }
  213. }
  214. void
  215. Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
  216. OptionBufferConstIter last) {
  217. // Buffer must comprise at least one byte with the flags.
  218. // The domain-name may be empty.
  219. if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
  220. isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
  221. << DHO_FQDN << ") is truncated");
  222. }
  223. // Parse flags
  224. flags_ = *(first++);
  225. // Parse RCODE1 and RCODE2.
  226. rcode1_ = Option4ClientFqdn::Rcode(*(first++));
  227. rcode2_ = Option4ClientFqdn::Rcode(*(first++));
  228. try {
  229. if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) {
  230. parseCanonicalDomainName(first, last);
  231. } else {
  232. parseASCIIDomainName(first, last);
  233. }
  234. } catch (const Exception& ex) {
  235. isc_throw(InvalidOption4FqdnDomainName,
  236. "failed to parse the domain-name in DHCPv4 Client FQDN"
  237. << " Option: " << ex.what());
  238. }
  239. }
  240. void
  241. Option4ClientFqdnImpl::parseCanonicalDomainName(OptionBufferConstIter first,
  242. OptionBufferConstIter last) {
  243. // Parse domain-name if any.
  244. if (std::distance(first, last) > 0) {
  245. // The FQDN may comprise a partial domain-name. In this case it lacks
  246. // terminating 0. If this is the case, we will need to add zero at
  247. // the end because Name object constructor requires it.
  248. if (*(last - 1) != 0) {
  249. // Create temporary buffer and add terminating zero.
  250. OptionBuffer buf(first, last);
  251. buf.push_back(0);
  252. // Reset domain name.
  253. isc::util::InputBuffer name_buf(&buf[0], buf.size());
  254. domain_name_.reset(new isc::dns::Name(name_buf));
  255. // Terminating zero was missing, so set the domain-name type
  256. // to partial.
  257. domain_name_type_ = Option4ClientFqdn::PARTIAL;
  258. } else {
  259. // We are dealing with fully qualified domain name so there is
  260. // no need to add terminating zero. Simply pass the buffer to
  261. // Name object constructor.
  262. isc::util::InputBuffer name_buf(&(*first),
  263. std::distance(first, last));
  264. domain_name_.reset(new isc::dns::Name(name_buf));
  265. // Set the domain-type to fully qualified domain name.
  266. domain_name_type_ = Option4ClientFqdn::FULL;
  267. }
  268. }
  269. }
  270. void
  271. Option4ClientFqdnImpl::parseASCIIDomainName(OptionBufferConstIter first,
  272. OptionBufferConstIter last) {
  273. if (std::distance(first, last) > 0) {
  274. std::string domain_name(first, last);
  275. domain_name_.reset(new isc::dns::Name(domain_name));
  276. domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ?
  277. Option4ClientFqdn::FULL : Option4ClientFqdn::PARTIAL;
  278. }
  279. }
  280. Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
  281. : Option(Option::V4, DHO_FQDN),
  282. impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
  283. }
  284. Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag,
  285. const Rcode& rcode,
  286. const std::string& domain_name,
  287. const DomainNameType domain_name_type)
  288. : Option(Option::V4, DHO_FQDN),
  289. impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
  290. domain_name_type)) {
  291. }
  292. Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first,
  293. OptionBufferConstIter last)
  294. : Option(Option::V4, DHO_FQDN, first, last),
  295. impl_(new Option4ClientFqdnImpl(first, last)) {
  296. }
  297. Option4ClientFqdn::~Option4ClientFqdn() {
  298. delete(impl_);
  299. }
  300. Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
  301. : Option(source),
  302. impl_(new Option4ClientFqdnImpl(*source.impl_)) {
  303. }
  304. Option4ClientFqdn&
  305. // This assignment operator handles assignment to self, it uses copy
  306. // constructor of Option4ClientFqdnImpl to copy all required values.
  307. // cppcheck-suppress operatorEqToSelf
  308. Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
  309. Option4ClientFqdnImpl* old_impl = impl_;
  310. impl_ = new Option4ClientFqdnImpl(*source.impl_);
  311. delete(old_impl);
  312. return (*this);
  313. }
  314. bool
  315. Option4ClientFqdn::getFlag(const uint8_t flag) const {
  316. // Caller should query for one of the: E, N, S or O flags. Any other value
  317. /// is invalid and results in the exception.
  318. if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
  319. isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
  320. << " Option flag specified, expected E, N, S or O");
  321. }
  322. return ((impl_->flags_ & flag) != 0);
  323. }
  324. void
  325. Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
  326. // Check that flag is in range between 0x1 and 0x7. Although it is
  327. // discouraged this check doesn't preclude the caller from setting
  328. // multiple flags concurrently.
  329. if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
  330. isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
  331. << " Option flag " << std::hex
  332. << static_cast<int>(flag) << std::dec
  333. << "is being set. Expected combination of E, N, S and O");
  334. }
  335. // Copy the current flags into local variable. That way we will be able
  336. // to test new flags settings before applying them.
  337. uint8_t new_flag = impl_->flags_;
  338. if (set_flag) {
  339. new_flag |= flag;
  340. } else {
  341. new_flag &= ~flag;
  342. }
  343. // Check new flags. If they are valid, apply them. Also, check that MBZ
  344. // bits are not set.
  345. Option4ClientFqdnImpl::checkFlags(new_flag, true);
  346. impl_->flags_ = new_flag;
  347. }
  348. void
  349. Option4ClientFqdn::setRcode(const Rcode& rcode) {
  350. impl_->rcode1_ = rcode;
  351. impl_->rcode2_ = rcode;
  352. }
  353. void
  354. Option4ClientFqdn::resetFlags() {
  355. impl_->flags_ = 0;
  356. }
  357. std::string
  358. Option4ClientFqdn::getDomainName() const {
  359. if (impl_->domain_name_) {
  360. return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
  361. PARTIAL));
  362. }
  363. // If an object holding domain-name is NULL it means that the domain-name
  364. // is empty.
  365. return ("");
  366. }
  367. void
  368. Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
  369. if (getFlag(FLAG_E)) {
  370. // Domain name, encoded as a set of labels.
  371. isc::dns::LabelSequence labels(*impl_->domain_name_);
  372. if (labels.getDataLength() > 0) {
  373. size_t read_len = 0;
  374. const uint8_t* data = labels.getData(&read_len);
  375. if (impl_->domain_name_type_ == PARTIAL) {
  376. --read_len;
  377. }
  378. buf.writeData(data, read_len);
  379. }
  380. } else {
  381. std::string domain_name = impl_->domain_name_->toText();
  382. buf.writeData(&domain_name[0], domain_name.size());
  383. }
  384. }
  385. void
  386. Option4ClientFqdn::setDomainName(const std::string& domain_name,
  387. const DomainNameType domain_name_type) {
  388. impl_->setDomainName(domain_name, domain_name_type);
  389. }
  390. void
  391. Option4ClientFqdn::resetDomainName() {
  392. setDomainName("", PARTIAL);
  393. }
  394. Option4ClientFqdn::DomainNameType
  395. Option4ClientFqdn::getDomainNameType() const {
  396. return (impl_->domain_name_type_);
  397. }
  398. void
  399. Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) {
  400. // Header = option code and length.
  401. packHeader(buf);
  402. // Flags field.
  403. buf.writeUint8(impl_->flags_);
  404. // RCODE1 and RCODE2
  405. buf.writeUint8(impl_->rcode1_.getCode());
  406. buf.writeUint8(impl_->rcode2_.getCode());
  407. // Domain name.
  408. packDomainName(buf);
  409. }
  410. void
  411. Option4ClientFqdn::unpack(OptionBufferConstIter first,
  412. OptionBufferConstIter last) {
  413. setData(first, last);
  414. impl_->parseWireData(first, last);
  415. // Check that the flags in the received option are valid. Ignore MBZ bits,
  416. // because we don't want to discard the whole option because of MBZ bits
  417. // being set.
  418. impl_->checkFlags(impl_->flags_, false);
  419. }
  420. std::string
  421. Option4ClientFqdn::toText(int indent) {
  422. std::ostringstream stream;
  423. std::string in(indent, ' '); // base indentation
  424. stream << in << "type=" << type_ << " (CLIENT_FQDN), "
  425. << "flags: ("
  426. << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
  427. << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", "
  428. << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
  429. << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
  430. << "domain-name='" << getDomainName() << "' ("
  431. << (getDomainNameType() == PARTIAL ? "partial" : "full")
  432. << ")";
  433. return (stream.str());
  434. }
  435. uint16_t
  436. Option4ClientFqdn::len() {
  437. // If domain name is partial, the NULL terminating character
  438. // is not included and the option length have to be adjusted.
  439. uint16_t domain_name_length = impl_->domain_name_type_ == FULL ?
  440. impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
  441. return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
  442. }
  443. } // end of isc::dhcp namespace
  444. } // end of isc namespace