TCP/IP学习(34)——IP包的发送流程(1)

940阅读 0评论2013-11-08 491357604
分类:LINUX

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
    

IP报文的发送的入口函数是ip_output,这个函数一般是挂载到dst->output上。在发送IP报文时,都需要进行路由查找,得到dst,然后通过dst->output来发送数据包。对于IPv4来说,一般情况下,dst->output都是指向ip_output。

  1. int ip_output(struct sk_buff *skb)
  2. {
  3.     /* 得到出口的dev */
  4.     struct net_device *dev = skb_dst(skb)->dev;
     /* 增加统计信息 */
  1.     IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);
     
  1.     skb->dev = dev;
  2.     skb->protocol = htons(ETH_P_IP);
     
     /* 进入netfilter的POSTROUTING链  */
  1.     return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev,
  2.              ip_finish_output,
  3.              !(IPCB(skb)->flags & IPSKB_REROUTED));
  4. }
进入ip_finish_output
  1. static int ip_finish_output(struct sk_buff *skb)
  2. {
  3. #if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
  4.     /* Policy lookup after SNAT yielded a new policy */
  5.     if (skb_dst(skb)->xfrm != NULL) {
  6.         IPCB(skb)->flags |= IPSKB_REROUTED;
  7.         return dst_output(skb);
  8.     }
  9. #endif
  10.     /* 处理需要IP分片的情况 */
  11.     if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))
  12.         return ip_fragment(skb, ip_finish_output2);
  13.     /* 不需要IP分片, 我们就看这种一般情况 */
  14.     else
  15.         return ip_finish_output2(skb);
  16. }
进入ip_finish_output2
  1. static inline int ip_finish_output2(struct sk_buff *skb)
  2. {
  3.     struct dst_entry *dst = skb_dst(skb);
  4.     struct rtable *rt = (struct rtable *)dst;
  5.     struct net_device *dev = dst->dev;
  6.     unsigned int hh_len = LL_RESERVED_SPACE(dev);
     
     /* 根据路由的类型,增加统计信息 */
  1.     if (rt->rt_type == RTN_MULTICAST) {
  2.         IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUTMCAST, skb->len);
  3.     } else if (rt->rt_type == RTN_BROADCAST)
  4.         IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUTBCAST, skb->len);

  5.     /* Be paranoid, rather than too clever. */
  6.     if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
  7.         /* 
  8.         skb的首部空间不足,无法保存l2层的硬件地址。
  9.         这时,需要重新分配一个更大bufer。
  10.         */
  11.         struct sk_buff *skb2;

  12.         skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
  13.         if (skb2 == NULL) {
  14.             kfree_skb(skb);
  15.             return -ENOMEM;
  16.         }
  17.         if (skb->sk)
  18.             skb_set_owner_w(skb2, skb->sk);
  19.         kfree_skb(skb);
  20.         skb = skb2;
  21.     }
     
     /* dst有L2层硬件地址的cache*/
  1.     if (dst->hh)
  2.         return neigh_hh_output(dst->hh, skb);
  3.     /* 
  4.     dst没有L2层地址的cache,需要调用neighbour子系统的output进行发送。
  5.     */
  6.     else if (dst->neighbour)
  7.         return dst->neighbour->output(skb);

     /* 
     正常情况下,不会到达此处
     */
  1.     if (net_ratelimit())
  2.         printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
  3.     kfree_skb(skb);
  4.     return -EINVAL;
  5. }
在IPv4中,neighbour subsystem使用的是arp,所以一般情况下,dst->neighbour->outpu指向的是arp_generic_ops->neigh_resolve_output(这是需要解析L2硬件地址的情况,其他情况本文暂不考虑)。

通常,neighbour subsystem使用的operation都是arp_generic_ops(在arp.c中定义,且还有其他的同类型变量,用于不同情况)。

下面看neigh_resolve_output
  1. int neigh_resolve_output(struct sk_buff *skb)
  2. {
  3.     struct dst_entry *dst = skb_dst(skb);
  4.     struct neighbour *neigh;
  5.     int rc = 0;

  6.     if (!dst || !(neigh = dst->neighbour))
  7.         goto discard;
  1.     __skb_pull(skb, skb_network_offset(skb));
    
     /* 判断是否发送neigh请求,对于IPv4来说就是arp request */
  1.     if (!neigh_event_send(neigh, skb)) {
  2.         /* 无需发送neigh请求, 可以直接从dev中获得 */
  3.         int err;
  4.         struct net_device *dev = neigh->dev;
         /* 从dev中获得L2硬件地址*/
  1.         if (dev->header_ops->cache && !dst->hh) {
  2.             write_lock_bh(&neigh->lock);
  3.             if (!dst->hh)
  4.                 neigh_hh_init(neigh, dst, dst->ops->protocol);
  5.             err = dev_hard_header(skb, dev, ntohs(skb->protocol),
  6.                      neigh->ha, NULL, skb->len);
  7.             write_unlock_bh(&neigh->lock);
  8.         } else {
  9.             read_lock_bh(&neigh->lock);
  10.             err = dev_hard_header(skb, dev, ntohs(skb->protocol),
  11.                      neigh->ha, NULL, skb->len);
  12.             read_unlock_bh(&neigh->lock);
  13.         }
  14.         if (err >= 0)
  15.             rc = neigh->ops->queue_xmit(skb); //发送数据包
  16.         else
  17.             goto out_kfree_skb;
  18.     }
  19. out:
  20.     return rc;
  21. discard:
  22.     NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n",
  23.          dst, dst ? dst->neighbour : NULL);
  24. out_kfree_skb:
  25.     rc = -EINVAL;
  26.     kfree_skb(skb);
  27.     goto out;
  28. }
在neigh_event_send中,实际上并不立刻执行发送neigh 请求,而是将这个数据包加到一个队列中__skb_queue_tail(&neigh->arp_queue, skb);,即语句中的arp_queue。
然后通过neighbour subsystem的定时处理函数neigh_timer_handler来处理这个队列。

neigh_timer_handler主要是neighbour状态的处理和变迁,本文并将其列为重点,留作以后分析学习。对于IPv4来说,当没有对应的neighbour信息时,会先调用arp_solicit发送ARP请求,当收到ARP包时,由arp_process进行处理(可参见我前面的博文TCP/IP学习(33)——ARP数据包的处理流程分析, http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=335262)。当收到ARP reply时,会调用neigh_update,当neighbour的状态为valid且neighbour的arp_queue不为空时,会调用n1->output将之前的skb数据包发送。

  1. while (neigh->nud_state & NUD_VALID &&
  2.        (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
  3.     struct neighbour *n1 = neigh;
  4.     write_unlock_bh(&neigh->lock);
  5.     /* On shaper/eql skb->dst->neighbour != neigh :( */
  6.     if (skb_dst(skb) && skb_dst(skb)->neighbour)
  7.         n1 = skb_dst(skb)->neighbour;
  8.     n1->output(skb);
  9.     write_lock_bh(&neigh->lock);
  10. }
当解析neighbour信息成功时,这个n1->output是指向neigh_connected_output,并最终调用dev_queue_xmit->__dev_xmit_skb将数据包添加到dev的queue中,并发送数据包。

当发送完毕时,引发NET_TX_SOFTIRQ软中断,进行下半部处理,如释放已发送的数据包skb的内存等。

本文还遗留了不少细节有待完善,如neighbour subsystem和发送软中断等。

上一篇:TCP/IP学习(35)——IP包的发送流程(2)
下一篇:Linux如何实现在一个CPU上只能有一个软中断运行实例