深入理解TCP协议及其源代码

一、TCP的三次握手

在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接:

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认,SYN为同步序列编号;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据

三次握手的过程如下图所示:

                                                                               

二、跟踪分析三次握手

在lab3目录下执行以下命令,在qemu中启动gdb server:

qemu -kernel ../../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append  nokaslr -s

注意此处是小写的"s"

重开一个终端依次执行下面的命令:

gdb
file ../../linux-5.0.1/vmlinux
target remote:1234
b __sys_socket
b __sys_bind
b __sys_connect
b __sys_listen
b __sys_accept4

对应在gdb中为: 

在qumu中输入replyhi命令并执行,然后在gdb中不断输入“c”使得程序继续执行,直到qemu不再处于阻塞状态。再输入hello命令重复上面的过程。我们可以看到gdb中的输出信息:

由终端输出的断点可知,执行顺序为1,2,4,5,1,3,5,对应前面打断点的次序,我们在到socket.c中找到对应的代码,分析程序的执行过程:

 ①__sys_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;
    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);
    if (retval < 0)
        return retval;

    return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}
__sys_socket函数创建一个socket描述符,它唯一标识一个socket

②__sys_bind
int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        err = move_addr_to_kernel(umyaddr, addrlen, &address);
        if (!err) {
            err = security_socket_bind(sock,
                           (struct sockaddr *)&address,
                           addrlen);
            if (!err)
                err = sock->ops->bind(sock,
                              (struct sockaddr *)
                              &address, addrlen);
        }
        fput_light(sock->file, fput_needed);
    }
    return err;
}
__sys_bind指定本地地址。

③__sys_connect
int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;
    err = move_addr_to_kernel(uservaddr, addrlen, &address);
    if (err < 0)
        goto out_put;

    err =
        security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
    if (err)
        goto out_put;

    err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
                 sock->file->f_flags);
out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}
调用__sys_connect()构造一个与目的地的TCP连接,并在不能构造连接时返回一个差错代码。

④__sys_listen
int __sys_listen(int fd, int backlog)
{
    struct socket *sock;
    int err, fput_needed;
    int somaxconn;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
        if ((unsigned int)backlog > somaxconn)
            backlog = somaxconn;

        err = security_socket_listen(sock, backlog);
        if (!err)
            err = sock->ops->listen(sock, backlog);

        fput_light(sock->file, fput_needed);
    }
    return err;
}
__sys_listen设置等待连接状态。对于一个服务器的程序,当申请到套接字,并调用__sys_bind与本地地址绑定后,就应该等待某个客户机的程序来要求连接。

⑤__sys_accept4
int __sys_accept4(int fd, struct sockaddr __user *upeer_sockaddr,
          int __user *upeer_addrlen, int flags)
{
    struct socket *sock, *newsock;
    struct file *newfile;
    int err, len, newfd, fput_needed;
    struct sockaddr_storage address;

    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
        return -EINVAL;

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

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    err = -ENFILE;
    newsock = sock_alloc();
    if (!newsock)
        goto out_put;

    newsock->type = sock->type;
    newsock->ops = sock->ops;

    /*
     * We don't need try_module_get here, as the listening socket (sock)
     * has the protocol module (sock->ops->owner) held.
     */
    __module_get(newsock->ops->owner);

    newfd = get_unused_fd_flags(flags);
    if (unlikely(newfd < 0)) {
        err = newfd;
        sock_release(newsock);
        goto out_put;
    }
    newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
    if (IS_ERR(newfile)) {
        err = PTR_ERR(newfile);
        put_unused_fd(newfd);
        goto out_put;
    }

    err = security_socket_accept(sock, newsock);
    if (err)
        goto out_fd;

    err = sock->ops->accept(sock, newsock, sock->file->f_flags, false);
    if (err < 0)
        goto out_fd;

    if (upeer_sockaddr) {
        len = newsock->ops->getname(newsock,
                    (struct sockaddr *)&address, 2);
        if (len < 0) {
            err = -ECONNABORTED;
            goto out_fd;
        }
        err = move_addr_to_user(&address,
                    len, upeer_sockaddr, upeer_addrlen);
        if (err < 0)
            goto out_fd;
    }

    /* File flags are not inherited via accept() unlike another OSes. */

    fd_install(newfd, newfile);
    err = newfd;

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
out_fd:
    fput(newfile);
    put_unused_fd(newfd);
    goto out_put;
}

__sys_accept4接受连接请求。服务器进程使用系统调用socket,bind和listen创建一个套接字,将它绑定到知名的端口,并指定连接请求的队列长度。然后,服务器调用Accept进入等待状态,直到到达一个连接请求。

 

由上我们可以看出服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen)。接下来就是三次握手的过程:客户端初始化一个Socket,然后连接服务器,这是第一次握手;服务器接收到连接请求,返回信息给客户端,这是第二次握手;客户端收到服务器的确认信息,再向服务器发送连接成功信息,这是第三次握手。

 

posted @ 2019-12-26 22:33  Xpeng2333  阅读(170)  评论(0编辑  收藏  举报