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