深入理解TCP协议及其源代码

三次握手如图所示:

 

 

 

TCP协议通过三次握手来建立一个可靠地连接。

第一次握手:客户端尝试连接服务器,向服务器发送连接请求报文(SYN包),其首部中的同步比特SYN置为1,seq=x,客户端进入SYN-SEND状态,等待服务器确认。

第二次握手:服务器接收到连接请求报文后,若同意,则发送确认报文。确认报文中,将SYN置为1,ack=x+1,同时也为自己随机选择一个序号y,seq=y,即SYN+ACK包,此时服务器进入SYN-RECV状态。

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包,其确认序号ack=y+1。此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

 

从C语言代码考虑,客户端的connect和服务端的accept分别对应着sys_connect 和 sys_accept4函数。结构体变量struct proto tcp_prot指定了TCP协议栈的访问接口函数。通过查看该结构体可知,sys_connect和sys_accept4实际上调用的是tcp_v4_connect和inet_csk_accept。该结构体位于linux-5.0.1/net/ipv4/tcp_ipv4.c中。

tcp_v4_connect代码如下,在tcp_v4_connect中,调用了ip_route_connect和ip_route_newports,分析代码,可以看出tcp_v4_connect所做的工作如下:

1.检查目的ip长度、协议,如果设置了源路由选项而且数据包目的地址不空,则从用户给定的源路由列表中取一个ip地址赋给网关地址。

2.调用ip_route_connect选路由,路由结构保存到rt->rt_dst中,实际调用的函数是ip_route_output_flow,如果是广播地址、组地址就返回。

3.调用tcp_set_state设置套接字状态为TCP_SYN_SENT。并将套接字加入到连接管理哈希链表中,为连接分配一个临时端口。

4.初始化序列号,调用tcp_connect函数完成建立连接,其中调用了tcp_transmit_skb将数据包发送到IP层。

5.若连接建立失败,就将TCP状态设为CLOSE,将套接字删除。

代码注释如下:

 

/* This will initiate an outgoing connection. */tcp
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
     //将通用地质结构转换为IPv4的地址结构
struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
     //变量inet指向套接字struct sock中的inet选项
struct inet_sock *inet = inet_sk(sk);
     //变量tp指向套接字struct sock中的TCP选项
struct tcp_sock *tp = tcp_sk(sk);
   __be16 orig_sport, orig_dport; __be32 daddr, nexthop;
struct flowi4 *fl4; struct rtable *rt; int err; struct ip_options_rcu *inet_opt; struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row;
     //地址长度检查
if (addr_len < sizeof(struct sockaddr_in)) return -EINVAL;      //协议组检查 if (usin->sin_family != AF_INET) return -EAFNOSUPPORT; //初始化目的地址和下一条地址 nexthop = daddr = usin->sin_addr.s_addr; inet_opt = rcu_dereference_protected(inet->inet_opt, lockdep_sock_is_held(sk)); if (inet_opt && inet_opt->opt.srr) { if (!daddr) return -EINVAL; nexthop = inet_opt->opt.faddr; } orig_sport = inet->inet_sport; orig_dport = usin->sin_port; fl4 = &inet->cork.fl.u.ip4;
     //查找路由表项,并通过变量rt记录下来 rt
= ip_route_connect(fl4, nexthop, inet->inet_saddr, RT_CONN_FLAGS(sk), sk->sk_bound_dev_if, IPPROTO_TCP, orig_sport, orig_dport, sk); if (IS_ERR(rt)) { err = PTR_ERR(rt); if (err == -ENETUNREACH) IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES); return err; }      //组传送地址、广播地址则返回错误 if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) { ip_rt_put(rt); return -ENETUNREACH; }      //如果没有设置源路由ip选项,就使用路由表寻址的路由 if (!inet_opt || !inet_opt->opt.srr) daddr = fl4->daddr; if (!inet->inet_saddr) inet->inet_saddr = fl4->saddr; sk_rcv_saddr_set(sk, inet->inet_saddr);      //初始化tcp选项 if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) { /* Reset inherited state */ tp->rx_opt.ts_recent = 0; tp->rx_opt.ts_recent_stamp = 0; if (likely(!tp->repair)) tp->write_seq = 0;  //序号初始化为0 } inet->inet_dport = usin->sin_port;  //目的端口地址 sk_daddr_set(sk, daddr);  //目的ip地址 inet_csk(sk)->icsk_ext_hdr_len = 0; if (inet_opt) inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT; /* Socket identity is still unknown (sport may be zero). * However we set state to SYN-SENT and not releasing socket * lock select source port, enter ourselves into the hash tables and * complete initialization after this. */
     //设置套接字状态为TCP_SYN_SENT
tcp_set_state(sk, TCP_SYN_SENT);
     //将套接字sk放入tcp连接管理哈希链表中 err
