ICMP,原始套接字,ping实现

时间:2021-05-14 10:59:57

ICMP学习笔记

          最近一直在看网络编程方面的书,对ping命令有了一些认识,在此写下来,已帮助自己记忆,同时也和大家一起分享学习。

         

    用一个ping实例来讲解ICMP协议。Ping命令是测试到另一端的线路是否能连通。

首先了解一下Ping命令的过程:

1.Ping命令加上一个主机地址或者域名

2.等待对方主机应答。

3.统计发送接收次数,是否有丢包。

这样一个ping过程就完成了。但是它是如果实现的呢。首先我们需要了解ICMP协议,因为ping其实就是遵循ICMP协议的。

Internet Control Message Protocol(ICMP)译为:互联网控制报文协议。ICMP不能单独传输,它需要封装在IP数据报里。如下图所示

      ICMP,原始套接字,ping实现

    发送ICMP数据报需要填充ICMP头部信息,头部+发送的数据部分组成一个ICMP报文。填充头部的意义是设置你想要的结果。ICMP头部占8个字节。类型一个字节,代码一个字节,校验和两个字节,标识符两个字节,序列号两个字节。类型和代码的组合可以表达不同的意义。这里我们会用到类型8代码0表示的是ECHO请求。当对方主机返回是类型为0,代码0表示ECHO应答。IP首部占20个字节下面给出ICMP头部和IP头部的定义。

    IP头部定义:

             
typedef struct IPHeader {
UCHAR iph_verlen; // Version and length 版本和首部长度
UCHAR iph_tos; // Type of service 服务类型
USHORT iph_length; // Total datagram length 总长度
USHORT iph_id; // Identification 标识符
USHORT iph_offset; // Flags, fragment offset 片偏移
UCHAR iph_ttl; // Time to live 生存时间
UCHAR iph_protocol; // Protocol 协议
USHORT iph_xsum; // Header checksum 首部校验和
ULONG iph_src; // Source address 源IP地址
ULONG iph_dest; // Destination address目的IP地址
}IPHeader;

    ICMP头部定义:

typedef struct ICMPHeader{
UCHARtype;//类型
UCHARcode;//代码
USHORT checknum;//校验和
USHORTid;//标识符
USHORT seq;//序列号

/* 这之后的不是标准 ICMP 首部, 用于记录时间 */
ULONG timestamp;//记录ping时间
}ICMPHeader;

    下面我们就要填充ICMP头部信息

/**************************************************************************
*
* 函数功能: 构造 ICMP 数据.
*
* 参数说明: [IN, OUT] icmp_data, ICMP 缓冲区;
* [IN] data_size, icmp_data 的长度;
* [IN] sequence, 序列号.
*
* 返 回 值: void.
*
**************************************************************************/
void Ping ::file_icmp_header(char *icmp_data, int data_size, int sequence)
{
ICMPHeader *icmp_hdr;

icmp_hdr = (ICMPHeader*)icmp_data;

icmp_hdr->type = ICMP_TYPE_ECHO;//这里就表示我们要请求ECHO
icmp_hdr->code = 0;
icmp_hdr->id = (USHORT)GetCurrentProcessId();
icmp_hdr->seq = sequence;

icmp_hdr->timestamp = GetTickCount();

icmp_hdr->checknum = ip_checksum((unsigned short*)icmp_data,data_size);
}
     icmp_hdr->id = (USHORT)GetCurrentProcesId();这里的ID是唯一的标识符,他的作用是从应答中找到属于我发起的请求应答,是什么意思呢,一个网络地址可能同时有多个人去PING一台主机,那么主机返回的数据谁去接受呢?其实就是用ID来决定的,比如A、B两个用户,他们都ping同一台主机,A的ID = 1 B的ID = 2;那么主机返回的时候这个ID值是不会改变的,A判断ID == 1 就表示我返回我要接受的数据,同理B判断ID == 2,就是这个意思。

     icmp_hdr->timestamp = GetTickCount();主要是用来记录请求应答的时间,当发送ECHO请求时记录发送时间,当接受到应答数据时,在用GetTickcount()- icmp_hdr->itmestamp这样就能得到请求应答需要多少时间了。

     发送ICMP数据使用原始套接字,用sendto(···)函数,函数的接受可以查看MSDN关于本文的所有API函数都可以通过MSDN查看,多查看MSDN对我们提高英文能力,和学习能力都有很好的帮助,学习编程一定要多查看文档,遇到API先看文档,如果不是很明白在去百度,google这样会好一些。

     下面是接受到ECHO应答后解析数据的过程:

/**************************************************************************
*
* 函数功能: 解析接收到的数据.
*
* 参数说明: [IN] buf, 数据缓冲区;
* [IN] buf_len, buf 的长度;
* [IN] from, 对方的地址.
*
* 返 回 值: 成功返回 0, 失败返回 -1.
*
**************************************************************************/
int Ping::icmp_parse_reply(char *buf, int buf_len,struct sockaddr_in *from)
{
struct IPHeader *ip_hdr;
struct ICMPHeader *icmp_hdr;
USHORT ip_hdr_len;
USHORT icmp_hdr_len;
int icmp_len;
ip_hdr = (IPHeader*)buf;
ip_hdr_len = (ip_hdr->iph_verlen&0xf)<<2;//IP头部长度
icmp_hdr_len = sizeof(ICMPHeader) - sizeof(ULONG);//ICMP头部长度
if (buf_len < ip_hdr_len + icmp_hdr_len)
{
printf("[Ping] Too few bytes from %s\n", inet_ntoa(from->sin_addr));
return -1;
}

icmp_hdr = (ICMPHeader*)(buf + ip_hdr_len);//指向ICMP头部
icmp_len = ntohs(ip_hdr->iph_length) - ip_hdr_len;//ICMP报文长度,包含头部;使用IP报文的总长度减去头部长度得来。(ICMP报文发送是需要IP头部的,IP报文只有头部没有数据,说以可以得到ICMP报文长度)

//检查校验和
if (ip_checksum((unsigned short*)icmp_hdr,icmp_len))
{
printf("[Ping] icmp checksum error!\n");
return -1;
}
//检查应答类型
if (icmp_hdr->type != ICMP_TYPE_ECHO_REPLY)
{
printf("[Ping] not echo reply : %d\n", icmp_hdr->type);
return -1;
}

//检查ID
if (icmp_hdr->id !=(USHORT)GetCurrentProcessId())
{
printf("[Ping] someone else's message!\n");
return -1;
}

USHORT trip_t = GetTickCount() - icmp_hdr->timestamp;
buf_len = ntohs(ip_hdr->iph_length) - ip_hdr_len - icmp_hdr_len;//返回的数据长度
printf("%d Bytes from %s",buf_len,inet_ntoa(from->sin_addr));
printf(" icmp_seq = %d time: %d ms\n",icmp_hdr->seq, trip_t);


return 0;
}


      下面是接受数据:

/**************************************************************************
*
* 函数功能: 接收数据, 处理应答.
*
* 参数说明: [IN] icmp_soc, 套接口描述符.
*
* 返 回 值: 成功返回 0, 失败返回 -1.
*
**************************************************************************/
int Ping::icmp_process_reply(SOCKET icmp_soc)
{
struct sockaddr_in from_addr;
int result;
char *recvbuf;
int from_len = sizeof(from_addr);
recvbuf = (char*)malloc(1024);
result = recvfrom(icmp_soc,recvbuf,strlen(recvbuf),0,(struct sockaddr*)&from_addr,&from_len);

if (SOCKET_ERROR == result)
{
if (WSAGetLastError() == WSAETIMEDOUT)
{
printf("time out!\n");
}
else
{
printf("recv data fail\n");
}
return WSAGetLastError();
}
result = icmp_parse_reply(recvbuf, result, &from_addr);
free(recvbuf);
return 0;
}
     下面是发送请求数据:

int main()
{
int result;
int Error_code;
WSADATA wsadata;
unsigned long ip_addr;
char *host_addr = "www.sina.com";
struct hostent *host_ent = NULL;
SOCKET icmp_soc;
struct sockaddr_in dest_addr;
int seq_no = 1;
result =WSAStartup(MAKEWORD(2,2),&wsadata);

if (0 != result)
{
Error_code = WSAGetLastError();
return Error_code;
}

icmp_soc = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);

if (SOCKET_ERROR == icmp_soc)
{
printf("Create socket fail!");
Error_code = WSAGetLastError();
return Error_code;
}


ip_addr = inet_addr(host_addr);
if (INADDR_NONE == ip_addr)
{
host_ent = gethostbyname(host_addr);
if (!host_ent)
{
printf("[PING] Fail to resolve %s\n", host_addr);
Error_code = WSAGetLastError();
return Error_code;
}
memcpy(&ip_addr,host_ent->h_addr_list[0],host_ent->h_length);
}
//else
{
memset(&dest_addr,0,sizeof(sockaddr_in));

dest_addr.sin_family = AF_INET;
//dest_addr.sin_port = htons()
dest_addr.sin_addr.s_addr = ip_addr;
}


int TimeOut = 1000;
//设置发送超时时间

result = setsockopt(icmp_soc,SOL_SOCKET,SO_SNDTIMEO,(char*)&TimeOut,sizeof(TimeOut));

if (SOCKET_ERROR == result)
{
printf("Set SendTime fail!");
Error_code = WSAGetLastError();
return Error_code;
}
//设置接收超时时间
result = setsockopt(icmp_soc,SOL_SOCKET,SO_RCVTIMEO,(char*)&TimeOut,sizeof(TimeOut));

if (SOCKET_ERROR == result)
{
printf("Set RecvTime fail!");
Error_code = WSAGetLastError();
return Error_code;
}

// //设置DontLinger 消除TIME_WAIT
// BOOL bDontLinger = FALSE;
//
// result = setsockopt(icmp_soc,SOL_SOCKET,SO_DONTLINGER,(char*)&bDontLinger,sizeof(bDontLinger));
// if (SOCKET_ERROR == result)
// {
// printf("Set DontLinger Fail!");
// Error_code = WSAGetLastError();
// return Error_code;
// }

if (host_ent)
printf("Ping %s [%s] with %d bytes data\n", host_addr,inet_ntoa(dest_addr.sin_addr), strlen(host_addr));
else
printf("Ping [%s] with %d bytes data\n", inet_ntoa(dest_addr.sin_addr),strlen(host_addr));

char *icmp_data = NULL;
int data_size = 32 + sizeof(Ping::ICMPHeader);
int send_len;
icmp_data = (char*)malloc(1024);
Ping ping;
for (int index = 0; index < 3; index++)
{
ping.file_icmp_header(icmp_data,data_size,seq_no++);

send_len = sendto(icmp_soc,icmp_data,data_size,0,(struct sockaddr*)&dest_addr,sizeof(dest_addr));

if (SOCKET_ERROR == send_len)
{
Error_code = WSAGetLastError();
if (Error_code == WSAETIMEDOUT)
{
printf("[PING] sendto is timeout\n");
continue;
}
printf("Send to data fail!");
break;
}
result = ping.icmp_process_reply(icmp_soc);
}


return 0;
}

       就讲到这里了,希望能和大家共同分享,当然有不足的地方也可以补充和交流。下面是一个Demo,实在没有分了,希望大家谅解

http://download.csdn.net/detail/sanshao1314/6453295