Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3460181
  • 博文数量: 198
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7256
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-23 18:56
个人简介

将晦涩难懂的技术讲的通俗易懂

文章分类

全部博文(198)

文章存档

2024年(1)

2023年(9)

2022年(4)

2021年(12)

2020年(8)

2019年(18)

2018年(19)

2017年(9)

2016年(26)

2015年(18)

2014年(54)

2013年(20)

分类: LINUX

2016-05-27 23:05:26

传输层到链路层

——lvyilong316

这里以TCP为例,讲述报文是如何从传输层进入链路层的。在TCP中将包打包成IP数据报的方法根据TCP段类型的不同而有多种接口。其中最常用的就是ip_queue_xmit()。我们就从这个接口开始。注:以下代码都去掉了不重要的一些逻辑

ip_queue_xmit

net/ipv4/ip_output.c

点击(此处)折叠或打开

  1. int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
  2. {
  3.     struct sock *sk = skb->sk;
  4.     struct inet_sock *inet = inet_sk(sk);
  5.     struct ip_options *opt = inet->opt;
  6.     struct rtable *rt;
  7.     struct iphdr *iph;
  8.     /*……*/
  9.     /* Make sure we can route this packet. */
  10. rt = (struct rtable )__sk_dst_check(sk, 0); /*取出sk中缓存的“路由缓存”*/
  11.    if (rt == NULL) { /*如果没有缓存“路由缓存”,则要查找路由缓存*/
  12.         __be32 daddr;
  13.         daddr = inet->daddr;
  14.         {
  15.             struct flowi fl = { .oif = sk->sk_bound_dev_if,
  16.                         .mark = sk->sk_mark,
  17.                         .nl_u = { .ip4_u =
  18.                               { .daddr = daddr,
  19.                             .saddr = inet->saddr,
  20.                             .tos = RT_CONN_FLAGS(sk) } },
  21.                         .proto = sk->sk_protocol,
  22.                         .flags = inet_sk_flowi_flags(sk),
  23.                         .uli_u = { .ports =
  24.                                { .sport = inet->sport,
  25.                              .dport = inet->dport } } };
  26.             if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
  27.                 goto no_route;
  28.         }
  29.         sk_setup_caps(sk, &rt->u.dst);
  30.     }
  31. skb_dst_set(skb, dst_clone(&rt->u.dst));
  32. packet_routed:
  33.     /* OK, we know where to send it, allocate and build IP header. */
  34.     /*这里省略了根据查找出得路由缓存设置ip头部字段,包括源、目的地址、ttl、是否允许分片等标示*/
  35.     return ip_local_out(skb);
  36. }

struct rtable标示路由缓存。下面主要看ip_route_output_flow这个函数,这个函数是用来查找路由缓存的(注意不是查找路由表的,只有路由缓存查找不命中时才会查找路由表)。另外注意查找路由缓存的keystruct flowi结构,而不单单是目的地址。

ip_route_output_flow

net/ipv4/route.c

点击(此处)折叠或打开

  1. /**
  2. *rp:当查找成功时返回查找得到的路由缓存项
  3. *flp:用于查找路由缓存的struct flowi结构
  4. *sk,flags:支持IPSec策略处理,此处不讨论
  5. */
  6. int ip_route_output_flow(struct net *net, struct rtable **rp, struct flowi *flp,
  7.              struct sock *sk, int flags)
  8. {
  9.     int err;
  10.     if ((err = __ip_route_output_key(net, rp, flp)) != 0)
  11.         return err;
  12.     return 0;
  13. }

这个函数进一步调用了__ip_route_output_key进行路由缓存的查找,下面看__ip_route_output_key

__ip_route_output_key

net/ipv4/route.c

