Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1321407
  • 博文数量: 107
  • 博客积分: 10155
  • 博客等级: 上将
  • 技术积分: 2166
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-25 16:57
文章分类

全部博文(107)

文章存档

2010年(1)

2009年(1)

2008年(105)

分类: LINUX

2008-11-24 09:13:17

我们上一节讲到了客户端请求到达服务器中的tcp_v4_conn_request()函数中时,服务器会给客户端发送一个ack,“第二次握手”的内容我们在上节中提到放在本文中完成,我们在tcp_v4_conn_request()函数中看到“第二次握手”是由__tcp_v4_send_synack()函数完成的

static int __tcp_v4_send_synack(struct sock *sk, struct request_sock *req,
                struct dst_entry *dst)
{
    const struct inet_request_sock *ireq = inet_rsk(req);
    int err = -1;
    struct sk_buff * skb;

    /* First, grab a route. */
    if (!dst && (dst = inet_csk_route_req(sk, req)) == NULL)
        return -1;

    skb = tcp_make_synack(sk, dst, req);

    if (skb) {
        struct tcphdr *th = tcp_hdr(skb);

        th->check = tcp_v4_check(skb->len,
                     ireq->loc_addr,
                     ireq->rmt_addr,
                     csum_partial((char *)th, skb->len,
                         skb->csum));

        err = ip_build_and_send_pkt(skb, sk, ireq->loc_addr,
                     ireq->rmt_addr,
                     ireq->opt);
        err = net_xmit_eval(err);
    }

    dst_release(dst);
    return err;
}

我们看到根据我们客户端的requst_sock结构得到inet_request_sock结构指针变量ireq,这个结构体我们在上一节http://blog.chinaunix.net/u2/64681/showart.php?id=1657954 中看到了。我们在上一节的tcp_v4_conn_request()函数中看到了取得路由的相关信息

dst = inet_csk_route_req(sk, req),在那个函数中会调用ip_route_output_flow()来查找路由表并且得到路由的信息,ip_route_output_flow()函数我们在第8http://blog.chinaunix.net/u2/64681/showart.php?id=1408613 的文章末尾处看到了,在那里我们还并没有深入探讨路由的一些细节,但是在将来我们会再次具体的分析这里。所以这里还是围绕着主线继续往下看,我们看到在上边的__tcp_v4_send_synack()函数中再次看一下路由信息是否已经找到了,如果没有的话再次调用inet_csk_route_req()查找,如果还是没有找到就不能进行下去了。接下来要调用tcp_make_synack()函数准备一个SYNack

struct sk_buff *tcp_make_synack(struct sock *sk, struct dst_entry *dst,
                struct request_sock *req)
{
    struct inet_request_sock *ireq = inet_rsk(req);
    struct tcp_sock *tp = tcp_sk(sk);
    struct tcphdr *th;
    int tcp_header_size;
    struct sk_buff *skb;
#ifdef CONFIG_TCP_MD5SIG
    struct tcp_md5sig_key *md5;
    __u8 *md5_hash_location;
#endif

    skb = sock_wmalloc(sk, MAX_TCP_HEADER + 15, 1, GFP_ATOMIC);
    if (skb == NULL)
        return NULL;

    /* Reserve space for headers. */
    skb_reserve(skb, MAX_TCP_HEADER);

    skb->dst = dst_clone(dst);

    tcp_header_size = (sizeof(struct tcphdr) + TCPOLEN_MSS +
             (ireq->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0) +
             (ireq->wscale_ok ? TCPOLEN_WSCALE_ALIGNED : 0) +
             /* SACK_PERM is in the place of NOP NOP of TS */
             ((ireq->sack_ok && !ireq->tstamp_ok) ? TCPOLEN_SACKPERM_ALIGNED : 0));

#ifdef CONFIG_TCP_MD5SIG
    /* Are we doing MD5 on this segment? If so - make room for it */
    md5 = tcp_rsk(req)->af_specific->md5_lookup(sk, req);
    if (md5)
        tcp_header_size += TCPOLEN_MD5SIG_ALIGNED;
#endif
    skb_push(skb, tcp_header_size);
    skb_reset_transport_header(skb);

    th = tcp_hdr(skb);
    memset(th, 0, sizeof(struct tcphdr));
    th->syn = 1;
    th->ack = 1;
    TCP_ECN_make_synack(req, th);
    th->source = inet_sk(sk)->sport;
    th->dest = ireq->rmt_port;
    /* Setting of flags are superfluous here for callers (and ECE is
     * not even correctly set)
     */

    tcp_init_nondata_skb(skb, tcp_rsk(req)->snt_isn,
             TCPCB_FLAG_SYN | TCPCB_FLAG_ACK);
    th->seq = htonl(TCP_SKB_CB(skb)->seq);
    th->ack_seq = htonl(tcp_rsk(req)->rcv_isn + 1);
    if (req->rcv_wnd == 0) { /* ignored for retransmitted syns */
        __u8 rcv_wscale;
        /* Set this up on the first call only */
        req->window_clamp = tp->window_clamp ? : dst_metric(dst, RTAX_WINDOW);
        /* tcp_full_space because it is guaranteed to be the first packet */
        tcp_select_initial_window(tcp_full_space(sk),
            dst_metric(dst, RTAX_ADVMSS) - (ireq->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0),
            &req->rcv_wnd,
            &req->window_clamp,
            ireq->wscale_ok,
            &rcv_wscale);
        ireq->rcv_wscale = rcv_wscale;
    }

