Section I Problem Specification
本次实验主要是写一个程序:能够追踪到达某外网ip时,所经过的路由器。也就说是:当我向某个ip地址发包时,是经过路由器帮我转发这些包,我现在就是想把这些经过的路由的ip显示出来。
ICMP
主要首先要介绍发的包,是属于ip数据包类型的一种,我已经学过的两种是TCP和UDP,这次是ICMP包。官方所解释的是:主要作用是用于IP主机和路由器之间传递控制消息。我的理解:就是我们在使用ping命令的时候就在发这个包,这个包能够非常短小,可以让我知道对方主机是否存在和与对方主机知否联通。这里我发送了一个ICMP请求包给目的主机,主机会回我一个ICMP回应包。
请求包和回应包的格式相同,如图所示:
其中:
type和code等下解释,
checksum:是针对ICMP message的检验和,代码在最终给出,检验范围是:type开始到Optionaldata的这个几个部分,OptionalData默认是32个字节,发送请求包时没什么用,我全部填了0。
checksum:是针对ICMP message的检验和,代码在最终给出,检验范围是:type开始到Optionaldata的这个几个部分,OptionalData默认是32个字节,发送请求包时没什么用,我全部填了0。
ID和Seq. NUM.:都是自己选填内容,回应包会把这个2个原封不动的发回来。所以想怎么用就自己想。
type和code共同指明了该ICMP包的用途:请看下表(来自
wiki)
本实验所用,无非就是0/0的回应包,8/0的请求包,11/0的超时包,其中“/”前为type,"/"后为Code
本实验所用,无非就是0/0的回应包,8/0的请求包,11/0的超时包,其中“/”前为type,"/"后为Code
Type | Code | Description |
---|---|---|
0 - Echo Reply | 0 | echo响应 (被程序ping使用) |
1 and 2 | 保留 | |
3 - 目的地不可到达 | 0 | 目标网络不可达 |
1 | 目标主机不可达 | |
2 | 目标协议不可达 | |
3 | 目标端口不可达 | |
4 | 要求分段并设置DF flag标志 | |
5 | 源路由失败 | |
6 | 未知的目标网络 | |
7 | 未知的目标主机 | |
8 | 源主机隔离 | |
9 | 禁止访问的网络 | |
10 | 禁止访问的主机 | |
11 | Network unreachable for TOS | |
12 | Host unreachable for TOS | |
13 | 网络流量被禁止 | |
4 - Source Quench | 0 | Source quench (congestion control) |
5 - Redirect Message | 0 | 重定向网络 |
1 | 重定向主机 | |
2 | Redirect Datagram for the TOS & network | |
3 | Redirect Datagram for the TOS & host | |
6 | Alternate Host Address | |
7 | 保留 | |
8 - Echo Request | 0 | Echo请求 |
9 - Router Advertisement | 0 | 路由建议 |
10 - Router Solicitation | 0 | Router discovery/selection/solicitation |
11 - Time Exceeded | 0 | TTL在传输中过期 |
1 | Fragment reassembly time exceeded | |
12 - 错误的IP头 | 0 | Pointer indicates the error |
1 | 丢失选项 | |
2 | 不支持的长度 | |
13 - Timestamp | 0 | 时间戳 |
14 - Timestamp Reply | 0 | 时间戳响应 |
15 - Information Request | 0 | Information Request |
16 - Information Reply | 0 | Information Reply |
17 - Address Mask Request | 0 | Address Mask Request |
18 - Address Mask Reply | 0 | Address Mask Reply |
19 | 因安全原因保留 | |
20 through 29 | Reserved for robustness experiment | |
30 - Traceroute | 0 | 信息请求 |
31 | 数据报转换出错 | |
32 | 手机网络重定向 | |
33 | Where-Are-You (originally meant for IPv6) | |
34 | Here-I-Am (originally meant for IPv6) | |
35 | Mobile Registration Request | |
36 | Mobile Registration Reply | |
37 | Domain Name Request | |
38 | Domain Name Reply | |
39 | SKIP Algorithm Discovery Protocol, Simple Key-Management for Internet Protocol | |
40 | Photuris, Security failures | |
41 | ICMP for experimental mobility protocols such as Seamoby [RFC4065] | |
42 through 255 | 保留 |
TTL
路由接到各种包时,会帮我们转发到下一跳(就是为了到达包中目的地址,需要传给的下一个路由)。但是这个包的ip数据头里面有一个TTL,存活时间,如果这个存活数(说是时间,其实跳路由的次数)等于0,那么就会发一个超时差错包给发送ICMP请求包的主机。
这就是本程序实现的原理:
当我们把ICMP请求包的TTL设置为1时,第一个路由接到该包本来是想转发下一个路由,但是因为TTL为1,所以超时差错包发回给了我们。我们就知道第一个路由了。
同理,TTL设置为2时,知道第二个路由,设置为3时知道第三个路由。
一直增加TTL的数量:当我们收到目的地址给我们的回应包时,我们的追踪就算完成了。
这里让我们看一下超时差错包的结构:
其中
1.每一个ICMP包必须有type/code/checksum这三个。
2.这个超时包,optionalData里面放是ICMP请求包的Ipheader数据和ICMP首部数据。发给原主机好让源主机确认
Section II Solution Method and Design
涉及的技术的是之前学过的,比如:
构建包,解析包。之前构建ARP包,这次是ICMP请求包
利用Winpcap收发包
在界面上增加控件和监听
做个线程
我是在原来的软件的基础上改,最后这个课下来,也许我还能做个合成的小软件
类图:
这次我用了继承的概念写了ICMP的包,包括收发包和回应包,还有超时包,
本次实验的涉及的类都写出来了:
本次实验的涉及的类都写出来了:
序列图:
Section III Test Cases and Results Analysis
Section IV Conclusion
1. 发ICMP的包的时候,如果自行构建ICMP包的话,那么发送到外网时,填的mac地址是网关的地址。
2.\t在MFC框架中的listbox不起作用的原因是要设置listbox的属性use Tapstop改成true。MFC真是个让人讨厌的框架
3.Ip包的checksum和ICMP发送的包里的checksum,均可由下面的代码计算出来,计算的范围传入参数也需要注意:
比如在我的代码里:
//ICMP包的checksum
unsigned char ICMPpacketToCheck[40];
memset(&ICMPpacketToCheck,0,sizeof(ICMPpacketToCheck));
memcpy(&ICMPpacketToCheck,&this->type,8);//后面全是0,只有前8个是我自己填的
this->cksum=GenerateChecksum((unsigned short*)ICMPpacketToCheck,40);
//ip包的checkSum
unsigned char IPheaderToCheck[20];
memset(&IPheaderToCheck,0,sizeof(IPheaderToCheck));
memcpy(&IPheaderToCheck,&this->Version_HeaderLength,20);//ip包头的大小
this->HeaderChecksum=GenerateChecksum((unsigned short*)IPheaderToCheck,20);
//产生网际校验和
unsigned short RequestAndReplyICMP::GenerateChecksum(unsigned short* pBuf, int iSize)
{
unsigned long cksum = 0;
while (iSize>1)
{
cksum += *pBuf++;
iSize -= sizeof(USHORT);
}
if (iSize)
cksum += *(UCHAR*)pBuf;
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (USHORT)(~cksum);
}
4.获得毫秒的时间差
MFC框架下,调用
unsigned long t1=GetTickCount();
可以得到一个系统运行时间,毫秒级的。
获得时间差的话,只需要再调用这个方法一次,然后相减
5.不知道为什么,我在实验室的电脑上调用while循环的那句pcap_sendpacket的时候,要发2次包,如果我设置TTL为2,那么它还要发一个TTL为1的。但是在我的个人电脑上就不会。
6.没研究如何获得路由的名字。
Section V References
书:计算机网络高级软件编程技术 第六章 Tracert程序
网站:维基百科
Section VI Appendix
主要的几个类:
#pragma once
class EthernetHeader
{
public:
unsigned char destinationAddress[6];
unsigned char sourceAddress[6];
unsigned short ethernetType;
EthernetHeader(void);
~EthernetHeader(void);
unsigned short getEthernetType() ;
void setEthernetType(unsigned short val);
};
#pragma once
#pragma pack(1)
#include "ethernetheader.h"
class Ipheader :
public EthernetHeader
{
public:
unsigned char Version_HeaderLength; // 版本(4位)+首部长度(4位)
unsigned char TypeOfService; // 服务类型
unsigned short TotalLength; // 总长度
unsigned short Identification; // 标识
unsigned short Flags_FragmentOffset; // 标志(3位)+分片偏移(13位)
unsigned char TimeToLive; // 生存时间
unsigned char Protocal; // 协议
unsigned short HeaderChecksum; // 首部校验和
unsigned long SourceAddress; // 源IP地址
unsigned long DestAddress; // 目的IP地址
Ipheader(void);
~Ipheader(void);
unsigned char getProtocal();
unsigned long getSourceAddress();
unsigned long getDestAddress();
};
#pragma once
#include "ipheader.h"
class rowICMP :
public Ipheader
{
public:
unsigned char type; //8位类型
unsigned char code; //8位代码
unsigned short cksum; //16位校验和
rowICMP(void);
~rowICMP(void);
};
#pragma once
#include "originalicmp.h"
#include "dialog1Dlg.h"
#pragma pack(1)
class RequestAndReplyICMP :
public rowICMP
{
public:
unsigned short id; //16位标识符
unsigned short seq; //16位序列号
//前两个不说了,后两个,第一个是为了获得本机MAC地址,传过去了现在被选的设备,第二个是用GetIpNetTable2获得本地arp列表的里的一个参数,这个参数里有网关的mac地址。
RequestAndReplyICMP(char TTL,LPVOID lpParameter,PIP_ADAPTER_INFO currentSlectedAdapter ,MIB_IPNET_ROW2 gatewayMAC);
~RequestAndReplyICMP(void);
unsigned short RequestAndReplyICMP::GenerateChecksum(unsigned short* pBuf, int iSize);
};
#include "StdAfx.h"
#include "RequestAndReplyICMP.h"
RequestAndReplyICMP::RequestAndReplyICMP(char TTL,LPVOID lpParameter,PIP_ADAPTER_INFO currentSlectedAdapter ,MIB_IPNET_ROW2 gatewayMAC)
{
Cdialog1Dlg *Cdialog=(Cdialog1Dlg*)lpParameter;
//EthernetHeader
int i=0;
for (i;i<6;i++)
{
this->destinationAddress[i]=gatewayMAC.PhysicalAddress[i];
this->sourceAddress[i]=currentSlectedAdapter->Address[i];
}
this->ethernetType=htons(0x0800);
//Ipheader
this->Version_HeaderLength=0x45;
this->TypeOfService=0x00;
this->TotalLength=htons(0x003c); //cmd的tracert命令一共花了92个字节,并且得到了正确的答案,我也打算这么做
this->Identification=htons(0x02b2);
this->Flags_FragmentOffset=htons(0x0000);
this->TimeToLive=TTL;
this->Protocal=1; //1就是ICMP的协议
this->HeaderChecksum=htons(0x0000);
CString text;
Cdialog->m_ListBoxIpAddress.GetText(0,text);
this->SourceAddress=inet_addr(text);
Cdialog->m_editTextIpToTrace.GetWindowText(text);
this->DestAddress=inet_addr(text);
//请求或者应答
this->type=0x08;
this->code=0x00;
this->cksum=htons(0x0000);
this->id=htons(0x0000);
this->seq=htons(0x0000);
//ICMP包的checksum
unsigned char ICMPpacketToCheck[40];
memset(&ICMPpacketToCheck,0,sizeof(ICMPpacketToCheck));
memcpy(&ICMPpacketToCheck,&this->type,8);//后面全是0,只有前8个是我自己填的
this->cksum=GenerateChecksum((unsigned short*)ICMPpacketToCheck,40);
//ip包的checkSum
unsigned char IPheaderToCheck[20];
memset(&IPheaderToCheck,0,sizeof(IPheaderToCheck));
memcpy(&IPheaderToCheck,&this->Version_HeaderLength,20);//ip包头的大小
this->HeaderChecksum=GenerateChecksum((unsigned short*)IPheaderToCheck,20);
//Cdialog->MessageBox("构造函数制作完成");
}
RequestAndReplyICMP::~RequestAndReplyICMP(void)
{
}
//产生网际校验和
unsigned short RequestAndReplyICMP::GenerateChecksum(unsigned short* pBuf, int iSize)
{
unsigned long cksum = 0;
while (iSize>1)
{
cksum += *pBuf++;
iSize -= sizeof(USHORT);
}
if (iSize)
cksum += *(UCHAR*)pBuf;
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (USHORT)(~cksum);
}
#pragma once
#pragma pack(1)
#include "originalicmp.h"
class TimeExceededICMP :
public rowICMP
{
int uesless;//无用的数据
//产生差错的数据包ip首部
unsigned char originalVersion_HeaderLength; // 版本(4位)+首部长度(4位)
unsigned char originalTypeOfService; // 服务类型
unsigned short originalTotalLength; // 总长度
unsigned short originalIdentification; // 标识
unsigned short originalFlags_FragmentOffset; // 标志(3位)+分片偏移(13位)
unsigned char originalTimeToLive; // 生存时间
unsigned char originalProtocal; // 协议
unsigned short originalHeaderChecksum; // 首部校验和
unsigned long originalSourceAddress; // 源IP地址
unsigned long originalDestAddress; // 目的IP地址
//产生差错的数据包ICMP首部
unsigned char originaltype; //8位类型
unsigned char originalcode; //8位代码
unsigned short originalcksum; //16位校验和
unsigned short originalid; //16位标识符
unsigned short originalseq; //16位序列号
public:
TimeExceededICMP(void);
~TimeExceededICMP(void);
};
发包和收包、解析包的线程
DWORD _stdcall ThreadProcGetRouteByWinpcap(LPVOID lpParameter){
Cdialog1Dlg *Cdialog=(Cdialog1Dlg*)lpParameter;
int TTL=1;//从1开始
char errBuf[PCAP_ERRBUF_SIZE];
pcap_findalldevs(&allDevs,errBuf);//列举所有设备 ,这里用来获取网络适配器信息的函数.pcap_findalldevs() returns 0 on success and -1 on failure.当errBuf满了会返回错误信息。
CString a=currentSlectedAdapter->AdapterName;
CString subfromIpHelper = a.Mid(a.ReverseFind('{')+1, 4);
for(Dev=allDevs;Dev;Dev=Dev->next){
a=Dev->name;
CString subfromWinpcap = a.Mid(a.ReverseFind('{')+1, 4);
if(subfromWinpcap==subfromIpHelper){
break;
}
}
currentOpenDev=pcap_open(Dev->name,65535,PCAP_OPENFLAG_PROMISCUOUS,1000,NULL,errBuf);//打开设备 http://blog.sina.com.cn/s/blog_48fa680e010002xy.html
RequestAndReplyICMP requestICMP=RequestAndReplyICMP(TTL,Cdialog,currentSlectedAdapter,gatewayMAC);//如果这里使用new字段的话,就会出现复制字符内容到frame里面的时候出错。
unsigned char frame[74];//根据cmd的tracert构建的包。每个ICMP的大小就是这么多
memset(&frame,0,sizeof(frame));
memcpy(&frame,&requestICMP,sizeof(requestICMP));
pcap_sendpacket(currentOpenDev,frame,sizeof(frame));
unsigned long t1=GetTickCount();
pcap_pkthdr* hdr;
const u_char *pkt_datas;
int overTime=0;//用于判断超时
unsigned long temp;
while(true){
overTime++;
if (overTime>30)
{
t1=GetTickCount()-t1;
str.Format("%02d\t%15s\t%4ldms",TTL,
"超时",
t1
);
Cdialog->m_listBoxShowRouter.AddString(str);
TTL++;
RequestAndReplyICMP requestICMP=RequestAndReplyICMP(TTL,Cdialog,currentSlectedAdapter,gatewayMAC);//如果这里使用new字段的话,就会出现复制字符内容到frame里面的时候出错。
unsigned char frame[74];//根据cmd的tracert构建的包。每个ICMP的大小就是这么多
memset(&frame,0,sizeof(frame));
memcpy(&frame,&requestICMP,sizeof(requestICMP));
pcap_sendpacket(currentOpenDev,frame,sizeof(frame));
t1=GetTickCount();
overTime=0;
continue;
}
pcap_next_ex(currentOpenDev,&hdr,&pkt_datas);
RequestAndReplyICMP *getICMP; ;
getICMP=(RequestAndReplyICMP *)pkt_datas;
if (ntohs(getICMP->ethernetType)==0x0800&&getICMP->Protocal==1)
{
if (getICMP->type==0x00)//这是一个回应包
{
overTime=0;
t1=GetTickCount()-t1;
str.Format("%02d\t%15s\t%4dms\t获取完毕",TTL,
inet_ntoa(*(in_addr *)&(getICMP->SourceAddress)),
t1
);
Cdialog->m_listBoxShowRouter.AddString(str);
break;
}else if (getICMP->type==11)//这是一个超时包
{
overTime=0;
TimeExceededICMP *timeExceededICMP;
timeExceededICMP=(TimeExceededICMP *)pkt_datas;
t1=GetTickCount()-t1;
str.Format("%02d\t%15s\t%4dms",TTL,
inet_ntoa(*(in_addr *)&(timeExceededICMP->SourceAddress))
,t1
);
Cdialog->m_listBoxShowRouter.AddString(str);
TTL++;
RequestAndReplyICMP requestICMP=RequestAndReplyICMP(TTL,Cdialog,currentSlectedAdapter,gatewayMAC);//如果这里使用new字段的话,就会出现复制字符内容到frame里面的时候出错。
unsigned char frame[74];//根据cmd的tracert构建的包。每个ICMP的大小就是这么多
memset(&frame,0,sizeof(frame));
memcpy(&frame,&requestICMP,sizeof(requestICMP));
pcap_sendpacket(currentOpenDev,frame,sizeof(frame));
t1=GetTickCount();
}
}
}
return 0;
}