点击(此处)折叠或打开

  1. int __ip_route_output_key(struct net *net, struct rtable **rp,
  2.               const struct flowi *flp)
  3. {
  4.     unsigned hash;
  5.     struct rtable *rth;
  6.     if (!rt_caching(net))
  7.         goto slow_output;
  8.     /*取得路由缓存对应的hash桶*/
  9.     hash = rt_hash(flp->fl4_dst, flp->fl4_src, flp->oif, rt_genid(net));
  10.     rcu_read_lock_bh();
  11.     for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;
  12.         rth = rcu_dereference(rth->u.dst.rt_next)) {
  13.         /*根据flowi结构查找路由缓存,如果找着到则通过rp返回,查找比较过程省略*/
  14.         }
  15.         RT_CACHE_STAT_INC(out_hlist_search);
  16.     }
  17.     rcu_read_unlock_bh();
  18. slow_output:
  19.     /*如果路由缓存查找不到,则查找路由表*/
  20.     return ip_route_output_slow(net, rp, flp);
  21. }

我们还是按照最坏的结果分析,如果路由缓存没有查找到,则调用ip_route_output_slow查处路由表。

ip_route_output_slow

net/ipv4/route.c

点击(此处)折叠或打开

  1. static int ip_route_output_slow(struct net *net, struct rtable **rp,
  2.                 const struct flowi *oldflp)
  3. {
  4.     u32 tos = RT_FL_TOS(oldflp);
  5.     struct flowi fl = { .nl_u = { .ip4_u =
  6.                       { .daddr = oldflp->fl4_dst,
  7.                     .saddr = oldflp->fl4_src,
  8.                     .tos = tos & IPTOS_RT_MASK,
  9.                     .scope = ((tos & RTO_ONLINK) ?
  10.                           RT_SCOPE_LINK :
  11.                           RT_SCOPE_UNIVERSE),
  12.                       } },
  13.                 .mark = oldflp->mark,
  14.                 .iif = net->loopback_dev->ifindex,
  15.                 .oif = oldflp->oif };
  16.     struct fib_result res; /*存放查找结果*/
  17. int err;
  18. /* fib_lookup 实现路由查找的核心逻辑*/
  19. if (fib_lookup(net, &fl, &res)) {
  20.    /*查找失败的处理。略*/
  21. }
  22. /*根据查找路由表的结果构建路由缓存*/
  23. make_route:
  24.     err = ip_mkroute_output(rp, &res, &fl, oldflp, dev_out, flags);
  25. }

查找路由表的逻辑主要在fib_lookup中实现,我们这里不在去分析,因为与发送逻辑无关,并且设计路由表的结构介绍。如果查找到路由后则会调用ip_mkroute_output根据路由表的查找结果构建一个路由缓存项,这样下次向同一个目的地址发送就可以直接查路由缓存了(其实对于TCP连路由缓存也不需要查,因为会将路由缓存存入sock结构,当然这个缓存有过期时间)。下面分析ip_mkroute_output

ip_mkroute_output

net/ipv4/route.c

点击(此处)折叠或打开

  1. static int ip_mkroute_output(struct rtable **rp,
  2.                  struct fib_result *res,
  3.                  const struct flowi *fl,
  4.                  const struct flowi *oldflp,
  5.                  struct net_device *dev_out,
  6.                  unsigned flags)
  7. {
  8. struct rtable *rth = NULL;
  9. /*根据路由查找结果构建路由缓存项*/
  10.     int err = __mkroute_output(&rth, res, fl, oldflp, dev_out, flags);
  11.     unsigned hash;
  12.     if (err == 0) {
  13.         hash = rt_hash(oldflp->fl4_dst, oldflp->fl4_src, oldflp->oif,
  14.                    rt_genid(dev_net(dev_out)));
  15.         /*将构建好的路由缓存项加入路由缓存的hash表中*/
  16.         err = rt_intern_hash(hash, rth, rp, NULL);
  17.     }
  18.     return err;
  19. }

__mkroute_output主要负责由路由查找结果struct fib_result构建出路由缓存项struct rtable,具体过程我们不做展开,因为与发送逻辑也没有关系。rt_intern_hash这个函数主要负责将构建好的路由缓存项加入路由缓存的hash表中,我们看下这个函数。

rt_intern_hash

