Kea 2.7.5
master_loader.cc
Go to the documentation of this file.
1// Copyright (C) 2012-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
10#include <dns/master_loader.h>
11#include <dns/master_lexer.h>
12#include <dns/name.h>
13#include <dns/rdataclass.h>
14#include <dns/rrttl.h>
15#include <dns/rrclass.h>
16#include <dns/rrtype.h>
17#include <dns/rdata.h>
18
19#include <boost/format.hpp>
20#include <boost/algorithm/string/predicate.hpp> // for iequals
21#include <boost/scoped_ptr.hpp>
22#include <boost/shared_ptr.hpp>
23
24#include <string>
25#include <memory>
26#include <vector>
27
28#include <cstdio> // for sscanf()
29
30using std::string;
31using std::unique_ptr;
32using std::vector;
33using std::pair;
34using boost::algorithm::iequals;
35using boost::shared_ptr;
36
37namespace isc {
38namespace dns {
39
40namespace {
41
42// An internal exception, used to control the code flow in case of errors.
43// It is thrown during the loading and caught later, not to be propagated
44// outside of the file.
45class InternalException : public isc::Exception {
46public:
47 InternalException(const char* filename, size_t line, const char* what) :
48 Exception(filename, line, what)
49 {}
50};
51
52} // end unnamed namespace
53
59// cppcheck-suppress noConstructor
61public:
82 MasterLoaderImpl(const char* master_file,
83 const Name& zone_origin,
84 const RRClass& zone_class,
85 const MasterLoaderCallbacks& callbacks,
86 const AddRRCallback& add_callback,
87 MasterLoader::Options options) :
88 lexer_(),
89 zone_origin_(zone_origin),
90 active_origin_(zone_origin),
91 zone_class_(zone_class),
92 callbacks_(callbacks),
93 add_callback_(add_callback),
94 options_(options),
95 master_file_(master_file),
96 initialized_(false),
97 ok_(true),
98 many_errors_((options & MANY_ERRORS) != 0),
99 previous_name_(false),
100 complete_(false),
101 seen_error_(false),
102 warn_rfc1035_ttl_(true),
103 rr_count_(0) {
104 }
105
116 void pushSource(const std::string& filename, const Name& current_origin) {
117 std::string error;
118 if (!lexer_.pushSource(filename.c_str(), &error)) {
119 if (initialized_) {
120 isc_throw(InternalException, error.c_str());
121 } else {
122 // Top-level file
123 reportError("", 0, error);
124 ok_ = false;
125 }
126 }
127 // Store the current status, so we can recover it upon popSource
128 include_info_.push_back(IncludeInfo(current_origin, last_name_));
129 initialized_ = true;
130 previous_name_ = false;
131 }
132
139 void pushStreamSource(std::istream& stream) {
140 lexer_.pushSource(stream);
141 initialized_ = true;
142 }
143
147 bool loadIncremental(size_t count_limit);
148
151 size_t getSize() const {
152 return (lexer_.getTotalSourceSize());
153 }
154
157 size_t getPosition() const {
158 return (lexer_.getPosition());
159 }
160
161private:
166 void reportError(const std::string& filename, size_t line,
167 const std::string& reason) {
168 seen_error_ = true;
169 callbacks_.error(filename, line, reason);
170 if (!many_errors_) {
171 // In case we don't have the lenient mode, every error is fatal
172 // and we throw
173 ok_ = false;
174 complete_ = true;
175 isc_throw(MasterLoaderError, reason.c_str());
176 }
177 }
178
185 bool popSource() {
186 if (lexer_.getSourceCount() == 1) {
187 return (false);
188 }
189 lexer_.popSource();
190 // Restore original origin and last seen name
191
192 // We move in tandem, there's an extra item included during the
193 // initialization, so we can never run out of them
194 isc_throw_assert(!include_info_.empty());
195 const IncludeInfo& info(include_info_.back());
196 active_origin_ = info.first;
197 last_name_ = info.second;
198 include_info_.pop_back();
199 previous_name_ = false;
200 return (true);
201 }
202
204 const string getString() {
205 lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
206 return (string_token_);
207 }
208
223 MasterToken handleInitialToken();
224
226 void doOrigin(bool is_optional) {
227 // Parse and create the new origin. It is relative to the previous
228 // one.
229 const MasterToken&
230 name_tok(lexer_.getNextToken(MasterToken::QSTRING, is_optional));
231
232 if (name_tok.getType() == MasterToken::QSTRING ||
233 name_tok.getType() == MasterToken::STRING) {
234
235 const MasterToken::StringRegion&
236 name_string(name_tok.getStringRegion());
237 active_origin_ = Name(name_string.beg, name_string.len,
238 &active_origin_);
239 if (name_string.len > 0 &&
240 name_string.beg[name_string.len - 1] != '.') {
241 callbacks_.warning(lexer_.getSourceName(),
242 lexer_.getSourceLine(),
243 "The new origin is relative, did you really"
244 " mean " + active_origin_.toText() + "?");
245 }
246 } else {
247 // If it is not optional, we must not get anything but
248 // a string token.
249 isc_throw_assert(is_optional);
250
251 // We return the newline there. This is because we want to
252 // behave the same if there is or isn't the name, leaving the
253 // newline there.
254 lexer_.ungetToken();
255 }
256 }
257
259 void doInclude() {
260 // First, get the filename to include
261 const string
262 filename(lexer_.getNextToken(MasterToken::QSTRING).getString());
263
264 // There optionally can be an origin, that applies before the include.
265 // We need to save the currently active origin before calling
266 // doOrigin(), because it would update active_origin_ while we need
267 // to pass the active origin before recognizing the new origin to
268 // pushSource. Note: RFC 1035 is not really clear on this: it reads
269 // "regardless of changes... within the included file", but the new
270 // origin is not really specified "within the included file".
271 // Nevertheless, this behavior is probably more likely to be the
272 // intent of the RFC, and it's compatible with BIND 9.
273 const Name current_origin = active_origin_;
274 doOrigin(true);
275
276 pushSource(filename, current_origin);
277 }
278
289 RRType parseRRParams(bool& explicit_ttl, MasterToken rrparam_token) {
290 // Find TTL, class and type. Both TTL and class are
291 // optional and may occur in any order if they exist. TTL
292 // and class come before type which must exist.
293 //
294 // [<TTL>] [<class>] <type> <RDATA>
295 // [<class>] [<TTL>] <type> <RDATA>
296
297 // named-signzone outputs TTL first, so try parsing it in order
298 // first.
299 if (setCurrentTTL(rrparam_token.getString())) {
300 explicit_ttl = true;
301 rrparam_token = lexer_.getNextToken(MasterToken::STRING);
302 } else {
303 // If it's not a TTL here, continue and try again
304 // after the RR class below.
305 }
306
307 boost::scoped_ptr<RRClass> rrclass
308 (RRClass::createFromText(rrparam_token.getString()));
309 if (rrclass) {
310 if (*rrclass != zone_class_) {
311 isc_throw(InternalException, "Class mismatch: " << *rrclass <<
312 " vs. " << zone_class_);
313 }
314 rrparam_token = lexer_.getNextToken(MasterToken::STRING);
315 }
316
317 // If we couldn't parse TTL earlier in the stream (above), try
318 // again at current location.
319 if (!explicit_ttl && setCurrentTTL(rrparam_token.getString())) {
320 explicit_ttl = true;
321 rrparam_token = lexer_.getNextToken(MasterToken::STRING);
322 }
323
324 // Return the current string token's value as the RRType.
325 return (RRType(rrparam_token.getString()));
326 }
327
347 void limitTTL(RRTTL& ttl, bool post_parsing) {
348 if (ttl > RRTTL::MAX_TTL()) {
349 const size_t src_line = lexer_.getSourceLine() -
350 (post_parsing ? 1 : 0);
351 callbacks_.warning(lexer_.getSourceName(), src_line,
352 "TTL " + ttl.toText() + " > MAXTTL, "
353 "setting to 0 per RFC2181");
354 ttl = RRTTL(0);
355 }
356 }
357
363 void setDefaultTTL(const RRTTL& ttl, bool post_parsing) {
364 assignTTL(default_ttl_, ttl);
365 limitTTL(*default_ttl_, post_parsing);
366 }
367
377 bool setCurrentTTL(const string& ttl_txt) {
378 // We use the factory version instead of RRTTL constructor as we
379 // need to expect cases where ttl_txt does not actually represent a TTL
380 // but an RR class or type.
381 RRTTL* rrttl = RRTTL::createFromText(ttl_txt);
382 if (rrttl) {
383 current_ttl_.reset(rrttl);
384 limitTTL(*current_ttl_, false);
385 return (true);
386 }
387 return (false);
388 }
389
399 const RRTTL& getCurrentTTL(bool explicit_ttl, const RRType& rrtype,
400 const rdata::ConstRdataPtr& rdata) {
401 // We've completed parsing the full of RR, and the lexer is already
402 // positioned at the next line. If we need to call callback,
403 // we need to adjust the line number.
404 const size_t current_line = lexer_.getSourceLine() - 1;
405
406 if (!current_ttl_ && !default_ttl_) {
407 if (rrtype == RRType::SOA()) {
408 callbacks_.warning(lexer_.getSourceName(), current_line,
409 "no TTL specified; "
410 "using SOA MINTTL instead");
411 const uint32_t ttl_val =
412 dynamic_cast<const rdata::generic::SOA&>(*rdata).
413 getMinimum();
414 setDefaultTTL(RRTTL(ttl_val), true);
415 assignTTL(current_ttl_, *default_ttl_);
416 } else {
417 // On catching the exception we'll try to reach EOL again,
418 // so we need to unget it now.
419 lexer_.ungetToken();
420 throw InternalException(__FILE__, __LINE__,
421 "no TTL specified; load rejected");
422 }
423 } else if (!explicit_ttl && default_ttl_) {
424 assignTTL(current_ttl_, *default_ttl_);
425 } else if (!explicit_ttl && warn_rfc1035_ttl_) {
426 // Omitted (class and) TTL values are default to the last
427 // explicitly stated values (RFC 1035, Sec. 5.1).
428 callbacks_.warning(lexer_.getSourceName(), current_line,
429 "using RFC1035 TTL semantics; default to the "
430 "last explicitly stated TTL");
431 warn_rfc1035_ttl_ = false; // we only warn about this once
432 }
433 isc_throw_assert(current_ttl_);
434 return (*current_ttl_);
435 }
436
441 void handleDirective(const char* directive, size_t length) {
442 if (iequals(directive, "INCLUDE")) {
443 doInclude();
444 } else if (iequals(directive, "ORIGIN")) {
445 doOrigin(false);
446 eatUntilEOL(true);
447 } else if (iequals(directive, "TTL")) {
448 setDefaultTTL(RRTTL(getString()), false);
449 eatUntilEOL(true);
450 } else {
451 isc_throw(InternalException, "Unknown directive '" <<
452 string(directive, directive + length) << "'");
453 }
454 }
455
457 void eatUntilEOL(bool reportExtra) {
458 // We want to continue. Try to read until the end of line
459 for (;;) {
460 const MasterToken& token(lexer_.getNextToken());
461 switch (token.getType()) {
463 callbacks_.warning(lexer_.getSourceName(),
464 lexer_.getSourceLine(),
465 "File does not end with newline");
466 // We don't pop here. The End of file will stay there,
467 // and we'll handle it in the next iteration of
468 // loadIncremental properly.
469 return;
471 // Found the end of the line. Good.
472 return;
473 default:
474 // Some other type of token.
475 if (reportExtra) {
476 reportExtra = false;
477 reportError(lexer_.getSourceName(),
478 lexer_.getSourceLine(),
479 "Extra tokens at the end of line");
480 }
481 break;
482 }
483 }
484 }
485
489 static void assignTTL(boost::scoped_ptr<RRTTL>& left, const RRTTL& right) {
490 if (!left) {
491 left.reset(new RRTTL(right));
492 } else {
493 *left = right;
494 }
495 }
496
497private:
498 MasterLexer lexer_;
499 const Name zone_origin_;
500 Name active_origin_; // The origin used during parsing
501 // (modifiable by $ORIGIN)
502 shared_ptr<Name> last_name_; // Last seen name (for INITIAL_WS handling)
503 const RRClass zone_class_;
504 MasterLoaderCallbacks callbacks_;
505 const AddRRCallback add_callback_;
506 boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
507 // unspecified. If null no default
508 // is known.
509 boost::scoped_ptr<RRTTL> current_ttl_; // The TTL used most recently.
510 // Initially unset. Once set
511 // always stores a valid
512 // RRTTL.
513 const MasterLoader::Options options_;
514 const std::string master_file_;
515 std::string string_token_;
516 bool initialized_;
517 bool ok_; // Is it OK to continue loading?
518 const bool many_errors_; // Are many errors allowed (or should we abort
519 // on the first)
520 // Some info about the outer files from which we include.
521 // The first one is current origin, the second is the last seen name
522 // in that file.
523 typedef pair<Name, shared_ptr<Name> > IncludeInfo;
524 vector<IncludeInfo> include_info_;
525 bool previous_name_; // True if there was a previous name in this file
526 // (false at the beginning or after an $INCLUDE line)
527
528public:
529 bool complete_; // All work done.
530 bool seen_error_; // Was there at least one error during the
531 // load?
532 bool warn_rfc1035_ttl_; // should warn if implicit TTL determination
533 // from the previous RR is used.
534 size_t rr_count_; // number of RRs successfully loaded
535};
536
538MasterLoader::MasterLoaderImpl::handleInitialToken() {
539 const MasterToken& initial_token =
541
542 // The most likely case is INITIAL_WS, and then string/qstring. We
543 // handle them first.
544 if (initial_token.getType() == MasterToken::INITIAL_WS) {
545 const MasterToken& next_token = lexer_.getNextToken();
546 if (next_token.getType() == MasterToken::END_OF_LINE) {
547 return (next_token); // blank line
548 } else if (next_token.getType() == MasterToken::END_OF_FILE) {
549 lexer_.ungetToken(); // handle it in the next iteration.
550 eatUntilEOL(true); // effectively warn about the unexpected EOF.
552 }
553
554 // This means the same name as previous.
555 if (!last_name_) {
556 isc_throw(InternalException, "No previous name to use in "
557 "place of initial whitespace");
558 } else if (!previous_name_) {
559 callbacks_.warning(lexer_.getSourceName(), lexer_.getSourceLine(),
560 "Owner name omitted around $INCLUDE, the result "
561 "might not be as expected");
562 }
563 return (next_token);
564 } else if (initial_token.getType() == MasterToken::STRING ||
565 initial_token.getType() == MasterToken::QSTRING) {
566 // If it is name (or directive), handle it.
568 name_string(initial_token.getStringRegion());
569
570 if (name_string.len > 0 && name_string.beg[0] == '$') {
571 // This should have either thrown (and the error handler
572 // will read up until the end of line) or read until the
573 // end of line.
574
575 // Exclude the $ from the string on this point.
576 handleDirective(name_string.beg + 1, name_string.len - 1);
577 // So, get to the next line, there's nothing more interesting
578 // in this one.
580 }
581
582 // This should be an RR, starting with an owner name. Construct the
583 // name, and some string token should follow.
584 last_name_.reset(new Name(name_string.beg, name_string.len,
585 &active_origin_));
586 previous_name_ = true;
587 return (lexer_.getNextToken(MasterToken::STRING));
588 }
589
590 switch (initial_token.getType()) { // handle less common cases
592 if (!popSource()) {
593 return (initial_token);
594 } else {
595 // We try to read a token from the popped source
596 // So continue to the next line of that source, but first, make
597 // sure the source is at EOL
598 eatUntilEOL(true);
599 return (MasterToken(MasterToken::END_OF_LINE));
600 }
602 return (initial_token); // empty line
604 // Error token here.
605 isc_throw(InternalException, initial_token.getErrorText());
606 default:
607 // Some other token (what could that be?)
608 isc_throw(InternalException, "Parser got confused (unexpected "
609 "token " << initial_token.getType() << ")");
610 }
611}
612
613bool
615 if (count_limit == 0) {
616 isc_throw(isc::InvalidParameter, "Count limit set to 0");
617 }
618 if (complete_) {
620 "Trying to load when already loaded");
621 }
622 if (!initialized_) {
623 pushSource(master_file_, active_origin_);
624 }
625 size_t count = 0;
626 while (ok_ && count < count_limit) {
627 try {
628 const MasterToken next_token = handleInitialToken();
629 if (next_token.getType() == MasterToken::END_OF_FILE) {
630 return (true); // we are done
631 } else if (next_token.getType() == MasterToken::END_OF_LINE) {
632 continue; // nothing more to do in this line
633 }
634 // We are going to parse an RR, have known the owner name,
635 // and are now seeing the next string token in the rest of the RR.
636 isc_throw_assert(next_token.getType() == MasterToken::STRING);
637
638 bool explicit_ttl = false;
639 const RRType rrtype = parseRRParams(explicit_ttl, next_token);
640 // TODO: Check if it is SOA, it should be at the origin.
641
642 const rdata::RdataPtr rdata =
643 rdata::createRdata(rrtype, zone_class_, lexer_,
644 &active_origin_, options_, callbacks_);
645
646 // In case we get null, it means there was error creating
647 // the Rdata. The errors should have been reported by
648 // callbacks_ already. We need to decide if we want to continue
649 // or not.
650 if (rdata) {
651 add_callback_(*last_name_, zone_class_, rrtype,
652 getCurrentTTL(explicit_ttl, rrtype, rdata),
653 rdata);
654 // Good, we loaded another one
655 ++count;
656 ++rr_count_;
657 } else {
658 seen_error_ = true;
659 if (!many_errors_) {
660 ok_ = false;
661 complete_ = true;
662 // We don't have the exact error here, but it was reported
663 // by the error callback.
664 isc_throw(MasterLoaderError, "Invalid RR data");
665 }
666 }
667 } catch (const isc::dns::DNSTextError& e) {
668 reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
669 e.what());
670 eatUntilEOL(false);
671 } catch (const MasterLexer::ReadError& e) {
672 reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
673 e.what());
674 eatUntilEOL(false);
675 } catch (const MasterLexer::LexerError& e) {
676 reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
677 e.what());
678 eatUntilEOL(false);
679 } catch (const InternalException& e) {
680 reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
681 e.what());
682 eatUntilEOL(false);
683 }
684 }
685 // When there was a fatal error and ok is false, we say we are done.
686 return (!ok_);
687}
688
689MasterLoader::MasterLoader(const char* master_file,
690 const Name& zone_origin,
691 const RRClass& zone_class,
692 const MasterLoaderCallbacks& callbacks,
693 const AddRRCallback& add_callback,
694 Options options) {
695 if (!add_callback) {
696 isc_throw(isc::InvalidParameter, "Empty add RR callback");
697 }
698 impl_.reset(new MasterLoaderImpl(master_file, zone_origin,
699 zone_class, callbacks, add_callback, options));
700}
701
702MasterLoader::MasterLoader(std::istream& stream,
703 const Name& zone_origin,
704 const RRClass& zone_class,
705 const MasterLoaderCallbacks& callbacks,
706 const AddRRCallback& add_callback,
707 Options options) {
708 if (!add_callback) {
709 isc_throw(isc::InvalidParameter, "Empty add RR callback");
710 }
711 impl_.reset(new MasterLoaderImpl("", zone_origin, zone_class,
712 callbacks, add_callback, options));
713 impl_->pushStreamSource(stream);
714}
715
718
719bool
720MasterLoader::loadIncremental(size_t count_limit) {
721 const bool result = impl_->loadIncremental(count_limit);
722 impl_->complete_ = result;
723 return (result);
724}
725
726bool
728 return (impl_->complete_ && !impl_->seen_error_);
729}
730
731size_t
733 return (impl_->getSize());
734}
735
736size_t
738 return (impl_->getPosition());
739}
740
741} // end namespace dns
742} // end namespace isc
This is a base class for exceptions thrown from the DNS library module.
A generic exception that is thrown if a function is called in a prohibited way.
A generic exception that is thrown if a parameter given to a method or function is considered invalid...
Base class for all sorts of text parse errors.
Exception thrown from a wrapper version of MasterLexer::getNextToken() for non fatal errors.
Exception thrown when we fail to read from the input stream or file.
@ QSTRING
recognize quoted string
@ INITIAL_WS
recognize begin-of-line spaces after an end-of-line
void ungetToken()
Return the last token back to the lexer.
size_t getSourceCount() const
Get number of sources inside the lexer.
bool pushSource(const char *filename, std::string *error=0)
Open a file and make it the current input source of MasterLexer.
size_t getTotalSourceSize() const
Return the total size of pushed sources.
std::string getSourceName() const
Return the name of the current input source name.
const MasterToken & getNextToken(Options options=NONE)
Parse and return another token from the input.
size_t getPosition() const
Return the position of lexer in the pushed sources so far.
void popSource()
Stop using the most recently opened input source (file or stream).
size_t getSourceLine() const
Return the input source line number.
Set of issue callbacks for a loader.
void error(const std::string &source_name, size_t source_line, const std::string &reason) const
Call callback for serious errors.
void warning(const std::string &source_name, size_t source_line, const std::string &reason) const
Call callback for potential problems.
Error while loading by MasterLoader without specifying the MANY_ERRORS option.
Private implementation class for the MasterLoader.
bool loadIncremental(size_t count_limit)
Implementation of MasterLoader::loadIncremental()
MasterLoaderImpl(const char *master_file, const Name &zone_origin, const RRClass &zone_class, const MasterLoaderCallbacks &callbacks, const AddRRCallback &add_callback, MasterLoader::Options options)
Constructor.
size_t getPosition() const
Return the line number being parsed in the pushed input sources.
size_t getSize() const
Return the total size of the input sources pushed so far.
void pushSource(const std::string &filename, const Name &current_origin)
Wrapper around MasterLexer::pushSource() (file version)
void pushStreamSource(std::istream &stream)
Wrapper around MasterLexer::pushSource() (stream version)
size_t getPosition() const
Return the position of the loader in zone.
MasterLoader(const char *master_file, const Name &zone_origin, const RRClass &zone_class, const MasterLoaderCallbacks &callbacks, const AddRRCallback &add_callback, Options options=DEFAULT)
Constructor.
bool loadedSuccessfully() const
Was the loading successful?
Options
Options how the parsing should work.
@ MANY_ERRORS
Lenient mode (see documentation of MasterLoader constructor).
size_t getSize() const
Return the total size of the zone files and streams.
bool loadIncremental(size_t count_limit)
Load some RRs.
Tokens for MasterLexer.
@ INITIAL_WS
White spaces at the beginning of a line after an end of line or at the beginning of file (if asked.
@ ERROR
Error detected in getting a token.
@ END_OF_LINE
End of line detected.
@ STRING
A single string.
@ QSTRING
A single string quoted by double-quotes (").
@ END_OF_FILE
End of file detected.
The Name class encapsulates DNS names.
Definition name.h:219
std::string toText(bool omit_final_dot=false) const
Convert the Name to a string.
Definition name.cc:503
The RRClass class encapsulates DNS resource record classes.
Definition rrclass.h:89
static RRClass * createFromText(const std::string &class_str)
A separate factory of RRClass from text.
Definition rrclass.cc:58
static RRTTL * createFromText(const std::string &ttlstr)
A separate factory of RRTTL from text.
Definition rrttl.cc:176
static const RRTTL & MAX_TTL()
The TTL of the max allowable value, per RFC2181 Section 8.
Definition rrttl.h:285
The RRType class encapsulates DNS resource record types.
Definition rrtype.h:96
static const RRType & SOA()
Definition rrtype.h:291
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition isc_assert.h:18
boost::shared_ptr< const Rdata > ConstRdataPtr
Definition rdata.h:69
RdataPtr createRdata(const RRType &rrtype, const RRClass &rrclass, const std::string &rdata_string)
Create RDATA of a given pair of RR type and class from a string.
Definition rdata.cc:54
boost::shared_ptr< Rdata > RdataPtr
The RdataPtr type is a pointer-like type, pointing to an object of some concrete derived class of Rda...
std::function< void(const Name &name, const RRClass &rrclass, const RRType &rrtype, const RRTTL &rrttl, const rdata::RdataPtr &rdata) AddRRCallback)
Type of callback to add a RR.
Defines the logger used by the top-level component of kea-lfc.
A simple representation of a range of a string.