Kea 2.5.8
option4_client_fqdn.cc
Go to the documentation of this file.
1// Copyright (C) 2013-2024 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()) {
147 parseWireData(first, last);
148 // Verify that flags value was correct. This constructor is used to parse
149 // incoming packet, so don't check MBZ bits. They are ignored because we
150 // don't want to discard the whole option because MBZ bits are set.
151 checkFlags(flags_, false);
152}
153
156 : flags_(source.flags_),
157 rcode1_(source.rcode1_),
158 rcode2_(source.rcode2_),
159 domain_name_(),
160 domain_name_type_(source.domain_name_type_) {
161 if (source.domain_name_) {
162 domain_name_.reset(new isc::dns::Name(*source.domain_name_));
163 }
164}
165
167// This assignment operator handles assignment to self, it copies all
168// required values.
169// cppcheck-suppress operatorEqToSelf
171 if (source.domain_name_) {
172 domain_name_.reset(new isc::dns::Name(*source.domain_name_));
173
174 } else {
175 domain_name_.reset();
176 }
177
178 // Assignment is exception safe.
179 flags_ = source.flags_;
180 rcode1_ = source.rcode1_;
181 rcode2_ = source.rcode2_;
183
184 return (*this);
185}
186
187void
189setDomainName(const std::string& domain_name,
190 // cppcheck 1.57 complains that const enum value is not passed
191 // by reference. Note that, it accepts the non-const enum
192 // to be passed by value. In both cases it is unnecessary to
193 // pass the enum by reference.
194 // cppcheck-suppress passedByValue
195 const Option4ClientFqdn::DomainNameType name_type) {
196 // domain-name must be trimmed. Otherwise, string comprising spaces only
197 // would be treated as a fully qualified name.
198 std::string name = isc::util::str::trim(domain_name);
199 if (name.empty()) {
200 if (name_type == Option4ClientFqdn::FULL) {
202 "fully qualified domain-name must not be empty"
203 << " when setting new domain-name for DHCPv4 Client"
204 << " FQDN Option");
205 }
206 // The special case when domain-name is empty is marked by setting the
207 // pointer to the domain-name object to NULL.
208 domain_name_.reset();
209
210 } else {
211 try {
212 // The second argument indicates that the name should be converted
213 // to lower case.
214 domain_name_.reset(new isc::dns::Name(name, true));
215
216 } catch (const Exception&) {
218 "invalid domain-name value '"
219 << domain_name << "' when setting new domain-name for"
220 << " DHCPv4 Client FQDN Option");
221
222 }
223 }
224
225 domain_name_type_ = name_type;
226}
227
228void
229Option4ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
230 // The Must Be Zero (MBZ) bits must not be set.
231 if (check_mbz && ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0)) {
233 "invalid DHCPv4 Client FQDN Option flags: 0x"
234 << std::hex << static_cast<int>(flags) << std::dec);
235 }
236
237 // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
238 // MUST be 0. Checking it here.
242 "both N and S flag of the DHCPv4 Client FQDN Option are set."
243 << " According to RFC 4702, if the N bit is 1 the S bit"
244 << " MUST be 0");
245 }
246}
247
248void
251
252 // Buffer must comprise at least one byte with the flags.
253 // The domain-name may be empty.
254 if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
255 isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
256 << DHO_FQDN << ") is truncated");
257 }
258
259 // Parse flags
260 flags_ = *(first++);
261
262 // Parse RCODE1 and RCODE2.
263 rcode1_ = Option4ClientFqdn::Rcode(*(first++));
264 rcode2_ = Option4ClientFqdn::Rcode(*(first++));
265
266 try {
267 if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) {
268 parseCanonicalDomainName(first, last);
269
270 } else {
271 parseASCIIDomainName(first, last);
272
273 }
274 } catch (const Exception& ex) {
275 std::ostringstream errmsg;
276 errmsg << "failed to parse the domain-name in DHCPv4 Client FQDN "
277 << " Option: " << ex.what();
279 isc_throw(SkipThisOptionError, errmsg.str());
280 } else {
282 }
283 }
284}
285
286void
289 // Parse domain-name if any.
290 if (std::distance(first, last) > 0) {
291 // The FQDN may comprise a partial domain-name. In this case it lacks
292 // terminating 0. If this is the case, we will need to add zero at
293 // the end because Name object constructor requires it.
294 if (*(last - 1) != 0) {
295 // Create temporary buffer and add terminating zero.
296 OptionBuffer buf(first, last);
297 buf.push_back(0);
298 // Reset domain name.
299 isc::util::InputBuffer name_buf(&buf[0], buf.size());
300 // The second argument indicates that the name should be converted
301 // to lower case.
302 domain_name_.reset(new isc::dns::Name(name_buf, true));
303 // Terminating zero was missing, so set the domain-name type
304 // to partial.
306 } else {
307 // We are dealing with fully qualified domain name so there is
308 // no need to add terminating zero. Simply pass the buffer to
309 // Name object constructor.
310 isc::util::InputBuffer name_buf(&(*first),
311 std::distance(first, last));
312 // The second argument indicates that the name should be converted
313 // to lower case.
314 domain_name_.reset(new isc::dns::Name(name_buf, true));
315 // Set the domain-type to fully qualified domain name.
317 }
318 }
319}
320
321void
324 if (std::distance(first, last) > 0) {
325 std::string domain_name(first, last);
326 // The second argument indicates that the name should be converted
327 // to lower case.
328 domain_name_.reset(new isc::dns::Name(domain_name, true));
329 domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ?
331 }
332}
333
334Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
335 : Option(Option::V4, DHO_FQDN),
336 impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
337}
338
340 const Rcode& rcode,
341 const std::string& domain_name,
342 const DomainNameType domain_name_type)
343 : Option(Option::V4, DHO_FQDN),
344 impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
345 domain_name_type)) {
346}
347
350 : Option(Option::V4, DHO_FQDN, first, last),
351 impl_(new Option4ClientFqdnImpl(first, last)) {
352}
353
355 delete (impl_);
356}
357
359 : Option(source),
360 impl_(new Option4ClientFqdnImpl(*source.impl_)) {
361}
362
365 return (cloneInternal<Option4ClientFqdn>());
366}
367
369// This assignment operator handles assignment to self, it uses copy
370// constructor of Option4ClientFqdnImpl to copy all required values.
371// cppcheck-suppress operatorEqToSelf
373 Option::operator=(source);
374 Option4ClientFqdnImpl* old_impl = impl_;
375 impl_ = new Option4ClientFqdnImpl(*source.impl_);
376 delete(old_impl);
377 return (*this);
378}
379
380bool
381Option4ClientFqdn::getFlag(const uint8_t flag) const {
382 // Caller should query for one of the: E, N, S or O flags. Any other value
384 if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
385 isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
386 << " Option flag specified, expected E, N, S or O");
387 }
388
389 return ((impl_->flags_ & flag) != 0);
390}
391
392void
393Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
394 // Check that flag is in range between 0x1 and 0x7. Although it is
395 // discouraged this check doesn't preclude the caller from setting
396 // multiple flags concurrently.
397 if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
398 isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
399 << " Option flag 0x" << std::hex
400 << static_cast<int>(flag) << std::dec
401 << " is being set. Expected combination of E, N, S and O");
402 }
403
404 // Copy the current flags into local variable. That way we will be able
405 // to test new flags settings before applying them.
406 uint8_t new_flag = impl_->flags_;
407 if (set_flag) {
408 new_flag |= flag;
409 } else {
410 new_flag &= ~flag;
411 }
412
413 // Check new flags. If they are valid, apply them. Also, check that MBZ
414 // bits are not set.
415 Option4ClientFqdnImpl::checkFlags(new_flag, true);
416 impl_->flags_ = new_flag;
417}
418
419std::pair<Option4ClientFqdn::Rcode, Option4ClientFqdn::Rcode>
421 return (std::make_pair(impl_->rcode1_, impl_->rcode2_));
422}
423
424void
426 impl_->rcode1_ = rcode;
427 impl_->rcode2_ = rcode;
428}
429
430void
432 impl_->flags_ = 0;
433}
434
435std::string
437 if (impl_->domain_name_) {
438 return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
439 PARTIAL));
440 }
441 // If an object holding domain-name is NULL it means that the domain-name
442 // is empty.
443 return ("");
444}
445
446void
448 // If domain-name is empty, do nothing.
449 if (!impl_->domain_name_) {
450 return;
451 }
452
453 if (getFlag(FLAG_E)) {
454 // Domain name, encoded as a set of labels.
456 if (labels.getDataLength() > 0) {
457 size_t read_len = 0;
458 const uint8_t* data = labels.getData(&read_len);
459 if (impl_->domain_name_type_ == PARTIAL) {
460 --read_len;
461 }
462 buf.writeData(data, read_len);
463 }
464
465 } else {
466 std::string domain_name = getDomainName();
467 if (!domain_name.empty()) {
468 buf.writeData(&domain_name[0], domain_name.size());
469 }
470
471 }
472}
473
474void
475Option4ClientFqdn::setDomainName(const std::string& domain_name,
476 const DomainNameType domain_name_type) {
477 impl_->setDomainName(domain_name, domain_name_type);
478}
479
480void
483}
484
487 return (impl_->domain_name_type_);
488}
489
490void
492 // Header = option code and length.
493 packHeader(buf, check);
494 // Flags field.
495 buf.writeUint8(impl_->flags_);
496 // RCODE1 and RCODE2
497 buf.writeUint8(impl_->rcode1_.getCode());
498 buf.writeUint8(impl_->rcode2_.getCode());
499 // Domain name.
500 packDomainName(buf);
501}
502
503void
506 setData(first, last);
507 impl_->parseWireData(first, last);
508 // Check that the flags in the received option are valid. Ignore MBZ bits,
509 // because we don't want to discard the whole option because of MBZ bits
510 // being set.
511 impl_->checkFlags(impl_->flags_, false);
512}
513
514std::string
515Option4ClientFqdn::toText(int indent) const {
516 std::ostringstream stream;
517 std::string in(indent, ' '); // base indentation
518 stream << in << "type=" << type_ << " (CLIENT_FQDN), "
519 << "flags: ("
520 << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
521 << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", "
522 << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
523 << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
524 << "domain-name='" << getDomainName() << "' ("
525 << (getDomainNameType() == PARTIAL ? "partial" : "full")
526 << ")";
527
528 return (stream.str());
529}
530
531uint16_t
533 uint16_t domain_name_length = 0;
534 // Try to calculate the length of the domain name only if there is
535 // any domain name specified.
536 if (impl_->domain_name_) {
537 // If the E flag is specified, the domain name is encoded in the
538 // canonical format. The length of the domain name depends on
539 // whether it is a partial or fully qualified names. For the
540 // former the length is 1 octet lesser because it lacks terminating
541 // zero.
542 if (getFlag(FLAG_E)) {
543 domain_name_length = impl_->domain_name_type_ == FULL ?
544 impl_->domain_name_->getLength() :
545 impl_->domain_name_->getLength() - 1;
546
547 // ASCII encoded domain name. Its length is just a length of the
548 // string holding the name.
549 } else {
550 domain_name_length = getDomainName().length();
551 }
552 }
553
554 return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
555}
556
557} // end of isc::dhcp namespace
558} // 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.
Option4ClientFqdn::Rcode rcode2_
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.
uint8_t getCode() const
Returns the value of the RCODE.
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:590
static bool lenient_parsing_
Governs whether options should be parsed less strictly.
Definition: option.h:483
virtual uint16_t getHeaderLen() const
Returns length of header (2 for v4, 4 for v6)
Definition: option.cc:321
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:427
void packHeader(isc::util::OutputBuffer &buf, bool check=true) const
Store option's header in a buffer.
Definition: option.cc:119
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.
Definition: labelsequence.h:35
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:82
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:347
void writeUint8(uint8_t data)
Write an unsigned 8-bit integer into the buffer.
Definition: buffer.h:474
void writeData(const void *data, size_t len)
Copy an arbitrary length of data into the buffer.
Definition: buffer.h:556
#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.