点击(此处)折叠或打开

  1. static int rt_intern_hash(unsigned hash, struct rtable *rt,
  2. struct rtable **rp, struct sk_buff *skb)
  3. {
  4.   if (rt->rt_type == RTN_UNICAST || rt->fl.iif == 0) {
  5.             /*绑定路由缓存到下一跳*/
  6.             int err = arp_bind_neighbour(&rt->u.dst);
  7.             if (err) {
  8.                 if (net_ratelimit())
  9.                     printk(KERN_WARNING
  10.                         "Neighbour table failure & not caching routes.\n");
  11.                 rt_drop(rt);
  12.                 return err;
  13.             }
  14.         }
  15.      /*找到struct rtable 对应的hash桶,并存放进去,过程省略*/
  16. }

对于本地生成的报文的输出路由和单播转发路由,需要ARP来解析下一跳的二层地址,因此需要绑定到该路由下一跳的ARP缓存项。arp_bind_neighbour负责为路由缓存项创建邻居项(struct neighbour)并与之绑定。

arp_bind_neighbour

net/ipv4/arp.c

点击(此处)折叠或打开

  1. int arp_bind_neighbour(struct dst_entry *dst)
  2. {
  3.     struct net_device *dev = dst->dev;
  4.     struct neighbour *n = dst->neighbour;
  5.     if (dev == NULL)
  6.         return -EINVAL;
  7.     if (n == NULL) { /*如果路由缓存没有绑定邻居表项*/
  8.         __be32 nexthop = ((struct rtable )dst)->rt_gateway;/*取得下一跳ip地址*/
  9.         if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
  10. nexthop = 0;
  11.         /*查找并创建下一跳ip对应的邻居表项*/
  12.         n = __neigh_lookup_errno(&arp_tbl, &nexthop, dev);
  13.         if (IS_ERR(n))
  14.             return PTR_ERR(n);
  15.         dst->neighbour = n;/将下一跳的邻居表项和目的地址的路由缓存绑定/
  16.     }
  17.     return 0;
  18. }

查找和创建下一跳对应的邻居表项主要由__neigh_lookup_errno负责。

__neigh_lookup_errno

include/net/neighbour.h

点击(此处)折叠或打开

  1. static inline struct neighbour *
  2. __neigh_lookup_errno(struct neigh_table *tbl, const void *pkey,
  3.   struct net_device *dev)
  4. {
  5.     /*根据下一跳ip地址和输出dev查找对应的邻居表项,如果查找到(之前查找过)则直接返回*/
  6.     struct neighbour *n = neigh_lookup(tbl, pkey, dev);
  7.     if (n)
  8.         return n;
  9.    /*如果没有查找到则创建对应的邻居表项*/
  10.     return neigh_create(tbl, pkey, dev);
  11. }

我们还是按照最坏的结果,之前没有对应的二层邻居表项缓存,那么我们需要创建,这个过程由neigh_create实现。

neigh_create

点击(此处)折叠或打开

  1. net/core/neighbour.c
  2. /**
  3. *tbl: 待创建邻居表项所属的邻居表,在ARP中为arp_tbl
  4. *pkey: 下一跳三层协议地址,作为邻居表项的关键字
  5. *dev: 该邻居表项的输出设备
  6. **/
  7. struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey,
  8.                    struct net_device *dev)
  9. {
  10.     u32 hash_val;
  11.     int key_len = tbl->key_len;
  12.     int error;
  13.     struct neighbour n1, rc, n = neigh_alloc(tbl);/*分配一个邻居表项实例*/
  14.     /*将三层地址和输出设备设置到邻居表项中*/
  15.     memcpy(n->primary_key, pkey, key_len);
  16.     n->dev = dev;
  17. dev_hold(dev);
  18.    /* 执行与协议相关的初始化函数,ARP中为arp_constructor*/
  19.     if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
  20.         rc = ERR_PTR(error);
  21.         goto out_neigh_release;
  22. }
  23. /*将创建的邻居表项插入邻居表项hash表中,过程省略*/
  24. }

