绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
内核中的TCP的追踪分析-12-TCP(IPV4)的socket连接-续4
2020-05-25 14:58:05

我们今天继续追踪tcp的socket的连接,上一节谈到了追踪到了ip_queue_xmit()发送数据包,我们来看这个函数
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;
/* Skip all of this if the packet is already routed,
* f.e. by something like SCTP.
*/
rt = skb->rtable;
if (rt != NULL)
goto packet_routed;
/* Make sure we can route this packet. */
rt = (struct rtable *)__sk_dst_check(sk, 0);
if (rt == NULL) {
__be32 daddr;
/* Use correct destination address if we have options. */
daddr = inet->daddr;
if(opt && opt->srr)
daddr = opt->faddr;
{
struct flowi fl = { .oif = sk->sk_bound_dev_if,
.nl_u = { .ip4_u =
{ .daddr = daddr,
.saddr = inet->saddr,
.tos = RT_CONN_FLAGS(sk) } },
.proto = sk->sk_protocol,
.uli_u = { .ports =
{ .sport = inet->sport,
.dport = inet->dport } } };
/* If this fails, retransmit mechanism of transport layer will
* keep trying until route appears or the connection times
* itself out.
*/
security_sk_classify_flow(sk, &fl);
if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
goto no_route;
}
sk_setup_caps(sk, &rt->u.dst);
}
skb->dst = dst_clone(&rt->u.dst);
packet_routed:
if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
goto no_route;
/* OK, we know where to send it, allocate and build IP header. */
skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
skb_reset_network_header(skb);
iph = ip_hdr(skb);
*((__be16 *)iph) = htons((4 12) | (5 8) | (inet->tos & 0xff));
if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)
iph->frag_off = htons(IP_DF);
else
iph->frag_off = 0;
iph->ttl = ip_select_ttl(inet, &rt->u.dst);
iph->protocol = sk->sk_protocol;
iph->saddr = rt->rt_src;
iph->daddr = rt->rt_dst;
/* Transport layer set skb->h.foo itself. */
if (opt && opt->optlen) {
iph->ihl += opt->optlen >> 2;
ip_options_build(skb, opt, inet->daddr, rt, 0);
}
ip_select_ident_more(iph, &rt->u.dst, sk,
(skb_shinfo(skb)->gso_segs ?: 1) - 1);
skb->priority = sk->sk_priority;
skb->mark = sk->sk_mark;
return ip_local_out(skb);
no_route:
IP_INC_STATS(IPSTATS_MIB_OUTNOROUTES);
kfree_skb(skb);
return -EHOSTUNREACH;
}
先是注意一下参数,skb是我们在前面为连接所准备的数据包,而ipfragok参数在tcp_transmit_skb()函数中传递下为是0。代码中首先看到了查看了数据包中的路由表是否已经找到,一般在流控制传输协议(SCTP)下,会提前设置数据包的路由查询表,SCTP这是一种在网络连接两端之间同时传输多个数据流的协议。SCTP有时称“下一代TCP”或TCPng,它设计用于在因特网上支持电话连接,电话连接要求语音及其它数据与信令信息同时发送。如果不是SCTP则进入__sk_dst_check()函数查找路由表
struct dst_entry *__sk_dst_check(struct sock *sk, u32 cookie)
{
struct dst_entry *dst = sk->sk_dst_cache;
if (dst && dst->obsolete && dst->ops->check(dst, cookie) == NULL) {
sk->sk_dst_cache = NULL;
dst_release(dst);
return NULL;
}
return dst;
}
我们知道dst_entry结构是专门用于路由目的而使用的结构体,
中列出过了,我们看到这个函数返回的是dst_entry结构指针,但是在ip_queue_xmit()函数中我们看到rt = (struct rtable *)__sk_dst_check(sk, 0);将这个结构体指针转换成了rtable的路由表查询结构指针。这是为什么呢?
一节中的struct rtable内容可以看到
struct rtable
{
union
{
struct dst_entry dst;
} u;
。。。。。。
};
这个结构体的开始处正是dst_entry,所以这里将dst_entry的指针转换为rtable的指针也就是将dst_entry的地址做为了rtable的起始地址。
在__sk_dst_check()中检查一下我们的sock结构中是否已经保存了路由用的dst_entry结构。然后如果没有路由的话就会在ip_queue_xmit()函数新建一个路由值并缓存到路由表中,关于具体的路由表过程我们暂时放在以后的章节中叙述
那篇文章的后部分进行了简要论述,接着看函数中根据路由表的地址对我们的数据包的目标地址进行了调整,然后我们看到进入了packet_routed标号段,首先是检查一下是否设置了ip的操作函数结构以及路由的检测,然后通过skb_push()函数在数据包中为我们的ip头结构预留出空间
中已经看过了,然后通过skb_reset_network_header()设置一下skb的network_header,之后代码中是对我们的ip头结构变量iph进行了一系列的初始化操作,其中的函数都是与此目的相关,我们就不进入了,我们看到函数后进入了ip_local_out()中
int ip_local_out(struct sk_buff *skb)
{
int err;
err = __ip_local_out(skb);
if (likely(err == 1))
err = dst_output(skb);
return err;
}
首先调用了
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->dev,
dst_output);
}
上面是对nf_hook()输出的过滤操作,我们不进去看了,我们接着看在ip_local_out()函数中调用了
dst_output()
static inline int dst_output(struct sk_buff *skb)
{
return skb->dst->output(skb);
}
第八节中看到过__xfrm_lookup()中那里只是简要的叙述了一下,这里的具体的输出必须要结合那里的__xfrm_lookup()才能够看明白,在那篇文章中我们节摘了互联网上的一段文字,尤其是:
5、后关键的一步是在dst_output的修改上,经过处理后的skbuff的dst_output已经不再是原有的ip_output,而是根据查找的xfrm_state设置成具体的ah_output或者是esp_output。
所以我们还是留在将来在具体的分析__xfrm_lookup()函数,但是在在第八节中我们还看到函数后调用了ip_route_output_slow()->ip_mkroute_output()->__mkroute_output()函数在那里rth->u.dst.output=ip_output;设置成了ip_output()。这个函数在/net/ipv4/ip_output.c中的299行处
int ip_output(struct sk_buff *skb)
{
struct net_device *dev = skb->dst->dev;
IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
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));
}
我们看到后通过宏NF_HOOK_COND直接进入了ip_finish_output()函数
static int ip_finish_output(struct sk_buff *skb)
{
#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
/* Policy lookup after SNAT yielded a new policy */
if (skb->dst->xfrm != NULL) {
IPCB(skb)->flags |= IPSKB_REROUTED;
return dst_output(skb);
}
#endif
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);
}
这个函数根据数据包的长度来确定是否分片段发送,但是我们可以看到其终都是调用ip_finish_output2()函数来完成发送过程,我们只看这一个函数
static inline int ip_finish_output2(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct rtable *rt = (struct rtable *)dst;
struct net_device *dev = dst->dev;
unsigned int hh_len = LL_RESERVED_SPACE(dev);
if (rt->rt_type == RTN_MULTICAST)
IP_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS);
else if (rt->rt_type == RTN_BROADCAST)
IP_INC_STATS(IPSTATS_MIB_OUTBCASTPKTS);
/* Be paranoid, rather than too clever. */
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;
}
if (dst->hh)
return neigh_hh_output(dst->hh, skb);
else if (dst->neighbour)
return dst->neighbour->output(skb);
if (net_ratelimit())
printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
kfree_skb(skb);
return -EINVAL;
}
我们对上面的函数只关心与发送相关的过程,函数其余的代码就相对比较简单了,所以上面的代码重要的地方只是在
if (dst->hh)
return neigh_hh_output(dst->hh, skb);
else if (dst->neighbour)
return dst->neighbour->output(skb);