    /* RFC1323: The window in SYN & SYN/ACK segments is never scaled. */
    th->window = htons(min(req->rcv_wnd, 65535U));
#ifdef CONFIG_SYN_COOKIES
    if (unlikely(req->cookie_ts))
        TCP_SKB_CB(skb)->when = cookie_init_timestamp(req);
    else
#endif
    TCP_SKB_CB(skb)->when = tcp_time_stamp;
    tcp_syn_build_options((__be32 *)(th + 1), dst_metric(dst, RTAX_ADVMSS), ireq->tstamp_ok,
             ireq->sack_ok, ireq->wscale_ok, ireq->rcv_wscale,
             TCP_SKB_CB(skb)->when,
             req->ts_recent,
             (
#ifdef CONFIG_TCP_MD5SIG
             md5 ? &md5_hash_location :
#endif
             NULL)
             );

    th->doff = (tcp_header_size >> 2);
    TCP_INC_STATS(TCP_MIB_OUTSEGS);

#ifdef CONFIG_TCP_MD5SIG
    /* Okay, we have all we need - do the md5 hash if needed */
    if (md5) {
        tp->af_specific->calc_md5_hash(md5_hash_location,
                     md5,
                     NULL, dst, req,
                     tcp_hdr(skb), sk->sk_protocol,
                     skb->len);
    }
#endif

    return skb;
}

这个函数我们就不细加分析了,总体来说是为“应答”的第二次握手准备一个skb数据结构,然后设置其相应的tcp头信息,我们看到其tcp的头设置了

         th->syn = 1;

         th->ack = 1;

当然还有路由信息也进行了设置,我们不细看了,继续往下看__tcp_v4_send_synack()函数中的代码,通过tcp_v4_check()计算并设置检验和后进入了ip_build_and_send_pkt()来建立ip头并发送数据包

int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk,
             __be32 saddr, __be32 daddr, struct ip_options *opt)
{
    struct inet_sock *inet = inet_sk(sk);
    struct rtable *rt = skb->rtable;
    struct iphdr *iph;

    /* Build the IP header. */
    skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
    skb_reset_network_header(skb);
    iph = ip_hdr(skb);
    iph->version = 4;
    iph->ihl = 5;
    iph->tos = inet->tos;
    if (ip_dont_fragment(sk, &rt->u.dst))
        iph->frag_off = htons(IP_DF);
    else
        iph->frag_off = 0;
    iph->ttl = ip_select_ttl(inet, &rt->u.dst);
    iph->daddr = rt->rt_dst;
    iph->saddr = rt->rt_src;
    iph->protocol = sk->sk_protocol;
    ip_select_ident(iph, &rt->u.dst, sk);

    if (opt && opt->optlen) {
        iph->ihl += opt->optlen>>2;
        ip_options_build(skb, opt, daddr, rt, 0);
    }

    skb->priority = sk->sk_priority;
    skb->mark = sk->sk_mark;

    /* Send it out. */
    return ip_local_out(skb);
}

关于ip头的具体设置请朋友们自己阅读,函数最后ip_local_out()这个函数我们在第12http://blog.chinaunix.net/u2/64681/showart.php?id=1420186 ,具体过程就象客户端开始向服务器端发送的过程是一样的,一样会从硬件网卡发向客户端的网卡,客户端的过程类似于我们上面看到的服务器的sock过程一样,同样会在客户端的sock过程中进入tcp_v4_do_rcv()函数,具体的进入过程完全与我们的前面四节,从第14http://blog.chinaunix.net/u2/64681/showart.php?id=1432417 开始分析的客户端到服务器端的过程相似,只不过这次是从服务器端的应答ack数据包到客户端的过程,我们不再具体描述如何进入tcp_v4_do_rcv()的函数过程,所以请朋友们参考前面四节的过程,只不过这次的运行过程在客户端的机器上,而不是服务器本身了。所以我们下边的分析仅限于客户端的linux内核的过程,请朋友们注意,我们假设客户端和服务器端都是一样的内核版本,即2.6.26版本。

tcp_v4_do_rcv()函数中会调用tcp_rcv_state_process()来处理接收到的数据包,我们假设客户端已经接收到了服务器的ack数据包,我们只关心与我们的过程相关部分代码

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
             struct tcphdr *th, unsigned len)
{
。。。。。。
case TCP_SYN_SENT:
        queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
        if (queued >= 0)
        if (queued >= 0)
            return queued;

        /* Do step6 onward by hand. */
        tcp_urg(sk, skb, th);
        __kfree_skb(skb);
        tcp_data_snd_check(sk);
        return 0;
。。。。。。
}

在第9http://blog.chinaunix.net/u2/64681/showart.php?id=1411408 tcp_v4_connect()函数的代码中我们曾经看到

tcp_set_state(sk, TCP_SYN_SENT);

将客户端的sock设置为了TCP_SYN_SENT的发送状态,所以客户端在接收到服务器端的ack包时会进入tcp_rcv_synsent_state_process()函数,函数的代码非常的长,我们不列出了,在这个函数中客户端会重新准备一个第三次握手的数据包然后调用tcp_send_ack()函数向服务器端发出“第三次握手”, tcp_send_ack()函数最终会调用tcp_transmit_skb()函数发送出去,而tcp_transmit_skb()函数我们在第11http://blog.chinaunix.net/u2/64681/showart.php?id=1415963 那篇中分析过了。这里我们就不详细探讨过程,先请朋友们对其有一个总体的了解,我们在将来的还会再详细的探讨这段过程。

阅读(5107) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~