理论部分

根据子网掩码,判断数据包是发给内部/外部

发给外部网络的数据包,先发给默认网关(一般就是本地的路由器),再由路由器向外转发

IP数据包分为IP数据包的包头部分和IP数据包的body部分

IP数据包最大64KB,而以太网只有1506B,有时候需要分片(本课程不分片)

Time to live字段每经过一个路由器就会-1,减到0路由器就不会再对此数据包进行转发了

代码实现部分

 1)在xnet_tiny.h中定义两个IP层的函数:

#define XNET_VERSION_IPV4	4
void xip_init(void);
void xip_in(xnet_packet_t* packet);

xip_init函数在xnet_init当中调用,完成IP层的初始化

void xnet_init(void)
{
	ethernet_init();
	xarp_init();
	xip_init();
}

在ethernet_in函数中调用xip_in来处理IP数据包

switch (swap_order16(hdr->protocol))
	{
	case XNET_PROTOCOL_ARP:
		remove_header(packet, sizeof(xether_hdr_t));
		xarp_in(packet);
		break;
	case XNET_PROTOCOL_IP:
		remove_header(packet, sizeof(xether_hdr_t));
		xip_in(packet);
		break;
	}
}

然后我们定义IPV4的数据结构,注意:一定要夹在pack(1)中间 :

typedef struct _xip_hdr_t
{
	uint8_t hdr_len : 4;	//首部长度(单位4B),低4位
	uint8_t version : 4;	//版本号,高4位
	uint8_t tos;			//服务类型(默认填0)
	uint16_t total_len;		//首部+数据(最大65535B)
	uint16_t id;			//IP数据包分片ID
	uint16_t flags_fragment;//标识位+分片偏移位置
	uint8_t ttl;			//生存时间(每经过一个路由器-1)
	uint8_t protocol;		//上层协议(ICMP/UDP/TCP)
	uint16_t hdr_checksum;	//首部校验和
	uint8_t src_ip[XNET_IPV4_ADDR_SIZE];
	uint8_t dst_ip[XNET_IPV4_ADDR_SIZE];
}xip_hdr_t;

2)xnet_tiny.c中添加IP层的初始化函数(暂不实现)

//IP层的初始化
void xip_init(void) {

}

再添加IP层的输入处理函数: 

void xip_in(xnet_packet_t* packet)
{
	xip_hdr_t* iphdr = (xip_hdr_t*)packet->data;
	uint32_t total_size, header_size;
	uint16_t pre_checksum;
	xipaddr_t src_ip;

	if (iphdr->version != XNET_VERSION_IPV4)
		return;
	header_size = iphdr->hdr_len * 4;
	total_size = swap_order16(iphdr->total_len);
	if ((header_size < sizeof(xip_hdr_t)) || ((total_size < header_size) || (packet->size < total_size)))
		return;

	pre_checksum = iphdr->hdr_checksum;
	iphdr->hdr_checksum = 0;
	if (pre_checksum != checksum16((uint16_t*)iphdr, header_size, 0, 1))
		return;
    iphdr->hdr_checksum = pre_checksum;
	if (!xipaddr_is_equal_buf(&netif_ipaddr, iphdr->dst_ip))
		return;

	//IP层的多路复用(ICMP/UDP/TCP)
	switch (iphdr->protocol)
	{
	default:
		break;
	}
}

浅剖析一下此函数:

①IP包头的长度单位是4字节,所以想要得到包头多少字节的话需要使用hdr_len*4

②IP的校验和只是计算了IP包包头的部分,不会对载荷部分进行校验

③IP包发送的时候,填写包头的字段时将checksum填0 ,然后对整个IP包头计算一遍之后再写到checksum中去,接收的时候同样的是先把checksum置零,然后在本地协议栈再计算一遍校验和,观察是否和发送时的计算结果一致,如果一致的话,就说明通过校验;另外需要说明的是即使通过了校验也不能说明数据一定是正确的,感兴趣的话可以自己去文档里看一下。

④还有判断一下这个包是不是发给本地协议栈的,因为在网络中发给别人的包也有可能发到我这里,比如我们的ICMP的广播包,我们可以收到但是经检查不是发给我的,就直接丢弃掉就好了,这种处理机制本身也可以认作是一种无回报的处理方式。

⑤最后我们就可以根据IP包中的协议字段,选择去交给哪个上层的协议实体去做进一步的处理了

如上图所示:数据从上层交付过来,准备从上往下发的时候,不同的协议都直接扔给IP即可

3)接下来完成校验和函数checksum16的实现

static uint16_t checksum16(uint16_t* buf, uint16_t len, uint16_t pre_sum, int complement) {

	uint32_t checksum = pre_sum;
	uint16_t high;

	while (len > 1) {
		//指针一次++2B
		checksum += *buf++;
		len -= 2;
	}
	if (len > 0) {
		checksum += *(uint8_t*)buf;
	}

	//
	while ((high=checksum>>16)!=0)
	{
		checksum = high + (checksum & 0xffff);
	}
	return complement ? (uint16_t)~checksum : (uint16_t)checksum;
}

我们对这个函数做一个简单的分析:

① 关于校验和的网络标准算法规定是按照16bit进行累加

② 如果长度为奇数,则把低字节再加进来

③ 由于网络标准规定校验和是16bit,加完之后,有可能出现checksum在累加的过程中变成32位的情况发生,因此我们需要判断checksum的高16位是不是非0,如果是非0的话,我们就把高16位加到低16位当中,直到高16位变成0为止

④ 最后按照要求决定是否将16bit的cheksum取反

调试部分

① 先在ethernet_in的XNET_PROTOCOL_IP打个断点,进入xip_in函数

② 测试在 xip_in里面的校验能否全部通过

如果你能够成功进入switch (iphdr->protocol),那么恭喜你可以继续前进了!!!

ok,今天的你就到此为止吧,明天还要接着🐺啊!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