(1) linux 3.x


http://blog.csdn.net/zhangskd/article/details/45951029


主要内容:客户端调用connect()时的TCP层实现。

内核版本:3.15.2 

我的博客:http://blog.csdn.net/zhangskd

connect的TCP层实现

SOCK_STREAM类socket的TCP层操作函数集实例为tcp_prot,其中客户端使用tcp_v4_connect()来发送SYN段。

  1. struct proto tcp_prot = {  
  2.     .name = "TCP",  
  3.     ...  
  4.     .connect = tcp_v4_connect,  
  5.     ...  
  6.     .h.hashinfo = &tcp_hashinfo,  
  7.     ...  
  8. };  

tcp_v4_connect()主要做了以下事情:

1. 检查socket的地址长度和使用的协议族。

2. 查找路由缓存。

3. 设置本端的IP。

4. 如果传输控制块已经被使用过了,则重新初始化相关变量。

5. 记录服务器端的IP和端口。

6. 把连接的状态更新为TCP_SYN_SENT。

7. 选取本地端口,可以是未被使用过的端口,也可以是允许重用的端口。

    这部有点复杂,下一篇会详细介绍。

8. 把sock链入本地端口的使用者哈希队列,把sock链入ehash哈希表。

9. 如果源端口或者目的端口发生改变,则需要重新查找路由。

10. 根据四元组,设置本端的初始序列号。

11. 根据初始序号和当前时间,设置IP首部ID字段值。

