网络通讯中的checksum算法

网络通讯中的checksum算法

一、实验目的和任务

  1. 本实验要求复习Debian系统抓包工具的使用。
  2. 本实验要求理解checksum算法原理和实现方法。
    二、实验设备介绍
    1.软件需求: win7操作系统,VMware workstation,ubuntu12 [配置交叉编译环境]。
    2.硬件需求: PC内存大于1G,硬盘空间大于20G;smart210开发板。
    三、注意事项和要求
    1.启动Debian系统,并设置正确网络IP地址。
    2.分析checksum字段与数据包的关系。
    3.请抓包结果截图放入实验报告中。
    四、实验内容和步骤

4.1较验字段checksum介绍

checksum意思是较验和,就是将一段已有长度的数据按照某种整数类型(通常是16位无符号整数)进行累加(进位部分再继续加到低位),累加后的结果再取反以用于校验。具有校验合的数据包再次进行校验后结果则为0,因为这种运算硬件或软件设计简单,要校验的数据长度可以在较大范围变化,对数据的校验提供较高的可靠性保证,适用场合很广。理论上checksum并不是100%保证数据是可靠的。
下面是一个具体的checksum数据计算过程;网络通讯中的checksum算法_第1张图片

4.2 Debian中捕获网络包来验证ICMP网络包中checksum算法原理

打开Debian虚拟机,打开一个终端,使用su命令,密码:333333更换为root账号,终端输入wireshark命令启动抓包程序,点击Options按钮打开抓包选项,输入icmp为抓包过滤选项,点击Start按钮开始抓包。再启动一个终端窗口,输入ping -c 3 www.baidu.com命令,执行ping命令,网络将产生ICMP的request请求包和ICMP的reply包。在这里插入图片描述
网络通讯中的checksum算法_第2张图片
抓包成功后得到下面的内容,一个ICMP请求包,一个完整的网络包是以太网头+IP头+ICMP包。选中ICMP部分,将ICMP包内容拷贝到文本文件中。网络通讯中的checksum算法_第3张图片
IP包头部分:
4500 0054 0000 4000 4001 b6e8 c0a8 031a 0ed7 b127
其中b6e8是checksum

ICMP请求包:
0800 5424 3a0f 0001 33f6 245a 2278 0400 0809 0a0b 0c0d 0e0f 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f 2021
2223 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 3435 3637
其中5424是checksum网络通讯中的checksum算法_第4张图片
ICMP回复包:
0000 5c24 3a0f 0001 33f6 245a 2278 0400 0809 0a0b 0c0d 0e0f 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f 2021
2223 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 3435 3637
其中5c24是checksum

4.3checksum算法设计与验证

<参考网址:http://blog.csdn.net/zjli321/article/details/74908451>
checksum较验是将原始数据每16位一组,进行带进位的累加,全部数据累结果超出16位则将进位部分再进行一次16位累加,累加后结果取反后作为最后的结果。网络通讯中的checksum算法_第5张图片
下面是计算checksum的例子算法代码。

//063.c checksum算法
#include "stdio.h"
unsigned short csum(unsigned short *addr, int count);
unsigned char data[] = 
{
    0x45,0x00,0x00,0x3c,
    0xe0,0x8a,0x00,0x00,
    0x40,0x01,0x00,0x00,//0x18,0xdd
    0xc0,0xa8,0x00,0x08,
    0xc0,0xa8,0x00,0x01
};
int main(void)
{
    printf("the check sum is 0x%x\n",csum((unsigned short *)data,sizeof(data)));
    return 0;
}

unsigned short csum(unsigned short *addr, int count)
{
    /* Compute Internet Checksum for "count" bytes
    * beginning at location "addr".
    */
    long sum = 0;
unsigned short tmp = 0;
//将原始数据取成16位数,加运算时以32位数相加,这样可以保留进位 
    while( count > 1 ) 
    {
        /* This is the inner loop */
        sum += (long)(* ((unsigned short *)addr++));
        count -= 2;
    }
    
    /* Add left-over byte, if any */
    if( count > 0 )
    {
        sum += * (unsigned char *) addr;//??
    }
/* Fold 32-bit sum to 16 bits */
//将高16位部分再加到低16位上
    while (sum>>16)
    {
        sum = (sum & 0xffff) + (sum >> 16);
    }    
    return ~sum;
}

请根据实验机器抓包内容,对IP头和ICMP头的数据,修改以上程序,验证checksum字段的计算过程,即查看程序对IP头部分运长输出是否为0xe8b6,进一步验证ICMP的checksum值。

4.4 Ping功能的实现

进一步验证checksum字段在实际网络协议中的应用。

