内核中的TCP的追踪分析-21-TCP(IPV4)的socket请求连接 -1

2020-05-26 00:00:00 函数 地址 路由 结构 键值

我们继续往下看

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()
if (!fl.fl4_dst) {
fl.fl4_dst = fl.fl4_src;
if (!fl.fl4_dst)
fl.fl4_dst = fl.fl4_src = htonl(INADDR_LOOPBACK);
if (dev_out)
dev_put(dev_out);
dev_out = net->loopback_dev;
dev_hold(dev_out);
fl.oif = net->loopback_dev->ifindex;
res.type = RTN_LOCAL;
flags |= RTCF_LOCAL;
goto make_route;
}
if (fib_lookup(net, &fl, &res)) {
res.fi = NULL;
if (oldflp->oif) {
/* Apparently, routing tables are wrong. Assume,
that the destination is on link.
WHY? DW.
Because we are allowed to send to iface
even if it has NO routes and NO assigned
addresses. When oif is specified, routing
tables are looked up with only one purpose:
to catch if destination is gatewayed, rather than
direct. Moreover, if MSG_DONTROUTE is set,
we send packet, ignoring both routing tables
and ifaddr state. --ANK
We could make it even if oif is unknown,
likely IPv6, but we do not.
*/
if (fl.fl4_src == 0)
fl.fl4_src = inet_select_addr(dev_out, 0,
RT_SCOPE_LINK);
res.type = RTN_UNICAST;
goto make_route;
}
if (dev_out)
dev_put(dev_out);
err = -ENETUNREACH;
goto out;
}
free_res = 1;
if (res.type == RTN_LOCAL) {
if (!fl.fl4_src)
fl.fl4_src = fl.fl4_dst;
if (dev_out)
dev_put(dev_out);
dev_out = net->loopback_dev;
dev_hold(dev_out);
fl.oif = dev_out->ifindex;
if (res.fi)
fib_info_put(res.fi);
res.fi = NULL;
flags |= RTCF_LOCAL;
goto make_route;
}
#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (res.fi->fib_nhs > 1 && fl.oif == 0)
fib_select_multipath(&fl, &res);
else
#endif
if (!res.prefixlen && res.type == RTN_UNICAST && !fl.oif)
fib_select_default(net, &fl, &res);
if (!fl.fl4_src)
fl.fl4_src = FIB_RES_PREFSRC(res);
if (dev_out)
dev_put(dev_out);
dev_out = FIB_RES_DEV(res);
dev_hold(dev_out);
fl.oif = dev_out->ifindex;
make_route:
err = ip_mkroute_output(rp, &res, &fl, oldflp, dev_out, flags);
if (free_res)
fib_res_put(&res);
if (dev_out)
dev_put(dev_out);
out: return err;
}
如果我们键值中的目标地址fl.fl4_dst,即服务器地址没有设置的话(我们看到上边已经对这个键值进行了设置),但是我们还是看一下这段代码,我们看到如果没有设置目标地址就在if语句中将目标地址设置为源地址,如果还是为空,即客户端的地址也没有设置的话,就设置为回接地址即127.0.0.1。既然是回接那就不需要网络设备了,所以我们看到它调用dev_put()递减了先前找到的网络设备结构net_device的计数,改用网络空间中记录的loopback_dev处的网络设备结构。并且让键值的oif记录下这个网络设备的索引编号,设置路由查找结构res的返回类型为本地路由类型。然后就跳转到make_route标号处,一会我们标号处的代码。接着我们看到了调用
fib_lookup()
根据我们前面终确定的路由键值查找路由表,这个函数在路由设置过程那一节中列出了,请朋友们查看那里对函数的分析。正常情况下如果找到了路由表就会函数返回值是0,如果出错就会将返回路由结果的res中的路由表信息指针设成NULL,并且将路由键值fl中的客户端地址通过inet_select_addr()重新调整一个,这个函数请朋友们自己阅读了,然后递减对net_device的引用计数,而且还将返回的路由类型指定为RTN_UNICAST,然后就出错返回了。如果查找到了路由表结构就会检查这个找到的路由表是否是本地路由表,此时就要检查我们客户端的IP地址是否指定了,这是记录在fl.fl4_src中的,如果没有指定就让他使用目标IP地址,然后使dev_out这个net_device结构指针指向记录在网络空间net中的loopback_dev指针。增加他的使用计数,让路由键值fl的oif记录下它的索引编号,此时如果路由查询结构中记录的路由信息结构如果存在的话还要使这个指针为空。然后设置一下路由标志RTCF_LOCAL,接着跳转到make_route标号处。如果不是本地路由表而是主路由表的话,还要根据是否启用了多路由的支持选项,通过fib_select_multipath()对路由表中的所有下一个跳转结构fib_nh的重新优化计算。

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()-->fib_select_multipath()
void fib_select_multipath(const struct flowi *flp, struct fib_result *res)
{
struct fib_info *fi = res->fi;
int w;
spin_lock_bh(&fib_multipath_lock);
if (fi->fib_power = 0) {
int power = 0;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD)) {
power += nh->nh_weight;
nh->nh_power = nh->nh_weight;
}
} endfor_nexthops(fi);
fi->fib_power = power;
if (power = 0) {
spin_unlock_bh(&fib_multipath_lock);
/* Race condition: route has just become dead. */
res->nh_sel = 0;
return;
}
}
/* w should be random number [0..fi->fib_power-1],
it is pretty bad approximation.
*/
w = jiffies % fi->fib_power;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) {
if ((w -= nh->nh_power) = 0) {
nh->nh_power--;
fi->fib_power--;
res->nh_sel = nhsel;
spin_unlock_bh(&fib_multipath_lock);
return;
}
}
} endfor_nexthops(fi);
/* Race condition: route has just become dead. */
res->nh_sel = 0;
spin_unlock_bh(&fib_multipath_lock);
}

