传输层到链路层
——lvyilong316
这里以TCP为例,讲述报文是如何从传输层进入链路层的。在TCP中将包打包成IP数据报的方法根据TCP段类型的不同而有多种接口。其中最常用的就是ip_queue_xmit()。我们就从这个接口开始。注:以下代码都去掉了不重要的一些逻辑。
lip_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结构,而不单单是目的地址。
lip_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查处路由表。
lip_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中实现,我们这里不在去分析,因为与发送逻辑无关,并且设