neigh_create通过调用arp_constructor创建下一跳ip对应的邻居表项,并加入邻居表项hash表。但是注意arp_constructor并没有填充邻居表项中的二层地址(即下一跳对应的mac地址),而仅仅是初始化对应的neigh->opsneigh->output输出操作函数,以及neigh->parms。创建完的邻居表项如下图。

有人会问,废了这么大劲创建出的邻居表项还没有二层地址,那发送报文时从哪里获取这个二层地址呢?或者说这个邻居表项中的二层mac地址在什么时候填充呢?接着往下看。让我们回到最开始的TCP发送函数ip_queue_xmit中,当查找到路由缓存后最终会调用ip_local_out函数,这样就从传输层进入了IP层。

ip_local_out又会调用__ip_local_out

__ip_local_out

net/ipv4/ip_output.c

点击(此处)折叠或打开

  1. int __ip_local_out(struct sk_buff *skb)
  2. {
  3.     struct iphdr *iph = ip_hdr(skb);
  4.     iph->tot_len = htons(skb->len);
  5.     ip_send_check(iph); /*计算并填充首部校验和*/
  6.     return nf_hook(PF_INET, NF_INET_LOCAL_OUT, skb, NULL, skb_dst(skb)->dev,
  7.                dst_output);
  8. }

经过netfilterNF_INET_LOCAL_OUT hook点后,会调用dst_output函数。而dst_output会调用之前查找到底路由缓存中的output函数,即skb->_skb_dst->output。对于单播数据报,目的路由缓存项中的输出接口outputip_output()

ip_output

net/ipv4/ip_output.c

点击(此处)折叠或打开

  1. int ip_output(struct sk_buff *skb)
  2. {
  3.     struct net_device *dev = skb_dst(skb)->dev;
  4.     skb->dev = dev;
  5.     skb->protocol = htons(ETH_P_IP);
  6.     return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,
  7.                 ip_finish_output,
  8.                 !(IPCB(skb)->flags & IPSKB_REROUTED));
  9. }

经过netfilterNF_INET_POST_ROUTING hook点处理后调用ip_finish_output

ip_finish_output

net/ipv4/ip_output.c

点击(此处)折叠或打开

  1. static int ip_finish_output(struct sk_buff *skb)
  2. {
  3.     if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))
  4.         return ip_fragment(skb, ip_finish_output2);
  5.     else
  6.         return ip_finish_output2(skb);
  7. }

此函数主要功能是:如果数据包大于MTU,则调用ip_fragment进行分片,否则调用ip_finish_output2输出,其实ip_fragment分片后也会调用ip_finish_output2

ip_finish_output2

net/ipv4/ip_output.c

点击(此处)折叠或打开

  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);
  7.     /*检测skb的前部空间是否能够存储链路层首部,如果不够则重新分片更大的空间存储skb,并释放原来的skb*/
  8.     if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
  9.         struct sk_buff *skb2;
  10.         skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
  11.         if (skb2 == NULL) {
  12.             kfree_skb(skb);
  13.             return -ENOMEM;
  14.         }
  15.         if (skb->sk)
  16.             skb_set_owner_w(skb2, skb->sk);
  17.         kfree_skb(skb);
  18.         skb = skb2;
  19.     }
  20.     /*是否缓存了链路层首部,如果缓存则调用neigh_hh_output 输出*/
  21.     if (dst->hh)
  22.         return neigh_hh_output(dst->hh, skb);
  23. else if (dst->neighbour)
  24.     /*没有缓存链路层首部,则如果存在对应的邻居表项则调用邻居表项的输出方法*/
  25.         return dst->neighbour->output(skb);
  26.     kfree_skb(skb);
  27.     return -EINVAL;
  28. }

我们知道之前创建邻居表项时,其output方法被初始化为neigh_resolve_output方法,下面就看看这个方法。

neigh_resolve_output

net/core/neighbour.c

