Kea 3.1.3
option4_client_fqdn.cc
Go to the documentation of this file.
1// Copyright (C) 2013-2025 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 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
188void
190setDomainName(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
229void
230Option4ClientFqdnImpl::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
249void
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) {
276 std::ostringstream errmsg;
277 errmsg << "failed to parse the domain-name in DHCPv4 Client FQDN "
278 << " Option: " << ex.what();
280 isc_throw(SkipThisOptionError, errmsg.str());
281 } else {
283 }
284 }
285}
286
287void
290 // Parse domain-name if any.
291 if (std::distance(first, last) > 0) {
292 // The FQDN may comprise a partial domain-name. In this case it lacks
293 // terminating 0. If this is the case, we will need to add zero at
294 // the end because Name object constructor requires it.
295 if (*(last - 1) != 0) {
296 // Create temporary buffer and add terminating zero.
297 OptionBuffer buf(first, last);
298 buf.push_back(0);
299 // Reset domain name.
300 isc::util::InputBuffer name_buf(&buf[0], buf.size());
301 // The second argument indicates that the name should be converted
302 // to lower case.
303 domain_name_.reset(new isc::dns::Name(name_buf, true));
304 // Terminating zero was missing, so set the domain-name type
305 // to partial.
307 } else {
308 // We are dealing with fully qualified domain name so there is
309 // no need to add terminating zero. Simply pass the buffer to
310 // Name object constructor.
311 isc::util::InputBuffer name_buf(&(*first),
312 std::distance(first, last));
313 // The second argument indicates that the name should be converted
314 // to lower case.
315 domain_name_.reset(new isc::dns::Name(name_buf, true));
316 // Set the domain-type to fully qualified domain name.
318 }
319 }
320}
321
322void
325 if (std::distance(first, last) > 0) {
326 std::string domain_name(first, last);
327 // The second argument indicates that the name should be converted
328 // to lower case.
329 domain_name_.reset(new isc::dns::Name(domain_name, true));
330 domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ?
332 }
333}
334
335Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
337 impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
338}
339
341 const Rcode& rcode,
342 const std::string& domain_name,
343 const DomainNameType domain_name_type)
345 impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
346 domain_name_type)) {
347}
348
354
356 delete (impl_);
357}
358
360 : Option(source),
361 impl_(new Option4ClientFqdnImpl(*source.impl_)) {
362}
363
368
370// This assignment operator handles assignment to self, it uses copy
371// constructor of Option4ClientFqdnImpl to copy all required values.
372// cppcheck-suppress operatorEqToSelf
374 Option::operator=(source);
375 Option4ClientFqdnImpl* old_impl = impl_;
376 impl_ = new Option4ClientFqdnImpl(*source.impl_);
377 delete(old_impl);
378 return (*this);
379}
380
381bool
382Option4ClientFqdn::getFlag(const uint8_t flag) const {
383 // Caller should query for one of the: E, N, S or O flags. Any other value
385 if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
386 isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
387 << " Option flag specified, expected E, N, S or O");
388 }
389
390 return ((impl_->flags_ & flag) != 0);
391}
392
393void
394Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
395 // Check that flag is in range between 0x1 and 0x7. Although it is
396 // discouraged this check doesn't preclude the caller from setting
397 // multiple flags concurrently.
398 if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
399 isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
400 << " Option flag 0x" << std::hex
401 << static_cast<int>(flag) << std::dec
402 << " is being set. Expected combination of E, N, S and O");
403 }
404
405 // Copy the current flags into local variable. That way we will be able
406 // to test new flags settings before applying them.
407 uint8_t new_flag = impl_->flags_;
408 if (set_flag) {
409 new_flag |= flag;
410 } else {
411 new_flag &= ~flag;
412 }
413
414 // Check new flags. If they are valid, apply them. Also, check that MBZ
415 // bits are not set.
416 Option4ClientFqdnImpl::checkFlags(new_flag, true);
417 impl_->flags_ = new_flag;
418}
419
420std::pair<Option4ClientFqdn::Rcode, Option4ClientFqdn::Rcode>
422 return (std::make_pair(impl_->rcode1_, impl_->rcode2_));
423}
424
425void
427 impl_->rcode1_ = rcode;
428 impl_->rcode2_ = rcode;
429}
430
431void
433 impl_->flags_ = 0;
434}
435
436std::string
438 if (impl_->domain_name_) {
439 return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
440 PARTIAL));
441 }
442 // If an object holding domain-name is NULL it means that the domain-name
443 // is empty.
444 return ("");
445}
446
447void
449 // If domain-name is empty, do nothing.
450 if (!impl_->domain_name_) {
451 return;
452 }
453
454 if (getFlag(FLAG_E)) {
455 // Domain name, encoded as a set of labels.
456 isc::dns::LabelSequence labels(*impl_->domain_name_);
457 if (labels.getDataLength() > 0) {
458 size_t read_len = 0;
459 const uint8_t* data = labels.getData(&read_len);
460 if (impl_->domain_name_type_ == PARTIAL) {
461 --read_len;
462 }
463 buf.writeData(data, read_len);
464 }
465
466 } else {
467 std::string domain_name = getDomainName();
468 if (!domain_name.empty()) {
469 buf.writeData(&domain_name[0], domain_name.size());
470 }
471
472 }
473}
474
475void
476Option4ClientFqdn::setDomainName(const std::string& domain_name,
477 const DomainNameType domain_name_type) {
478 impl_->setDomainName(domain_name, domain_name_type);
479}
480
481void
485
488 return (impl_->domain_name_type_);
489}
490
491void
493 // Header = option code and length.
494 packHeader(buf, check);
495 // Flags field.
496 buf.writeUint8(impl_->flags_);
497 // RCODE1 and RCODE2
498 buf.writeUint8(impl_->rcode1_.getCode());
499 buf.writeUint8(impl_->rcode2_.getCode());
500 // Domain name.
501 packDomainName(buf);
502}
503
504void
507 setData(first, last);
508 impl_->parseWireData(first, last);
509 // Check that the flags in the received option are valid. Ignore MBZ bits,
510 // because we don't want to discard the whole option because of MBZ bits
511 // being set.
512 impl_->checkFlags(impl_->flags_, false);
513}
514
515std::string
516Option4ClientFqdn::toText(int indent) const {
517 std::ostringstream stream;
518 std::string in(indent, ' '); // base indentation
519 stream << in << "type=" << type_ << " (CLIENT_FQDN), "
520 << "flags: ("
521 << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
522 << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", "
523 << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
524 << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
525 << "domain-name='" << getDomainName() << "' ("
526 << (getDomainNameType() == PARTIAL ? "partial" : "full")
527 << ")";
528
529 return (stream.str());
530}
531
532uint16_t
534 uint16_t domain_name_length = 0;
535 // Try to calculate the length of the domain name only if there is
536 // any domain name specified.
537 if (impl_->domain_name_) {
538 // If the E flag is specified, the domain name is encoded in the
539 // canonical format. The length of the domain name depends on
540 // whether it is a partial or fully qualified names. For the
541 // former the length is 1 octet lesser because it lacks terminating
542 // zero.
543 if (getFlag(FLAG_E)) {
544 domain_name_length = impl_->domain_name_type_ == FULL ?
545 impl_->domain_name_->getLength() :
546 impl_->domain_name_->getLength() - 1;
547
548 // ASCII encoded domain name. Its length is just a length of the
549 // string holding the name.
550 } else {
551 domain_name_length = getDomainName().length();
552 }
553 }
554
555 return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
556}
557
558} // end of isc::dhcp namespace
559} // 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 Option6ClientFqdn 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.
DomainNameType
Type of the domain-name: partial or full.
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.
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:597
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:327
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.