局域网截包程序实验报告

来源:工作范文网 时间:2020-10-26 09:17:06

PAGE

PAGE 11

多思版权所有 不得复制 TITLE VPN SAVEDATE 4/14/2012 8:14:00 AM

AUTHOR zyj FILENAME 计算机网络实验课.doc

设计报告

课 程 计算机网络

设计名称 局域网截包程序设计

专业班级

同组人姓名

同组人学号

实验日期 2012/3/19——2012/3/23

指导教师

成 绩

2012 年3月23日

一、设计目的和要求

1、实验目的

通过本次网络编程课程设计使学生更进一步掌握计算机网络课程的有关知识(包括IP包、ICMP、TCP、UDP等报文格式),加深对计算机网络知识的理解。

2、实验要求

将网卡设置为混杂模式,监听网络中的数据包

二、设计说明(包括设计分析,系统运行环境,设计中的重点和难点,输入和输出输出条件等)

设计分析: 通过建立rawsocket来完成对TCP/IP数据包的截获。从Windows 2000开始,Winsock 2开始支持原始socket,可以截获所有经过本机的TCP/IP数据包,支持拨号网络,但对本机向外发送的TCP/IP数据包截获有缺陷。

截取了一个TCP/IP数据包后,首先分离出IP协议(IP“Internet Protocol”协议是TCP/IP协议族中最为核心的协议,所有的TCP、UDP、ICMP和IGMP数据等都是以IP数据报格式传输的)的头部分,从IP协议头中可以得到很多关键的数据,如IP头的长度、源IP、目的IP、TCP/IP协议类型等。用类似的方法也可以获得了协议类型等其他数据。假设协议类型是TCP,那么接着就可以从数据包中分离出TCP协议(TCP协议是可靠的端到端协议)头的数据,从TCP协议头中得到很多关键的数据,如源端口、目的端口、数据偏移、控制位等。从目的端口中我们可以简单的(但不一定准确)判断一下跟着的数据是什么协议,如端口是80那么是HTTP协议、端口是21是FTP等(HTTP、FTP等协议都是基于TCP协议实现的)。

系统运行环境:windows2000操作系统以上,VC++6.0

设计中的重点和难点:while循环函数和TCP、UDP、ICMP解包函数

输入和输出条件:按q键停止捕获数据包,按s键暂停捕获数据包

三、系统详细设计(包括程序流程、主要函数等)

程序流程图:

开始

开始

获取

获取本机IP

打开网卡(混杂模式)

打开网卡(混杂模式)

捕获IP包

捕获IP包

分析协议类型

分析协议类型

继续ICMP解包IGMP解包

继续

ICMP

解包

IGMP

解包

UDP

解包

TCP

解包

输出

输出

是否有按键?

是否有按键?

s暂停

s

暂停

q

q

结束

结束

主要函数:

int DecodeTcpPack(char *, int,FILE *); //TCP解包函数

int DecodeUdpPack(char *, int,FILE *); //UDP解包函数

int DecodeIcmpPack(char *, int,FILE *); //ICMP解包函数

while循环函数

switch(iph->proto) 分析协议类型函数

四、程序源代码及注释

#define RCVALL_ON 1

#define MAX_ADDR_LEN 16 //点分十进制地址的最大长度

#define MAX_PROTO_TEXT_LEN 16 //子协议名称(如"TCP")最大长度

#define PACKAGE_SIZE sizeof(IPHeader)+1000

#pragma comment(lib, "Ws2_32.lib")

#include <stdio.h>

#include <winsock2.h>

#include <mstcpip.h>

#include <conio.h>

typedef struct iphdr //定义IP首部

{

unsigned char h_lenver; //4位首部长度+4位IP版本号

unsigned char tos; //8位服务类型TOS

unsigned short total_len; //16位总长度(字节)

unsigned short ident; //16位标识

unsigned short frag_and_flags; //3位标志位

unsigned char ttl; //8位生存时间 TTL

unsigned char proto; //8位协议 (TCP, UDP 或其他)

unsigned short checksum; //16位IP首部校验和

unsigned int sourceIP; //32位源IP地址

unsigned int destIP; //32位目的IP地址

}IPHeader;

typedef struct _tcphdr //定义TCP首部

{

USHORT th_sport; //16位源端口

USHORT th_dport; //16位目的端口

unsigned int th_seq; //32位序列号

unsigned int th_ack; //32位确认号

unsigned char th_lenres; //4位首部长度/6位保留字

unsigned char th_flag; //6位标志位

USHORT th_win; //16位窗口大小

USHORT th_sum; //16位校验和

USHORT th_urp; //16位紧急数据偏移量

}TCP_HEADER;