在分析这段代码之前我们必须再次回忆一下inet_init()函数
,我们在这个初始化函数中曾经看到了对arp的初始化arp_init();我们这里就不再贴出inet_init()的代码了,我们看一下arp_init()函数
void __init arp_init(void)
{
neigh_table_init(&arp_tbl);
。。。。。。
}
初始化了一个arp_tbl全局的结构变量
struct neigh_table arp_tbl = {
.family = AF_INET,
.entry_size = sizeof(struct neighbour) + 4,
.key_len = 4,
.hash = arp_hash,
.constructor = arp_constructor,
.proxy_redo = parp_redo,
.id = "arp_cache",
.parms = {
.tbl = &arp_tbl,
.base_reachable_time = 30 * HZ,
.retrans_time = 1 * HZ,
.gc_staletime = 60 * HZ,
.reachable_time = 30 * HZ,
.delay_probe_time = 5 * HZ,
.queue_len = 3,
.ucast_probes = 3,
.mcast_probes = 3,
.anycast_delay = 1 * HZ,
.proxy_delay = (8 * HZ) / 10,
.proxy_qlen = 64,
.locktime = 1 * HZ,
},
.gc_interval = 30 * HZ,
.gc_thresh1 = 128,
.gc_thresh2 = 512,
.gc_thresh3 = 1024,
};
首先结构体中具体变量我们不分析了,这个结构体struct neigh_table是用于arp协议中的“邻居表”操作,获取ip地址与mac地址的对应关系所使用的
这节内容的练习,同样为了将来分析的需要我们把struct neigh_table的代码贴在这里以便将来学习方便
struct neigh_table
{
struct neigh_table *next;
int family;
int entry_size;
int key_len;
__u32 (*hash)(const void *pkey, const struct net_device *);
int (*constructor)(struct neighbour *);
int (*pconstructor)(struct pneigh_entry *);
void (*pdestructor)(struct pneigh_entry *);
void (*proxy_redo)(struct sk_buff *skb);
char *id;
struct neigh_parms parms;
/* HACK. gc_* shoul follow parms without a gap! */
int gc_interval;
int gc_thresh1;
int gc_thresh2;
int gc_thresh3;
unsigned long last_flush;
struct timer_list gc_timer;
struct timer_list proxy_timer;
struct sk_buff_head proxy_queue;
atomic_t entries;
rwlock_t lock;
unsigned long last_rand;
struct kmem_cache *kmem_cachep;
struct neigh_statistics *stats;
struct neighbour **hash_buckets;
unsigned int hash_mask;
__u32 hash_rnd;
unsigned int hash_chain_gc;
struct pneigh_entry **phash_buckets;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *pde;
#endif
};
关于这个数据结构的详细解释请朋友们看
http://blog.chinaunix.net/u1/51562/showart_414578.html
这篇文章。我们先来回忆一下在以前看到过的ip_route_output_slow()函数的代码,代码我们在我们在第八节建立tcp连接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;
}
很显然这个函数其他部分我们暂且放一放,只关心与我们的arp初始化相关部分,因为我们上面必须追踪溯源,这里重要的部分莫过于err = rt_intern_hash(hash, rth, rp)然后进入rt_intern_hash()在那里调用了 arp_bind_neighbour()过程中的函数我们不看了,直接看一下这个arp 的重要函数
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;
if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
nexthop = 0;
n = __neigh_lookup_errno(
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
dev->type == ARPHRD_ATM ? clip_tbl_hook :
#endif
&arp_tbl, &nexthop, dev);
if (IS_ERR(n))
return PTR_ERR(n);
dst->neighbour = n;
}
return 0;
}
我们看到这个函数中重要的是__neigh_lookup_errno()函数的调用
static inline struct neighbour *
__neigh_lookup_errno(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n = neigh_lookup(tbl, pkey, dev);
if (n)
return n;
return neigh_create(tbl, pkey, dev);
}
如果我们的neighhour还没有建立的话就要进入neigh_create()创建一个,在那里我们看到了上边arp_init()时的全局的arp_tbl中的arp_constructor钩子函数, 这是在arp_bind_neighbour()时做为参数传递下来的
n = __neigh_lookup_errno(
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
dev->type == ARPHRD_ATM ? clip_tbl_hook :
#endif
&arp_tbl, &nexthop, dev);
我们接下看到在neigh_create()这句
/* Protocol specific setup. */
if (tbl->constructor && (error = tbl->constructor(n)) 0) {
rc = ERR_PTR(error);
goto out_neigh_release;
}
很明显要执行arp_constructor()函数,在这个“构造函数”的代码中我们看到了

