数据包接收系列 — IP协议处理流程(一)

本文主要内容:在接收数据包时,IP协议的处理流程。

内核版本:2.6.37

Author:zhangskd @ csdn blog 

 

IP报头

 

IP报头:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct iphdr {  
  2. #if defined(__LITTLE_ENDIAN_BITFIELD)  
  3.     __u8 ihl:4,  
  4.          version:4;  
  5. #elif defined(__BIG_ENDIAN_BITFIELD)  
  6.     __u8 version:4/* 协议版本,IPv4为4 */  
  7.          ihl:4/* 首部长度,不包括选项为5,表示20字节 */  
  8. #else  
  9. #error "Please fix <asm/byteorder.h>"  
  10. #endif  
  11.   
  12.     __u8 tos; /* TOS服务类型,6位DSCP,2为ECN */  
  13.     __be16 tot_len; /* IP包总长度,最大为65535 */  
  14.     __be16 id; /* 标识符,同一个IP包的不同分片具有相同的标识符 */  
  15.     __be16 frag_off; /* 3个标志位,13位偏移 */  
  16.     __u8 ttl; /* 存活时间,一般为64跳 */  
  17.     __u8 protocol; /* L4协议值 */  
  18.     __sum16 check; /* 报头校验和,不包含载荷 */  
  19.     __be32 saddr; /* 源IP */  
  20.     __be32 daddr; /* 目的IP */  
  21. };   

 

ip_rcv

 

调用ip_rcv()时skb中的一些变量:

 

ip_rcv()是IP层的入口,主要做了:

丢弃L2目的地址不是本机的数据包(这说明网卡处于混杂模式,嗅探器会处理这些包)。

检查skb的引用计数,如果大于1,说明其它地方也在使用此skb,则克隆一个skb返回;否则直接返回原来的skb。

数据包合法性检查:

data room必须大于IP报头长度。

IP报头长度至少是20,类型为IPv4。

data room至少能容纳IP报头(包括IP选项)。

检查IP报头校验和是否正确。

数据包没被截断(skb->len >= 报总长),报总长不小于20。

如果L2有进行填充(以太网帧最小长度为64),则把IP包裁剪成原大小,去除填充。此时如果接收的NIC

已计算出校验和,则让其失效,让L4自己重新计算。

最后,调用netfilter的NF_INET_PRE_ROUTING的钩子函数,如果此数据包被钩子函数放行,则调用

ip_rcv_finish()继续处理。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /* Main IP Receive routinue. */  
  2.   
  3. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,   
  4.     struct net_device *orig_dev)  
  5. {  
  6.     struct iphdr *iph;  
  7.     u32 len;  
  8.   
  9.     /* When the interface is in promisc mode, drop all the crap that it receives, 
  10.      * do not try to analyse it. 
  11.      * 当数据帧的L2目的地址和接收接口的地址不同时,skb->pkt_type就被设成PACKET_OTHERHOST。 
  12.      * 网卡本身会丢弃这些包,除非设成混杂模式。嗅探器自会处理这种包,IP层无需理会。 
  13.      */  
  14.     if (skb->pkt_type == PACKET_OTHERHOST)  
  15.         goto drop;  
  16.   
  17.     IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);  
  18.   
  19.     /* 如果此skb的引用计数大于1,说明在其它地方也被使用,则克隆一个skb返回。 
  20.      * 否则直接返回原来的skb。 
  21.      */  
  22.     if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {  
  23.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);  
  24.         goto out;  
  25.     }  
  26.   
  27.     /* 确保data room >= IP报头 */  
  28.     if (! pskb_may_pull(skb, sizeof(struct iphdr)))  
  29.         goto inhdr_error;  
  30.   
  31.     iph = ip_hdr(skb);  
  32.    
  33.     /* 
  34.      * RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum. 
  35.      * Is the datagram acceptable? 
  36.      * 1. Length at least the size of an ip header 
  37.      * 2. Version of 4 
  38.      * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums] 
  39.      * 4. Doesn't have a bogus length 
  40.      */  
  41.   
  42.     /* IP报头长度至少是20,类型为IPv4 */  
  43.     if (iph->ihl < 5 || iph->version != 4)  
  44.         goto inhdr_error;  
  45.   
  46.     /* data room至少能容纳IP报头(包括IP选项) */  
  47.     if (! pskb_may_pull(skb, iph->ihl * 4))  
  48.         goto inhdr_error;  
  49.   
  50.     iph = ip_hdr(skb);  
  51.   
  52.     /* 检查IP报头校验和是否正确 */  
  53.     if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))  
  54.         goto inhdr_error;  
  55.   
  56.     len = ntohs(iph->tot_len); /* IP报文总长度 */  
  57.   
  58.     /* L2为了满足最小帧的长度可能会进行填充,所以skb->len >= len。 
  59.      * Ethernet数据帧的最小帧长度为64字节。 
  60.      */  
  61.     if (skb->len < len) { /* 数据包被截断了 */  
  62.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);  
  63.     } else if (len < (iph->ihl * 4))  
  64.         goto inhdr_error;  
  65.   
  66.     /* Our transport medium may have padded the buffer out. Now we know it is 
  67.      * IP we can trim to the true length of the frame. 
  68.      * Note this now means skb->len holds ntohs(iph->tot_len). 
  69.      */  
  70.   
  71.     /* 如果L2有进行填充,则把IP包裁剪成原大小。 
  72.      * 如果接收的NIC已计算出校验和,则让其失效,让L4自己重新计算。 
  73.      */  
  74.     if (pskb_trim_rcsum(skb, len)) {  
  75.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);  
  76.         goto drop;  
  77.     }  
  78.   
  79.     /* Remove any debris in the socket control block */  
  80.     memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));  
  81.   
  82.     /* Must drop socket now because of tproxy. */  
  83.     skb_orphan(skb);  
  84.   
  85.     /* 调用netfilter的NF_INET_PRE_ROUTING钩子 */  
  86.     return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,  
  87.                    ip_rcv_finish);  
  88.   
  89. inhdr_error:  
  90.    IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);  
  91.   
  92. drop:  
  93.     kfree_skb(skb);  
  94.   
  95. out:  
  96.     return NET_RX_DROP;  
  97. }  

