分类
协议栈分析

TCP socket 创建

IPV4的TCP socket 创建和其他IPV4 socket 创建在调用inet_create之前流程都是一样的,直到在inet_create函数中,才会调用4层协议特有的init函数。
创建socket的系统调用始于SYSCALL_DEFINE3

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
        return __sys_socket(family, type, protocol); //work function family 3层协议类型。type套接字类型,protocol 四层协议类型。
}

其中__sys_socket函数的作用主要是创建socket,并给socket分配文件描述符

int __sys_socket(int family, int type, int protocol)
{
        int retval;
        struct socket *sock;
        int flags;

        /* Check the SOCK_* constants for consistency.  */
        BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
        BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
        BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
        BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

        flags = type & ~SOCK_TYPE_MASK;  //type除了传递socket类型还可以传递socket flags
        if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
                return -EINVAL;
        type &= SOCK_TYPE_MASK;

        if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
                flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

        retval = sock_create(family, type, protocol, &sock); //创建socket 主函数
        if (retval < 0)
                return retval;

        return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); //将socket映射成一个文件描述符。通过文件的读写操作来操作套接字。
}

sock_create函数稍微复杂 后续分解。先分解 sock_map_fd函数

struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
{
        struct file *file;

        if (!dname)
                dname = sock->sk ? sock->sk->sk_prot_creator->name : "";

        file = alloc_file_pseudo(SOCK_INODE(sock), sock_mnt, dname,
                                O_RDWR | (flags & O_NONBLOCK),
                                &socket_file_ops); //创建文件结构,操作函数是socket_file_ops
        if (IS_ERR(file)) {
                sock_release(sock);
                return file;
        }

        sock->file = file;  //sock file 结构为该文件
        file->private_data = sock; //file的private_data指向sock。后续读写文件实质上操作 这个socket。具体查看socket_file_ops对应的函数。
        return file;
}
EXPORT_SYMBOL(sock_alloc_file);

static int sock_map_fd(struct socket *sock, int flags)
{
        struct file *newfile;
        int fd = get_unused_fd_flags(flags); // 获取空闲的文件描述符
        if (unlikely(fd < 0)) {
                sock_release(sock);
                return fd;
        }

        newfile = sock_alloc_file(sock, flags, NULL); //创建文件结构,上面的函数
        if (likely(!IS_ERR(newfile))) {
                fd_install(fd, newfile);  //将文件描述符和文件关联
                return fd;   //返回文件描述符
        }

        put_unused_fd(fd);
        return PTR_ERR(newfile);
}

sock_create函数最后调用__sock_create函数创建socket。__sock_create函数做了一些检查,创建 socket结构后,查找三层协议。最后调用三层协议的create函数,这个函数的过程简单,这里就不分析,对应IPV4 TCP套接字最后调用的是inet_create函数。
inet_create函数较长,前面部分主要是根据类型和协议查找四层协议的inet_protosw结构。下面列出该函数的关键代码

        sock->ops = answer->ops;            //type的操作函数
        answer_prot = answer->prot;         //proto结构体,创建sk使用
        answer_flags = answer->flags;       //四层协议的flags
        rcu_read_unlock();

        WARN_ON(!answer_prot->slab);

        err = -ENOBUFS;
        sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern); //创建sk,这个函数很简单,就是创建sk,并将answer_prot赋值到sk的sk_prot变量中。
        if (!sk)
                goto out;
......
        if (sk->sk_prot->init) {  //判断四层协议是否有init函数
                err = sk->sk_prot->init(sk); //调用init函数,TCP中调用的是tcp_v4_init_sock函数 
                if (err) {
                        sk_common_release(sk);
                        goto out;
                }
        }

tcp_v4_init_sock函数只是对tcp_init_sock函数进行封装,并赋值icsk_af_ops为ipv4特有的操作。这个结构主要是tcp在ipv4协议中的一些特定操作(发包 socket option 创建ip头等 ),ipv6中也有一个类似的结构。
tcp_init_sock函数才是tcp socket 初始化的核心函数。代码如下

