TCP主动打开 之 第一次握手-发送SYN
Posted wanpengcoder
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP主动打开 之 第一次握手-发送SYN相关的知识,希望对你有一定的参考价值。
tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端;
tcp_v4_connect为发起连接主流程,首先对必要参数进行检查,获取路由信息,改变连接状态成SYN_SENT,再调用inet_hash_connect将控制块加入到ehash,最后调用tcp_connect发送syn;
1 /* This will initiate an outgoing connection. */ 2 int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) 3 { 4 struct sockaddr_in *usin = (struct sockaddr_in *)uaddr; 5 struct inet_sock *inet = inet_sk(sk); 6 struct tcp_sock *tp = tcp_sk(sk); 7 __be16 orig_sport, orig_dport; 8 __be32 daddr, nexthop; 9 struct flowi4 *fl4; 10 struct rtable *rt; 11 int err; 12 struct ip_options_rcu *inet_opt; 13 14 /* timewait控制块结构 */ 15 struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row; 16 17 /* 地址长度不合法 */ 18 if (addr_len < sizeof(struct sockaddr_in)) 19 return -EINVAL; 20 21 /* 地址族不合法 */ 22 if (usin->sin_family != AF_INET) 23 return -EAFNOSUPPORT; 24 25 /* 设置下一跳和目的地址 */ 26 nexthop = daddr = usin->sin_addr.s_addr; 27 28 /* 获取ip选项 */ 29 inet_opt = rcu_dereference_protected(inet->inet_opt, 30 lockdep_sock_is_held(sk)); 31 32 /* 使用了源路由选项 */ 33 if (inet_opt && inet_opt->opt.srr) { 34 if (!daddr) 35 return -EINVAL; 36 /* 下一跳地址设置为选项中的地址 */ 37 nexthop = inet_opt->opt.faddr; 38 } 39 40 /* 获取源端口目的端口 */ 41 orig_sport = inet->inet_sport; 42 orig_dport = usin->sin_port; 43 44 /* 查找路由 */ 45 fl4 = &inet->cork.fl.u.ip4; 46 rt = ip_route_connect(fl4, nexthop, inet->inet_saddr, 47 RT_CONN_FLAGS(sk), sk->sk_bound_dev_if, 48 IPPROTO_TCP, 49 orig_sport, orig_dport, sk); 50 /* 查找失败 */ 51 if (IS_ERR(rt)) { 52 err = PTR_ERR(rt); 53 if (err == -ENETUNREACH) 54 IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES); 55 return err; 56 } 57 58 59 /* 查找成功 */ 60 61 /* 路由是组播或者广播 */ 62 if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) { 63 ip_rt_put(rt); 64 return -ENETUNREACH; 65 } 66 67 /* 选项为空或者未启用源路由选项 */ 68 /* 设置目的地址为路由缓存中地址 */ 69 if (!inet_opt || !inet_opt->opt.srr) 70 daddr = fl4->daddr; 71 72 /* 源地址为空 */ 73 /* 使用路由缓存中的源地址 */ 74 if (!inet->inet_saddr) 75 inet->inet_saddr = fl4->saddr; 76 /* 设置接收地址为源地址 */ 77 sk_rcv_saddr_set(sk, inet->inet_saddr); 78 79 /* 控制块中的时间戳存在&& 目的地址不是当前地址 */ 80 /* 控制块被使用过,重新初始化 */ 81 if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) { 82 /* Reset inherited state */ 83 tp->rx_opt.ts_recent = 0; 84 tp->rx_opt.ts_recent_stamp = 0; 85 if (likely(!tp->repair)) 86 tp->write_seq = 0; 87 } 88 89 /* 设置目的端口 */ 90 inet->inet_dport = usin->sin_port; 91 /* 设置目的地址 */ 92 sk_daddr_set(sk, daddr); 93 94 /* 获取ip选项长度 */ 95 inet_csk(sk)->icsk_ext_hdr_len = 0; 96 if (inet_opt) 97 inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; 98 99 /* 设置mss */ 100 tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT; 101 102 /* Socket identity is still unknown (sport may be zero). 103 * However we set state to SYN-SENT and not releasing socket 104 * lock select source port, enter ourselves into the hash tables and 105 * complete initialization after this. 106 */ 107 /* 设置连接状态为TCP_SYN_SENT */ 108 tcp_set_state(sk, TCP_SYN_SENT); 109 110 /* 端口绑定,加入ehash */ 111 err = inet_hash_connect(tcp_death_row, sk); 112 if (err) 113 goto failure; 114 115 /* 设置hash值 */ 116 sk_set_txhash(sk); 117 118 /* 119 如果源端口或者目的端口发生变化, 120 重新获取路由,并更新sk的路由缓存 121 */ 122 rt = ip_route_newports(fl4, rt, orig_sport, orig_dport, 123 inet->inet_sport, inet->inet_dport, sk); 124 if (IS_ERR(rt)) { 125 err = PTR_ERR(rt); 126 rt = NULL; 127 goto failure; 128 } 129 /* OK, now commit destination to socket. */ 130 sk->sk_gso_type = SKB_GSO_TCPV4; 131 132 /* 存储目的路由缓存和网络设备特性到控制块 */ 133 sk_setup_caps(sk, &rt->dst); 134 rt = NULL; 135 136 if (likely(!tp->repair)) { 137 /* 获取发送序号 */ 138 if (!tp->write_seq) 139 tp->write_seq = secure_tcp_seq(inet->inet_saddr, 140 inet->inet_daddr, 141 inet->inet_sport, 142 usin->sin_port); 143 /* 时间戳偏移 */ 144 tp->tsoffset = secure_tcp_ts_off(inet->inet_saddr, 145 inet->inet_daddr); 146 } 147 148 /* 设置ip首部的id */ 149 inet->inet_id = tp->write_seq ^ jiffies; 150 151 /* fastopen */ 152 if (tcp_fastopen_defer_connect(sk, &err)) 153 return err; 154 if (err) 155 goto failure; 156 157 /* 发送syn */ 158 err = tcp_connect(sk); 159 160 if (err) 161 goto failure; 162 163 return 0; 164 165 failure: 166 /* 167 * This unhashes the socket and releases the local port, 168 * if necessary. 169 */ 170 tcp_set_state(sk, TCP_CLOSE); 171 ip_rt_put(rt); 172 sk->sk_route_caps = 0; 173 inet->inet_dport = 0; 174 return err; 175 }
__inet_hash_connect将端口检查通过的控制块加入到ehash;函数对是否设置端口进行了不同处理,若未设置端口,则需要查找一个端口;函数还调用check_established检查是否可以复用处在TIME_WAIT的控制块,以及调用inet_ehash_nolisten将端口对应的控制块加入的ehash;
1 int __inet_hash_connect(struct inet_timewait_death_row *death_row, 2 struct sock *sk, u32 port_offset, 3 int (*check_established)(struct inet_timewait_death_row *, 4 struct sock *, __u16, struct inet_timewait_sock **)) 5 { 6 struct inet_hashinfo *hinfo = death_row->hashinfo; 7 struct inet_timewait_sock *tw = NULL; 8 struct inet_bind_hashbucket *head; 9 int port = inet_sk(sk)->inet_num; 10 struct net *net = sock_net(sk); 11 struct inet_bind_bucket *tb; 12 u32 remaining, offset; 13 int ret, i, low, high; 14 static u32 hint; 15 16 /* 存在端口 */ 17 if (port) { 18 head = &hinfo->bhash[inet_bhashfn(net, port, 19 hinfo->bhash_size)]; 20 21 /* 找到端口绑定信息 */ 22 tb = inet_csk(sk)->icsk_bind_hash; 23 spin_lock_bh(&head->lock); 24 25 /* 当前端口绑定的只有当前控制块 */ 26 if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) { 27 /* 将控制块加入只ehash */ 28 inet_ehash_nolisten(sk, NULL); 29 spin_unlock_bh(&head->lock); 30 return 0; 31 } 32 spin_unlock(&head->lock); 33 /* No definite answer... Walk to established hash table */ 34 /* 检查复用情况 */ 35 ret = check_established(death_row, sk, port, NULL); 36 local_bh_enable(); 37 return ret; 38 } 39 40 41 /* 没有确定端口,则随机端口 */ 42 43 inet_get_local_port_range(net, &low, &high); 44 high++; /* [32768, 60999] -> [32768, 61000[ */ 45 remaining = high - low; 46 if (likely(remaining > 1)) 47 remaining &= ~1U; 48 49 offset = (hint + port_offset) % remaining; 50 /* In first pass we try ports of @low parity. 51 * inet_csk_get_port() does the opposite choice. 52 */ 53 offset &= ~1U; 54 other_parity_scan: 55 port = low + offset; 56 57 /* 遍历端口 */ 58 for (i = 0; i < remaining; i += 2, port += 2) { 59 if (unlikely(port >= high)) 60 port -= remaining; 61 /* 保留端口 */ 62 if (inet_is_local_reserved_port(net, port)) 63 continue; 64 65 /* 找到端口对应的绑定hash桶 */ 66 head = &hinfo->bhash[inet_bhashfn(net, port, 67 hinfo->bhash_size)]; 68 spin_lock_bh(&head->lock); 69 70 /* Does not bother with rcv_saddr checks, because 71 * the established check is already unique enough. 72 */ 73 /* 遍历绑定的链表中的节点 */ 74 inet_bind_bucket_for_each(tb, &head->chain) { 75 76 /* 找到端口相同节点 */ 77 if (net_eq(ib_net(tb), net) && tb->port == port) { 78 79 /* 设置被重用了,继续找,随机端口不能重用 */ 80 if (tb->fastreuse >= 0 || 81 tb->fastreuseport >= 0) 82 goto next_port; 83 WARN_ON(hlist_empty(&tb->owners)); 84 85 /* 检查timewait复用情况 */ 86 if (!check_established(death_row, sk, 87 port, &tw)) 88 goto ok; 89 goto next_port; 90 } 91 } 92 93 /* 遍历没有重复 */ 94 95 /* 创建该端口的绑定信息节点,加入绑定hash */ 96 tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep, 97 net, head, port); 98 if (!tb) { 99 spin_unlock_bh(&head->lock); 100 return -ENOMEM; 101 } 102 103 /* 设置默认重用标记 */ 104 tb->fastreuse = -1; 105 tb->fastreuseport = -1; 106 goto ok; 107 next_port: 108 spin_unlock_bh(&head->lock); 109 cond_resched(); 110 } 111 112 /* 继续从下一半端口中找 */ 113 offset++; 114 if ((offset & 1) && remaining > 1) 115 goto other_parity_scan; 116 117 return -EADDRNOTAVAIL; 118 119 ok: 120 hint += i + 2; 121 122 /* Head lock still held and bh‘s disabled */ 123 124 /* 控制块加入该端口的使用者列表 */ 125 inet_bind_hash(sk, tb, port); 126 127 /* 初始化源端口,加入到ehash */ 128 if (sk_unhashed(sk)) { 129 inet_sk(sk)->inet_sport = htons(port); 130 inet_ehash_nolisten(sk, (struct sock *)tw); 131 } 132 /*有timewait控制块则从bind列表中移除 */ 133 if (tw) 134 inet_twsk_bind_unhash(tw, hinfo); 135 spin_unlock(&head->lock); 136 137 /* 调度销毁timewait控制块 */ 138 if (tw) 139 inet_twsk_deschedule_put(tw); 140 local_bh_enable(); 141 return 0; 142 }
__inet_check_established用于检查与相同端口中处于TIME_WAIT状态的控制块是否可以复用;
1 /* called with local bh disabled */ 2 static int __inet_check_established(struct inet_timewait_death_row *death_row, 3 struct sock *sk, __u16 lport, 4 struct inet_timewait_sock **twp) 5 { 6 struct inet_hashinfo *hinfo = death_row->hashinfo; 7 struct inet_sock *inet = inet_sk(sk); 8 __be32 daddr = inet->inet_rcv_saddr; 9 __be32 saddr = inet->inet_daddr; 10 int dif = sk->sk_bound_dev_if; 11 INET_ADDR_COOKIE(acookie, saddr, daddr); 12 const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport); 13 struct net *net = sock_net(sk); 14 unsigned int hash = inet_ehashfn(net, daddr, lport, 15 saddr, inet->inet_dport); 16 struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash); 17 spinlock_t *lock = inet_ehash_lockp(hinfo, hash); 18 struct sock *sk2; 19 const struct hlist_nulls_node *node; 20 struct inet_timewait_sock *tw = NULL; 21 22 spin_lock(lock); 23 24 /* 遍历链表 */ 25 sk_nulls_for_each(sk2, node, &head->chain) { 26 27 /* hash不等 */ 28 if (sk2->sk_hash != hash) 29 continue; 30 31 /* 找到节点 */ 32 if (likely(INET_MATCH(sk2, net, acookie, 33 saddr, daddr, ports, dif))) { 34 /* 节点连接处于timewait状态 */ 35 if (sk2->sk_state == TCP_TIME_WAIT) { 36 tw = inet_twsk(sk2); 37 38 /* 可以复用 */ 39 if (twsk_unique(sk, sk2, twp)) 40 break; 41 } 42 43 /* 不处于tw,或者不能复用 */ 44 goto not_unique; 45 } 46 } 47 48 /* Must record num and sport now. Otherwise we will see 49 * in hash table socket with a funny identity. 50 */ 51 /* 设置端口和hash */ 52 inet->inet_num = lport; 53 inet->inet_sport = htons(lport); 54 sk->sk_hash = hash; 55 WARN_ON(!sk_unhashed(sk)); 56 57 /* 节点加入ehash */ 58 __sk_nulls_add_node_rcu(sk, &head->chain); 59 if (tw) { 60 /* 删除tw节点 */ 61 sk_nulls_del_node_init_rcu((struct sock *)tw); 62 __NET_INC_STATS(net, LINUX_MIB_TIMEWAITRECYCLED); 63 } 64 spin_unlock(lock); 65 66 /* 增加使用计数 */ 67 sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); 68 69 /* 设置能复用的控制块 */ 70 if (twp) { 71 *twp = tw; 72 } else if (tw) { 73 /* Silly. Should hash-dance instead... */ 74 inet_twsk_deschedule_put(tw); 75 } 76 return 0; 77 78 not_unique: 79 spin_unlock(lock); 80 return -EADDRNOTAVAIL; 81 }
inet_ehash_nolisten用于将控制块加入ehash,并根据结果做不同处理;
1 /* 添加到ehash中 */ 2 bool inet_ehash_nolisten(struct sock *sk, struct sock *osk) 3 { 4 /* 添加到ehash中 */ 5 bool ok = inet_ehash_insert(sk, osk); 6 7 if (ok) { 8 /* 成功增加计数 */ 9 sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); 10 } else { 11 /* 增加孤儿数量 */ 12 percpu_counter_inc(sk->sk_prot->orphan_count); 13 /* 标识连接关闭状态 */ 14 sk->sk_state = TCP_CLOSE; 15 /* 设置销毁标记 */ 16 sock_set_flag(sk, SOCK_DEAD); 17 /* 销毁控制块 */ 18 inet_csk_destroy_sock(sk); 19 } 20 return ok; 21 }
tcp_connect用于构造syn包并发送之,发送之后需要设置syn包的重传定时器;
1 /* Build a SYN and send it off. */ 2 int tcp_connect(struct sock *sk) 3 { 4 struct tcp_sock *tp = tcp_sk(sk); 5 struct sk_buff *buff; 6 int err; 7 8 /* 检查重建路由 */ 9 if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk)) 10 return -EHOSTUNREACH; /* Routing failure or similar. */ 11 12 /* 初始化控制块中与连接相关的成员 */ 13 tcp_connect_init(sk); 14 15 if (unlikely(tp->repair)) { 16 tcp_finish_connect(sk, NULL); 17 return 0; 18 } 19 20 /* 分配skb */ 21 buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true); 22 if (unlikely(!buff)) 23 return -ENOBUFS; 24 25 /* 无数据的skb相关控制信息初始化 */ 26 tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN); 27 28 /* 设置发送syn的时间 */ 29 tp->retrans_stamp = tcp_time_stamp; 30 31 /* 加入发送队列 */ 32 tcp_connect_queue_skb(sk, buff); 33 34 /* enc拥塞通告支持 */ 35 tcp_ecn_send_syn(sk, buff); 36 37 /* Send off SYN; include data in Fast Open. */ 38 /* 发送syn */ 39 err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) : 40 tcp_transmit_skb(sk, buff, 1, sk->sk_allocation); 41 if (err == -ECONNREFUSED) 42 return err; 43 44 /* We change tp->snd_nxt after the tcp_transmit_skb() call 45 * in order to make this packet get counted in tcpOutSegs. 46 */ 47 /* 设置序号信息 */ 48 tp->snd_nxt = tp->write_seq; 49 tp->pushed_seq = tp->write_seq; 50 TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS); 51 52 /* Timer for repeating the SYN until an answer. */ 53 /* 启动重传定时器 */ 54 inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, 55 inet_csk(sk)->icsk_rto, TCP_RTO_MAX); 56 return 0; 57 }
以上是关于TCP主动打开 之 第一次握手-发送SYN的主要内容,如果未能解决你的问题,请参考以下文章