asiolink.cc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. // Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <cstdlib> // For rand(), temporary until better forwarding is done
  16. #include <unistd.h> // for some IPC/network system calls
  17. #include <sys/socket.h>
  18. #include <netinet/in.h>
  19. #include <vector>
  20. #include <asio.hpp>
  21. #include <boost/lexical_cast.hpp>
  22. #include <boost/bind.hpp>
  23. #include <boost/date_time/posix_time/posix_time_types.hpp>
  24. #include <boost/shared_ptr.hpp>
  25. #include <dns/buffer.h>
  26. #include <dns/message.h>
  27. #include <dns/rcode.h>
  28. #include <asiolink/asiolink.h>
  29. #include <asiolink/internal/tcpdns.h>
  30. #include <asiolink/internal/udpdns.h>
  31. #include <log/dummylog.h>
  32. using namespace asio;
  33. using asio::ip::udp;
  34. using asio::ip::tcp;
  35. using namespace std;
  36. using namespace isc::dns;
  37. using isc::log::dlog;
  38. using namespace boost;
  39. // Is this something we can use in libdns++?
  40. namespace {
  41. class SectionInserter {
  42. public:
  43. SectionInserter(MessagePtr message, const Message::Section sect) :
  44. message_(message), section_(sect)
  45. {}
  46. void operator()(const RRsetPtr rrset) {
  47. message_->addRRset(section_, rrset, true);
  48. }
  49. MessagePtr message_;
  50. const Message::Section section_;
  51. };
  52. /// \brief Copies the parts relevant for a DNS answer to the
  53. /// target message
  54. ///
  55. /// This adds all the RRsets in the answer, authority and
  56. /// additional sections to the target, as well as the response
  57. /// code
  58. void copyAnswerMessage(const Message& source, MessagePtr target) {
  59. target->setRcode(source.getRcode());
  60. for_each(source.beginSection(Message::SECTION_ANSWER),
  61. source.endSection(Message::SECTION_ANSWER),
  62. SectionInserter(target, Message::SECTION_ANSWER));
  63. for_each(source.beginSection(Message::SECTION_AUTHORITY),
  64. source.endSection(Message::SECTION_AUTHORITY),
  65. SectionInserter(target, Message::SECTION_AUTHORITY));
  66. for_each(source.beginSection(Message::SECTION_ADDITIONAL),
  67. source.endSection(Message::SECTION_ADDITIONAL),
  68. SectionInserter(target, Message::SECTION_ADDITIONAL));
  69. }
  70. }
  71. namespace asiolink {
  72. typedef pair<string, uint16_t> addr_t;
  73. class IOServiceImpl {
  74. private:
  75. IOServiceImpl(const IOService& source);
  76. IOServiceImpl& operator=(const IOService& source);
  77. public:
  78. /// \brief The constructor
  79. IOServiceImpl() :
  80. io_service_(),
  81. work_(io_service_)
  82. {};
  83. /// \brief The destructor.
  84. ~IOServiceImpl() {};
  85. //@}
  86. /// \brief Start the underlying event loop.
  87. ///
  88. /// This method does not return control to the caller until
  89. /// the \c stop() method is called via some handler.
  90. void run() { io_service_.run(); };
  91. /// \brief Run the underlying event loop for a single event.
  92. ///
  93. /// This method return control to the caller as soon as the
  94. /// first handler has completed. (If no handlers are ready when
  95. /// it is run, it will block until one is.)
  96. void run_one() { io_service_.run_one();} ;
  97. /// \brief Stop the underlying event loop.
  98. ///
  99. /// This will return the control to the caller of the \c run() method.
  100. void stop() { io_service_.stop();} ;
  101. /// \brief Return the native \c io_service object used in this wrapper.
  102. ///
  103. /// This is a short term work around to support other BIND 10 modules
  104. /// that share the same \c io_service with the authoritative server.
  105. /// It will eventually be removed once the wrapper interface is
  106. /// generalized.
  107. asio::io_service& get_io_service() { return io_service_; };
  108. private:
  109. asio::io_service io_service_;
  110. asio::io_service::work work_;
  111. };
  112. IOService::IOService() {
  113. io_impl_ = new IOServiceImpl();
  114. }
  115. IOService::~IOService() {
  116. delete io_impl_;
  117. }
  118. void
  119. IOService::run() {
  120. io_impl_->run();
  121. }
  122. void
  123. IOService::run_one() {
  124. io_impl_->run_one();
  125. }
  126. void
  127. IOService::stop() {
  128. io_impl_->stop();
  129. }
  130. asio::io_service&
  131. IOService::get_io_service() {
  132. return (io_impl_->get_io_service());
  133. }
  134. class DNSServiceImpl {
  135. public:
  136. DNSServiceImpl(IOService& io_service, const char& port,
  137. const ip::address* v4addr, const ip::address* v6addr,
  138. SimpleCallback* checkin, DNSLookup* lookup,
  139. DNSAnswer* answer);
  140. IOService& io_service_;
  141. typedef boost::shared_ptr<UDPServer> UDPServerPtr;
  142. typedef boost::shared_ptr<TCPServer> TCPServerPtr;
  143. typedef boost::shared_ptr<DNSServer> DNSServerPtr;
  144. vector<DNSServerPtr> servers_;
  145. SimpleCallback *checkin_;
  146. DNSLookup *lookup_;
  147. DNSAnswer *answer_;
  148. void addServer(uint16_t port, const ip::address& address) {
  149. try {
  150. dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<string>(port));
  151. TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
  152. address, port, checkin_, lookup_, answer_));
  153. (*tcpServer)();
  154. servers_.push_back(tcpServer);
  155. dlog(std::string("Initialize UDP server at ") + address.to_string() + ":" + boost::lexical_cast<string>(port));
  156. UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
  157. address, port, checkin_, lookup_, answer_));
  158. (*udpServer)();
  159. servers_.push_back(udpServer);
  160. }
  161. catch (const asio::system_error& err) {
  162. // We need to catch and convert any ASIO level exceptions.
  163. // This can happen for unavailable address, binding a privilege port
  164. // without the privilege, etc.
  165. isc_throw(IOError, "Failed to initialize network servers: " <<
  166. err.what());
  167. }
  168. }
  169. void addServer(const char& port, const ip::address& address) {
  170. uint16_t portnum;
  171. try {
  172. // XXX: SunStudio with stlport4 doesn't reject some invalid
  173. // representation such as "-1" by lexical_cast<uint16_t>, so
  174. // we convert it into a signed integer of a larger size and perform
  175. // range check ourselves.
  176. const int32_t portnum32 = boost::lexical_cast<int32_t>(&port);
  177. if (portnum32 < 0 || portnum32 > 65535) {
  178. isc_throw(IOError, "Invalid port number '" << &port);
  179. }
  180. portnum = portnum32;
  181. } catch (const boost::bad_lexical_cast& ex) {
  182. isc_throw(IOError, "Invalid port number '" << &port << "': " <<
  183. ex.what());
  184. }
  185. addServer(portnum, address);
  186. }
  187. };
  188. DNSServiceImpl::DNSServiceImpl(IOService& io_service,
  189. const char& port,
  190. const ip::address* const v4addr,
  191. const ip::address* const v6addr,
  192. SimpleCallback* checkin,
  193. DNSLookup* lookup,
  194. DNSAnswer* answer) :
  195. io_service_(io_service),
  196. checkin_(checkin),
  197. lookup_(lookup),
  198. answer_(answer)
  199. {
  200. if (v4addr) {
  201. addServer(port, *v4addr);
  202. }
  203. if (v6addr) {
  204. addServer(port, *v6addr);
  205. }
  206. }
  207. DNSService::DNSService(IOService& io_service,
  208. const char& port, const char& address,
  209. SimpleCallback* checkin,
  210. DNSLookup* lookup,
  211. DNSAnswer* answer) :
  212. impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
  213. answer)), io_service_(io_service)
  214. {
  215. addServer(port, &address);
  216. }
  217. DNSService::DNSService(IOService& io_service,
  218. const char& port,
  219. const bool use_ipv4, const bool use_ipv6,
  220. SimpleCallback* checkin,
  221. DNSLookup* lookup,
  222. DNSAnswer* answer) :
  223. impl_(NULL), io_service_(io_service)
  224. {
  225. const ip::address v4addr_any = ip::address(ip::address_v4::any());
  226. const ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL;
  227. const ip::address v6addr_any = ip::address(ip::address_v6::any());
  228. const ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
  229. impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer);
  230. }
  231. DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
  232. DNSLookup* lookup, DNSAnswer *answer) :
  233. impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
  234. answer)), io_service_(io_service)
  235. {
  236. }
  237. DNSService::~DNSService() {
  238. delete impl_;
  239. }
  240. namespace {
  241. typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
  242. }
  243. RecursiveQuery::RecursiveQuery(DNSService& dns_service,
  244. const AddressVector& upstream,
  245. const AddressVector& upstream_root,
  246. int timeout, unsigned retries) :
  247. dns_service_(dns_service), upstream_(new AddressVector(upstream)),
  248. upstream_root_(new AddressVector(upstream_root)),
  249. timeout_(timeout), retries_(retries)
  250. {}
  251. namespace {
  252. ip::address
  253. convertAddr(const string& address) {
  254. error_code err;
  255. ip::address addr = ip::address::from_string(address, err);
  256. if (err) {
  257. isc_throw(IOError, "Invalid IP address '" << &address << "': "
  258. << err.message());
  259. }
  260. return (addr);
  261. }
  262. }
  263. void
  264. DNSService::addServer(const char& port, const string& address) {
  265. impl_->addServer(port, convertAddr(address));
  266. }
  267. void
  268. DNSService::addServer(uint16_t port, const string& address) {
  269. impl_->addServer(port, convertAddr(address));
  270. }
  271. void
  272. DNSService::clearServers() {
  273. // FIXME: This does not work, it does not close the socket.
  274. // How is it done?
  275. impl_->servers_.clear();
  276. }
  277. namespace {
  278. /*
  279. * This is a query in progress. When a new query is made, this one holds
  280. * the context information about it, like how many times we are allowed
  281. * to retry on failure, what to do when we succeed, etc.
  282. *
  283. * Used by RecursiveQuery::sendQuery.
  284. */
  285. class RunningQuery : public UDPQuery::Callback {
  286. private:
  287. // The io service to handle async calls
  288. asio::io_service& io_;
  289. // Info for (re)sending the query (the question and destination)
  290. Question question_;
  291. // This is where we build and store our final answer
  292. MessagePtr answer_message_;
  293. // currently we use upstream as the current list of NS records
  294. // we should differentiate between forwarding and resolving
  295. shared_ptr<AddressVector> upstream_;
  296. // root servers...just copied over to the zone_servers_
  297. shared_ptr<AddressVector> upstream_root_;
  298. // Buffer to store the result.
  299. OutputBufferPtr buffer_;
  300. // Server to notify when we succeed or fail
  301. shared_ptr<DNSServer> server_;
  302. /*
  303. * TODO Do something more clever with timeouts. In the long term, some
  304. * computation of average RTT, increase with each retry, etc.
  305. */
  306. // Timeout information
  307. int timeout_;
  308. unsigned retries_;
  309. // normal query state
  310. // if we change this to running and add a sent, we can do
  311. // decoupled timeouts i think
  312. bool done;
  313. // Not using NSAS at this moment, so we keep a list
  314. // of 'current' zone servers
  315. vector<addr_t> zone_servers_;
  316. // Update the question that will be sent to the server
  317. void setQuestion(const Question& new_question) {
  318. question_ = new_question;
  319. }
  320. // (re)send the query to the server.
  321. void send() {
  322. const int uc = upstream_->size();
  323. const int zs = zone_servers_.size();
  324. buffer_->clear();
  325. if (uc > 0) {
  326. int serverIndex = rand() % uc;
  327. dlog("Sending upstream query (" + question_.toText() +
  328. ") to " + upstream_->at(serverIndex).first);
  329. UDPQuery query(io_, question_,
  330. upstream_->at(serverIndex).first,
  331. upstream_->at(serverIndex).second, buffer_, this,
  332. timeout_);
  333. io_.post(query);
  334. } else if (zs > 0) {
  335. int serverIndex = rand() % zs;
  336. dlog("Sending query to zone server (" + question_.toText() +
  337. ") to " + zone_servers_.at(serverIndex).first);
  338. UDPQuery query(io_, question_,
  339. zone_servers_.at(serverIndex).first,
  340. zone_servers_.at(serverIndex).second, buffer_, this,
  341. timeout_);
  342. io_.post(query);
  343. } else {
  344. dlog("Error, no upstream servers to send to.");
  345. }
  346. }
  347. // This function is called by operator() if there is an actual
  348. // answer from a server and we are in recursive mode
  349. // depending on the contents, we go on recursing or return
  350. //
  351. // Note that the footprint may change as this function may
  352. // need to append data to the answer we are building later.
  353. //
  354. // returns true if we are done
  355. // returns false if we are not done
  356. bool handleRecursiveAnswer(const Message& incoming) {
  357. if (incoming.getRRCount(Message::SECTION_ANSWER) > 0) {
  358. dlog("Got final result, copying answer.");
  359. copyAnswerMessage(incoming, answer_message_);
  360. return true;
  361. } else {
  362. dlog("Got delegation, continuing");
  363. // ok we need to do some more processing.
  364. // the ns list should contain all nameservers
  365. // while the additional may contain addresses for
  366. // them.
  367. // this needs to tie into NSAS of course
  368. // for this very first mockup, hope there is an
  369. // address in additional and just use that
  370. // send query to the addresses in the delegation
  371. bool found_ns_address = false;
  372. zone_servers_.clear();
  373. for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
  374. rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
  375. rrsi++) {
  376. ConstRRsetPtr rrs = *rrsi;
  377. if (rrs->getType() == RRType::A()) {
  378. // found address
  379. RdataIteratorPtr rdi = rrs->getRdataIterator();
  380. // just use the first for now
  381. if (!rdi->isLast()) {
  382. std::string addr_str = rdi->getCurrent().toText();
  383. dlog("[XX] first address found: " + addr_str);
  384. // now we have one address, simply
  385. // resend that exact same query
  386. // to that address and yield, when it
  387. // returns, loop again.
  388. // should use NSAS
  389. zone_servers_.push_back(addr_t(addr_str, 53));
  390. found_ns_address = true;
  391. }
  392. }
  393. }
  394. if (found_ns_address) {
  395. // next resolver round
  396. send();
  397. return false;
  398. } else {
  399. dlog("[XX] no ready-made addresses in additional. need nsas.");
  400. // this will result in answering with the delegation. oh well
  401. copyAnswerMessage(incoming, answer_message_);
  402. return true;
  403. }
  404. }
  405. }
  406. public:
  407. RunningQuery(asio::io_service& io, const Question &question,
  408. MessagePtr answer_message, shared_ptr<AddressVector> upstream,
  409. shared_ptr<AddressVector> upstream_root,
  410. OutputBufferPtr buffer, DNSServer* server, int timeout,
  411. unsigned retries) :
  412. io_(io),
  413. question_(question),
  414. answer_message_(answer_message),
  415. upstream_(upstream),
  416. upstream_root_(upstream_root),
  417. buffer_(buffer),
  418. server_(server->clone()),
  419. timeout_(timeout),
  420. retries_(retries),
  421. zone_servers_()
  422. {
  423. dlog("Started a new RunningQuery");
  424. done = false;
  425. // should use NSAS for root servers
  426. // Adding root servers if not a forwarder
  427. if (upstream_->empty()) {
  428. if (upstream_root_->empty()) { //if no root ips given, use this
  429. zone_servers_.push_back(addr_t("192.5.5.241", 53));
  430. }
  431. else
  432. {
  433. //copy the list
  434. dlog("Size is " +
  435. boost::lexical_cast<string>(upstream_root_->size()) +
  436. "\n");
  437. //Use BOOST_FOREACH here? Is it faster?
  438. for(AddressVector::iterator it = upstream_root_->begin();
  439. it < upstream_root_->end(); it++) {
  440. zone_servers_.push_back(addr_t(it->first,it->second));
  441. dlog("Put " + zone_servers_.back().first + "into root list\n");
  442. }
  443. }
  444. }
  445. send();
  446. }
  447. // This function is used as callback from DNSQuery.
  448. virtual void operator()(UDPQuery::Result result) {
  449. // XXX is this the place for TCP retry?
  450. if (result != UDPQuery::TIME_OUT) {
  451. // we got an answer
  452. Message incoming(Message::PARSE);
  453. InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
  454. incoming.fromWire(ibuf);
  455. if (upstream_->size() == 0 &&
  456. incoming.getRcode() == Rcode::NOERROR()) {
  457. done = handleRecursiveAnswer(incoming);
  458. } else {
  459. copyAnswerMessage(incoming, answer_message_);
  460. done = true;
  461. }
  462. if (done) {
  463. server_->resume(result == UDPQuery::SUCCESS);
  464. delete this;
  465. }
  466. } else if (retries_--) {
  467. // We timed out, but we have some retries, so send again
  468. dlog("Timeout, resending query");
  469. send();
  470. } else {
  471. // out of retries, give up for now
  472. server_->resume(false);
  473. delete this;
  474. }
  475. }
  476. };
  477. }
  478. void
  479. RecursiveQuery::sendQuery(const Question& question,
  480. MessagePtr answer_message,
  481. OutputBufferPtr buffer,
  482. DNSServer* server)
  483. {
  484. // XXX: eventually we will need to be able to determine whether
  485. // the message should be sent via TCP or UDP, or sent initially via
  486. // UDP and then fall back to TCP on failure, but for the moment
  487. // we're only going to handle UDP.
  488. asio::io_service& io = dns_service_.get_io_service();
  489. // It will delete itself when it is done
  490. new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
  491. buffer, server, timeout_, retries_);
  492. }
  493. class IntervalTimerImpl {
  494. private:
  495. // prohibit copy
  496. IntervalTimerImpl(const IntervalTimerImpl& source);
  497. IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
  498. public:
  499. IntervalTimerImpl(IOService& io_service);
  500. ~IntervalTimerImpl();
  501. void setupTimer(const IntervalTimer::Callback& cbfunc,
  502. const uint32_t interval);
  503. void callback(const asio::error_code& error);
  504. void cancel() {
  505. timer_.cancel();
  506. interval_ = 0;
  507. }
  508. uint32_t getInterval() const { return (interval_); }
  509. private:
  510. // a function to update timer_ when it expires
  511. void updateTimer();
  512. // a function to call back when timer_ expires
  513. IntervalTimer::Callback cbfunc_;
  514. // interval in seconds
  515. uint32_t interval_;
  516. // asio timer
  517. asio::deadline_timer timer_;
  518. };
  519. IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
  520. interval_(0), timer_(io_service.get_io_service())
  521. {}
  522. IntervalTimerImpl::~IntervalTimerImpl()
  523. {}
  524. void
  525. IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
  526. const uint32_t interval)
  527. {
  528. // Interval should not be 0.
  529. if (interval == 0) {
  530. isc_throw(isc::BadValue, "Interval should not be 0");
  531. }
  532. // Call back function should not be empty.
  533. if (cbfunc.empty()) {
  534. isc_throw(isc::InvalidParameter, "Callback function is empty");
  535. }
  536. cbfunc_ = cbfunc;
  537. interval_ = interval;
  538. // Set initial expire time.
  539. // At this point the timer is not running yet and will not expire.
  540. // After calling IOService::run(), the timer will expire.
  541. updateTimer();
  542. return;
  543. }
  544. void
  545. IntervalTimerImpl::updateTimer() {
  546. if (interval_ == 0) {
  547. // timer has been canceled. Do nothing.
  548. return;
  549. }
  550. try {
  551. // Update expire time to (current time + interval_).
  552. timer_.expires_from_now(boost::posix_time::seconds(interval_));
  553. } catch (const asio::system_error& e) {
  554. isc_throw(isc::Unexpected, "Failed to update timer");
  555. }
  556. // Reset timer.
  557. timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
  558. }
  559. void
  560. IntervalTimerImpl::callback(const asio::error_code& cancelled) {
  561. // Do not call cbfunc_ in case the timer was cancelled.
  562. // The timer will be canelled in the destructor of asio::deadline_timer.
  563. if (!cancelled) {
  564. cbfunc_();
  565. // Set next expire time.
  566. updateTimer();
  567. }
  568. }
  569. IntervalTimer::IntervalTimer(IOService& io_service) {
  570. impl_ = new IntervalTimerImpl(io_service);
  571. }
  572. IntervalTimer::~IntervalTimer() {
  573. delete impl_;
  574. }
  575. void
  576. IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
  577. return (impl_->setupTimer(cbfunc, interval));
  578. }
  579. void
  580. IntervalTimer::cancel() {
  581. impl_->cancel();
  582. }
  583. uint32_t
  584. IntervalTimer::getInterval() const {
  585. return (impl_->getInterval());
  586. }
  587. }