1、 ping程序分析报告 姓 名: 班 级: 学 号: 目录 1、Ping功能简介 …………………………………………………1 2、程序流程图 (1)主函数流程图 …………………………………………………3 (2)创建套接字流程图 ……………………………………………3 (3)建立IP选项头部流程图 ……………………………………4 (4)创建SockRaw套接字的接收/发送时限属性
2、流程图…………4 (5)判断终端的主机名获取信息流程图 …………………………5 (6)分配堆内存流程图 ……………………………………………5 (7)接收/发送ICMP数据包流程图 ………………………………6 (8)清空Socket库所占内存 ……………………………………7 (9)传参解析函数流程图 …………………………………………7 (10)解析IP选项函数流程图 ……………………………………8 3、源代码清单 ……………………………………………………8 4、心得体会 ………………………………………………………16 1、ping功能简介 Ping
3、 是DOS命令,一般用于检测网络通与不通 ,也叫时延,其值越大,速度越慢 PING (Packet Internet Grope),因特网包探索器,用于测试网络连接量的程序。Ping发送一个ICMP回声请求消息给目的地并报告是否收到所希望的ICMP回声应答。 它是用来检查网络是否通畅或者网络连接速度的命令。作为一个生活在网络上的管理员或者黑客来说,ping命令是第一个必须掌握的DOS命令,它所利用的原理是这样的:网络上的机器都有唯一确定的IP地址,我们给目标IP地址发送一个数据包,对方就要返回一个同样大小的数据包,根据返回的数据包我们可以确定目标主机的存在,可以初步判断目标主机的操作系统
4、等。 Ping 是Windows系列自带的一个可执行命令。利用它可以检查网络是否能够连通,用好它可以很好地帮助我们分析判定网络故障。应用格式:Ping IP地址。该命令还可以加许多参数使用,具体是键入Ping按回车即可看到详细说明。 ping指的是端对端连通,通常用来作为可用性的检查, 但是某些病毒木马会强行大量远程执行ping命令抢占你的网络资源,导致系统变慢,网速变慢。 2、程序流程图 ①请求版本信息,创建一个套接口 ②建立IP选项首部 ③创建SockRaw套接字的接收/发送时限属性
5、 失败 成功 退出main函数,并返回一个值-1 调用memset函数 ④判断终端的主机名获取信息 ⑤创建ICMP数据包,分配堆内存 ⑥接收/发送ICMP数据包 ⑦清空Socket库所占内存 图1 main函数流程图 调用WSAStartup()函数请求版本信息 判断其返回值 返回值为0,即执行 返回值为非0,即执
6、行 失败 成功 退出main函数,并返回一个值2 调用传参解析函数 用socket函数创建一个套接口,设置属性 创建失败 创建成功 退出main函数,并返回一个值3 进入main函数步骤②
7、图2创建套接字流程图 bRecordRoute TRUE FALSE 调用setsockopt()函数,并判断其返回值 返回值为 返回值为一个 进入main函数步骤 SOCKET_ERROR
8、 IP_OPTIONS ③ 的属性 获得当前错误的进程号, 进入main函数步骤③ 并输出错误信息 图3 建立IP选项头部流程图 调用setsockopt()函数设置套接字接收时限属性,判断其返回值 设置失败 设置成功 取
9、得错误的进程号,输出错误信息 timeout=1000; 退出main 函数 调用setsockopt()函数设置套接字发送时限属性,判断其返回值 设置失败 设置成功 取得错误的进程号,输出错误信息 调用memset()函数填充数据段 退出main 函数 图4 创建SockRaw套接字的接收/发送时限属性流程图
10、 将点分十进制地址转化为二进制地址 将该地址赋给dest.sin_addr.s_addr 无效地址 判断其值 有效地址 通过主机名获取主机信息 为NULL 不是NULL 取得错误的进程号 ,输出错误信息, 输出主机信息 退出main 函数 进入main函数步骤⑤
11、 图5判断终端的主机名获取信息流程图 利用HeapAlloc()分配堆内存函数 分配成功 分配失败 调用memset()函数填充数据段 输出堆内存分配失败 推出主函数 调用ICMP首部初始化函数 返回值-1 图6 分配
12、堆内存流程图 nCount=0 nCount++ nCount++==4 break 调用cksum 发送ICMP数据包 失败 成功 超时 bwrote < datasize Y N N Y 输出 退出 输出错误
13、超时 函数 信息 sendto bread 失败 成功 超时 y n 调用 ICMP解析 输出 退出 超时 sleep(1000) 信息 Sleep()
14、 图7 接收/发送ICMP数据包流程图 套接口是否有效 有效 无效 关闭套接口 利用HeapFree()函数释放堆 释放SOCKET库所占的系统资源 返回值0,退出主函数 图8清空Socket库所占内存 i=1
15、
i 16、
YES NO
将数组argv 第i行第1列 判断数组argv第i行第0列
的字符转化为小写字母 的字符是否为数字
转化 17、后为 其他字母 YES NO
‘r’
bRecordRoute 调用usage()函数 将数组argv第i 将数组argv第i
=TRUE; ;退出; 行的字符转化为 行的字符赋给
退出; 18、 长整型数赋给datasize Ipdest
i++
图9 传参解析函数流程图
i=0
计算RR选项中记录的地址个数n
i 19、 通过IP地址获得主机信息
赋 给host
host
真 假
输出主机名和地址 输出地址
return
i++
20、
图10解析IP首部函数流程图
3、源代码清单
// Module Name: Ping.c
#include "windows.h"
#include "winsock.h"
#include "stdio.h"
#define IP_RECORD_ROUTE 0x7 //IP记录路由
#define ICMP_ECHO 8 //ICMP回显
#define ICMP_EC 21、HOREPLY 0 //ICMP回显应答
#define ICMP_MIN 8 //ICMP数据包最小长度
#define DEF_PACKET_SIZE 32 //差错报文长度
#define MAX_PACKET 0x10000 // ICMP包最大长度
#define MAX_IP_HDR_SIZE 60 //IP首部最大字节数
//IP头文件定义
typedef struct _iphdr
{
unsigned int h_len:4; //头 22、部长度 4字节
unsigned int version:4; //IP版本号 IPv4
unsigned char tos; //服务类型
unsigned short total_len; //数据包总长度
unsigned short ident; //ID标识
unsigned short frag_and_flags; //3位标志,13位片偏移
unsigned char ttl; //生存期
unsigned char proto; //协议类型
unsigned short checksum; //IP头部的检验和
unsi 23、gned int sourceIP; //源地址
unsigned int destIP; //目的地址
} IpHeader;
//ICMP头部定义
typedef struct _icmphdr
{
BYTE i_type; //ICMP类型(8位)
BYTE i_code; //代码类型(8位)
USHORT i_cksum; //头部及数据检验和(16位)
USHORT i_id;ID//标识
USHORT i_seq; //序列号
ULONG timestamp; //时间戳
} IcmpHeader;
//I 24、P选项首部定义
typedef struct _ipoptionhdr
{
unsigned char code; //IP选项的类型
unsigned char len; //RR选项总字节长度
unsigned char ptr; //指针字段
unsigned long addr[9]; // IP地址清单
} IpOptionHeader;
BOOL bRecordRoute;
int datasize;
char *lpdest;
//定义3个全局变量
//使用信息
void usage(char *progname)
{
print 25、f("usage: ping -r [data size]\n");
printf(" -r record route\n");
printf(" host remote machine to ping\n");
printf(" datasize can be up to 0x10000 Byte\n");
ExitProcess(-1); //结束进程
}
//ICMP首部初始化
void FillICMPData(char *icmp_data, int datasize)
{
IcmpHeader *icmp_hdr = NULL;
char *da 26、tapart = NULL; //指针定义及初始化
icmp_hdr = (IcmpHeader*)icmp_data;
icmp_hdr->i_type = ICMP_ECHO; //ICMP回显请求
icmp_hdr->i_code = 0;
icmp_hdr->i_id = (USHORT)GetCurrentProcessId();//取得当前进程号
icmp_hdr->i_cksum = 0; //检验和字段置0
icmp_hdr->i_seq = 0;
datapart = icmp_data + sizeof(IcmpHeader); //dat 27、apart指针指向数据报文开头
memset(datapart, 'E', datasize - sizeof(IcmpHeader)); //填充数据段
}
//计算检验和
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum = 0; // 检验和字段置0
while (size > 1)
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if (size)
{
cksum += *(UCHAR*)buffer; 28、
}
cksum = (cksum >> 16) + (cksum & 0xffff);//将检验和字段高16位右移16位再与低16位相加
cksum += (cksum >>16);//将所加的检验和再与剩余低16位相加
return (USHORT)(~cksum);//检验和取反,并返回
}
//解析IP选项
void DecodeIPOptions(char *buf, int bytes)
{
IpOptionHeader *ipopt = NULL;
IN_ADDR inaddr; //声明结构体
int i;
HOSTENT *h 29、ost = NULL;
ipopt = (IpOptionHeader *)(buf + 20); //去掉IP首部,指针指向数据选项首部
printf("RR: ");
for(i = 0; i < (ipopt->ptr / 4) - 1; i++)
{
inaddr.S_un.S_addr = ipopt->addr[i];
if (i != 0)
{
printf(" ");
}
host = gethostbyaddr((char *)&inaddr.S_un.S_addr, sizeof(inaddr.S_un.S_addr), 30、 AF_INET);//通过IP地址获得主机信息
if (host)
{
printf("(%-15s) %s\n", inet_ntoa(inaddr), host->h_name);
//打印IP地址和主机名
}
else
{
printf("(%-15s)\n", inet_ntoa(inaddr)); //打印IP地址
}
}
return;
}
//解析ICMP首部函数
void DecodeICMPHeader(char *buf, int bytes, struct sockaddr_in *from)
31、{
IpHeader *iphdr = NULL;
IcmpHeader *icmphdr = NULL;
unsigned short iphdrlen;
DWORD tick; //毫秒级数
static int icmpcount = 0;
iphdr = (IpHeader *)buf;
iphdrlen = iphdr->h_len * 4; //IP首部实际长度
tick = GetTickCount();//获得毫秒级数
if ((iphdrlen == MAX_IP_HDR_SIZE) && (!icmpcount)) //判断是否为一个I 32、P数据包
{
DecodeIPOptions(buf, bytes); //调用IP 选项解析函数
}
if (bytes < iphdrlen + ICMP_MIN)判断IP数据包长度
{
printf("Too few bytes from %s\n",
inet_ntoa(from->sin_addr));
}
icmphdr = (IcmpHeader*)(buf + iphdrlen); //指针指向ICMP报文首部
if (icmphdr->i_type != ICMP_ECHOREPLY) //判断ICMP类型是否为ICMP回显应答 33、
{
printf("nonecho type %d recvd\n", icmphdr->i_type); //输出其类型
return;
}
if (icmphdr->i_id != (USHORT)GetCurrentProcessId())//获得当前进程的ID,判断是否为ICMP标识
{
printf("someone else's packet!\n");
return ;
}
printf("%d bytes from %s:", bytes, inet_ntoa(from->sin_addr));
printf(" icmp_ 34、seq = %d. ", icmphdr->i_seq); //输出ICMP序列号
printf(" time: %d ms", tick - icmphdr->timestamp); //打印时间戳
printf("\n");
icmpcount++;
return;
}
//传参解析函数
void ValidateArgs(int argc, char **argv)
{
int i;
bRecordRoute = FALSE;赋初值
lpdest = NULL;
datasize = DEF_PACKET_SIZE; //初始化datasize 35、使其等于差错报文长度
for(i = 1; i < argc; i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/')) //判断数组argv第i行第0列是否为‘-’或‘/’
{
switch (tolower(argv[i][1])) //将数组argv第i行第1列的字符转化为小写字母
{
case 'r': //记录路由选项
bRecordRoute = TRUE;
break;
default:
usage(argv[0]);调用usage函数 36、
break;
}
}
else if (isdigit(argv[i][0])) //判断数组argv第i行第0列是否为数字
{
datasize = atoi(argv[i]); //将数组argv第i 行的字符转化成长整型数
}
else
{
lpdest = argv[i];
}
}
}
//main函数
int main(int argc, char **argv) // argc代表命令行中的参数个数,* *argv 是指向字符串的指针
{
char *icmp_data = NULL;
37、
char *recvbuf = NULL;
USHORT seq_no = 0;
//定义及初始化
struct sockaddr_in dest = {'\0'};
struct sockaddr_in from= {'\0'};
struct hostent *hp = NULL;
//声明结构体
int bread = 0;
int ret = 0;
int fromlen = sizeof(from);
int timeout = 1000;
unsigned int addr = 0;
//定义及初始化
WSADATA wsaDa 38、ta; //声明数据结构
SOCKET sockRaw = INVALID_SOCKET;
IpOptionHeader ipopt = {'\0'};
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)//指明程序请求使用的socket版本,返回请求的版本信息
{
printf("WSAStartup() failed: %d\n", GetLastError());//获得当前的进程错误号,并输出“请求版本信息失败”
return 2;
}
ValidateArgs(argc, argv);//调用传参解析函 39、数
sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);//创建sockRaw套接字
if (sockRaw == INVALID_SOCKET)//创建失败
{
printf("WSASocket() failed: %d\n", WSAGetLastError());
return 3;//返回值3,并退出main函数
}
if (bRecordRoute)
{
ZeroMemory(&ipopt, sizeof(ipopt));
ipopt.code = IP_RECORD_ROUTE; 40、
ipopt.ptr = 4; //指向第一个地址列表
ipopt.len = 39;
ret = setsockopt(sockRaw, IPPROTO_IP, IP_OPTIONS,
(char *)&ipopt, sizeof(ipopt));// 设置sockRaw套接字属性
if (ret == SOCKET_ERROR)//设置属性失败
{
printf("setsockopt(IP_OPTIONS) failed: %d\n",
WSAGetLastError());//取得当前错误进程号,并输出“属性设置失败”
} 41、
}
bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO,
(char*)&timeout, sizeof(timeout));//设置套接字接收时限属性
if(bread == SOCKET_ERROR)//设置失败
{
printf("setsockopt(SO_RCVTIMEO) failed: %d\n",
WSAGetLastError());
return -1;返回值-1,并退出main函数
}
timeout = 1000;
bread = setsockopt(sockRaw 42、 SOL_SOCKET, SO_SNDTIMEO,
(char*)&timeout, sizeof(timeout));//设置套接字发送时限属性
if (bread == SOCKET_ERROR)//设置发送时限失败
{
printf("setsockopt(SO_SNDTIMEO) failed: %d\n",
WSAGetLastError());
return -1; 返回值-1,并退出main函数
}
memset(&dest, 0, sizeof(dest));//填充数据段,
dest.sin_family = AF_INET;// 43、指定地址族
if ((dest.sin_addr.s_addr = inet_addr(lpdest)) == INADDR_NONE)
//网络地址是无效的地址
{
if ((hp = gethostbyname(lpdest)) != NULL)
{
memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
dest.sin_family = hp->h_addrtype;将hp->h_addrtype的类型赋给dest.sin_family
printf("dest.sin_addr = %s\n 44、", inet_ntoa(dest.sin_addr));//输出IP地址
}
else
{
printf("gethostbyname() failed: %d\n",
WSAGetLastError());
return -1;退出main函数,并返回值-1
}
}
datasize += sizeof(IcmpHeader);
icmp_data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET);//分配堆内存
recvbuf = HeapAlloc( 45、GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET);
if (!icmp_data)//分配失败
{
printf("HeapAlloc() failed: %d\n", GetLastError());//获得当前错误进程号,输出“堆内存分配失败”
return -1; //退出main函数,并返回值-1
}
memset(icmp_data,0,MAX_PACKET);//填充数据段
FillICMPData(icmp_data,datasize);//调用ICMP 初始化函数
while(1)
{
46、 static int nCount = 0; //初始化
int bwrote;
if (nCount++ == 4)
{
break;结束整个循环
}
((IcmpHeader*)icmp_data)->i_cksum = 0;
((IcmpHeader*)icmp_data)->timestamp = GetTickCount();//设定时间戳
((IcmpHeader*)icmp_data)->i_seq = seq_no++;//设置ICMP头部序列号
((IcmpHeader*)icmp_data)->i_cksum =
47、
checksum((USHORT*)icmp_data, datasize);//调用计算检验和函数,将其返回值赋给i_cksum
bwrote = sendto(sockRaw, icmp_data, datasize, 0,
(struct sockaddr*)&dest, sizeof(dest)); //发送ICMP数据包
if (bwrote == SOCKET_ERROR) //发送失败
{
if (WSAGetLastError() == WSAETIMEDOUT) //当前错误进程号为超时
{
printf("time 48、d out\n");
continue; //结束本次循环
}
printf("sendto() failed: %d\n", WSAGetLastError());
return -1; //退出main函数,并返回值-1
}
if (bwrote < datasize)//已发送字节数小于datasize
{
printf("Wrote %d bytes\n", bwrote);
}
bread = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*) 49、from, &fromlen);// 接收ICMP数据包
if (bread == SOCKET_ERROR) //接收失败
{
if (WSAGetLastError() == WSAETIMEDOUT) //当前错误进程号为超时
{
printf("timed out\n");
continue; //结束本次循环
}
printf("recvfrom() failed: %d\n", WSAGetLastError());
return -1; 退出main函数,并返回值-1
}
DecodeICMP 50、Header(recvbuf, bread, &from); //调用解析ICMP头部函数
Sleep(1000);//暂停1000ms
}
if (sockRaw != INVALID_SOCKET) //套接字有效
{
closesocket(sockRaw); //关闭套接字
}
HeapFree(GetProcessHeap(), 0, recvbuf);
HeapFree(GetProcessHeap(), 0, icmp_data); //释放堆内存
WSACleanup();//应用程序完成对请求的Socket库的使用调用WSACleanu