如果skb的引用计数大于1,说明在其它地方也被使用,则克隆一个skb返回,否则直接返回原来的skb。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * skb_shard_check - check if buffer is shard and if so clone it 
  3.  * @skb: buffer to check 
  4.  * @pri: priority for memory allocation 
  5.  *  
  6.  * If the buffer is shared the buffer is cloned and the old copy drops a 
  7.  * reference. A new clone with a single reference is returned. 
  8.  * If the buffer is not shared the original buffer is returned. When being called 
  9.  * from interrupt status or with spinlocks held pri must be GFP_ATOMIC. 
  10.  * NULL is returned on a memory allocation failure. 
  11.  */  
  12.   
  13. static inline struct sk_buff *skb_shared_check(struct sk_buff *skb, gfp_t pri)  
  14. {  
  15.     /* 不能睡眠,否则调用might_sleep()打印栈的回溯信息 */  
  16.     might_sleep_if(pri & __GFP_WAIT);   
  17.   
  18.     if (skb_shared(skb)) { /* skb->users是否为1 */  
  19.         struct sk_buff *nskb = skb_clone(skb, pri);  
  20.         kfree_skb(skb);  
  21.         skb = nskb;  
  22.     }  
  23.   
  24.     return skb;  
  25. }  
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * skb_orphan - orphan a buffer 
  3.  * @skb: buffer to orphan 
  4.  * If a buffer currently has an owner then we call the owner's destructor 
  5.  * function and make the @skb unowned. The buffer continues to exist 
  6.  * but is no longer charged to its former owner. 
  7.  */  
  8. static inline void skb_orphan(struct sk_buff *skb)  
  9. {  
  10.     if (skb->destructor)  
  11.         skb->destructor(skb);  
  12.   
  13.     skb->destructor = NULL;  
  14.     skb->sk = NULL;  
  15. }  

  

ip_rcv_finish

 

ip_rcv_finish()主要做了:

查找路由,决定要把数据包发送到哪,赋值skb_dst()->input(),发往本地为ip_local_deliver,转发为ip_forward()。

更新Traffic Control (Qos)层的统计数据。

处理IP选项,检查选项是否正确,然后将选项存储在IPCB(skb)->opt中。

最后执行skb_dst()->input(),要么发往四层,要么进行转发,取决于IP的目的地址。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. static int ip_rcv_finish(struct sk_buff *skb)  
  2. {  
  3.     const struct iphdr *iph = ip_hdr(skb);  
  4.     struct rtable *rt;  
  5.   
  6.     /*  
  7.      * Initialise the virtual path cache for the packet. 
  8.      * It describes how the packet travels inside linux networking. 
  9.      */  
  10.     if (skb_dst(skb) == NULL) {  
  11.   
  12.         /* 查找路由,决定要把包送往哪里 */  
  13.         int err = ip_route_input_noref(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);  
  14.   
  15.         if (unlikely(err)) {  
  16.             if (err == -EHOSTUNREACH) /* no route to host,主机不可达 */  
  17.                 IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INADDRERRORS);  
  18.             else if (err == -ENETUNREACH) /* Network is unreachable,网络不可达 */  
  19.                 IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INNOROUTES);  
  20.             else if (err == -EXDEV) /* Cross-device link */  
  21.                 NET_INC_STATS_BH(dev_net(skb->dev), LINUX_MIB_IPRPFILTER);  
  22.   
  23.             goto drop; /* 目的地不可达,丢弃 */  
  24.         }  
  25.     }  
  26.   
  27. /* 更新Traffic Control (Qos)层的统计数据 */  
  28. #ifdef CONFIG_NET_CLS_ROUTE  
  29.     if (unlikely(skb_dst(skb)->tclassid)) {  
  30.         struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);  
  31.         u32 idx = skb_dst(skb)->tclassid;  
  32.         st[idx & 0xFF].o_packets++;  
  33.         st[idx & 0xFF].o_bytes += skb->len;  
  34.         st[(idx >> 16) & 0xFF].i_packets++;  
  35.         st[(idx >> 16) & 0xFF].i_bytes += skb->len;  
  36.     }  
  37. #endif  
  38.   
  39.     /* 处理IP选项,调用ip_options_compile()来检查选项是否正确,然后将选项存储 
  40.      * 在IPCB(skb)->opt中。 
  41.      */  
  42.     if (iph->ihl > 5 && ip_rcv_options(skb))  
  43.         goto drop;  
  44.   
  45.     rt = skb_rtable(skb);  
  46.     if (rt->rt_type == RTN_MULTICAST) {  
  47.         IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST, skb->len);  
  48.     } else if (rt->rt_type == RTN_BROADCAST)  
  49.         IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST, skb->len);  
  50.   
  51.     /* skb_dst(skb)->input()在ip_route_input_noref()中被赋值,要么是ip_local_deliver(), 
  52.     * 要么是ip_forward(),取决于数据包的目的地址。 
  53.     */  
  54.     return dst_input(skb);  
  55.   
  56. drop:  
  57.     kfree_skb(skb);  
  58.     return NET_RX_DROP;  
  59. }  
  60.   
  61. /* Input packet from network to transport. */  
  62. static inline int dst_input(struct sk_buff *skb)  
  63. {  
  64.     return skb_dst(skb)->input(skb);  
  65. }  

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值