/*064.c   ping.c*/ 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  /*bzero*/
#include 
#include 
/*保存已经发送包的状态值*/
typedef struct pingm_pakcet{
	struct timeval tv_begin;	/*发送的时间*/
	struct timeval tv_end;		/*接收到的时间*/
	short seq;					/*序列号*/
	int flag;		/*1,表示已经发送但没有接收到回应包0,表示接收到回应包*/
}pingm_pakcet;
//用数组来记录每个ICMP包
static pingm_pakcet pingpacket[128];
static pingm_pakcet *icmp_findpacket(int seq);
static unsigned short icmp_cksum(unsigned char *data,  int len);
static struct timeval icmp_tvsub(struct timeval end,struct timeval begin);
static void icmp_statistics(void);
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length );
static int icmp_unpack(char *buf,int len);
static void *icmp_recv(void *argv);
static void *icmp_send(void *argv);
static void icmp_sigint(int signo); 
#define K 1024
#define BUFFERSIZE 72					/*发送缓冲区大小*/
static char send_buff[BUFFERSIZE];
static char recv_buff[2*K];	/*为防止接收溢出,接收缓冲区设置大一些*/
static struct sockaddr_in dest;		/*目的地址*/
static int rawsock = 0;					/*发送和接收线程需要的socket描述符*/
static pid_t pid=0;						/*进程PID*/
static int alive = 0;					/*是否接收到退出信号*/
/*alive是一个全局变量,主线程开始将其值设为1,然后启动发送线程和接收线程,
发送线程会循环检测alive值是否为0,不为0则一直发送请求包,直到alive为0时退出。
接收线程会循环检测alive值是否为0,不为0则一直检测是否有包到达,直到alive为0时退出。
当用户按下ctrl+c键时,alive被icmp_sigint函数设为0。然后三个线程都结束
*/
static short packet_send = 0;			/*已经发送的数据包有多少*/
static short packet_recv = 0;			/*已经接收的数据包有多少*/
static char dest_str[80];				/*目的主机字符串*/
static struct timeval tv_begin, tv_end,tv_interval;
/*本程序开始发送、结束和时间间隔*/
 
/* Description of data base entry for a single service.    
struct protoent  
{  
  char *p_name;         // Official protocol name.  
  char **p_aliases;     // Alias list.    
  int p_proto;          // Protocol number.    
};


proto name: icmp
alias name: ICMP
proto number: 1
*/





/*主程序*/
int main(int argc, char *argv[])
{
	struct hostent * host = NULL;
	struct protoent *protocol = NULL;
	char protoname[]= "icmp";
	unsigned long inaddr = 1;
	int size = 128*K;
	/*参数是否数量正确*/
	if(argc < 2)
	{
		printf("Arguments count not corret.   usage:\n");
		printf("%s www.baidu.com\n",argv[0]);
		return -1;
	}
	/*获取协议类型ICMP*/
	//从字符串格式转化为协议值 
	protocol = getprotobyname(protoname);
	if (protocol == NULL)
	{
		perror("getprotobyname()");
		return -1;
	}
	/*复制目的地址字符串*/
	memcpy(dest_str,  argv[1], strlen(argv[1])+1);
	memset(pingpacket, 0, sizeof(pingm_pakcet) * 128);
	printf("[icmp]:protocol->p_proto=%d\n",protocol->p_proto);
	/*socket初始化*/
	rawsock = socket(AF_INET, SOCK_RAW,  protocol->p_proto);
	if(rawsock < 0)
	{
		perror("socket");
		return -1;
	}
	/*为了与其他进程的ping程序区别,加入pid*/
	//原书为 pid = getuid(); 改为getpid();
	pid = getpid();
	/*增大接收缓冲区,防止接收的包被覆盖*/
	setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
	bzero(&dest, sizeof(dest));
	/*获取目的地址的IP地址*/
	dest.sin_family = AF_INET;
	
	/*输入的目的地址为字符串IP地址*/
	inaddr = inet_addr(argv[1]);
	if(inaddr == INADDR_NONE)
	{
		/*输入的是DNS地址*/
		host = gethostbyname(argv[1]);
		if(host == NULL)
		{
			perror("gethostbyname");
			return -1;
		}
		/*将地址复制到dest中*/
		memcpy((char *)&dest.sin_addr, host->h_addr, host->h_length);
	}
	else		/*为IP地址字符串*/
	{
		memcpy((char*)&dest.sin_addr, &inaddr, sizeof(inaddr));
	} 
	/*打印提示*/
	inaddr = dest.sin_addr.s_addr;
	printf("PING %s 目标IP地址=%ld.%ld.%ld.%ld 56(84) bytes of data.\n", 
		dest_str, 
		(inaddr&0x000000FF)>>0,
		(inaddr&0x0000FF00)>>8,
		(inaddr&0x00FF0000)>>16,
		(inaddr&0xFF000000)>>24);
	/*截取信号SIGINT,将icmp_sigint挂接上*/
	//SIGINT由CTRL +C产生
	signal(SIGINT, icmp_sigint);
	alive = 1;						/*初始化为可运行*/
	pthread_t send_id, recv_id;		/*建立两个线程,用于发送和接收*/
	int err = 0;
	//创建发送ICMP包线程
	err = pthread_create(&send_id, NULL, icmp_send, NULL);		/*发送*/
	if(err < 0)
	{
		return -1;
	}
	//创建接收ICMP包线程
	err = pthread_create(&recv_id, NULL, icmp_recv, NULL);		/*接收*/
	if(err < 0)
	{
		return -1;
	}
	
	/*主线程等待发送线程和接收线程结束*/
	pthread_join(send_id, NULL);
	pthread_join(recv_id, NULL);
	/*清理并打印统计结果*/
	close(rawsock);
	icmp_statistics();
	return 0;	
}