if (dev->header_ops->cache)
neigh->ops = &arp_hh_ops;
else
neigh->ops = &arp_generic_ops;
这要根据网卡驱动是否设置了对于header_ops的相关设置,如果我们看一下网卡的驱动程序在
/net/eithernet/eth.c中的322处
void ether_setup(struct net_device *dev)
{
dev->header_ops = ð_header_ops;
。。。。。。
}
很显然那里设置了header_ops。我们在309行处看到
const struct header_ops eth_header_ops ____cacheline_aligned = {
.create = eth_header,
.parse = eth_header_parse,
.rebuild = eth_rebuild_header,
.cache = eth_header_cache,
.cache_update = eth_header_cache_update,
};
所以很显然上面的arp_constructor()函数会执行
neigh->ops = &arp_hh_ops;
这个钩子结构是在/net/ipv4/arp.c中的145行处
static struct neigh_ops arp_hh_ops = {
.family = AF_INET,
.solicit = arp_solicit,
.error_report = arp_error_report,
.output = neigh_resolve_output,
.connected_output = neigh_resolve_output,
.hh_output = dev_queue_xmit,
.queue_xmit = dev_queue_xmit,
};
设置完成后,返回我们在上边的看到的arp_bind_neighbour()函数中
dst->neighbour = n;
将新创建的struct neighbour结构赋值到dst的neighbour,然后,
我们回到ip_finish_output2()函数中继续分析
if (dst->hh)
return neigh_hh_output(dst->hh, skb);
else if (dst->neighbour)
return dst->neighbour->output(skb);
这里我们结合上面arp_constructor()代码中的
if (neigh->nud_state&NUD_VALID)
neigh->output = neigh->ops->connected_output;
else
neigh->output = neigh->ops->output;
我们知道上面已经将neigh->ops 指向 &arp_hh_ops了,所以这里的output也就是neigh_resolve_output()钩子函数了,所以我们上边的ip_finish_output2()函数会执行neigh_resolve_output()
int neigh_resolve_output(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct neighbour *neigh;
int rc = 0;
if (!dst || !(neigh = dst->neighbour))
goto discard;
__skb_pull(skb, skb_network_offset(skb));
if (!neigh_event_send(neigh, skb)) {
int err;
struct net_device *dev = neigh->dev;
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);
}
if (err >= 0)
rc = neigh->ops->queue_xmit(skb);
else
goto out_kfree_skb;
}
out:
return rc;
discard:
NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n",
dst, dst ? dst->neighbour : NULL);
out_kfree_skb:
rc = -EINVAL;
kfree_skb(skb);
goto out;
}
上面函数关键的部分是rc = neigh->ops->queue_xmit(skb),其余部分我们暂且放一放
linux协议栈之邻居子系统分析文章的详细论述,,这个函数又是对应我们arp_hh_ops钩子结构中的
.queue_xmit = dev_queue_xmit,
也就是进入dev_queue_xmit()函数执行发送,我们明天继续这个函数的分析。


文章来源CU社区:内核中的TCP的追踪分析-12-TCP(IPV4)的socket连接-续4

分享好友

分享这个小栈给你的朋友们,一起进步吧。

内核源码
创建时间:2020-05-18 13:36:55
内核源码精华帖内容汇总
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~