cnping

Minimal Graphical Ping Tool
git clone https://git.sinitax.com/cnlohr/cnping
Log | Files | Refs | Submodules | README | LICENSE | sfeed.txt

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*)&lttl, 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