TCP三次握手源码分析

TCP握手分为三个阶段,在握手开始之前,通信双方的套接字状态均为“TCP_CLOSE”,以下是这三个阶段:

(1)客户端发送一个标志位中SYN位为1的报文给服务端,并设套接字状态为“TCP_SYNSENT”

(2)服务端接到SYN报文,设套接字状态为“TCP_SYNRCV”,并回送一个SYN+ACK位均为1的报文

(3)客户端接到SYN+ACK报文,回送一个ACK位为1的报文,设套接字状态为“TCP_ESTABLISHED”,服务端接到ACK报文后,同样设置为“TCP_ESTABLISHED”

第一阶段

第一阶段客户端通过调用connect函数完成,connect实际上调用了内核中的__sys_connect函数。

以下代码是有关__sys_connect函数在文件net/scoket.c中的系统调用定义,由此可以看出,__sys_connect函数就是connect在内核中的实现。

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
		int, addrlen)
{
	return __sys_connect(fd, uservaddr, addrlen);
}

从__sys_connect函数开始进入三次握手的第一阶段,以下是部分代码:

int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
{        ...
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
        ...
	err = move_addr_to_kernel(uservaddr, addrlen, &address);
	        ...

	err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
				 sock->file->f_flags);
        ...
}
代码中的sock->ops->connect即是tcp_v4_connect函数,现在转到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     /* Socket identity is still unknown (sport may be zero).
 73      * However we set state to SYN-SENT and not releasing socket
 74      * lock select source port, enter ourselves into the hash tables and
 75      * complete initialization after this.
 76      */
 77     tcp_set_state(sk, TCP_SYN_SENT);
 78     err = inet_hash_connect(tcp_death_row, sk);
 79     if (err)
 80         goto failure;
 81
 82     sk_set_txhash(sk);
 83
 84     rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
 85                    inet->inet_sport, inet->inet_dport, sk);
 86     if (IS_ERR(rt)) {
 87         err = PTR_ERR(rt);
 88         rt = NULL;
 89         goto failure;
 90     }
 91     /* OK, now commit destination to socket.  */
 92     sk->sk_gso_type = SKB_GSO_TCPV4;
 93     sk_setup_caps(sk, &rt->dst);
 94     rt = NULL;
 95
 96     if (likely(!tp->repair)) {
 97         if (!tp->write_seq)
 98             tp->write_seq = secure_tcp_seq(inet->inet_saddr,
 99                                inet->inet_daddr,
100                                inet->inet_sport,
101                                usin->sin_port);
102         tp->tsoffset = secure_tcp_ts_off(sock_net(sk),
103                          inet->inet_saddr,
104                          inet->inet_daddr);
105     }
106
107     inet->inet_id = tp->write_seq ^ jiffies;
108
109     if (tcp_fastopen_defer_connect(sk, &err))
110         return err;
111     if (err)
112         goto failure;
113
114     err = tcp_connect(sk);
115
116     if (err)
117         goto failure;
118
119     return 0;
120
121 failure:
122     /*
123      * This unhashes the socket and releases the local port,
124      * if necessary.
125      */
126     tcp_set_state(sk, TCP_CLOSE);
127     ip_rt_put(rt);
128     sk->sk_route_caps = 0;
129     inet->inet_dport = 0;
130     return err;
131 }

在tcp_v4_connect函数中为套接字填充一些变量,将套接字的状态修改为“TCP_SYNSENT”,然后进入tcp_connect函数。

 1 int tcp_connect(struct sock *sk)
 2 {
 3     struct tcp_sock *tp = tcp_sk(sk);
 4     struct sk_buff *buff;
 5     int err;
 6
 7     tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_CONNECT_CB, 0, NULL);
 8
 9     if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
10         return -EHOSTUNREACH; /* Routing failure or similar. */
11
12     tcp_connect_init(sk);
13
14     if (unlikely(tp->repair)) {
15         tcp_finish_connect(sk, NULL);
16         return 0;
17     }
18
19     buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
20     if (unlikely(!buff))
21         return -ENOBUFS;
22
23     tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
24     tcp_mstamp_refresh(tp);
25     tp->retrans_stamp = tcp_time_stamp(tp);
26     tcp_connect_queue_skb(sk, buff);
27     tcp_ecn_send_syn(sk, buff);
28     tcp_rbtree_insert(&sk->tcp_rtx_queue, buff);
29
30     /* Send off SYN; include data in Fast Open. */
31     err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
32           tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
33
39     ...47
48     /* Timer for repeating the SYN until an answer. */
49     inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
50                   inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
51     return 0;
52 }

通过调用tcp_transmit_skb函数构造SYN报文并发送出去,并设立一个定时器。

这一阶段函数的调用栈:

__sys_connect -> inet_stream_connect -> __inet_stream_connect -> tcp_v4_connect -> tcp_connect -> tcp_transmit_skb

第二阶段