/*CRC16校验和计算icmp_cksum
参数:
	data:数据
	len:指示data开始的数据长度
返回值:
	计算结果,short类型
*/
static unsigned short icmp_cksum(unsigned char *data,  int len)
{
	int sum=0;							/*计算结果*/
	int odd = len & 0x01;					/*是否为奇数*/
	/*将数据按照2字节为单位累加起来*/
	while( len & 0xfffe)  {
		sum += *(unsigned short*)data;
		data += 2;
		len -=2;
	}
	/*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一字节*/
	if( odd) {
		unsigned short tmp = ((*data)<<8)&0xff00;
		sum += tmp;
	}
	sum = (sum >>16) + (sum & 0xffff);	/*高低位相加*/
	sum += (sum >>16) ;					/*将进位继续加到当前值*/
	
	return ~sum; 							/*返回取反值*/
}

/*设置ICMP报头*/
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length )
{
	unsigned char i = 0;
	/*设置报头*/
	icmph->icmp_type = ICMP_ECHO;	/*ICMP回显请求*/
	icmph->icmp_code = 0;			/*code值为0*/
	icmph->icmp_cksum = 0;	        /*初始时将cksum字段值填写0*/
	icmph->icmp_seq = seq;			/*本报的序列号*/
	icmph->icmp_id = pid &0xffff;	/*填写PID*/
	for(i = 0; i< length; i++)
		icmph->icmp_data[i] = i;
									/*计算校验和,并将其赋值给cksum字段*/
	icmph->icmp_cksum = icmp_cksum((unsigned char*)icmph, length);
}

/*解压接收到的包,并打印信息*/
static int icmp_unpack(char *buf,int len)
{
	int iphdrlen;
	struct ip *ip = NULL;
	struct icmp *icmp = NULL;
	int rtt;
	
	ip=(struct ip *)buf; 					/*IP头部*/
	iphdrlen=ip->ip_hl*4;					/*IP头部长度*/
	icmp=(struct icmp *)(buf+iphdrlen);		/*ICMP段的地址*/
	len-=iphdrlen;
											/*判断长度是否为ICMP包*/
	if( len<8) 
	{
		printf("ICMP packets\'s length is less than 8\n");
		return -1;
	}
	/*ICMP类型为ICMP_ECHOREPLY并且为本进程的PID*/
	if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id== pid) )	
	{
		struct timeval tv_internel,tv_recv,tv_send;
		/*在发送表格中查找已经发送的包,按照seq*/
		pingm_pakcet* packet = icmp_findpacket(icmp->icmp_seq);
		if(packet == NULL)
			return -1;
		packet->flag = 0;	/*取消标志*/
		tv_send = packet->tv_begin;			/*获取本包的发送时间*/
		gettimeofday(&tv_recv, NULL);		/*读取此时间,计算时间差*/
		tv_internel = icmp_tvsub(tv_recv,tv_send);
		rtt = tv_internel.tv_sec*1000+tv_internel.tv_usec/1000; 
		/*打印结果,包含
		*  ICMP段长度
		*  源IP地址
		*  包的序列号
		*  TTL
		*  时间差
		*/
		printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",
			len,
			inet_ntoa(ip->ip_src),
			icmp->icmp_seq,
			ip->ip_ttl,
			rtt);
		
		packet_recv ++;						/*接收包数量加1*/
	}
	else
	{
		return -1;
	}
	return 0;
}