void tcp_init_sock(struct sock *sk)
{
        struct inet_connection_sock *icsk = inet_csk(sk); //初始化 icsk 套接字
        struct tcp_sock *tp = tcp_sk(sk);   //初始化tcp套接字

        tp->out_of_order_queue = RB_ROOT;
        sk->tcp_rtx_queue = RB_ROOT;
        tcp_init_xmit_timers(sk);    //初始化tcp套接字定时器,retran delay_ack keepalive等 
        INIT_LIST_HEAD(&tp->tsq_node);
        INIT_LIST_HEAD(&tp->tsorted_sent_queue);

        icsk->icsk_rto = TCP_TIMEOUT_INIT;
        tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT);
        minmax_reset(&tp->rtt_min, tcp_jiffies32, ~0U);

        /* So many TCP implementations out there (incorrectly) count the
         * initial SYN frame in their delayed-ACK and congestion control
         * algorithms that we must have the following bandaid to talk
         * efficiently to them.  -DaveM
         */
        tp->snd_cwnd = TCP_INIT_CWND; //tcp默认发送窗口 居然是10 好小 

        /* There's a bubble in the pipe until at least the first ACK. */
        tp->app_limited = ~0U;

        /* See draft-stevens-tcpca-spec-01 for discussion of the
         * initialization of these values.
         */
        tp->snd_ssthresh = TCP_INFINITE_SSTHRESH;
        tp->snd_cwnd_clamp = ~0;
        tp->mss_cache = TCP_MSS_DEFAULT;

        tp->reordering = sock_net(sk)->ipv4.sysctl_tcp_reordering;
        tcp_assign_congestion_control(sk);  //这个是拥塞算法的初始化函数

        tp->tsoffset = 0;
        tp->rack.reo_wnd_steps = 1;

        sk->sk_state = TCP_CLOSE;       //创建socket的初始状态 TCP_CLOSE状态

        sk->sk_write_space = sk_stream_write_space; //唤醒阻塞在socket上的写进程
        sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);

        icsk->icsk_sync_mss = tcp_sync_mss;  //协商 mss 的函数 通过pmtu获取貌似

        sk->sk_sndbuf = sock_net(sk)->ipv4.sysctl_tcp_wmem[1]; //这个应该是发送buf
        sk->sk_rcvbuf = sock_net(sk)->ipv4.sysctl_tcp_rmem[1]; //这个应该是接受buf

        sk_sockets_allocated_inc(sk);  //貌似是做统计用的
        sk->sk_route_forced_caps = NETIF_F_GSO; //暂时不知道干嘛用的 ,大概跟GSO有关系
}
EXPORT_SYMBOL(tcp_init_sock); 

tcp_assign_congestion_control函数是拥塞控制算法的初始化函数,这个里面会选取默认的拥塞控制算法并初始化。代码如下

void tcp_assign_congestion_control(struct sock *sk)
{
        struct net *net = sock_net(sk);
        struct inet_connection_sock *icsk = inet_csk(sk);
        const struct tcp_congestion_ops *ca;

        rcu_read_lock();
        ca = rcu_dereference(net->ipv4.tcp_congestion_control);
        if (unlikely(!try_module_get(ca->owner)))
                ca = &tcp_reno;
        icsk->icsk_ca_ops = ca;   //直接赋值默认拥塞控制算法的结构
        rcu_read_unlock();

        memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv));
        if (ca->flags & TCP_CONG_NEEDS_ECN)
                INET_ECN_xmit(sk);
        else
                INET_ECN_dontxmit(sk);
}

到这里,TCP socket 的创建就完事,接下来,有两种用法,作为客户端进行connect。 作为服务器端 bind 本地端口后进行listen。 connect很有意思的地方在于,刚发出去syn包,本地就认为这个链接是establish得了。这个会下回分解。

分类
协议栈分析

