Kea 2.5.8
ncr_udp.cc
Go to the documentation of this file.
1// Copyright (C) 2013-2024 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
10#include <dhcp_ddns/ncr_udp.h>
11#include <stats/stats_mgr.h>
12
13#include <functional>
14
15namespace ph = std::placeholders;
16
17namespace isc {
18namespace dhcp_ddns {
19
20//*************************** UDPCallback ***********************
21UDPCallback::UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
22 UDPEndpointPtr& data_source,
23 const UDPCompletionHandler& handler)
24 : handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
25 if (!handler) {
26 isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
27 }
28
29 if (!buffer) {
30 isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
31 }
32}
33
34void
35UDPCallback::operator ()(const boost::system::error_code error_code,
36 const size_t bytes_transferred) {
37
38 // Save the result state and number of bytes transferred.
39 setErrorCode(error_code);
40 setBytesTransferred(bytes_transferred);
41
42 // Invoke the NameChangeRequest layer completion handler.
43 // First argument is a boolean indicating success or failure.
44 // The second is a pointer to "this" callback object. By passing
45 // ourself in, we make all of the service related data available
46 // to the completion handler.
47 handler_(!error_code, this);
48}
49
50void
51UDPCallback::putData(const uint8_t* src, size_t len) {
52 if (!src) {
53 isc_throw(NcrUDPError, "UDPCallback putData, data source is NULL");
54 }
55
56 if (len > data_->buf_size_) {
57 isc_throw(NcrUDPError, "UDPCallback putData, data length too large");
58 }
59
60 memcpy (data_->buffer_.get(), src, len);
61 data_->put_len_ = len;
62}
63
64//*************************** NameChangeUDPListener ***********************
67 const uint32_t port, const NameChangeFormat format,
68 RequestReceiveHandlerPtr ncr_recv_handler,
69 const bool reuse_address)
70 : NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
71 port_(port), format_(format), reuse_address_(reuse_address) {
72 // Instantiate the receive callback. This gets passed into each receive.
73 // Note that the callback constructor is passed an instance method
74 // pointer to our completion handler method, receiveCompletionHandler.
75 RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
76 UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
77 recv_callback_.reset(new UDPCallback(buffer, RECV_BUF_MAX, data_source,
79 this, ph::_1, ph::_2)));
80}
81
83 // Clean up.
85}
86
87void
89
90 // create our endpoint and bind the low level socket to it.
91 isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
92
93 io_service_ = io_service;
94
95 // Create the low level socket.
96 try {
97 asio_socket_.reset(new boost::asio::ip::udp::
98 socket(io_service_->getInternalIOService(),
99 (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
100 boost::asio::ip::udp::v6())));
101
102 // Set the socket option to reuse addresses if it is enabled.
103 if (reuse_address_) {
104 asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
105 }
106
107 // Bind the low level socket to our endpoint.
108 asio_socket_->bind(endpoint.getASIOEndpoint());
109 } catch (const boost::system::system_error& ex) {
110 asio_socket_.reset();
111 io_service_.reset();
112 isc_throw (NcrUDPError, ex.code().message());
113 }
114
115 // Create the asiolink socket from the low level socket.
116 socket_.reset(new NameChangeUDPSocket(*asio_socket_));
117}
118
119void
121 // Call the socket's asynchronous receiving, passing ourself in as callback.
122 RawBufferPtr recv_buffer = recv_callback_->getBuffer();
123 socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
124 0, recv_callback_->getDataSource().get(),
125 *recv_callback_);
126}
127
128void
130 // Whether we think we are listening or not, make sure we aren't.
131 // Since we are managing our own socket, we need to close it ourselves.
132 // NOTE that if there is a pending receive, it will be canceled, which
133 // WILL generate an invocation of the callback with error code of
134 // "operation aborted".
135 if (asio_socket_) {
136 if (asio_socket_->is_open()) {
137 try {
138 asio_socket_->close();
139 } catch (const boost::system::system_error& ex) {
140 // It is really unlikely that this will occur.
141 // If we do reopen later it will be with a new socket
142 // instance. Repackage exception as one that is conformant
143 // with the interface.
144 isc_throw (NcrUDPError, ex.code().message());
145 }
146 }
147
148 asio_socket_.reset();
149 }
150
151 if (socket_) {
152 socket_->close();
153 }
154 socket_.reset();
155 io_service_.reset();
156}
157
158void
160 const UDPCallback *callback) {
162 Result result = SUCCESS;
163
164 if (successful) {
165 // Make an InputBuffer from our internal array
166 isc::util::InputBuffer input_buffer(callback->getData(),
167 callback->getBytesTransferred());
168
169 try {
170 ncr = NameChangeRequest::fromFormat(format_, input_buffer);
172 static_cast<int64_t>(1));
173 } catch (const NcrMessageError& ex) {
174 // log it and go back to listening
177 static_cast<int64_t>(1));
178
179 // Queue up the next receive.
180 // NOTE: We must call the base class, NEVER doReceive
181 receiveNext();
182 return;
183 }
184 } else {
185 boost::system::error_code error_code = callback->getErrorCode();
186 if (error_code.value() == boost::asio::error::operation_aborted) {
187 // A shutdown cancels all outstanding reads. For this reason,
188 // it can be an expected event, so log it as a debug message.
191 result = STOPPED;
192 } else {
194 .arg(error_code.message());
196 static_cast<int64_t>(1));
197 result = ERROR;
198 }
199 }
200
201 // Call the application's registered request receive handler.
202 invokeRecvHandler(result, ncr);
203}
204
205//*************************** NameChangeUDPSender ***********************
206
209 const uint32_t port,
210 const isc::asiolink::IOAddress& server_address,
211 const uint32_t server_port, const NameChangeFormat format,
212 RequestSendHandlerPtr ncr_send_handler,
213 const size_t send_que_max, const bool reuse_address)
214 : NameChangeSender(ncr_send_handler, send_que_max),
215 ip_address_(ip_address), port_(port), server_address_(server_address),
216 server_port_(server_port), format_(format),
217 reuse_address_(reuse_address) {
218 // Instantiate the send callback. This gets passed into each send.
219 // Note that the callback constructor is passed the an instance method
220 // pointer to our completion handler, sendCompletionHandler.
221 RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
222 UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
223 send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
225 this, ph::_1, ph::_2)));
226}
227
229 // Clean up.
230 stopSending();
231}
232
233void
235 // create our endpoint and bind the low level socket to it.
236 isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
237
238 io_service_ = io_service;
239
240 // Create the low level socket.
241 try {
242 asio_socket_.reset(new boost::asio::ip::udp::
243 socket(io_service_->getInternalIOService(),
244 (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
245 boost::asio::ip::udp::v6())));
246
247 // Set the socket option to reuse addresses if it is enabled.
248 if (reuse_address_) {
249 asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
250 }
251
252 // Bind the low level socket to our endpoint.
253 asio_socket_->bind(endpoint.getASIOEndpoint());
254 } catch (const boost::system::system_error& ex) {
255 asio_socket_.reset();
256 io_service_.reset();
257 isc_throw (NcrUDPError, ex.code().message());
258 }
259
260 // Create the asiolink socket from the low level socket.
261 socket_.reset(new NameChangeUDPSocket(*asio_socket_));
262
263 // Create the server endpoint
264 server_endpoint_.reset(new isc::asiolink::
265 UDPEndpoint(server_address_, server_port_));
266
267 send_callback_->setDataSource(server_endpoint_);
268
269 closeWatchSocket();
270 watch_socket_.reset(new util::WatchSocket());
271}
272
273void
275 // Whether we think we are sending or not, make sure we aren't.
276 // Since we are managing our own socket, we need to close it ourselves.
277 // NOTE that if there is a pending send, it will be canceled, which
278 // WILL generate an invocation of the callback with error code of
279 // "operation aborted".
280 if (asio_socket_) {
281 if (asio_socket_->is_open()) {
282 try {
283 asio_socket_->close();
284 } catch (const boost::system::system_error& ex) {
285 // It is really unlikely that this will occur.
286 // If we do reopen later it will be with a new socket
287 // instance. Repackage exception as one that is conformant
288 // with the interface.
289 isc_throw (NcrUDPError, ex.code().message());
290 }
291 }
292
293 asio_socket_.reset();
294 }
295
296 if (socket_) {
297 socket_->close();
298 }
299 socket_.reset();
300
301 closeWatchSocket();
302 watch_socket_.reset();
303}
304
305void
307 // Now use the NCR to write JSON to an output buffer.
309 ncr->toFormat(format_, ncr_buffer);
310
311 // Copy the wire-ized request to callback. This way we know after
312 // send completes what we sent (or attempted to send).
313 send_callback_->putData(ncr_buffer.getData(), ncr_buffer.getLength());
314
315 // Call the socket's asynchronous send, passing our callback
316 socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
317 send_callback_->getDataSource().get(), *send_callback_);
318
319 // Set IO ready marker so sender activity is visible to select() or poll().
320 // Note, if this call throws it will manifest itself as a throw from
321 // from sendRequest() which the application calls directly and is documented
322 // as throwing exceptions; or caught inside invokeSendHandler() which
323 // will invoke the application's send_handler with an error status.
324 watch_socket_->markReady();
325}
326
327void
329 const UDPCallback *send_callback) {
330 if (!watch_socket_) {
331 return;
332 }
333 // Clear the IO ready marker.
334 try {
335 watch_socket_->clearReady();
336 } catch (const std::exception& ex) {
337 // This can only happen if the WatchSocket's select_fd has been
338 // compromised which is a programmatic error. We'll log the error
339 // here, then continue on and process the IO result we were given.
340 // WatchSocket issue will resurface on the next send as a closed
341 // fd in markReady(). This allows application's handler to deal
342 // with watch errors more uniformly.
344 .arg(ex.what());
345 }
346
347 Result result;
348 if (successful) {
349 result = SUCCESS;
350 } else {
351 // On a failure, log the error and set the result to ERROR.
352 boost::system::error_code error_code = send_callback->getErrorCode();
353 if (error_code.value() == boost::asio::error::operation_aborted) {
355 .arg(error_code.message());
356 result = STOPPED;
357 } else {
359 .arg(error_code.message());
360 result = ERROR;
361 }
362 }
363
364 // Call the application's registered request send handler.
365 invokeSendHandler(result);
366}
367
368int
370 if (!amSending()) {
371 isc_throw(NotImplemented, "NameChangeUDPSender::getSelectFd"
372 " not in send mode");
373 }
374
375 return (watch_socket_->getSelectFd());
376}
377
378bool
380 if (watch_socket_) {
381 return (watch_socket_->isReady());
382 }
383
384 return (false);
385}
386
387void
388NameChangeUDPSender::closeWatchSocket() {
389 if (watch_socket_) {
390 std::string error_string;
391 watch_socket_->closeSocket(error_string);
392 if (!error_string.empty()) {
394 .arg(error_string);
395 }
396 }
397}
398
399} // end of isc::dhcp_ddns namespace
400} // end of isc namespace
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 interface for receiving NameChangeRequests.
Definition: ncr_io.h:167
boost::shared_ptr< RequestReceiveHandler > RequestReceiveHandlerPtr
Defines a smart pointer to an instance of a request receive handler.
Definition: ncr_io.h:206
void stopListening()
Closes the IO source and stops listen logic.
Definition: ncr_io.cc:91
void invokeRecvHandler(const Result result, NameChangeRequestPtr &ncr)
Calls the NCR receive handler registered with the listener.
Definition: ncr_io.cc:108
Result
Defines the outcome of an asynchronous NCR receive.
Definition: ncr_io.h:171
void receiveNext()
Initiates an asynchronous receive.
Definition: ncr_io.cc:85
static NameChangeRequestPtr fromFormat(const NameChangeFormat format, isc::util::InputBuffer &buffer)
Static method for creating a NameChangeRequest from a buffer containing a marshalled request in a giv...
Definition: ncr_msg.cc:299
Abstract interface for sending NameChangeRequests.
Definition: ncr_io.h:468
asiolink::IOServicePtr io_service_
Pointer to the IOService currently being used by the sender.
Definition: ncr_io.h:833
void stopSending()
Closes the IO sink and stops send logic.
Definition: ncr_io.cc:204
boost::shared_ptr< RequestSendHandler > RequestSendHandlerPtr
Defines a smart pointer to an instance of a request send handler.
Definition: ncr_io.h:512
bool amSending() const
Returns true if the sender is in send mode, false otherwise.
Definition: ncr_io.h:738
void invokeSendHandler(const NameChangeSender::Result result)
Calls the NCR send completion handler registered with the sender.
Definition: ncr_io.cc:305
Result
Defines the outcome of an asynchronous NCR send.
Definition: ncr_io.h:478
NameChangeUDPListener(const isc::asiolink::IOAddress &ip_address, const uint32_t port, const NameChangeFormat format, RequestReceiveHandlerPtr ncr_recv_handler, const bool reuse_address=false)
Constructor.
Definition: ncr_udp.cc:66
virtual void close()
Closes the UDPSocket.
Definition: ncr_udp.cc:129
virtual void open(const isc::asiolink::IOServicePtr &io_service)
Opens a UDP socket using the given IOService.
Definition: ncr_udp.cc:88
virtual ~NameChangeUDPListener()
Destructor.
Definition: ncr_udp.cc:82
static const size_t RECV_BUF_MAX
Defines the maximum size packet that can be received.
Definition: ncr_udp.h:320
void receiveCompletionHandler(const bool successful, const UDPCallback *recv_callback)
Implements the NameChangeRequest level receive completion handler.
Definition: ncr_udp.cc:159
void doReceive()
Initiates an asynchronous read on the socket.
Definition: ncr_udp.cc:120
void sendCompletionHandler(const bool successful, const UDPCallback *send_callback)
Implements the NameChangeRequest level send completion handler.
Definition: ncr_udp.cc:328
virtual bool ioReady()
Returns whether or not the sender has IO ready to process.
Definition: ncr_udp.cc:379
static const size_t SEND_BUF_MAX
Defines the maximum size packet that can be sent.
Definition: ncr_udp.h:446
virtual void open(const isc::asiolink::IOServicePtr &io_service)
Opens a UDP socket using the given IOService.
Definition: ncr_udp.cc:234
virtual int getSelectFd()
Returns a file descriptor suitable for use with select.
Definition: ncr_udp.cc:369
virtual void close()
Closes the UDPSocket.
Definition: ncr_udp.cc:274
virtual void doSend(NameChangeRequestPtr &ncr)
Sends a given request asynchronously over the socket.
Definition: ncr_udp.cc:306
NameChangeUDPSender(const isc::asiolink::IOAddress &ip_address, const uint32_t port, const isc::asiolink::IOAddress &server_address, const uint32_t server_port, const NameChangeFormat format, RequestSendHandlerPtr ncr_send_handler, const size_t send_que_max=NameChangeSender::MAX_QUEUE_DEFAULT, const bool reuse_address=false)
Constructor.
Definition: ncr_udp.cc:208
virtual ~NameChangeUDPSender()
Destructor.
Definition: ncr_udp.cc:228
Exception thrown when NameChangeRequest marshalling error occurs.
Definition: ncr_msg.h:30
Thrown when a UDP level exception occurs.
Definition: ncr_udp.h:122
Implements the callback class passed into UDPSocket calls.
Definition: ncr_udp.h:146
size_t getBytesTransferred() const
Returns the number of bytes transferred by the completed IO service.
Definition: ncr_udp.h:229
void setErrorCode(const boost::system::error_code value)
Sets the completed IO layer service outcome status.
Definition: ncr_udp.h:248
const uint8_t * getData() const
Returns a pointer the data transfer buffer content.
Definition: ncr_udp.h:263
void putData(const uint8_t *src, size_t len)
Copies data into the data transfer buffer.
Definition: ncr_udp.cc:51
UDPCallback(RawBufferPtr &buffer, const size_t buf_size, UDPEndpointPtr &data_source, const UDPCompletionHandler &handler)
Used as the callback object for UDPSocket services.
Definition: ncr_udp.cc:21
void setBytesTransferred(const size_t value)
Sets the number of bytes transferred.
Definition: ncr_udp.h:236
void operator()(const boost::system::error_code error_code, const size_t bytes_transferred)
Operator that will be invoked by the asiolink layer.
Definition: ncr_udp.cc:35
boost::system::error_code getErrorCode() const
Returns the completed IO layer service outcome status.
Definition: ncr_udp.h:241
static StatsMgr & instance()
Statistics Manager accessor method.
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition: buffer.h:82
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:347
const uint8_t * getData() const
Return a pointer to the head of the data stored in the buffer.
Definition: buffer.h:398
size_t getLength() const
Return the length of data written in the buffer.
Definition: buffer.h:412
Provides an IO "ready" semaphore for use with select() or poll() WatchSocket exposes a single open fi...
Definition: watch_socket.h:47
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_ERROR
isc::log::Logger dhcp_ddns_logger("libdhcp-ddns")
Defines the logger used within lib dhcp_ddns.
Definition: dhcp_ddns_log.h:18
NameChangeFormat
Defines the list of data wire formats supported.
Definition: ncr_msg.h:59
const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_CANCELED
const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_ERROR
const isc::log::MessageID DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR
std::function< void(const bool, const UDPCallback *)> UDPCompletionHandler
Defines a function pointer for NameChangeRequest completion handlers.
Definition: ncr_udp.h:130
const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_CANCELED
isc::asiolink::UDPSocket< UDPCallback > NameChangeUDPSocket
Convenience type for UDP socket based listener.
Definition: ncr_udp.h:309
boost::shared_ptr< NameChangeRequest > NameChangeRequestPtr
Defines a pointer to a NameChangeRequest.
Definition: ncr_msg.h:241
const isc::log::MessageID DHCP_DDNS_INVALID_NCR
boost::shared_array< uint8_t > RawBufferPtr
Defines a dynamically allocated shared array.
Definition: ncr_udp.h:133
boost::shared_ptr< asiolink::UDPEndpoint > UDPEndpointPtr
Definition: ncr_udp.h:135
const isc::log::MessageID DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
Defines the logger used by the top-level component of kea-lfc.
This file provides UDP socket based implementation for sending and receiving NameChangeRequests.
Container class which stores service invocation related data.
Definition: ncr_udp.h:156