模拟ICMP过程

首先建立common头文件

//
// comm.h文件

// 包含一些公共函数

#ifndef __COMM_H__
#define __COMM_H__

// 校验和的计算
// 以16位的字为单位将缓冲区的内容相加,如果缓冲区长度为奇数,
// 则再加上一个字节。它们的和存入一个32位的双字中
USHORT	checksum(USHORT* buff, int size);

BOOL	SetTTL(SOCKET s, int nValue);
BOOL	SetTimeout(SOCKET s, int nTime, BOOL bRecv = TRUE);

#endif // __COMM_H__

然后建立数据包头头文件

//
// protoinfo.h文件

/*

定义协议格式
定义协议中使用的宏

 */
#include 
#ifndef __PROTOINFO_H__
#define __PROTOINFO_H__

#define ETHERTYPE_IP    0x0800
#define ETHERTYPE_ARP   0x0806

typedef struct _ETHeader         // 14字节的以太头
{
	UCHAR	dhost[6];			// 目的MAC地址destination mac address
	UCHAR	shost[6];			// 源MAC地址source mac address
	USHORT	type;				// 下层协议类型,如IP(ETHERTYPE_IP)、ARP(ETHERTYPE_ARP)等
} ETHeader, *PETHeader;

#define ARPHRD_ETHER 	1

// ARP协议opcodes
#define	ARPOP_REQUEST	1		// ARP 请求	
#define	ARPOP_REPLY		2		// ARP 响应

typedef struct _ARPHeader		// 28字节的ARP头
{
	USHORT	hrd;				//	硬件地址空间,以太网中为ARPHRD_ETHER
	USHORT	eth_type;			//  以太网类型,ETHERTYPE_IP ??
	UCHAR	maclen;				//	MAC地址的长度,为6
	UCHAR	iplen;				//	IP地址的长度,为4
	USHORT	opcode;				//	操作代码,ARPOP_REQUEST为请求,ARPOP_REPLY为响应
	UCHAR	smac[6];			//	源MAC地址
	UCHAR	saddr[4];			//	源IP地址
	UCHAR	dmac[6];			//	目的MAC地址
	UCHAR	daddr[4];			//	目的IP地址
} ARPHeader, *PARPHeader;

// 协议
#define PROTO_ICMP    1
#define PROTO_IGMP    2
#define PROTO_TCP     6
#define PROTO_UDP     17

typedef struct _IPHeader		// 20字节的IP头
{
    UCHAR     iphVerLen;      // 版本号和头长度(各占4位)
    UCHAR     ipTOS;          // 服务类型 
    USHORT    ipLength;       // 封包总长度,即整个IP报的长度
    USHORT    ipID;			  // 封包标识,惟一标识发送的每一个数据报
    USHORT    ipFlags;	      // 标志
    UCHAR     ipTTL;	      // 生存时间,就是TTL
    UCHAR     ipProtocol;     // 协议,可能是TCP、UDP、ICMP等
    USHORT    ipChecksum;     // 校验和
    ULONG     ipSource;       // 源IP地址
    ULONG     ipDestination;  // 目标IP地址
} IPHeader, *PIPHeader; 

// 定义TCP标志
#define   TCP_FIN   0x01
#define   TCP_SYN   0x02
#define   TCP_RST   0x04
#define   TCP_PSH   0x08
#define   TCP_ACK   0x10
#define   TCP_URG   0x20
#define   TCP_ACE   0x40
#define   TCP_CWR   0x80

typedef struct _TCPHeader		// 20字节的TCP头
{
	USHORT	sourcePort;			// 16位源端口号
	USHORT	destinationPort;	// 16位目的端口号
	ULONG	sequenceNumber;		// 32位序列号
	ULONG	acknowledgeNumber;	// 32位确认号
	UCHAR	dataoffset;			// 高4位表示数据偏移
	UCHAR	flags;				// 6位标志位
								//FIN - 0x01
								//SYN - 0x02
								//RST - 0x04 
								//PUSH- 0x08
								//ACK- 0x10
								//URG- 0x20
								//ACE- 0x40
								//CWR- 0x80

	USHORT	windows;			// 16位窗口大小
	USHORT	checksum;			// 16位校验和
	USHORT	urgentPointer;		// 16位紧急数据偏移量 
} TCPHeader, *PTCPHeader;

typedef struct _UDPHeader
{
	USHORT			sourcePort;		// 源端口号		
	USHORT			destinationPort;// 目的端口号		
	USHORT			len;			// 封包长度
	USHORT			checksum;		// 校验和
} UDPHeader, *PUDPHeader;

#endif // __PROTOINFO_H__

common.cpp

//
// comm.cpp文件

#include 

#include "Ws2tcpip.h"

#include "common.h"
#include 
USHORT checksum(USHORT* buff, int size)
{
	unsigned long cksum = 0;
	while(size>1)
	{
		cksum += *buff++;
		size -= sizeof(USHORT);
	}
	// 是奇数
	if(size)
	{
		cksum += *(UCHAR*)buff;
	}
	// 将32位的chsum高16位和低16位相加,然后取反
	cksum = (cksum >> 16) + (cksum & 0xffff);
	cksum += (cksum >> 16);			
	return (USHORT)(~cksum);
}

