网络篇之connect

网络篇之socket

网络篇之bind

网络篇之listen

网络篇之accept

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后面的逻辑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值