12. 构造一个SYN段,并发送出去。

  1. /* This will initiate an outgoing connection. */  
  2.   
  3. int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)  
  4. {  
  5.     struct sockaddr_in *usin = (struct sockaddr_in *) uaddr;  
  6.     struct inet_sock *inet = inet_sk(sk);  
  7.     struct tcp_sock *tp = tcp_sk(sk);  
  8.     __be16 orig_sport, orig_dport;  
  9.     __be32 daddr, nexthop;  
  10.     struct flowi4 *fl4;  
  11.     struc rtable *rt;  
  12.     int err;  
  13.     struct ip_options_rcu *inet_opt;  
  14.    
  15.     /* Socket地址的长度不正确 */  
  16.     if (addr_len < sizeof(struct sockaddr_in))  
  17.         return -EINVAL;  
  18.    
  19.     /* Socket地址的协议族不正确 */  
  20.     if (usin->sin_family != AF_INET)  
  21.         return -EAFNOSUPPORT;  
  22.    
  23.     nexthop = daddr = usin->sin_addr.s_addr; /* 服务器IP */  
  24.   
  25.     inet_opt = rcu_dereference_protected(inet->inet_opt, sock_owned_by_user(sk));  
  26.     if (inet_opt && inet_opt->opt.srr) { /* 如果使用源地址路由 */  
  27.         if (! daddr)  
  28.             return -EINVAL;  
  29.         nexthop = inet_opt->opt.faddr; /* 设置下一跳地址 */  
  30.     }  
  31.   
  32.     orig_sport = inet->inet_sport; /* 本端端口,目前可能已绑定,也可能没分配 */  
  33.     orig_dport = usin->sin_port; /* 服务器端口 */  
  34.   
  35.     fl4 = &inet->cork.fl.u.ip4;  
  36.   
  37.     /* 查找路由缓存项 */  
  38.     rt = ip_route_connect(fl4, nexthop, inet->inet_saddr, RT_CONN_FLAGS(sk),   
  39.                sk->sk_bound_dev_if, IPPROTO_TCP, orig_sport, orig_dport, sk);  
  40.     if (IS_ERR(rt)) {  
  41.         err = PTR_ERR(rt);  
  42.   
  43.         if (err == -ENETUREACH)  
  44.             IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);  
  45.         return err;  
  46.     }  
  47.    
  48.     /* 不能使用类型为多播或广播的路由缓存 */  
  49.     if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {  
  50.         ip_rt_put(rt);  
  51.         return -ENETUNREACH;  
  52.     }  
  53.    
  54.     /* 如果没有使用源路由选项 */  
  55.     if (! inet_opt || ! inet_opt->opt.srr)  
  56.         daddr = fl4->daddr;  
  57.   
  58.     /* 如果本端的源IP还没有设置 */  
  59.     if (! inet->inet_saddr)  
  60.         inet->inet_saddr = fl4->saddr;  
  61.     inet->inet_rcv_saddr = inet->inet_saddr;  
  62.       
  63.     /* 如果传输控制块已经被使用过了,则重新初始化相关变量 */  
  64.     if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {  
  65.         /* Reset inherited state */  
  66.         tp->rx_opt.ts_recent = 0;  
  67.         tp->rx_opt.ts_recent_stamp = 0;  
  68.   
  69.         /* 使用TCP_REPAIR选项时,不会重置当前发送缓存的最后一个字节的序号 */          
  70.         if (likely(! tp->repair))  
  71.             tp->write_seq = 0/* Tail +1 of data held in tcp send buffer */  
  72.     }  
  73.    
  74.     /* 如果启用了tcp_tw_recycle,那么从路由缓存中获取时间戳来初始化相关变量 */  
  75.     if (tcp_death_row.sysctl_tw_recycle && ! tp->rx_opt.ts_recent_stamp &&  
  76.         fl4->daddr == daddr)  
  77.         tcp_fetch_timewait_stamp(sk, &rt->dst);  
  78.   
  79.     inet->inet_dport = usin->sin_port; /* 记录服务器端口 */  
  80.     inet->inet_daddr = daddr; /* 记录服务器IP */  
  81.     inet_csk(sk)->icsk_ext_hdr_len = 0/* IP选项长度 */  
  82.   
  83.     if (inet_opt)  
  84.         inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; /* IP选项长度 */  
  85.     tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT; /* 默认对端的MSS,之后会更新 */   
  86.   
  87.     /* Socket identity is still unknown (sport may be zero). 
  88.      * However we set state to SYN-SENT and not releasing socket lock 
  89.      * select source port, enter ourselves into the hash tables complete 
  90.      * initialization after this. 
  91.      */   
  92.     tcp_set_state(sk, TCP_SYN_SENT); /* 把连接状态更新为TCP_SYN_SENT */  
  93.   
  94.     /* 选取本地端口,把sk链入端口的使用者队列,把sk链入ehash哈希表 */  
  95.     err = inet_hash_connect(&tcp_death_row, sk);  
  96.     if (err)  
  97.         goto failure;  
  98.   
  99.     /* 如果源端口或者目的端口发生改变,需要重新查找路由 */  
  100.     rt = ip_route_newports(fl4, rt, orig_sport, orig_dport, inet->inet_sport,   
  101.              inet->inet_dport, sk);  
  102.     if (IS_ERR(rt)) {  
  103.        err = PTR_ERR(rt);  
  104.         rt = NULL;  
  105.   
  106.         goto failure;  
  107.     }  
  108.   
  109.     /* OK, now commit destination to socket. */  
  110.     sk->sk_gso_type = SKB_GSO_TCPV4;  
  111.     sk_setup_caps(sk, &rt->dst); /* 根据路由缓存,设置网卡的特性 */  
  112.   
  113.     /* 根据四元组,设置本端的初始序列号,计算方式和服务器端的一样。 
  114.      * 如果使用了TCP_REPAIR选项,就不用重新计算。 
  115.      */  
  116.     if (! tp->write_seq && likely(! tp->repair))  
  117.         tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr, inet->inet_daddr,  
  118.                             inet->inet_sport, usin->sin_port);  
  119.   
  120.     inet->inet_id = tp->write_seq ^ jiffies; /* 根据初始序号和当前时间,设置IP首部ID字段值 */  
  121.   
  122.     err = tcp_connect(sk); /* 构造一个SYN段,并发送出去 */  
  123.   
  124.     rt = NULL;  
  125.     if (err)  
  126.         goto failure;  
  127.   
  128.     return 0;  
  129.   
  130. failure:  
  131.     /* This unhashes the socket and releases the local port, if necessary. */  
  132.     tcp_set_state(sk, TCP_CLOSE);  
  133.     ip_rt_put(rt);  
  134.     sk->sk_route_caps = 0;  
  135.     inet->inet_dport = 0;  
  136.     return err;  
  137. }  

