1.什么是三次握手?
TCP协议建立连接时,需要三次发送数据包:
第一次:客户机向服务器端请求建立连接
第二次:服务器收到客户机的请求,发出响应
第三次:客户机收到响应 认为连接建立成功
详细过程:
名词解释:
SYN - 标志位 只有第一次和第二次为1,第三次和其他任何情况都是0
ACK - 标志位 只有第一次不为1,第二,三次和其他任何情况都是1
Sequence Number 顺序号,初始值为随机数
Acknowledgment Number 确认号,下一次对收到的数据顺序号的期望
第一次:
客户机 >>服务器
SYN =1
ACK =0
Sequence Number=X(随机数)
第二次:
SYN =1
ACK =1
Sequence Number=Y(随机数)
Acknowledgment Number=X+1
客户机<<服务器
第三次:
SYN =0
ACK =1
Sequence Number=X+1
Acknowledgment Number=Y+1
客户机 >>服务器
2.为什么要有三次握手?
我们考虑一次和两次握手为什么不行:
考虑
链路1 客户机 >>服务器
链路2 客户机<<服务器
一次握手:
如果链路1故障,客户机仍然会认为连接成功,而服务器不知道连接发生
如果链路2故障,服务器和客户机都会认为连接成功
两次握手:
如果链路2故障,服务器会认为连接成功
接下来考虑三次握手:
如果链路1故障
服务器端收不到第一次握手包,因而不会认为有连接请求,没有误认为连接成功,也不会发送第二次握手包
客户机收不到第二次握手包(服务器端没有发送),没有误认为连接成功
双方都不会误认为连接成功
如果链路2故障
客户机收不到第二次握手包(服务器端发送了但由于链路故障没有收到),没有误认为连接成功,也不会发送第三次握手包
服务器端收不到第三次握手包(客户机没有发送),没有误认为连接成功
双方都不会误认为连接成功
3.捕获一次典型的TCP三次握手:
我们使用wireshark工具
首先 在cmd执行 ping www.baidu.com
这是为了确定目标IP地址,便于设置捕获规则
ip.addr==180.101.49.12
打开www.baidu.com
捕获到
这三个数据包即是TCP三次握手数据包(192.168.3.89是本地IP)
这里有一个问题:为什么Seq的初始值是0而不是一个随机数?
这是wireShark软件本身的特性,显示的不是实际值而是相对值
三次Sequence Number和Acknowledgment Number的真实值(使用十六进制):
第一次(分别是前八位和后八位):
第二次:
第三次:
可以看到符合
X 0
Y X+1
X+1 Y+1的规律
和我们的预期相符
三次数据包的标志位:
第一次:
第二次:
第三次:
可以看到wireshark已经非常贴心的替我们做好了标注
也和我们的预期一致
3.代码追踪和分析:
在之前的实验中,我们知道发出TCP连接请求的函数是__sys_connect
我们分析这个函数的源代码
1 int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen) 2 { 3 struct socket *sock; 4 struct sockaddr_storage address; 5 int err, fput_needed; 6 sock = sockfd_lookup_light(fd, &err, &fput_needed); 7 if (!sock) 8 goto out; 9 err = move_addr_to_kernel(uservaddr, addrlen, &address); 10 if (err < 0) 11 goto out_put; 12 err = 13 security_socket_connect(sock, (struct sockaddr *)&address, addrlen); 14 if (err) 15 goto out_put; 16 17 err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, 18 sock->file->f_flags); 19 out_put: 20 fput_light(sock->file, fput_needed); 21 out: 22 return err; 23 }
主要的执行过程是
1 err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, 2 sock->file->f_flags)
这是一个函数指针,我们通过gdb,发现指向:inet_stream_connect
源代码
1 int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, 2 int addr_len, int flags) 3 { 4 int err; 5 6 lock_sock(sock->sk); 7 err = __inet_stream_connect(sock, uaddr, addr_len, flags, 0); 8 release_sock(sock->sk); 9 return err; 10 }
发现是对__inet_stream_connect的封装,前面应当是并发控制
继续追踪源代码:
1 int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, 2 int addr_len, int flags) 3 { 4 struct sock *sk = sock->sk; 5 int err; 6 long timeo; 7 8 if (addr_len < sizeof(uaddr->sa_family)) 9 return -EINVAL; 10 11 if (uaddr->sa_family == AF_UNSPEC) { 12 err = sk->sk_prot->disconnect(sk, flags); 13 sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED; 14 goto out; 15 } 16 switch (sock->state) { 17 default: 18 err = -EINVAL; 19 goto out; 20 case SS_CONNECTED: 21 err = -EISCONN; 22 goto out; 23 case SS_CONNECTING: 24 err = -EALREADY; 25 break; 26 case SS_UNCONNECTED: 27 err = -EISCONN; 28 if (sk->sk_state != TCP_CLOSE) 29 goto out; 30 err = sk->sk_prot->connect(sk, uaddr, addr_len); 31 ...太长了 后面的先省略
重点是err = sk->sk_prot->connect(sk, uaddr, addr_len);
可以看到这个函数又是通过一个函数指针工作的
err = sk->sk_prot->connect(sk, uaddr, addr_len);
追踪这个函数指针,发现最终指向:tcp_v4_connect
源代码
1 int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) 2 { 3 struct sockaddr_in *usin = (struct sockaddr_in *)uaddr; 4 struct inet_sock *inet = inet_sk(sk); 5 struct tcp_sock *tp = tcp_sk(sk); 6 __be16 orig_sport, orig_dport; 7 __be32 daddr, nexthop; 8 struct flowi4 *fl4; 9 struct rtable *rt; 10 int err; 11 struct ip_options_rcu *inet_opt; 12 struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row; 13 14 if (addr_len < sizeof(struct sockaddr_in)) 15 return -EINVAL; 16 17 if (usin->sin_family != AF_INET) 18 return -EAFNOSUPPORT; 19 20 nexthop = daddr = usin->sin_addr.s_addr; 21 inet_opt = rcu_dereference_protected(inet->inet_opt, 22 lockdep_sock_is_held(sk)); 23 if (inet_opt && inet_opt->opt.srr) { 24 if (!daddr) 25 return -EINVAL; 26 nexthop = inet_opt->opt.faddr; 27 } 28 29 orig_sport = inet->inet_sport; 30 orig_dport = usin->sin_port; 31 fl4 = &inet->cork.fl.u.ip4; 32 rt = ip_route_connect(fl4, nexthop, inet->inet_saddr, 33 RT_CONN_FLAGS(sk), sk->sk_bound_dev_if, 34 IPPROTO_TCP, 35 orig_sport, orig_dport, sk); 36 if (IS_ERR(rt)) { 37 err = PTR_ERR(rt); 38 if (err == -ENETUNREACH) 39 IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES); 40 return err; 41 } 42 43 if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) { 44 ip_rt_put(rt); 45 return -ENETUNREACH; 46 } 47 48 if (!inet_opt || !inet_opt->opt.srr) 49 daddr = fl4->daddr; 50 51 if (!inet->inet_saddr) 52 inet->inet_saddr = fl4->saddr; 53 sk_rcv_saddr_set(sk, inet->inet_saddr); 54 55 if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) { 56 /* Reset inherited state */ 57 tp->rx_opt.ts_recent = 0; 58 tp->rx_opt.ts_recent_stamp = 0; 59 if (likely(!tp->repair)) 60 tp->write_seq = 0; 61 } 62 63 inet->inet_dport = usin->sin_port; 64 sk_daddr_set(sk, daddr); 65 66 inet_csk(sk)->icsk_ext_hdr_len = 0; 67 if (inet_opt) 68 inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; 69 70 tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT; 71 72 tcp_set_state(sk, TCP_SYN_SENT); 73 err = inet_hash_connect(tcp_death_row, sk); 74 if (err) 75 goto failure; 76 77 sk_set_txhash(sk); 78 79 rt = ip_route_newports(fl4, rt, orig_sport, orig_dport, 80 inet->inet_sport, inet->inet_dport, sk); 81 if (IS_ERR(rt)) { 82 err = PTR_ERR(rt); 83 rt = NULL; 84 goto failure; 85 } 86 87 sk->sk_gso_type = SKB_GSO_TCPV4; 88 sk_setup_caps(sk, &rt->dst); 89 rt = NULL; 90 91 if (likely(!tp->repair)) { 92 if (!tp->write_seq) 93 tp->write_seq = secure_tcp_seq(inet->inet_saddr, 94 inet->inet_daddr, 95 inet->inet_sport, 96 usin->sin_port); 97 tp->tsoffset = secure_tcp_ts_off(sock_net(sk), 98 inet->inet_saddr, 99 inet->inet_daddr); 100 } 101 102 inet->inet_id = tp->write_seq ^ jiffies; 103 104 if (tcp_fastopen_defer_connect(sk, &err)) 105 return err; 106 if (err) 107 goto failure; 108 109 err = tcp_connect(sk); 110 111 if (err) 112 goto failure; 113 114 return 0; 115 116 failure: 117 118 tcp_set_state(sk, TCP_CLOSE); 119 ip_rt_put(rt); 120 sk->sk_route_caps = 0; 121 inet->inet_dport = 0; 122 return err; 123 }