typedef struct _udphdr //定义UDP首部

{

unsigned short uh_sport; //16位源端口

unsigned short uh_dport; //16位目的端口

unsigned short uh_len; //16位长度

unsigned short uh_sum; //16位校验和

}UDP_HEADER;

typedef struct _icmphdr //定义ICMP首部

{

BYTE i_type; //8位类型

BYTE i_code; //8位代码

USHORT i_cksum; //16位校验和

USHORT i_id; //识别号(一般用进程号作为识别号)

USHORT i_seq; //报文序列号

ULONG timestamp; //时间戳

}ICMP_HEADER;

int iTTL,iLEN,iBYTES;

char szSourceIP[MAX_ADDR_LEN], szDestIP[MAX_ADDR_LEN];/*MAX_ADDR_LEN=16 为点分十进制地址的最大长度*/

int iSourcePort,iDestPort;

int fflag=0; //file flag

void HandleError(char *func);

//functions

int DecodeTcpPack(char *, int,FILE *); //TCP解包函数

int DecodeUdpPack(char *, int,FILE *); //UDP解包函数

int DecodeIcmpPack(char *, int,FILE *); //ICMP解包函数

/*Internet协议地址结构

struct sockaddr_in

{

short sin_family; // 地址族, AF_INET,2 bytes

u_short sin_port; // 端口,2 bytes

struct in_addr sin_addr; // IPV4地址,4 bytes

char sin_zero[8]; // 8 bytes unused

}; */

//MAIN

int main(int argc, char *argv[])

{

sockaddr_in saSource,saDest;

WSAData wsaData;

WORD wVersionRequested;

wVersionRequested=MAKEWORD(2,2);/*设置版本号*/

char buf[PACKAGE_SIZE];

WSAStartup(wVersionRequested, &wsaData); /*初始化,WINSOCK_VERSION指定使用的版本号, 本程序中WINSOCK_VERSION为常量,已经指定为MAKEWORD(2, 2) ;&wsaData是一个指向WSADATA结构的指针,它返回关于Winsock实现的详细信息*/

SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP); /*应用程序在使用套接口通信前,必须要拥有一个套接口,使用socket()函数来给应用程序创建一个套接口。AF_INET参数说明套接字接口要使用的协议地址族,地址族与协议族含义相同。如果想建立一个TCP或UDP,只能用常量AF_INET表示使用互联网协议(IP)地址; SOCK_RAW参数描述套接口的类型,第一个参数是AF_INET的时候只能为SOCK_STREAM、SOCK_DGRAM或SOCK_RAW;IPPROTO_IP说明该套接口使用的特定协议,当协议地址族和协议类型确定后,协议字段可以使用的值是限定的*/

if(sock == SOCKET_ERROR)

{

HandleError("socket");

WSACleanup();

return -1;

}

/*获取本机IP地址*/

struct sockaddr_in addr;

memset(&addr, 0, sizeof(addr)); /*把&addr所指内存区域的前sizeof(addr)个字节设置成字符0*/

char name[256];

hostent *hostinfo;

if( gethostname ( name, sizeof(name)) == 0)/*name是一个字符数组,sizeof(name)是该数组的长度。当调用成功,函数返回0并且将本机的名称赋值给name;当调用失败,则返回SOCKET_ERROR.*/

{

if((hostinfo = gethostbyname(name)) != NULL)

{

memcpy(&(addr.sin_addr.S_un.S_addr) , (struct in_addr *)*hostinfo->h_addr_list , sizeof((struct in_addr *)*hostinfo->h_addr_list ));/* 由(struct in_addr *)*hostinfo->h_addr_list所指内存区域复制sizeof((struct in_addr *)*hostinfo->h_addr_list个字节到&(addr.sin_addr.S_un.S_addr)所指内存区域*/

}

}

printf("本机的IP Address是:?%s\n", inet_ntoa(addr.sin_addr));

addr.sin_family = AF_INET;/*将协议设为internet协议*/

if(bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)

/*sock是socket()返回的socket描述符;(struct sockaddr*)&addr是指向包含本机IP地址及端口号等信息的sockaddr类型的指针; sizeof(addr)一般被设置为sizeof(struct sockaddr_in); 成功被调用时返回0;出现错误时返回"-1" */

{

HandleError("bind");/*见最后一个子函数*/

}

/*设置SOCK_RAW为SIO_RCVALL,以便接收所有的IP包*/

int on = RCVALL_ON; /* RCVALL_ON为已定义的常量1*/

DWORD num;

