深入理解TCP协议运行过程和系统调用过程

深入理解TCP协议运行过程和系统调用过程

本次实验主要从以下几个方面展开:

  1. TCP/IP 分层结构
  2. TCP协议运行过程中的系统调用
  3. 跟踪验证

一、TCP/IP分层结构

相信所有考过研的小伙伴对TCP/IP模型的分层结构一定不陌生。我们在本科学习的时候,其实学习的5层结构,IEEE定义的网络模型结构是7层,而在真正生活中应用的则是4层结构,这4层分别是:应用层,传输层,网际层、网络接口层。这里我总结了四层、五层和七层结构的对比图,有遗忘的小伙伴可以看看。如下

二、TCP协议运行过程中的系统调用

通常来讲,TCP协议的运行过程可以用“三次握手”和“四次挥手”来总结

  • TCP协议的“三次握手”

    (1)第一次握手:
    Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
    (2)第二次握手:
    Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
    (3)第三次握手:
    Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

  • TCP协议的“四次挥手”

    (1)Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

    (2)Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。

    (3)Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

    (4)Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

  • TCP的运行过程的转态转换

我们从上图的转态转换以及上次实验的结果得出,典型的TCP客户端和服务器应用程序发出一系列TCP系统调用以实现某些功能,这些系统调用包括socket()、bind()、listen()、accept()、connect()、send()、receieve()、close()、shutdown()。当TCP应用运行的时候,会发生如下的事情:

  1. 首先,套接字接受进行的任何TCP系统调用。套接字层验证TCP应用程序传递的参数的正确性。
  2. 套接字层下面是协议层,其中包含协议的实际实现。当套接字层对协议层进行调用时,它确保对两个层之间共享的 数据结构具有独占访问权限。这样做是为了避免任何数据结构损坏。
  3. 各种网络设备驱动程序运行在接口层,该接口从数据链路层接受数据并向物理链路传输数据。

Socket

socket (struct proc ∗p, struct socket_args ∗uap, int retval)   
struct sock_args 
{
int domain, 
int type,
int protocol;
};

在socket系统调用中 :

  • p是指向socket调用的进程proc结构的指针
  • uap是指向socket_args结构的指针,该结构包含在socket系统调用中传递给进程的参数
  • retval是系统调用的返回值

socket系统调用创建通过分配新的描述符来创建新的套接字。新的描述符返回到调用过程。任何后续的系统调用都使用创建的套接字标识。套接字系统调用还将协议分配给创建的套接字描述符。domain,type和protocol参数值指定要分配给创建的套接字的族,类型和协议。

Bind

bind (struct proc ∗p, struct bind_args ∗uap, int ∗retval)   
struct bind_args 
{   int s;
    caddr_t name;
    int namelen;
};

bind系统调用中:

  • 是套接字描述符
  • name是指向包含网络传输地址的缓冲区的指针
  • namelen代表缓冲区的大小

bind系统调用将本地网络传输地址与套接字相关联。当客户端进程发出连接系统调用使,内核负责进行隐式绑定。服务器进程通常必须发出明确的绑定请求,然后才能接受连接或开始于客户端的通信

Listen

listen (struct proc ∗p, struct listen_args ∗uap, int ∗retval);     
struct listen_args
{ int s;
int backlog;
};

listen系统调用中:

  • s是套接字描述符
  • backlog是套接字上连接数的队列限制

listen系统调用向协议指示服务器进程已准备好接受套接字上的任何新的传入连接。但是可以排队的连接数有限制,在此之后,任何其他连接请求都将被忽略。

Accept

accept(struct proc ∗p, struct accept_args ∗uap, int ∗retval);   
struct  accept_args 
{
    int s;
    caddr_t name;
    int ∗anamelen;
};

listen系统调用中:

  • s是套接字描述符
  • name 是一个缓冲区,其中包含外部主机的网络传输地址
  • anamelen是name缓冲区的大小

accept系统调用是等待传入连接的阻塞调用。一旦处理了连接请求,accept将返回一个新的套接字描述符。这个新的套接字已连接到客户端,其他套接字仍保持LISTEN状态以接受进一步的连接

Connect

connect (struct proc ∗p, struct connect_args ∗uap, int ∗retval);   
struct connect_args 
{
    int s;
    caddr_t name;
    int namelen;
};

connect系统调用:

  • s是套接字描述符
  • name是指向具有外部IP /端口地址对的缓冲区的指针
  • namelen是缓冲的大小

客户端进程通常会调用connect系统调用以连接到服务器进程。如果客户端进程在启动连接之前未显式发出bind系统调用,则堆栈将处理本地套接字上的隐式绑定。

Shutdown

shutdown (struct proc ∗p, struct shutdown_args ∗uap, int ∗retval);   
Struct shutdown_args
{
    int s;
    int how;
}

shutdown系统调用中:

  • s是套接字描述符
  • how指定要关闭连接的哪一端。值分别为0,1,2,分别用于指定如何关闭从客户端到服务端的连接,从服务端向客户端的连接,或者把两个连接都关闭。

shutdown系统调用将关闭连接的一端或两端。如果需要关闭的是服务端指向客户端的连接,那么将丢弃接收缓冲区中存在的所有数据,并关闭连接的那一端。如果关闭的是客户端指向服务端的连接,TCP发送所有剩余数据,然后终止连接的写端

Close

soo_close(struct file ∗fp , struct proc ∗p);  

close系统调用中:

  • fp是指向文件结构的指针
  • p是指向调用过程的proc结构的指针

close系统调用将关闭或终止套接字上的所有挂起的连接

Send

sendmsg ( struct proc∗p, struct sendmsg_args ∗uap, int retval);   
struct sendmsg_args
{    
   int s;
   caddr_t msg;
   int flags;
};

send系统调用中:

  • s是套接字描述符
  • msg是指向msghdr结构的指针
  • flags是控制信息

Receieve

recvmsg(struct proc ∗p, struct recvmsg_args ∗uap , int ∗retval);   
struct recvmsg_args 
{
 int s,
 struct msghdr ∗msg,
 int flags,
};

Receieve系统调用中

  • s 是套接字描述符
  • msg是指向msghdr结构的指针
  • flags是控制信息

三、跟踪验证

  1. 首先,在启动qemuqemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -append nokaslr -s -S
  2. 然后另开一个命令行输入gdb,进入调试模式,然后输入file linux-5.0.1/vmlinux加载符号表,
  3. 设置断点。按照上面的分析,如下图所示
  4. 输入target remote:1234建立连接
  5. 不停的按c回车运行,直到运行结束。
    运行结果如下图所示:

结合上面的tcp运行过程和以及实际跟踪情况来看,会调用socket系统调用,这个系统调用对应着 sys_connectsys_accept,这两个函数又对应着刚刚我们设置短点的函数tcp_v4_connectinet_csk_accept函数。
inet_csk_accept函数会从客户端发出的请求中取出一个请求,如果没有客户端发出连接请求,那么服务端就会调用inet_csk_wait_for_connet函数并阻塞监听端口,等待客户端发出连接请求。
除了上面这个,服务端还会使用到sys_socket,sys_socket在其内部又调用了socket_create用于创建套接字
一旦请求来到,服务端就会调用sys_connect ,这个函数又会调用inet_csk_accept函数,这个函
tcp_v4_connect函数用于发起一个tcp连接请求,主要是客户端会调用这个

实验参考:
https://developer.ibm.com/articles/au-tcpsystemcalls/
https://github.com/mengning/net/tree/master

posted @ 2019-12-26 21:53  Dlemon  阅读(2163)  评论(0编辑  收藏  举报