深入Linux系统追踪TCP初始化

1.理论分析

TCP是面向连接的传输层协议,所谓面向连接就是在真正的数据传输开始前要完成连接建立的过程,否则不会进入真正的数据传输阶段。
TCP的连接建立过程通常被称为三次握手(three-way handshake),过程如下:请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN)。这个SYN段为报文段1。 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。 这三个报文段完成连接的建立。
发送第一个SYN的一端将执行主动打开(active open)。接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open)
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。RFC 793 [Postel1981c]指出ISN可看作是一个32比特的计数器,每4 ms加1。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它作错误的解释。
如何进行序号选择? 在4.4 BSD(和多数的伯克利的实现版)中,系统初始化时初始的发送序号被初始化为1。这个变量每0.5秒增加64000,并每隔9.5小时又回到0。另外,每次建立一个连接后,这个变量将增加64000。

接下来我们就通过代码追踪的方式,分析Linux中bind函数、accept函数、connect函数分别停留在上图中哪个阶段?

2.代码追踪

上次实验中,追踪到了socket的数据结构如下所示:

struct socket {
    socket_state        state;
    short            type;
    unsigned long        flags;
    struct socket_wq    *wq;
    struct file        *file;
    struct sock        *sk;
    const struct proto_ops    *ops;
};
在socket中Proto_ops结构体定义如下:
struct proto_ops {
    int        family;
    struct module    *owner;
    int        (*release)   (struct socket *sock);
    int        (*bind)         (struct socket *sock,
                      struct sockaddr *myaddr,
                      int sockaddr_len);
    int        (*connect)   (struct socket *sock,
                      struct sockaddr *vaddr,
                      int sockaddr_len, int flags);
    int        (*socketpair)(struct socket *sock1,
                      struct socket *sock2);
    int        (*accept)    (struct socket *sock,
                      struct socket *newsock, int flags, bool kern);
    int        (*getname)   (struct socket *sock,
                      struct sockaddr *addr,
                      int peer);
    __poll_t    (*poll)         (struct file *file, struct socket *sock,
                      struct poll_table_struct *wait);
    int        (*ioctl)     (struct socket *sock, unsigned int cmd,
                      unsigned long arg);
#ifdef CONFIG_COMPAT
    int         (*compat_ioctl) (struct socket *sock, unsigned int cmd,
                      unsigned long arg);
#endif
    int        (*listen)    (struct socket *sock, int len);
    int        (*shutdown)  (struct socket *sock, int flags);
    int        (*setsockopt)(struct socket *sock, int level,
                      int optname, char __user *optval, unsigned int optlen);
    int        (*getsockopt)(struct socket *sock, int level,
                      int optname, char __user *optval, int __user *optlen);
#ifdef CONFIG_COMPAT
    int        (*compat_setsockopt)(struct socket *sock, int level,
                      int optname, char __user *optval, unsigned int optlen);
    int        (*compat_getsockopt)(struct socket *sock, int level,
                      int optname, char __user *optval, int __user *optlen);
#endif
    int        (*sendmsg)   (struct socket *sock, struct msghdr *m,
                      size_t total_len);
    /* Notes for implementing recvmsg:
     * ===============================
     * msg->msg_namelen should get updated by the recvmsg handlers
     * iff msg_name != NULL. It is by default 0 to prevent
     * returning uninitialized memory to user space.  The recvfrom
     * handlers can assume that msg.msg_name is either NULL or has
     * a minimum size of sizeof(struct sockaddr_storage).
     */
    int        (*recvmsg)   (struct socket *sock, struct msghdr *m,
                      size_t total_len, int flags);
    int        (*mmap)         (struct file *file, struct socket *sock,
                      struct vm_area_struct * vma);
    ssize_t        (*sendpage)  (struct socket *sock, struct page *page,
                      int offset, size_t size, int flags);
    ssize_t     (*splice_read)(struct socket *sock,  loff_t *ppos,
                       struct pipe_inode_info *pipe, size_t len, unsigned int flags);
    int        (*set_peek_off)(struct sock *sk, int val);
    int        (*peek_len)(struct socket *sock);