IP/TCP 协议栈解析 —- 初始化

tcp 是4层协议,向上通过socket与用户态程序交互,向下通过接口函数与3层协议通讯(ip,ipv6)。可能由于历史原因,tcp协议栈和ip协议栈在目录上做的不是很模块化,其实在ipv4目录下的tcp文件很大一部分代码都是和ipv6的tcp共享的。而且很奇怪的是,4层协议栈和3层协议栈之间完全可以做成模块结构,但是tcp和ip在初始化的时候代码完全杂糅在一起。在这一方面,ipv6的初始化反而更清晰。
IP初始函数
由于ip协议对应的tcp协议栈和ip协议初始化代码没有分离,因此,tcp协议的初始化函数在ip 初始化函数里面。函数是 net/ipv4/af_inet.c中的inet_init函数。

static int __init inet_init(void)
{
        struct inet_protosw *q;
        struct list_head *r;
        int rc = -EINVAL;

        sock_skb_cb_check_size(sizeof(struct inet_skb_parm));

        rc = proto_register(&tcp_prot, 1);//这个函数做一些初始化,创建memcache,并注册到socket的一个全局表中,后续看在主要的发包流程中不会用到这个全局表。看上去这个表主要是在proc文件中显示当前系统中有多少个socket协议
        if (rc)
                goto out;

        rc = proto_register(&udp_prot, 1);
        if (rc)
                goto out_unregister_tcp_proto;

        rc = proto_register(&raw_prot, 1);
        if (rc)
                goto out_unregister_udp_proto;

        rc = proto_register(&ping_prot, 1);
        if (rc)
                goto out_unregister_raw_proto;

        /*
         *      Tell SOCKET that we are alive...
         */

        (void)sock_register(&inet_family_ops);//注册ip协议族,socket协同调用的时候指定的domain。就是在选协议族。

#ifdef CONFIG_SYSCTL
        ip_static_sysctl_init();
#endif

        /*
         *      Add all the base protocols.
         */

        if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
                pr_crit("%s: Cannot add ICMP protocol\n", __func__);
        if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
                pr_crit("%s: Cannot add UDP protocol\n", __func__);
        if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) //注册tcp收包结构,结构在下文贴出
                pr_crit("%s: Cannot add TCP protocol\n", __func__);
#ifdef CONFIG_IP_MULTICAST
        if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
                pr_crit("%s: Cannot add IGMP protocol\n", __func__);
#endif

        /* Register the socket-side information for inet_create. */
        for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
                INIT_LIST_HEAD(r);

        for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
                inet_register_protosw(q); //注册四层协议结构。用户态程序创建socket第二个和第三个参数 type 和protol,就是用来筛选这个结构的。 

tcp 接受ip层数据接口

static struct net_protocol tcp_protocol = {
        .early_demux    =       tcp_v4_early_demux,
        .early_demux_handler =  tcp_v4_early_demux, 
        .handler        =       tcp_v4_rcv,  //这个是正常流程的入口函数
        .err_handler    =       tcp_v4_err,  //这个是出错是入口函数
        .no_policy      =       1, 
        .netns_ok       =       1,
        .icmp_strict_tag_validation = 1,
};

tcp inetsw_array结构

        {
                .type =       SOCK_STREAM,    //流类型
                .protocol =   IPPROTO_TCP,    //协议类型
                .prot =       &tcp_prot,      // 协议的结构
                .ops =        &inet_stream_ops,// 流操作函数
                .flags =      INET_PROTOSW_PERMANENT |
                              INET_PROTOSW_ICSK,
        },

在linux中,对四层协议栈又做了一次分类,同类型的协议抽象了一层操作函数,然后在操作函数中在调用各个具体协议的操作函数。当前分类一共有以下几种

