传输层到链路层
——lvyilong316
这里以TCP为例,讲述报文是如何从传输层进入链路层的。在TCP中将包打包成IP数据报的方法根据TCP段类型的不同而有多种接口。其中最常用的就是ip_queue_xmit()。我们就从这个接口开始。注:以下代码都去掉了不重要的一些逻辑。
l ip_queue_xmit
net/ipv4/ip_output.c
-
int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
-
{
-
struct sock *sk = skb->sk;
-
struct inet_sock *inet = inet_sk(sk);
-
struct ip_options *opt = inet->opt;
-
struct rtable *rt;
-
struct iphdr *iph;
-
/*……*/
-
/* Make sure we can route this packet. */
-
rt = (struct rtable )__sk_dst_check(sk, 0); /*取出sk中缓存的“路由缓存”*/
-
if (rt == NULL) { /*如果没有缓存“路由缓存”,则要查找路由缓存*/
-
__be32 daddr;
-
daddr = inet->daddr;
-
{
-
struct flowi fl = { .oif = sk->sk_bound_dev_if,
-
.mark = sk->sk_mark,
-
.nl_u = { .ip4_u =
-
{ .daddr = daddr,
-
.saddr = inet->saddr,
-
.tos = RT_CONN_FLAGS(sk) } },
-
.proto = sk->sk_protocol,
-
.flags = inet_sk_flowi_flags(sk),
-
.uli_u = { .ports =
-
{ .sport = inet->sport,
-
.dport = inet->dport } } };
-
if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
-
goto no_route;
-
}
-
sk_setup_caps(sk, &rt->u.dst);
-
}
-
skb_dst_set(skb, dst_clone(&rt->u.dst));
-
packet_routed:
-
/* OK, we know where to send it, allocate and build IP header. */
-
/*这里省略了根据查找出得路由缓存设置ip头部字段,包括源、目的地址、ttl、是否允许分片等标示*/
-
return ip_local_out(skb);
-
}
struct rtable标示路由缓存。下面主要看ip_route_output_flow这个函数,这个函数是用来查找路由缓存的(注意不是查找路由表的,只有路由缓存查找不命中时才会查找路由表)。另外注意查找路由缓存的key是struct flowi结构,而不单单是目的地址。
l ip_route_output_flow
net/ipv4/route.c
-
/**
-
*rp:当查找成功时返回查找得到的路由缓存项
-
*flp:用于查找路由缓存的struct flowi结构
-
*sk,flags:支持IPSec策略处理,此处不讨论
-
*/
-
int ip_route_output_flow(struct net *net, struct rtable **rp, struct flowi *flp,
-
struct sock *sk, int flags)
-
{
-
int err;
-
if ((err = __ip_route_output_key(net, rp, flp)) != 0)
-
return err;
-
return 0;
-
}
这个函数进一步调用了__ip_route_output_key进行路由缓存的查找,下面看__ip_route_output_key。
l __ip_route_output_key
net/ipv4/route.c
-
int __ip_route_output_key(struct net *net, struct rtable **rp,
-
const struct flowi *flp)
-
{
-
unsigned hash;
-
struct rtable *rth;
-
if (!rt_caching(net))
-
goto slow_output;
-
/*取得路由缓存对应的hash桶*/
-
hash = rt_hash(flp->fl4_dst, flp->fl4_src, flp->oif, rt_genid(net));
-
rcu_read_lock_bh();
-
for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;
-
rth = rcu_dereference(rth->u.dst.rt_next)) {
-
/*根据flowi结构查找路由缓存,如果找着到则通过rp返回,查找比较过程省略*/
-
}
-
RT_CACHE_STAT_INC(out_hlist_search);
-
}
-
rcu_read_unlock_bh();
-
slow_output:
-
/*如果路由缓存查找不到,则查找路由表*/
-
return ip_route_output_slow(net, rp, flp);
-
}
我们还是按照最坏的结果分析,如果路由缓存没有查找到,则调用ip_route_output_slow查处路由表。
l ip_route_output_slow
net/ipv4/route.c
-
static int ip_route_output_slow(struct net *net, struct rtable **rp,
-
const struct flowi *oldflp)
-
{
-
u32 tos = RT_FL_TOS(oldflp);
-
struct flowi fl = { .nl_u = { .ip4_u =
-
{ .daddr = oldflp->fl4_dst,
-
.saddr = oldflp->fl4_src,
-
.tos = tos & IPTOS_RT_MASK,
-
.scope = ((tos & RTO_ONLINK) ?
-
RT_SCOPE_LINK :
-
RT_SCOPE_UNIVERSE),
-
} },
-
.mark = oldflp->mark,
-
.iif = net->loopback_dev->ifindex,
-
.oif = oldflp->oif };
-
struct fib_result res; /*存放查找结果*/
-
int err;
-
/* fib_lookup 实现路由查找的核心逻辑*/
-
if (fib_lookup(net, &fl, &res)) {
-
/*查找失败的处理。略*/
-
}
-
/*根据查找路由表的结果构建路由缓存*/
-
make_route:
-
err = ip_mkroute_output(rp, &res, &fl, oldflp, dev_out, flags);
-
}
查找路由表的逻辑主要在fib_lookup中实现,我们这里不在去分析,因为与发送逻辑无关,并且设计路由表的结构介绍。如果查找到路由后则会调用ip_mkroute_output根据路由表的查找结果构建一个路由缓存项,这样下次向同一个目的地址发送就可以直接查路由缓存了(其实对于TCP连路由缓存也不需要查,因为会将路由缓存存入sock结构,当然这个缓存有过期时间)。下面分析ip_mkroute_output。
l ip_mkroute_output
net/ipv4/route.c
-
static int ip_mkroute_output(struct rtable **rp,
-
struct fib_result *res,
-
const struct flowi *fl,
-
const struct flowi *oldflp,
-
struct net_device *dev_out,
-
unsigned flags)
-
{
-
struct rtable *rth = NULL;
-
/*根据路由查找结果构建路由缓存项*/
-
int err = __mkroute_output(&rth, res, fl, oldflp, dev_out, flags);
-
unsigned hash;
-
if (err == 0) {
-
hash = rt_hash(oldflp->fl4_dst, oldflp->fl4_src, oldflp->oif,
-
rt_genid(dev_net(dev_out)));
-
/*将构建好的路由缓存项加入路由缓存的hash表中*/
-
err = rt_intern_hash(hash, rth, rp, NULL);
-
}
-
return err;
-
}
__mkroute_output主要负责由路由查找结果struct fib_result构建出路由缓存项struct rtable,具体过程我们不做展开,因为与发送逻辑也没有关系。rt_intern_hash这个函数主要负责将构建好的路由缓存项加入路由缓存的hash表中,我们看下这个函数。
l rt_intern_hash
-
static int rt_intern_hash(unsigned hash, struct rtable *rt,
-
struct rtable **rp, struct sk_buff *skb)
-
{
-
if (rt->rt_type == RTN_UNICAST || rt->fl.iif == 0) {
-
/*绑定路由缓存到下一跳*/
-
int err = arp_bind_neighbour(&rt->u.dst);
-
if (err) {
-
if (net_ratelimit())
-
printk(KERN_WARNING
-
"Neighbour table failure & not caching routes.\n");
-
rt_drop(rt);
-
return err;
-
}
-
}
-
/*找到struct rtable 对应的hash桶,并存放进去,过程省略*/
-
}
对于本地生成的报文的输出路由和单播转发路由,需要ARP来解析下一跳的二层地址,因此需要绑定到该路由下一跳的ARP缓存项。arp_bind_neighbour负责为路由缓存项创建邻居项(struct neighbour)并与之绑定。
l arp_bind_neighbour
net/ipv4/arp.c
-
int arp_bind_neighbour(struct dst_entry *dst)
-
{
-
struct net_device *dev = dst->dev;
-
struct neighbour *n = dst->neighbour;
-
if (dev == NULL)
-
return -EINVAL;
-
if (n == NULL) { /*如果路由缓存没有绑定邻居表项*/
-
__be32 nexthop = ((struct rtable )dst)->rt_gateway;/*取得下一跳ip地址*/
-
if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
-
nexthop = 0;
-
/*查找并创建下一跳ip对应的邻居表项*/
-
n = __neigh_lookup_errno(&arp_tbl, &nexthop, dev);
-
if (IS_ERR(n))
-
return PTR_ERR(n);
-
dst->neighbour = n;/将下一跳的邻居表项和目的地址的路由缓存绑定/
-
}
-
return 0;
-
}
查找和创建下一跳对应的邻居表项主要由__neigh_lookup_errno负责。
l __neigh_lookup_errno
include/net/neighbour.h
-
static inline struct neighbour *
-
__neigh_lookup_errno(struct neigh_table *tbl, const void *pkey,
-
struct net_device *dev)
-
{
-
/*根据下一跳ip地址和输出dev查找对应的邻居表项,如果查找到(之前查找过)则直接返回*/
-
struct neighbour *n = neigh_lookup(tbl, pkey, dev);
-
if (n)
-
return n;
-
/*如果没有查找到则创建对应的邻居表项*/
-
return neigh_create(tbl, pkey, dev);
-
}
我们还是按照最坏的结果,之前没有对应的二层邻居表项缓存,那么我们需要创建,这个过程由neigh_create实现。
l neigh_create
-
net/core/neighbour.c
-
/**
-
*tbl: 待创建邻居表项所属的邻居表,在ARP中为arp_tbl
-
*pkey: 下一跳三层协议地址,作为邻居表项的关键字
-
*dev: 该邻居表项的输出设备
-
**/
-
struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey,
-
struct net_device *dev)
-
{
-
u32 hash_val;
-
int key_len = tbl->key_len;
-
int error;
-
struct neighbour n1, rc, n = neigh_alloc(tbl);/*分配一个邻居表项实例*/
-
/*将三层地址和输出设备设置到邻居表项中*/
-
memcpy(n->primary_key, pkey, key_len);
-
n->dev = dev;
-
dev_hold(dev);
-
/* 执行与协议相关的初始化函数,ARP中为arp_constructor*/
-
if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
-
rc = ERR_PTR(error);
-
goto out_neigh_release;
-
}
-
/*将创建的邻居表项插入邻居表项hash表中,过程省略*/
-
}
neigh_create通过调用arp_constructor创建下一跳ip对应的邻居表项,并加入邻居表项hash表。但是注意arp_constructor并没有填充邻居表项中的二层地址(即下一跳对应的mac地址),而仅仅是初始化对应的neigh->ops,neigh->output输出操作函数,以及neigh->parms。创建完的邻居表项如下图。
有人会问,废了这么大劲创建出的邻居表项还没有二层地址,那发送报文时从哪里获取这个二层地址呢?或者说这个邻居表项中的二层mac地址在什么时候填充呢?接着往下看。让我们回到最开始的TCP发送函数ip_queue_xmit中,当查找到路由缓存后最终会调用ip_local_out函数,这样就从传输层进入了IP层。
ip_local_out又会调用__ip_local_out。
l __ip_local_out
net/ipv4/ip_output.c
-
int __ip_local_out(struct sk_buff *skb)
-
{
-
struct iphdr *iph = ip_hdr(skb);
-
iph->tot_len = htons(skb->len);
-
ip_send_check(iph); /*计算并填充首部校验和*/
-
return nf_hook(PF_INET, NF_INET_LOCAL_OUT, skb, NULL, skb_dst(skb)->dev,
-
dst_output);
-
}
经过netfilter的NF_INET_LOCAL_OUT hook点后,会调用dst_output函数。而dst_output会调用之前查找到底路由缓存中的output函数,即skb->_skb_dst->output。对于单播数据报,目的路由缓存项中的输出接口output是ip_output()。
l ip_output
net/ipv4/ip_output.c
-
int ip_output(struct sk_buff *skb)
-
{
-
struct net_device *dev = skb_dst(skb)->dev;
-
skb->dev = dev;
-
skb->protocol = htons(ETH_P_IP);
-
return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,
-
ip_finish_output,
-
!(IPCB(skb)->flags & IPSKB_REROUTED));
-
}
经过netfilter的NF_INET_POST_ROUTING hook点处理后调用ip_finish_output。
l ip_finish_output
net/ipv4/ip_output.c
-
static int ip_finish_output(struct sk_buff *skb)
-
{
-
if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))
-
return ip_fragment(skb, ip_finish_output2);
-
else
-
return ip_finish_output2(skb);
-
}
此函数主要功能是:如果数据包大于MTU,则调用ip_fragment进行分片,否则调用ip_finish_output2输出,其实ip_fragment分片后也会调用ip_finish_output2。
l ip_finish_output2
net/ipv4/ip_output.c
-
static inline int ip_finish_output2(struct sk_buff *skb)
-
{
-
struct dst_entry *dst = skb_dst(skb);
-
struct rtable rt = (struct rtable )dst; /*取得之前查找到的路由缓存*/
-
struct net_device dev = dst->dev; /*取得输出设备*/
-
unsigned int hh_len = LL_RESERVED_SPACE(dev);
-
/*检测skb的前部空间是否能够存储链路层首部,如果不够则重新分片更大的空间存储skb,并释放原来的skb*/
-
if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
-
struct sk_buff *skb2;
-
skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
-
if (skb2 == NULL) {
-
kfree_skb(skb);
-
return -ENOMEM;
-
}
-
if (skb->sk)
-
skb_set_owner_w(skb2, skb->sk);
-
kfree_skb(skb);
-
skb = skb2;
-
}
-
/*是否缓存了链路层首部,如果缓存则调用neigh_hh_output 输出*/
-
if (dst->hh)
-
return neigh_hh_output(dst->hh, skb);
-
else if (dst->neighbour)
-
/*没有缓存链路层首部,则如果存在对应的邻居表项则调用邻居表项的输出方法*/
-
return dst->neighbour->output(skb);
-
kfree_skb(skb);
-
return -EINVAL;
-
}
我们知道之前创建邻居表项时,其output方法被初始化为neigh_resolve_output方法,下面就看看这个方法。
l neigh_resolve_output
net/core/neighbour.c
-
int neigh_resolve_output(struct sk_buff *skb)
-
{
-
struct dst_entry dst = skb_dst(skb); /*取得对应的路由缓存*/
-
struct neighbour *neigh;
-
int rc = 0;
-
if (!dst || !(neigh = dst->neighbour))
-
goto discard;
-
/*指向三层(ip)头部*/
-
__skb_pull(skb, skb_network_offset(skb));
-
/*确保用于输出的邻居项状态有效才能发送数据包*/
-
if (!neigh_event_send(neigh, skb)) {
-
int err;
-
struct net_device *dev = neigh->dev;
-
/*如果邻居项的输出设备支持hard_header_cache,同时路由缓存项中的二层首部缓存尚未建立,则先为该路由缓存建立硬件首部缓存(struce hh_cache),然后在输出的报文前添加该硬件首部,否则直接在报文前添加硬件首部*/
-
if (dev->header_ops->cache && !dst->hh) {
-
write_lock_bh(&neigh->lock);
-
if (!dst->hh)
-
neigh_hh_init(neigh, dst, dst->ops->protocol);
-
err = dev_hard_header(skb, dev, ntohs(skb->protocol),
-
neigh->ha, NULL, skb->len);
-
write_unlock_bh(&neigh->lock);
-
} else {
-
read_lock_bh(&neigh->lock);
-
err = dev_hard_header(skb, dev, ntohs(skb->protocol),
-
neigh->ha, NULL, skb->len);
-
read_unlock_bh(&neigh->lock);
-
}
-
/*如果添加硬件首部成功,则调用queue_xmit()将报文输出到网络设备*/
-
if (err >= 0)
-
rc = neigh->ops->queue_xmit(skb);
-
else
-
goto out_kfree_skb;
-
}
-
out:
-
return rc;
-
}
因为在前面创建邻居表项时neigh_alloc中会将邻居表项的状态置为UND_NONE,所以neigh_event_send是会返回状态非法的,下面看下neigh_event_send的大概过程。
l neigh_event_send
include/net/neighbour.h
-
static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
-
{
-
neigh->used = jiffies;
-
if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
-
return __neigh_event_send(neigh, skb);
-
return 0;
-
}
又会调用__neigh_event_send。
l __neigh_event_send
-
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
-
{
-
int rc;
-
unsigned long now;
-
write_lock_bh(&neigh->lock);
-
rc = 0;
-
/*邻居状态处于NUD_CONNECTED、NUD_DELAY或NUD_PROBE则直接返回 */
-
if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
-
goto out_unlock_bh;
-
now = jiffies;
-
/*此时剩下的未考察状态为NUD_STALE 、 NUD_INCOMPLETE和UND_NONE,因此如果当前状态不为NUD_STALE和NUD_INCOMPLETE 则必为UND_NONE*/
-
if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
-
/*如果允许发送arp广播请求报文或者允许应用程序发送请求报文来解析邻居地址,则将邻居项状态设置为NUD_INCOMPLETE,并启动邻居状态处理定时器*/
-
if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
-
atomic_set(&neigh->probes, neigh->parms->ucast_probes);
-
neigh->nud_state = NUD_INCOMPLETE;
-
neigh->updated = jiffies;
-
neigh_add_timer(neigh, now + 1);
-
} else {
-
/*否则邻居项只能转换为NUD_FAILED 状态,并释放待输出报文,同时返回1,标示邻居项无效,不能输出*/
-
neigh->nud_state = NUD_FAILED;
-
neigh->updated = jiffies;
-
write_unlock_bh(&neigh->lock);
-
kfree_skb(skb);
-
return 1;
-
}
-
} else if (neigh->nud_state & NUD_STALE) {
-
/* NUD_STALE 状态处理,略*/
-
}
-
/* 由前面可知邻居表项由UND_NONE状态已经转换为了NUD_INCOMPLETE ,所以会进入这个判断*/
-
if (neigh->nud_state == NUD_INCOMPLETE) {
-
if (skb) {
-
/*如果请求缓存项队列长度还未达到上限,则将待输出报文缓存到队列中,否则只能丢弃该报文。无论那种情况都返回1,标示还不能发送报文*/
-
if (skb_queue_len(&neigh->arp_queue) >=
-
neigh->parms->queue_len) {
-
struct sk_buff *buff;
-
buff = __skb_dequeue(&neigh->arp_queue);
-
kfree_skb(buff);
-
NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);
-
}
-
__skb_queue_tail(&neigh->arp_queue, skb);
-
}
-
rc = 1;
-
}
-
out_unlock_bh:
-
write_unlock_bh(&neigh->lock);
-
return rc;
-
}
所以neigh_resolve_output的执行结果,就是1)将邻居表项置为NUD_INCOMPLETE;2)将待发送的报文存入邻居表项的缓存队列。看到这里就很奇怪了,邻居表项中的mac地址还是没有找到啊,再说数据包被放入邻居表项队列中去以后由谁来发送呢?注意前面neigh_add_timer还启动了邻居表项的状态定时器。这个状态定时器的处理函数为neigh_timer_handler。
l neigh_timer_handler
net/core/neighbour.c
-
static void neigh_timer_handler(unsigned long arg)
-
{
-
/*……*/
-
/*如果邻居表项状态处于NUD_INCOMPLETE 或NUD_PROBE,且发送ARP请求次数为达到上限,则向邻居发送ARP请求 */
-
if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {
-
/*需要根据之前缓存的待发送数据包构造ARP请求包*/
-
struct sk_buff *skb = skb_peek(&neigh->arp_queue);
-
/* keep skb alive even if arp_queue overflows */
-
if (skb)
-
skb = skb_copy(skb, GFP_ATOMIC);
-
write_unlock(&neigh->lock);
-
neigh->ops->solicit(neigh, skb); /*发送ARP请求*/
-
atomic_inc(&neigh->probes);
-
kfree_skb(skb);
-
}
-
}
neigh->ops->solicit被初始化为arp_solicit(),用来构造和发送ARP请求。但是发送完请求后呢?自然是等待ARP应答了,当收到ARP应答后,最终会调用arp_process()函数处理。
l arp_process
net/ipv4/arp.c
-
static int arp_process(struct sk_buff *skb)
-
{
-
/*……*/
-
/*根据ARP应答找出对应的邻居表项,如果没有则创建*/
-
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
-
if (n) {
-
int state = NUD_REACHABLE; /*邻居表项将被更新为NUD_REACHABLE 状态*/
-
int override;
-
override = time_after(jiffies, n->updated + n->parms->locktime);
-
if (arp->ar_op != htons(ARPOP_REPLY) ||
-
skb->pkt_type != PACKET_HOST)
-
state = NUD_STALE;
-
/*更新邻居表状态*/
-
neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);
-
neigh_release(n);
-
}
-
/*……*/
-
}
neigh_update()用来更新指定的邻居项,更新内容是硬件地址和状态(二层地址就是在这个函数中存入邻居表项的)。在更新状态之后,会根据新状态设置相应的输出函数:如果更新状态为UND_CONNECTED(NUD_REACHABLE就属于这种状态),则更新邻居表项的输出函数(neigh->output)为neigh_connect()连否则禁止快速路径转发,更新输出函数为neigh_suspect()。
l neigh_update
net/core/neighbour.c
-
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
-
u32 flags)
-
{
-
/*省略邻居表项mac地址的填充*/
-
if (new == old)
-
goto out;
-
if (new & NUD_CONNECTED)
-
neigh_connect(neigh);/*设置neigh->output */
-
else
-
neigh_suspect(neigh);
-
/*如果邻居表项由无效状态变为有效状态(注:之前状态为NUD_INCOMPLETE,属于无效状态,而即将变为的NUD_REACHABLE为有效状态的一种)*/
-
if (!(old & NUD_VALID)) {
-
struct sk_buff *skb;
-
/*遍历邻居表项的缓存队列arp_queue,将缓存在队列中的报文逐个输出*/
-
while (neigh->nud_state & NUD_VALID &&
-
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
-
struct neighbour *n1 = neigh;
-
write_unlock_bh(&neigh->lock);
-
/* On shaper/eql skb->dst->neighbour != neigh :( */
-
if (skb_dst(skb) && skb_dst(skb)->neighbour)
-
n1 = skb_dst(skb)->neighbour;
-
n1->output(skb);
-
write_lock_bh(&neigh->lock);
-
}
-
skb_queue_purge(&neigh->arp_queue);
-
}
-
}
至此,终于将邻居表项中的mac填充了,构建出了完整的邻居表项,也终于将数据包发送出去。这里使用的发送函数为neigh->output在neigh_connect中被设置。
l neigh_connect
net/core/neighbour.c
-
static void neigh_connect(struct neighbour *neigh)
-
{
-
struct hh_cache *hh;
-
neigh->output = neigh->ops->connected_output;
-
for (hh = neigh->hh; hh; hh = hh->hh_next)
-
hh->hh_output = neigh->ops->hh_output;
-
}
可以看到neigh->output被初始化为connected_output,对ARP就是neigh_connected_output。
l neigh_connected_output
net/core/neighbour.c
-
int neigh_connected_output(struct sk_buff *skb)
-
{
-
int err;
-
struct dst_entry *dst = skb_dst(skb);
-
struct neighbour *neigh = dst->neighbour;
-
struct net_device *dev = neigh->dev;
-
__skb_pull(skb, skb_network_offset(skb));
-
read_lock_bh(&neigh->lock);
-
/*构建报文二层mac头部*/
-
err = dev_hard_header(skb, dev, ntohs(skb->protocol),
-
neigh->ha, NULL, skb->len);
-
read_unlock_bh(&neigh->lock);
-
if (err >= 0)
-
err = neigh->ops->queue_xmit(skb);/*发送skb*/
-
else {
-
err = -EINVAL;
-
kfree_skb(skb);
-
}
-
return err;
-
}
这里构建完报文二层头部后最后由调用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_output给skb添加mac头,然后调用dev_queue_xmit将数据包发往链路层。
具体过程如下图:
当然我们这里描述的是一个数据包第一次完整的发送流程,之后由于路由缓存和邻居表项都已经建立,所以相应发送函数也会替换,过程要比这个简单的多。
最后附上几个重要数据结构的关系图:
阅读(6559) | 评论(1) | 转发(0) |