SYN段的构造和发送

tcp_connect()用于构造和发送SYN段,主要做了以下事情:

1. 初始化传输控制块中和TCP层或连接相关的变量。

2. 申请一个skb,进行控制字段的初始化。

3. 把skb插入到发送队列的尾部,并更新相关的变量。

4. 调用发送函数,把skb传递到IP层。

5. 开启超时重传定时器,SYN段的初始超时时间为1s。

  1. /* Build a SYN and send it off. */  
  2.   
  3. int tcp_connect(struct sock *sk)  
  4. {  
  5.     struct tcp_sock *tp = tcp_sk(sk);  
  6.     struct sk_buff *buff;  
  7.     int err;  
  8.   
  9.     /* 初始化TCP控制块中的相关变量 */  
  10.     tcp_connect_init(sk);  
  11.   
  12.     /* 如果使用TCP_REPAIR选项,那么不发送SYN和等待SYNACK,而是直接进入成功状态 */  
  13.     if (unlikely(tp->repair)) {  
  14.         tcp_finsih_connect(sk, NULL);  
  15.     }  
  16.   
  17.     buff = alloc_skb_fclone(MAX_TCP_HEADER + 15, sk->sk_allocation);  
  18.     if (unlikely(buff == NULL))  
  19.         return -ENOBUFS; /* No buffer space available */  
  20.   
  21.     /* Reserve space for headers. 预留报文头部的空间*/  
  22.     skb_reserve(buff, MAX_TCP_HEADER);  
  23.   
  24.     /* 初始化不携带数据的skb的一些控制字段 */  
  25.     tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);  
  26.     tp->retrans_stamp = TCP_SKB_CB(buff)->when = tcp_time_stamp; /* 记下SYN的发送时间 */  
  27.     tcp_connect_queue_skb(sk, buff); /* 把skb加入到发送队列的尾部,并更新相关变量 */  
  28.     TCP_ECN_send_syn(sk, buff); /* 设置ECN相关变量 */  
  29.      
  30.     /* 根据是否使用TCP Fast Open选项,来选择SYN段的发送函数 */  
  31.     err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :  
  32.          tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);  
  33.     if (err = -ECONNREFUSED) /* Connection refused */  
  34.         return err;  
  35.   
  36.     /* We change tp->snd_nxt after the tcp_transmit_skb() call in order to 
  37.      * make this packet get counted in tcpOutSegs. 
  38.      */  
  39.     tp->snd_nxt = tp->write_seq;  
  40.     tp->pushed_seq = tp->write_seq; /* Last pushed seq, required to talk to windows */  
  41.     TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS);  
  42.    
  43.     /* Timer for repeating the SYN until an answer. 
  44.      * 开启超时重传定时器,SYN段的初始超时时间为1s。 
  45.      */  
  46.     inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, inet_csk(sk)->icsk_rto, TCP_RTO_MAX);  
  47. }  