点击(此处)折叠或打开

  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;
  8.     /*指向三层(ip)头部*/
  9.     __skb_pull(skb, skb_network_offset(skb));
  10.     /*确保用于输出的邻居项状态有效才能发送数据包*/
  11.     if (!neigh_event_send(neigh, skb)) {
  12.         int err;
  13.         struct net_device *dev = neigh->dev;
  14.     /*如果邻居项的输出设备支持hard_header_cache,同时路由缓存项中的二层首部缓存尚未建立,则先为该路由缓存建立硬件首部缓存(struce hh_cache),然后在输出的报文前添加该硬件首部,否则直接在报文前添加硬件首部*/
  15.         if (dev->header_ops->cache && !dst->hh) {
  16.             write_lock_bh(&neigh->lock);
  17.             if (!dst->hh)
  18.                 neigh_hh_init(neigh, dst, dst->ops->protocol);
  19.             err = dev_hard_header(skb, dev, ntohs(skb->protocol),
  20.                           neigh->ha, NULL, skb->len);
  21.             write_unlock_bh(&neigh->lock);
  22.         } else {
  23.             read_lock_bh(&neigh->lock);
  24.             err = dev_hard_header(skb, dev, ntohs(skb->protocol),
  25.                           neigh->ha, NULL, skb->len);
  26.             read_unlock_bh(&neigh->lock);
  27.         }
  28.         /*如果添加硬件首部成功,则调用queue_xmit()将报文输出到网络设备*/
  29.         if (err >= 0)
  30.             rc = neigh->ops->queue_xmit(skb);
  31.         else
  32.             goto out_kfree_skb;
  33.     }
  34. out:
  35.     return rc;
  36. }

因为在前面创建邻居表项时neigh_alloc中会将邻居表项的状态置为UND_NONE,所以neigh_event_send是会返回状态非法的,下面看下neigh_event_send的大概过程。

neigh_event_send

include/net/neighbour.h

点击(此处)折叠或打开

  1. static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
  2. {
  3.     neigh->used = jiffies;
  4.     if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
  5.         return __neigh_event_send(neigh, skb);
  6.     return 0;
  7. }

   又会调用__neigh_event_send

__neigh_event_send

点击(此处)折叠或打开

  1. int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
  2. {
  3.     int rc;
  4.     unsigned long now;
  5.     write_lock_bh(&neigh->lock);
  6. rc = 0;
  7. /*邻居状态处于NUD_CONNECTED、NUD_DELAY或NUD_PROBE则直接返回 */
  8.     if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
  9.         goto out_unlock_bh;
  10.     now = jiffies;
  11.     /*此时剩下的未考察状态为NUD_STALE 、 NUD_INCOMPLETE和UND_NONE,因此如果当前状态不为NUD_STALE和NUD_INCOMPLETE 则必为UND_NONE*/
  12. if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
  13. /*如果允许发送arp广播请求报文或者允许应用程序发送请求报文来解析邻居地址,则将邻居项状态设置为NUD_INCOMPLETE,并启动邻居状态处理定时器*/
  14.         if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
  15.             atomic_set(&neigh->probes, neigh->parms->ucast_probes);
  16.             neigh->nud_state = NUD_INCOMPLETE;
  17.             neigh->updated = jiffies;
  18.             neigh_add_timer(neigh, now + 1);
  19.         } else {
  20.         /*否则邻居项只能转换为NUD_FAILED 状态,并释放待输出报文,同时返回1,标示邻居项无效,不能输出*/
  21.             neigh->nud_state = NUD_FAILED;
  22.             neigh->updated = jiffies;
  23.             write_unlock_bh(&neigh->lock);
  24.             kfree_skb(skb);
  25.             return 1;
  26.         }
  27. } else if (neigh->nud_state & NUD_STALE) {
  28.    /* NUD_STALE 状态处理,略*/
  29. }
  30. /* 由前面可知邻居表项由UND_NONE状态已经转换为了NUD_INCOMPLETE ,所以会进入这个判断*/
  31.     if (neigh->nud_state == NUD_INCOMPLETE) {
  32.         if (skb) {
  33.         /*如果请求缓存项队列长度还未达到上限,则将待输出报文缓存到队列中,否则只能丢弃该报文。无论那种情况都返回1,标示还不能发送报文*/
  34.             if (skb_queue_len(&neigh->arp_queue) >=
  35.                 neigh->parms->queue_len) {
  36.                 struct sk_buff *buff;
  37.                 buff = __skb_dequeue(&neigh->arp_queue);
  38.                 kfree_skb(buff);
  39.                 NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);
  40.             }
  41.             __skb_queue_tail(&neigh->arp_queue, skb);
  42.         }
  43.         rc = 1;
  44.     }
  45. out_unlock_bh:
  46.     write_unlock_bh(&neigh->lock);
  47.     return rc;
  48. }

