Kea 3.3.3
ping_channel.cc
Go to the documentation of this file.
1// Copyright (C) 2023-2026 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
9#include <dhcp/iface_mgr.h>
13
14#include <iostream>
15
16#include <ping_check_log.h>
17
18using namespace isc;
19using namespace isc::asiolink;
20using namespace isc::dhcp;
21using namespace isc::util;
22
23namespace ph = std::placeholders;
24
25namespace isc {
26namespace ping_check {
27
28uint32_t
30 static uint32_t echo_instance_num = 0x00010000;
31 if (echo_instance_num == UINT32_MAX) {
32 echo_instance_num = 0x00010001;
33 } else {
34 ++echo_instance_num;
35 }
36
37 return (echo_instance_num);
38}
39
41 NextToSendCallback next_to_send_cb,
42 UpdateToSendCallback update_to_send_cb,
43 EchoSentCallback echo_sent_cb,
44 ReplyReceivedCallback reply_received_cb,
45 ShutdownCallback shutdown_cb)
46 : io_service_(io_service),
47 next_to_send_cb_(next_to_send_cb),
48 update_to_send_cb_(update_to_send_cb),
49 echo_sent_cb_(echo_sent_cb),
50 reply_received_cb_(reply_received_cb),
51 shutdown_cb_(shutdown_cb),
52 socket_(0), input_buf_(256),
53 reading_(false), sending_(false), stopping_(false), mutex_(new std::mutex),
54 send_mutex_(new std::mutex), single_threaded_(!MultiThreadingMgr::instance().getMode()),
56 if (!io_service_) {
58 "PingChannel ctor - io_service cannot be empty");
59 }
60}
61
65
66void
68 try {
70 if (socket_ && socket_->isOpen()) {
71 return;
72 }
73
74 // For open(), the endpoint is only used to determine protocol,
75 // the address is irrelevant.
77 SocketCallback socket_cb(
78 [](boost::system::error_code ec, size_t /*length */) {
79 isc_throw(Unexpected, "ICMPSocket open is synchronous, should not invoke cb: "
80 << ec.message());
81 }
82 );
83
84 socket_.reset(new PingSocket(io_service_));
85 socket_->open(&ping_to_endpoint, socket_cb);
86 reading_ = false;
87 sending_ = false;
88 stopping_ = false;
89
90 if (single_threaded_) {
91 // Open new watch socket.
93
94 // Register the WatchSocket with IfaceMgr to signal data ready to write.
95 registered_write_fd_ = watch_socket_->getSelectFd();
97
98 // Register ICMPSocket with IfaceMgr to signal data ready to read.
99 registered_read_fd_ = socket_->getNative();
101 }
102
103 } catch (const std::exception& ex) {
104 isc_throw(Unexpected, "PingChannel::open failed:" << ex.what());
105 }
106
107 // Start reader now so unexpected ICMP traffic won't go unhandled and pile up.
108 startRead();
109
111}
112
113bool
116 return (socket_ && socket_->isOpen());
117}
118
119void
121 try {
123
124 if (single_threaded_) {
125 // Unregister from IfaceMgr.
126 if (registered_write_fd_ != -1) {
129 }
130
131 if (registered_read_fd_ != -1) {
134 }
135
136 // Close watch socket.
137 if (watch_socket_) {
138 std::string error_string;
139 watch_socket_->closeSocket(error_string);
140 if (!error_string.empty()) {
142 .arg(error_string);
143 }
144
145 watch_socket_.reset();
146 }
147 }
148
149 if (!socket_ || !socket_->isOpen()) {
150 return;
151 }
152
153 socket_->close();
154 } catch (const std::exception& ex) {
155 // On close error, log but do not throw.
157 .arg(ex.what());
158 }
159
161}
162
163void
165 {
167 if (stopping_) {
168 return;
169 }
170
171 stopping_ = true;
172 }
173
175 close();
176
177 if (shutdown_cb_) {
178 (shutdown_cb_)();
179 }
180}
181
182void
183PingChannel::asyncReceive(void* data, size_t length, size_t offset,
184 asiolink::IOEndpoint* endpoint, SocketCallback& callback) {
185 socket_->asyncReceive(data, length, offset, endpoint, callback);
186}
187
188void
189PingChannel::asyncSend(void* data, size_t length, asiolink::IOEndpoint* endpoint,
190 SocketCallback& callback) {
191 socket_->asyncSend(data, length, endpoint, callback);
192
193 if (single_threaded_) {
194 // Set IO ready marker so sender activity is visible to select() or poll().
195 watch_socket_->markReady();
196 }
197}
198
199void
201 try {
203 if (!canRead()) {
204 return;
205 }
206
207 reading_ = true;
208
209 // Create instance of the callback. It is safe to pass the
210 // local instance of the callback, because the underlying
211 // std functions make copies as needed.
213 shared_from_this(),
214 ph::_1, // error
215 ph::_2)); // bytes_transferred
216 asyncReceive(static_cast<void*>(getInputBufData()), getInputBufSize(),
217 0, &reply_endpoint_, cb);
218 } catch (const std::exception& ex) {
219 // Normal IO failures should be passed to the callback. A failure here
220 // indicates the call to asyncReceive() itself failed.
222 .arg(ex.what());
223 stopChannel();
224 }
225}
226
227void
228PingChannel::socketReadCallback(boost::system::error_code ec, size_t length) {
229 {
231 if (stopping_) {
232 return;
233 }
234 }
235
236 if (ec) {
237 if (ec.value() == boost::asio::error::operation_aborted) {
238 // IO service has been stopped and the connection is probably
239 // going to be shutting down.
240 return;
241 } else if ((ec.value() == boost::asio::error::try_again) ||
242 (ec.value() == boost::asio::error::would_block)) {
243 // We got EWOULDBLOCK or EAGAIN which indicates that we may be able to
244 // read something from the socket on the next attempt. Just make sure
245 // we don't try to read anything now in case there is any garbage
246 // passed in length.
247 length = 0;
248 } else {
249 // Anything else is fatal for the socket.
251 .arg(ec.message());
252 stopChannel();
253 return;
254 }
255 }
256
257 // Unpack the reply and pass it to the reply callback.
258 ICMPMsgPtr reply;
259 if (length > 0) {
260 {
261 try {
264 if (reply->getType() == ICMPMsg::ECHO_REPLY) {
267 .arg(reply->getSource())
268 .arg(reply->getId())
269 .arg(reply->getSequence());
270 }
271 } catch (const std::exception& ex) {
274 .arg(ex.what());
275 }
276 }
277 }
278
279 {
281 reading_ = false;
282 }
283
284 if (reply) {
285 (reply_received_cb_)(reply);
286 }
287
288 // Start the next read.
289 doRead();
290}
291
292void
295 if (canSend()) {
296 // Post the call to sendNext to the IOService.
297 // This ensures its carried out on a thread
298 // associated with the channel's IOService
299 // not the thread invoking this function.
300 auto f = [](PingChannelPtr ptr) { ptr->sendNext(); };
301 io_service_->post(std::bind(f, shared_from_this()));
302 }
303}
304
305void
308 if (canRead()) {
309 // Post the call to doRead to the IOService.
310 // This ensures its carried out on a thread
311 // associated with the channel's IOService
312 // not the thread invoking this function.
313 auto f = [](PingChannelPtr ptr) { ptr->doRead(); };
314 io_service_->post(std::bind(f, shared_from_this()));
315 }
316}
317
318void
320 try {
321 // Mutex used to do atomic read of the store entry using
322 // @ref PingContextStore::getNextToSend and update the context from
323 // WAITING_TO_SEND state to SENDING state using
324 // @ref PingContext::setState. Both functions (when transitioning from
325 // WAITING_TO_SEND state to SENDING state) should be called only
326 // with this mutex locked.
328
329 // Fetch the next one to send (outside the mutex) to avoid a possible
330 // deadlock with the mutex in the @ref PingCheckMgr::nextToSend callback.
331 PingContextPtr context = ((next_to_send_cb_)());
332 if (!context) {
333 // Nothing to send.
334 return;
335 }
336
338 if (!canSend()) {
339 // Can't send right now, get out.
340 return;
341 }
342
343 // Update context to SENDING (inside the mutex).
344 if (update_to_send_cb_) {
345 (update_to_send_cb_)(context);
346 }
347
348 // Have an target IP, build an ECHO REQUEST for it.
349 sending_ = true;
350 ICMPMsgPtr next_echo(new ICMPMsg());
351 next_echo->setType(ICMPMsg::ECHO_REQUEST);
352 next_echo->setDestination(context->getTarget());
353
354 uint32_t instance_num = nextEchoInstanceNum();
355 next_echo->setId(static_cast<uint16_t>(instance_num >> 16));
356 next_echo->setSequence(static_cast<uint16_t>(instance_num & 0x0000FFFF));
357
358 // Get packed wire-form.
359 ICMPPtr echo_icmp = next_echo->pack();
360
361 // Create instance of the callback. It is safe to pass the
362 // local instance of the callback, because the underlying
363 // std functions make copies as needed.
365 shared_from_this(),
366 next_echo,
367 ph::_1, // error
368 ph::_2)); // bytes_transferred
369
370 ICMPEndpoint target_endpoint(context->getTarget());
371 asyncSend(echo_icmp.get(), sizeof(struct icmp), &target_endpoint, cb);
372 } catch (const std::exception& ex) {
373 // Normal IO failures should be passed to the callback. A failure here
374 // indicates the call to asyncSend() itself failed.
376 .arg(ex.what());
377 stopChannel();
378 return;
379 }
380}
381
382void
383PingChannel::socketWriteCallback(ICMPMsgPtr echo, boost::system::error_code ec,
384 size_t length) {
385 {
387 if (stopping_) {
388 return;
389 }
390 }
391
392 if (single_threaded_) {
393 try {
394 // Clear the IO ready marker.
395 watch_socket_->clearReady();
396 } catch (const std::exception& ex) {
397 // This can only happen if the WatchSocket's select_fd has been
398 // compromised which is a programmatic error. We'll log the error
399 // here, then continue on and process the IO result we were given.
400 // WatchSocket issue will resurface on the next send as a closed
401 // fd in markReady() rather than fail out of this callback.
403 .arg(ex.what());
404 }
405 }
406
407 // Handle an error. Note we can't use a case statement as some values
408 // on some OSes are the same (e.g. try_again and would_block) which causes
409 // duplicate case compilation errors.
410 bool send_failed = false;
411 if (ec) {
412 auto error_value = ec.value();
413 if (error_value == boost::asio::error::operation_aborted) {
414 // IO service has been stopped and the connection is probably
415 // going to be shutting down.
416 return;
417 } else if ((error_value == boost::asio::error::try_again) ||
418 (error_value == boost::asio::error::would_block)) {
419 // We got EWOULDBLOCK or EAGAIN which indicates that we may be able to
420 // write something from the socket on the next attempt. Set the length
421 // to zero so we skip the completion callback.
422 length = 0;
423 } else if ((error_value == boost::asio::error::network_unreachable) ||
424 (error_value == boost::asio::error::host_unreachable) ||
425 (error_value == boost::asio::error::network_down)) {
426 // One of these implies an interface might be down, or there's no
427 // way to ping this network. Other networks might be working OK.
428 send_failed = true;
429 } else if (error_value == boost::asio::error::no_buffer_space) {
430 // Writing faster than the kernel will write them out.
431 send_failed = true;
432 } else if (error_value == boost::asio::error::access_denied) {
433 // Means the address we tried to ping is not allowed. Most likey a broadcast
434 // address.
435 send_failed = true;
436 } else {
437 // Anything else is fatal for the socket.
439 .arg(ec.message());
440 stopChannel();
441 return;
442 }
443 }
444
445 {
447 sending_ = false;
448 }
449
450 if (send_failed) {
451 // Invoke the callback with send failed. This instructs the manager
452 // to treat the address as free to use.
454 .arg(echo->getDestination())
455 .arg(ec.message());
456 // Invoke the send completed callback.
457 (echo_sent_cb_)(echo, true);
458 } else if (length > 0) {
461 .arg(echo->getDestination())
462 .arg(echo->getId())
463 .arg(echo->getSequence());
464 // Invoke the send completed callback.
465 (echo_sent_cb_)(echo, false);
466 }
467
468 // Schedule the next send.
469 sendNext();
470}
471
472size_t
474 return (input_buf_.size());
475}
476
477unsigned char*
479 if (input_buf_.empty()) {
481 "PingChannel::getInputBufData() - cannot access empty buffer");
482 }
483
484 return (input_buf_.data());
485}
486
487} // end of namespace ping_check
488} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown if a function is called in a prohibited way.
A generic exception that is thrown when an unexpected error condition occurs.
void deleteExternalSocket(int socketfd)
Deletes external socket.
Definition iface_mgr.cc:398
std::function< void(int fd)> SocketCallback
Defines callback used when data is received over external sockets.
Definition iface_mgr.h:745
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition iface_mgr.cc:52
void addExternalSocket(int socketfd, SocketCallback callback)
Adds external socket and a callback.
Definition iface_mgr.cc:367
The ICMPEndpoint class is a concrete derived class of IOEndpoint that represents an endpoint of a ICM...
Embodies an ICMP message.
Definition icmp_msg.h:38
static ICMPMsgPtr unpack(const uint8_t *wire_data, size_t length)
Unpacks an ICMP message from the given wire_data.
Definition icmp_msg.cc:32
int registered_read_fd_
ICMPSocket fd registered with IfaceMgr.
size_t getInputBufSize() const
Returns input buffer size.
void doRead()
Initiates an asynchronous socket read.
virtual ~PingChannel()
Destructor.
PingSocketPtr socket_
Socket through which to ping.
void stopChannel()
Closes the socket channel and invokes the shutdown callback.
bool stopping_
Indicates whether or not the channel has been told to stop.
virtual void sendNext()
Initiates sending the next ECHO REQUEST.
bool sending_
Indicates whether or not the socket has a write in progress.
UpdateToSendCallback update_to_send_cb_
Callback to invoke to update selected context to SENDING state.
bool isOpen() const
Indicates whether or not the channel socket is open.
void open()
Opens the socket for communications.
static uint32_t nextEchoInstanceNum()
returns the next unique ECHO instance number.
const boost::scoped_ptr< std::mutex > send_mutex_
The mutex used to protect internal state on send events.
std::vector< uint8_t > input_buf_
Buffer to hold the contents for most recent socket read.
int registered_write_fd_
WatchSocket fd registered with IfaceMgr.
void socketWriteCallback(ICMPMsgPtr echo_sent, boost::system::error_code ec, size_t length)
Socket write completion callback.
ICMPEndpoint reply_endpoint_
Retains the endpoint from which the most recent reply was received.
EchoSentCallback echo_sent_cb_
Callback to invoke when an ECHO write has completed.
unsigned char * getInputBufData()
Returns pointer to the first byte of the input buffer.
bool canRead()
Indicates whether or not a read can be initiated.
asiolink::IOServicePtr io_service_
IOService instance the drives socket IO.
PingChannel(asiolink::IOServicePtr &io_service, NextToSendCallback next_to_send_cb, UpdateToSendCallback update_to_send_cb, EchoSentCallback echo_sent_cb, ReplyReceivedCallback reply_received_cb, ShutdownCallback shutdown_cb=ShutdownCallback())
Constructor.
void close()
Closes the channel's socket.
ReplyReceivedCallback reply_received_cb_
Callback to invoke when an ICMP reply has been received.
ShutdownCallback shutdown_cb_
Callback to invoke when the channel has shutdown.
util::WatchSocketPtr watch_socket_
Pointer to WatchSocket instance supplying the "select-fd".
bool reading_
Indicates whether or not the socket has a read in progress.
NextToSendCallback next_to_send_cb_
Callback to invoke to fetch the next context with target address to ping.
void socketReadCallback(boost::system::error_code ec, size_t length)
Socket read completion callback.
virtual void asyncSend(void *data, size_t length, asiolink::IOEndpoint *endpoint, SocketCallback &callback)
Send data on the socket asynchronously.
const boost::scoped_ptr< std::mutex > mutex_
The mutex used to protect internal state.
virtual void asyncReceive(void *data, size_t length, size_t offset, asiolink::IOEndpoint *endpoint, SocketCallback &callback)
Receive data on the socket asynchronously.
bool single_threaded_
True if channel was opened in single-threaded mode, false otherwise.
bool canSend()
Indicates whether or not a send can be initiated.
Functor associated with the socket object.
Provides an IO "ready" semaphore for use with select() or poll() WatchSocket exposes a single open fi...
#define UINT32_MAX
Definition d2_lexer.cc:365
#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
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
const int DBGLVL_TRACE_BASIC
Trace basic operations.
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
std::function< void()> ShutdownCallback
Function type for callback to invoke when the channel has shutdown.
boost::shared_ptr< ICMPMsg > ICMPMsgPtr
Shared pointer type for ICMPMsg.
Definition icmp_msg.h:29
isc::log::Logger ping_check_logger("ping-check-hooks")
std::function< void(ICMPMsgPtr &reply)> ReplyReceivedCallback
Function type for callback to invoke when an ICMP reply has been received.
std::function< void(ICMPMsgPtr &echo, bool send_failed)> EchoSentCallback
Function type for callback to invoke upon ECHO send completion.
std::function< void(PingContextPtr context)> UpdateToSendCallback
Function type for callback to update a context to SENDING state.
boost::shared_ptr< struct icmp > ICMPPtr
Shared pointer type for struct icmp.
Definition icmp_msg.h:32
boost::shared_ptr< PingContext > PingContextPtr
Defines a shared pointer to a PingContext.
boost::shared_ptr< PingChannel > PingChannelPtr
Defines a smart pointer to PingChannel.
std::function< PingContextPtr()> NextToSendCallback
Function type for callback to fetch a context with next target to ping.
ICMPSocket< SocketCallback > PingSocket
Socket type for performing ICMP socket IO.
Defines the logger used by the top-level component of kea-lfc.
const isc::log::MessageID PING_CHECK_CHANNEL_STOP
const isc::log::MessageID PING_CHECK_CHANNEL_WATCH_SOCKET_CLEAR_ERROR
const isc::log::MessageID PING_CHECK_CHANNEL_SOCKET_CLOSED
const isc::log::MessageID PING_CHECK_CHANNEL_MALFORMED_PACKET_RECEIVED
const isc::log::MessageID PING_CHECK_CHANNEL_SOCKET_READ_FAILED
const isc::log::MessageID PING_CHECK_CHANNEL_SOCKET_WRITE_FAILED
const isc::log::MessageID PING_CHECK_CHANNEL_ECHO_REPLY_RECEIVED
const isc::log::MessageID PING_CHECK_UNEXPECTED_WRITE_ERROR
const isc::log::MessageID PING_CHECK_CHANNEL_SOCKET_OPENED
const isc::log::MessageID PING_CHECK_UNEXPECTED_READ_ERROR
const isc::log::MessageID PING_CHECK_CHANNEL_WATCH_SOCKET_CLOSE_ERROR
const isc::log::MessageID PING_CHECK_CHANNEL_SOCKET_CLOSE_ERROR
const isc::log::MessageID PING_CHECK_CHANNEL_NETWORK_WRITE_ERROR
const isc::log::MessageID PING_CHECK_CHANNEL_ECHO_REQUEST_SENT
RAII lock object to protect the code in the same scope with a mutex.