├── LICENSE ├── README.md ├── example ├── makefile ├── ping.cbp ├── ping.cpp └── ping.vcxproj └── icmplib.h /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 | */ -------------------------------------------------------------------------------- /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/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 | 40 | 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------