option6_client_fqdn.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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/dhcp6.h>
  15. #include <dhcp/option6_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 Option6ClientFqdn class from the interface. This implementation
  27. /// uses b10-libdns classes to process FQDNs. At some point it may be
  28. /// desired to split b10-libdhcp++ from b10-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 Option6ClientFqdnImpl {
  34. public:
  35. /// Holds flags carried by the option.
  36. uint8_t flags_;
  37. /// Holds the pointer to a domain name carried in the option.
  38. boost::shared_ptr<isc::dns::Name> domain_name_;
  39. /// Indicates whether domain name is partial or fully qualified.
  40. Option6ClientFqdn::DomainNameType domain_name_type_;
  41. /// @brief Constructor, from domain name.
  42. ///
  43. /// @param flags A value of the flags option field.
  44. /// @param domain_name A domain name carried by the option given in the
  45. /// textual format.
  46. /// @param domain_name_type A value which indicates whether domain-name
  47. /// is partial of fully qualified.
  48. Option6ClientFqdnImpl(const uint8_t flags,
  49. const std::string& domain_name,
  50. const Option6ClientFqdn::DomainNameType name_type);
  51. /// @brief Constructor, from wire data.
  52. ///
  53. /// @param first An iterator pointing to the begining of the option data
  54. /// in the wire format.
  55. /// @param last An iterator poiting to the end of the option data in the
  56. /// wire format.
  57. Option6ClientFqdnImpl(OptionBufferConstIter first,
  58. OptionBufferConstIter last);
  59. /// @brief Copy constructor.
  60. ///
  61. /// @param source An object being copied.
  62. Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source);
  63. /// @brief Assignment operator.
  64. ///
  65. /// @param source An object which is being assigned.
  66. Option6ClientFqdnImpl& operator=(const Option6ClientFqdnImpl& source);
  67. /// @brief Set a new domain name for the option.
  68. ///
  69. /// @param domain_name A new domain name to be assigned.
  70. /// @param name_type A value which indicates whether the domain-name is
  71. /// partial or fully qualified.
  72. void setDomainName(const std::string& domain_name,
  73. const Option6ClientFqdn::DomainNameType name_type);
  74. /// @brief Check if flags are valid.
  75. ///
  76. /// In particular, this function checks if the N and S bits are not
  77. /// set to 1 in the same time.
  78. ///
  79. /// @param flags A value carried by the flags field of the option.
  80. /// @param check_mbz A boolean value which indicates if this function should
  81. /// check if the MBZ bits are set (if true). This parameter should be set
  82. /// to false when validating flags in the received message. This is because
  83. /// server should ignore MBZ bits in received messages.
  84. /// @throw InvalidOption6FqdnFlags if flags are invalid.
  85. static void checkFlags(const uint8_t flags, const bool check_mbz);
  86. /// @brief Parse the Option provided in the wire format.
  87. ///
  88. /// @param first An iterator pointing to the begining of the option data
  89. /// in the wire format.
  90. /// @param last An iterator poiting to the end of the option data in the
  91. /// wire format.
  92. void parseWireData(OptionBufferConstIter first,
  93. OptionBufferConstIter last);
  94. };
  95. Option6ClientFqdnImpl::
  96. Option6ClientFqdnImpl(const uint8_t flags,
  97. const std::string& domain_name,
  98. const Option6ClientFqdn::DomainNameType name_type)
  99. : flags_(flags),
  100. domain_name_(),
  101. domain_name_type_(name_type) {
  102. // Check if flags are correct. Also check if MBZ bits are set.
  103. checkFlags(flags_, true);
  104. // Set domain name. It may throw an exception if domain name has wrong
  105. // format.
  106. setDomainName(domain_name, name_type);
  107. }
  108. Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
  109. OptionBufferConstIter last) {
  110. parseWireData(first, last);
  111. // Verify that flags value was correct. Do not check if MBZ bits are
  112. // set because we should ignore those bits in received message.
  113. checkFlags(flags_, false);
  114. }
  115. Option6ClientFqdnImpl::
  116. Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
  117. : flags_(source.flags_),
  118. domain_name_(),
  119. domain_name_type_(source.domain_name_type_) {
  120. if (source.domain_name_) {
  121. domain_name_.reset(new isc::dns::Name(*source.domain_name_));
  122. }
  123. }
  124. Option6ClientFqdnImpl&
  125. Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
  126. if (source.domain_name_) {
  127. domain_name_.reset(new isc::dns::Name(*source.domain_name_));
  128. } else {
  129. domain_name_.reset();
  130. }
  131. // This assignment should be exception safe.
  132. flags_ = source.flags_;
  133. domain_name_type_ = source.domain_name_type_;
  134. return (*this);
  135. }
  136. void
  137. Option6ClientFqdnImpl::
  138. setDomainName(const std::string& domain_name,
  139. const Option6ClientFqdn::DomainNameType name_type) {
  140. // domain-name must be trimmed. Otherwise, string comprising spaces only
  141. // would be treated as a fully qualified name.
  142. std::string name = isc::util::str::trim(domain_name);
  143. if (name.empty()) {
  144. if (name_type == Option6ClientFqdn::FULL) {
  145. isc_throw(InvalidOption6FqdnDomainName,
  146. "fully qualified domain-name must not be empty"
  147. << " when setting new domain-name for DHCPv6 Client"
  148. << " FQDN Option");
  149. }
  150. // The special case when domain-name is empty is marked by setting the
  151. // pointer to the domain-name object to NULL.
  152. domain_name_.reset();
  153. } else {
  154. try {
  155. domain_name_.reset(new isc::dns::Name(name));
  156. domain_name_type_ = name_type;
  157. } catch (const Exception& ex) {
  158. isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
  159. << domain_name << "' when setting new domain-name for"
  160. << " DHCPv6 Client FQDN Option");
  161. }
  162. }
  163. }
  164. void
  165. Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
  166. // The Must Be Zero (MBZ) bits must not be set.
  167. if (check_mbz && ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0)) {
  168. isc_throw(InvalidOption6FqdnFlags,
  169. "invalid DHCPv6 Client FQDN Option flags: 0x"
  170. << std::hex << static_cast<int>(flags) << std::dec);
  171. }
  172. // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
  173. // MUST be 0. Checking it here.
  174. if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
  175. == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
  176. isc_throw(InvalidOption6FqdnFlags,
  177. "both N and S flag of the DHCPv6 Client FQDN Option are set."
  178. << " According to RFC 4704, if the N bit is 1 the S bit"
  179. << " MUST be 0");
  180. }
  181. }
  182. void
  183. Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
  184. OptionBufferConstIter last) {
  185. // Buffer must comprise at least one byte with the flags.
  186. // The domain-name may be empty.
  187. if (std::distance(first, last) < Option6ClientFqdn::FLAG_FIELD_LEN) {
  188. isc_throw(OutOfRange, "DHCPv6 Client FQDN Option ("
  189. << D6O_CLIENT_FQDN << ") is truncated. Minimal option"
  190. << " size is " << Option6ClientFqdn::FLAG_FIELD_LEN
  191. << ", got option with size " << std::distance(first, last));
  192. }
  193. // Parse flags
  194. flags_ = *(first++);
  195. // Parse domain-name if any.
  196. if (std::distance(first, last) > 0) {
  197. // The FQDN may comprise a partial domain-name. In this case it lacks
  198. // terminating 0. If this is the case, we will need to add zero at
  199. // the end because Name object constructor requires it.
  200. if (*(last - 1) != 0) {
  201. // Create temporary buffer and add terminating zero.
  202. OptionBuffer buf(first, last);
  203. buf.push_back(0);
  204. // Reset domain name.
  205. isc::util::InputBuffer name_buf(&buf[0], buf.size());
  206. try {
  207. domain_name_.reset(new isc::dns::Name(name_buf));
  208. } catch (const Exception& ex) {
  209. isc_throw(InvalidOption6FqdnDomainName, "failed to parse"
  210. "partial domain-name from wire format");
  211. }
  212. // Terminating zero was missing, so set the domain-name type
  213. // to partial.
  214. domain_name_type_ = Option6ClientFqdn::PARTIAL;
  215. } else {
  216. // We are dealing with fully qualified domain name so there is
  217. // no need to add terminating zero. Simply pass the buffer to
  218. // Name object constructor.
  219. isc::util::InputBuffer name_buf(&(*first),
  220. std::distance(first, last));
  221. try {
  222. domain_name_.reset(new isc::dns::Name(name_buf));
  223. } catch (const Exception& ex) {
  224. isc_throw(InvalidOption6FqdnDomainName, "failed to parse"
  225. "fully qualified domain-name from wire format");
  226. }
  227. // Set the domain-type to fully qualified domain name.
  228. domain_name_type_ = Option6ClientFqdn::FULL;
  229. }
  230. }
  231. }
  232. Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag)
  233. : Option(Option::V6, D6O_CLIENT_FQDN),
  234. impl_(new Option6ClientFqdnImpl(flag, "", PARTIAL)) {
  235. }
  236. Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
  237. const std::string& domain_name,
  238. const DomainNameType domain_name_type)
  239. : Option(Option::V6, D6O_CLIENT_FQDN),
  240. impl_(new Option6ClientFqdnImpl(flag, domain_name, domain_name_type)) {
  241. }
  242. Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
  243. OptionBufferConstIter last)
  244. : Option(Option::V6, D6O_CLIENT_FQDN, first, last),
  245. impl_(new Option6ClientFqdnImpl(first, last)) {
  246. }
  247. Option6ClientFqdn::~Option6ClientFqdn() {
  248. delete(impl_);
  249. }
  250. Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
  251. : Option(source),
  252. impl_(new Option6ClientFqdnImpl(*source.impl_)) {
  253. }
  254. Option6ClientFqdn&
  255. Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
  256. Option6ClientFqdnImpl* old_impl = impl_;
  257. impl_ = new Option6ClientFqdnImpl(*source.impl_);
  258. delete(old_impl);
  259. return (*this);
  260. }
  261. bool
  262. Option6ClientFqdn::getFlag(const uint8_t flag) const {
  263. // Caller should query for one of the: N, S or O flags. Any other
  264. // value is invalid.
  265. if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) {
  266. isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
  267. << " Option flag specified, expected N, S or O");
  268. }
  269. return ((impl_->flags_ & flag) != 0);
  270. }
  271. void
  272. Option6ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
  273. // Check that flag is in range between 0x1 and 0x7. Note that this
  274. // allows to set or clear multiple flags concurrently. Setting
  275. // concurrent bits is discouraged (see header file) but it is not
  276. // checked here so it will work.
  277. if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
  278. isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
  279. << " Option flag " << std::hex
  280. << static_cast<int>(flag) << std::dec
  281. << "is being set. Expected: N, S or O");
  282. }
  283. // Copy the current flags into local variable. That way we will be able
  284. // to test new flags settings before applying them.
  285. uint8_t new_flag = impl_->flags_;
  286. if (set_flag) {
  287. new_flag |= flag;
  288. } else {
  289. new_flag &= ~flag;
  290. }
  291. // Check new flags. If they are valid, apply them.
  292. Option6ClientFqdnImpl::checkFlags(new_flag, true);
  293. impl_->flags_ = new_flag;
  294. }
  295. void
  296. Option6ClientFqdn::resetFlags() {
  297. impl_->flags_ = 0;
  298. }
  299. std::string
  300. Option6ClientFqdn::getDomainName() const {
  301. if (impl_->domain_name_) {
  302. return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
  303. PARTIAL));
  304. }
  305. // If an object holding domain-name is NULL it means that the domain-name
  306. // is empty.
  307. return ("");
  308. }
  309. void
  310. Option6ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
  311. // Domain name, encoded as a set of labels.
  312. isc::dns::LabelSequence labels(*impl_->domain_name_);
  313. if (labels.getDataLength() > 0) {
  314. size_t read_len = 0;
  315. const uint8_t* data = labels.getData(&read_len);
  316. if (impl_->domain_name_type_ == PARTIAL) {
  317. --read_len;
  318. }
  319. buf.writeData(data, read_len);
  320. }
  321. }
  322. void
  323. Option6ClientFqdn::setDomainName(const std::string& domain_name,
  324. const DomainNameType domain_name_type) {
  325. impl_->setDomainName(domain_name, domain_name_type);
  326. }
  327. void
  328. Option6ClientFqdn::resetDomainName() {
  329. setDomainName("", PARTIAL);
  330. }
  331. Option6ClientFqdn::DomainNameType
  332. Option6ClientFqdn::getDomainNameType() const {
  333. return (impl_->domain_name_type_);
  334. }
  335. void
  336. Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
  337. // Header = option code and length.
  338. packHeader(buf);
  339. // Flags field.
  340. buf.writeUint8(impl_->flags_);
  341. // Domain name.
  342. packDomainName(buf);
  343. }
  344. void
  345. Option6ClientFqdn::unpack(OptionBufferConstIter first,
  346. OptionBufferConstIter last) {
  347. setData(first, last);
  348. impl_->parseWireData(first, last);
  349. }
  350. std::string
  351. Option6ClientFqdn::toText(int indent) {
  352. std::ostringstream stream;
  353. std::string in(indent, ' '); // base indentation
  354. stream << in << "type=" << type_ << "(CLIENT_FQDN)" << ", "
  355. << "flags: ("
  356. << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
  357. << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
  358. << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
  359. << "domain-name='" << getDomainName() << "' ("
  360. << (getDomainNameType() == PARTIAL ? "partial" : "full")
  361. << ")";
  362. return (stream.str());
  363. }
  364. uint16_t
  365. Option6ClientFqdn::len() {
  366. // If domain name is partial, the NULL terminating character
  367. // is not included and the option length have to be adjusted.
  368. uint16_t domain_name_length = impl_->domain_name_type_ == FULL ?
  369. impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
  370. return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
  371. }
  372. } // end of isc::dhcp namespace
  373. } // end of isc namespace