Kea 3.1.1
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 EchoSentCallback echo_sent_cb,
40 ReplyReceivedCallback reply_received_cb,
41 ShutdownCallback shutdown_cb)
42 : io_service_(io_service),
43 next_to_send_cb_(next_to_send_cb),
44 echo_sent_cb_(echo_sent_cb),
45 reply_received_cb_(reply_received_cb),
46 shutdown_cb_(shutdown_cb),
47 socket_(0), input_buf_(256),
48 reading_(false), sending_(false), stopping_(false), mutex_(new std::mutex),
49 single_threaded_(!MultiThreadingMgr::instance().getMode()),
51 if (!io_service_) {
53 "PingChannel ctor - io_service cannot be empty");
54 }
55}
56
60
61void
63 try {
65 if (socket_ && socket_->isOpen()) {
66 return;
67 }
68
69 // For open(), the endpoint is only used to determine protocol,
70 // the address is irrelevant.
72 SocketCallback socket_cb(
73 [](boost::system::error_code ec, size_t /*length */) {
74 isc_throw(Unexpected, "ICMPSocket open is synchronous, should not invoke cb: "
75 << ec.message());
76 }
77 );
78
79 socket_.reset(new PingSocket(io_service_));
80 socket_->open(&ping_to_endpoint, socket_cb);
81 reading_ = false;
82 sending_ = false;
83 stopping_ = false;
84
85 if (single_threaded_) {
86 // Open new watch socket.
88
89 // Register the WatchSocket with IfaceMgr to signal data ready to write.
90 registered_write_fd_ = watch_socket_->getSelectFd();
92
93 // Register ICMPSocket with IfaceMgr to signal data ready to read.
94 registered_read_fd_ = socket_->getNative();
96 }
97
98 } catch (const std::exception& ex) {
99 isc_throw(Unexpected, "PingChannel::open failed:" << ex.what());
100 }
101
103}
104
105bool
108 return (socket_ && socket_->isOpen());
109}
110
111void
113 try {
115
116 if (single_threaded_) {
117 // Unregister from IfaceMgr.
118 if (registered_write_fd_ != -1) {
121 }
122
123 if (registered_read_fd_ != -1) {
126 }
127
128 // Close watch socket.
129 if (watch_socket_) {
130 std::string error_string;
131 watch_socket_->closeSocket(error_string);
132 if (!error_string.empty()) {
134 .arg(error_string);
135 }
136
137 watch_socket_.reset();
138 }
139 }
140
141 if (!socket_ || !socket_->isOpen()) {
142 return;
143 }
144
145 socket_->close();
146 } catch (const std::exception& ex) {
147 // On close error, log but do not throw.
149 .arg(ex.what());
150 }
151
153}
154
155void
157 {
159 if (stopping_) {
160 return;
161 }
162
163 stopping_ = true;
164 }
165
167 close();
168
169 if (shutdown_cb_) {
170 (shutdown_cb_)();
171 }
172}
173
174void
175PingChannel::asyncReceive(void* data, size_t length, size_t offset,
176 asiolink::IOEndpoint* endpoint, SocketCallback& callback) {
177 socket_->asyncReceive(data, length, offset, endpoint, callback);
178}
179
180void
181PingChannel::asyncSend(void* data, size_t length, asiolink::IOEndpoint* endpoint,
182 SocketCallback& callback) {
183 socket_->asyncSend(data, length, endpoint, callback);
184
185 if (single_threaded_) {
186 // Set IO ready marker so sender activity is visible to select() or poll().
187 watch_socket_->markReady();
188 }
189}
190
191void
193 try {
195 if (!canRead()) {
196 return;
197 }
198
199 reading_ = true;
200
201 // Create instance of the callback. It is safe to pass the
202 // local instance of the callback, because the underlying
203 // std functions make copies as needed.
205 shared_from_this(),
206 ph::_1, // error
207 ph::_2)); // bytes_transferred
208 asyncReceive(static_cast<void*>(getInputBufData()), getInputBufSize(),
209 0, &reply_endpoint_, cb);
210 } catch (const std::exception& ex) {
211 // Normal IO failures should be passed to the callback. A failure here
212 // indicates the call to asyncReceive() itself failed.
214 .arg(ex.what());
215 stopChannel();
216 }
217}
218
219void
220PingChannel::socketReadCallback(boost::system::error_code ec, size_t length) {
221 {
223 if (stopping_) {
224 return;
225 }
226 }
227
228 if (ec) {
229 if (ec.value() == boost::asio::error::operation_aborted) {
230 // IO service has been stopped and the connection is probably
231 // going to be shutting down.
232 return;
233 } else if ((ec.value() == boost::asio::error::try_again) ||
234 (ec.value() == boost::asio::error::would_block)) {
235 // We got EWOULDBLOCK or EAGAIN which indicates that we may be able to
236 // read something from the socket on the next attempt. Just make sure
237 // we don't try to read anything now in case there is any garbage
238 // passed in length.
239 length = 0;
240 } else {
241 // Anything else is fatal for the socket.
243 .arg(ec.message());
244 stopChannel();
245 return;
246 }
247 }
248
249 // Unpack the reply and pass it to the reply callback.
250 ICMPMsgPtr reply;
251 if (length > 0) {
252 {
253 try {
256 if (reply->getType() == ICMPMsg::ECHO_REPLY) {
259 .arg(reply->getSource())
260 .arg(reply->getId())
261 .arg(reply->getSequence());
262 }
263 } catch (const std::exception& ex) {
266 .arg(ex.what());
267 }
268 }
269 }
270
271 {
273 reading_ = false;
274 }
275
276 if (reply) {
277 (reply_received_cb_)(reply);
278 }
279
280 // Start the next read.
281 doRead();
282}
283
284void
287 if (canSend()) {
288 // Post the call to sendNext to the IOService.
289 // This ensures its carried out on a thread
290 // associated with the channel's IOService
291 // not the thread invoking this function.
292 auto f = [](PingChannelPtr ptr) { ptr->sendNext(); };
293 io_service_->post(std::bind(f, shared_from_this()));
294 }
295}
296
297void
300 if (canRead()) {
301 // Post the call to doRead to the IOService.
302 // This ensures its carried out on a thread
303 // associated with the channel's IOService
304 // not the thread invoking this function.
305 auto f = [](PingChannelPtr ptr) { ptr->doRead(); };
306 io_service_->post(std::bind(f, shared_from_this()));
307 }
308}
309
310void
312 try {
314 if (!canSend()) {
315 // Can't send right now, get out.
316 return;
317 }
318
319 // Fetch the next one to send.
320 IOAddress target("0.0.0.0");
321 if (!((next_to_send_cb_)(target))) {
322 // Nothing to send.
323 return;
324 }
325
326 // Have an target IP, build an ECHO REQUEST for it.
327 sending_ = true;
328 ICMPMsgPtr next_echo(new ICMPMsg());
329 next_echo->setType(ICMPMsg::ECHO_REQUEST);
330 next_echo->setDestination(target);
331
332 uint32_t instance_num = nextEchoInstanceNum();
333 next_echo->setId(static_cast<uint16_t>(instance_num >> 16));
334 next_echo->setSequence(static_cast<uint16_t>(instance_num & 0x0000FFFF));
335
336 // Get packed wire-form.
337 ICMPPtr echo_icmp = next_echo->pack();
338
339 // Create instance of the callback. It is safe to pass the
340 // local instance of the callback, because the underlying
341 // std functions make copies as needed.
343 shared_from_this(),
344 next_echo,
345 ph::_1, // error
346 ph::_2)); // bytes_transferred
347
348 ICMPEndpoint target_endpoint(target);
349 asyncSend(echo_icmp.get(), sizeof(struct icmp), &target_endpoint, cb);
350 } catch (const std::exception& ex) {
351 // Normal IO failures should be passed to the callback. A failure here
352 // indicates the call to asyncSend() itself failed.
354 .arg(ex.what());
355 stopChannel();
356 return;
357 }
358}
359
360void
361PingChannel::socketWriteCallback(ICMPMsgPtr echo, boost::system::error_code ec,
362 size_t length) {
363 {
365 if (stopping_) {
366 return;
367 }
368 }
369
370 if (single_threaded_) {
371 try {
372 // Clear the IO ready marker.
373 watch_socket_->clearReady();
374 } catch (const std::exception& ex) {
375 // This can only happen if the WatchSocket's select_fd has been
376 // compromised which is a programmatic error. We'll log the error
377 // here, then continue on and process the IO result we were given.
378 // WatchSocket issue will resurface on the next send as a closed
379 // fd in markReady() rather than fail out of this callback.
381 .arg(ex.what());
382 }
383 }
384
385 // Handle an error. Note we can't use a case statement as some values
386 // on some OSes are the same (e.g. try_again and would_block) which causes
387 // duplicate case compilation errors.
388 bool send_failed = false;
389 if (ec) {
390 auto error_value = ec.value();
391 if (error_value == boost::asio::error::operation_aborted) {
392 // IO service has been stopped and the connection is probably
393 // going to be shutting down.
394 return;
395 } else if ((error_value == boost::asio::error::try_again) ||
396 (error_value == boost::asio::error::would_block)) {
397 // We got EWOULDBLOCK or EAGAIN which indicates that we may be able to
398 // write something from the socket on the next attempt. Set the length
399 // to zero so we skip the completion callback.
400 length = 0;
401 } else if ((error_value == boost::asio::error::network_unreachable) ||
402 (error_value == boost::asio::error::host_unreachable) ||
403 (error_value == boost::asio::error::network_down)) {
404 // One of these implies an interface might be down, or there's no
405 // way to ping this network. Other networks might be working OK.
406 send_failed = true;
407 } else if (error_value == boost::asio::error::no_buffer_space) {
408 // Writing faster than the kernel will write them out.
409 send_failed = true;
410 } else if (error_value == boost::asio::error::access_denied) {
411 // Means the address we tried to ping is not allowed. Most likey a broadcast
412 // address.
413 send_failed = true;
414 } else {
415 // Anything else is fatal for the socket.
417 .arg(ec.message());
418 stopChannel();
419 return;
420 }
421 }
422
423 {
425 sending_ = false;
426 }
427
428 if (send_failed) {
429 // Invoke the callback with send failed. This instructs the manager
430 // to treat the address as free to use.
432 .arg(echo->getDestination())
433 .arg(ec.message());
434 // Invoke the send completed callback.
435 (echo_sent_cb_)(echo, true);
436 } else if (length > 0) {
439 .arg(echo->getDestination())
440 .arg(echo->getId())
441 .arg(echo->getSequence());
442 // Invoke the send completed callback.
443 (echo_sent_cb_)(echo, false);
444 }
445
446 // Schedule the next send.
447 sendNext();
448}
449
450size_t
452 return (input_buf_.size());
453}
454
455unsigned char*
457 if (input_buf_.empty()) {
459 "PingChannel::getInputBufData() - cannot access empty buffer");
460 }
461
462 return (input_buf_.data());
463}
464
465} // end of namespace ping_check
466} // 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.
bool isOpen() const
Indicates whether or not the channel socket is open.
void open()
Opens the socket for communications.
PingChannel(asiolink::IOServicePtr &io_service, NextToSendCallback next_to_send_cb, EchoSentCallback echo_sent_cb, ReplyReceivedCallback reply_received_cb, ShutdownCallback shutdown_cb=ShutdownCallback())
Constructor.
static uint32_t nextEchoInstanceNum()
returns the next unique ECHO instance number.
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.
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 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< bool(asiolink::IOAddress &target)> NextToSendCallback
Function type for callback that fetches next IOAddress to ping.
boost::shared_ptr< struct icmp > ICMPPtr
Shared pointer type for struct icmp.
Definition icmp_msg.h:29
boost::shared_ptr< PingChannel > PingChannelPtr
Defines a smart pointer to PingChannel.
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.