[计算机]发现网络中的活动主机报告及源代码
目 录
一(课程设计目的…………………………………………………………………2 二. 课程设计要求…………………………………………………………………2 三(相关知识………………………………………………………………………2 四(课程设计分析…………………………………………………………………4 五(程序
图……………………………………………………………………7 六(程序运行结果截图……………………………………………………………10 七. 课程设计心得…………………………………………………………………10 八.附录:参考文献……………………………………………………………… 11
0
一(课程设计目的:
IP
的优点是简洁,但缺少差错控制和查询机制,而网际控制报文协议(ICMP)具有补充IP功能的作用。在网络管理中,常常要确定当前网络中处于活动状态的主机,这时可以通过使用ICMP的回送和回送响应消息来完成这项工作。本课程设计的目的就是编制程序,利用ICMP数据包,发现指定网段中的活动主机。通过课程设计,使学生更加熟悉ICMP报文的结构,对ICMP协议有更好的理解和认识。
二(课程设计要求:
设计程序,其功能是发送ICMP数据包,以获取指定网段中的活动主机,并将结果显示在
输出上。
程序的具体要求如下:
1)用命令行形式运行: scanhost Start_IP End_IP
其中scanhost为程序名;Start_IP为被搜索网段的开始IP地址;End_IP
为被搜索网段的
结束IP地址。
2)输出格式为:
活动主机1
活动主机2
……
三(相关知识:
编制程序前首先要对ICMP报文的格式有一定的了解,ICMP报文是在IP数据报内部传输的,其结构如图10-1所示:
IP数据报
IP首部 ICMP报文
ICMP报文的格式如图10-2所示:
0 7 8 15 16 31(位)
类型字段 代码字段 校验和字段
1
(不同类型和代码有不同内容)
所有报文的前4个字节都是一样的,但是其它字节则互不相同。其中类型字段可以有15个不同的值,以描述特定类型的ICMP报文,某些ICMP报文还使用代码字段的值来进一步描述不用的条件。按验和字段为2字节,校验的范围是整个ICMP报文。检验和是必须的,其计算方法与IP协议头部校验和的计算方法一样。
各种类型的ICMP报文如图10-3所示(ICMP报文类型),不同类型由报文中的类型字段和代码字段来共同决定。
类 型 代 码 描 述
0 0 回送响应(PING应答)
3 目的不可达
0 网络不可达
1 主机不可达
2 协议不可达
3 端口不可达
4 需要进行分片但设置了禁止分片比特
5 源主机选择路由失败
6 无法识别目的网络
7 无法识别目的主机
8 源主机被隔离
9 目的网络被禁止
10 目的主机被禁止
11 由于服务类型(TOS),网络不可达
12 由于服务类型(TOS),主机不可达
13 由于过滤,通信被强行禁止
14 主机越权
15 优先权终止生效
4 0 源端被关闭(基本流控制)
5 重定向
0 对网络重定向
1 对主机重定向
2 对服务类型和网络重定向
3 对服务类型和主机重定向
8 0 回送请求(PING请求)
9 0 路由器通告
10 0 路由器请求
11 超时
0 传输期间生存期减为0
1 数据报组装期间生存期减为0
2
12 参数问题
0 各种IP头部错误
1 缺少必须的选项
13 0 时间戳请求
14 0 时间戳应答
15 0 信息请求(已作废)
16 0 信息应答(已作废)
17 0 地址掩码请求
18 0 地址掩码应答
10-3 ICMP报文类型
本课程设计的目的是发现网络中的活动主机,就是使用ICMP的回送和回送响应消息发现网络中的活动主机,即Ping消息的请求和应答。那幺,发送的ICMP的数据包类型设置为回送请求(类型号为8)。
四(课程设计分析:
本程序使用原始套接字生成ICMP报文来进行活动主机的探查。这个程序使用的是回送请求与应答消息。程序的大致思想是把ICMP的数据包类型设置为回送请求,将它发送给网络上的一个IP地址,如果这个IP地址已经被占用的话,那幺使用位于这个IP地址的主机上的TCP/IP软件就能够接收到这个ICMP回送请求,从而返回一个ICMP回送响应(类型号为0)信息。信息封装在一个IP包中,我们需要解析该IP包,从中找到ICMP数据信息。相反,如果这个IP地址没有人使用,那幺发送的ICMP回送请求在设定的延时内就不可能得到响应。
在初始化原始套接字之后,本程序就要开始在一个IP网段内寻找活动主机。因为要寻找的主机可能很多,为节省时间可以采用多线程编程。下面接结合核心代码对程序的具体实现进行讲解,同时为使程序流程更加清晰,去掉了错误检查等保护性代码。
1.使用原始套接字
为了实现发送/监听ICMP报文,必须使用原始套接字,创建原始套接字的代码如下:
socket sockRaw;
sockRaw = WSAocket (AF_INET, sock_Raw, IPPROTO_ICMP, NULL, 0,
WSA_FLAG_OVERLAPPED);
在WSASocket函数中,我们使用IPPROTO_ICMP
示接收ICMP数据包,为了使
3
用发送超时设置(设置SO_RCVTIMEO或SO_SNDTIMEO),必须将标志位置为WSA_FLAG_OVERLAPPED。然后调用setsockopt函数设置读取延迟。
Int timeout=1000;
Setsockopt(sockRaw,SQL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(time
out);setsockopt(sockRaw,SQL_socket,SO_SNDTIMEO,(char*)&timeout,sizeof
(timeout))
在setsockopt函数中,sockRaw是之前创建的原始套接字,设置SQL_SOCKET表明使用基本套接字处理ICMP报文。设置SO_RCVTIMEO表示使用接收超时设置,SO_SNDTIMEO表示使用发送超时设置,在这里,超时时间均设置为1000ms。
2(定义IP头部和ICMP头部的数据结构
由于socket发送/捕获的是IP包,因此要分别定义IP头部的数据结构ICMP头部数据结构。
//IP报头的数据结构
typedef struct iphdr{
unsigned int headlen:4; //IP头长度
unsigned int version:4; //IP版本号
unsigned char tos; //服务类型
unsigned short totallen; //IP包总长度
unsigned short id;; //ID号
unsigned short flag; //标记
unsigned char ttl; //生存时间
unsigned char prot; //协议(UDP TCP)
nsigned short checksum; //校验和
unsigned int sourceIP; //源IP
unsigned int destIP; //目的IP
}IpHeader;
//ICMP头部的数据结构
typedef struct icmphdr{
BYTE type; //ICMP类型码,回送请求的类型码为8
BYTE code; //子类型码,保存与特定ICMP报文类型相关细节信息
USHORT checksum; //校验和
USHORT id; //ICMP报文ID号(一般用进程号作ID)
USHORT seq; //ICMP数据报的序列号
}IcmpHeader;
3(填充并发送回送请求类型的ICMP报文
为了使收到数据包的目的主机发送响应,我们需要向目的主机发送回送请求类型的ICMP报文。从图10-3中可知,回送请求的类型号为8。因此ICMP报文
4
的填充代码如下:
请求回送 #define ICNP_ECHO 8 //#define DEF_PACKET_SIZE 32 //缺省数据报长度 #define MAX_PACKET 1024 //最大数据块长度
char icmp_data[MAX_PACKET]; //ICMP数据报最大可能的长度 memset(icmp_data,0,MAX_PACKET); //将数据报清空初始化 int datasize=DEF_PACKET_SIZE; //ICMP数据报报文体的缺省长度 datasize+=sizeof(IcmpHeader); //再加上ICMP头部的长度 IcmpHeader*icmp_hdr:
Char *datapart;
Icmp_hdr = (IcmpHeader*)icmp_data; Icmp_hdr->type = ICMP_ECHO; //设置类型 Icmp_hdr->id = (USHORT)GetCurrentThreadId(); //设置其ID号为当前线程号 Datapart = icmp_data + sizeof(IcmpHeader); //计算出数据报的数据部分
填入数据 Memset(datapart,A,datasize-sizeof(IcmpHeader)); //
((IcmpHeader*)icmp_data)->seq= 0; //序列号为0 ((IcmpHeader*)icmp_data)->checksum = 0; //先将校验和置0 ((IcmpHeader*)icmp_data)->checksum = checksum((USHORT*)icmp_data,datasize);
checksum为校验和的函数,设校验和初值为0 ,然后对数据每16为求异或,结果取反,便得校验和。其代码如下:
USHORT checksum(USHORT *buffer, int size) 计算校验和 {
unsigned long cksum = 0;
while(size>1)
{
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size)
{
cksum += (UCHAR*)buffer;
}
cksum = (cksum >> 16)+(cksum & 0xffff); cksum +=(cksum >> 16);
return(USHORT)(-cksum);
}
填充ICMP报文之后,应在ICMP报文之前加上IP报头并发送出去。可调用下面的代码发送数据包。注意,这里的DEST是填入目的主机IP地址的一个
5
sockaddr_in数据结构,IP—STRING是目的主机的IP地址字符串。
Struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr(IP_STRING); //填入搜索的IP地址
sendto(sockRaw,icmp_data,datasize,0,(sockaddr*)&dest,sizeof(dest));
4.解析数据包
如果所Ping的目的主机所在,那么它会发送一个回送应答包。这是一个IP包,收到后解析此数据包并获取其中的ICMP信息。根据IP报头信息中的IP报头长度字段,就可以得到ICMP报文的真实地址。ICMP数据包中的IP地址就是活动主机的IP。代码如下:
#define ICMP_MIN 8 //ICMP报文头长度(最小ICMP报文长度)
#define MAX_PING_PACKET_SIZE (MAX_PACKET + SIZEOF(IPHeader))
char *recvbuf=new char[MAX_PING_PACKET_SIZE]; //保证大与发送包的大小
//from是一个sockaddr_in数据结构,用于保存响应的目的的主机的地址
struct sockaddr_in from;
int fromlen = sizeof(from);
int bytes = recvfrom(sockRaw,recvbuf,MAX_PACKET, 0,(struck
sockaddr*)&from),&fromlen);
IpHeader *iphdr;
IcmpHeader *icmphdr;
Unsigned short iphdrlen;
Iphdr=(Ipheader *)buf;
Iphdrlen = iphdr->headlen*4 ; //IP报头的长度
Icmphdr=(Icmpheader *)(buf+iphdrlen); //跳过IP报头
//数据包太短,丢弃
if(bytes
type !=ICMP_ECHO_REPLY) return;
//Id号不相符,丢弃
if(icmphdr->id!=(USHOT)GetCurrentThreadId()) return;
//输出正在使用的IP地址。
Cout<<”活动主机:”<sin_addr)<
#include
#include
#include
#include
#include #include
typedef struct iphdr
{
unsigned int headlen:4;
unsigned int version:4;
unsigned char tos;
unsigned short totallen;
10
unsigned short id;
unsigned short falg;
unsigned char ttl;
unsigned char prot;
unsigned short checksum;
unsigned int sourceIP;
unsigned int destIP;
}IpHeader;
typedef struct icmphdr {
BYTE type;
BYTE code;
USHORT checksum;
USHORT id;
USHORT seg;
}IcmpHeader;
#define ICMP_RCHO 8
11
#define ICMP_RCHO_REPLY 0
#define ICMP_MIN 8
#define STATUS_FAILED 0xFFFF
#define DEF_PACKET_SIZE 32
#define MAX_PACKET 1024
#define MAX_PING_PACKET_SIZE (MAX_PACKET+sizeof(IpHeader))
void fill_icmp_data(char *,int);
USHORT checksum(USHORT *,int);
void decode_resp(char *,int,struct sockaddr_in *);
DWORD WINAPI FindIP(LPVOID pIPAddrTemp);
WSADATA wsaData;
SOCKET sockRaw;
struct sockaddr_in dest,from,end;
int fromlen =sizeof(from);
char *recvbuf=new char[MAX_PING_PACKET_SIZE];
unsigned int addr=0;
12
long ThreadNumCounter=0,ThreadNumLimit=20; long *aa=&ThreadNumCounter;
void main(int argc,char *argv[])
{
/*if(argc!=3)
{
cout<<"输入格式错误: start_ip end_ip"<ThreadNumLimit)
{
Sleep(5000);
continue;
}
DWORD ThreadID;
sockaddr_in *pIPAddrTemp=new (sockaddr_in);
if(!pIPAddrTemp)
{
cout<<"memory alloc failed"<type=ICMP_RCHO;
icmp_hdr->id=(USHORT)GetCurrentThreadId();
datapart=icmp_data+sizeof(IcmpHeader);
memset(datapart,'A',datasize-sizeof(IcmpHeader));
}
18
void decode_resp(char *buf,int bytes,struct sockaddr_in *from)
{
IpHeader *iphdr;
IcmpHeader *icmphdr;
unsigned short iphdrlen;
iphdr=(IpHeader*) buf;
iphdrlen=iphdr->headlen*4;
icmphdr=(IcmpHeader *)(buf+iphdrlen);
if(bytestype!=ICMP_RCHO_REPLY)return;
if(icmphdr->id!=(USHORT)GetCurrentThreadId())return;
19
cout<<"活动主机: "<sin_addr)<1)
{
cksum+=*buffer++;
size-=sizeof(USHORT);
}
if(size)
{
20
cksum+=*(UCHAR*)buffer;
}
cksum=(cksum>>16)+(cksum& 0xffff);
cksum+=(cksum>>16);
return (USHORT)(~cksum);
}
DWORD WINAPI FindIP(LPVOID pIPAddrTemp)
{
InterlockedIncrement(aa);
char icmp_data[MAX_PACKET];
memset(icmp_data,0,MAX_PACKET);
int datasize=DEF_PACKET_SIZE;
datasize+=sizeof(IcmpHeader);
fill_icmp_data(icmp_data,datasize);
((IcmpHeader*)icmp_data)->checksum=0;
((IcmpHeader*)icmp_data)->seg=0;
21
((IcmpHeader*)icmp_data)->checksum=checksum((USHORT*)ic
mp_data,datasize);
int
bwrote=sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr
*)pIPAddrTemp,sizeof(dest));
int n=0;
if(bwrote==SOCKET_ERROR)
{
if(WSAGetLastError()==WSAETIMEDOUT)
{
cout<<"timed out"<