Kea  2.1.7-git
option4_client_fqdn.cc
Go to the documentation of this file.
1 // Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <dhcp/dhcp4.h>
11 #include <dns/labelsequence.h>
12 #include <util/buffer.h>
13 #include <util/io_utilities.h>
14 #include <util/strutil.h>
15 #include <sstream>
16 
17 namespace isc {
18 namespace dhcp {
19 
31 public:
33  uint8_t flags_;
38  boost::shared_ptr<isc::dns::Name> domain_name_;
41 
50  Option4ClientFqdnImpl(const uint8_t flags,
51  const Option4ClientFqdn::Rcode& rcode,
52  const std::string& domain_name,
53  const Option4ClientFqdn::DomainNameType name_type);
54 
63 
68 
73 
79  void setDomainName(const std::string& domain_name,
80  const Option4ClientFqdn::DomainNameType name_type);
81 
93  static void checkFlags(const uint8_t flags, const bool check_mbz);
94 
102  OptionBufferConstIter last);
103 
107  OptionBufferConstIter last);
108 
116  OptionBufferConstIter last);
117 
118 };
119 
121 Option4ClientFqdnImpl(const uint8_t flags,
122  const Option4ClientFqdn::Rcode& rcode,
123  const std::string& domain_name,
124  // cppcheck 1.57 complains that const enum value is not passed
125  // by reference. Note that, it accepts the non-const enum value
126  // to be passed by value. In both cases it is unnecessary to
127  // pass the enum by reference.
128  // cppcheck-suppress passedByValue
129  const Option4ClientFqdn::DomainNameType name_type)
130  : flags_(flags),
131  rcode1_(rcode),
132  rcode2_(rcode),
133  domain_name_(),
134  domain_name_type_(name_type) {
135 
136  // Check if flags are correct. Also, check that MBZ bits are not set. If
137  // they are, throw exception.
138  checkFlags(flags_, true);
139  // Set domain name. It may throw an exception if domain name has wrong
140  // format.
141  setDomainName(domain_name, name_type);
142 }
143 
146  : rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
147  rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
148  parseWireData(first, last);
149  // Verify that flags value was correct. This constructor is used to parse
150  // incoming packet, so don't check MBZ bits. They are ignored because we
151  // don't want to discard the whole option because MBZ bits are set.
152  checkFlags(flags_, false);
153 }
154 
157  : flags_(source.flags_),
158  rcode1_(source.rcode1_),
159  rcode2_(source.rcode2_),
160  domain_name_(),
162  if (source.domain_name_) {
163  domain_name_.reset(new isc::dns::Name(*source.domain_name_));
164  }
165 }
166 
168 // This assignment operator handles assignment to self, it copies all
169 // required values.
170 // cppcheck-suppress operatorEqToSelf
172  if (source.domain_name_) {
173  domain_name_.reset(new isc::dns::Name(*source.domain_name_));
174 
175  } else {
176  domain_name_.reset();
177  }
178 
179  // Assignment is exception safe.
180  flags_ = source.flags_;
181  rcode1_ = source.rcode1_;
182  rcode2_ = source.rcode2_;
184 
185  return (*this);
186 }
187 
188 void
190 setDomainName(const std::string& domain_name,
191  // cppcheck 1.57 complains that const enum value is not passed
192  // by reference. Note that, it accepts the non-const enum
193  // to be passed by value. In both cases it is unnecessary to
194  // pass the enum by reference.
195  // cppcheck-suppress passedByValue
196  const Option4ClientFqdn::DomainNameType name_type) {
197  // domain-name must be trimmed. Otherwise, string comprising spaces only
198  // would be treated as a fully qualified name.
199  std::string name = isc::util::str::trim(domain_name);
200  if (name.empty()) {
201  if (name_type == Option4ClientFqdn::FULL) {
203  "fully qualified domain-name must not be empty"
204  << " when setting new domain-name for DHCPv4 Client"
205  << " FQDN Option");
206  }
207  // The special case when domain-name is empty is marked by setting the
208  // pointer to the domain-name object to NULL.
209  domain_name_.reset();
210 
211  } else {
212  try {
213  // The second argument indicates that the name should be converted
214  // to lower case.
215  domain_name_.reset(new isc::dns::Name(name, true));
216 
217  } catch (const Exception&) {
219  "invalid domain-name value '"
220  << domain_name << "' when setting new domain-name for"
221  << " DHCPv4 Client FQDN Option");
222 
223  }
224  }
225 
226  domain_name_type_ = name_type;
227 }
228 
229 void
230 Option4ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
231  // The Must Be Zero (MBZ) bits must not be set.
232  if (check_mbz && ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0)) {
234  "invalid DHCPv4 Client FQDN Option flags: 0x"
235  << std::hex << static_cast<int>(flags) << std::dec);
236  }
237 
238  // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
239  // MUST be 0. Checking it here.
243  "both N and S flag of the DHCPv4 Client FQDN Option are set."
244  << " According to RFC 4702, if the N bit is 1 the S bit"
245  << " MUST be 0");
246  }
247 }
248 
249 void
251  OptionBufferConstIter last) {
252 
253  // Buffer must comprise at least one byte with the flags.
254  // The domain-name may be empty.
255  if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
256  isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
257  << DHO_FQDN << ") is truncated");
258  }
259 
260  // Parse flags
261  flags_ = *(first++);
262 
263  // Parse RCODE1 and RCODE2.
264  rcode1_ = Option4ClientFqdn::Rcode(*(first++));
265  rcode2_ = Option4ClientFqdn::Rcode(*(first++));
266 
267  try {
268  if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) {
269  parseCanonicalDomainName(first, last);
270 
271  } else {
272  parseASCIIDomainName(first, last);
273 
274  }
275  } catch (const Exception& ex) {
277  "failed to parse the domain-name in DHCPv4 Client FQDN"
278  << " Option: " << ex.what());
279  }
280 }
281 
282 void
284  OptionBufferConstIter last) {
285  // Parse domain-name if any.
286  if (std::distance(first, last) > 0) {
287  // The FQDN may comprise a partial domain-name. In this case it lacks
288  // terminating 0. If this is the case, we will need to add zero at
289  // the end because Name object constructor requires it.
290  if (*(last - 1) != 0) {
291  // Create temporary buffer and add terminating zero.
292  OptionBuffer buf(first, last);
293  buf.push_back(0);
294  // Reset domain name.
295  isc::util::InputBuffer name_buf(&buf[0], buf.size());
296  // The second argument indicates that the name should be converted
297  // to lower case.
298  domain_name_.reset(new isc::dns::Name(name_buf, true));
299  // Terminating zero was missing, so set the domain-name type
300  // to partial.
302  } else {
303  // We are dealing with fully qualified domain name so there is
304  // no need to add terminating zero. Simply pass the buffer to
305  // Name object constructor.
306  isc::util::InputBuffer name_buf(&(*first),
307  std::distance(first, last));
308  // The second argument indicates that the name should be converted
309  // to lower case.
310  domain_name_.reset(new isc::dns::Name(name_buf, true));
311  // Set the domain-type to fully qualified domain name.
313  }
314  }
315 }
316 
317 void
319  OptionBufferConstIter last) {
320  if (std::distance(first, last) > 0) {
321  std::string domain_name(first, last);
322  // The second argument indicates that the name should be converted
323  // to lower case.
324  domain_name_.reset(new isc::dns::Name(domain_name, true));
325  domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ?
327  }
328 }
329 
330 Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
331  : Option(Option::V4, DHO_FQDN),
332  impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
333 }
334 
336  const Rcode& rcode,
337  const std::string& domain_name,
338  const DomainNameType domain_name_type)
339  : Option(Option::V4, DHO_FQDN),
340  impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
341  domain_name_type)) {
342 }
343 
346  : Option(Option::V4, DHO_FQDN, first, last),
347  impl_(new Option4ClientFqdnImpl(first, last)) {
348 }
349 
351  delete (impl_);
352 }
353 
355  : Option(source),
356  impl_(new Option4ClientFqdnImpl(*source.impl_)) {
357 }
358 
359 OptionPtr
361  return (cloneInternal<Option4ClientFqdn>());
362 }
363 
365 // This assignment operator handles assignment to self, it uses copy
366 // constructor of Option4ClientFqdnImpl to copy all required values.
367 // cppcheck-suppress operatorEqToSelf
369  Option::operator=(source);
370  Option4ClientFqdnImpl* old_impl = impl_;
371  impl_ = new Option4ClientFqdnImpl(*source.impl_);
372  delete(old_impl);
373  return (*this);
374 }
375 
376 bool
377 Option4ClientFqdn::getFlag(const uint8_t flag) const {
378  // Caller should query for one of the: E, N, S or O flags. Any other value
380  if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
381  isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
382  << " Option flag specified, expected E, N, S or O");
383  }
384 
385  return ((impl_->flags_ & flag) != 0);
386 }
387 
388 void
389 Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
390  // Check that flag is in range between 0x1 and 0x7. Although it is
391  // discouraged this check doesn't preclude the caller from setting
392  // multiple flags concurrently.
393  if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
394  isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
395  << " Option flag 0x" << std::hex
396  << static_cast<int>(flag) << std::dec
397  << " is being set. Expected combination of E, N, S and O");
398  }
399 
400  // Copy the current flags into local variable. That way we will be able
401  // to test new flags settings before applying them.
402  uint8_t new_flag = impl_->flags_;
403  if (set_flag) {
404  new_flag |= flag;
405  } else {
406  new_flag &= ~flag;
407  }
408 
409  // Check new flags. If they are valid, apply them. Also, check that MBZ
410  // bits are not set.
411  Option4ClientFqdnImpl::checkFlags(new_flag, true);
412  impl_->flags_ = new_flag;
413 }
414 
415 std::pair<Option4ClientFqdn::Rcode, Option4ClientFqdn::Rcode>
417  return (std::make_pair(impl_->rcode1_, impl_->rcode2_));
418 }
419 
420 void
422  impl_->rcode1_ = rcode;
423  impl_->rcode2_ = rcode;
424 }
425 
426 void
428  impl_->flags_ = 0;
429 }
430 
431 std::string
433  if (impl_->domain_name_) {
434  return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
435  PARTIAL));
436  }
437  // If an object holding domain-name is NULL it means that the domain-name
438  // is empty.
439  return ("");
440 }
441 
442 void
444  // If domain-name is empty, do nothing.
445  if (!impl_->domain_name_) {
446  return;
447  }
448 
449  if (getFlag(FLAG_E)) {
450  // Domain name, encoded as a set of labels.
451  isc::dns::LabelSequence labels(*impl_->domain_name_);
452  if (labels.getDataLength() > 0) {
453  size_t read_len = 0;
454  const uint8_t* data = labels.getData(&read_len);
455  if (impl_->domain_name_type_ == PARTIAL) {
456  --read_len;
457  }
458  buf.writeData(data, read_len);
459  }
460 
461  } else {
462  std::string domain_name = getDomainName();
463  if (!domain_name.empty()) {
464  buf.writeData(&domain_name[0], domain_name.size());
465  }
466 
467  }
468 }
469 
470 void
471 Option4ClientFqdn::setDomainName(const std::string& domain_name,
472  const DomainNameType domain_name_type) {
473  impl_->setDomainName(domain_name, domain_name_type);
474 }
475 
476 void
478  setDomainName("", PARTIAL);
479 }
480 
483  return (impl_->domain_name_type_);
484 }
485 
486 void
488  // Header = option code and length.
489  packHeader(buf, check);
490  // Flags field.
491  buf.writeUint8(impl_->flags_);
492  // RCODE1 and RCODE2
493  buf.writeUint8(impl_->rcode1_.getCode());
494  buf.writeUint8(impl_->rcode2_.getCode());
495  // Domain name.
496  packDomainName(buf);
497 }
498 
499 void
501  OptionBufferConstIter last) {
502  setData(first, last);
503  impl_->parseWireData(first, last);
504  // Check that the flags in the received option are valid. Ignore MBZ bits,
505  // because we don't want to discard the whole option because of MBZ bits
506  // being set.
507  impl_->checkFlags(impl_->flags_, false);
508 }
509 
510 std::string
511 Option4ClientFqdn::toText(int indent) const {
512  std::ostringstream stream;
513  std::string in(indent, ' '); // base indentation
514  stream << in << "type=" << type_ << " (CLIENT_FQDN), "
515  << "flags: ("
516  << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
517  << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", "
518  << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
519  << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
520  << "domain-name='" << getDomainName() << "' ("
521  << (getDomainNameType() == PARTIAL ? "partial" : "full")
522  << ")";
523 
524  return (stream.str());
525 }
526 
527 uint16_t
529  uint16_t domain_name_length = 0;
530  // Try to calculate the length of the domain name only if there is
531  // any domain name specified.
532  if (impl_->domain_name_) {
533  // If the E flag is specified, the domain name is encoded in the
534  // canonical format. The length of the domain name depends on
535  // whether it is a partial or fully qualified names. For the
536  // former the length is 1 octet lesser because it lacks terminating
537  // zero.
538  if (getFlag(FLAG_E)) {
539  domain_name_length = impl_->domain_name_type_ == FULL ?
540  impl_->domain_name_->getLength() :
541  impl_->domain_name_->getLength() - 1;
542 
543  // ASCII encoded domain name. Its length is just a length of the
544  // string holding the name.
545  } else {
546  domain_name_length = getDomainName().length();
547  }
548  }
549 
550  return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
551 }
552 
553 } // end of isc::dhcp namespace
554 } // end of isc namespace
The Name class encapsulates DNS names.
Definition: name.h:223
static const uint8_t FLAG_N
Bit N.
DomainNameType
Type of the domain-name: partial or full.
void resetFlags()
Sets the flag field value to 0.
bool getFlag(const uint8_t flag) const
Checks if the specified flag of the DHCPv4 Client FQDN Option is set.
virtual ~Option4ClientFqdn()
Destructor.
Represents the value of one of the RCODE1 or RCODE2 fields.
void resetDomainName()
Set empty domain-name.
void parseASCIIDomainName(OptionBufferConstIter first, OptionBufferConstIter last)
Parse domain-name encoded in the deprecated ASCII format.
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
virtual void pack(isc::util::OutputBuffer &buf, bool check=true) const
Writes option in the wire format into a buffer.
void packHeader(isc::util::OutputBuffer &buf, bool check=true) const
Store option&#39;s header in a buffer.
Definition: option.cc:119
Represents DHCPv4 Client FQDN Option (code 81).
size_t getDataLength() const
Return the length of the wire-format data of this LabelSequence.
void setDomainName(const std::string &domain_name, const DomainNameType domain_name_type)
Set new domain-name.
virtual uint16_t getHeaderLen() const
Returns length of header (2 for v4, 4 for v6)
Definition: option.cc:321
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition: option.h:24
static const uint16_t FIXED_FIELDS_LEN
The size in bytes of the fixed fields within DHCPv4 Client Fqdn Option.
void writeData(const void *data, size_t len)
Copy an arbitrary length of data into the buffer.
Definition: buffer.h:550
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
static const uint8_t FLAG_S
Bit S.
static void checkFlags(const uint8_t flags, const bool check_mbz)
Check if flags are valid.
const uint8_t * getData(size_t *len) const
Return the wire-format data for this LabelSequence.
DomainNameType getDomainNameType() const
Returns enumerator value which indicates whether domain-name is partial or full.
virtual std::string toText(int indent=0) const
Returns string representation of the option.
Implements the logic for the Option6ClientFqdn class.
Option4ClientFqdn & operator=(const Option4ClientFqdn &source)
Assignment operator.
std::string getDomainName() const
Returns the domain-name in the text format.
void check() const
A protected method used for option correctness.
Definition: option.cc:90
Option4ClientFqdn::Rcode rcode1_
Holds RCODE1 and RCODE2 values.
Exception thrown when invalid flags have been specified for DHCPv4 Client FQDN Option.
Option4ClientFqdn::Rcode rcode2_
Option4ClientFqdn::DomainNameType domain_name_type_
Indicates whether domain name is partial or fully qualified.
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
OptionBuffer::const_iterator OptionBufferConstIter
const_iterator for walking over OptionBuffer
Definition: option.h:30
This is a base class for exceptions thrown from the DNS library module.
Defines the logger used by the top-level component of kea-lfc.
Exception thrown when invalid domain name is specified.
Option & operator=(const Option &rhs)
Assignment operator.
Definition: option.cc:73
std::pair< Rcode, Rcode > getRcode() const
Returns Rcode objects representing value of RCODE1 and RCODE2.
uint8_t getCode() const
Returns the value of the RCODE.
static const uint8_t FLAG_E
Bit E.
static const uint8_t FLAG_O
Bit O.
void setDomainName(const std::string &domain_name, const Option4ClientFqdn::DomainNameType name_type)
Set a new domain name for the option.
virtual void unpack(OptionBufferConstIter first, OptionBufferConstIter last)
Parses option from the received buffer.
void writeUint8(uint8_t data)
Write an unsigned 8-bit integer into the buffer.
Definition: buffer.h:466
void parseWireData(OptionBufferConstIter first, OptionBufferConstIter last)
Parse the Option provided in the wire format.
Option4ClientFqdn(const uint8_t flags, const Rcode &rcode, const std::string &domain_name, const DomainNameType domain_name_type=FULL)
Constructor, creates option instance using flags and domain name.
virtual uint16_t len() const
Returns length of the complete option (data length + DHCPv4 option header).
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition: buffer.h:81
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
Option4ClientFqdnImpl(const uint8_t flags, const Option4ClientFqdn::Rcode &rcode, const std::string &domain_name, const Option4ClientFqdn::DomainNameType name_type)
Constructor, from domain name.
string trim(const string &instring)
Trim Leading and Trailing Spaces.
Definition: strutil.cc:53
void packDomainName(isc::util::OutputBuffer &buf) const
Writes domain-name in the wire format into a buffer.
uint16_t type_
option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
Definition: option.h:588
boost::shared_ptr< isc::dns::Name > domain_name_
Holds the pointer to a domain name carried in the option.
Option4ClientFqdnImpl & operator=(const Option4ClientFqdnImpl &source)
Assignment operator.
void setData(InputIterator first, InputIterator last)
Sets content of this option from buffer.
Definition: option.h:427
uint8_t flags_
Holds flags carried by the option.
static const uint8_t FLAG_MASK
Mask which zeroes MBZ flag bits.
void setFlag(const uint8_t flag, const bool set)
Modifies the value of the specified DHCPv4 Client Fqdn Option flag.
Light-weight Accessor to Name data.
Definition: labelsequence.h:35
void parseCanonicalDomainName(OptionBufferConstIter first, OptionBufferConstIter last)
Parse domain-name encoded in the canonical format.
void setRcode(const Rcode &rcode)
Set Rcode value.
virtual OptionPtr clone() const
Copies this option and returns a pointer to the copy.