一、课程设计目的和意义
通过计算机网络的理论知识的学习,我掌握了一些计算机网络技术的基
础知识和基本技能。但是理论知识的学习缺乏实践的检验。通过课程设计,了解
并掌握数据结构与算法的设计方法,具备了初步的独立分析和设计能力和初步掌
握软件开发过程的问题分析、系统设计、程序编码、测试等基本方法和技能。提
高了自己综合运用所学的理论知识和方法独立分析和解决问题的能力,训练用系
统的观点和软件开发一般规范进行软件开发,为进一步的应用开发打好基础。在
课程设计中,我们可以解决其它学科有关问题,也利用其它课程的有关知识来解
决信息技术中比较抽象很难理解的知识
本次课程设计的目的就是设计一个解析 IP 数据包的程序,并根据这个程序,
说明 IP 数据包的结构及 IP 协议的相关问题,从而对 IP 层的工作原理有更好的
理解和认识。
二、课程设计的内容和要求
本课程设计的目标是捕获网络中的 IP 数据包,解析数据包的内容,将结果
显示在标准输出上,并同时写入日志文件。
程序的具体要求如下:
1)以命令行形式运行:ipparse logfile,其中 ipparse 是程序名, 而
logfile 则代表记录结果的日志文件。
2)在标准输出和日志文件中写入捕获的 IP 包的版本、头长度、服务类型、
数据包总长度、数据包标识、分段标志、分段偏移值、生存时间、上层协议类型、
头校验和、源 IP 地址和目的 IP 地址等内容。
3)当程序接收到键盘输入 Ctrl+C 时退出。
三、课程设计的相关技术
互联网络层是 TCP/IP 协议参考模型中的关键部分。IP 协议把传输层送来的
消息组装成 IP 数据包,并把 IP 数据包传递给数据链路层。IP 协议在 TCP/IP 协
议族中处于核心地位,IP 协议制定了统一的 IP 数据包格式,以消除各通信子网
间的差异,从而为信息发送方和接收方提供了透明的传输通道。
IP 数据包的第一个字段是版本字段,其长度为四位,表示所使用的 IP 协议
1
的版本。目前的版本是 IPv4,版本字段的值为 4,下一代的版本是 IPv6,版本字
段的值为 6.本程序主要针对版本值为 4 的 IP 数据包的解析(如图 1 所示)。
0
4
8
16
19
24
31(位)
版本 报头标长 服务类型
总长度
标 识
标志
片偏移
生存时间
协议
头校验和
源 IP 地址
目的 IP 地址
选项
数据部分
图 1 IP 数据包的格式
填充域
为了获取网络中的 IP 数据包,必须对网卡进行编程,在这里我们使用套接
字进行编程。但是,在通常情况下,网络通信的套接字程序只能响应与自己硬件
地址相匹配的数据包或是以广播形式发出的数据包。对于其他形式的数据包,如
已到达网络接口但却不是发送到此地址的数据包,网络接口在验证投递地址并非
自身地址之后将不引起响应,也就是说应用程序无法收取与自己无关的数据包。
我们要想获取流经网络设备的所有数据包,就需要把网卡设置为混杂模式。
使用套接字
套接字分为三种,即流套接字(Stream socket)、数据报套接字(Datagram
Socket)和原始套接字(Raw Socket)。要进行 IP 层数据包的接收和发送,应使用
原始套接字。创建原始套接字的代码如下:
Socket sock;
Sock=wsasocket(af_inet,sock_raw,ipproto-ip,null,0,wsa-fl
ag-overlapped);
创建套接后,IP 头就会包含在接收数据包中。然后,我可以设置 IP 头操作
选项,调用 setsockopt 函数。其中 flag 设置为 true,并设定 IP-HDRINCL 选项,表
明用户可以亲自对 IP 头进行处理。最后使用 bind()函数将 socket 绑定到本地网
卡上。绑定网卡后,需用 WSAIoctl()函数把网卡设置为混杂模式,使网卡能够接
收所有的网络数据。如果接收的数据包中的协议类型和定义的原始套接字匹配,
那么接收的数据就拷贝到套接字中,因此,网卡就可以接收所有经过的 IP 包。
接收数据包
2
在程序中可使用 recv()函数接收经过的 IP 包。该函数有四个参数,第一个参
数接收操作所用的套接字描述符;第二个参数接收缓冲区的地址;第三个参数接
收缓冲区的大小,也就是所要接收的字节数;第四个参数是一个附加标志,如果
对所发送的数据没特殊要求,直接设为 0。因为 IP 数据包的最大长度是 65535B,
因此缓冲区的大小不能小于 65535B。设置缓冲区后,可利用循环来反复监听接
收 IP 包,用 recv()函数实现接收功能。
定义 IP 数据包
程序需要定义一个数据结构表示 IP 头部。其代码如下:
Typedef struct_IP_HEADER
//定义 IP 头
{
{
};
{
};
union
BYTE Version;
//版本(前四位)
BYTE Hdrlen;
//报头标长(后四位),IP 头的长度
BYTE ServiceType; //服务类型
WORD
TotalLen;
//总长度
WORD ID;
//标识
Union
WORD Flags;
//标志(前三位)
WORD FragOff; //分段偏移(后 13 位)
BYTE TimeToLive;
//生命期
BYTE Protocol;
//协议
WORD HdrChksum; //头校验和
DWORD SrcAddr; //源地址
DWORD DstAddr;
//目的地址
BYTE Options;
//选项
}IP_HEADER;
3
IP 包的解析
解析 IP 包的字段有两种策略。针对长度为 8 位、16 位和 32 位的字段(或子
字段)时,可以利用 IP-HEADER 的成员直接获取。要解析长度不是 8 位倍数的字
段(或子字段)时,可以利用 C 语言中的移位以人、及与、或操作完成。
四、课程设计过程
Visual C++6.0 是 微 软 公 司 推 出 的 开 发 Win32 应 用 程 序 ( Windows
95/98/2000/XP/NT)的、面向对象的可视化集成工具。
打开 Visual C++6.0,新建工程 213123,新建源文件 network,键入以
下代码。
#include "winsock2.h"
#include "ws2tcpip.h"
#include "iostream.h"
#include "stdio.h"
typedef struct _IP_HEADER
{
union
{
BYTE Version;
BYTE HdrLen;
};
BYTE ServiceType;
WORD TotalLen;
WORD ID;
union
{
WORD Flags;
WORD FragOff;
};
BYTE TimeTolive;
BYTE Protocol;
WORD HdrChksum;
DWORD SrcAddr;
DWORD DstAddr;
BYTE Options;
}IP_HEADER;
void getVersion(BYTE b,BYTE &version)
{
4
version = b>>4;
}
void getIHL(BYTE b, BYTE &result)
{
result =(b & 0x0f)*4;
}
char *parseServiceType_getProcedence(BYTE b)
{
switch(b>>5)
{
case 7:
return "Network Control";
break;
case 6:
return "Internet work Control";
break;
case 5:
return "CRITIC/ECP";
break;
case 4:
return "Flash Override";
break;
case 3:
return "Flash";
break;
case 2:
return "Immediate";
break;
case 1:
return "Priority";
break;
case 0:
return "Routine";
break;
default:
return "Unknown";
}
}
char *parseServiceType_getTOS(BYTE b)
{
b=(b>>1)&0x0f;
5
switch(b)
{
case 0:
return "Normal service";
break;
case 1:
return "Minimize monetary cost";
break;
case 2:
return "Maximize reliability";
break;
case 4:
return "Maximize throughput";
break;
case 8:
return "Minimize delay";
break;
case 15:
return "Maximize security";
break;
default:
return "Unknown";
}
}
void getFlags(WORD w,BYTE &DF,BYTE &MF)
{
DF=(w>>14)&0x01;
MF=(w>>13)&0x01;
}
void getFragOff(WORD w,WORD &fragOff)
{
fragOff=w&0x1fff;
}
char *getProtocol(BYTE Protocol)
{
switch(Protocol)
{
case 1:
return "ICMP";
6
case 2:
return "IGMP";
case 4:
return "IP in IP";
case 6:
return "TCP";
case 8:
return "BGP";
case 17:
return "UDP";
case 41:
return "IPv6";
case 46:
return "RSVP";
case 89:
return "OSPF";
default:
return "UNKNOWN";
}
}
void ipparse(FILE *file,char *buffer)
{
IP_HEADER ip =*(IP_HEADER *)buffer;
fseek(file ,0,SEEK_END);
BYTE version;
getVersion(ip.Version,version);
fprintf(file,"版本=%d\r\n",version);
BYTE headerLen;
getIHL(ip.HdrLen,headerLen);
fprintf(file,"头长度 = %d(BYTE)\r\n",headerLen);
fprintf(file,"服务类型 =%s, %s\r\n",
parseServiceType_getProcedence(ip.ServiceType),
parseServiceType_getTOS(ip.ServiceType));
fprintf(file,"数据报长度=%d(BYTE)\r\n",ip.TotalLen);
fprintf(file,"数据报 ID=%d\r\n",ip.ID);
7
BYTE DF,MF;
getFlags (ip.Flags,DF,MF);
fprintf(file,"分段标志 DF=%d,MF=%d\r\n",DF,MF);
WORD fragOff;
getFragOff(ip.FragOff,fragOff);
fprintf(file,"分段偏移值=%d\r\n",fragOff);
fprintf(file,"生存期=%d(hops)\r\n",ip.TimeTolive);
fprintf(file,"协议=%s\r\n",getProtocol(ip.Protocol));
fprintf(file,"头校验和=0x%0x\r\n",ip.HdrChksum);
fprintf(file,"源 IP 地址=%s\r\n",inet_ntoa(*(in_addr*)&ip.SrcAddr));
fprintf(file,"目的 IP 地址=%s\r\n",inet_ntoa(*(in_addr*)&ip.DstAddr));
fprintf(file,"--------------------------------------\r\n");
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("usage error!\n");
return -1;
}
FILE *file;
if((file = fopen(argv[1],"wb+"))==NULL)
{
printf("fail to open file %s",argv[1]);
return -1;
}
WSADATA wsData;
if(WSAStartup(MAKEWORD(2,2),&wsData)!=0)
{
printf("WSAStartup failed!\n");
return -1;
}
SOCKET sock;
8