Kea 3.1.3
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
105}
106
107bool
110 return (socket_ && socket_->isOpen());
111}
112
113void
115 try {
117
118 if (single_threaded_) {
119 // Unregister from IfaceMgr.
120 if (registered_write_fd_ != -1) {
123 }
124
125 if (registered_read_fd_ != -1) {
128 }
129
130 // Close watch socket.
131 if (watch_socket_) {
132 std::string error_string;
133 watch_socket_->closeSocket(error_string);
134 if (!error_string.empty()) {
136 .arg(error_string);
137 }
138
139 watch_socket_.reset();
140 }
141 }
142
143 if (!socket_ || !socket_->isOpen()) {
144 return;
145 }
146
147 socket_->close();
148 } catch (const std::exception& ex) {
149 // On close error, log but do not throw.
151 .arg(ex.what());
152 }
153
155}
156
157void
159 {
161 if (stopping_) {
162 return;
163 }
164
165 stopping_ = true;
166 }
167
169 close();
170
171 if (shutdown_cb_) {
172 (shutdown_cb_)();
173 }
174}
175
176void
177PingChannel::asyncReceive(void* data, size_t length, size_t offset,
178 asiolink::IOEndpoint* endpoint, SocketCallback& callback) {
179 socket_->asyncReceive(data, length, offset, endpoint, callback);
180}
181
182void
183PingChannel::asyncSend(void* data, size_t length, asiolink::IOEndpoint* endpoint,
184 SocketCallback& callback) {
185 socket_->asyncSend(data, length, endpoint, callback);
186
187 if (single_threaded_) {
188 // Set IO ready marker so sender activity is visible to select() or poll().
189 watch_socket_->markReady();
190 }
191}
192
193void
195 try {
197 if (!canRead()) {
198 return;
199 }
200
201 reading_ = true;
202
203 // Create instance of the callback. It is safe to pass the
204 // local instance of the callback, because the underlying
205 // std functions make copies as needed.
207 shared_from_this(),
208 ph::_1, // error
209 ph::_2)); // bytes_transferred
210 asyncReceive(static_cast<void*>(getInputBufData()), getInputBufSize(),
211 0, &reply_endpoint_, cb);
212 } catch (const std::exception& ex) {
213 // Normal IO failures should be passed to the callback. A failure here
214 // indicates the call to asyncReceive() itself failed.
216 .arg(ex.what());
217 stopChannel();
218 }
219}
220
221void
222PingChannel::socketReadCallback(boost::system::error_code ec, size_t length) {
223 {
225 if (stopping_) {
226 return;
227 }
228 }
229
230 if (ec) {
231 if (ec.value() == boost::asio::error::operation_aborted) {
232 // IO service has been stopped and the connection is probably
233 // going to be shutting down.
234 return;
235 } else if ((ec.value() == boost::asio::error::try_again) ||
236 (ec.value() == boost::asio::error::would_block)) {
237 // We got EWOULDBLOCK or EAGAIN which indicates that we may be able to
238 // read something from the socket on the next attempt. Just make sure
239 // we don't try to read anything now in case there is any garbage
240 // passed in length.
241 length = 0;
242 } else {
243 // Anything else is fatal for the socket.
245 .arg(ec.message());
246 stopChannel();
247 return;
248 }
249 }
250
251 // Unpack the reply and pass it to the reply callback.
252 ICMPMsgPtr reply;
253 if (length > 0) {
254 {
255 try {
258 if (reply->getType() == ICMPMsg::ECHO_REPLY) {
261 .arg(reply->getSource())
262 .arg(reply->getId())
263 .arg(reply->getSequence());
264 }
265 } catch (const std::exception& ex) {
268 .arg(ex.what());
269 }
270 }
271 }
272
273 {
275 reading_ = false;
276 }
277
278 if (reply) {
279 (reply_received_cb_)(reply);
280 }
281
282 // Start the next read.
283 doRead();
284}
285
286void
289 if (canSend()) {
290 // Post the call to sendNext to the IOService.
291 // This ensures its carried out on a thread
292 // associated with the channel's IOService
293 // not the thread invoking this function.
294 auto f = [](PingChannelPtr ptr) { ptr->sendNext(); };
295 io_service_->post(std::bind(f, shared_from_this()));
296 }
297}
298
299void
302 if (canRead()) {
303 // Post the call to doRead to the IOService.
304 // This ensures its carried out on a thread
305 // associated with the channel's IOService
306 // not the thread invoking this function.
307 auto f = [](PingChannelPtr ptr) { ptr->doRead(); };
308 io_service_->post(std::bind(f, shared_from_this()));
309 }
310}
311
312void
314 try {
315 // Mutex used to do atomic read of the store entry using
316 // @ref PingContextStore::getNextToSend and update the context from
317 // WAITING_TO_SEND state to SENDING state using
318 // @ref PingContext::setState. Both functions (when transitioning from
319 // WAITING_TO_SEND state to SENDING state) should be called only
320 // with this mutex locked.
322
323 // Fetch the next one to send (outside the mutex) to avoid a possible
324 // deadlock with the mutex in the @ref PingCheckMgr::nextToSend callback.
325 PingContextPtr context = ((next_to_send_cb_)());
326 if (!context) {
327 // Nothing to send.
328 return;
329 }
330
332 if (!canSend()) {
333 // Can't send right now, get out.
334 return;
335 }
336
337 // Update context to SENDING (inside the mutex).
338 if (update_to_send_cb_) {
339 (update_to_send_cb_)(context);
340 }
341
342 // Have an target IP, build an ECHO REQUEST for it.
343 sending_ = true;
344 ICMPMsgPtr next_echo(new ICMPMsg());
345 next_echo->setType(ICMPMsg::ECHO_REQUEST);
346 next_echo->setDestination(context->getTarget());
347
348 uint32_t instance_num = nextEchoInstanceNum();
349 next_echo->setId(static_cast<uint16_t>(instance_num >> 16));
350 next_echo->setSequence(static_cast<uint16_t>(instance_num & 0x0000FFFF));
351
352 // Get packed wire-form.
353 ICMPPtr echo_icmp = next_echo->pack();
354
355 // Create instance of the callback. It is safe to pass the
356 // local instance of the callback, because the underlying
357 // std functions make copies as needed.
359 shared_from_this(),
360 next_echo,
361 ph::_1, // error
362 ph::_2)); // bytes_transferred
363
364 ICMPEndpoint target_endpoint(context->getTarget());
365 asyncSend(echo_icmp.get(), sizeof(struct icmp), &target_endpoint, cb);
366 } catch (const std::exception& ex) {
367 // Normal IO failures should be passed to the callback. A failure here
368 // indicates the call to asyncSend() itself failed.
370 .arg(ex.what());
371 stopChannel();
372 return;
373 }
374}
375
376void
377PingChannel::socketWriteCallback(ICMPMsgPtr echo, boost::system::error_code ec,
378 size_t length) {
379 {
381 if (stopping_) {
382 return;
383 }
384 }
385
386 if (single_threaded_) {
387 try {
388 // Clear the IO ready marker.
389 watch_socket_->clearReady();
390 } catch (const std::exception& ex) {
391 // This can only happen if the WatchSocket's select_fd has been
392 // compromised which is a programmatic error. We'll log the error
393 // here, then continue on and process the IO result we were given.
394 // WatchSocket issue will resurface on the next send as a closed
395 // fd in markReady() rather than fail out of this callback.
397 .arg(ex.what());
398 }
399 }
400
401 // Handle an error. Note we can't use a case statement as some values
402 // on some OSes are the same (e.g. try_again and would_block) which causes
403 // duplicate case compilation errors.
404 bool send_failed = false;
405 if (ec) {
406 auto error_value = ec.value();
407 if (error_value == boost::asio::error::operation_aborted) {
408 // IO service has been stopped and the connection is probably
409 // going to be shutting down.
410 return;
411 } else if ((error_value == boost::asio::error::try_again) ||
412 (error_value == boost::asio::error::would_block)) {
413 // We got EWOULDBLOCK or EAGAIN which indicates that we may be able to
414 // write something from the socket on the next attempt. Set the length
415 // to zero so we skip the completion callback.
416 length = 0;
417 } else if ((error_value == boost::asio::error::network_unreachable) ||
418 (error_value == boost::asio::error::host_unreachable) ||
419 (error_value == boost::asio::error::network_down)) {
420 // One of these implies an interface might be down, or there's no
421 // way to ping this network. Other networks might be working OK.
422 send_failed = true;
423 } else if (error_value == boost::asio::error::no_buffer_space) {
424 // Writing faster than the kernel will write them out.
425 send_failed = true;
426 } else if (error_value == boost::asio::error::access_denied) {
427 // Means the address we tried to ping is not allowed. Most likey a broadcast
428 // address.
429 send_failed = true;
430 } else {
431 // Anything else is fatal for the socket.
433 .arg(ec.message());
434 stopChannel();
435 return;
436 }
437 }
438
439 {
441 sending_ = false;
442 }
443
444 if (send_failed) {
445 // Invoke the callback with send failed. This instructs the manager
446 // to treat the address as free to use.
448 .arg(echo->getDestination())
449 .arg(ec.message());
450 // Invoke the send completed callback.
451 (echo_sent_cb_)(echo, true);
452 } else if (length > 0) {
455 .arg(echo->getDestination())
456 .arg(echo->getId())
457 .arg(echo->getSequence());
458 // Invoke the send completed callback.
459 (echo_sent_cb_)(echo, false);
460 }
461
462 // Schedule the next send.
463 sendNext();
464}
465
466size_t
468 return (input_buf_.size());
469}
470
471unsigned char*
473 if (input_buf_.empty()) {
475 "PingChannel::getInputBufData() - cannot access empty buffer");
476 }
477
478 return (input_buf_.data());
479}
480
481} // end of namespace ping_check
482} // 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:352
std::function< void(int fd)> SocketCallback
Defines callback used when data is received over external sockets.
Definition iface_mgr.h:661
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition iface_mgr.cc:54
void addExternalSocket(int socketfd, SocketCallback callback)
Adds external socket and a callback.
Definition iface_mgr.cc:329
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.