这一阶段从中通过tcp_v4_rcv函数从ip层接收数据开始,以下是tcp_v4_rcv的部分代码:

 1 int tcp_v4_rcv(struct sk_buff *skb)
 2 {
 3     ...
 4
 5     if (sk->sk_state == TCP_LISTEN) {
 6         ret = tcp_v4_do_rcv(sk, skb);
 7         goto put_and_return;
 8     }
 9
10     ...
11
12 put_and_return:
13     if (refcounted)
14         sock_put(sk);
15
16     return ret;
17         ...
18 }

由于当前套接字状态为“TCP_LISTEN”,进入tcp_v4_do_rcv函数执行

 1 int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
 2 {
 3     ...
 4
 5     if (sk->sk_state == TCP_LISTEN) {
 6
 7     if (tcp_rcv_state_process(sk, skb)) {
 8         rsk = sk;
 9         goto reset;
10     }
11     return 0;
12        ...
13 }

tcp_rcv_state_process函数专门用来处理套接字状态的转换,先贴出一张状态转换图:

 1 int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
 2 {
 3         ...
 4
 5     switch (sk->sk_state) {
 6     case TCP_LISTEN:
 7         if (th->ack)
 8             return 1;
 9
10         if (th->rst)
11             goto discard;
12
13         if (th->syn) {
14             if (th->fin)
15                 goto discard;
16             /* It is possible that we process SYN packets from backlog,
17              * so we need to make sure to disable BH and RCU right there.
18              */
19             rcu_read_lock();
20             local_bh_disable();
21             acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
22             local_bh_enable();
23             rcu_read_unlock();
24
25             if (!acceptable)
26                 return 1;
27             consume_skb(skb);
28             return 0;
29         }
30         goto discard;
31                 ...
32 }

这是tcp_rcv_state_process在“TCP_LISTEN”阶段执行的代码,核心在于22行的icsk->icsk_af_ops->conn_request,在此处一路执行tcp_v4_conn_request, tcp_conn_request。

以下是tcp_conn_request的部分代码:

 1 if (fastopen_sk) {
 2         af_ops->send_synack(fastopen_sk, dst, &fl, req,
 3                     &foc, TCP_SYNACK_FASTOPEN);
 4         /* Add the child socket directly into the accept queue */
 5         inet_csk_reqsk_queue_add(sk, req, fastopen_sk);
 6         sk->sk_data_ready(sk);
 7         bh_unlock_sock(fastopen_sk);
 8         sock_put(fastopen_sk);
 9     } else {
10         tcp_rsk(req)->tfo_listener = false;
11         if (!want_cookie)
12             inet_csk_reqsk_queue_hash_add(sk, req,
13                 tcp_timeout_init((struct sock *)req));
14         af_ops->send_synack(sk, dst, &fl, req, &foc,
15                     !want_cookie ? TCP_SYNACK_NORMAL :
16                            TCP_SYNACK_COOKIE);
17         if (want_cookie) {
18             reqsk_free(req);
19             return 0;
20         }
21     }

主要执行了send_synack函数,send_synack函数用于将SYN+ACK报文发送出去。

这一阶段函数的调用栈:

tcp_v4_rcv -> tcp_v4_do_rcv -> tcp_rcv_state_process -> tcp_v4_conn_request -> tcp_conn_request -> tcp_v4_send_synack

第三阶段

同上一阶段一样,从ip接收到报文后一路执行tcp_v4_rcv, tcp_v4_do_rcv,进入tcp_rcv_state_process函数:

 1 int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
 2               const struct tcphdr *th, unsigned int len)
 3 {
 4     ...
 5     switch (sk->sk_state) {
 6     case TCP_SYN_SENT:
 7         //进入到synack报文的处理流程
 8         queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
 9         if (queued >= 0)
10             return queued;
11
12         /* Do step6 onward by hand. */
13         tcp_urg(sk, skb, th);
14         __kfree_skb(skb);
15         tcp_data_snd_check(sk);
16         return 0;
17     }
18     ...
19 }

在tcp_rcv_synsent_state_process函数中又调用了tcp_finish_connect函数,tcp_finish_connect函数做了三件事:
(1)将套接字状态设置为"TCP_ESTABLISHED"
(2)调用tcp_send_ack函数发送一个ACK包
(3)初始化一些参数
tcp_send_ack函数又调用tcp_transmit_skb将ACK报文从网络上发出去。
最后是服务端接收到ACK报文,依次执行tcp_v4_rcv,tcp_v4_do_rcv,tcp_rcv_state_process函数,将套接字的状态设置为"TCP_ESTABLISHED",至此,三次握手过程结束。

这一阶段函数的调用栈:

tcp_v4_rcv -> tcp_v4_do_rcv -> tcp_rcv_synsent_state_process -> tcp_send_ack -> tcp_transmit_skb

tcp_v4_rcv -> tcp_rcv_state_process

原文地址:https://www.cnblogs.com/hujisha/p/12104441.html

时间: 2024-10-12 00:08:18

TCP三次握手源码分析的相关文章

Linux tcp被动打开内核源码分析

[我是从2个角度来看,其实所谓2个角度,是发现我分析源码时,分析重复了,写了2个分析报告,所以现在都贴出来.] [如果你是想看看,了解一下内核tcp被动打开时如何实现的话,我觉得还是看看概念就可以了,因为即使看了源码,过一个个礼拜你就忘了,如果是你正在修改协议栈,为不知道流程而发愁,那么希望你能看看源码以及注释,希望你给你帮助.] 概念: tcp被动打开,前提是你listen,这个被动打开的前提.你listen过后,其实创建了一个监听套接字,专门负责监听,不会负责传输数据. 当第一个syn包到达

u-boot学习(三):u-boot源码分析

建立域模型和关系数据模型有着不同的出发点: 域模型: 由程序代码组成, 通过细化持久化类的的粒度可提高代码的可重用性, 简化编程 在没有数据冗余的情况下, 应该尽可能减少表的数目, 简化表之间的参照关系, 以便提高数据的访问速度 Hibernate 把持久化类的属性分为两种: 值(value)类型: 没有 OID, 不能被单独持久化, 生命周期依赖于所属的持久化类的对象的生命周期 实体(entity)类型: 有 OID, 可以被单独持久化, 有独立的生命周期(如果实体类型包含值类型,这个值类型就

Neural Turing Machines-NTM系列(三)ntm-lasagne源码分析

Neural Turing Machines-NTM系列(三)ntm-lasagne源码分析 在NTM系列文章(二)中,我们已经成功运行了一个ntm工程的源代码.在这一章中,将对它的源码实现进行分析. 1.网络结构 1.1 模块结构图 在图中可以看到,输入的数据在经过NTM的处理之后,输出经过NTM操作后的,跟之前大小相同的数据块.来看下CopyTask的完整输出图: 图中右侧的Input是输入数据,Output是目标数据,Prediction是通过NTM网络预测出来的输出数据,可以看出预测数据

Java 序列化和反序列化(三)Serializable 源码分析 - 2

目录 Java 序列化和反序列化(三)Serializable 源码分析 - 2 1. ObjectStreamField 1.1 数据结构 1.2 构造函数 2. ObjectStreamClass Java 序列化和反序列化(三)Serializable 源码分析 - 2 在上一篇文章中围绕 ObjectOutputStream#writeObject 讲解了一下序列化的整个流程,这中间很多地方涉及到了 ObjectStreamClass 和 ObjectStreamField 这两个类.

TCP三次握手及数据传输分析

TCP包结构 一个TCP包结构如下: 一个TCP包主要由TCP包头和数据部分组成,包头固定部分为20字节,选项和数据部分根据实际情况设置为4N(N可以为0)字节. 1.16bit源端口和目的端口号,它可以确认数据的传输方向(暂不考虑更底层的包) 2.32bit序号,它是为TCP包中数据部分进行编号的部分.假设要发送的数据有100M,由于受MSS( Maximum Segment Size 最大报文段长度)限制,一个TCP包是不可能传输完这100M的数据,于是需要将数据拆分,为了确保拆分传输后的数

Webpack-源码三,从源码分析如何写一个plugin

经过上一篇博客分析webpack从命令行到打包完成的整体流程,我们知道了webpage的plugin是基于事件机制工作的,这样最大的好处是易于扩展.社区里很多webpack的plugin,但是具体到我们的项目并不一定适用,这篇博客告诉你如何入手写一个plugin,然后分析源码相关部分告诉你你的plugin是如何工作.知其然且知其所以然. 该系列博客的所有测试代码. 从黑盒角度学习写一个plugin 所谓黑盒,就是先不管webpack的plugin如何运作,只去看官网介绍. Compiler和Co

Struts2【三】 StrutsPrepareAndExecuteFilter 源码分析&lt;一&gt;

先把关键的类总体一览一下 用JadClipse反编译debug源码 都知道Filter三个方法,init,doFilter,destory 先看init方法初始化了什么 先按名字记住几个关键类,initOperation初始化处理器,Dispatcher派发器,PrepareOperations预处理器,ExecuteOperations执行处理器 55.FilterHostConfig包装了FilterConfig 56.nit.initLogging不用管,这个貌似是过滤器初始化参数指定的日

三)CodeIgniter源码分析之Common.php

1 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 2 3 // ------------------------------------------------------------------------ 4 5 /** 6 * Common Functions 7 */ 8 9 /** 10 * 为什么还要定义这些全局函数呢?比如说,下面有很多函数,如get_config().confi

【JUC】JDK1.8源码分析之ReentrantLock(三)

一.前言 在分析了AbstractQueuedSynchronier源码后,接着分析ReentrantLock源码,其实在AbstractQueuedSynchronizer的分析中,已经提到过ReentrantLock,ReentrantLock表示下面具体分析ReentrantLock源码. 二.ReentrantLock数据结构 ReentrantLock的底层是借助AbstractQueuedSynchronizer实现,所以其数据结构依附于AbstractQueuedSynchroni