ping.c (12469B)
1//Copyright 2017 <>< C. Lohr, under the MIT/x11 License 2//Rewritten from Sean Walton and Macmillan Publishers. 3//Most of it was rewritten but the header was never updated. 4//Now I finished the job. 5 6#include <string.h> 7#include <fcntl.h> 8#include <errno.h> 9#include <stdio.h> 10#include <stdlib.h> 11#include <assert.h> 12#include "ping.h" 13#include "error_handling.h" 14#include "resolve.h" 15 16#ifdef TCC 17#include "tccheader.h" 18#endif 19 20int ping_failed_to_send; 21float pingperiodseconds; 22int precise_ping; 23struct sockaddr_in6 psaddr; 24socklen_t psaddr_len; 25int using_regular_ping; 26 27#ifdef WIN_USE_NO_ADMIN_PING 28 29#ifdef TCC 30 //... 31#else 32#include <inaddr.h> 33#include <ws2tcpip.h> 34#include <ipexport.h> 35#include <icmpapi.h> 36#include <windows.h> 37#endif 38 39#include "rawdraw/os_generic.h" 40 41 42 43#define MAX_PING_SIZE 16384 44#define PINGTHREADS 100 45 46#if !defined( MINGW_BUILD ) && !defined( TCC ) 47 #pragma comment(lib, "Ws2_32.lib") 48 #pragma comment(lib, "iphlpapi.lib") 49#endif 50 51static og_sema_t s_disp; 52static og_sema_t s_ping; 53 54void ping_setup(const char * strhost, const char * device) 55{ 56 // resolve host 57 psaddr_len = sizeof(psaddr); 58 if( strhost ) 59 resolveName((struct sockaddr*) &psaddr, &psaddr_len, strhost, AF_INET); // only resolve ipv4 on windows 60 else 61 psaddr.sin6_family = AF_INET; 62 63 s_disp = OGCreateSema(); 64 s_ping = OGCreateSema(); 65 //This function is executed first. 66} 67 68void listener() 69{ 70 static uint8_t listth; 71 if( listth ) return; 72 listth = 1; 73 74 OGUnlockSema( s_disp ); 75 //Normally needs to call display(buf + 28, bytes - 28 ); on successful ping. 76 //This function is executed as a thread after setup. 77 //Really, we just use the s_disp semaphore to make sure we only launch disp's at correct times. 78 79 while(1) { OGSleep( 100000 ); } 80 return; 81} 82 83static HANDLE pinghandles[PINGTHREADS]; 84 85static void * pingerthread( void * v ) 86{ 87 uint8_t ping_payload[MAX_PING_SIZE]; 88 89 HANDLE ih = *((HANDLE*)v); 90 91 int timeout_ms = pingperiodseconds * (PINGTHREADS-1) * 1000; 92 while(1) 93 { 94 OGLockSema( s_ping ); 95 int rl = load_ping_packet( ping_payload, sizeof( ping_payload ) ); 96 struct repl_t 97 { 98 ICMP_ECHO_REPLY rply; 99 uint8_t err_data[16384]; 100 } repl; 101 102 DWORD res = IcmpSendEcho( ih, 103 ((struct sockaddr_in*) &psaddr)->sin_addr.s_addr, ping_payload, rl, 104 0, &repl, sizeof( repl ), 105 timeout_ms ); 106 int err; 107 if( !res ) err = GetLastError(); 108 OGLockSema( s_disp ); 109 110 if( !res ) 111 { 112 if( err == 11050 ) 113 { 114 printf( "GENERAL FAILURE\n" ); 115 } 116 else 117 { 118 printf( "ERROR: %d\n", err ); 119 } 120 } 121 if( res ) 122 { 123 display( repl.rply.Data, rl ); 124 } 125 OGUnlockSema( s_disp ); 126 } 127 return 0; 128} 129 130void singleping(struct sockaddr *addr, socklen_t addr_len ) 131{ 132 int i; 133 (void) addr; 134 (void) addr_len; 135 136 if( psaddr.sin6_family != AF_INET ) 137 { 138 // ipv6 ICMP Ping is not supported on windows 139 ERRM( "ERROR: ipv6 ICMP Ping is not supported on windows\n" ); 140 exit( -1 ); 141 } 142 143 static int did_init_threads = 0; 144 if( !did_init_threads ) 145 { 146 did_init_threads = 1; 147 //Launch pinger threads 148 for( i = 0; i < PINGTHREADS; i++ ) 149 { 150 HANDLE ih = pinghandles[i] = IcmpCreateFile(); 151 if( ih == INVALID_HANDLE_VALUE ) 152 { 153 ERRM( "Cannot create ICMP thread %d\n", i ); 154 exit( 0 ); 155 } 156 157 OGCreateThread( pingerthread, &pinghandles[i] ); 158 } 159 } 160 //This function is executed as a thread after setup. 161 162 if( i >= PINGTHREADS-1 ) i = 0; 163 else i++; 164 OGUnlockSema( s_ping ); 165} 166 167void ping(struct sockaddr *addr, socklen_t addr_len ) 168{ 169 int i; 170 (void) addr; 171 (void) addr_len; 172 173 using_regular_ping = 1; 174 175 if( psaddr.sin6_family != AF_INET ) 176 { 177 // ipv6 ICMP Ping is not supported on windows 178 ERRM( "ERROR: ipv6 ICMP Ping is not supported on windows\n" ); 179 exit( -1 ); 180 } 181 182 //Launch pinger threads 183 for( i = 0; i < PINGTHREADS; i++ ) 184 { 185 HANDLE ih = pinghandles[i] = IcmpCreateFile(); 186 if( ih == INVALID_HANDLE_VALUE ) 187 { 188 ERRM( "Cannot create ICMP thread %d\n", i ); 189 exit( 0 ); 190 } 191 192 OGCreateThread( pingerthread, &pinghandles[i] ); 193 } 194 //This function is executed as a thread after setup. 195 196 while(1) 197 { 198 if( i >= PINGTHREADS-1 ) i = 0; 199 else i++; 200 OGUnlockSema( s_ping ); 201 OGUSleep( (int)(pingperiodseconds * 1000000) ); 202 } 203} 204 205 206 207#else // ! WIN_USE_NO_ADMIN_PING 208 209//The normal way to do it - only problem is Windows needs admin privs. 210 211 212#ifdef WIN32 213 #include <winsock2.h> 214 #define SOL_IP 0 215 #define F_SETFL 4 216 #define ICMP_ECHO 8 217 #define IP_TTL 2 218 #define O_NONBLOCK 04000 219#ifndef MINGW_BUILD 220 #pragma comment(lib, "Ws2_32.lib") 221#endif 222 #include <windows.h> 223 #include <winsock2.h> 224 #include <ws2tcpip.h> 225 #include <stdint.h> 226#else // ! WIN32 227 #ifdef __FreeBSD__ 228 #include <netinet/in.h> 229 #endif 230 #include <unistd.h> 231 #include <sys/socket.h> 232 #include <resolv.h> 233 #include <netdb.h> 234 #if defined(__APPLE__) || defined(__FreeBSD__) 235 #ifndef SOL_IP 236 #define SOL_IP IPPROTO_IP 237 #endif 238 #endif 239 #include <netinet/ip.h> 240 #include <netinet/ip_icmp.h> 241 #include <netinet/icmp6.h> 242#endif 243 244#include "rawdraw/os_generic.h" 245 246#if defined WIN32 || defined __APPLE__ 247struct icmphdr 248{ 249 uint8_t type; 250 uint8_t code; 251 uint16_t checksum; 252 union 253 { 254 struct 255 { 256 uint16_t id; 257 uint16_t sequence; 258 } echo; 259 uint32_t gateway; 260 struct 261 { 262 uint16_t __unused; 263 uint16_t mtu; 264 } frag; 265 } un; 266}; 267#endif 268 269 270#define PACKETSIZE 65536 271 272struct packet 273{ 274#ifdef __FreeBSD__ 275 struct icmp hdr; 276 unsigned char msg[PACKETSIZE-sizeof(struct icmp)]; 277#else 278 struct icmphdr hdr; 279 unsigned char msg[PACKETSIZE-sizeof(struct icmphdr)]; 280#endif 281}; 282 283int sd; 284int pid=-1; 285 286uint16_t checksum( const unsigned char * start, uint16_t len ) 287{ 288 uint16_t i; 289 const uint16_t * wptr = (uint16_t*) start; 290 uint32_t csum = 0; 291 for (i=1;i<len;i+=2) 292 csum += (uint32_t)(*(wptr++)); 293 if( len & 1 ) //See if there's an odd number of bytes? 294 csum += *(uint8_t*)wptr; 295 if (csum>>16) 296 csum = (csum & 0xFFFF)+(csum >> 16); 297 //csum = (csum>>8) | ((csum&0xff)<<8); 298 return ~csum; 299} 300 301// setsockopt TTL to 255 302void setTTL(int sock) 303{ 304 const int val=255; 305 306 assert(psaddr.sin6_family == AF_INET || psaddr.sin6_family == AF_INET6); 307 308 if ( setsockopt(sd, (psaddr.sin6_family == AF_INET) ? SOL_IP : SOL_IPV6, IP_TTL, &val, sizeof(val)) != 0) 309 { 310 ERRM("Error: Failed to set TTL option. Are you root? Or can do sock_raw sockets?\n"); 311 exit( -1 ); 312 } 313} 314 315// 0 = failed, 1 = this is a ICMP Response 316int isICMPResponse(unsigned char* buf, int bytes) 317{ 318 assert(psaddr.sin6_family == AF_INET || psaddr.sin6_family == AF_INET6); 319 320 if( bytes == -1 ) return 0; 321 322 if( psaddr.sin6_family == AF_INET ) // ipv4 compare 323 { 324 if( buf[9] != IPPROTO_ICMP ) return 0; 325 if( buf[20] != ICMP_ECHOREPLY ) return 0; 326 327 } 328 else if( psaddr.sin6_family == AF_INET6 ) // ipv6 compare 329 { 330 if( buf[0] != ICMP6_ECHO_REPLY ) return 0; 331 } 332 333 return 1; 334} 335 336int createSocket() 337{ 338 if( psaddr.sin6_family == AF_INET ) 339 { 340 return socket(PF_INET, SOCK_RAW, IPPROTO_ICMP); 341 } 342 else if( psaddr.sin6_family == AF_INET6 ) 343 { 344 return socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6); 345 } 346 347 // invalid af_family 348 return -1; 349} 350 351void listener() 352{ 353#ifndef WIN32 354 int sd = createSocket(); 355 356 setTTL(sd); 357#endif 358 359 struct sockaddr_in6 addr; 360 unsigned char buf[66000]; 361#ifdef WIN32 362 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); 363#endif 364 for (;;) 365 { 366 socklen_t addrlenval=sizeof(addr); 367 int bytes; 368 369#ifdef WIN32 370 WSAPOLLFD fda[1]; 371 fda[0].fd = sd; 372 fda[0].events = POLLIN; 373 WSAPoll(fda, 1, 10); 374#endif 375 keep_retry_quick: 376 377 bytes = recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &addrlenval ); 378 if( !isICMPResponse(buf, bytes) ) continue; 379 380 // compare the sender 381 if( using_regular_ping && memcmp(&addr, &psaddr, addrlenval) != 0 ) continue; 382 383 // sizeof(packet.hdr) + 20 384 int offset = 0; 385 if(addr.sin6_family == AF_INET) // ipv4 386 { 387#ifdef __FreeBSD__ 388 offset = 48; 389#else 390 offset = 28; 391#endif 392 } 393 else // ipv6 394 { 395 offset = 8; 396 } 397 398 if ( bytes > 0 ) 399 display(buf + offset, bytes - offset ); 400 else 401 { 402 ERRM("Error: recvfrom failed"); 403 } 404 405 goto keep_retry_quick; 406 } 407 408 ERRM( "Fault on listen().\n" ); 409 exit( 0 ); 410} 411 412void singleping(struct sockaddr *addr, socklen_t addr_len ) 413{ 414 int cnt=1; 415 416#ifdef WIN32 417 { 418 //Setup windows socket for nonblocking io. 419 unsigned long iMode = 1; 420 ioctlsocket(sd, FIONBIO, &iMode); 421 } 422#else 423 if ( fcntl(sd, F_SETFL, O_NONBLOCK) != 0 ) 424 ERRM("Warning: Request nonblocking I/O failed."); 425#endif 426 427 double stime = OGGetAbsoluteTime(); 428 429 struct packet pckt; 430 431 { 432 int rsize = load_ping_packet( pckt.msg, sizeof( pckt.msg ) ); 433 memset( &pckt.hdr, 0, sizeof( pckt.hdr ) ); //This needs to be here, but I don't know why, since I think the struct is fully populated. 434#ifdef __FreeBSD__ 435 pckt.hdr.icmp_code = 0; 436 pckt.hdr.icmp_type = ICMP_ECHO; 437 pckt.hdr.icmp_id = pid; 438 pckt.hdr.icmp_seq = cnt++; 439 pckt.hdr.icmp_cksum = checksum((const unsigned char *)&pckt, sizeof( pckt.hdr ) + rsize ); 440#else 441 pckt.hdr.code = 0; 442 pckt.hdr.type = (psaddr.sin6_family == AF_INET) ? ICMP_ECHO : ICMP6_ECHO_REQUEST; 443 pckt.hdr.un.echo.id = pid; 444 pckt.hdr.un.echo.sequence = cnt++; 445 pckt.hdr.checksum = checksum((const unsigned char *)&pckt, sizeof( pckt.hdr ) + rsize ); 446#endif 447 448 int sr = sendto(sd, (char*)&pckt, sizeof( pckt.hdr ) + rsize , 0, addr, addr_len); 449 450 if( sr <= 0 ) 451 { 452 ping_failed_to_send = 1; 453 if( using_regular_ping ) 454 { 455 ERRMB("Ping send failed:\n%s (%d)\n", strerror(errno), errno); 456 } 457 } 458 else 459 { 460 ping_failed_to_send = 0; 461 } 462 } 463} 464 465void ping(struct sockaddr *addr, socklen_t addr_len ) 466{ 467 int cnt=1; 468 469 using_regular_ping = 1; 470#ifdef WIN32 471 { 472 //Setup windows socket for nonblocking io. 473 unsigned long iMode = 1; 474 ioctlsocket(sd, FIONBIO, &iMode); 475 } 476#else 477 if ( fcntl(sd, F_SETFL, O_NONBLOCK) != 0 ) 478 ERRM("Warning: Request nonblocking I/O failed."); 479#endif 480 481 double stime = OGGetAbsoluteTime(); 482 483 struct packet pckt; 484 do 485 { 486 int rsize = load_ping_packet( pckt.msg, sizeof( pckt.msg ) ); 487 memset( &pckt.hdr, 0, sizeof( pckt.hdr ) ); //This needs to be here, but I don't know why, since I think the struct is fully populated. 488#ifdef __FreeBSD__ 489 pckt.hdr.icmp_code = 0; 490 pckt.hdr.icmp_type = ICMP_ECHO; 491 pckt.hdr.icmp_id = pid; 492 pckt.hdr.icmp_seq = cnt++; 493 pckt.hdr.icmp_cksum = checksum((const unsigned char *)&pckt, sizeof( pckt.hdr ) + rsize ); 494#else 495 pckt.hdr.code = 0; 496 pckt.hdr.type = (psaddr.sin6_family == AF_INET) ? ICMP_ECHO : ICMP6_ECHO_REQUEST; 497 pckt.hdr.un.echo.id = pid; 498 pckt.hdr.un.echo.sequence = cnt++; 499 pckt.hdr.checksum = checksum((const unsigned char *)&pckt, sizeof( pckt.hdr ) + rsize ); 500#endif 501 502 int sr = sendto(sd, (char*)&pckt, sizeof( pckt.hdr ) + rsize , 0, addr, addr_len); 503 504 if( sr <= 0 ) 505 { 506 ping_failed_to_send = 1; 507 if( using_regular_ping ) 508 { 509 ERRMB("Ping send failed:\n%s (%d)\n", strerror(errno), errno); 510 } 511 } 512 else 513 { 514 ping_failed_to_send = 0; 515 } 516 517 if( precise_ping ) 518 { 519 double ctime; 520 do 521 { 522 ctime = OGGetAbsoluteTime(); 523 if( pingperiodseconds >= 1000 ) stime = ctime; 524 } while( ctime < stime + pingperiodseconds ); 525 stime += pingperiodseconds; 526 } 527 else 528 { 529 if( pingperiodseconds > 0 ) 530 { 531 uint32_t dlw = 1000000.0*pingperiodseconds; 532 OGUSleep( dlw ); 533 } 534 } 535 } while( pingperiodseconds >= 0 ); 536 //close( sd ); //Hacky, we don't close here because SD doesn't come from here, rather from ping_setup. We may want to run this multiple times. 537} 538 539void ping_setup(const char * strhost, const char * device) 540{ 541 pid = getpid(); 542 543#ifdef WIN32 544 WSADATA wsaData; 545 int r = WSAStartup(MAKEWORD(2,2), &wsaData); 546 if( r ) 547 { 548 ERRM( "Fault in WSAStartup\n" ); 549 exit( -2 ); 550 } 551#endif 552 553 554#ifdef WIN32 555 sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, WSA_FLAG_OVERLAPPED); 556 { 557 int lttl = 0xff; 558 if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*)<tl, sizeof(lttl)) == SOCKET_ERROR) 559 { 560 printf( "Warning: No IP_TTL.\n" ); 561 } 562 } 563#else 564 // resolve host 565 psaddr_len = sizeof(psaddr); 566 567 if( strhost ) 568 resolveName((struct sockaddr*) &psaddr, &psaddr_len, strhost, AF_UNSPEC); 569 else 570 psaddr.sin6_family = AF_INET; 571 572 sd = createSocket(); 573 574 setTTL(sd); 575 576 if(device) 577 { 578 if( setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) +1) != 0) 579 { 580 ERRM("Error: Failed to set Device option. Are you root? Or can do sock_raw sockets?\n"); 581 exit( -1 ); 582 } 583 } 584 585#endif 586 if ( sd < 0 ) 587 { 588 ERRM("Error: Could not create raw socket\n"); 589 exit(0); 590 } 591 592} 593 594#endif // WIN_USE_NO_ADMIN_PING 595 596 597void do_pinger( ) 598{ 599 ping((struct sockaddr*) &psaddr, psaddr_len ); 600} 601 602// used by the ERRMB makro from error_handling.h 603char errbuffer[1024]; 604