Kea  2.1.7-git
dns_client.cc
Go to the documentation of this file.
1 // Copyright (C) 2013-2021 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 <d2srv/d2_log.h>
10 #include <d2srv/dns_client.h>
11 #include <dns/messagerenderer.h>
12 #include <stats/stats_mgr.h>
13 #include <limits>
14 
15 namespace isc {
16 namespace d2 {
17 
18 namespace {
19 
20 // OutputBuffer objects are pre-allocated before data is written to them.
21 // This is a default number of bytes for the buffers we create within
22 // DNSClient class.
23 const size_t DEFAULT_BUFFER_SIZE = 128;
24 
25 }
26 
27 using namespace isc::util;
28 using namespace isc::asiolink;
29 using namespace isc::asiodns;
30 using namespace isc::dns;
31 using namespace isc::stats;
32 
33 // This class provides the implementation for the DNSClient. This allows for
34 // the separation of the DNSClient interface from the implementation details.
35 // Currently, implementation uses IOFetch object to handle asynchronous
36 // communication with the DNS. This design may be revisited in the future. If
37 // implementation is changed, the DNSClient API will remain unchanged thanks
38 // to this separation.
40 public:
43 
55 
59 
62 
65 
67  std::string tsig_key_name_;
68 
78  DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
79  DNSClient::Callback* callback,
80  const DNSClient::Protocol proto);
81 
83  virtual ~DNSClientImpl();
84 
89  virtual void operator()(asiodns::IOFetch::Result result);
90 
103  void doUpdate(asiolink::IOService& io_service,
104  const asiolink::IOAddress& ns_addr,
105  const uint16_t ns_port,
106  D2UpdateMessage& update,
107  const unsigned int wait,
108  const D2TsigKeyPtr& tsig_key);
109 
114  DNSClient::Status getStatus(const asiodns::IOFetch::Result result);
115 
121  void incrStats(const std::string& stat, bool update_key = true);
122 };
123 
125  DNSClient::Callback* callback,
126  const DNSClient::Protocol proto)
127  : in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
128  response_(response_placeholder), callback_(callback), proto_(proto) {
129 
130  // Response should be an empty pointer. It gets populated by the
131  // operator() method.
132  if (response_) {
133  isc_throw(isc::BadValue, "Response buffer pointer should be null");
134  }
135 
136  // @todo Currently we only support UDP. The support for TCP is planned for
137  // the future release.
138  if (proto_ == DNSClient::TCP) {
139  isc_throw(isc::NotImplemented, "TCP is currently not supported as a"
140  << " Transport protocol for DNS Updates; please use UDP");
141  }
142 
143  // Given that we already eliminated the possibility that TCP is used, it
144  // would be sufficient to check that (proto != DNSClient::UDP). But, once
145  // support TCP is added the check above will disappear and the extra check
146  // will be needed here anyway.
147  // Note that cascaded check is used here instead of:
148  // if (proto_ != DNSClient::TCP && proto_ != DNSClient::UDP)..
149  // because some versions of GCC compiler complain that check above would
150  // be always 'false' due to limited range of enumeration. In fact, it is
151  // possible to pass out of range integral value through enum and it should
152  // be caught here.
153  if (proto_ != DNSClient::TCP) {
154  if (proto_ != DNSClient::UDP) {
155  isc_throw(isc::NotImplemented, "invalid transport protocol type '"
156  << proto_ << "' specified for DNS Updates");
157  }
158  }
159 }
160 
162 }
163 
164 void
166  // Get the status from IO. If no success, we just call user's callback
167  // and pass the status code.
168  DNSClient::Status status = getStatus(result);
169  if (status == DNSClient::SUCCESS) {
170  // Allocate a new response message. (Note that Message::fromWire
171  // may only be run once per message, so we need to start fresh
172  // each time.)
174 
175  // Server's response may be corrupted. In such case, fromWire will
176  // throw an exception. We want to catch this exception to return
177  // appropriate status code to the caller and log this event.
178  try {
179  response_->fromWire(in_buf_->getData(), in_buf_->getLength(),
180  tsig_context_.get());
181  incrStats("update-success");
182  } catch (const isc::Exception& ex) {
185  DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
186  incrStats("update-error");
187  }
188 
189  if (tsig_context_) {
190  // Context is a one-shot deal, get rid of it.
191  tsig_context_.reset();
192  }
193  } else if (status == DNSClient::TIMEOUT) {
194  incrStats("update-timeout");
195  } else {
196  incrStats("update-error");
197  }
198 
199  // Once we are done with internal business, let's call a callback supplied
200  // by a caller.
201  if (callback_ != NULL) {
202  (*callback_)(status);
203  }
204 }
205 
208  switch (result) {
209  case IOFetch::SUCCESS:
210  return (DNSClient::SUCCESS);
211 
212  case IOFetch::TIME_OUT:
213  return (DNSClient::TIMEOUT);
214 
215  case IOFetch::STOPPED:
216  return (DNSClient::IO_STOPPED);
217 
218  default:
219  ;
220  }
221  return (DNSClient::OTHER);
222 }
223 
224 void
226  const IOAddress& ns_addr,
227  const uint16_t ns_port,
228  D2UpdateMessage& update,
229  const unsigned int wait,
230  const D2TsigKeyPtr& tsig_key) {
231  // The underlying implementation which we use to send DNS Updates uses
232  // signed integers for timeout. If we want to avoid overflows we need to
233  // respect this limitation here.
234  if (wait > DNSClient::getMaxTimeout()) {
235  isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
236  " not exceed " << DNSClient::getMaxTimeout()
237  << ". Provided timeout value is '" << wait << "'");
238  }
239 
240  // Create a TSIG context if we have a key, otherwise clear the context
241  // pointer. Message marshalling uses non-null context is the indicator
242  // that TSIG should be used.
243  if (tsig_key) {
244  tsig_context_ = tsig_key->createContext();
245  tsig_key_name_ = tsig_key->getKeyName().toText();
246  } else {
247  tsig_context_.reset();
248  tsig_key_name_.clear();
249  }
250 
251  // A renderer is used by the toWire function which creates the on-wire data
252  // from the DNS Update message. A renderer has its internal buffer where it
253  // renders data by default. However, this buffer can't be directly accessed.
254  // Fortunately, the renderer's API accepts user-supplied buffers. So, let's
255  // create our own buffer and pass it to the renderer so as the message is
256  // rendered to this buffer. Finally, we pass this buffer to IOFetch.
257  dns::MessageRenderer renderer;
258  OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
259  renderer.setBuffer(msg_buf.get());
260 
261  // Render DNS Update message. This may throw a bunch of exceptions if
262  // invalid message object is given.
263  update.toWire(renderer, tsig_context_.get());
264 
265  // IOFetch has all the mechanisms that we need to perform asynchronous
266  // communication with the DNS server. The last but one argument points to
267  // this object as a completion callback for the message exchange. As a
268  // result operator()(Status) will be called.
269 
270  // Timeout value is explicitly cast to the int type to avoid warnings about
271  // overflows when doing implicit cast. It should have been checked by the
272  // caller that the unsigned timeout value will fit into int.
273  IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
274  in_buf_, this, static_cast<int>(wait));
275 
276  // Post the task to the task queue in the IO service. Caller will actually
277  // run these tasks by executing IOService::run.
278  io_service.post(io_fetch);
279 
280  // Update sent statistics.
281  incrStats("update-sent");
282  if (tsig_key) {
283  incrStats("update-signed", false);
284  } else {
285  incrStats("update-unsigned", false);
286  }
287 }
288 
289 void
290 DNSClientImpl::incrStats(const std::string& stat, bool update_key) {
291  StatsMgr& mgr = StatsMgr::instance();
292  mgr.addValue(stat, static_cast<int64_t>(1));
293  if (update_key && !tsig_key_name_.empty()) {
295  static_cast<int64_t>(1));
296  }
297 }
298 
300  Callback* callback, const DNSClient::Protocol proto)
301  : impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
302 }
303 
305 }
306 
307 unsigned int
309  static const unsigned int max_timeout = std::numeric_limits<int>::max();
310  return (max_timeout);
311 }
312 
313 void
315  const IOAddress& ns_addr,
316  const uint16_t ns_port,
317  D2UpdateMessage& update,
318  const unsigned int wait,
319  const D2TsigKeyPtr& tsig_key) {
320  impl_->doUpdate(io_service, ns_addr, ns_port, update, wait, tsig_key);
321 }
322 
323 } // namespace d2
324 } // namespace isc
void setBuffer(isc::util::OutputBuffer *buffer)
Set or reset a temporary output buffer.
Upstream Fetch Processing.
Definition: io_fetch.h:45
The D2UpdateMessage encapsulates a DNS Update message.
void incrStats(const std::string &stat, bool update_key=true)
This function updates statistics.
Definition: dns_client.cc:290
Control code, fetch has been stopped.
Definition: io_fetch.h:73
DNSClientImpl(D2UpdateMessagePtr &response_placeholder, DNSClient::Callback *callback, const DNSClient::Protocol proto)
Constructor.
Definition: dns_client.cc:124
~DNSClient()
Virtual destructor, does nothing.
Definition: dns_client.cc:304
A generic exception that is thrown when a function is not implemented.
std::string tsig_key_name_
TSIG key name for stats.
Definition: dns_client.cc:67
boost::shared_ptr< D2UpdateMessage > D2UpdateMessagePtr
Pointer to the DNS Update Message.
boost::shared_ptr< D2TsigKey > D2TsigKeyPtr
Type of pointer to a D2 TSIG key.
Definition: d2_tsig_key.h:71
void toWire(dns::AbstractMessageRenderer &renderer, dns::TSIGContext *const tsig_ctx=NULL)
Encode outgoing message into wire format.
No response, timeout.
Definition: dns_client.h:60
Failure, fetch timed out.
Definition: io_fetch.h:72
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
static StatsMgr & instance()
Statistics Manager accessor method.
util::OutputBufferPtr in_buf_
A buffer holding response from a DNS.
Definition: dns_client.cc:42
Statistics Manager class.
Callback for the DNSClient class.
Definition: dns_client.h:72
Other, unclassified error.
Definition: dns_client.h:63
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Definition: edns.h:19
isc::log::Logger d2_to_dns_logger("d2-to-dns")
Definition: d2_log.h:20
dns::TSIGContextPtr tsig_context_
TSIG context used to sign outbound and verify inbound messages.
Definition: dns_client.cc:64
DNSClient::Status getStatus(const asiodns::IOFetch::Result result)
This function maps the IO error to the DNSClient error.
Definition: dns_client.cc:207
Status
A status code of the DNSClient.
Definition: dns_client.h:58
void doUpdate(asiolink::IOService &io_service, const asiolink::IOAddress &ns_addr, const uint16_t ns_port, D2UpdateMessage &update, const unsigned int wait, const D2TsigKeyPtr &tsig_key=D2TsigKeyPtr())
Start asynchronous DNS Update with TSIG.
Definition: dns_client.cc:314
D2UpdateMessagePtr & response_
A caller-supplied object which will hold the parsed response from DNS.
Definition: dns_client.cc:54
Response received and is ok.
Definition: dns_client.h:59
DNSClient::Callback * callback_
A caller-supplied external callback which is invoked when DNS message exchange is complete or interru...
Definition: dns_client.cc:58
static unsigned int getMaxTimeout()
Returns maximal allowed timeout value accepted by DNSClient::doUpdate.
Definition: dns_client.cc:308
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
static std::string generateName(const std::string &context, Type index, const std::string &stat_name)
Generates statistic name in a given context.
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
This is a base class for exceptions thrown from the DNS library module.
Defines the logger used by the top-level component of kea-lfc.
Protocol
Transport layer protocol used by a DNS Client to communicate with a server.
Definition: dns_client.h:52
Response received but invalid.
Definition: dns_client.h:62
The MessageRenderer is a concrete derived class of AbstractMessageRenderer as a general purpose imple...
DNSClient(D2UpdateMessagePtr &response_placeholder, Callback *callback, const Protocol proto=UDP)
Constructor.
Definition: dns_client.cc:299
boost::shared_ptr< TSIGContext > TSIGContextPtr
Definition: tsig.h:435
Result
Result of Upstream Fetch.
Definition: io_fetch.h:70
const isc::log::MessageID DHCP_DDNS_INVALID_RESPONSE
Definition: d2_messages.h:44
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
boost::shared_ptr< OutputBuffer > OutputBufferPtr
Definition: buffer.h:603
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:75
void doUpdate(asiolink::IOService &io_service, const asiolink::IOAddress &ns_addr, const uint16_t ns_port, D2UpdateMessage &update, const unsigned int wait, const D2TsigKeyPtr &tsig_key)
Starts asynchronous DNS Update using TSIG.
Definition: dns_client.cc:225
virtual ~DNSClientImpl()
Destructor.
Definition: dns_client.cc:161
Success, fetch completed.
Definition: io_fetch.h:71
DNSClient::Protocol proto_
A Transport Layer protocol used to communicate with a DNS.
Definition: dns_client.cc:61
virtual void operator()(asiodns::IOFetch::Result result)
This internal callback is called when the DNS update message exchange is complete.
Definition: dns_client.cc:165
I/O Fetch Callback.
Definition: io_fetch.h:98