    /* The following functions are called internally by kernel with
     * sock lock already held.
     */
    int        (*read_sock)(struct sock *sk, read_descriptor_t *desc,
                     sk_read_actor_t recv_actor);
    int        (*sendpage_locked)(struct sock *sk, struct page *page,
                       int offset, size_t size, int flags);
    int        (*sendmsg_locked)(struct sock *sk, struct msghdr *msg,
                      size_t size);
    int        (*set_rcvlowat)(struct sock *sk, int val);
};

在上次是实验中,已经追踪到bind函数之后,在进行安全检查之后,调用了ops结构体中的listen指针指向的方法在经过单步调试之后,

ops结构体中间的各个函数指针指向了net/ipv4/af_inet文件,我们接下来去找找这个文件中的各个方法

对该文件中各个方法打上断点

按照断点运行,可得过程如下:

上图中断点运行步骤展示了服务器端的函数执行顺序如下:

Inet_create->inet_bind ->inet_listen ->accept

因此TCP初始化过程大部分都是在create函数中完成的:

static int inet_create(struct net *net, struct socket *sock, int protocol,
               int kern)
{
    struct sock *sk;
    struct inet_protosw *answer;
    struct inet_sock *inet;
    struct proto *answer_prot;
    unsigned char answer_flags;
    int try_loading_module = 0;
    int err;

    if (protocol < 0 || protocol >= IPPROTO_MAX)
        return -EINVAL;

    sock->state = SS_UNCONNECTED;

    /* Look for the requested type/protocol pair. */
lookup_protocol:
    err = -ESOCKTNOSUPPORT;
    rcu_read_lock();
    list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

        err = 0;
        /* Check the non-wild match. */
        if (protocol == answer->protocol) {
            if (protocol != IPPROTO_IP)
                break;
        } else {
            /* Check for the two wild cases. */
            if (IPPROTO_IP == protocol) {
                protocol = answer->protocol;
                break;
            }
            if (IPPROTO_IP == answer->protocol)
                break;
        }
        err = -EPROTONOSUPPORT;
    }

    if (unlikely(err)) {
        if (try_loading_module < 2) {
            rcu_read_unlock();
            /*
             * Be more specific, e.g. net-pf-2-proto-132-type-1
             * (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
             */
            if (++try_loading_module == 1)
                request_module("net-pf-%d-proto-%d-type-%d",
                           PF_INET, protocol, sock->type);
            /*
             * Fall back to generic, e.g. net-pf-2-proto-132
             * (net-pf-PF_INET-proto-IPPROTO_SCTP)
             */
            else
                request_module("net-pf-%d-proto-%d",
                           PF_INET, protocol);
            goto lookup_protocol;
        } else
            goto out_rcu_unlock;
    }

    err = -EPERM;
    if (sock->type == SOCK_RAW && !kern &&
        !ns_capable(net->user_ns, CAP_NET_RAW))
        goto out_rcu_unlock;

    sock->ops = answer->ops;
    answer_prot = answer->prot;
    answer_flags = answer->flags;
    rcu_read_unlock();

    WARN_ON(!answer_prot->slab);

    err = -ENOBUFS;
    sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
    if (!sk)
        goto out;

    err = 0;
    if (INET_PROTOSW_REUSE & answer_flags)
        sk->sk_reuse = SK_CAN_REUSE;

    inet = inet_sk(sk);
    inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

    inet->nodefrag = 0;

    if (SOCK_RAW == sock->type) {
        inet->inet_num = protocol;
        if (IPPROTO_RAW == protocol)
            inet->hdrincl = 1;
    }

    if (net->ipv4.sysctl_ip_no_pmtu_disc)
        inet->pmtudisc = IP_PMTUDISC_DONT;
    else
        inet->pmtudisc = IP_PMTUDISC_WANT;

    inet->inet_id = 0;

    sock_init_data(sock, sk);

    sk->sk_destruct       = inet_sock_destruct;
    sk->sk_protocol       = protocol;
    sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;

    inet->uc_ttl    = -1;
    inet->mc_loop    = 1;
    inet->mc_ttl    = 1;
    inet->mc_all    = 1;
    inet->mc_index    = 0;
    inet->mc_list    = NULL;
    inet->rcv_tos    = 0;

    sk_refcnt_debug_inc(sk);

    if (inet->inet_num) {
        /* It assumes that any protocol which allows
         * the user to assign a number at socket
         * creation time automatically
         * shares.
         */
        inet->inet_sport = htons(inet->inet_num);
        /* Add to protocol hash chains. */
        err = sk->sk_prot->hash(sk);
        if (err) {
            sk_common_release(sk);
            goto out;
        }
    }

    if (sk->sk_prot->init) {
        err = sk->sk_prot->init(sk);
        if (err) {
            sk_common_release(sk);
            goto out;
        }
    }

    if (!kern) {
        err = BPF_CGROUP_RUN_PROG_INET_SOCK(sk);
        if (err) {
            sk_common_release(sk);
            goto out;
        }
    }
