Kea 3.1.5
ping_channel.cc
Go to the documentation of this file.
1// Copyright (C) 2023-2025 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#include <ping_channel.h>
9#include <ping_check_log.h>
10#include <dhcp/iface_mgr.h>
13#include <iostream>
14
15using namespace isc;
16using namespace isc::asiolink;
17using namespace isc::dhcp;
18using namespace isc::util;
19
20namespace ph = std::placeholders;
21
22namespace isc {
23namespace ping_check {
24
25uint32_t
27 static uint32_t echo_instance_num = 0x00010000;
28 if (echo_instance_num == UINT32_MAX) {
29 echo_instance_num = 0x00010001;
30 } else {
31 ++echo_instance_num;
32 }
33
34 return (echo_instance_num);
35}
36
38 NextToSendCallback next_to_send_cb,
39 UpdateToSendCallback update_to_send_cb,
40 EchoSentCallback echo_sent_cb,
41 ReplyReceivedCallback reply_received_cb,
42 ShutdownCallback shutdown_cb)
43 : io_service_(io_service),
44 next_to_send_cb_(next_to_send_cb),
45 update_to_send_cb_(update_to_send_cb),
46 echo_sent_cb_(echo_sent_cb),
47 reply_received_cb_(reply_received_cb),
48 shutdown_cb_(shutdown_cb),
49 socket_(0), input_buf_(256),
50 reading_(false), sending_(false), stopping_(false), mutex_(new std::mutex),
51 send_mutex_(new std::mutex), single_threaded_(!MultiThreadingMgr::instance().getMode()),
53 if (!io_service_) {
55 "PingChannel ctor - io_service cannot be empty");
56 }
57}
58
62
63void
65 try {
67 if (socket_ && socket_->isOpen()) {
68 return;
69 }
70
71 // For open(), the endpoint is only used to determine protocol,
72 // the address is irrelevant.
74 SocketCallback socket_cb(
75 [](boost::system::error_code ec, size_t /*length */) {
76 isc_throw(Unexpected, "ICMPSocket open is synchronous, should not invoke cb: "
77 << ec.message());
78 }
79 );
80
81 socket_.reset(new PingSocket(io_service_));
82 socket_->open(&ping_to_endpoint, socket_cb);
83 reading_ = false;
84 sending_ = false;
85 stopping_ = false;
86
87 if (single_threaded_) {
88 // Open new watch socket.
90
91 // Register the WatchSocket with IfaceMgr to signal data ready to write.
92 registered_write_fd_ = watch_socket_->getSelectFd();
94
95 // Register ICMPSocket with IfaceMgr to signal data ready to read.
96 registered_read_fd_ = socket_->getNative();
98 }
99
100 } catch (const std::exception& ex) {
101 isc_throw(Unexpected, "PingChannel::open failed:" << ex.what());
102 }
103
104 // Start reader now so unexpected ICMP traffic won't go unhandled and pile up.
105 startRead();
106
108}
109
110bool
113 return (socket_ && socket_->isOpen());
114}
115
116void
118 try {
120
121 if (single_threaded_) {
122 // Unregister from IfaceMgr.
123 if (registered_write_fd_ != -1) {
126 }
127
128 if (registered_read_fd_ != -1) {
131 }
132
133 // Close watch socket.
134 if (watch_socket_) {
135 std::string error_string;
136 watch_socket_->closeSocket(error_string);
137 if (!error_string.empty()) {
139 .arg(error_string);
140 }
141
142 watch_socket_.reset();
143 }
144 }
145
146 if (!socket_ || !socket_->isOpen()) {
147 return;
148 }
149
150 socket_->close();
151 } catch (const std::exception& ex) {
152 // On close error, log but do not throw.
154 .arg(ex.what());
155 }
156
158}
159
160void
162 {
164 if (stopping_) {
165 return;
166 }
167
168 stopping_ = true;
169 }
170
172 close();
173
174 if (shutdown_cb_) {
175 (shutdown_cb_)();
176 }
177}
178
179void
180PingChannel::asyncReceive(void* data, size_t length, size_t offset,
181 asiolink::IOEndpoint* endpoint, SocketCallback& callback) {
182 socket_->asyncReceive(data, length, offset, endpoint, callback);
183}
184
185void
186PingChannel::asyncSend(void* data, size_t length, asiolink::IOEndpoint* endpoint,
187 SocketCallback& callback) {
188 socket_->asyncSend(data, length, endpoint, callback);
189
190 if (single_threaded_) {
191 // Set IO ready marker so sender activity is visible to select() or poll().
192 watch_socket_->markReady();
193 }
194}
195
196void
198 try {
200 if (!canRead()) {
201 return;
202 }
203
204 reading_ = true;
205
206 // Create instance of the callback. It is safe to pass the
207 // local instance of the callback, because the underlying
208 // std functions make copies as needed.
210 shared_from_this(),
211 ph::_1, // error
212 ph::_2)); // bytes_transferred
213 asyncReceive(static_cast<void*>(getInputBufData()), getInputBufSize(),
214 0, &reply_endpoint_, cb);
215 } catch (const std::exception& ex) {
216 // Normal IO failures should be passed to the callback. A failure here
217 // indicates the call to asyncReceive() itself failed.
219 .arg(ex.what());
220 stopChannel();
221 }
222}
223
224void
225PingChannel::socketReadCallback(boost::system::error_code ec, size_t length) {
226 {
228 if (stopping_) {
229 return;
230 }
231 }
232
233 if (ec) {
234 if (ec.value() == boost::asio::error::operation_aborted) {
235 // IO service has been stopped and the connection is probably
236 // going to be shutting down.
237 return;
238 } else if ((ec.value() == boost::asio::error::try_again) ||
239 (ec.value() == boost::asio::error::would_block)) {
240 // We got EWOULDBLOCK or EAGAIN which indicates that we may be able to
241 // read something from the socket on the next attempt. Just make sure
242 // we don't try to read anything now in case there is any garbage
243 // passed in length.
244 length = 0;
245 } else {
246 // Anything else is fatal for the socket.
248 .arg(ec.message());
249 stopChannel();
250 return;
251 }
252 }
253
254 // Unpack the reply and pass it to the reply callback.
255 ICMPMsgPtr reply;
256 if (length > 0) {
257 {
258 try {
261 if (reply->getType() == ICMPMsg::ECHO_REPLY) {
264 .arg(reply->getSource())
265 .arg(reply->getId())
266 .arg(reply->getSequence());
267 }
268 } catch (const std::exception& ex) {
271 .arg(ex.what());
272 }
273 }
274 }
275
276 {
278 reading_ = false;
279 }
280
281 if (reply) {
282 (reply_received_cb_)(reply);
283 }
284
285 // Start the next read.
286 doRead();
287}
288
289void
292 if (canSend()) {
293 // Post the call to sendNext to the IOService.
294 // This ensures its carried out on a thread
295 // associated with the channel's IOService
296 // not the thread invoking this function.
297 auto f = [](PingChannelPtr ptr) { ptr->sendNext(); };
298 io_service_->post(std::bind(f, shared_from_this()));
299 }
300}
301
302void
305 if (canRead()) {
306 // Post the call to doRead to the IOService.
307 // This ensures its carried out on a thread
308 // associated with the channel's IOService
309 // not the thread invoking this function.
310 auto f = [](PingChannelPtr ptr) { ptr->doRead(); };
311 io_service_->post(std::bind(f, shared_from_this()));
312 }
313}
314
315void
317 try {
318 // Mutex used to do atomic read of the store entry using
319 // @ref PingContextStore::getNextToSend and update the context from
320 // WAITING_TO_SEND state to SENDING state using
321 // @ref PingContext::setState. Both functions (when transitioning from
322 // WAITING_TO_SEND state to SENDING state) should be called only
323 // with this mutex locked.
325
326 // Fetch the next one to send (outside the mutex) to avoid a possible
327 // deadlock with the mutex in the @ref PingCheckMgr::nextToSend callback.
328 PingContextPtr context = ((next_to_send_cb_)());
329 if (!context) {
330 // Nothing to send.
331 return;
332 }
333
335 if (!canSend()) {
336 // Can't send right now, get out.
337 return;
338 }
339
340 // Update context to SENDING (inside the mutex).
341 if (update_to_send_cb_) {
342 (update_to_send_cb_)(context);
343 }
344
345 // Have an target IP, build an ECHO REQUEST for it.
346 sending_ = true;
347 ICMPMsgPtr next_echo(new ICMPMsg());
348 next_echo->setType(ICMPMsg::ECHO_REQUEST);
349 next_echo->setDestination(context->getTarget());
350
351 uint32_t instance_num = nextEchoInstanceNum();
352 next_echo->setId(static_cast<uint16_t>(instance_num >> 16));
353 next_echo->setSequence(static_cast<uint16_t>(instance_num & 0x0000FFFF));
354
355 // Get packed wire-form.
356 ICMPPtr echo_icmp = next_echo->pack();
357
358 // Create instance of the callback. It is safe to pass the
359 // local instance of the callback, because the underlying
360 // std functions make copies as needed.
362 shared_from_this(),
363 next_echo,
364 ph::_1, // error
365 ph::_2)); // bytes_transferred
366
367 ICMPEndpoint target_endpoint(context->getTarget());
368 asyncSend(echo_icmp.get(), sizeof(struct icmp), &target_endpoint, cb);
369 } catch (const std::exception& ex) {
370 // Normal IO failures should be passed to the callback. A failure here
371 // indicates the call to asyncSend() itself failed.
373 .arg(ex.what());
374 stopChannel();
375 return;
376 }
377}
378
379void
380PingChannel::socketWriteCallback(ICMPMsgPtr echo, boost::system::error_code ec,
381 size_t length) {
382 {
384 if (stopping_) {
385 return;
386 }
387 }
388
389 if (single_threaded_) {
390 try {
391 // Clear the IO ready marker.
392 watch_socket_->clearReady();
393 } catch (const std::exception& ex) {
394 // This can only happen if the WatchSocket's select_fd has been
395 // compromised which is a programmatic error. We'll log the error
396 // here, then continue on and process the IO result we were given.
397 // WatchSocket issue will resurface on the next send as a closed
398 // fd in markReady() rather than fail out of this callback.
400 .arg(ex.what());
401 }
402 }
403
404 // Handle an error. Note we can't use a case statement as some values
405 // on some OSes are the same (e.g. try_again and would_block) which causes
406 // duplicate case compilation errors.
407 bool send_failed = false;
408 if (ec) {
409 auto error_value = ec.value();
410 if (error_value == boost::asio::error::operation_aborted) {
411 // IO service has been stopped and the connection is probably
412 // going to be shutting down.
413 return;
414 } else if ((error_value == boost::asio::error::try_again) ||
415 (error_value == boost::asio::error::would_block)) {
416 // We got EWOULDBLOCK or EAGAIN which indicates that we may be able to
417 // write something from the socket on the next attempt. Set the length
418 // to zero so we skip the completion callback.
419 length = 0;
420 } else if ((error_value == boost::asio::error::network_unreachable) ||
421 (error_value == boost::asio::error::host_unreachable) ||
422 (error_value == boost::asio::error::network_down)) {
423 // One of these implies an interface might be down, or there's no
424 // way to ping this network. Other networks might be working OK.
425 send_failed = true;
426 } else if (error_value == boost::asio::error::no_buffer_space) {
427 // Writing faster than the kernel will write them out.
428 send_failed = true;
429 } else if (error_value == boost::asio::error::access_denied) {
430 // Means the address we tried to ping is not allowed. Most likey a broadcast
431 // address.
432 send_failed = true;
433 } else {
434 // Anything else is fatal for the socket.
436 .arg(ec.message());
437 stopChannel();
438 return;
439 }
440 }
441
442 {
444 sending_ = false;
445 }
446
447 if (send_failed) {
448 // Invoke the callback with send failed. This instructs the manager
449 // to treat the address as free to use.
451 .arg(echo->getDestination())
452 .arg(ec.message());
453 // Invoke the send completed callback.
454 (echo_sent_cb_)(echo, true);
455 } else if (length > 0) {
458 .arg(echo->getDestination())
459 .arg(echo->getId())
460 .arg(echo->getSequence());
461 // Invoke the send completed callback.
462 (echo_sent_cb_)(echo, false);
463 }
464
465 // Schedule the next send.
466 sendNext();
467}
468
469size_t
471 return (input_buf_.size());
472}
473
474unsigned char*
476 if (input_buf_.empty()) {
478 "PingChannel::getInputBufData() - cannot access empty buffer");
479 }
480
481 return (input_buf_.data());
482}
483
484} // end of namespace ping_check
485} // end of namespace isc
#define UINT32_MAX
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:363
std::function< void(int fd)> SocketCallback
Defines callback used when data is received over external sockets.
Definition iface_mgr.h:688
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition iface_mgr.cc:49
void addExternalSocket(int socketfd, SocketCallback callback)
Adds external socket and a callback.
Definition iface_mgr.cc:332
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:35
static ICMPMsgPtr unpack(const uint8_t *wire_data, size_t length)
Unpacks an ICMP message from the given wire_data.
Definition icmp_msg.cc:30
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 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:26
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:29
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.