if(WSAIoctl(sock, SIO_RCVALL, &on, sizeof(on), NULL, 0, &num, NULL, NULL) == SOCKET_ERROR)/* 控制一个套接口的模式;sock为一个套接口的句柄,SIO_RCVALL为将进行的操作的控制代码,&on为输入缓冲区的地址,sizeof(on)输入缓冲区的大小,NULL输出缓冲区的地址, 0输出缓冲区的大小, &num输出实际字节数的地址, NULL WSAOVERLAPPED结构的地址, NULL一个指向操作结束后调用的例程指针; 调用成功后,WSAIoctl ()函数返回0。否则的话,将返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()来获取相应的错误代码。*/

{

HandleError("wsaIoctl set");

}

struct sockaddr_in from;

int fromlen;

int size;

FILE *fp;

if((fp=fopen("log.txt","w+"))==NULL)/*fp=fopen(log.txt表示要打开的文件名,"w+"为读写建立一个新的文本文件,若无法建立新的文件则返回NULL*/

{

printf("open file error,can't save list to file");

fflag=1;

}

//侦听IP报文

printf("*************************************************************\n");

printf(" 现在开始截获报文\n");

printf(" 按q停止\n");

printf(" 按s暂停\n");

printf("*************************************************************\n");

printf(" 按任意键开始:\n");

char c;

c=getchar();

while(1)/*检查当前是否有键盘输入,若有则返回一个非0值,否则返回0*/

{ while(!kbhit())

{

memset(buf, 0, sizeof(num)); /*把buf所指内存区域的前sizeof(num)个字节设置成字符0*/

memset(&from, 0, sizeof(from)); /*把&from所指内存区域的前sizeof(from)个字节设置成字符0*/

fromlen = sizeof(from);

size=recvfrom(sock, buf, PACKAGE_SIZE, 0, (struct sockaddr*)&from, &fromlen);/* 对于无连接的套接口来说,要从套接口上接收一个数据报并保存发送数据的源地址,就要使用recvfrom()函数,sock标识一个套接口的描述字, buf接收数据的缓冲区, PACKAGE_SIZE接收数据缓冲区的长度,0调用操作方式, (struct sockaddr*)&from可选指针,指向装有源地址的缓冲区, &fromlen可选指针,指向from缓冲区的长度值;recvfrom()函数返回接收到的字节数,当出错时返回-1 */

if(size == SOCKET_ERROR)

{

if(WSAGetLastError() == WSAEMSGSIZE)/* WSAGetLastError()来获取WSAIoctl ()的错误代码*/

{

HandleError("recvfrom");

continue;

}

}

IPHeader *iph=(IPHeader *)buf;

/**/

//源地址

saSource.sin_addr.s_addr = iph->sourceIP;

strncpy(szSourceIP, inet_ntoa(saSource.sin_addr), MAX_ADDR_LEN);/* inet_ntoa(saSource.sin_addr)网络字节序的IP地址转换为点分十进制数表示的IP地址*/

//目的地址

saDest.sin_addr.s_addr = iph->destIP;

strncpy(szDestIP, inet_ntoa(saDest.sin_addr), MAX_ADDR_LEN);

iTTL = iph->ttl;

//计算IP首部的长度

int IpHeadLen = 4 * (iph->h_lenver & 0xf);

//根据协议类型分别调用相应的函数

switch(iph->proto)

{

case IPPROTO_ICMP:

DecodeIcmpPack(buf+IpHeadLen, size,fp);

break;

case IPPROTO_IGMP:

printf("IGMP ");

printf("%15s: ->%15s: ", szSourceIP, szDestIP);

printf("%d",size);

printf("%s\n", buf);

break;

case IPPROTO_TCP:

DecodeTcpPack((buf+IpHeadLen),size,fp);

break;

case IPPROTO_UDP:

DecodeUdpPack(buf+IpHeadLen, size,fp);

break;

default:

printf("unknown datagram from %s\n", inet_ntoa(from.sin_addr));

printf("%s\n", buf);

break;

} //end switch

Sleep(200);

}

char m;

m=getch();

if(m=='q')

{

break;

}

else

if(m=='s')

{

printf("暂停,按任意键继续\n");

char a;

a=getch();

continue;

}

}//end while

fclose(fp);

closesocket(sock);/* 一个套接口不再使用时一定要关闭这个套接口,以释放与该套接口关联的所有资源,包括等候处理的数据。*/

WSACleanup();/*当应用程序不再使用Winsock API中的任何函数时,必须调用WSACleanup()将其从Windows Socket的实现中注销,以释放为此应用程序或DLL分配的任何资源。*/

