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
14namespace isc {
15namespace dhcp_ddns {
16
17NameChangeProtocol 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
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
54void
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) {
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 {
75 } catch (const isc::Exception& ex) {
77 isc_throw(NcrListenerReceiveError, "doReceive failed: " << ex.what());
78 }
79}
80
81void
83 io_pending_ = true;
84 doReceive();
85}
86
87void
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
104void
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.
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
163void
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
190void
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
222void
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
245void
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
268void
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
319void
321 if (!send_queue_.empty()) {
322 // Discards the request at the front of the queue.
323 send_queue_.pop_front();
324 }
325}
326
327void
329 if (amSending()) {
330 isc_throw(NcrSenderError, "Cannot clear queue while sending");
331 }
332
333 send_queue_.clear();
334}
335
336void
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}
347NameChangeSender::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
358void
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
383int
385 isc_throw(NotImplemented, "NameChangeSender::getSelectFd is not supported");
386}
387
388void
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
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
This is a base class for exceptions thrown from the DNS library module.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown when a function is not implemented.
Abstract class for defining application layer receive callbacks.
Definition: ncr_io.h:181
void stopListening()
Closes the IO source and stops listen logic.
Definition: ncr_io.cc:88
virtual void close()=0
Abstract method which closes the IO source.
virtual void open(isc::asiolink::IOService &io_service)=0
Abstract method which opens the IO source for reception.
void startListening(isc::asiolink::IOService &io_service)
Prepares the IO for reception and initiates the first receive.
Definition: ncr_io.cc:55
virtual void doReceive()=0
Initiates an IO layer asynchronous read.
NameChangeListener(RequestReceiveHandler &recv_handler)
Constructor.
Definition: ncr_io.cc:48
void invokeRecvHandler(const Result result, NameChangeRequestPtr &ncr)
Calls the NCR receive handler registered with the listener.
Definition: ncr_io.cc:105
bool amListening() const
Returns true if the listener is listening, false otherwise.
Definition: ncr_io.h:305
Result
Defines the outcome of an asynchronous NCR receive.
Definition: ncr_io.h:169
void receiveNext()
Initiates an asynchronous receive.
Definition: ncr_io.cc:82
Abstract class for defining application layer send callbacks.
Definition: ncr_io.h:479
Abstract interface for sending NameChangeRequests.
Definition: ncr_io.h:457
void stopSending()
Closes the IO sink and stops send logic.
Definition: ncr_io.cc:191
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
size_t getQueueMaxSize() const
Returns the maximum number of entries allowed in the send queue.
Definition: ncr_io.h:684
size_t getQueueSize() const
Returns the number of entries currently in the send queue.
Definition: ncr_io.h:699
const NameChangeRequestPtr & peekAt(const size_t index) const
Returns the entry at a given position in the queue.
Definition: ncr_io.cc:347
virtual bool ioReady()=0
Returns whether or not the sender has IO ready to process.
virtual void open(isc::asiolink::IOService &io_service)=0
Abstract method which opens the IO sink for transmission.
void skipNext()
Removes the request at the front of the send queue.
Definition: ncr_io.cc:320
void clearSendQueue()
Flushes all entries in the send queue.
Definition: ncr_io.cc:328
bool amSending() const
Returns true if the sender is in send mode, false otherwise.
Definition: ncr_io.h:671
NameChangeSender(RequestSendHandler &send_handler, size_t send_queue_max=MAX_QUEUE_DEFAULT)
Constructor.
Definition: ncr_io.cc:154
virtual void doSend(NameChangeRequestPtr &ncr)=0
Initiates an IO layer asynchronous send.
void setQueueMaxSize(const size_t new_max)
Sets the maximum queue size to the given value.
Definition: ncr_io.cc:337
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.
void sendRequest(NameChangeRequestPtr &ncr)
Queues the given request to be sent.
Definition: ncr_io.cc:223
virtual void runReadyIO()
Processes sender IO events.
Definition: ncr_io.cc:389
SendQueue & getSendQueue()
Returns a reference to the send queue.
Definition: ncr_io.h:739
Result
Defines the outcome of an asynchronous NCR send.
Definition: ncr_io.h:467
void sendNext()
Dequeues and sends the next request on the send queue.
Definition: ncr_io.cc:246
void startSending(isc::asiolink::IOService &io_service)
Prepares the IO for transmission.
Definition: ncr_io.cc:164
Exception thrown if an NcrListenerError encounters a general error.
Definition: ncr_io.h:91
Exception thrown if an error occurs during IO source open.
Definition: ncr_io.h:98
Exception thrown if an error occurs initiating an IO receive.
Definition: ncr_io.h:105
Thrown when a NameChangeSender encounters an error.
Definition: ncr_io.h:348
Exception thrown if an error occurs during IO source open.
Definition: ncr_io.h:355
Exception thrown if an error occurs initiating an IO send.
Definition: ncr_io.h:362
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
isc::log::Logger dhcp_ddns_logger("libdhcp-ddns")
Defines the logger used within lib dhcp_ddns.
Definition: dhcp_ddns_log.h:18
NameChangeProtocol stringToNcrProtocol(const std::string &protocol_str)
Function which converts labels to NameChangeProtocol enum values.
Definition: ncr_io.cc:17
NameChangeProtocol
Defines the list of socket protocols supported.
Definition: ncr_io.h:66
std::string ncrProtocolToString(NameChangeProtocol protocol)
Function which converts NameChangeProtocol enums to text labels.
Definition: ncr_io.cc:30
boost::shared_ptr< NameChangeRequest > NameChangeRequestPtr
Defines a pointer to a NameChangeRequest.
Definition: ncr_msg.h:214
Defines the logger used by the top-level component of kea-dhcp-ddns.
This file defines abstract classes for exchanging NameChangeRequests.