上一篇文章中我们说道了ip层的处理函数为ip_rcv,现在我们就来看一下网络层收到数据报后是如何处理的。
第一部分
不过在此之前我们先来看一下会用到的一些结构或函数。
1.net/tcp/ip.h:
84 struct ip_header
85 {
86 unsigned char ihl:4, version:4;
87 unsigned char tos;
88 unsigned short tot_len;
89 unsigned short id;
90 unsigned short frag_off;
91 unsigned char ttl;
92 unsigned char protocol;
93 unsigned short check;
94 unsigned long saddr;
95 unsigned long daddr;
96 /*The options start here. */
97 };
ip首部格式如下:
85 {
86 unsigned char ihl:4, version:4;
87 unsigned char tos;
88 unsigned short tot_len;
89 unsigned short id;
90 unsigned short frag_off;
91 unsigned char ttl;
92 unsigned char protocol;
93 unsigned short check;
94 unsigned long saddr;
95 unsigned long daddr;
96 /*The options start here. */
97 };
ip首部格式如下:
版本号(Version):长度4比特。标识目前采用的IP协议的版本号。一般的值为0100(IPv4),0110(IPv6)
IP包头长度(Header Length): 长度4比特。这个字段的作用是为了描述IP包头的长度,因为在IP包头中有变长的可选部分。该部分占4个bit位,单位为32bit(4个字节),即本区 域值= IP头部长度(单位为bit)/(8*4),因此,一个IP包头的长度最长为“1111”,即15*4=60个字节。IP包头最小长度为20字节。
服务类型(Type of Service):长度8比特。8位 按位被如下定义 PPP DTRC0
PPP:定义包的优先级,取值越大数据越重要
000 普通 (Routine)
001 优先的 (Priority)
010 立即的发送 (Immediate)
011 闪电式的 (Flash)
100 比闪电还闪电式的 (Flash Override)
101 CRI/TIC/ECP(找不到这个词的翻译)
110 网间控制 (Internetwork Control)
111 网络控制 (Network Control)
D 时延: 0:普通 1:延迟尽量小
T 吞吐量: 0:普通 1:流量尽量大
R 可靠性: 0:普通 1:可靠性尽量大
M 传输成本: 0:普通 1:成本尽量小
0 最后一位被保留,恒定为0
IP包总长(Total Length):长度16比特。 以字节为单位计算的IP包的长度 (包括头部和数据),所以IP包最大长度65535字节。
标识符(Identifier):长度16比特。该字段和Flags和Fragment Offest字段联合使用,对较大的上层数据包进行分段(fragment)操作。路由器将一个包拆分后,所有拆分开的小包被标记相同的值,以便目的端设备能够区分哪个包属于被拆分开的包的一部分。
标记(Flags): 长度3比特。该字段第一位不使用。第二位是DF(Don't Fragment)位,DF位设为1时表明路由器不能对该上层数据包分段。如果一个上层数据包无法在不分段的情况下进行转发,则路由器会丢弃该上层数据包 并返回一个错误信息。第三位是MF(More Fragments)位,当路由器对一个上层数据包分段,则路由器会在除了最后一个分段的IP包的包头中将MF位设为1。
片偏移(Fragment Offset):长度13比特。表示该IP包在该组分片包中位置,接收端靠此来组装还原IP包。
生存时间(TTL):长度8比特。当IP包进行传送时,先会对该字段赋予某个特定的值。当IP包经过每一个沿途的路由器的时候,每个沿途的路由器会将IP包的TTL值减少1。如果TTL减少为0,则该IP包会被丢弃。这个字段可以防止由于路由环路而导致IP包在网络中不停被转发。
协议(Protocol):长度8比特。标识了上层所使用的协议。
以下是比较常用的协议号:
1 ICMP
2 IGMP
6 TCP
17 UDP
88 IGRP
89 OSPF
头部校验(Header Checksum):长度16位。用来做IP头部的正确性检测,但不包含数据部分。 因为每个路由器要改变TTL的值,所以路由器会为每个通过的数据包重新计算这个值。
起源和目标地址(Source and Destination Addresses):这两个地段都是32比特。标识了这个IP包的起源和目标地址。要注意除非使用NAT,否则整个传输的过程中,这两个地址不会改变。
IP包头长度(Header Length): 长度4比特。这个字段的作用是为了描述IP包头的长度,因为在IP包头中有变长的可选部分。该部分占4个bit位,单位为32bit(4个字节),即本区 域值= IP头部长度(单位为bit)/(8*4),因此,一个IP包头的长度最长为“1111”,即15*4=60个字节。IP包头最小长度为20字节。
服务类型(Type of Service):长度8比特。8位 按位被如下定义 PPP DTRC0
PPP:定义包的优先级,取值越大数据越重要
000 普通 (Routine)
001 优先的 (Priority)
010 立即的发送 (Immediate)
011 闪电式的 (Flash)
100 比闪电还闪电式的 (Flash Override)
101 CRI/TIC/ECP(找不到这个词的翻译)
110 网间控制 (Internetwork Control)
111 网络控制 (Network Control)
D 时延: 0:普通 1:延迟尽量小
T 吞吐量: 0:普通 1:流量尽量大
R 可靠性: 0:普通 1:可靠性尽量大
M 传输成本: 0:普通 1:成本尽量小
0 最后一位被保留,恒定为0
IP包总长(Total Length):长度16比特。 以字节为单位计算的IP包的长度 (包括头部和数据),所以IP包最大长度65535字节。
标识符(Identifier):长度16比特。该字段和Flags和Fragment Offest字段联合使用,对较大的上层数据包进行分段(fragment)操作。路由器将一个包拆分后,所有拆分开的小包被标记相同的值,以便目的端设备能够区分哪个包属于被拆分开的包的一部分。
标记(Flags): 长度3比特。该字段第一位不使用。第二位是DF(Don't Fragment)位,DF位设为1时表明路由器不能对该上层数据包分段。如果一个上层数据包无法在不分段的情况下进行转发,则路由器会丢弃该上层数据包 并返回一个错误信息。第三位是MF(More Fragments)位,当路由器对一个上层数据包分段,则路由器会在除了最后一个分段的IP包的包头中将MF位设为1。
片偏移(Fragment Offset):长度13比特。表示该IP包在该组分片包中位置,接收端靠此来组装还原IP包。
生存时间(TTL):长度8比特。当IP包进行传送时,先会对该字段赋予某个特定的值。当IP包经过每一个沿途的路由器的时候,每个沿途的路由器会将IP包的TTL值减少1。如果TTL减少为0,则该IP包会被丢弃。这个字段可以防止由于路由环路而导致IP包在网络中不停被转发。
协议(Protocol):长度8比特。标识了上层所使用的协议。
以下是比较常用的协议号:
1 ICMP
2 IGMP
6 TCP
17 UDP
88 IGRP
89 OSPF
头部校验(Header Checksum):长度16位。用来做IP头部的正确性检测,但不包含数据部分。 因为每个路由器要改变TTL的值,所以路由器会为每个通过的数据包重新计算这个值。
起源和目标地址(Source and Destination Addresses):这两个地段都是32比特。标识了这个IP包的起源和目标地址。要注意除非使用NAT,否则整个传输的过程中,这两个地址不会改变。
第二部分
net/tcp/ip.c:
703 int
704 ip_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
705 {
706 struct ip_header *iph;
707 unsigned char hash;
708 unsigned char flag=0;
709 static struct options opt; /* since we don't use these yet, and they
710 take up stack space. */
711 struct ip_protocol *ipprot;
712
713 iph=skb->h.iph;
因为这里是IP协议的处理,所以iph就指向sk_buff的h.iph属性,它指向的是ip数据报头的首地址。
715 PRINTK("<<\n");
716 print_iph(iph);
打印信息
704 ip_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
705 {
706 struct ip_header *iph;
707 unsigned char hash;
708 unsigned char flag=0;
709 static struct options opt; /* since we don't use these yet, and they
710 take up stack space. */
711 struct ip_protocol *ipprot;
712
713 iph=skb->h.iph;
因为这里是IP协议的处理,所以iph就指向sk_buff的h.iph属性,它指向的是ip数据报头的首地址。
715 PRINTK("<<\n");
716 print_iph(iph);
打印信息
718 if (ip_csum (iph) || do_options (iph,&opt) || iph->version != 4)
719 {
720 PRINTK ("ip packet thrown out. \n");
721 skb->sk = NULL;
722 kfree_skb(skb, 0);
723 return (0);
724 }
718行进行了一系列的首部校验工作(包括检验和,可选参数和ip协议版本),如果校验未通过就丢掉该数据报,释放内存,返回。
719 {
720 PRINTK ("ip packet thrown out. \n");
721 skb->sk = NULL;
722 kfree_skb(skb, 0);
723 return (0);
724 }
718行进行了一系列的首部校验工作(包括检验和,可选参数和ip协议版本),如果校验未通过就丢掉该数据报,释放内存,返回。
726 /* for now we will only deal with packets meant for us. */
727 if (!my_ip_addr(iph->daddr))
728 {
729 PRINTK ("packet meant for someone else.\n");
730 skb->sk = NULL;
731 kfree_skb(skb, 0);
732 return (0);
733 }
这里就是看一下数据报是不是发给我的,根据的是目的IP地址。如果不是直接丢弃,释放内存并返回。
727 if (!my_ip_addr(iph->daddr))
728 {
729 PRINTK ("packet meant for someone else.\n");
730 skb->sk = NULL;
731 kfree_skb(skb, 0);
732 return (0);
733 }
这里就是看一下数据报是不是发给我的,根据的是目的IP地址。如果不是直接丢弃,释放内存并返回。
735 /* deal with fragments. or don't for now.*/
736 if ((iph->frag_off & 32) || (net16(iph->frag_off)&0x1fff))
737 {
738 printk ("packet fragmented. \n");
739 skb->sk = NULL;
740 kfree_skb(skb, 0);
741 return(0);
742 }
这是处理IP分片的,根据第一部分中对ip首部的描述,726行就是判断ip数据报是否分片了,如果分片的话,738行打印信息,然后释放内存并返回。也就是0.99版不允许分片。
736 if ((iph->frag_off & 32) || (net16(iph->frag_off)&0x1fff))
737 {
738 printk ("packet fragmented. \n");
739 skb->sk = NULL;
740 kfree_skb(skb, 0);
741 return(0);
742 }
这是处理IP分片的,根据第一部分中对ip首部的描述,726行就是判断ip数据报是否分片了,如果分片的话,738行打印信息,然后释放内存并返回。也就是0.99版不允许分片。
744 skb->h.raw += iph->ihl*4;
让raw指向ip数据报的数据部分,也就是传输层数据报的首部位置。
让raw指向ip数据报的数据部分,也就是传输层数据报的首部位置。
746 hash = iph->protocol & (MAX_IP_PROTOS -1);
获取协议hash值作为索引,下面将遍历这些协议
747 for (ipprot = ip_protos[hash]; ipprot != NULL; ipprot=ipprot->next)
748 {
749 struct sk_buff *skb2;
750 if (ipprot->protocol != iph->protocol) continue;
751 PRINTK ("Using protocol = %X:\n", ipprot);
752 print_ipprot (ipprot);
753 /* pass it off to everyone who wants it. */
754 /* we should check the return values here. */
755 /* see if we need to make a copy of it. This will
756 only be set if more than one protpocol wants it.
757 and then not for the last one. */
758
759 if (ipprot->copy)
760 {
761 skb2 = kmalloc (skb->mem_len, GFP_ATOMIC);
762 if (skb2 == NULL) continue;
763 memcpy (skb2, skb, skb->mem_len);
764 skb2->mem_addr = skb2;
765 skb2->lock = 0;
766 skb2->h.raw = (void *)((unsigned long)skb2
767 + (unsigned long)skb->h.raw
768 - (unsigned long)skb);
747 for (ipprot = ip_protos[hash]; ipprot != NULL; ipprot=ipprot->next)
748 {
749 struct sk_buff *skb2;
750 if (ipprot->protocol != iph->protocol) continue;
751 PRINTK ("Using protocol = %X:\n", ipprot);
752 print_ipprot (ipprot);
753 /* pass it off to everyone who wants it. */
754 /* we should check the return values here. */
755 /* see if we need to make a copy of it. This will
756 only be set if more than one protpocol wants it.
757 and then not for the last one. */
758
759 if (ipprot->copy)
760 {
761 skb2 = kmalloc (skb->mem_len, GFP_ATOMIC);
762 if (skb2 == NULL) continue;
763 memcpy (skb2, skb, skb->mem_len);
764 skb2->mem_addr = skb2;
765 skb2->lock = 0;
766 skb2->h.raw = (void *)((unsigned long)skb2
767 + (unsigned long)skb->h.raw
768 - (unsigned long)skb);
这里是为相应对象(对此数据报感兴趣的)拷贝数据报副本的过程
769 }
770 else
771 {
772 skb2 = skb;
773 }
769 }
770 else
771 {
772 skb2 = skb;
773 }
774 flag = 1;
775 ipprot->handler (skb2, dev, &opt, iph->daddr,
776 net16(iph->tot_len) - iph->ihl*4,
777 iph->saddr, 0, ipprot);
778 调用相应协议的注册的处理函数
779 }//for
780 if (!flag)
781 {
782 icmp_reply (skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, dev);
783 skb->sk = NULL;
784 kfree_skb (skb, 0);
785 }
781 {
782 icmp_reply (skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, dev);
783 skb->sk = NULL;
784 kfree_skb (skb, 0);
785 }
780行如果为true,说明flag=0,也就是没有找到对应的目标对象来传输该数据报,这时就需要向源主机发送主机ICMP差错报文。
788 return (0);
789 }
这里的ip_protos与《linux0.99网络模块-数据链路层(接收)》中ptype_base作用是一样的,ptype_base以链表方式保存了注册到数据链路层的协议,而ip_protos则保存了注册到网络层的协议,有所不同的是它采用的是数组加链表的方式保存的。
788 return (0);
789 }
这里的ip_protos与《linux0.99网络模块-数据链路层(接收)》中ptype_base作用是一样的,ptype_base以链表方式保存了注册到数据链路层的协议,而ip_protos则保存了注册到网络层的协议,有所不同的是它采用的是数组加链表的方式保存的。
总结
上一篇文章中提到,链路层接收到数据报后会向注册到链路层中的协议进行传递。因为ip层注册到了链路层,而且处理函数为ip_rcv,本文分析了这个函数的操作流程。它在收到数据报后会对首部进行检查(校验和,选项参数,ip协议版本),通过检查后,进一步检验是否是发给本机的(目标ip地址是否是本主机),通过检查后进一步检查是否进行了分片(这里的处理还比较简陋,遇到分片的直接丢弃),通过检查后处理一下当前数据报,把h.raw指向上层协议首部起始地址。到此通过全部健康检查,可以向上传递(前面任何一项不满足都会丢弃数据报,释放内存,返回)。接下来就遍历注册到IP层的上层协议,如果有多余一个上层协议对此数据报感兴趣就需要拷贝一个副本传给它注册的函数来处理。最后,如果该数据报没有找到对其感兴趣的对象,就给源主机发送ICMP差错报文。接下来的文章中,我们的就来看看传输层是如何处理接收到的数据报的。