printf("Stopped!\n");

getch();/*从控制台读取一个字符,但不显示在屏幕上*/

return 0;

} //end of main

//TCP解包程序

int DecodeTcpPack(char * TcpBuf, int iBufSize,FILE *fp)

{

unsigned char FlagMask;FlagMask = 1;

int i;

TCP_HEADER *tcph;

tcph = (TCP_HEADER*)TcpBuf;

//计算TCP首部长度

int TcpHeadLen = tcph->th_lenres>>4;

TcpHeadLen *= sizeof(unsigned long);

char *TcpData=TcpBuf+TcpHeadLen;

iSourcePort = ntohs(tcph->th_sport);

iDestPort = ntohs(tcph->th_dport);

//输出

printf("TCP ");

printf("%15s:%5d ->%15s:%5d ", szSourceIP, iSourcePort, szDestIP, iDestPort);

printf("TTL=%3d ", iTTL);

if(fflag==1)

//判断TCP标志位

for( i=0; i<6; i++ )

{

if((tcph->th_flag) & FlagMask)

printf("1");

else printf("0");

FlagMask=FlagMask<<1;

}

printf(" bytes=%4d", iBufSize);

printf("\n");

if(fflag=1)//写入文件

fprintf(fp,"TCP %15s:%5d ->%15s:%5d TTL=%3d bytes=%4d\n",szSourceIP, iSourcePort, szDestIP, iDestPort, iTTL,iBufSize);

return 0;

}

//UDP解包程序

int DecodeUdpPack(char * UdpBuf, int iBufSize,FILE *fp)

{

UDP_HEADER *udph;

udph = (UDP_HEADER*)UdpBuf;

iSourcePort = ntohs(udph->uh_sport);

iDestPort = ntohs(udph->uh_dport);

//输出

printf("UDP ");

printf("%15s:%5d ->%15s:%5d ", szSourceIP, iSourcePort, szDestIP, iDestPort);

printf("TTL=%3d ", iTTL);

printf("Len=%4d ", ntohs(udph->uh_len));

printf("bytes=%4d", iBufSize);

printf("\n");

if(fflag=1)//写入文件

fprintf(fp,"UDP %15s:%5d ->%15s:%5d TTL=%3d Len=%4d bytes=%4d\n" ,szSourceIP, iSourcePort, szDestIP, iDestPort, iTTL, ntohs(udph->uh_len), iBufSize);

return 0;

}

//ICMP解包程序

int DecodeIcmpPack(char * IcmpBuf, int iBufSize,FILE *fp)

{

ICMP_HEADER * icmph;

icmph = (ICMP_HEADER * )IcmpBuf;

int iIcmpType = icmph->i_type;

int iIcmpCode = icmph->i_code;

//输出

printf("ICMP ");

printf("%15s ->%15s ", szSourceIP, szDestIP);

printf("TTL=%3d ", iTTL);

printf("Type%2d,%d ",iIcmpType,iIcmpCode);

printf("bytes=%4d", iBufSize);

printf("\n");

if(fflag=1)//写入文件

fprintf(fp,"ICMP %15s ->%15s TTL=%3d Type%2d,%d bytes=%4d", szSourceIP, szDestIP, iTTL,iIcmpType,iIcmpCode, iBufSize);

return 0;

}

void HandleError(char *func)

{

char info[65]= {0};

_snprintf(info, 64, "%s: %d\n", func, WSAGetLastError());

printf(info);

}

/*int snprintf(char *str,size_t size,const char *format,…);将可变个参数(…)按照format格式化成字符串,然后复制到str中;如果格式化后的字符串长度小于size,则将此字符创全部复制到str中,并在其后面添加一个字符串结束符("\0")如果格式化后字符串长度大于等于size,则只讲其中(size-1)个字符复制到str中,并给其后添加一个字符串结束符("\0");若成功则返回欲写入的字符串长度,若出错,则返回负值*/

五、实验数据、结果分析

六、总结

经过这为期一周的课程设计,我们小组共同分析讨论,查阅资料,解决问题,最终如期完成,在这短短的一周时间内收获不少,总结起来主要有下几点:

首先,通过这次课程设计,我们对课本所学知识理解更透彻了,而且对RAW模式的SOCKET编程有了一定的了解。

 其次,学会了要解决问题,我们必须借助查阅资料,学会利用网络学习,咨询老师同学。最后,学会了设计一个项目的具体步骤,应该首先画设计好流程图,并且分模块实现,可以节省时间,减少错误。

因此,我们认为这次网络课程设计是很好也很有必要的。

七、教师意见