对tcp_sock和inet_connection_sock中的部分变量进行初始化。

  1. /* Do all connect socket setups that can be done AF independent. */  
  2.   
  3. static void tcp_connect_init(struct sock *sk)  
  4. {  
  5.     const struct dst_entry *dst = __sk_dst_get(sk); /* 路由缓存 */  
  6.     struct tcp_sock *tp = tcp_sk(sk);  
  7.     __u8 rcv_wscale;  
  8.   
  9.     /* We'll fix this up when we get a response from the other end. 
  10.      * See tcp_input.c: tcp_rcv_state_process case TCP_SYN_SENT. 
  11.      */  
  12.     tp->tcp_header_len = sizeof(struct tcphdr) +   
  13.             (sysctl_tcp_timestamp ? TCPOLEN_TSTAMP_ALIGNED : 0); /* TCP报头长度,包含时间戳选项 */  
  14.   
  15. #ifdef CONFIG_TCP_MD5SIG  
  16.     if (tp->af_specific->md5_lookup(sk, sk) != NULL)  
  17.         tp->tcp_header_len += TCPOLEN_MD5SIG_ALIGNED; /* MD5SIG选项 */  
  18. #endif  
  19.   
  20.     /* If user gave his TCP_MAXSEG, record it to clamp */  
  21.     /* 如果用户通过TCP_MAXSEG选项设置了MSS上限 */  
  22.     if (tp->rx_opt.user_mss)  
  23.         tp->rx_opt.mss_clamp = tp->rx_opt.user_mss; /* 设置对端的接收MSS上限 */  
  24.   
  25.     tp->max_window = 0/* 见过的对端最大接收窗口 */  
  26.     tcp_mtup_init(sk); /* TCP的PMTU初始化 */  
  27.     tcp_sync_mss(sk, dst_mtu(dst)); /* 更新MSS */  
  28.   
  29.     if (! tp->window_clamp) /* 本端接收窗口的上限 */  
  30.         tp->window_clamp = dst_metric(dst, RTAX_WINDOW);  
  31.     tp->advmss = dst_metric_advmss(dst); /* 本端在建立连接时通告的MSS */  
  32.   
  33.     if (tp->rx_opt.user_mss && tp->rx_opt.user_mss < tp->advmss)  
  34.         tp->advmss = tp->rx_opt.user_mss; /* 不能超过用户设置的MSS */  
  35.   
  36.     tcp_initialize_rcv_mss(sk); /* 对端有效的发送MSS估算值做初始化 */  
  37.   
  38.     /* 如果用户使用SO_RCVBUF选项限制接收缓存。 
  39.      * limit the window selection if the user enforce a smaller rx buffer. 
  40.      */  
  41.     if (sk->sk_userlocks & SOCK_RCVBUF_LOCK &&  
  42.         (tp->window_clamp > tcp_full_space(sk) || tp->window_clamp == 0))  
  43.         tp->window_clamp = tcp_full_space(sk); /* 3/4 sk->sk_rcvbuf */  
  44.   
  45.     /* 获取接收窗口的初始值、窗口扩大因子和接收窗口的上限 */  
  46.     tcp_select_initial_window(tcp_full_space(sk),   
  47.         tp->advmss - (tp->rx_opt.ts_recent_stamp? tp->tcp_header_len - sizeof(struct tcphdr) : 0),  
  48.         &tp->rcv_wnd,  
  49.         &tp->window_clamp,  
  50.         sysctl_tcp_window_scaling,  
  51.         &rcv_wscale,  
  52.         dst_metric(dst, RTAX_INITRWND));  
  53.    
  54.     tp->rx_opt.rcv_wscale = rcv_wscale; /* 设置接收窗口的扩大因子 */  
  55.     tp->rcv_ssthresh = tp->rcv_wnd;  
  56.   
  57.     sk->sk_err = 0;  
  58.     sock_reset_flag(sk, SOCK_DONE);  
  59.     tp->snd_wnd = 0;  
  60.     tcp_init_wl(tp, 0); /* tp->snd_wl1为最近更新发送窗口的ACK段序号 */  
  61.     tp->snd_una = tp->write_seq;  
  62.     tp->snd_sml = tp->write_seq;  
  63.     tp->snd_up = tp->write_seq;  
  64.     tp->snd_nxt = tp->write_seq;  
  65.   
  66.     if (likely(! tp->repair)) /* TCP Repair选项相关 */      
  67.         tp->rcv_nxt = 0;  
  68.     else  
  69.         tp->rcv_tstamp = tcp_time_stamp;  
  70.   
  71.     tp->rcv_wup = tp->rcv_nxt;  
  72.     tp->copied_seq = tp->rcv_nxt;  
  73.   
  74.     inet_csk(sk)->icsk_rto = TCP_TIMEOUT_INIT; /* RTO的初始值为1s */  
  75.     inet_csk(sk)->icsk_retransmits = 0;  
  76.     tcp_clear_retrans(tp);  
  77. }  

