connect是通信的双方进行信息的确认,为后续的数据传输做好准备。我们平常所说的三次握手,就是出现在这一阶段。首先借用网上的一张图,看下三次握手时,客户端与服务器的状态。
从客户端的角度来看,发送SYN后,本身变成SYN_SENT状态;收到服务器发送的SYN+ACK后,变成ESTABLISHED状态,即连接建立成功。
从服务端的角度来看,接收到SYN后,本身变成SYN_RCVD状态;接收到ACK后,变成ESTABLISHED状态。
建立连接之前为什么要有这三次握手呢,主要是为了协商seq和ack两个参数的值。这两个参数是判断收发数据是否完整的基础。
上图是对tcp的建立过程进行抓包整理得到的,其中107为客户端,106为服务器,“->”表示左边IP发给右边IP,“<-”表示右边IP发给左边IP。
64:106向106发送SYN包,107的SEQ为2961699079,,此时还未收到服务端的任何包,ACK为0。
65:106收到SYN包后,向107发送SYN+ACK,106的SEQ为1646806352,ACK为2961699080(107的SEQ+1)。
66:107将本身的SEQ+1,作为本次发送的SEQ(2961699080);将收到的服务端的SEQ+1,作为本次发送的ACK(1646806353)。
以客户端的角度,对三次握手做个总结:客户端发送SYN包是,将本身的SEQ(2961699079)传给服务端;收到服务端发送的SYN+ACK后,收到的ACK是SEQ+1(2961699080),tcp包的其他校验也都成功后,将状态改成ESTABLISHED。
对三次握手的分析到此结束。
SEQ记录了本身发送数据的长度,ACK则记录了接收数据的长度。下面是收发数据时,SEQ和ACK的变化情况:
67:106向107发送42字节的tcp数据。106当前的。SEQ是1646806353,ACK是2961699080。
收到[67]的数据之前,107的SEQ和ACK为[66]所示。
68:107向106发送50字节的数据,SEQ为2961699080,由于收到了106发送的42字节的数据,因此ACK变成1646806395(1646806353+42)。
69:107再次向106发送1416字节数据,上一包发送了50字节数据,SEQ变成2961699130(2961699080+50)。
一 发送SYN包
connect的系统调用的原型是,SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen)位于net/socket.c文件中。该函数主要是根据用户空间传入的句柄fd,查找对应的socket,连接的大部分逻辑在tcp_v4_connect函数中。
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
struct inet_sock *inet = inet_sk(sk);
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_dst中
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;
}
// tcp不允许使用多播和广播
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);
// 时间戳选项相关处理
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;
}
inet->inet_dport = usin->sin_port;
sk_daddr_set(sk, daddr); // 设置目的地址
// 设置tcp首部选项部分长度
inet_csk(sk)->icsk_ext_hdr_len = 0;
if (inet_opt)
inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen;
// 设置最小允许mss值
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_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.
*/
// 设置套接字状态
tcp_set_state(sk, TCP_CLOSE);
ip_rt_put(rt);
sk->sk_route_caps = 0;
inet->inet_dport = 0;
return err;
}
上面的代码中都加了注释,需要着重说一下的是:
tcp_set_state(sk, TCP_SYN_SENT);
1.将状态改成TCP_SYN_SENT,此状态保存在sk->sk_state字段中。
tp->write_seq = secure_tcp_seq(inet->inet_saddr,inet->inet_daddr,
inet->inet_sport, usin->sin_port);
2.根据源/目的IP和源/目的端口,计算出初始的seq,此seq即发送SYN包时的seq。
下面看下tcp_connect:
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);
if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
return -EHOSTUNREACH; /* Routing failure or similar. */
// 对tcb进行一定初始化
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); // 将buff连接到tcp_rtx_queue上
/* Send off SYN; include data in Fast Open. */
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
// 发送syn请求报文
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);
/* Timer for repeating the SYN until an answer. */
// 启动syn重传定时器,初始超时时间1s
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
return 0;
}
buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
1. 内核网络发送和接收的数据,保存在一个sk_buff结构中,这里是申请此结构的地方;
tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
2.将当前的seq添加到buff中,并将当前的seq+1;
tp->snd_nxt = tp->write_seq;
3.记录seq+1后的值,用于下一次发送tcp负载数据时的seq(发送ACK包是,seq不增加);
tcp_transmit_skb->ip_queue_xmit->ip_local_out->NF_INET_LOCAL_OUT->ip_output->ip_finish_output
4.数据被发送出去之前,还经过了上述多个方法;
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
重传列表如下:
struct inet_connection_sock *icsk = inet_csk(sk);
icsk->icsk_retransmit_timer
5.最后将发送的数据添加到重传列表中,如果指定时间内没有收到回包,则重新发送数据包。
二 等待连接建立完成
在__inet_stream_connect中,发送完SYN包后,调用inet_wait_for_connect等待连接建立完成。
static long inet_wait_for_connect(struct sock *sk, long timeo, int writebias)
{
DEFINE_WAIT_FUNC(wait, woken_wake_function);
add_wait_queue(sk_sleep(sk), &wait);
sk->sk_write_pending += writebias;
/* Basic assumption: if someone sets sk->sk_err, he _must_
* change state of the socket from TCP_SYN_*.
* Connect() does not allow to get error notifications
* without closing the socket.
*/
while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
release_sock(sk);
timeo = wait_woken(&wait, TASK_INTERRUPTIBLE, timeo);
lock_sock(sk);
if (signal_pending(current) || !timeo)
break;
}
remove_wait_queue(sk_sleep(sk), &wait);
sk->sk_write_pending -= writebias;
return timeo;
}
定义一个wait对象,并添加到sk->sk_wq链表上。
客户端当前的状态为SYN_SENT,接收到服务端发送的SYN+ACK包,将经过tcp_rcv_state_process到达tcp_rcv_synsent_state_process。在tcp_rcv_synsent_state_process中,调用tcp_finish_connect将socket状态改成ESTABLISHED后,调用sk->sk_state_change(sk) 。sk_state_change实际上是sock_def_wakeup。
static void sock_def_wakeup(struct sock *sk)
{
struct socket_wq *wq;
rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
if (skwq_has_sleeper(wq))
wake_up_interruptible_all(&wq->wait);
rcu_read_unlock();
}
sock_def_wakeup中唤醒sk->sk_wq上等待的进程。
客户端继续执行__inet_stream_connect中inet_wait_for_connect后面的逻辑。