深入理解TCP协议及其源代码
前言
在前面实验我们分别实现了Socket 通信工具,探讨了Socket API、Socket 调用原理等。但是还没有针对某一实例进行讲解,在本实验我们将针对TCP协议进行详细分析,期待在Linux内核进行分析TCP原理。
1.Tcp基本原理
TCP是一种面向连接、可靠、基于字节流的传输协议,位于TCP/IP模型的传输层。
- 面向连接:不同于UDP,TCP协议需要通信双方确定彼此已经建立连接后才可以进行数据传输;
- 可靠:连接建立的双方在进行通信时,TCP保证了不会存在数据丢失,或是数据丢失后存在拯救丢失的措施;
- 字节流:实际传输中,不论是何种数据,TCP都按照字节的方式传输,而非以数据包为单位。
针对它的这三种特性,本小节我们将对其原理进行探究。
1.1面向连接(三次握手)
- 第一次握手。如图,TCP双方在进行连接时首先由Client(客户端)发起连接请求,请求中附带连接参数,包括随机数字起点Seq(预防传输时字节序列被预测收到攻击),连接请求标志位SYN(占用1字节序号)等。
- 第二次握手:当Server(服务器)分配资源打开监听请求,收到客户端请求后,对请求头进行解析。若连接建立成功则分配相应资源,并返回针对客户端请求的确认报文,其中响应报文头部参数包括:连接建立标志位SYN、Server端针对该通信过程的随机Seq、针对该请求的确认号ack、可附加接收窗口大小信息等。
- 第三次握手。客户端收到服务端的确认连接请求后将会发送对该确认请求的确认(简单来说也就是A请求B,B告诉A准许,A再告诉B我知道你准许了),试想若不对该请求进行响应那么服务端将白白分配资源并等待。
若以上三次握手都没问题则连接建立,在第三次握手的时候即可开始传送数据。1.2可靠
2.基本原理探究
在上次实验,我们通过追踪qemu底层的sys_call入口观察系统态和内核态之间的联系,理清了系统层面是怎样对底层接口进行调用的。在本小节,详细分析一下TCP协议在内核中的基本原理。
TCP协议的初始化及socket创建TCP套接字描述符
如图所示,上面展示了TCP调用系统内核中的相关函数进行资源分配和通信。经过上次实验对qemu的跟踪不难发现在建立连接及通信时在服务端经历了socket()->bind()->listen()->accept()四个步骤,在accet() 函数之后会进行客户端的数据通信。
为了验证连接建立的过程,我们对gdb跟踪的函数过程进行抓包,只要出现三次握手就能够捕捉到,同时对系统调用接口函数打断点,就能够知道在那个函数调用之间进行了三次通信。
初始执行过程,在wireshark中捕捉不到TCP通信,不断跳过断点,当客户端执行到connect(),服务端执行到accept()后捕获到TCP通信过程如下图。
不难发现开始建立三次握手的过程发生在服务端accpet()后,当连接建立后,执行send()及rev()进行数据通信。
知道TCP建立连接的过程,接下来我们来探究一下它的初始化和套接字初始化过程。在文件/net/tcp /下找到accpet函数定义发现它最终调用了__sys_accept4,同时connect函数调用了__sys_connect函数。
TCP的三次握手从用户程序的角度看就是客户端connect和服务端accept建立起连接时背后完成的工作,在内核socket接口层这两个socket API函数对应着sys_connect和sys_accept函数,进一步对应着sock->opt->connect和sock->opt->accept两个函数指针,在TCP协议中这两个函数指针对应着tcp_v4_connect函数和inet_csk_accept函数。
分析到这里,应该能够想到我们可以通过MenuOS的内核调试环境设置断点跟踪tcp_v4_connect函数和inet_csk_accept函数来进一步验证三次握手的过程。
tcp_v4_connect函数
tcp_v4_connect函数的主要作用就是发起一个TCP连接,建立TCP连接的过程自然需要底层协议的支持,因此我们从这个函数中可以看到它调用了IP层提供的一些服务,比如ip_route_connect和ip_route_newports从名称就可以简单分辨,这里我们关注在TCP层面的三次握手,不去深究底层协议提供的功能细节。我们可以看到这里设置了 TCP_SYN_SENT并进一步调用了 tcp_connect(sk)来实际构造SYN并发送出去。
tcp_connect函数具体负责构造一个携带SYN标志位的TCP头并发送出去,同时还设置了计时器超时重发。
其中tcp_transmit_skb函数负责将tcp数据发送出去,这里调用了icsk->icsk_af_ops->queue_xmit函数指针,实际上就是在TCP/IP协议栈初始化时设定好的IP层向上提供数据发送接口ip_queue_xmit函数,这里TCP协议栈通过调用这个icsk->icsk_af_ops->queue_xmit函数指针来触发IP协议栈代码发送数据,感兴趣的读者可以查找queue_xmit函数指针是如何初始化为ip_queue_xmit函数的。具体ip_queue_xmit函数内部的实现我们在后续内容中会专题研究,这里聚焦在TCP协议的三次握手。
inet_csk_accept函数
服务端调用inet_csk_accept函数会从请求队列中取出一个连接请求,如果队列为空则通过inet_csk_wait_for_connect阻塞住等待客户端的连接。
inet_csk_wait_for_connect函数就是无限for循环,一旦有连接请求进来则跳出循环。
按如上思路跟踪调试代码的话,会发现connect之后将连接请求发送出去,accept等待连接请求,connect启动到返回和accept返回之间就是所谓三次握手的时间。
这俩函数核心是通过sock->opt->connect和sock->opt->accept这两个函数指针调用了某个函数。
其中sock是struct socket类型的,为了追踪下去我们还得找到struct socket这个结构体的定义。
和TCP初始化基本参数的过程,我们将这几个底层函数拆分开来,进行单独的gdb跟踪调试
原文地址:https://www.cnblogs.com/xshun/p/12093145.html