ping工具的C語言簡單實現 (ICMP echo)

_nMaple_發表於2015-07-20

Windows和Linux 平臺下都有一個使用非常頻繁的工具: ping

此工具主要是檢驗網路中兩節點間傳遞的資料包是否可達,計算耗時等,其原理是使用ICMP協議傳送echo,並得到目的主機的回顯。

以下是實現了最基本ping 功能的C程式,一共有三個檔案:

icmphd.h  —— 定義報文結構

icmphd.c ——實現其宣告的函式

main.c —— 主函式入口


編譯環境:

作業系統: win7 sp1

CPU :x86_64

編譯器:

1) MinGW-GCC version 4.8.1 (tdm64-2)

2) Microsoft Visual Studio Ultimate 2012 version 11.0.50727.1 RTMREL

注:若使用的是MinGW-GCC,則編譯時連線庫: -lwsock32,否則因為編譯器對函式的預設呼叫規則不同,無法在Windows SDK中找到

匯出的符號(符號不匹配)。執行與除錯時使用管理員許可權,否則傳送時報錯,WinSock Error Code = 10013


原始碼:

icmphd.h

/*
  author : ez
  date : 2014/10/20
  describe : -
*/

#ifndef _ICMPHD_H_
#	define _ICMPHD_H_

#include <stdint.h>


#	define DEF_DATA_LEN    0x10
// random number
// #	define DEF_IDENTIFY		0x0003

#pragma pack (1)

struct iphd {
	uint8_t m_cVersionAndHeaderLen;
	uint8_t m_cTypeOfService;
	uint16_t m_sTotalLenOfPacket;
	uint16_t m_sPacketID;
	uint16_t m_sSliceinfo;
	uint8_t m_cTTL;
	uint8_t m_cTypeOfProtocol;
	uint16_t m_sCheckSum;
	uint32_t m_uiSourIp;
	uint32_t m_uiDestIp;
};

struct icmphd {
	uint8_t type;
	uint8_t code;
	uint16_t chksum;
	uint16_t identifier;
	uint16_t seqnum;    // big-endian
};

struct icmppk {
	struct icmphd hd;
	uint8_t data [DEF_DATA_LEN];
};

#pragma pack ()

#	define ICMPHD_SIZE     (sizeof (struct icmphd))
#	define ICMPPK_SIZE     (sizeof (struct icmppk))
#	define IPHD_SIZE		(sizeof (struct iphd))


extern struct icmppk* 
create_icmppk (uint8_t, uint8_t, uint16_t);


#endif // ~ _ICMPHD_H_

icmphd.c

/*
  author : ez
  date : 2014/10/20
  describe : -
*/
#include "icmphd.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

static uint16_t  
icmp_cal_cksum(uint8_t*, int);


struct icmppk*
create_icmppk (uint8_t _type, uint8_t _code, uint16_t _seq) {
	struct icmppk* pk = (struct icmppk*) malloc (ICMPPK_SIZE);
	if (pk != NULL) {
		int i = 0;
		memset (pk, 0, ICMPPK_SIZE);
		pk -> hd.type = _type;
		pk -> hd.code = _code;
		pk -> hd.identifier = 1;
		// big-endian
		((uint8_t*) (&(pk -> hd.seqnum))) [0] = (uint8_t) ((_seq & 0xff00) >> 8);
		((uint8_t*) (&(pk -> hd.seqnum))) [1] = _seq & 0x00ff;
		for (; i < DEF_DATA_LEN; i ++)   // random data
			pk -> data [i] = i + '0';
		pk -> hd.chksum = icmp_cal_cksum ((uint8_t*) pk, ICMPPK_SIZE);
	}
	return pk;
}


static uint16_t  
icmp_cal_cksum(uint8_t* _data,int _data_len) {
    int sum = 0;  
    int odd = _data_len & 0x01;  
    uint16_t* value = (uint16_t*) _data;  
    while (_data_len & 0xfffe) {  
        sum += *(uint16_t*) _data;  
        _data += 2;
        _data_len -= 2;
    }
    if (odd) {
        uint16_t tmp = ((*_data) << 8) & 0xff00;  
        sum += tmp;  
    }  
    sum = (sum >> 16) + (sum & 0xffff);  
    sum += (sum >> 16);  
    return ~sum;  
}  

main.c

/* 
 author : ez 
 date : 2015/7/11
 describe : -
*/

#undef UNICODE

#include "icmphd.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <winsock2.h>
#include <time.h>

#if defined _WIN32 || defined _WIN64
#include <windows.h> 
#endif 

#if !defined MAKEWORD
#	define MAKEWORD(low,high) ((WORD)(((BYTE)(low)) | ((WORD)((BYTE)(high))) << 8))
#endif

#pragma comment (lib, "ws2_32.lib")

#	define ADDR_OFFSET   1
#	define println(___STR)     printf ("%s\n", ___STR)

uint16_t sequence = 0;
time_t start = 0, end = 0;
uint32_t ip_num = 0;
char* ip_char = NULL;
struct icmppk* send_data;
uint8_t* rcv_data;
SOCKET sockfd = INVALID_SOCKET;
struct sockaddr_in local;

