计算机网络课程
一、设计内容及设计要求
1.1课程设计内容:
利用ICMP数据包,通过使用ICMP的回送和回送响应消息来确定当前网络中处于活动状态的主机,即ping消息的请求和应答,将发送的ICMP的数据包类型设置为回送请求(类型号为8),并显示在
输出上。用命令行形式运行:scanhost Start_IP End_IP,其中scanhost为程序名;Start_IP为被搜索网段的开始IP;End_IP为被搜索网段的结束IP地址。
1.2课程设计目的:
IP
的优点是简单,但缺少差错控制和查询机制,而网际控制报文协议(ICMP具有补充IP功能的作用。在网络管理中,常常要确定当前网络在红处于活动状态的主机,这时可以通过ICMP的回送和回送响应消息来完成这项工作。这课程设计的目的就是编制程序,利用ICMP数据包,发现网络中的活动主机,即ping消息的请求和应答。通过课程设计,熟悉ICMP报文的结构,对ICMP协议有更好的理解和认识,培养综合运用网络知识解决实际问题能力。
1.3课程设计要求:
设计程序,其功能是发送ICMP数据包,以获取指定望段中的活动主机,并将结果显示在标准输出设备上程序的具体要求如下:
1.用命令形式运行
scanhost为程序名;start_ip为被搜索网段;end_ip为被搜索网段的结束IP地址。如在命令行输入 scanhost 192.168.0.1 192.168.0.100
2.输出格式
活动主机1的IP地址
活动主机2的IP地址
活动主机 n的IP地址
二、总体设计
2.1设计原理
首先对ICMP报文的格式有一定的了解,ICMP报文是在IP数据报内部传输的,其结构如图所示:
IP数据报
IP首部 ICMP报文
ICMP报文的格式如图所示:
0 7 8 15 16 31(位)
类型字段
代码字段
校验和字段
(不同类型和代码有不同内容)
所有报文的前4个字节都是一样的,但是其它字节则互不相同。其中类型字段可以有15个不同的值,以描述特定类型的ICMP报文,某些ICMP报文还使用代码字段的值来进一步描述不用的条件。按验和字段为2字节,校验的范围是整个ICMP报文。检验和是必须的,其计算方法与IP协议头部校验和的计算方法一样。
各种类型的ICMP报文如图所示(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
12 参数问题
0 各种IP头部错误
1 缺少必须的选项
13 0 时间戳请求
14 0 时间戳应答
15 0 信息请求(已作废)
16 0 信息应答(已作废)
17 0 地址掩码请求
18 0 地址掩码应答
课程设计的目的是发现网络中的活动主机,就是使用ICMP的回送和回送响应消息发现网络中的活动主机,即Ping消息的请求和应答。那幺,发送的ICMP的数据包类型设置为回送请求(类型号为8)。
本程序使用的原始套接字生成ICMP请求/应答报文来进行活动主机的探查。这个程序使用的是回送请求和应答消息。程序的大致思想是把ICMP的数据报类型设置为回送请求,将它发送给网络上的一个IP地址,如果这个IP地址已经被占用的话,那么使用位于这个IP地址的主机上的TCP/IP软件就能接受到这个ICMP回送请求,从而返回一个ICMP回送请求(类型号为0)信息。信息封装在一个IP包中,我们需要解析该IP包,从中找到ICMP数据信息,相反,如果这个IP地址没有人使用,那么发送的ICMP回送请求在设定的延时内就不可能得到响应。
2.2概要设计
主程序
图
子线程流程图
三、详细设计及代码
3.1 ICMP报文分析
ICMP是一种差错和控制报文协议,用于传输错误
和控制信息。
ICMP报文分为头部和数据部分。ICMP报文封装在IP数据报中传输。IP报头中的类型为1时,表示报文的数据部分为ICMP报文。虽然ICMP报文由IP报文传输,但是并不能认为ICMP是IP的上层协议,而是IP协议的有机补充。把ICMP报文放在IP包中,是要利用IP的转发功能。
类型(TYPE)是一个字节,表示ICMP消息的类型。代码(CODE)也是一个字节,表示报文类型的下一步信息。校验和共有两个字节,提供对整个ICMP报文的校验和(和IP报文类型的进一步信息)。校验和共两个字节,提供对整个ICMP报文的校验和。按照协议的功能来分,ICMP报文可以分为
[1]. ICMP差错报文
包括目的不可达报告,超时报告,参数出错报告。
[2] .ICMP控制报文
包括拥塞控制和源抑制报文,路游控制和重定向报文
[3] .ICMP测试报文
包括请求应答报文,时戳请求应答报文。
本课程设计就是使用ICMP请求/应答报文来测试目的主机是否存在,请求者想某特定的主机发送请求,其中包含任选的数据。目的主机收到请求后,发送应答报文。在同一时刻,一台机器可以同时向多台主机发送请求报文。ICMP报文格式如图所示:
类型
代码
校 验 和
数据区(变长)
ICMP回送报文格式如下图所示:
类型(8,0) 码(0)
校验和
标志位
序号
任选数据
3.2程序功能分析
本程序使用原始套接字生成ICMP报文来进行活动主机的探查。这个程序使用的是回送请求与应答信息。程序的大致思想是把ICMP的数据包类型设置为回送请求,将它发送给网络上的一个IP地址,如果这个IP地址已经被占用的话,那么使用位于这个IP地址的主机上的TCP/IP软件就能够接受到这个ICMP回送请求,从而返回一个ICMP回送响应(类型号为0)信息。信息封装在一个IP包中,需要解析该IP包,从中找到ICMP数据信息。相反,如果这个IP地址没有人使用,那么发送的ICMP回送请求在设定的延时内就不可能得到响应。
在初始化原始套接字之后,本程序就要开始在一个IP网段内寻找活动主机。因为要寻找活动的主机可能很多,为节省时间可以采用多线程编程。结合核心代码对程序的具体进行分析。
3.2.1使用原始套接字
为了实现发送/监听ICMP抱文,必须使用原始套接字,创建原始套接字的代码如下:SOCKET sockraw;
sockraw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,wsa_flag_overlapped);
在WSASocket函数中,我们使用IPPROTO_ICMP表示接受ICMP数据包,为了使用发送接受超时设置(设置SO_RCVTIMEO或SO_SNDTIMEO),必须将标志位置为WSA_FLAG_OVERLAPPED。然后调用setsockopt函数设置读取迟延。在setsockopt函数中,sockraw是之前创建的原始套接字,设置SOL_SOCKET表明使用基本套接字处理ICMP抱文。设置SO_RCVTIMEO表示使用接受超时设置,SOSNDTIMEO表示使用发送超时设置,在这里,超时时间均设置为1000ms。
3.2.2定义IP头部的数据结构
typedef struct iphdr{
unsigned int headlen:4; //ip头长度
unsigned int wersion:4; //ip版本号
unsigned char tos; //服务类型
unsigned short totallen; //ip包总长度
unsigned short id; //ip号
unsigned short flag; //标记
unsigned char ttl; //生存时间
unsigned char prot; //协议(UDP TCP)
unsigned short checksum; //校验和
unsigned int sourceip; //源ip
unsigned int destip; //目的ip
}IpHeader;
3.2.3定义ICMP头部数据结构
typedef struct icmphdr{
BYTE type; //icmp类型码,回送请求的类型码为8
BYTE code; //子类型码,保存与特定ICMP报文类型相关的细节信息
USHORT checksum; //校验和
USHORT id; //ICMP报文id号
USHORT seq; //ICMP数据报的序列号
}Icmpheader;
3.2.4填充并发送请求类型的ICMP报文
#define ICMP_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数据头部
icmp_header *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); //计算出ICMP数据报的数据部分
memset(datapart,'A',datasize-sizeof(icmphearder)); //填入数据
((IcmpHeader*)icmp_data)->seq=0; //序列号
((IcmpHeader*)icmp_data)->check_sum=0; //先将检验和置0
((IcmpHeader*)icmp_data)->checksum=checksum(USHORT*) icmp_data,data_size);
Checksum 为计算校验和的函数,设校验和初值为0,然后对数据每16位求异或,结果取反,便得校验和。其代码如下:
USHORT checksum(USHORT * buffer, int size) //计算校验和
{
unsinged 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地址的一个sockaddr_in数据结构,IP_STRING是目的的主机的IP地址字符串。
Struct sockaddr_in_dest;
Dest.sin_family=AF_INET;
Dest.sin_addr.s_addr=inet_addr(IP_STRING);
Sendto(sockraw,icmp_data,datasize,0,(sockaddr*)&dest,size of(dest));
3.2.5解析数据包
如果所ping的目的主机存在,那么它会发出一个回送应答包。这是一个IP包,受到后解析此数据包并获得其中的ICMP信息。根据IP报头信息中的IP报头长度字段,就可以得到ICMP报文的真实地址。ICMP数据包中的IP地址就是活动主机的IP。代码分析如下:
#define ICMP_MIN 8
#define MAX_PING_PACKET_SIZE(MAX_PACKET+sizeof(IpHeader))
char *recvbuf=new[MAX_PING_PACKET_SIZE];
struct sockaddr_in dest,from,end;
int formlen=sizeof(from);
int bytes=recvfrom(sockraw,recvbuf,MAX_PACKET,0,(Struct 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!=(USHORT)getcurrentthreadid()) return;
//输出正在使用的IP地址
cout<<"活动主机"<sin_addr)<
#include
#include
#include
#include
#include
//THE IP HEADER
typedef struct iphdr{
unsigned int headlen:4; //ip头长度
unsigned int wersion:4; //ip版本号
unsigned char tos; //服务类型
unsigned short totallen; //ip包总长度
unsigned short id; //ip号
unsigned short flag; //标记
unsigned char ttl; //生存时间
unsigned char prot; //协议(UDP TCP)
unsigned short checksum; //校验和
unsigned int sourceip; //源ip
unsigned int destip; //目的ip
}IpHeader;
//ICMP HEADER
typedef struct icmphdr{
BYTE type; //icmp类型码,回送请求的类型码为8
BYTE code; //子类型码,保存与特定ICMP报文类型相关的
//节信息
USHORT checksum; //校验和
USHORT id; //ICMP报文id号
USHORT seq; //ICMP数据报的序列号
}Icmpheader;
#define ICMP_ECHO 8 //请求回送
#define ICMP_ECHO_REPLY 0 //请求回应
#define ICMP_MIN 8 //ICMP包头长度(最小ICMP包长度)
#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_date(char * ,int); //填充ICMP包
USHORT checksum(USHORT *,int); //校验和函数
void decode_resp(char*,int,struct sockaddr_in *); //找到此数据报IP地址
DWORD WINAPI FindIp(LPVOID pipaddrtemp); //线程调用子函数
//scanhost.cpp
#include "scanhost.h"
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;
long threadnumcounter = 0, threadnumlimit = 20;
long *aa = &threadnumcounter;
void main(int argc, char *argv[])
{
if(argc != 3)
{
cout<<"输入格式错误:scanhost start_ip end_ip"< threadnumlimit)
{
Sleep(5000);
continue;
}
DWORD Threadid;
sockaddr_in * pipaddrtemp = new(sockaddr_in);
if(!pipaddrtemp)
{
cout<<"memory alloc failed"<type = ICMP_ECHO;
icmp_hdr->id = (USHORT)GetCurrentThreadId();
datapart = icmp_data + sizeof(Icmpheader);
memset(datapart, 'A', datasize - sizeof(Icmpheader));
}
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(bytes < iphdrlen + ICMP_MIN)
return;
if(icmphdr->type != ICMP_ECHO_REPLY)
return;
if(icmphdr->id != (USHORT)GetCurrentThreadId())
return;
cout<<"活动主机"<sin_addr)< 1)
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if(size)
{
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)->seq = 0;
//计算校验和后填入
((Icmpheader* )icmp_data)->checksum = checksum((USHORT* )icmp_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"<start_ip
结束IP地址->end ip
将start_ip填入到dest中
start_ip< = end ip?
线程数目太多?
创建一个线程并执行
start_ip ++
还有线程在执行?
结 束
等待一定时间
N
Y
Y
N
等待一定时间
Y
开 始
填充ICMP数据报
发送数据报
接受数据报
去掉IP头,获取ICMP信息
数据包太短?
不是回送响应?
ID不符合?
数据库中的IP地址
结 束
Y
Y
Y
N
N
N