主要是依据nh_weight和nh_power的计算还有对跳转次数也进行了调整。这个函数是基于算法的过程我们不具体解析了。
我们继续看ip_route_connect()函数的代码,如果res这个路由查询结构中记录的找到的路由的前缀长度是0并且路由类型是RTN_UNICAST,而且键值的索引值是0的话,就要调用fib_select_default(),这个函数也把它留给朋友们自己阅读了。如果键值中的客户端地址还是空的,即源地址IP,就要使用路由信息结构中记录的地址了。根据下一个跳转结构中记录的网络设备也要调整这里的dev_out结构指针以及路由键值中所记录的索引编号。后函数进入了ip_mkroute_output()函数中建立一个路由并插入到缓存中。

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()-->ip_mkroute_output()

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);
err = rt_intern_hash(hash, rth, rp);
}
return err;
}

首先是_mkroute_output()函数,这个函数

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()-->ip_mkroute_output()-->__mkroute_output()
static int __mkroute_output(struct rtable **result,
struct fib_result *res,
const struct flowi *fl,
const struct flowi *oldflp,
struct net_device *dev_out,
unsigned flags)
{
struct rtable *rth;
struct in_device *in_dev;
u32 tos = RT_FL_TOS(oldflp);
int err = 0;
if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))
return -EINVAL;
if (fl->fl4_dst == htonl(0xFFFFFFFF))
res->type = RTN_BROADCAST;
else if (ipv4_is_multicast(fl->fl4_dst))
res->type = RTN_MULTICAST;
else if (ipv4_is_lbcast(fl->fl4_dst) || ipv4_is_zeronet(fl->fl4_dst))
return -EINVAL;
if (dev_out->flags & IFF_LOOPBACK)
flags |= RTCF_LOCAL;
/* get work reference to inet device */
in_dev = in_dev_get(dev_out);
if (!in_dev)
return -EINVAL;
if (res->type == RTN_BROADCAST) {
flags |= RTCF_BROADCAST | RTCF_LOCAL;
if (res->fi) {
fib_info_put(res->fi);
res->fi = NULL;
}
} else if (res->type == RTN_MULTICAST) {
flags |= RTCF_MULTICAST|RTCF_LOCAL;
if (!ip_check_mc(in_dev, oldflp->fl4_dst, oldflp->fl4_src,
oldflp->proto))
flags &= ~RTCF_LOCAL;
/* If multicast route do not exist use
default one, but do not gateway in this case.
Yes, it is hack.
*/
if (res->fi && res->prefixlen 4) {
fib_info_put(res->fi);
res->fi = NULL;
}
}
rth = dst_alloc(&ipv4_dst_ops);
if (!rth) {
err = -ENOBUFS;
goto cleanup;
}
atomic_set(&rth->u.dst.__refcnt, 1);
rth->u.dst.flags= DST_HOST;
if (IN_DEV_CONF_GET(in_dev, NOXFRM))
rth->u.dst.flags |= DST_NOXFRM;
if (IN_DEV_CONF_GET(in_dev, NOPOLICY))
rth->u.dst.flags |= DST_NOPOLICY;
rth->fl.fl4_dst = oldflp->fl4_dst;
rth->fl.fl4_tos = tos;
rth->fl.fl4_src = oldflp->fl4_src;
rth->fl.oif = oldflp->oif;
rth->fl.mark = oldflp->mark;
rth->rt_dst = fl->fl4_dst;
rth->rt_src = fl->fl4_src;
rth->rt_iif = oldflp->oif ? : dev_out->ifindex;
/* get references to the devices that are to be hold by the routing
cache entry */
rth->u.dst.dev = dev_out;
dev_hold(dev_out);
rth->idev = in_dev_get(dev_out);
rth->rt_gateway = fl->fl4_dst;
rth->rt_spec_dst= fl->fl4_src;
rth->u.dst.output=ip_output;
rth->rt_genid = atomic_read(&rt_genid);
RT_CACHE_STAT_INC(out_slow_tot);
if (flags & RTCF_LOCAL) {
rth->u.dst.input = ip_local_deliver;
rth->rt_spec_dst = fl->fl4_dst;
}
if (flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
rth->rt_spec_dst = fl->fl4_src;
if (flags & RTCF_LOCAL &&
!(dev_out->flags & IFF_LOOPBACK)) {
rth->u.dst.output = ip_mc_output;
RT_CACHE_STAT_INC(out_slow_mc);
}
#ifdef CONFIG_IP_MROUTE
if (res->type == RTN_MULTICAST) {
if (IN_DEV_MFORWARD(in_dev) &&
!ipv4_is_local_multicast(oldflp->fl4_dst)) {
rth->u.dst.input = ip_mr_input;
rth->u.dst.output = ip_mc_output;
}
}
#endif
}
rt_set_nexthop(rth, res, 0);
rth->rt_flags = flags;
*result = rth;
cleanup:
/* release work reference to inet device */
in_dev_put(in_dev);
return err;
}
我们看到这个函数实际上就是新分配了一个新的rtable结构,然后根据我们前边查找的路由信息还有路由键值、下一次跳转信息等内容对这个结构进行设置。上面这些代码对朋友们来说都不难理解,也很明显,分配路由表结构的函数是通过dst_alloc()函数来完成的,应该注意传递给他的dst_ops结构指针是ipv4_dst_ops。我们在以前地址绑定的章节的路由表的初始化那里对这个函数进行了描述,它是IPV4的路由操作函数表,当时我们没有把这个全局的操作函数表贴出来,现在贴在下边
static struct dst_ops ipv4_dst_ops = {
.family = AF_INET,
.protocol = __constant_htons(ETH_P_IP),
.gc = rt_garbage_collect,
.check = ipv4_dst_check,
.destroy = ipv4_dst_destroy,
.ifdown = ipv4_dst_ifdown,
.negative_advice = ipv4_negative_advice,
.link_failure = ipv4_link_failure,
.update_pmtu = ip_rt_update_pmtu,
.local_out = __ip_local_out,
.entry_size = sizeof(struct rtable),
.entries = ATOMIC_INIT(0),
};
我们下边结合dst_alloc()函数来看一下

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()-->ip_mkroute_output()-->__mkroute_output()-->dst_alloc()
void * dst_alloc(struct dst_ops * ops)
{
struct dst_entry * dst;
if (ops->gc && atomic_read(&ops->entries) > ops->gc_thresh) {
if (ops->gc(ops))
return NULL;
}
dst = kmem_cache_zalloc(ops->kmem_cachep, GFP_ATOMIC);
if (!dst)
return NULL;
atomic_set(&dst->__refcnt, 0);
dst->ops = ops;
dst->lastuse = jiffies;
dst->path = dst;
dst->input = dst->output = dst_discard;
#if RT_CACHE_DEBUG >= 2
atomic_inc(&dst_total);
#endif
atomic_inc(&ops->entries);
return dst;
}
dst_entry结构是专门用于路由目的而使用的结构体,我们在后边会看到这个结构体的内容。可能有的朋友们说这个函数明明是为对dst_entry结构的分配,为什么反而是对rtable结构的分配呢,如果看一下上边ipv4_dst_ops结构中的entry_size是sizeof(struct rtable),我想疑问也就解释了。朋友们可能会看到这里没有对rtable的具体分配,如果结合一下
http://blog.chinaunix.net/u2/64681/showart.php?id=1715717
 那节中对路由表的初始化过程中的ip_rt_init()函数有这样的一句代码

