日期:2014-05-16  浏览次数:20833 次

Linux 内核路由机制(二) ---- ip层开始 -> 直到包被处理

接上面两篇:点击打开链接

                     点击打开链接


先看看ip头结构:

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)   // 小端
         __u8    ihl:4,                 // 首部长度(4位):首部长度指的是IP层头部占32 bit字的数目(也就是IP层头部包含多少个4字节 -- 32位),包括任何选项
                 version:4;             // 版本(4位),目前的协议版本号是4,称作IPv4。(注意头最长是:当4位全部取1即1111=15,那么15*32/8=60B)
 #elif defined (__BIG_ENDIAN_BITFIELD)  // 大端:调换位置
         __u8    version:4,             // 因为这两个字段共享一个字节,所以必须要区分大小端
                 ihl:4;
 #else
 #error  "Please fix <asm/byteorder.h>"
 #endif
         __u8    tos;                   // 服务类型字段(8位): 服务类型(TOS)字段包括一个3 bit的优先权子字段,4 bit的TOS子字段和1 bit未用位但必须置0。4 bit的TOS子字段分别代表:最小时延、最大吞吐量、最高可靠性和最小费用。4 bit中只能设置其中1 bit。如果所有4 bit均为0,那么就意味着是一般服务。
         __be16  tot_len;               // 总长度字段(16位)是指整个IP数据报的长度,以字节为单位。
         __be16  id;                    // 标识字段(16位)唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1。
         __be16  frag_off;              // frag_off域的低13位 -- 分段偏移(Fragment offset)域指明了该分段在当前数据报中的什么位置上。除了一个数据报的最后一个分段以外,其他所有的分段(分片)必须是8字节的倍数。这是8字节是基本分段单位。iphdr->frag_off的高3位(1) 比特0是保留的,必须为0;(2) 比特1是“更多分片”(MF -- More Fragment)标志。除了最后一片外,其他每个组成数据报的片都要把该比特置1。 (3) 比特2是“不分片”(DF -- Don't Fragment)标志,如果将这一比特置1,IP将不对数据报进行分片,这时如果有需要进行分片的数据报到来,会丢弃此数据报并发送一个ICMP差错报文给起始端。
         __u8    ttl;                   // 生存时间字段设置了数据报可以经过的最多路由器数
         __u8    protocol;              // 协议字段(8位):指明了该将它交给哪个传输进程。TCP是一种可能,但是UDP或者其他的协议也是可能的。
         __sum16 check;                 // 首部检验和字段(16位)是根据IP首部计算的检验和码。它不对首部后面的数据进行计算。
         __be32  saddr;                 // 源IP地址
         __be32  daddr;                 // 目的ip地址
         /*The options start here. */   // 
 };

下面从开始ip_rcv函数开始:

/*
 *      Main IP Receive routine.
 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, 

struct net_device *orig_dev)
{
         struct iphdr *iph;
         u32 len;
 
         if (dev->nd_net != &init_net)     // 看一下初始化的网络空间是不是包含这个设备,不匹配则丢包
                 goto drop;
 
         /* When the interface is in promisc. mode, drop all the crap
          * that it receives, do not try to analyse it.
          *///注意当包类型是PACKET_OTHERHOST时候,上一层就会直接丢掉所有的包,如果网卡此时被设置为promisc混杂模式,此时包就会传递到3层, 这个时侯内核会有hook函数来处理这个,而这里就只需要直接丢掉所有的包!
         if (skb->pkt_type == PACKET_OTHERHOST) // 类型查看在if_packet.h中
                 goto drop;
 
         IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES); // 给需要重组的碎片计数
 
         if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) { // 检查缓冲区skb是不是共享的!如果共享,克隆一个返回给skb,如果不是原skb返回!
                 IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);        // 如果为NULL,那么返回内存分配失败!
                 goto out;                              // 检查是否共享就是检查是不是超过一个人引用了这个skb~~~
         }
         // 注意参数是skb和ip的头长度,如果skb比头长度还小,肯定哪里出错了!直接error,否则ok~
         if (!pskb_may_pull(skb, sizeof(struct iphdr)))
                 goto inhdr_error;
 
         iph = ip_hdr(skb);   // 得到ip头(具体放入ip的头看上面的分析)
 
         /*
          *      RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the checksum.
          *
          *      Is the datagram acceptable?
          *
          *      1.      Length at least the size of an ip header
          *      2.      Version of 4
          *      3.      Checksums correctly. [Speed optimisation for later, ski