├── example
├── makefile
├── ping.cbp
├── ping.cpp
└── ping.vcxproj
├── LICENSE
├── README.md
└── icmplib.h
/example/makefile:
--------------------------------------------------------------------------------
1 | ping_example: ping.cpp
2 | $(CROSS_COMPILE)g++ -Wall -std=c++11 -I../ -O3 -o ping_example ping.cpp
3 |
4 | clean:
5 | rm ./ping_example
--------------------------------------------------------------------------------
/example/ping.cbp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | /*
2 | ICMPLib - a C++ header-only ICMP Ping library
3 |
4 | Copyright (c) 2021, Marcin Kondej
5 | All rights reserved.
6 |
7 | See https://github.com/markondej/cpp-icmplib
8 |
9 | Redistribution and use in source and binary forms, with or without modification, are
10 | permitted provided that the following conditions are met:
11 |
12 | 1. Redistributions of source code must retain the above copyright notice, this list
13 | of conditions and the following disclaimer.
14 |
15 | 2. Redistributions in binary form must reproduce the above copyright notice, this
16 | list of conditions and the following disclaimer in the documentation and/or other
17 | materials provided with the distribution.
18 |
19 | 3. Neither the name of the copyright holder nor the names of its contributors may be
20 | used to endorse or promote products derived from this software without specific
21 | prior written permission.
22 |
23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
24 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
26 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
28 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
31 | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 | */
--------------------------------------------------------------------------------
/example/ping.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "icmplib.h"
3 |
4 | int main(int argc, char *argv[])
5 | {
6 | std::string address = "8.8.8.8", resolved;
7 | if (argc > 1) { address = argv[1]; }
8 | try {
9 | if (!icmplib::IPAddress::IsCorrect(address, icmplib::IPAddress::Type::Unknown)) {
10 | resolved = address; address = icmplib::IPAddress(address);
11 | }
12 | } catch (...) {
13 | std::cout << "Ping request could not find host " << address << ". Please check the name and try again." << std::endl;
14 | return 1;
15 | }
16 |
17 | int ret = EXIT_SUCCESS;
18 | std::cout << "Pinging " << (resolved.empty() ? address : resolved + " [" + address + "]")
19 | << " with " << ICMPLIB_PING_DATA_SIZE << " bytes of data:" << std::endl;
20 | auto result = icmplib::Ping(address, ICMPLIB_TIMEOUT_1S);
21 | switch (result.response) {
22 | case icmplib::PingResponseType::Failure:
23 | std::cout << "Network error." << std::endl;
24 | ret = EXIT_FAILURE;
25 | break;
26 | case icmplib::PingResponseType::Timeout:
27 | std::cout << "Request timed out." << std::endl;
28 | break;
29 | default:
30 | std::cout << "Reply from " << static_cast(result.address) << ": ";
31 | switch (result.response) {
32 | case icmplib::PingResponseType::Success:
33 | std::cout << "time=" << result.delay;
34 | if (result.address.GetType() != icmplib::IPAddress::Type::IPv6) {
35 | std::cout << " TTL=" << static_cast(result.ttl);
36 | }
37 | break;
38 | case icmplib::PingResponseType::Unreachable:
39 | std::cout << "Destination unreachable.";
40 | break;
41 | case icmplib::PingResponseType::TimeExceeded:
42 | std::cout << "Time exceeded.";
43 | break;
44 | default:
45 | std::cout << "Response not supported.";
46 | }
47 | std::cout << std::endl;
48 | }
49 | return ret;
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A C++ header-only ICMP/ICMPv6 Ping library
2 |
3 | cpp-icmplib is simple header-only cross-platform library, which allows performing system-like ping requests from C++ applications without need of using system "ping" command.
4 | As this library is socket-based, on most operating systems, it will require administrator privilages (root) to run.
5 |
6 | ## How to use
7 |
8 | icmplib delivers function Ping declared as:
9 | ```
10 | PingResult Ping(const icmplib::AddressIP &target, unsigned timeout = 1000, uint16_t sequence = 1, uint8_t ttl = 255);
11 | ```
12 | Notice:
13 | * target - Network address (may be created from std::string)
14 | * timeout - Timeout in milliseconds
15 | * sequence - Sequence number to be sent
16 | * ttl - Time-to-live to be set for packet
17 |
18 | PingResult structure is declared as:
19 | ```
20 | struct PingResult {
21 | enum class ResponseType {
22 | Success,
23 | Unreachable,
24 | TimeExceeded,
25 | Timeout,
26 | Unsupported,
27 | Failure
28 | } response;
29 | double delay;
30 | icmplib::AddressIP address;
31 | uint8_t code;
32 | uint8_t ttl;
33 | };
34 | ```
35 | Notice:
36 | * delay - Time in miliseconds between sending request and receiving response
37 | * address - Address of responding host
38 | * code - ICMP Code parameter
39 | * ttl - Received IPv4 header TTL parameter
40 | * response - Type of received response
41 |
42 | ```
43 | ResponseType | Meaning
44 | --------------------------------------------------------------------------------------------------------
45 | Success | ICMP Echo Response successfully received
46 | Unreachable | ICMP Destination Ureachable message received (eg. target host does not exist)
47 | TimeExceeded | ICMP Time Exceeded message received (eg. TTL meet zero value on some host)
48 | Timeout | No message recived in given time (see "timeout" parameter)
49 | Unsupported | Received unsupported ICMP packet
50 | Failure | Failed to send ICMP Echo Request to given target host
51 | ```
52 |
53 | ## Examples
54 |
55 | In order to make internet connection test simply use:
56 | ```
57 | #include "icmplib.h"
58 |
59 | ...
60 |
61 | bool isConnected()
62 | {
63 | return icmplib::Ping("8.8.8.8", ICMPLIB_TIMEOUT_1S).response == icmplib::PingResponseType::Success; // Test Google DNS address
64 | }
65 | ```
66 |
67 | Simple traceroute implementation:
68 | ```
69 | #include "icmplib.h"
70 | #include
71 | ...
72 |
73 | std::vector traceroute(const std::string &address)
74 | {
75 | std::vector result;
76 | for (uint8_t ttl = 1; ttl != 0; ttl++) {
77 | auto ping = icmplib::Ping(address, ICMPLIB_TIMEOUT_1S, 1, ttl);
78 | switch (ping.response) {
79 | case icmplib::PingResult::ResponseType::TimeExceeded:
80 | result.push_back(ping.address.ToString());
81 | break;
82 | case icmplib::PingResult::ResponseType::Success:
83 | result.push_back(ping.address.ToString());
84 | return result;
85 | default:
86 | return result;
87 | }
88 | }
89 | return result;
90 | }
91 | ```
92 |
93 | ## Known issues
94 |
95 | On Windows 10 ICMP messages other than Echo Response seem to be blocked and, while being received, are not passed to application via socket, timeout is detected instead
96 |
--------------------------------------------------------------------------------
/example/ping.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | 15.0
23 | {63814305-4048-4DF8-A51F-C51C1151CFE3}
24 | Win32Proj
25 | ping
26 | 10.0
27 |
28 |
29 |
30 | Application
31 | true
32 | v142
33 | Unicode
34 |
35 |
36 | Application
37 | false
38 | v143
39 | true
40 | Unicode
41 |
42 |
43 | Application
44 | true
45 | v143
46 | Unicode
47 |
48 |
49 | Application
50 | false
51 | v143
52 | true
53 | Unicode
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | true
75 |
76 |
77 | true
78 |
79 |
80 | false
81 |
82 |
83 | false
84 |
85 |
86 |
87 |
88 |
89 | Level3
90 | Disabled
91 | true
92 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
93 | true
94 | ../
95 |
96 |
97 | Console
98 | true
99 | RequireAdministrator
100 |
101 |
102 |
103 |
104 |
105 |
106 | Level3
107 | Disabled
108 | true
109 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
110 | true
111 | ../
112 |
113 |
114 | Console
115 | true
116 | RequireAdministrator
117 |
118 |
119 |
120 |
121 |
122 |
123 | Level3
124 | MaxSpeed
125 | true
126 | true
127 | true
128 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
129 | true
130 | ../
131 |
132 |
133 | Console
134 | true
135 | true
136 | true
137 | RequireAdministrator
138 |
139 |
140 |
141 |
142 |
143 |
144 | Level3
145 | MaxSpeed
146 | true
147 | true
148 | true
149 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
150 | true
151 | ../
152 |
153 |
154 | Console
155 | true
156 | true
157 | true
158 | RequireAdministrator
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
--------------------------------------------------------------------------------
/icmplib.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifndef ICMPLIB_PING_DATA_SIZE
4 | #define ICMPLIB_PING_DATA_SIZE 64
5 | #endif
6 |
7 | #ifndef ICMPLIB_RECV_BUFFER_SIZE
8 | #define ICMPLIB_RECV_BUFFER_SIZE 1024
9 | #endif
10 |
11 | #define _WINSOCK_DEPRECATED_NO_WARNINGS
12 |
13 | #include
14 | #include
15 | #include
16 | #include
17 | #ifdef _WIN32
18 | #define _WIN32_WINNT 0x0601
19 | #include
20 | #else
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #endif
29 |
30 | #define ICMPLIB_ICMP_ECHO_RESPONSE 0
31 | #define ICMPLIB_ICMP_DESTINATION_UNREACHABLE 3
32 | #define ICMPLIB_ICMP_ECHO_REQUEST 8
33 | #define ICMPLIB_ICMP_TIME_EXCEEDED 11
34 | #define ICMPLIB_ICMPV6_DESTINATION_UNREACHABLE 1
35 | #define ICMPLIB_ICMPV6_TIME_EXCEEDED 3
36 | #define ICMPLIB_ICMPV6_ECHO_REQUEST 128
37 | #define ICMPLIB_ICMPV6_ECHO_RESPONSE 129
38 |
39 | #define ICMPLIB_INET4_HEADER_SIZE 20
40 | #define ICMPLIB_INET4_TTL_OFFSET 8
41 | #define ICMPLIB_INET4_ORIGINAL_DATA_SIZE ICMPLIB_INET4_HEADER_SIZE + 8
42 | #define ICMPLIB_INET6_ORIGINAL_DATA_SIZE 8
43 |
44 | #define ICMPLIB_TIMEOUT_1S 1000
45 |
46 | #ifdef _WIN32
47 | #define ICMPLIB_SOCKET SOCKET
48 | #define ICMPLIB_SOCKLEN int
49 | #define ICMPLIB_SOCKET_ERROR SOCKET_ERROR
50 | #define ICMPLIB_CLOSESOCKET closesocket
51 | #else
52 | #define ICMPLIB_SOCKET int
53 | #define ICMPLIB_SOCKLEN socklen_t
54 | #define ICMPLIB_SOCKET_ERROR -1
55 | #define ICMPLIB_CLOSESOCKET close
56 | #endif
57 |
58 | #if (defined _WIN32 && defined _MSC_VER)
59 | #pragma comment(lib, "ws2_32.lib")
60 | #endif
61 |
62 | namespace icmplib {
63 | #ifdef _WIN32
64 | class WinSock {
65 | public:
66 | WinSock(const WinSock &) = delete;
67 | WinSock(WinSock &&) = delete;
68 | virtual ~WinSock() {
69 | WSACleanup();
70 | }
71 | WinSock &operator=(const WinSock &) = delete;
72 | static WinSock &Initialize() {
73 | static WinSock instance;
74 | return instance;
75 | }
76 | private:
77 | WinSock() {
78 | WSADATA wsaData;
79 | int error = WSAStartup(MAKEWORD(2, 2), &wsaData);
80 | if (error != NO_ERROR) {
81 | throw std::runtime_error("Cannot initialize WinSock!");
82 | }
83 | if ((LOBYTE(wsaData.wVersion) != 2) || (HIBYTE(wsaData.wVersion) != 2)) {
84 | WSACleanup();
85 | throw std::runtime_error("Cannot initialize WinSock!");
86 | }
87 | }
88 | };
89 |
90 | #endif
91 | class IPAddress {
92 | public:
93 | enum class Type {
94 | IPv4,
95 | IPv6,
96 | Unknown
97 | };
98 | IPAddress() {
99 | address = reinterpret_cast(new sockaddr_in);
100 | std::memset(address, 0, sizeof(sockaddr_in));
101 | reinterpret_cast(address)->sin_family = AF_INET;
102 | }
103 | IPAddress(const std::string &address, Type type = Type::Unknown) : IPAddress() {
104 | auto init = [&](Type type) {
105 | switch (type) {
106 | case Type::IPv6:
107 | delete this->address;
108 | this->address = reinterpret_cast(new sockaddr_in6);
109 | std::memset(this->address, 0, sizeof(sockaddr_in6));
110 | reinterpret_cast(this->address)->sin6_family = AF_INET6;
111 | if (inet_pton(AF_INET6, address.c_str(), &reinterpret_cast(this->address)->sin6_addr) <= 0) {
112 | throw std::runtime_error("Incorrect IPv6 address provided");
113 | }
114 | break;
115 | case Type::IPv4:
116 | default:
117 | if (inet_pton(AF_INET, address.c_str(), &reinterpret_cast(this->address)->sin_addr) <= 0) {
118 | throw std::runtime_error("Incorrect IPv4 address provided");
119 | }
120 | }
121 | };
122 | if ((type != Type::Unknown) && IsCorrect(address, type)) {
123 | init(type);
124 | return;
125 | } else if (type == Type::Unknown) {
126 | if (IsCorrect(address, Type::IPv4)) {
127 | init(Type::IPv4);
128 | return;
129 | } else if (IsCorrect(address, Type::IPv6)) {
130 | init(Type::IPv6);
131 | return;
132 | }
133 | }
134 | Resolve(address, type);
135 | }
136 | IPAddress(const std::string &address, uint16_t port, Type type = Type::Unknown) : IPAddress(address, type) {
137 | SetPort(port);
138 | }
139 | IPAddress(uint32_t address) : IPAddress() {
140 | reinterpret_cast(this->address)->sin_addr.s_addr = htonl(address);
141 | }
142 | IPAddress(uint32_t address, uint16_t port) : IPAddress(address) {
143 | SetPort(port);
144 | }
145 | IPAddress(const IPAddress &source) {
146 | switch (source.GetType()) {
147 | case Type::IPv6:
148 | address = reinterpret_cast(new sockaddr_in6);
149 | std::memcpy(address, source.address, sizeof(sockaddr_in6));
150 | break;
151 | case Type::IPv4:
152 | default:
153 | address = reinterpret_cast(new sockaddr_in);
154 | std::memcpy(address, source.address, sizeof(sockaddr_in));
155 | }
156 | }
157 | IPAddress(IPAddress &&source) {
158 | address = source.address;
159 | source.address = reinterpret_cast(new sockaddr_in);
160 | std::memset(source.address, 0, sizeof(sockaddr_in));
161 | reinterpret_cast(source.address)->sin_family = AF_INET;
162 | }
163 | virtual ~IPAddress() {
164 | delete address;
165 | }
166 | IPAddress &operator=(const IPAddress &source) {
167 | delete address;
168 | switch (source.GetType()) {
169 | case Type::IPv6:
170 | address = reinterpret_cast(new sockaddr_in6);
171 | std::memcpy(address, source.address, sizeof(sockaddr_in6));
172 | break;
173 | case Type::IPv4:
174 | default:
175 | address = reinterpret_cast(new sockaddr_in);
176 | std::memcpy(address, source.address, sizeof(sockaddr_in));
177 | }
178 | return *this;
179 | }
180 | IPAddress &operator=(IPAddress &&source) {
181 | delete address;
182 | address = source.address;
183 | source.address = reinterpret_cast(new sockaddr_in);
184 | std::memset(source.address, 0, sizeof(sockaddr_in));
185 | reinterpret_cast(source.address)->sin_family = AF_INET;
186 | return *this;
187 | }
188 | IPAddress &Resolve(const std::string &address, Type type = Type::IPv4) {
189 | #ifdef _WIN32
190 | WinSock::Initialize();
191 | #endif
192 | addrinfo hints;
193 | std::memset(&hints, 0, sizeof(addrinfo));
194 | hints.ai_family = AF_UNSPEC;
195 | hints.ai_socktype = SOCK_STREAM;
196 | hints.ai_protocol = IPPROTO_TCP;
197 |
198 | addrinfo *result = NULL;
199 | if (getaddrinfo(address.c_str(), NULL, &hints, &result) == 0) {
200 | for (addrinfo *ptr = result; ptr != NULL; ptr = ptr->ai_next) {
201 | switch (ptr->ai_family) {
202 | case AF_INET:
203 | if ((type != Type::IPv4) && (type != Type::Unknown)) {
204 | break;
205 | }
206 | delete this->address;
207 | this->address = reinterpret_cast(new sockaddr_in);
208 | std::memcpy(this->address, ptr->ai_addr, sizeof(sockaddr_in));
209 | freeaddrinfo(result);
210 | type = Type::IPv4;
211 | return *this;
212 | case AF_INET6:
213 | if ((type != Type::IPv6) && (type != Type::Unknown)) {
214 | break;
215 | }
216 | delete this->address;
217 | this->address = reinterpret_cast(new sockaddr_in6);
218 | std::memcpy(this->address, ptr->ai_addr, sizeof(sockaddr_in6));
219 | freeaddrinfo(result);
220 | type = Type::IPv6;
221 | return *this;
222 | default:
223 | break;
224 | }
225 | }
226 | freeaddrinfo(result);
227 | }
228 | throw std::runtime_error("Cannot resolve host address: " + address);
229 | }
230 | operator std::string() const {
231 | char buffer[INET6_ADDRSTRLEN];
232 | switch (GetType()) {
233 | case Type::IPv6:
234 | if (inet_ntop(AF_INET6, &reinterpret_cast(address)->sin6_addr, buffer, INET6_ADDRSTRLEN) != NULL) {
235 | return std::string(buffer);
236 | }
237 | throw std::runtime_error("Cannot convert IPv6 address structure");
238 | case Type::IPv4:
239 | default:
240 | if (inet_ntop(AF_INET, &reinterpret_cast(address)->sin_addr, buffer, INET6_ADDRSTRLEN) != NULL) {
241 | return std::string(buffer);
242 | }
243 | throw std::runtime_error("Cannot convert IPv4 address structure");
244 | }
245 | }
246 | void SetPort(uint16_t port) {
247 | switch (GetType()) {
248 | case Type::IPv6:
249 | reinterpret_cast(address)->sin6_port = htons(port);
250 | break;
251 | case Type::IPv4:
252 | default:
253 | reinterpret_cast(address)->sin_port = htons(port);
254 | }
255 | }
256 | uint16_t GetPort() const {
257 | switch (GetType()) {
258 | case Type::IPv6:
259 | return ntohs(reinterpret_cast(address)->sin6_port);
260 | break;
261 | case Type::IPv4:
262 | default:
263 | return ntohs(reinterpret_cast(address)->sin_port);
264 | }
265 | }
266 | Type GetType() const {
267 | switch (address->sa_family) {
268 | case AF_INET6:
269 | return Type::IPv6;
270 | case AF_INET:
271 | default:
272 | return Type::IPv4;
273 | }
274 | }
275 | sockaddr *GetSockAddr() const {
276 | return address;
277 | }
278 | ICMPLIB_SOCKLEN GetSockAddrLength() const {
279 | switch (GetType()) {
280 | case Type::IPv6:
281 | return sizeof(sockaddr_in6);
282 | case Type::IPv4:
283 | default:
284 | return sizeof(sockaddr_in);
285 | }
286 | }
287 | static bool IsCorrect(const std::string &address, Type type = Type::IPv4) {
288 | switch (type) {
289 | case Type::IPv4:
290 | return std::regex_match(address, std::regex("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"));
291 | case Type::IPv6:
292 | return std::regex_match(address, std::regex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"));
293 | default:
294 | return IsCorrect(address, Type::IPv4) || IsCorrect(address, Type::IPv6);
295 | }
296 | }
297 | static int GetFamily(Type type) {
298 | switch (type) {
299 | case Type::IPv6:
300 | return AF_INET6;
301 | case Type::IPv4:
302 | default:
303 | return AF_INET;
304 | }
305 | }
306 | private:
307 | sockaddr *address;
308 | };
309 |
310 | class ICMPEcho {
311 | public:
312 | struct Result {
313 | enum class ResponseType {
314 | Success,
315 | Unreachable,
316 | TimeExceeded,
317 | Timeout,
318 | Unsupported,
319 | Failure
320 | } response;
321 | double delay;
322 | IPAddress address;
323 | uint8_t code;
324 | uint8_t ttl;
325 | };
326 | ICMPEcho() = delete;
327 | ICMPEcho(const ICMPEcho &) = delete;
328 | ICMPEcho(ICMPEcho &&) = delete;
329 | ICMPEcho &operator=(const ICMPEcho &) = delete;
330 | static Result Execute(const IPAddress &target, unsigned timeout = ICMPLIB_TIMEOUT_1S, uint16_t sequence = 1, uint8_t ttl = 255) {
331 | Result result = { Result::ResponseType::Timeout, static_cast(timeout), IPAddress(), 0, 0 };
332 | try {
333 | #ifdef _WIN32
334 | WinSock::Initialize();
335 | #endif
336 | ICMPSocket sock(target.GetType(), ttl);
337 |
338 | ICMPRequest request(target.GetType(), sequence);
339 | request.Send(sock.GetSocket(), target);
340 | auto start = std::chrono::high_resolution_clock::now();
341 | IPAddress source(target);
342 |
343 | while (true) {
344 | ICMPResponse response;
345 | bool recv = response.Receive(sock.GetSocket(), source, timeout);
346 | auto end = std::chrono::high_resolution_clock::now();
347 | if (!recv) {
348 | unsigned delta = static_cast(std::chrono::duration_cast(end - start).count());
349 | if (delta >= timeout) {
350 | break;
351 | }
352 | timeout -= delta;
353 | continue;
354 | }
355 |
356 | result.response = (source.GetType() != IPAddress::Type::IPv6) ? GetResponseType(request, response) : GetResponseTypeV6(request, response);
357 | if (result.response != Result::ResponseType::Timeout) {
358 | result.delay = static_cast(std::chrono::duration_cast(end - start).count()) / 1000.0;
359 | result.address = source;
360 | result.code = response.GetICMPHeader().code;
361 | result.ttl = response.GetTTL();
362 | break;
363 | }
364 | }
365 | } catch (...) {
366 | return { Result::ResponseType::Failure, 0, IPAddress(), 0, 0 };
367 | }
368 | return result;
369 | }
370 | private:
371 | struct ICMPHeader {
372 | uint8_t type;
373 | uint8_t code;
374 | uint16_t checksum;
375 | };
376 |
377 | struct ICMPEchoMessage : ICMPHeader {
378 | uint16_t id;
379 | uint16_t seq;
380 | uint8_t data[ICMPLIB_PING_DATA_SIZE];
381 | };
382 |
383 | struct ICMPRevertedMessage : ICMPHeader {
384 | uint32_t unused;
385 | uint8_t data[ICMPLIB_INET4_ORIGINAL_DATA_SIZE];
386 | };
387 |
388 | class ICMPSocket {
389 | public:
390 | ICMPSocket(IPAddress::Type type, uint8_t ttl) {
391 | int protocol = IPPROTO_ICMP;
392 | if (type == IPAddress::Type::IPv6) {
393 | protocol = IPPROTO_ICMPV6;
394 | }
395 |
396 | sock = socket(IPAddress::GetFamily(type), SOCK_RAW, protocol);
397 | #ifdef _WIN32
398 | if (sock == INVALID_SOCKET) {
399 | #else
400 | if (sock <= 0) {
401 | #endif
402 | throw std::runtime_error("Cannot initialize socket!");
403 | }
404 |
405 | switch (type) {
406 | case IPAddress::Type::IPv6:
407 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, reinterpret_cast(&ttl), sizeof(uint8_t)) == ICMPLIB_SOCKET_ERROR) {
408 | ICMPLIB_CLOSESOCKET(sock);
409 | throw std::runtime_error("Cannot set socket options!");
410 | }
411 | break;
412 | case IPAddress::Type::IPv4:
413 | default:
414 | if (setsockopt(sock, IPPROTO_IP, IP_TTL, reinterpret_cast(&ttl), sizeof(uint8_t)) == ICMPLIB_SOCKET_ERROR) {
415 | ICMPLIB_CLOSESOCKET(sock);
416 | throw std::runtime_error("Cannot set socket options!");
417 | }
418 | }
419 |
420 | #ifdef _WIN32
421 | unsigned long mode = 1;
422 | if (ioctlsocket(sock, FIONBIO, &mode) != NO_ERROR) {
423 | #else
424 | int flags = fcntl(sock, F_GETFL, 0);
425 | if ((flags == -1) || fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
426 | #endif
427 | ICMPLIB_CLOSESOCKET(sock);
428 | throw std::runtime_error("Cannot set socket options!");
429 | }
430 | }
431 | virtual ~ICMPSocket() {
432 | ICMPLIB_CLOSESOCKET(sock);
433 | }
434 | const ICMPLIB_SOCKET &GetSocket() {
435 | return sock;
436 | }
437 | private:
438 | ICMPLIB_SOCKET sock;
439 | };
440 |
441 | class ICMPRequest : public ICMPEchoMessage {
442 | public:
443 | ICMPRequest() = delete;
444 | ICMPRequest(IPAddress::Type protocol, uint16_t sequence = 1) {
445 | std::memset(this, 0, sizeof(ICMPEchoMessage));
446 | id = rand() % USHRT_MAX;
447 | type = (protocol != IPAddress::Type::IPv6) ? ICMPLIB_ICMP_ECHO_REQUEST : ICMPLIB_ICMPV6_ECHO_REQUEST;
448 | seq = sequence;
449 | if (protocol != IPAddress::Type::IPv6) {
450 | SetChecksum(*this);
451 | }
452 | }
453 | void Send(ICMPLIB_SOCKET sock, const IPAddress &address) {
454 | int bytes = sendto(sock, reinterpret_cast(this), sizeof(ICMPEchoMessage), 0, address.GetSockAddr(), address.GetSockAddrLength());
455 | if (bytes == ICMPLIB_SOCKET_ERROR) {
456 | throw std::runtime_error("Failed to send request!");
457 | }
458 | }
459 | };
460 |
461 | class ICMPResponse {
462 | public:
463 | ICMPResponse() : protocol(IPAddress::Type::IPv4), header(nullptr), length(0) {
464 | std::memset(&buffer, 0, sizeof(uint8_t) * ICMPLIB_RECV_BUFFER_SIZE);
465 | }
466 | virtual ~ICMPResponse() {
467 | if (header) {
468 | delete header;
469 | }
470 | }
471 | bool Receive(ICMPLIB_SOCKET sock, IPAddress &address, unsigned timeout) {
472 | fd_set sock_set;
473 | FD_ZERO(&sock_set);
474 | FD_SET(sock, &sock_set);
475 |
476 | timeval timeout_val;
477 | timeout_val.tv_sec = timeout / 1000;
478 | timeout_val.tv_usec = (timeout % 1000) * 1000;
479 |
480 | int activity = select(sock + 1, &sock_set, NULL, NULL, &timeout_val);
481 | if ((activity <= 0) | !FD_ISSET(sock, &sock_set)) {
482 | return false;
483 | }
484 |
485 | ICMPLIB_SOCKLEN length = address.GetSockAddrLength();
486 | int bytes = recvfrom(sock, reinterpret_cast(buffer), ICMPLIB_RECV_BUFFER_SIZE, 0, address.GetSockAddr(), &length);
487 | if (bytes <= 0) {
488 | return false;
489 | }
490 | this->length = static_cast(bytes);
491 | protocol = address.GetType();
492 | return true;
493 | };
494 | template
495 | const T Generate() const {
496 | if (sizeof(T) > length) {
497 | throw std::runtime_error("Incorrect ICMP packet size!");
498 | }
499 | T packet;
500 | std::memset(&packet, 0, sizeof(T));
501 | switch (protocol) {
502 | case IPAddress::Type::IPv6:
503 | std::memcpy(&packet, buffer, static_cast(length) > sizeof(T) ? sizeof(T) : static_cast(length));
504 | break;
505 | case IPAddress::Type::IPv4:
506 | default:
507 | std::memcpy(&packet, &buffer[ICMPLIB_INET4_HEADER_SIZE], static_cast(length) - ICMPLIB_INET4_HEADER_SIZE > sizeof(T) ? sizeof(T) : static_cast(length) - ICMPLIB_INET4_HEADER_SIZE);
508 | }
509 | return packet;
510 | }
511 | const ICMPHeader &GetICMPHeader() {
512 | if (!header) {
513 | header = new ICMPHeader;
514 | *header = Generate();
515 | }
516 | return *header;
517 | }
518 | IPAddress::Type GetProtocol() const {
519 | return protocol;
520 | }
521 | uint8_t GetTTL() const {
522 | switch (protocol) {
523 | case IPAddress::Type::IPv6:
524 | return 0;
525 | break;
526 | case IPAddress::Type::IPv4:
527 | default:
528 | return buffer[ICMPLIB_INET4_TTL_OFFSET];
529 | }
530 | }
531 | unsigned GetSize() const {
532 | switch (protocol) {
533 | case IPAddress::Type::IPv6:
534 | return length;
535 | break;
536 | case IPAddress::Type::IPv4:
537 | default:
538 | return length - ICMPLIB_INET4_HEADER_SIZE;
539 | }
540 | }
541 | private:
542 | IPAddress::Type protocol;
543 | uint8_t buffer[ICMPLIB_RECV_BUFFER_SIZE];
544 | ICMPHeader *header;
545 | unsigned length;
546 | };
547 |
548 | static Result::ResponseType GetResponseType(const ICMPRequest &request, ICMPResponse &response) {
549 | Result::ResponseType result = Result::ResponseType::Timeout;
550 | ICMPEchoMessage echo;
551 | ICMPRevertedMessage reverted;
552 | switch (response.GetICMPHeader().type) {
553 | case ICMPLIB_ICMP_ECHO_RESPONSE:
554 | result = Result::ResponseType::Success;
555 | echo = response.Generate();
556 | echo.checksum = 0;
557 | if ((response.GetICMPHeader().checksum != SetChecksum(echo)) || (request.id != echo.id)) {
558 | result = Result::ResponseType::Unsupported;
559 | }
560 | break;
561 | case ICMPLIB_ICMP_DESTINATION_UNREACHABLE:
562 | result = Result::ResponseType::Unreachable;
563 | case ICMPLIB_ICMP_TIME_EXCEEDED:
564 | if (result == Result::ResponseType::Timeout) {
565 | result = Result::ResponseType::TimeExceeded;
566 | }
567 | reverted = response.Generate();
568 | reverted.checksum = 0;
569 | if (response.GetICMPHeader().checksum != SetChecksum(reverted)) {
570 | result = Result::ResponseType::Unsupported;
571 | }
572 | break;
573 | case ICMPLIB_ICMP_ECHO_REQUEST:
574 | break;
575 | default:
576 | result = Result::ResponseType::Unsupported;
577 | }
578 |
579 | return result;
580 | };
581 |
582 | static Result::ResponseType GetResponseTypeV6(const ICMPRequest &request, ICMPResponse &response) {
583 | Result::ResponseType result = Result::ResponseType::Timeout;
584 | ICMPEchoMessage echo;
585 | switch (response.GetICMPHeader().type) {
586 | case ICMPLIB_ICMPV6_ECHO_RESPONSE:
587 | result = Result::ResponseType::Success;
588 | echo = response.Generate();
589 | if (request.id != echo.id) {
590 | result = Result::ResponseType::Unsupported;
591 | }
592 | break;
593 | case ICMPLIB_ICMPV6_DESTINATION_UNREACHABLE:
594 | result = Result::ResponseType::Unreachable;
595 | case ICMPLIB_ICMPV6_TIME_EXCEEDED:
596 | if (result == Result::ResponseType::Timeout) {
597 | result = Result::ResponseType::TimeExceeded;
598 | }
599 | break;
600 | case ICMPLIB_ICMPV6_ECHO_REQUEST:
601 | break;
602 | default:
603 | result = Result::ResponseType::Unsupported;
604 | }
605 |
606 | return result;
607 | };
608 |
609 | template
610 | static uint16_t SetChecksum(T &packet) {
611 | uint16_t *element = reinterpret_cast(&packet);
612 | unsigned long size = sizeof(T);
613 | uint32_t sum = 0;
614 | for (; size > 1; size -= 2) {
615 | sum += *element++;
616 | }
617 | if (size > 0) {
618 | sum += *reinterpret_cast(element);
619 | }
620 | sum = (sum >> 16) + (sum & 0xffff);
621 | sum += (sum >> 16);
622 | packet.checksum = static_cast(~sum);
623 | return packet.checksum;
624 | };
625 | };
626 |
627 | using PingResult = ICMPEcho::Result;
628 | using PingResponseType = ICMPEcho::Result::ResponseType;
629 |
630 | inline PingResult Ping(const IPAddress &target, unsigned timeout = ICMPLIB_TIMEOUT_1S, uint16_t sequence = 1, uint8_t ttl = 255) {
631 | return ICMPEcho::Execute(target, timeout, sequence, ttl);
632 | }
633 | }
634 |
--------------------------------------------------------------------------------