书:计算机网络高级软件编程技术(P88) 之 基础训练:路由追踪程序的实现(tracert程序)

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。
ID和Seq. NUM.:都是自己选填内容,回应包会把这个2个原封不动的发回来。所以想怎么用就自己想。
type和code共同指明了该ICMP包的用途:请看下表(来自 wiki
本实验所用,无非就是0/0的回应包,8/0的请求包,11/0的超时包,其中“/”前为type,"/"后为Code
Type Code Description
0 - Echo Reply0echo响应 (被程序ping使用)
1 and 2 保留
3 - 目的地不可到达0目标网络不可达
1目标主机不可达
2目标协议不可达
3目标端口不可达
4要求分段并设置DF flag标志
5源路由失败
6未知的目标网络
7未知的目标主机
8源主机隔离
9禁止访问的网络
10禁止访问的主机
11Network unreachable for TOS
12Host unreachable for TOS
13网络流量被禁止
4 - Source Quench0Source quench (congestion control)
5 - Redirect Message0重定向网络
1重定向主机
2Redirect Datagram for the TOS & network
3Redirect Datagram for the TOS & host
6 Alternate Host Address
7 保留
8 - Echo Request0Echo请求
9 - Router Advertisement0路由建议
10 - Router Solicitation0Router discovery/selection/solicitation
11 - Time Exceeded0TTL在传输中过期
1Fragment reassembly time exceeded
12 - 错误的IP头0Pointer indicates the error
1丢失选项
2不支持的长度
13 - Timestamp0时间戳
14 - Timestamp Reply0时间戳响应
15 - Information Request0Information Request
16 - Information Reply0Information Reply
17 - Address Mask Request0Address Mask Request
18 - Address Mask Reply0Address Mask Reply
19 因安全原因保留
20 through 29 Reserved for robustness experiment
30 - Traceroute0信息请求
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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值