所以neigh_resolve_output的执行结果,就是1)将邻居表项置为NUD_INCOMPLETE2)将待发送的报文存入邻居表项的缓存队列。看到这里就很奇怪了,邻居表项中的mac地址还是没有找到啊,再说数据包被放入邻居表项队列中去以后由谁来发送呢?注意前面neigh_add_timer还启动了邻居表项的状态定时器。这个状态定时器的处理函数为neigh_timer_handler

neigh_timer_handler

net/core/neighbour.c

点击(此处)折叠或打开

  1. static void neigh_timer_handler(unsigned long arg)
  2. {
  3. /*……*/
  4. /*如果邻居表项状态处于NUD_INCOMPLETE 或NUD_PROBE,且发送ARP请求次数为达到上限,则向邻居发送ARP请求 */
  5. if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {
  6.    /*需要根据之前缓存的待发送数据包构造ARP请求包*/
  7.         struct sk_buff *skb = skb_peek(&neigh->arp_queue);
  8.         /* keep skb alive even if arp_queue overflows */
  9.         if (skb)
  10.             skb = skb_copy(skb, GFP_ATOMIC);
  11.         write_unlock(&neigh->lock);
  12.         neigh->ops->solicit(neigh, skb); /*发送ARP请求*/
  13.         atomic_inc(&neigh->probes);
  14.         kfree_skb(skb);
  15.     }
  16. }

neigh->ops->solicit被初始化为arp_solicit(),用来构造和发送ARP请求。但是发送完请求后呢?自然是等待ARP应答了,当收到ARP应答后,最终会调用arp_process()函数处理。

arp_process

net/ipv4/arp.c

点击(此处)折叠或打开

  1. static int arp_process(struct sk_buff *skb)
  2. {
  3.    /*……*/
  4.     /*根据ARP应答找出对应的邻居表项,如果没有则创建*/
  5. n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
  6.     if (n) {
  7.         int state = NUD_REACHABLE; /*邻居表项将被更新为NUD_REACHABLE 状态*/
  8.         int override;
  9.         override = time_after(jiffies, n->updated + n->parms->locktime);
  10.         if (arp->ar_op != htons(ARPOP_REPLY) ||
  11.             skb->pkt_type != PACKET_HOST)
  12. state = NUD_STALE;
  13.         /*更新邻居表状态*/
  14.         neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);
  15.         neigh_release(n);
  16. }
  17. /*……*/
  18. }

neigh_update()用来更新指定的邻居项,更新内容是硬件地址和状态(二层地址就是在这个函数中存入邻居表项的)。在更新状态之后,会根据新状态设置相应的输出函数:如果更新状态为UND_CONNECTEDNUD_REACHABLE就属于这种状态),则更新邻居表项的输出函数(neigh->output)neigh_connect()连否则禁止快速路径转发,更新输出函数为neigh_suspect()

neigh_update

net/core/neighbour.c