= inet_hash_connect(tcp_death_row, sk); if (err) goto failure; sk_set_txhash(sk);      //为连接分配一个临时端口 rt = ip_route_newports(fl4, rt, orig_sport, orig_dport, inet->inet_sport, inet->inet_dport, sk); if (IS_ERR(rt)) { err = PTR_ERR(rt); rt = NULL; goto failure; } /* OK, now commit destination to socket. */ sk->sk_gso_type = SKB_GSO_TCPV4; sk_setup_caps(sk, &rt->dst); rt = NULL; if (likely(!tp->repair)) { if (!tp->write_seq)//初始化tcp数据段序列号 tp->write_seq = secure_tcp_seq(inet->inet_saddr, inet->inet_daddr, inet->inet_sport, usin->sin_port); tp->tsoffset = secure_tcp_ts_off(sock_net(sk), inet->inet_saddr, inet->inet_daddr); } inet->inet_id = tp->write_seq ^ jiffies; if (tcp_fastopen_defer_connect(sk, &err)) return err; if (err) goto failure;      //构建SYN包调用tcp_transmit_skb发送到IP层 err = tcp_connect(sk); if (err) goto failure; return 0; failure: /* * This unhashes the socket and releases the local port, * if necessary. */
     //失败设置套接字状态为CLOSED tcp_set_state(sk, TCP_CLOSE); ip_rt_put(rt); sk->sk_route_caps = 0; inet->inet_dport = 0; return err; } EXPORT_SYMBOL(tcp_v4_connect);

 

 

 

 

tcp_connect代码如下,在tcp_connect中,主要完成的操作位构造SYN报文并将其发送出去,但是由于TCP要提供可靠性的服务,故发送完成后,还需要将该SYN报文加入到发送队列,并启动重传定时器,只有等对方收到SYN报文并回发ACK报文,待我方接收到才将该报文从发送队列中删除并停止重传定时器。

故三次握手的第一次握手完成。

/* Build a SYN and send it off. */
int tcp_connect(struct sock *sk)
{
        struct tcp_sock *tp = tcp_sk(sk);
        struct sk_buff *buff;
        int err;
  
        tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_CONNECT_CB, 0, NULL);

        if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
                return -EHOSTUNREACH; /* Routing failure or similar. */
     //对tcp进行一定的初始化
        tcp_connect_init(sk);

        if (unlikely(tp->repair)) {
                tcp_finish_connect(sk, NULL);
                return 0;
        }
     //分配一个SKB用于构造SYN段
        buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
        if (unlikely(!buff))
                return -ENOBUFS;

        tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
        tcp_mstamp_refresh(tp);
        tp->retrans_stamp = tcp_time_stamp(tp);
        tcp_connect_queue_skb(sk, buff);
        tcp_ecn_send_syn(sk, buff);
        tcp_rbtree_insert(&sk->tcp_rtx_queue, buff);

        /* Send off SYN; include data in Fast Open. */
     //发送SYN请求报文
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) : tcp_transmit_skb(sk, buff, 1, sk->sk_allocation); if (err == -ECONNREFUSED) return err; /* We change tp->snd_nxt after the tcp_transmit_skb() call * in order to make this packet get counted in tcpOutSegs. */ tp->snd_nxt = tp->write_seq; tp->pushed_seq = tp->write_seq; buff = tcp_send_head(sk); if (unlikely(buff)) { tp->snd_nxt = TCP_SKB_CB(buff)->seq; tp->pushed_seq = TCP_SKB_CB(buff)->seq; } TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS);
     //启动SYN重传定时器,初始超时时间为1s
/* Timer for repeating the SYN until an answer. */ inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, inet_csk(sk)->icsk_rto, TCP_RTO_MAX); return 0; } EXPORT_SYMBOL(tcp_connect);

 

当服务端收到第一次握手的请求后,服务端会调用tcp_v4_recv进行第二次握手的处理。

在tcp_v4_recv中,调用函数tcp_v4_do_rcv,该函数主要作用是防止洪泛和拥塞控制,接着是调用tcp_rcv_state_process,所有的收到的报文都会在该函数中处理。然后调用tcp_v4_conn_request,tcp_v4_conn_request又调用tcp_conn_request,之后调用tcp_v4_send_synack对客户端的请求报文进行确认。此时完成第二次握手。

static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst,
                  struct flowi *fl,
                  struct request_sock *req,
                  struct tcp_fastopen_cookie *foc,
                  enum tcp_synack_type synack_type)
{
    const struct inet_request_sock *ireq = inet_rsk(req);
    struct flowi4 fl4;
    int err = -1;
    struct sk_buff *skb;
 
    if (!dst && (dst = inet_csk_route_req(sk, &fl4, req)) == NULL)
        return -1;
    //构建SYN+ACK段
    skb = tcp_make_synack(sk, dst, req, foc, synack_type);
 
    //生成SYN+ACK段
    if (skb) {
        __tcp_v4_send_check(skb, ireq->ir_loc_addr, ireq->ir_rmt_addr);
        err = ip_build_and_send_pkt(skb, sk, ireq->ir_loc_addr,
                        ireq->ir_rmt_addr,
                        ireq->opt);
        err = net_xmit_eval(err);
    }
    return err;
}

 

客户端收到服务端的SYN+ACK报文后,会调用tcp_v4_rcv来进行处理,在该函数中调用tcp_child_process,在tcp_child_process中又调用tcp_rcv_state_process来完成第三次握手。

 

 

posted @ 2019-12-26 22:08  mingjian6666  阅读(325)  评论(0编辑  收藏  举报