免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1032 | 回复: 0
打印 上一主题 下一主题

内核中的TCP的追踪分析-12-TCP(IPV4)的socket连接-续4 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-11-17 13:58 |只看该作者 |倒序浏览
我们今天继续追踪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结构是专门用于路由目的而使用的结构体,这个结构体的定义我们在
http://blog.chinaunix.net/u2/64681/showart_1414314.html
中列出过了,我们看到这个函数返回的是dst_entry结构指针,但是在ip_queue_xmit()函数中我们看到rt = (struct rtable *)__sk_dst_check(sk, 0);将这个结构体指针转换成了rtable的路由表查询结构指针。这是为什么呢?对照一下
http://blog.chinaunix.net/u2/64681/showart_1408613.html
一节中的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()函数新建一个路由值并缓存到路由表中,关于具体的路由表过程我们暂时放在以后的章节中叙述,ip_route_output_flow()函数我们已经在
http://blog.chinaunix.net/u2/64681/showart_1408613.html
那篇文章的最后部分进行了简要论述,接着看函数中根据路由表的地址对我们的数据包的目标地址进行了调整,然后我们看到进入了packet_routed标号段,首先是检查一下是否设置了ip的操作函数结构以及路由的检测,然后通过skb_push()函数在数据包中为我们的ip头结构预留出空间,这个函数我们在
http://blog.chinaunix.net/u2/64681/showart_1415963.html
中已经看过了,然后通过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);
}
这个函数将我们的数据包发送出去,我们在
http://blog.chinaunix.net/u2/64681/showart_1408613.html
第八节中看到过__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);
我是无名小卒,转载的朋友请注明出处
http://qinjiana0786.cublog.cn

在分析这段代码之前我们必须再次回忆一下inet_init()函数,这个函数在初始那节中曾经讲到了他的由来
http://blog.chinaunix.net/u2/64681/showart_1358880.html
,我们在这个初始化函数中曾经看到了对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地址的对应关系所使用的,关于arp的一些理解请看一下
http://blog.chinaunix.net/u2/64681/showart_695622.html
这节内容的练习,同样为了将来分析的需要我们把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        = &eth_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),其余部分我们暂且放一放,有兴趣了的朋友可看一下
http://blog.chinaunix.net/u1/51562/showart_414626.html
linux协议栈之邻居子系统分析文章的详细论述,,这个函数又是对应我们arp_hh_ops钩子结构中的
.queue_xmit =             dev_queue_xmit,
也就是进入dev_queue_xmit()函数执行发送,我们明天继续这个函数的分析。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/64681/showart_1420186.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP