Kea  2.5.3
ncr_udp.cc
Go to the documentation of this file.
1 // Copyright (C) 2013-2022 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 
15 namespace ph = std::placeholders;
16 
17 namespace isc {
18 namespace dhcp_ddns {
19 
20 //*************************** UDPCallback ***********************
21 UDPCallback::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 
34 void
35 UDPCallback::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 
50 void
51 UDPCallback::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 
65 //*************************** NameChangeUDPListener ***********************
68  const uint32_t port, const NameChangeFormat format,
69  RequestReceiveHandler& ncr_recv_handler,
70  const bool reuse_address)
71  : NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
72  port_(port), format_(format), reuse_address_(reuse_address) {
73  // Instantiate the receive callback. This gets passed into each receive.
74  // Note that the callback constructor is passed an instance method
75  // pointer to our completion handler method, receiveCompletionHandler.
76  RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
77  UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
78  recv_callback_.reset(new UDPCallback(buffer, RECV_BUF_MAX, data_source,
80  this, ph::_1, ph::_2)));
81 }
82 
84  // Clean up.
85  stopListening();
86 }
87 
88 void
90  // create our endpoint and bind the low level socket to it.
91  isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
92 
93  // Create the low level socket.
94  try {
95  asio_socket_.reset(new boost::asio::ip::udp::
96  socket(io_service.get_io_service(),
97  (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
98  boost::asio::ip::udp::v6())));
99 
100  // Set the socket option to reuse addresses if it is enabled.
101  if (reuse_address_) {
102  asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
103  }
104 
105  // Bind the low level socket to our endpoint.
106  asio_socket_->bind(endpoint.getASIOEndpoint());
107  } catch (const boost::system::system_error& ex) {
108  asio_socket_.reset();
109  isc_throw (NcrUDPError, ex.code().message());
110  }
111 
112  // Create the asiolink socket from the low level socket.
113  socket_.reset(new NameChangeUDPSocket(*asio_socket_));
114 }
115 
116 
117 void
119  // Call the socket's asynchronous receiving, passing ourself in as callback.
120  RawBufferPtr recv_buffer = recv_callback_->getBuffer();
121  socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
122  0, recv_callback_->getDataSource().get(),
123  *recv_callback_);
124 }
125 
126 void
128  // Whether we think we are listening or not, make sure we aren't.
129  // Since we are managing our own socket, we need to close it ourselves.
130  // NOTE that if there is a pending receive, it will be canceled, which
131  // WILL generate an invocation of the callback with error code of
132  // "operation aborted".
133  if (asio_socket_) {
134  if (asio_socket_->is_open()) {
135  try {
136  asio_socket_->close();
137  } catch (const boost::system::system_error& ex) {
138  // It is really unlikely that this will occur.
139  // If we do reopen later it will be with a new socket
140  // instance. Repackage exception as one that is conformant
141  // with the interface.
142  isc_throw (NcrUDPError, ex.code().message());
143  }
144  }
145 
146  asio_socket_.reset();
147  }
148 
149  socket_.reset();
150 }
151 
152 void
154  const UDPCallback *callback) {
156  Result result = SUCCESS;
157 
158  if (successful) {
159  // Make an InputBuffer from our internal array
160  isc::util::InputBuffer input_buffer(callback->getData(),
161  callback->getBytesTransferred());
162 
163  try {
164  ncr = NameChangeRequest::fromFormat(format_, input_buffer);
165  isc::stats::StatsMgr::instance().addValue("ncr-received",
166  static_cast<int64_t>(1));
167  } catch (const NcrMessageError& ex) {
168  // log it and go back to listening
171  static_cast<int64_t>(1));
172 
173  // Queue up the next receive.
174  // NOTE: We must call the base class, NEVER doReceive
175  receiveNext();
176  return;
177  }
178  } else {
179  boost::system::error_code error_code = callback->getErrorCode();
180  if (error_code.value() == boost::asio::error::operation_aborted) {
181  // A shutdown cancels all outstanding reads. For this reason,
182  // it can be an expected event, so log it as a debug message.
185  result = STOPPED;
186  } else {
188  .arg(error_code.message());
190  static_cast<int64_t>(1));
191  result = ERROR;
192  }
193  }
194 
195  // Call the application's registered request receive handler.
196  invokeRecvHandler(result, ncr);
197 }
198 
199 
200 //*************************** NameChangeUDPSender ***********************
201 
204  const uint32_t port,
205  const isc::asiolink::IOAddress& server_address,
206  const uint32_t server_port, const NameChangeFormat format,
207  RequestSendHandler& ncr_send_handler,
208  const size_t send_que_max, const bool reuse_address)
209  : NameChangeSender(ncr_send_handler, send_que_max),
210  ip_address_(ip_address), port_(port), server_address_(server_address),
211  server_port_(server_port), format_(format),
212  reuse_address_(reuse_address) {
213  // Instantiate the send callback. This gets passed into each send.
214  // Note that the callback constructor is passed the an instance method
215  // pointer to our completion handler, sendCompletionHandler.
216  RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
217  UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
218  send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
220  this, ph::_1, ph::_2)));
221 }
222 
224  // Clean up.
225  stopSending();
226 }
227 
228 void
230  // create our endpoint and bind the low level socket to it.
231  isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
232 
233  // Create the low level socket.
234  try {
235  asio_socket_.reset(new boost::asio::ip::udp::
236  socket(io_service.get_io_service(),
237  (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
238  boost::asio::ip::udp::v6())));
239 
240  // Set the socket option to reuse addresses if it is enabled.
241  if (reuse_address_) {
242  asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
243  }
244 
245  // Bind the low level socket to our endpoint.
246  asio_socket_->bind(endpoint.getASIOEndpoint());
247  } catch (const boost::system::system_error& ex) {
248  isc_throw (NcrUDPError, ex.code().message());
249  }
250 
251  // Create the asiolink socket from the low level socket.
252  socket_.reset(new NameChangeUDPSocket(*asio_socket_));
253 
254  // Create the server endpoint
255  server_endpoint_.reset(new isc::asiolink::
256  UDPEndpoint(server_address_, server_port_));
257 
258  send_callback_->setDataSource(server_endpoint_);
259 
260  closeWatchSocket();
261  watch_socket_.reset(new util::WatchSocket());
262 }
263 
264 void
266  // Whether we think we are sending or not, make sure we aren't.
267  // Since we are managing our own socket, we need to close it ourselves.
268  // NOTE that if there is a pending send, it will be canceled, which
269  // WILL generate an invocation of the callback with error code of
270  // "operation aborted".
271  if (asio_socket_) {
272  if (asio_socket_->is_open()) {
273  try {
274  asio_socket_->close();
275  } catch (const boost::system::system_error& ex) {
276  // It is really unlikely that this will occur.
277  // If we do reopen later it will be with a new socket
278  // instance. Repackage exception as one that is conformant
279  // with the interface.
280  isc_throw (NcrUDPError, ex.code().message());
281  }
282  }
283 
284  asio_socket_.reset();
285  }
286 
287  socket_.reset();
288 
289  closeWatchSocket();
290  watch_socket_.reset();
291 }
292 
293 void
295  // Now use the NCR to write JSON to an output buffer.
297  ncr->toFormat(format_, ncr_buffer);
298 
299  // Copy the wire-ized request to callback. This way we know after
300  // send completes what we sent (or attempted to send).
301  send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
302  ncr_buffer.getLength());
303 
304  // Call the socket's asynchronous send, passing our callback
305  socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
306  send_callback_->getDataSource().get(), *send_callback_);
307 
308  // Set IO ready marker so sender activity is visible to select() or poll().
309  // Note, if this call throws it will manifest itself as a throw from
310  // from sendRequest() which the application calls directly and is documented
311  // as throwing exceptions; or caught inside invokeSendHandler() which
312  // will invoke the application's send_handler with an error status.
313  watch_socket_->markReady();
314 }
315 
316 void
318  const UDPCallback *send_callback) {
319  // Clear the IO ready marker.
320  try {
321  watch_socket_->clearReady();
322  } catch (const std::exception& ex) {
323  // This can only happen if the WatchSocket's select_fd has been
324  // compromised which is a programmatic error. We'll log the error
325  // here, then continue on and process the IO result we were given.
326  // WatchSocket issue will resurface on the next send as a closed
327  // fd in markReady(). This allows application's handler to deal
328  // with watch errors more uniformly.
330  .arg(ex.what());
331  }
332 
333  Result result;
334  if (successful) {
335  result = SUCCESS;
336  } else {
337  // On a failure, log the error and set the result to ERROR.
338  boost::system::error_code error_code = send_callback->getErrorCode();
339  if (error_code.value() == boost::asio::error::operation_aborted) {
341  .arg(error_code.message());
342  result = STOPPED;
343  } else {
345  .arg(error_code.message());
346  result = ERROR;
347  }
348  }
349 
350  // Call the application's registered request send handler.
351  invokeSendHandler(result);
352 }
353 
354 int
356  if (!amSending()) {
357  isc_throw(NotImplemented, "NameChangeUDPSender::getSelectFd"
358  " not in send mode");
359  }
360 
361  return(watch_socket_->getSelectFd());
362 }
363 
364 bool
366  if (watch_socket_) {
367  return (watch_socket_->isReady());
368  }
369 
370  return (false);
371 }
372 
373 void
374 NameChangeUDPSender::closeWatchSocket() {
375  if (watch_socket_) {
376  std::string error_string;
377  watch_socket_->closeSocket(error_string);
378  if (!error_string.empty()) {
380  .arg(error_string);
381  }
382  }
383 }
384 
385 } // end of isc::dhcp_ddns namespace
386 } // 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 class for defining application layer receive callbacks.
Definition: ncr_io.h:183
Abstract interface for receiving NameChangeRequests.
Definition: ncr_io.h:167
void stopListening()
Closes the IO source and stops listen logic.
Definition: ncr_io.cc:94
void invokeRecvHandler(const Result result, NameChangeRequestPtr &ncr)
Calls the NCR receive handler registered with the listener.
Definition: ncr_io.cc:111
Result
Defines the outcome of an asynchronous NCR receive.
Definition: ncr_io.h:171
void receiveNext()
Initiates an asynchronous receive.
Definition: ncr_io.cc:88
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 class for defining application layer send callbacks.
Definition: ncr_io.h:488
Abstract interface for sending NameChangeRequests.
Definition: ncr_io.h:466
void stopSending()
Closes the IO sink and stops send logic.
Definition: ncr_io.cc:207
bool amSending() const
Returns true if the sender is in send mode, false otherwise.
Definition: ncr_io.h:733
void invokeSendHandler(const NameChangeSender::Result result)
Calls the NCR send completion handler registered with the sender.
Definition: ncr_io.cc:295
Result
Defines the outcome of an asynchronous NCR send.
Definition: ncr_io.h:476
virtual void close()
Closes the UDPSocket.
Definition: ncr_udp.cc:127
virtual ~NameChangeUDPListener()
Destructor.
Definition: ncr_udp.cc:83
static const size_t RECV_BUF_MAX
Defines the maximum size packet that can be received.
Definition: ncr_udp.h:322
virtual void open(isc::asiolink::IOService &io_service)
Opens a UDP socket using the given IOService.
Definition: ncr_udp.cc:89
void receiveCompletionHandler(const bool successful, const UDPCallback *recv_callback)
Implements the NameChangeRequest level receive completion handler.
Definition: ncr_udp.cc:153
void doReceive()
Initiates an asynchronous read on the socket.
Definition: ncr_udp.cc:118
NameChangeUDPListener(const isc::asiolink::IOAddress &ip_address, const uint32_t port, const NameChangeFormat format, RequestReceiveHandler &ncr_recv_handler, const bool reuse_address=false)
Constructor.
Definition: ncr_udp.cc:67
void sendCompletionHandler(const bool successful, const UDPCallback *send_callback)
Implements the NameChangeRequest level send completion handler.
Definition: ncr_udp.cc:317
virtual void open(isc::asiolink::IOService &io_service)
Opens a UDP socket using the given IOService.
Definition: ncr_udp.cc:229
virtual bool ioReady()
Returns whether or not the sender has IO ready to process.
Definition: ncr_udp.cc:365
static const size_t SEND_BUF_MAX
Defines the maximum size packet that can be sent.
Definition: ncr_udp.h:445
virtual int getSelectFd()
Returns a file descriptor suitable for use with select.
Definition: ncr_udp.cc:355
virtual void close()
Closes the UDPSocket.
Definition: ncr_udp.cc:265
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, RequestSendHandler &ncr_send_handler, const size_t send_que_max=NameChangeSender::MAX_QUEUE_DEFAULT, const bool reuse_address=false)
Constructor.
Definition: ncr_udp.cc:203
virtual void doSend(NameChangeRequestPtr &ncr)
Sends a given request asynchronously over the socket.
Definition: ncr_udp.cc:294
virtual ~NameChangeUDPSender()
Destructor.
Definition: ncr_udp.cc:223
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:147
size_t getBytesTransferred() const
Returns the number of bytes transferred by the completed IO service.
Definition: ncr_udp.h:231
void setErrorCode(const boost::system::error_code value)
Sets the completed IO layer service outcome status.
Definition: ncr_udp.h:250
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
const uint8_t * getData() const
Returns a pointer the data transfer buffer content.
Definition: ncr_udp.h:265
void setBytesTransferred(const size_t value)
Sets the number of bytes transferred.
Definition: ncr_udp.h:238
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:243
static StatsMgr & instance()
Statistics Manager accessor method.
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition: buffer.h:81
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
const void * getData() const
Return a pointer to the head of the data stored in the buffer.
Definition: buffer.h:401
size_t getLength() const
Return the length of data written in the buffer.
Definition: buffer.h:403
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:60
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:128
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:311
boost::shared_ptr< NameChangeRequest > NameChangeRequestPtr
Defines a pointer to a NameChangeRequest.
Definition: ncr_msg.h:240
const isc::log::MessageID DHCP_DDNS_INVALID_NCR
boost::shared_array< uint8_t > RawBufferPtr
Defines a dynamically allocated shared array.
Definition: ncr_udp.h:134
boost::shared_ptr< asiolink::UDPEndpoint > UDPEndpointPtr
Definition: ncr_udp.h:136
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
std::string format(const std::string &format, const std::vector< std::string > &args)
Apply Formatting.
Definition: strutil.cc:157
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:157