Kea 3.1.1
icmp_socket.h
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#ifndef ICMP_SOCKET_H
8#define ICMP_SOCKET_H 1
9
10#include <netinet/in.h>
11#include <sys/socket.h>
12#include <unistd.h>
13
14#include <cstddef>
15
17#include <asiolink/io_service.h>
18#include <icmp_endpoint.h>
19
21
22namespace isc {
23namespace ping_check {
24
29template <typename C>
30class ICMPSocket : public asiolink::IOAsioSocket<C> {
31private:
33 explicit ICMPSocket(const ICMPSocket&);
34 ICMPSocket& operator=(const ICMPSocket&);
35
36public:
37 enum {
38 MIN_SIZE = 4096 // Minimum send and receive size
39 };
40
46 explicit ICMPSocket(boost::asio::ip::icmp::socket& socket);
47
54 explicit ICMPSocket(const asiolink::IOServicePtr& service);
55
57 virtual ~ICMPSocket();
58
62 virtual int getNative() const {
63#if BOOST_VERSION < 106600
64 return (socket_.native());
65#else
66 return (socket_.native_handle());
67#endif
68 }
69
73 virtual int getProtocol() const {
74 return (IPPROTO_ICMP);
75 }
76
81 virtual bool isOpenSynchronous() const {
82 return true;
83 }
84
88 virtual bool isOpen() const {
89 return isopen_;
90 }
91
100 virtual void open(const asiolink::IOEndpoint* endpoint, C& callback);
101
112 virtual void asyncSend(const void* data, size_t length,
113 const asiolink::IOEndpoint* endpoint, C& callback);
114
126 virtual void asyncReceive(void* data, size_t length, size_t offset,
127 asiolink::IOEndpoint* endpoint, C& callback);
128
144 virtual bool processReceivedData(const void* staging, size_t length,
145 size_t& cumulative, size_t& offset,
146 size_t& expected,
148
150 virtual void cancel();
151
153 virtual void close();
154
161 static uint16_t calcChecksum(const uint8_t* buf, const uint32_t buf_size);
162
163private:
165 isc::asiolink::IOServicePtr io_service_;
166
167 // Two variables to hold the socket - a socket and a pointer to it. This
168 // handles the case where a socket is passed to the ICMPSocket on
169 // construction, or where it is asked to manage its own socket.
170
172 std::unique_ptr<boost::asio::ip::icmp::socket> socket_ptr_;
173
174 // Socket
175 boost::asio::ip::icmp::socket& socket_;
176
177 // True when socket is open
178 bool isopen_;
179};
180
181// Constructor - caller manages socket
182
183template <typename C>
184ICMPSocket<C>::ICMPSocket(boost::asio::ip::icmp::socket& socket) :
185 socket_ptr_(), socket_(socket), isopen_(true) {
186}
187
188// Constructor - create socket on the fly
189
190template <typename C>
191ICMPSocket<C>::ICMPSocket(const asiolink::IOServicePtr& io_service) :
192 io_service_(io_service),
193 socket_ptr_(new boost::asio::ip::icmp::socket(io_service_->getInternalIOService())),
194 socket_(*socket_ptr_), isopen_(false) {
195}
196
197// Destructor.
198
199template <typename C>
202
203// Open the socket.
204
205template <typename C> void
207
208 // Ignore opens on already-open socket. (Don't throw a failure because
209 // of uncertainties as to what precedes when using asynchronous I/O.)
210 // It also allows us a treat a passed-in socket in exactly the same way as
211 // a self-managed socket (in that we can call the open() and close() methods
212 // of this class).
213 if (!isopen_) {
214 if (endpoint->getFamily() == AF_INET) {
215 socket_.open(boost::asio::ip::icmp::v4());
216 } else {
217 socket_.open(boost::asio::ip::icmp::v6());
218 }
219 isopen_ = true;
220
221 // Ensure it can send and receive at least 4K buffers.
222 boost::asio::ip::icmp::socket::send_buffer_size snd_size;
223 socket_.get_option(snd_size);
224 if (snd_size.value() < MIN_SIZE) {
225 snd_size = MIN_SIZE;
226 socket_.set_option(snd_size);
227 }
228
229 boost::asio::ip::icmp::socket::receive_buffer_size rcv_size;
230 socket_.get_option(rcv_size);
231 if (rcv_size.value() < MIN_SIZE) {
232 rcv_size = MIN_SIZE;
233 socket_.set_option(rcv_size);
234 }
235
236 boost::asio::socket_base::do_not_route option(false);
237 socket_.set_option(option);
238 }
239}
240
241// Send a message. Should never do this if the socket is not open, so throw
242// an exception if this is the case.
243
244template <typename C> void
245ICMPSocket<C>::asyncSend(const void* data, size_t length,
246 const asiolink::IOEndpoint* endpoint, C& callback) {
247 if (isopen_) {
248
249 // Upconvert to a ICMPEndpoint. We need to do this because although
250 // IOEndpoint is the base class of ICMPEndpoint and TCPEndpoint, it
251 // does not contain a method for getting at the underlying endpoint
252 // type - that is in the derived class and the two classes differ on
253 // return type.
254 isc_throw_assert(endpoint->getProtocol() == IPPROTO_ICMP);
255 const ICMPEndpoint* udp_endpoint =
256 static_cast<const ICMPEndpoint*>(endpoint);
257
258 // ... and send the message.
259 socket_.async_send_to(boost::asio::buffer(data, length),
260 udp_endpoint->getASIOEndpoint(), callback);
261 } else {
263 "attempt to send on a ICMP socket that is not open");
264 }
265}
266
267// Receive a message. Should never do this if the socket is not open, so throw
268// an exception if this is the case.
269
270template <typename C> void
271ICMPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
272 asiolink::IOEndpoint* endpoint, C& callback) {
273 if (isopen_) {
274
275 // Upconvert the endpoint again.
276 isc_throw_assert(endpoint->getProtocol() == IPPROTO_ICMP);
277 ICMPEndpoint* udp_endpoint = static_cast<ICMPEndpoint*>(endpoint);
278
279 // Ensure we can write into the buffer
280 if (offset >= length) {
281 isc_throw(asiolink::BufferOverflow, "attempt to read into area beyond end of "
282 "ICMP receive buffer");
283 }
284 void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
285
286 // Issue the read
287 socket_.async_receive_from(boost::asio::buffer(buffer_start, length - offset),
288 udp_endpoint->getASIOEndpoint(), callback);
289 } else {
291 "attempt to receive from a ICMP socket that is not open");
292 }
293}
294
295// Receive complete. Just copy the data across to the output buffer and
296// update arguments as appropriate.
297
298template <typename C> bool
299ICMPSocket<C>::processReceivedData(const void* staging, size_t length,
300 size_t& cumulative, size_t& offset,
301 size_t& expected,
303 // Set return values to what we should expect.
304 cumulative = length;
305 expected = length;
306 offset = 0;
307
308 // Copy data across
309 outbuff->writeData(staging, length);
310
311 // ... and mark that we have everything.
312 return (true);
313}
314
315// Cancel I/O on the socket. No-op if the socket is not open.
316
317template <typename C> void
319 if (isopen_) {
320 socket_.cancel();
321 }
322}
323
324// Close the socket down. Can only do this if the socket is open and we are
325// managing it ourself.
326
327template <typename C> void
329 if (isopen_ && socket_ptr_) {
330 socket_.close();
331 isopen_ = false;
332 }
333}
334
335template <typename C> uint16_t
336ICMPSocket<C>::calcChecksum(const uint8_t* buf, const uint32_t buf_size) {
337 uint32_t sum = 0;
338 uint32_t i;
339 for (i = 0; i < (buf_size & ~1U); i += 2) {
340 uint16_t chunk = buf[i] << 8 | buf[i + 1];
341 sum += chunk;
342 if (sum > 0xFFFF) {
343 sum -= 0xFFFF;
344 }
345 }
346 // If one byte has left, we also need to add it to the checksum.
347 if (i < buf_size) {
348 sum += buf[i] << 8;
349 if (sum > 0xFFFF) {
350 sum -= 0xFFFF;
351 }
352 }
353
354 return (sum);
355}
356
357} // namespace ping_check
358} // namespace isc
359#endif // ICMP_SOCKET_H
The ICMPEndpoint class is a concrete derived class of IOEndpoint that represents an endpoint of a ICM...
const boost::asio::ip::icmp::endpoint & getASIOEndpoint() const
Fetches the underlying ASIO endpoint implementation.
virtual void close()
Close socket.
virtual void cancel()
Cancel I/O On Socket.
ICMPSocket(const asiolink::IOServicePtr &service)
Constructor.
virtual void open(const asiolink::IOEndpoint *endpoint, C &callback)
Open Socket.
virtual bool processReceivedData(const void *staging, size_t length, size_t &cumulative, size_t &offset, size_t &expected, isc::util::OutputBufferPtr &outbuff)
Process received data.
virtual int getProtocol() const
Return protocol of socket.
Definition icmp_socket.h:73
static uint16_t calcChecksum(const uint8_t *buf, const uint32_t buf_size)
Calculates the checksum for the given buffer of data.
virtual void asyncReceive(void *data, size_t length, size_t offset, asiolink::IOEndpoint *endpoint, C &callback)
Receive Asynchronously.
virtual bool isOpenSynchronous() const
Is "open()" synchronous?
Definition icmp_socket.h:81
ICMPSocket(boost::asio::ip::icmp::socket &socket)
Constructor from an ASIO ICMP socket.
virtual ~ICMPSocket()
Destructor.
virtual bool isOpen() const
Indicates if the socket is currently open.
Definition icmp_socket.h:88
virtual void asyncSend(const void *data, size_t length, const asiolink::IOEndpoint *endpoint, C &callback)
Send Asynchronously.
virtual int getNative() const
Return file descriptor of underlying socket.
Definition icmp_socket.h:62
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition isc_assert.h:18
boost::shared_ptr< OutputBuffer > OutputBufferPtr
Type of pointers to output buffers.
Definition buffer.h:574
Defines the logger used by the top-level component of kea-lfc.