Kea  1.5.0
ncr_io.cc
Go to the documentation of this file.
1 // Copyright (C) 2013-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>
10 #include <dhcp_ddns/ncr_io.h>
11 
12 #include <boost/algorithm/string/predicate.hpp>
13 
14 namespace isc {
15 namespace dhcp_ddns {
16 
17 NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
18  if (boost::iequals(protocol_str, "UDP")) {
19  return (NCR_UDP);
20  }
21 
22  if (boost::iequals(protocol_str, "TCP")) {
23  return (NCR_TCP);
24  }
25 
27  "Invalid NameChangeRequest protocol: " << protocol_str);
28 }
29 
30 std::string ncrProtocolToString(NameChangeProtocol protocol) {
31  switch (protocol) {
32  case NCR_UDP:
33  return ("UDP");
34  case NCR_TCP:
35  return ("TCP");
36  default:
37  break;
38  }
39 
40  std::ostringstream stream;
41  stream << "UNKNOWN(" << protocol << ")";
42  return (stream.str());
43 }
44 
45 
46 //************************** NameChangeListener ***************************
47 
49  recv_handler)
50  : listening_(false), io_pending_(false), recv_handler_(recv_handler) {
51 };
52 
53 
54 void
56  if (amListening()) {
57  // This amounts to a programmatic error.
58  isc_throw(NcrListenerError, "NameChangeListener is already listening");
59  }
60 
61  // Call implementation dependent open.
62  try {
63  open(io_service);
64  } catch (const isc::Exception& ex) {
65  stopListening();
66  isc_throw(NcrListenerOpenError, "Open failed: " << ex.what());
67  }
68 
69  // Set our status to listening.
70  setListening(true);
71 
72  // Start the first asynchronous receive.
73  try {
74  receiveNext();
75  } catch (const isc::Exception& ex) {
76  stopListening();
77  isc_throw(NcrListenerReceiveError, "doReceive failed: " << ex.what());
78  }
79 }
80 
81 void
83  io_pending_ = true;
84  doReceive();
85 }
86 
87 void
89  try {
90  // Call implementation dependent close.
91  close();
92  } catch (const isc::Exception &ex) {
93  // Swallow exceptions. If we have some sort of error we'll log
94  // it but we won't propagate the throw.
95  LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR)
96  .arg(ex.what());
97  }
98 
99  // Set it false, no matter what. This allows us to at least try to
100  // re-open via startListening().
101  setListening(false);
102 }
103 
104 void
106  NameChangeRequestPtr& ncr) {
107  // Call the registered application layer handler.
108  // Surround the invocation with a try-catch. The invoked handler is
109  // not supposed to throw, but in the event it does we will at least
110  // report it.
111  try {
112  io_pending_ = false;
113  recv_handler_(result, ncr);
114  } catch (const std::exception& ex) {
115  LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
116  .arg(ex.what());
117  }
118 
119  // Start the next IO layer asynchronous receive.
120  // In the event the handler above intervened and decided to stop listening
121  // we need to check that first.
122  if (amListening()) {
123  try {
124  receiveNext();
125  } catch (const isc::Exception& ex) {
126  // It is possible though unlikely, for doReceive to fail without
127  // scheduling the read. While, unlikely, it does mean the callback
128  // will not get called with a failure. A throw here would surface
129  // at the IOService::run (or run variant) invocation. So we will
130  // close the window by invoking the application handler with
131  // a failed result, and let the application layer sort it out.
132  LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_RECV_NEXT_ERROR)
133  .arg(ex.what());
134 
135  // Call the registered application layer handler.
136  // Surround the invocation with a try-catch. The invoked handler is
137  // not supposed to throw, but in the event it does we will at least
138  // report it.
139  NameChangeRequestPtr empty;
140  try {
141  io_pending_ = false;
142  recv_handler_(ERROR, empty);
143  } catch (const std::exception& ex) {
145  DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
146  .arg(ex.what());
147  }
148  }
149  }
150 }
151 
152 //************************* NameChangeSender ******************************
153 
155  size_t send_queue_max)
156  : sending_(false), send_handler_(send_handler),
157  send_queue_max_(send_queue_max), io_service_(NULL) {
158 
159  // Queue size must be big enough to hold at least 1 entry.
160  setQueueMaxSize(send_queue_max);
161 }
162 
163 void
165  if (amSending()) {
166  // This amounts to a programmatic error.
167  isc_throw(NcrSenderError, "NameChangeSender is already sending");
168  }
169 
170  // Clear send marker.
171  ncr_to_send_.reset();
172 
173  // Call implementation dependent open.
174  try {
175  // Remember io service we're given.
176  io_service_ = &io_service;
177  open(io_service);
178  } catch (const isc::Exception& ex) {
179  stopSending();
180  isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
181  }
182 
183  // Set our status to sending.
184  setSending(true);
185 
186  // If there's any queued already.. we'll start sending.
187  sendNext();
188 }
189 
190 void
192  // Set it send indicator to false, no matter what. This allows us to at
193  // least try to re-open via startSending(). Also, setting it false now,
194  // allows us to break sendNext() chain in invokeSendHandler.
195  setSending(false);
196 
197  // If there is an outstanding IO to complete, attempt to process it.
198  if (ioReady() && io_service_ != NULL) {
199  try {
200  runReadyIO();
201  } catch (const std::exception& ex) {
202  // Swallow exceptions. If we have some sort of error we'll log
203  // it but we won't propagate the throw.
205  DHCP_DDNS_NCR_FLUSH_IO_ERROR).arg(ex.what());
206  }
207  }
208 
209  try {
210  // Call implementation dependent close.
211  close();
212  } catch (const isc::Exception &ex) {
213  // Swallow exceptions. If we have some sort of error we'll log
214  // it but we won't propagate the throw.
216  DHCP_DDNS_NCR_SEND_CLOSE_ERROR).arg(ex.what());
217  }
218 
219  io_service_ = NULL;
220 }
221 
222 void
224  if (!amSending()) {
225  isc_throw(NcrSenderError, "sender is not ready to send");
226  }
227 
228  if (!ncr) {
229  isc_throw(NcrSenderError, "request to send is empty");
230  }
231 
232  if (send_queue_.size() >= send_queue_max_) {
234  "send queue has reached maximum capacity: "
235  << send_queue_max_ );
236  }
237 
238  // Put it on the queue.
239  send_queue_.push_back(ncr);
240 
241  // Call sendNext to schedule the next one to go.
242  sendNext();
243 }
244 
245 void
247  if (ncr_to_send_) {
248  // @todo Not sure if there is any risk of getting stuck here but
249  // an interval timer to defend would be good.
250  // In reality, the derivation should ensure they timeout themselves
251  return;
252  }
253 
254  // If queue isn't empty, then get one from the front. Note we leave
255  // it on the front of the queue until we successfully send it.
256  if (!send_queue_.empty()) {
257  ncr_to_send_ = send_queue_.front();
258 
259  // @todo start defense timer
260  // If a send were to hang and we timed it out, then timeout
261  // handler need to cycle thru open/close ?
262 
263  // Call implementation dependent send.
264  doSend(ncr_to_send_);
265  }
266 }
267 
268 void
270  // @todo reset defense timer
271  if (result == SUCCESS) {
272  // It shipped so pull it off the queue.
273  send_queue_.pop_front();
274  }
275 
276  // Invoke the completion handler passing in the result and a pointer
277  // the request involved.
278  // Surround the invocation with a try-catch. The invoked handler is
279  // not supposed to throw, but in the event it does we will at least
280  // report it.
281  try {
282  send_handler_(result, ncr_to_send_);
283  } catch (const std::exception& ex) {
284  LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
285  .arg(ex.what());
286  }
287 
288  // Clear the pending ncr pointer.
289  ncr_to_send_.reset();
290 
291  // Set up the next send
292  try {
293  if (amSending()) {
294  sendNext();
295  }
296  } catch (const isc::Exception& ex) {
297  // It is possible though unlikely, for sendNext to fail without
298  // scheduling the send. While, unlikely, it does mean the callback
299  // will not get called with a failure. A throw here would surface
300  // at the IOService::run (or run variant) invocation. So we will
301  // close the window by invoking the application handler with
302  // a failed result, and let the application layer sort it out.
303  LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_SEND_NEXT_ERROR)
304  .arg(ex.what());
305 
306  // Invoke the completion handler passing in failed result.
307  // Surround the invocation with a try-catch. The invoked handler is
308  // not supposed to throw, but in the event it does we will at least
309  // report it.
310  try {
311  send_handler_(ERROR, ncr_to_send_);
312  } catch (const std::exception& ex) {
314  DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR).arg(ex.what());
315  }
316  }
317 }
318 
319 void
321  if (!send_queue_.empty()) {
322  // Discards the request at the front of the queue.
323  send_queue_.pop_front();
324  }
325 }
326 
327 void
329  if (amSending()) {
330  isc_throw(NcrSenderError, "Cannot clear queue while sending");
331  }
332 
333  send_queue_.clear();
334 }
335 
336 void
337 NameChangeSender::setQueueMaxSize(const size_t new_max) {
338  if (new_max == 0) {
339  isc_throw(NcrSenderError, "NameChangeSender:"
340  " queue size must be greater than zero");
341  }
342 
343  send_queue_max_ = new_max;
344 
345 }
347 NameChangeSender::peekAt(const size_t index) const {
348  if (index >= getQueueSize()) {
350  "NameChangeSender::peekAt peek beyond end of queue attempted"
351  << " index: " << index << " queue size: " << getQueueSize());
352  }
353 
354  return (send_queue_.at(index));
355 }
356 
357 
358 void
360  if (source_sender.amSending()) {
361  isc_throw(NcrSenderError, "Cannot assume queue:"
362  " source sender is actively sending");
363  }
364 
365  if (amSending()) {
366  isc_throw(NcrSenderError, "Cannot assume queue:"
367  " target sender is actively sending");
368  }
369 
370  if (getQueueMaxSize() < source_sender.getQueueSize()) {
371  isc_throw(NcrSenderError, "Cannot assume queue:"
372  " source queue count exceeds target queue max");
373  }
374 
375  if (!send_queue_.empty()) {
376  isc_throw(NcrSenderError, "Cannot assume queue:"
377  " target queue is not empty");
378  }
379 
380  send_queue_.swap(source_sender.getSendQueue());
381 }
382 
383 int
385  isc_throw(NotImplemented, "NameChangeSender::getSelectFd is not supported");
386 }
387 
388 void
390  if (!io_service_) {
391  isc_throw(NcrSenderError, "NameChangeSender::runReadyIO"
392  " sender io service is null");
393  }
394 
395  // We shouldn't be here if IO isn't ready to execute.
396  // By running poll we're guaranteed not to hang.
399  io_service_->get_io_service().poll_one();
400 }
401 
402 
403 } // namespace isc::dhcp_ddns
404 } // namespace isc
bool amSending() const
Returns true if the sender is in send mode, false otherwise.
Definition: ncr_io.h:671
Exception thrown if an error occurs initiating an IO receive.
Definition: ncr_io.h:105
bool amListening() const
Returns true if the listener is listening, false otherwise.
Definition: ncr_io.h:305
A generic exception that is thrown when a function is not implemented.
NameChangeProtocol stringToNcrProtocol(const std::string &protocol_str)
Function which converts labels to NameChangeProtocol enum values.
Definition: ncr_io.cc:17
This file defines abstract classes for exchanging NameChangeRequests.
virtual void close()=0
Abstract method which closes the IO source.
virtual bool ioReady()=0
Returns whether or not the sender has IO ready to process.
virtual int getSelectFd()=0
Returns a file descriptor suitable for use with select.
Definition: ncr_io.cc:384
void assumeQueue(NameChangeSender &source_sender)
Move all queued requests from a given sender into the send queue.
Definition: ncr_io.cc:359
Result
Defines the outcome of an asynchronous NCR send.
Definition: ncr_io.h:467
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
void skipNext()
Removes the request at the front of the send queue.
Definition: ncr_io.cc:320
size_t getQueueSize() const
Returns the number of entries currently in the send queue.
Definition: ncr_io.h:699
boost::shared_ptr< NameChangeRequest > NameChangeRequestPtr
Defines a pointer to a NameChangeRequest.
Definition: ncr_msg.h:212
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Thrown when a NameChangeSender encounters an error.
Definition: ncr_io.h:348
Exception thrown if an NcrListenerError encounters a general error.
Definition: ncr_io.h:91
#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...
SendQueue & getSendQueue()
Returns a reference to the send queue.
Definition: ncr_io.h:739
std::string ncrProtocolToString(NameChangeProtocol protocol)
Function which converts NameChangeProtocol enums to text labels.
Definition: ncr_io.cc:30
void clearSendQueue()
Flushes all entries in the send queue.
Definition: ncr_io.cc:328
NameChangeListener(RequestReceiveHandler &recv_handler)
Constructor.
Definition: ncr_io.cc:48
isc::log::Logger dhcp_ddns_logger("libdhcp-ddns")
Defines the logger used within lib dhcp_ddns.
Definition: dhcp_ddns_log.h:18
Exception thrown if an error occurs during IO source open.
Definition: ncr_io.h:355
const NameChangeRequestPtr & peekAt(const size_t index) const
Returns the entry at a given position in the queue.
Definition: ncr_io.cc:347
NameChangeSender(RequestSendHandler &send_handler, size_t send_queue_max=MAX_QUEUE_DEFAULT)
Constructor.
Definition: ncr_io.cc:154
Abstract class for defining application layer send callbacks.
Definition: ncr_io.h:479
void startListening(isc::asiolink::IOService &io_service)
Prepares the IO for reception and initiates the first receive.
Definition: ncr_io.cc:55
size_t getQueueMaxSize() const
Returns the maximum number of entries allowed in the send queue.
Definition: ncr_io.h:684
virtual void open(isc::asiolink::IOService &io_service)=0
Abstract method which opens the IO sink for transmission.
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.
void receiveNext()
Initiates an asynchronous receive.
Definition: ncr_io.cc:82
virtual void doSend(NameChangeRequestPtr &ncr)=0
Initiates an IO layer asynchronous send.
virtual void runReadyIO()
Processes sender IO events.
Definition: ncr_io.cc:389
void stopSending()
Closes the IO sink and stops send logic.
Definition: ncr_io.cc:191
virtual void open(isc::asiolink::IOService &io_service)=0
Abstract method which opens the IO source for reception.
void startSending(isc::asiolink::IOService &io_service)
Prepares the IO for transmission.
Definition: ncr_io.cc:164
Exception thrown if an error occurs initiating an IO send.
Definition: ncr_io.h:362
void sendNext()
Dequeues and sends the next request on the send queue.
Definition: ncr_io.cc:246
NameChangeProtocol
Defines the list of socket protocols supported.
Definition: ncr_io.h:66
void setQueueMaxSize(const size_t new_max)
Sets the maximum queue size to the given value.
Definition: ncr_io.cc:337
Abstract interface for sending NameChangeRequests.
Definition: ncr_io.h:457
void invokeRecvHandler(const Result result, NameChangeRequestPtr &ncr)
Calls the NCR receive handler registered with the listener.
Definition: ncr_io.cc:105
void sendRequest(NameChangeRequestPtr &ncr)
Queues the given request to be sent.
Definition: ncr_io.cc:223
void stopListening()
Closes the IO source and stops listen logic.
Definition: ncr_io.cc:88
void invokeSendHandler(const NameChangeSender::Result result)
Calls the NCR send completion handler registered with the sender.
Definition: ncr_io.cc:269
virtual void close()=0
Abstract method which closes the IO sink.
Abstract class for defining application layer receive callbacks.
Definition: ncr_io.h:181
virtual void doReceive()=0
Initiates an IO layer asynchronous read.
Result
Defines the outcome of an asynchronous NCR receive.
Definition: ncr_io.h:169
Exception thrown if an error occurs during IO source open.
Definition: ncr_io.h:98