ipv4_dst_ops.kmem_cachep =
kmem_cache_create("ip_dst_cache", sizeof(struct rtable), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
再结合上面的
dst = kmem_cache_zalloc(ops->kmem_cachep, GFP_ATOMIC);
这样就理解了其实在分配dst时是按照rtable结构的长度分配的,而dst又在rtable结构的开始处,所以说dst的指针也就是rtable的起始地址。(虽然看一下上边的rtable结构体的定义在那里是在一个union之中,但是这个union也是在rtable的开始处,并且它只有一个dst)。我们再回到ip_mkroute_output()函数处继续往下看rt_hash()函数

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()-->ip_mkroute_output()-->rt_hash()
static inline unsigned int rt_hash(__be32 daddr, __be32 saddr, int idx)
{
return jhash_3words((__force u32)(__be32)(daddr),
(__force u32)(__be32)(saddr),
idx, atomic_read(&rt_genid))
& rt_hash_mask;
}
这个函数计算利用我们的要连接的地址,即服务器的地址,还有本机地址,即客户端的地址,以及网络设备的索引编号三者计算一个hash的键值。然后调用rt_intern_hash()函数,将我们新分配的rtable结构项插入到缓存中的hash桶队列中。

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()-->ip_mkroute_output()-->rt_intern_hash()

static int rt_intern_hash(unsigned hash, struct rtable *rt, struct rtable **rp)
{
struct rtable *rth, **rthp;
unsigned long now;
struct rtable *cand, **candp;
u32 min_score;
int chain_length;
int attempts = !in_softirq();
restart:
chain_length = 0;
min_score = ~(u32)0;
cand = NULL;
candp = NULL;
now = jiffies;
rthp = &rt_hash_table[hash].chain;
spin_lock_bh(rt_hash_lock_addr(hash));
while ((rth = *rthp) != NULL) {
if (rth->rt_genid != atomic_read(&rt_genid)) {
*rthp = rth->u.dst.rt_next;
rt_free(rth);
continue;
}
if (compare_keys(&rth->fl, &rt->fl) && compare_netns(rth, rt)) {
/* Put it first */
*rthp = rth->u.dst.rt_next;
/*
* Since lookup is lockfree, the deletion
* must be visible to another weakly ordered CPU before
* the insertion at the start of the hash chain.
*/
rcu_assign_pointer(rth->u.dst.rt_next,
rt_hash_table[hash].chain);
/*
* Since lookup is lockfree, the update writes
* must be ordered for consistency on SMP.
*/
rcu_assign_pointer(rt_hash_table[hash].chain, rth);
dst_use(&rth->u.dst, now);
spin_unlock_bh(rt_hash_lock_addr(hash));
rt_drop(rt);
*rp = rth;
return 0;
}
if (!atomic_read(&rth->u.dst.__refcnt)) {
u32 score = rt_score(rth);
if (score = min_score) {
cand = rth;
candp = rthp;
min_score = score;
}
}
chain_length++;
rthp = &rth->u.dst.rt_next;
}
if (cand) {
/* ip_rt_gc_elasticity used to be average length of chain
* length, when exceeded gc becomes really aggressive.
*
* The second limit is less certain. At the moment it allows
* only 2 entries per bucket. We will see.
*/
if (chain_length > ip_rt_gc_elasticity) {
*candp = cand->u.dst.rt_next;
rt_free(cand);
}
}
/* Try to bind route to arp only if it is output
route or unicast forwarding path.
*/
if (rt->rt_type == RTN_UNICAST || rt->fl.iif == 0) {
int err = arp_bind_neighbour(&rt->u.dst);
if (err) {
spin_unlock_bh(rt_hash_lock_addr(hash));
if (err != -ENOBUFS) {
rt_drop(rt);
return err;
}
/* Neighbour tables are full and nothing
can be released. Try to shrink route cache,
it is most likely it holds some neighbour records.
*/
if (attempts-- > 0) {
int saved_elasticity = ip_rt_gc_elasticity;
int saved_int = ip_rt_gc_min_interval;
ip_rt_gc_elasticity = 1;
ip_rt_gc_min_interval = 0;
rt_garbage_collect(&ipv4_dst_ops);
ip_rt_gc_min_interval = saved_int;
ip_rt_gc_elasticity = saved_elasticity;
goto restart;
}
if (net_ratelimit())
printk(KERN_WARNING "Neighbour table overflow.\n");
rt_drop(rt);
return -ENOBUFS;
}
}
rt->u.dst.rt_next = rt_hash_table[hash].chain;
#if RT_CACHE_DEBUG >= 2
if (rt->u.dst.rt_next) {
struct rtable *trt;
printk(KERN_DEBUG "rt_cache @%02x: " NIPQUAD_FMT, hash,
NIPQUAD(rt->rt_dst));
for (trt = rt->u.dst.rt_next; trt; trt = trt->u.dst.rt_next)
printk(" . " NIPQUAD_FMT, NIPQUAD(trt->rt_dst));
printk("\n");
}
#endif
rt_hash_table[hash].chain = rt;
spin_unlock_bh(rt_hash_lock_addr(hash));
*rp = rt;
return 0;
}
首先要根据我们已经通过rt_hash计算的hash键值在全局的rt_hash_table这个rt_hash_bucket哈希表桶数组中的队列中找到所属的hash队列头,然后沿着这个队列做一些检查,主要是通过compare_keys来比对路由键值,通过compare_netns来比对目标设备。如果这些比对成功了,就通过rcu_assign_pointer()函数将找到的路由插入到队列的前端,然后就通过rt_drop释放我们新建的路由,因为已经存在了就不需要这个新建的路由了。还要通过atomic_read增加对找到的路由的使用计数。如果没有找到我们看到在循环中还会不断的通过rt_score来计算一个分值来选出一个可以删除的路由让cand指向它。当循环结束时,也就是在缓存中没有找到已经存在的路由,就要将找到的适合删除的路由释放掉,接下来检查我们要插入的路由是否是输出用的路由或者是RTN_UNICAST类型的路由,就要调用arp_bind_neighbour()函数绑定路由给arp了。我们以后看过neighbour的内容后再回过头来看这个函数,如果绑定成功了,接下来rt_intern_hash()函数就要将我们的路由插入到哈希表桶中的队列开始处了。然后我们层层返回到ip_route_connect()函数中,继续往下看,为了阅读方便我们把其中的代码再列在下边

if (err)
return err;
fl.fl4_dst = (*rp)->rt_dst;
fl.fl4_src = (*rp)->rt_src;
ip_rt_put(*rp);
*rp = NULL;

我们继续看ip_route_connect()函数中得到了这个rp路由表后则根据路由表中提供的目标地址和源地址调整这里的路由键值fl,后函数进入ip_route_output_flow()返回

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> ip_route_output_flow()
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;
if (flp->proto) {
if (!flp->fl4_src)
flp->fl4_src = (*rp)->rt_src;
if (!flp->fl4_dst)
flp->fl4_dst = (*rp)->rt_dst;
err = __xfrm_lookup((struct dst_entry **)rp, flp, sk,
flags ? XFRM_LOOKUP_WAIT : 0);
if (err == -EREMOTE)
err = ipv4_dst_blackhole(rp, flp);
return err;
}
return 0;
}
我们看到在这个函数中再次通过__ip_route_output_key()函数确定一下是否能在路由的杂凑缓存表中找到我们的路由表,也主要是调整键值中的地址。然后进入__xfrm_lookup()函数,当前这不是我们的重点,我们看到这个函数在include/net/dst.h中是直接返回0了。这个函数是根据内核是否使用了xfrm策略,而进入xfrm目录下的xfrm_policy.c文件中,__xfrm_lookup()非常大主要是ipsec的过程,有兴趣的朋友可以参考下面linuxforum论坛中的一段文字
事实上无论是本机的包还是转发的包,都必须经过xfrm_lookup这个函数的处理。
而每个skbuff在xfrm_lookup处理前dst_output 已经有了一个默认的设置,比如说是ip_output
而在经过xfrm_lookup处理过程中首先要检查的是策略,也就是policy,策略无非就是绕过、处理和丢弃。如果是绕过,则不作任何的处理,直接返回,然后根据原有的dst_output将数据包发出。这个过程和Linux-2.4内核的处理过程基本相同。关键区别是在加入ipsec处理上
如果策略决定数据包需要经过ipsec处理,那在xfrm_lookup中就有一个复杂的处理过程了,大概顺序是
1、查策略
2、找sa(xfrm_state),xfrm_find_bundle
3、没有找到就创建一个xfrm_bundle_create
4、创建完以后还要添加sa(xfrm_state)
5、后关键的一步是在dst_output的修改上,经过处理后的skbuff的dst_output已经不再是原有的ip_output,而是根据查找的xfrm_state设置成具体的ah_output或者是esp_output。
而这些所有的dst_output都连成了一个链,往往需要进行ipsec处理的都放在链头先获得处理,处理完后又被修改为ip_output这样的正常处理,又重新挂到链上处理。
这样需要ipsec处理的包可以得到不同的处理。
到此我们返回到tcp_v4_connect()函数中继续往下看,我们看到除了一些对函数返回值的检查,我们忽略对出错的检查,接着对路由表进行检查,如果是多播或者广播的话也要释放路由表返回,还对inet的地址进行了调整,主要是源地址和目标地址的设置。还要对tcp_sock的结构变量tp的状态的检查并做出调整。


文章来源CU社区:内核中的TCP的追踪分析-21-TCP(IPV4)的socket请求连接 -1

相关文章