enum sock_type {
        SOCK_STREAM     = 1, //流类型,有链接
        SOCK_DGRAM      = 2, //数据包包类型无连接
        SOCK_RAW        = 3, //raw socket类型直接发包
        SOCK_RDM        = 4, //可靠连接类型,这个类型貌似就是RDM
        SOCK_SEQPACKET  = 5, //顺序包类型
        SOCK_DCCP       = 6, //数据包拥塞控制类型
        SOCK_PACKET     = 10, //直接从dev获取包的类型,linux特有。
};

tcp prot 结构,这个结构 太过复杂,现在只是搞懂了几个用的函数在哪里调用。剩下的需要慢慢搞懂。

struct proto tcp_prot = {
        .name                   = "TCP",
        .owner                  = THIS_MODULE,
        .close                  = tcp_close,
        .pre_connect            = tcp_v4_pre_connect,   //创建连接前调用
        .connect                = tcp_v4_connect,       //创建连接时调用,syn包
        .disconnect             = tcp_disconnect,       //断开连接时调用
        .accept                 = inet_csk_accept,    //接受连接时调用
        .ioctl                  = tcp_ioctl,         //ioctl 低啊用
        .init                   = tcp_v4_init_sock,  //创建socket时调用
        .destroy                = tcp_v4_destroy_sock,
        .shutdown               = tcp_shutdown,
        .setsockopt             = tcp_setsockopt,   //设置opt调用
        .getsockopt             = tcp_getsockopt,   //获取opt调用
        .keepalive              = tcp_set_keepalive,
        .recvmsg                = tcp_recvmsg,
        .sendmsg                = tcp_sendmsg,
        .sendpage               = tcp_sendpage,
        .backlog_rcv            = tcp_v4_do_rcv,
        .release_cb             = tcp_release_cb,
        .hash                   = inet_hash,
        .unhash                 = inet_unhash,
        .get_port               = inet_csk_get_port,
        .enter_memory_pressure  = tcp_enter_memory_pressure,
        .leave_memory_pressure  = tcp_leave_memory_pressure,
        .stream_memory_free     = tcp_stream_memory_free,
        .sockets_allocated      = &tcp_sockets_allocated,
        .orphan_count           = &tcp_orphan_count,
        .memory_allocated       = &tcp_memory_allocated,
        .memory_pressure        = &tcp_memory_pressure,
        .sysctl_mem             = sysctl_tcp_mem,
        .sysctl_wmem_offset     = offsetof(struct net, ipv4.sysctl_tcp_wmem),
        .sysctl_rmem_offset     = offsetof(struct net, ipv4.sysctl_tcp_rmem),
        .max_header             = MAX_TCP_HEADER,
        .obj_size               = sizeof(struct tcp_sock), //tcp sock,这个sock里面套了一堆结构,第一个结构是就是sock。注意内核在这做了三次抽象 sock , connection_sock最后是tcp_sock.通过这三次抽象减少重复代码。面向对象啊
        .slab_flags             = SLAB_TYPESAFE_BY_RCU,
        .twsk_prot              = &tcp_timewait_sock_ops,
        .rsk_prot               = &tcp_request_sock_ops,
        .h.hashinfo             = &tcp_hashinfo,
        .no_autobind            = true,
#ifdef CONFIG_COMPAT
        .compat_setsockopt      = compat_tcp_setsockopt,
        .compat_getsockopt      = compat_tcp_getsockopt,
#endif
        .diag_destroy           = tcp_abort,
};
EXPORT_SYMBOL(tcp_prot);

inet_add_protocol函数,很简单就是讲四层协议的收包结构注册到三层协议中,方便调用。使用cmpxchg
inet_register_protosw 函数,注册AF_INET协议栈的协议类型,这个函数也很简单,首先通过根据类型分类,然后在同类型的链表中查找是否有重名显现如果么有则注册。

proto_register 这个函数,跟进去看了一下,主要是做了一些memcache的创建。然后注册到一个全局链表中。它注册的那个链表后续貌似没有用到的地方看代码只有在proc文件显示的时候中用到。
到这里初始化就完成了。下一章将分析tcp socket创建