BOOL SetTTL(SOCKET s, int nValue)
{
	int ret = ::setsockopt(s, IPPROTO_IP, IP_TTL, (char*)&nValue, sizeof(nValue));
	return ret != SOCKET_ERROR;
}

BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv)
{
	int ret = ::setsockopt(s, SOL_SOCKET, 
		bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime));
	return ret != SOCKET_ERROR;
}

最后是主文件,接受ICMP回送应答报文。type为0  code为0

///
// ping.cpp文件


#include "protoinfo.h"
#include "common.h"
#include 
#include 



typedef struct icmp_hdr
{
    unsigned char   icmp_type;		// 消息类型
    unsigned char   icmp_code;		// 代码
    unsigned short  icmp_checksum;	// 校验和

	// 下面是回显头
    unsigned short  icmp_id;		// 用来惟一标识此请求的ID号,通常设置为进程ID
    unsigned short  icmp_sequence;	// 序列号
    unsigned long   icmp_timestamp; // 时间戳
} ICMP_HDR, *PICMP_HDR;

int main()
{
	// 目的IP地址,即要Ping的IP地址
	char szDestIp[] = "115.25.217.12";	// 127.0.0.1
	WSAData w;
	if(WSAStartup(MAKEWORD(2,2),&w)==SOCKET_ERROR)
		return -1;
		// 创建原始套节字
	SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	int time=1500;
	setsockopt(sRaw,SOL_SOCKET,SO_RCVTIMEO,(char *)&time,sizeof(int));
	// 设置接收超时
	

	// 设置目的地址
	SOCKADDR_IN dest;
	dest.sin_family = AF_INET;
	dest.sin_port = htons(0);
	dest.sin_addr.S_un.S_addr = inet_addr(szDestIp);

	// 创建ICMP封包
	char buff[sizeof(ICMP_HDR) + 32];
	ICMP_HDR* pIcmp = (ICMP_HDR*)buff;

	// 填写ICMP封包数据,请求一个ICMP回显
	pIcmp->icmp_type = 8;	
	pIcmp->icmp_code = 0;
	pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();
	pIcmp->icmp_checksum = 0;
	pIcmp->icmp_sequence = 0;

	// 填充数据部分,可以为任意
	memset(&buff[sizeof(ICMP_HDR)], 'E', 32);
	
	// 开始发送和接收ICMP封包
	USHORT	nSeq = 0;
	char recvBuf[1024];
	SOCKADDR_IN from;
	int nLen = sizeof(from);
	while(TRUE)
	{
		static int nCount = 0;
		int nRet;

		// ping次数
		if(nCount++ == 1000)
			break;

		pIcmp->icmp_checksum = 0;
		pIcmp->icmp_timestamp = ::GetTickCount();
		pIcmp->icmp_sequence = nSeq++;
		pIcmp->icmp_checksum = checksum((USHORT*)buff, sizeof(ICMP_HDR) + 32);
		nRet = ::sendto(sRaw, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest));
		if(nRet == SOCKET_ERROR)
		{
			printf(" sendto() failed: %d /n", ::WSAGetLastError());
			return -1;
		}
		nRet = ::recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen);
		if(nRet == SOCKET_ERROR)
		{
			if(::WSAGetLastError() == WSAETIMEDOUT)
			{
				printf(" timed out/n");
				continue;
			}
			printf(" recvfrom() failed: %d\n", ::WSAGetLastError());
			return -1;
		}

		// 下面开始解析接收到的ICMP封包
		int nTick = ::GetTickCount();
		if(nRet < sizeof(IPHeader) + sizeof(ICMP_HDR))
		{
			printf(" Too few bytes from %s \n", ::inet_ntoa(from.sin_addr));
		}

		// 接收到的数据中包含IP头,IP头大小为20个字节,所以加20得到ICMP头
		// (ICMP_HDR*)(recvBuf + sizeof(IPHeader));
		ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + 20); 
		if(pRecvIcmp->icmp_type != 0)	// 回显
		{
			printf(" nonecho type %d recvd \n", pRecvIcmp->icmp_type);
			printf("should quit\n");
			return -1;
		}

		if(pRecvIcmp->icmp_id != ::GetCurrentProcessId())
		{
			printf(" someone else's packet! \n");
			printf("should quit\n");
			return -1;
		}
		
		printf("从 %s 返回 %d 字节:\n", inet_ntoa(from.sin_addr),nRet);
		printf(" 数据包序列号 = %d. \t", pRecvIcmp->icmp_sequence);
		printf(" 延时大小: %d ms\n", nTick - pRecvIcmp->icmp_timestamp);
		printf(" \n");

		// 每一秒发送一次就行了
		::Sleep(1000);
	}
	return 0;
}




你可能感兴趣的:(C++)