手写TCP/IP协议——IP层输入处理(八)
理论部分
根据子网掩码,判断数据包是发给内部/外部
发给外部网络的数据包,先发给默认网关(一般就是本地的路由器),再由路由器向外转发


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,今天的你就到此为止吧,明天还要接着🐺啊!
更多推荐


所有评论(0)