点击(此处)折叠或打开

  1. int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
  2.          u32 flags)
  3. {
  4.    /*省略邻居表项mac地址的填充*/
  5.     if (new == old)
  6.         goto out;
  7.     if (new & NUD_CONNECTED)
  8.         neigh_connect(neigh);/*设置neigh->output */
  9.     else
  10.         neigh_suspect(neigh);
  11. /*如果邻居表项由无效状态变为有效状态(注:之前状态为NUD_INCOMPLETE,属于无效状态,而即将变为的NUD_REACHABLE为有效状态的一种)*/
  12.     if (!(old & NUD_VALID)) {
  13.         struct sk_buff *skb;
  14.         /*遍历邻居表项的缓存队列arp_queue,将缓存在队列中的报文逐个输出*/
  15.         while (neigh->nud_state & NUD_VALID &&
  16.                (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
  17.             struct neighbour *n1 = neigh;
  18.             write_unlock_bh(&neigh->lock);
  19.             /* On shaper/eql skb->dst->neighbour != neigh :( */
  20.             if (skb_dst(skb) && skb_dst(skb)->neighbour)
  21.                 n1 = skb_dst(skb)->neighbour;
  22.             n1->output(skb);
  23.             write_lock_bh(&neigh->lock);
  24.         }
  25.         skb_queue_purge(&neigh->arp_queue);
  26.     }
  27. }

至此,终于将邻居表项中的mac填充了,构建出了完整的邻居表项,也终于将数据包发送出去。这里使用的发送函数为neigh->outputneigh_connect中被设置。

neigh_connect

net/core/neighbour.c

点击(此处)折叠或打开

  1. static void neigh_connect(struct neighbour *neigh)
  2. {
  3.     struct hh_cache *hh;
  4.     neigh->output = neigh->ops->connected_output;
  5.     for (hh = neigh->hh; hh; hh = hh->hh_next)
  6.         hh->hh_output = neigh->ops->hh_output;
  7. }

  可以看到neigh->output被初始化为connected_output,对ARP就是neigh_connected_output

neigh_connected_output

net/core/neighbour.c

点击(此处)折叠或打开

  1. int neigh_connected_output(struct sk_buff *skb)
  2. {
  3.     int err;
  4.     struct dst_entry *dst = skb_dst(skb);
  5.     struct neighbour *neigh = dst->neighbour;
  6.     struct net_device *dev = neigh->dev;
  7.     __skb_pull(skb, skb_network_offset(skb));
  8. read_lock_bh(&neigh->lock);
  9. /*构建报文二层mac头部*/
  10.     err = dev_hard_header(skb, dev, ntohs(skb->protocol),
  11.                   neigh->ha, NULL, skb->len);
  12.     read_unlock_bh(&neigh->lock);
  13.     if (err >= 0)
  14.         err = neigh->ops->queue_xmit(skb);/*发送skb*/
  15.     else {
  16.         err = -EINVAL;
  17.         kfree_skb(skb);
  18.     }
  19.     return err;
  20. }

这里构建完报文二层头部后最后由调用neigh->ops->queue_xmit(skb)来发送skb,这个函数被初始化为dev_queue_xmit。而dev_queue_xmit的调用也标志着数据包进入了链路层。链路层之后的处理参见之前的博文分析:http://blog.chinaunix.net/uid-28541347-id-5613919.html

我们首先回忆一下发送一个数据包经历的过程:

1) 查找路由缓存(struct rtable),如果没有查到则查找路由表则2);

2) 根据路由表构建路由缓存,并给路由缓存绑定下一跳的邻居表项(struct neightbour),如果没有对应的邻居表项则创建;

3) 调用邻居表项的输出函数,这时由于邻居表项刚创建出来没有对应下一跳的mac,所以需要先将数据包缓存,同时触发邻居表项定时器。

4) 邻居表项定时器触发arp_ solicit()发送ARP请求,arp_process()处理接受的的ARP应答,调用neigh_update填入邻居表项对应的mac,并将之前在邻居表项队列中缓存的数据包用neigh_connected_output发出。

5) neigh_connected_outputskb添加mac头,然后调用dev_queue_xmit将数据包发往链路层。

具体过程如下图:

当然我们这里描述的是一个数据包第一次完整的发送流程,之后由于路由缓存和邻居表项都已经建立,所以相应发送函数也会替换,过程要比这个简单的多。

最后附上几个重要数据结构的关系图:

阅读(6241) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

不懂IT2016-06-07 22:31:43

好强大!!!