static struct icmppk*
request_echo_icmp (/*const char* */);

static void
dispose_resources ();

static void
parse_reply_echo_icmp (struct icmppk*, void*);

int 
ipstr_to_numeric (const char*, int *);

static
void usage_disp () {
	printf ("Usage: ping <ip_address>\n");
}


static struct icmppk*
request_echo_icmp (/*const char* _dst_addr */) {
	uint16_t ip_numeric = 0;
	struct icmppk* hd = NULL;
	 // create icmp request
	hd = create_icmppk (8, 0, ++ sequence);
	return hd;
}

static void
rcv_parse_reply_echo_icmp () {
	int rcv_len = 0;
	int frm_len = ICMPPK_SIZE;
	while (1) {
		rcv_len = recvfrom (sockfd, (char*) rcv_data, 0x100, 0,
			(struct sockaddr*) &local, &frm_len);
		if (rcv_len > 0) {
			struct icmppk* ptr = (struct icmppk*) (((uint8_t*) rcv_data) + IPHD_SIZE);
			if (((struct iphd*) rcv_data) -> m_uiSourIp == ip_num &&
				((struct iphd*) rcv_data) -> m_cTypeOfProtocol == IPPROTO_ICMP) {
				end = time (NULL);
				printf ("Reply from %s: bytes = %d, time = %d nm, req = %d, TTL = %d\n", 
					ip_char,
					DEF_DATA_LEN, 
					difftime (end, start),
					ntohs (ptr -> hd.seqnum),
					((struct iphd*) rcv_data) -> m_cTTL);
				start = end = 0;
#ifdef  _MSC_VER
				Sleep (1000);
#elif  __GNUC__
				sleep (1);
#endif
				break;
			} else 
				continue;
		} else /*if (rcv_len == 0)*/
			continue;
		// else  
			// break;
	}
}

static void
display_frame (struct icmppk* _pk, int _len) {
	int i = 0;
	for (; i < _len; i ++)
		printf ("0x%02x ", ((uint8_t*) _pk) [i]);
}

int 
ipstr_to_numeric (const char* _str, int * _addr) {
	const char* index;
	unsigned char* addr = (unsigned char*) _addr;
	int isnumeric = 1;
	int i = 3;
	*_addr = 0;

	index = _str;
	while  ((*index) && (isnumeric))  {
		if (isdigit  ((unsigned char) *index))
			addr [i] = addr [i] * 10 + (*index  - '0');  // big-endian
		else if (*index == '.') {
			i --;
			if (i == -1) isnumeric = 0;
		} else 
			isnumeric = 0;
		index ++;
	}

	if (isnumeric && i)  return -1;   // error
	if (isnumeric)  return 0; // successful
}


int 
main (int argc, char *argv []) {  
      
    WSADATA wsaData;
    struct sockaddr_in hints;
	int remte_addr = 0;
	int timeout = 1000;
    int res;
	
	if (argc < 2) {
		usage_disp ();
		return 0;
	}
  
    // Initialize Winsock
    res = WSAStartup (MAKEWORD (2,2), &wsaData);  
	
    if (res != 0) {
        printf("WSAStartup fault, error: %d\n", res);
        return 1;
    }

	rcv_data = (uint8_t*) malloc (0x100);
	
    memset (&hints, 0, sizeof (struct sockaddr_in)); 
	memset (&local, 0, sizeof (struct sockaddr_in));
    hints.sin_family = AF_INET;
	hints.sin_addr.s_addr = ip_num 
						= inet_addr (ip_char = argv [ADDR_OFFSET]);
	
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = inet_addr ("127.0.0.1");
	  
    sockfd = socket (AF_INET, SOCK_RAW,
        IPPROTO_ICMP);
		
    if (sockfd == INVALID_SOCKET) {
        printf ("Socket fault, error: %ld\n", WSAGetLastError ());  
        WSACleanup ();
		dispose_resources ();
        return 1;
    }
	
	printf ("Ping host %s\n", ip_char);
  
	while (1) {
	
		send_data = request_echo_icmp ();
		start = time (NULL);
		// send data to server
		res = sendto (sockfd, (const char*) send_data, ICMPPK_SIZE, 0,
			(struct sockaddr*) &hints, sizeof (hints));
		if (res == SOCKET_ERROR) {
			printf ("Send Error : %d\n", WSAGetLastError ());
			closesocket (sockfd);
			WSACleanup ();
			dispose_resources ();
			return 1;
		}

		// receive from remote
		rcv_parse_reply_echo_icmp ();
	}
  
    // shutdown (sockfd, SD_BOTH);
    closesocket (sockfd);
    WSACleanup(); 
	dispose_resources ();
    
    return 0;  
}

static void
dispose_resources () {
	rcv_data && ( free (rcv_data), (rcv_data = NULL));
	send_data && ( free (send_data), (send_data = NULL));
}
當然因為寫的有點匆忙,很多地方還需要改進,本想做成跨平臺的(Windows與Linux),也只寫了一點點。還有輸入的內容沒有過濾等

很多問題,不過算是基本能用,下圖是我擷取的,在我的電腦上執行的結果。歡迎提出寶貴的意見。


相關文章