把skb加入到发送队列的尾部,并更新相关变量。

  1. static void tcp_connect_queue_skb(struct sock *sk, struct sk_buff *skb)  
  2. {  
  3.     struct tcp_sock *tp = tcp_sk(sk);  
  4.     struct tcp_skb_cb *tcb = TCP_SKB_CB(skb);  
  5.   
  6.     tcb->end_seq += skb->len;  
  7.     skb_header_release(skb); /* 增加skb负荷部分的引用计数 */  
  8.   
  9.     __tcp_add_write_queue_tail(sk, skb); /* 把skb放入发送队列的尾部 */  
  10.   
  11.     /* sk_wmem_queued为发送队列的总大小,包含发送队列中skb数据区、 
  12.      * sk_buff、sk_shared_info结构体,以及协议头等额外开销。 
  13.      */  
  14.     sk->sk_wmem_queued += skb->truesize; /* 更新发送队列的总大小 */  
  15.     sk_mem_charge(sk, skb->truesize); /* 减少预分配但未使用的内存 */  
  16.   
  17.     tp->write_seq += tcb->end_seq; /* 更新发送缓存中的最后一个字节序号(+1) */  
  18.     tp->packets_out += tcp_skb_pcount(skb); /* 更新发送且未确认的数据段个数 */  
  19. }  
  1. /* skb_header_release - release reference to header 
  2.  * @skb: buffer to operate on 
  3.  * 
  4.  * Drop a reference to the header part of the buffer. 
  5.  * This is done by acquiring a payload reference. 
  6.  * You must not read from the header part of skb->data after this. 
  7.  */  
  8. static inline void skb_header_release(struct sk_buff *skb)  
  9. {  
  10.     BUG_ON(skb->nohdr);  
  11.     skb->nohdr = 1/* skb没有协议头 */  
  12.   
  13.     /* 增加skb负荷部分的引用计数 */  
  14.     atomic_add(1 << SKB_DATAREF_SHIFT, &skb_shinfo(skb)->dataref);  
  15. }  
  16.    
  17. /* We divide dataref into two halves. The higher 16 bits hold references to 
  18.  * the payload part of skb->data. The lower 16 bits hold references to the  
  19.  * entire skb->data. A clone of a headerless skb holds the length of the header 
  20.  * in skb->hdr_len. 
  21.  *  
  22.  * All users must obey the rule that the skb->data reference count must be greater 
  23.  * than or equal to the payload reference count. 
  24.  *  
  25.  * Holding a reference to the payload part means that the user does not care about 
  26.  * modifications to the header part of skb->data. 
  27.  */  
  28. #define SKB_DATAREF_SHIFT 16  
  29. #define SKB_DATAREF_MASK ((1 << SKB_DATAREF_SHIFT - 1)  

 SYN段的ECN标志设置。

  1. /* Packet ECN state for a SYN. */  
  2.   
  3. static inline void TCP_ECN_send_syn(struct sock *sk, struct sk_buff *skb)  
  4. {  
  5.     struct tcp_sock *tp = tcp_sk(sk);  
  6.     tp->ecn_flags = 0;  
  7.   
  8.     /* 如果支持ECN */  
  9.     if (sock_net(sk)->ipv4.sysctl_tcp_ecn == 1) {  
  10.         TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_ECE | TCPHDR_CWR;  
  11.         tp->ecn_flags = TCP_ECN_OK;  
  12.     }  
  13. }  


原文地址:https://www.cnblogs.com/ztguang/p/12644870.html