out:
    return err;
out_rcu_unlock:
    rcu_read_unlock();
    goto out;
}

那么Linux的 TCP协议中又是通过什么来切换状态的呢?

在逐步跟踪之后,我们找到了如下状态切换函数

tcp_set_state(sk, TCP_SYN_SENT);
    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;

 tcp_set_state(sk, TCP_SYN_SENT)把套接字的状态从CLOSE切换到SYN_SENT,并进一步调用了 tcp_connect(sk)来实际构造SYN并发送出去。然后调用inet_hash_connect(&tcp_death_row, sk)和

ip_route_newports(fl4, rt, orig_sport, orig_dport, inet->inet_sport, inet->inet_dport, sk),为套接字绑定一个端口,并记录在TCP的哈希表中。

  总的来说,整个过程的函数调用关系是这样的:

  tcp_v4_connect -> tcp_connect_init -> tcp_transmit_skb -> icsk->icsk_af_ops->send_check (tcp_v4_send_check)-> icsk->icsk_af_ops->queue_xmit (ip_queue_xmit)-> inet_csk_reset_xmit_timer

总体来说TCP的初始化过程大概就是如上文所示了,最后放一张TCP状态转换图

原文地址:https://www.cnblogs.com/AmosYang6814/p/12103715.html

时间: 2024-08-01 10:08:52

深入Linux系统追踪TCP初始化的相关文章

linux系统初始化--&#8203;配置主机网络

配置主机网络 安装完成linux 系统后,我们需要为主机配置一个可用的网络地址 下面以配置eth0接口为例来说明下 说明 在RHEL7中重新定义了Linux网卡的命名规则,不再遵循原来的eth[0,1,--].下面的例子中全部使用RHEL6的示例. 关于RHEL7的网卡命名原则 在进行centos7的pxe安装测试中发现了网卡命名不再遵从来有的命名规则而启用了最新的命名方式,官网文档描述如下: 在 Red Hat Enterprise Linux 7 中,systemd 和 udevd 支持大量

详解linux系统的启动过程及系统初始化

一.linux系统的启动流程 关于linux系统的启动流程我们可以按步进行划分为如下: POST加电自检 -->BIOS(Boot Sequence)-->加载对应引导上的MBR(bootloader)-->主引导设置加载其 BootLoader-->Kernel初始化-->initrd—>/etc/init进程加载/etc/inittab,其进程流 程图如下: 二.剖析详细启动过程 ⑴. POST开机自检:电脑主机打开电源的时候,随后会听到滴的一声,系统启动开始了开机

TCP/IP 协议工作原理与Linux系统下调优