/*计算时间差 icmp_tvsub
参数:
	end,接收到的时间
	begin,开始发送的时间
返回值:
	使用的时间
*/
static struct timeval icmp_tvsub(struct timeval end,struct timeval begin)
{
	struct timeval tv;
	/*计算差值*/
	tv.tv_sec = end.tv_sec - begin.tv_sec;
	tv.tv_usec = end.tv_usec - begin.tv_usec;
	/*如果接收时间的usec值小于发送时的usec值,从usec域借位*/
	if(tv.tv_usec < 0)
	{
		tv.tv_sec --;
		tv.tv_usec += 1000000; 
	}
	
	return tv;
}

/*发送ICMP回显请求包--icmp_send线程*/
static void* icmp_send(void *argv)
{
	/*保存程序开始发送数据的时间*/
	gettimeofday(&tv_begin, NULL);
	while(alive)
	{
		int size = 0;
		struct timeval tv;
		gettimeofday(&tv, NULL);			/*当前包的发送时间*/
		/*在发送包状态数组中找一个空闲位置*/
		pingm_pakcet *packet = icmp_findpacket(-1);
		if(packet)
		{
			packet->seq = packet_send;		/*设置seq*/
			packet->flag = 1;				/*已经使用*/
			gettimeofday( &packet->tv_begin, NULL);	/*发送时间*/
		}
		/*生成ICMP请求包数据*/
		icmp_pack((struct icmp *)send_buff, packet_send, &tv, 64 );
		//使用rawsock将ICMP请求包发送出去
		size = sendto (rawsock,  send_buff, 64,  0,		/*发送给目的地址*/
			(struct sockaddr *)&dest, sizeof(dest) );
		if(size <0)
		{
			perror("sendto error");
			continue;
		}
		//记录请求包的发送数量,用于统计信息显示
		packet_send++;					/*计数增加*/
		/*每隔1s,发送一个ICMP回显请求包*/
		sleep(1);
	}
}

/*接收ping目的主机的回复  icmp_recv线程*/
static void *icmp_recv(void *argv)
{
	/*轮询等待时间*/
	struct timeval tv;
	tv.tv_usec = 200;
	tv.tv_sec = 0;
	fd_set  readfd;
	/*当没有信号发出一直接收数据*/
	while(alive)
	{
		int ret = 0;
		FD_ZERO(&readfd);
		FD_SET(rawsock, &readfd);
		ret = select(rawsock+1,&readfd, NULL, NULL, &tv);
		switch(ret)
		{
			case -1:
				/*错误发生*/
				break;
			case 0:
				/*超时*/
				break;
			default:
				{
					/*接收数据*/
					int size = recv(rawsock, recv_buff,sizeof(recv_buff), 
					0);
					if(errno == EINTR)
					{
						perror("recvfrom error");
						continue;
					}
					/*解包,并设置相关变量*/
					ret = icmp_unpack(recv_buff, size);
					if(ret == -1)
					{
						continue;
					}
				}
				break;
		}
		
	}
}

/*查找一个合适的包位置
*当seq为-1时,表示查找空包
*其他值表示查找seq对应的包*/
static pingm_pakcet *icmp_findpacket(int seq)
{
	int i=0;
	pingm_pakcet *found = NULL;
	/*查找包的位置*/
	if(seq == -1)							/*查找空包的位置*/
	{
		for(i = 0;i<128;i++)
		{
			if(pingpacket[i].flag == 0)
			{
				found = &pingpacket[i];
				break;
			}
			
		}
	}
	else if(seq >= 0)						/*查找对应seq的包*/
	{
		for(i = 0;i<128;i++)
		{
			if(pingpacket[i].seq == seq)
			{
				found = &pingpacket[i];
				break;
			}
			
		}
	}
	return found;
}

/*打印全部ICMP发送接收统计结果*/
static void icmp_statistics(void)
{       
	long time = (tv_interval.tv_sec * 1000 )+ (tv_interval.tv_usec/1000);
	printf("--- %s ping statistics ---\n",dest_str);	/*目的IP地址*/
	printf("%d packets transmitted, %d received, %d%% packet loss, time %ldms\n",
		packet_send,									/*发送*/
		packet_recv,  									/*接收*/
		(packet_send-packet_recv)*100/packet_send, 	/*丢失百分比*/
		time); 											/*时间*/
} 

/*终端信号处理函数SIGINT*/
static void icmp_sigint(int signo)
{
	
	alive = 0;							/*告诉接收和发送线程结束程序*/	
	gettimeofday(&tv_end, NULL);		/*读取程序结束时间*/	
	tv_interval = icmp_tvsub(tv_end, tv_begin);  /*计算一下总共所用时间*/
	
	return;
}

你可能感兴趣的:(Linux基础,算法,网络,java,python,嵌入式)