Kea  1.9.9-git
dns_client.cc
Go to the documentation of this file.
1 // Copyright (C) 2013-2015,2017 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 #include <d2/dns_client.h>
9 #include <d2/d2_log.h>
10 #include <dns/messagerenderer.h>
11 #include <limits>
12 
13 namespace isc {
14 namespace d2 {
15 
16 namespace {
17 
18 // OutputBuffer objects are pre-allocated before data is written to them.
19 // This is a default number of bytes for the buffers we create within
20 // DNSClient class.
21 const size_t DEFAULT_BUFFER_SIZE = 128;
22 
23 }
24 
25 using namespace isc::util;
26 using namespace isc::asiolink;
27 using namespace isc::asiodns;
28 using namespace isc::dns;
29 
30 // This class provides the implementation for the DNSClient. This allows for
31 // the separation of the DNSClient interface from the implementation details.
32 // Currently, implementation uses IOFetch object to handle asynchronous
33 // communication with the DNS. This design may be revisited in the future. If
34 // implementation is changed, the DNSClient API will remain unchanged thanks
35 // to this separation.
37 public:
38  // A buffer holding response from a DNS.
40  // A caller-supplied object which will hold the parsed response from DNS.
41  // The response object is (or descends from) isc::dns::Message and is
42  // populated using Message::fromWire(). This method may only be called
43  // once in the lifetime of a Message instance. Therefore, response_ is a
44  // pointer reference thus allowing this class to replace the object
45  // pointed to with a new Message instance each time a message is
46  // received. This allows a single DNSClientImpl instance to be used for
47  // multiple, sequential IOFetch calls. (@todo Trac# 3286 has been opened
48  // against dns::Message::fromWire. Should the behavior of fromWire change
49  // the behavior here with could be reexamined).
51  // A caller-supplied external callback which is invoked when DNS message
52  // exchange is complete or interrupted.
54  // A Transport Layer protocol used to communicate with a DNS.
56  // TSIG context used to sign outbound and verify inbound messages.
58 
59  // Constructor and Destructor
60  DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
61  DNSClient::Callback* callback,
62  const DNSClient::Protocol proto);
63  virtual ~DNSClientImpl();
64 
65  // This internal callback is called when the DNS update message exchange is
66  // complete. It further invokes the external callback provided by a caller.
67  // Before external callback is invoked, an object of the D2UpdateMessage
68  // type, representing a response from the server is set.
69  virtual void operator()(asiodns::IOFetch::Result result);
70 
71  // Starts asynchronous DNS Update using TSIG.
72  void doUpdate(asiolink::IOService& io_service,
73  const asiolink::IOAddress& ns_addr,
74  const uint16_t ns_port,
75  D2UpdateMessage& update,
76  const unsigned int wait,
77  const dns::TSIGKeyPtr& tsig_key);
78 
79  // This function maps the IO error to the DNSClient error.
81 };
82 
84  DNSClient::Callback* callback,
85  const DNSClient::Protocol proto)
86  : in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
87  response_(response_placeholder), callback_(callback), proto_(proto) {
88 
89  // Response should be an empty pointer. It gets populated by the
90  // operator() method.
91  if (response_) {
92  isc_throw(isc::BadValue, "Response buffer pointer should be null");
93  }
94 
95  // @todo Currently we only support UDP. The support for TCP is planned for
96  // the future release.
97  if (proto_ == DNSClient::TCP) {
98  isc_throw(isc::NotImplemented, "TCP is currently not supported as a"
99  << " Transport protocol for DNS Updates; please use UDP");
100  }
101 
102  // Given that we already eliminated the possibility that TCP is used, it
103  // would be sufficient to check that (proto != DNSClient::UDP). But, once
104  // support TCP is added the check above will disappear and the extra check
105  // will be needed here anyway.
106  // Note that cascaded check is used here instead of:
107  // if (proto_ != DNSClient::TCP && proto_ != DNSClient::UDP)..
108  // because some versions of GCC compiler complain that check above would
109  // be always 'false' due to limited range of enumeration. In fact, it is
110  // possible to pass out of range integral value through enum and it should
111  // be caught here.
112  if (proto_ != DNSClient::TCP) {
113  if (proto_ != DNSClient::UDP) {
114  isc_throw(isc::NotImplemented, "invalid transport protocol type '"
115  << proto_ << "' specified for DNS Updates");
116  }
117  }
118 }
119 
121 }
122 
123 void
125  // Get the status from IO. If no success, we just call user's callback
126  // and pass the status code.
127  DNSClient::Status status = getStatus(result);
128  if (status == DNSClient::SUCCESS) {
129  // Allocate a new response message. (Note that Message::fromWire
130  // may only be run once per message, so we need to start fresh
131  // each time.)
133 
134  // Server's response may be corrupted. In such case, fromWire will
135  // throw an exception. We want to catch this exception to return
136  // appropriate status code to the caller and log this event.
137  try {
138  response_->fromWire(in_buf_->getData(), in_buf_->getLength(),
139  tsig_context_.get());
140  } catch (const isc::Exception& ex) {
143  DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
144 
145  }
146 
147  if (tsig_context_) {
148  // Context is a one-shot deal, get rid of it.
149  tsig_context_.reset();
150  }
151  }
152 
153  // Once we are done with internal business, let's call a callback supplied
154  // by a caller.
155  if (callback_ != NULL) {
156  (*callback_)(status);
157  }
158 }
159 
162  switch (result) {
163  case IOFetch::SUCCESS:
164  return (DNSClient::SUCCESS);
165 
166  case IOFetch::TIME_OUT:
167  return (DNSClient::TIMEOUT);
168 
169  case IOFetch::STOPPED:
170  return (DNSClient::IO_STOPPED);
171 
172  default:
173  ;
174  }
175  return (DNSClient::OTHER);
176 }
177 void
179  const IOAddress& ns_addr,
180  const uint16_t ns_port,
181  D2UpdateMessage& update,
182  const unsigned int wait,
183  const dns::TSIGKeyPtr& tsig_key) {
184  // The underlying implementation which we use to send DNS Updates uses
185  // signed integers for timeout. If we want to avoid overflows we need to
186  // respect this limitation here.
187  if (wait > DNSClient::getMaxTimeout()) {
188  isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
189  " not exceed " << DNSClient::getMaxTimeout()
190  << ". Provided timeout value is '" << wait << "'");
191  }
192 
193  // Create a TSIG context if we have a key, otherwise clear the context
194  // pointer. Message marshalling uses non-null context is the indicator
195  // that TSIG should be used.
196  if (tsig_key) {
197  tsig_context_.reset(new TSIGContext(*tsig_key));
198  } else {
199  tsig_context_.reset();
200  }
201 
202  // A renderer is used by the toWire function which creates the on-wire data
203  // from the DNS Update message. A renderer has its internal buffer where it
204  // renders data by default. However, this buffer can't be directly accessed.
205  // Fortunately, the renderer's API accepts user-supplied buffers. So, let's
206  // create our own buffer and pass it to the renderer so as the message is
207  // rendered to this buffer. Finally, we pass this buffer to IOFetch.
208  dns::MessageRenderer renderer;
209  OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
210  renderer.setBuffer(msg_buf.get());
211 
212  // Render DNS Update message. This may throw a bunch of exceptions if
213  // invalid message object is given.
214  update.toWire(renderer, tsig_context_.get());
215 
216  // IOFetch has all the mechanisms that we need to perform asynchronous
217  // communication with the DNS server. The last but one argument points to
218  // this object as a completion callback for the message exchange. As a
219  // result operator()(Status) will be called.
220 
221  // Timeout value is explicitly cast to the int type to avoid warnings about
222  // overflows when doing implicit cast. It should have been checked by the
223  // caller that the unsigned timeout value will fit into int.
224  IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
225  in_buf_, this, static_cast<int>(wait));
226 
227  // Post the task to the task queue in the IO service. Caller will actually
228  // run these tasks by executing IOService::run.
229  io_service.post(io_fetch);
230 }
231 
233  Callback* callback, const DNSClient::Protocol proto)
234  : impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
235 }
236 
238  delete (impl_);
239 }
240 
241 unsigned int
243  static const unsigned int max_timeout = std::numeric_limits<int>::max();
244  return (max_timeout);
245 }
246 
247 void
249  const IOAddress& ns_addr,
250  const uint16_t ns_port,
251  D2UpdateMessage& update,
252  const unsigned int wait,
253  const dns::TSIGKeyPtr& tsig_key) {
254  impl_->doUpdate(io_service, ns_addr, ns_port, update, wait, tsig_key);
255 }
256 
257 } // namespace d2
258 } // 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.
Control code, fetch has been stopped.
Definition: io_fetch.h:73
DNSClientImpl(D2UpdateMessagePtr &response_placeholder, DNSClient::Callback *callback, const DNSClient::Protocol proto)
Definition: dns_client.cc:83
~DNSClient()
Virtual destructor, does nothing.
Definition: dns_client.cc:237
A generic exception that is thrown when a function is not implemented.
TSIG session context.
Definition: tsig.h:171
std::function< void()> callback_
The callback function.
Definition: io_service.cc:38
boost::shared_ptr< D2UpdateMessage > D2UpdateMessagePtr
Pointer to the DNS Update Message.
void toWire(dns::AbstractMessageRenderer &renderer, dns::TSIGContext *const tsig_ctx=NULL)
Encode outgoing message into wire format.
No response, timeout.
Definition: dns_client.h:62
Failure, fetch timed out.
Definition: io_fetch.h:72
util::OutputBufferPtr in_buf_
Definition: dns_client.cc:39
Callback for the DNSClient class.
Definition: dns_client.h:74
Other, unclassified error.
Definition: dns_client.h:65
#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_
Definition: dns_client.cc:57
Status
A status code of the DNSClient.
Definition: dns_client.h:60
boost::shared_ptr< TSIGKey > TSIGKeyPtr
Definition: tsig.h:436
D2UpdateMessagePtr & response_
Definition: dns_client.cc:50
Response received and is ok.
Definition: dns_client.h:61
DNSClient::Callback * callback_
Definition: dns_client.cc:53
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
static unsigned int getMaxTimeout()
Returns maximal allowed timeout value accepted by DNSClient::doUpdate.
Definition: dns_client.cc:242
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
DNSClient::Status getStatus(const asiodns::IOFetch::Result)
Definition: dns_client.cc:161
This is a base class for exceptions thrown from the DNS library module.
Defines the logger used by the top-level component of kea-dhcp-ddns.
Protocol
Transport layer protocol used by a DNS Client to communicate with a server.
Definition: dns_client.h:54
Response received but invalid.
Definition: dns_client.h:64
void doUpdate(asiolink::IOService &io_service, const asiolink::IOAddress &ns_addr, const uint16_t ns_port, D2UpdateMessage &update, const unsigned int wait, const dns::TSIGKeyPtr &tsig_key=dns::TSIGKeyPtr())
Start asynchronous DNS Update with TSIG.
Definition: dns_client.cc:248
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:232
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:42
#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:599
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:71
void doUpdate(asiolink::IOService &io_service, const asiolink::IOAddress &ns_addr, const uint16_t ns_port, D2UpdateMessage &update, const unsigned int wait, const dns::TSIGKeyPtr &tsig_key)
Definition: dns_client.cc:178
Success, fetch completed.
Definition: io_fetch.h:71
DNSClient::Protocol proto_
Definition: dns_client.cc:55
virtual void operator()(asiodns::IOFetch::Result result)
Callback method.
Definition: dns_client.cc:124
I/O Fetch Callback.
Definition: io_fetch.h:98