TCP/IP建立连接的三次握手过程: 建立TCP连接共需要三个packet Client--> syn=1,ack=0,fin=0 -->  Server Client<--  syn=1,ack=1,fin=0 <--  Server Client-->  syn=0,ack=1,fin=0 -->  Server TCP/IP关闭连接的四个过程: 关闭TCP连接需要四个packet: Client-->  FIN  -->  Server Client&

linux系统初始化& 优化总结文档

16条优化汇总 1-不用root管理,以普通用户的名义通过sudo授权管理 2-更改默认的远程连接ssh服务端口,22-->55113,禁止root用户远程连接,只监听内网ip(使用vpn,视情况而定) 3-定时自动更新服务器时间,使其和互联网时间同步 4-配置yum更新源,从国外更新源下载安装软件包 5-关闭selinux及防火墙(生产环境中,如果使用了外部ip一定要调整防火墙) 6-调整文件描述符的数量,进程及文件的打开都会消耗文件描述符 7-定时自动清理邮件目录垃圾文件,防止innodes

青蛙学Linux&mdash;系统初始化init及运行级

1.什么是init和运行级 1.1.init Linux系统的启动首先从BIOS开始,接下来Linux的引导程序将内核映像加载到内存,进行内核初始化.在内核初始化的最后一步,就是启动PID为1的init进程,这个进程是系统的第一个进程,负责启动那些开机时需要启动的服务. 大多数的Linux发行版的init系统都是和System V兼容的,所以也被称为sysinit,这是最早也是最流行的init系统.sysinit概念简单清晰,主要依赖于shell脚本.它一次一个串行启动进程,导致了它的致命缺点,

一键linux系统初始化脚本

一.前言一般我们在安装新的系统时,系统的一些默认配置对我们来说是不行的,所以我们要自定义初始化系统. 二.需求1)设置时区并把同步时间加入计划任务2)禁用selinux3)清空原防火墙默认策略只保留ssh4)历史命令显示操作时间级用户5)创建ALL权限用户并禁止root远程登录6)禁止定时任务发送邮件7)设置最大打开文件数8)减少swap使用9)系统内核参数优化10)安装系统性能分析工具及其他常常用工具注意:安装新系统最好最小化安装. 三.脚本正文#!/bin/bash #设置时区并同步时间ln

TCP协议,UDP协议,Utp,双绞线,DHCP协议,子网掩码,LAN,VLAN,网口,服务器,UI设计,Linux系统,Unix系统,名词解释

TCP协议: TCP:Transmission Control Protocol 传输控制协议TCP是一种面向连接(连接导向)的.可靠的.基于字节流的运输层(Transport layer)通信协议,由IETF的RFC 793说明(specified).在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,UDP是同一层内另一个重要的传输协议. UDP协议: UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Int

linux系统初始化--&#8203;-调整linux文件系统

调整linux文件系统 安装系统时,我们面临的问题就是磁盘的划分,当然linux很早就明白这点,所以有了LVM卷,下面我们将开始讨论和了解LVM卷的相关内容.除此之外,我们后期会讨论下存储相关的信息,这里不再讨论.当然对于磁盘的其它相关知识这里不再赘述,优化相关的内容,我们后面再扯,这里仅仅去介绍如何为平台建设去调整磁盘.并且以LVM为主. 文件系统简述对比 文件系统 分区大小 单文件大小 最大目录数 其它特性 备注 Ext3 16TB 2TB 32000 高可用性.数据完整性比ext2好,支持

linux系统日常管理

以下资料来源于<跟阿铭学Linux> 1.监控当前系统状态 1. w查看当前系统的负载 [[email protected] sbin]# w 15:23:46 up 3:34, 2 users, load average: 0.03, 0.05, 0.00 USER TTY FROM [email protected] IDLE JCPU PCPU WHAT root tty1 - 12:26 2:55m 0.11s 0.11s -bash root pts/0 10.72.137.53 1