• 从listen()方法调用一窥Socket与系统调用


    在上一次实验通过内核搭建TCP通信的基础上,让我们来探究socket程序中的系统调用

    1.源码分析(以Replyhi为例)

    int Replyhi()
    {
        char szBuf[MAX_BUF_LEN] = "";
        char szReplyMsg[MAX_BUF_LEN] = "hi";
        InitializeService();
        while (1)
        {
            ServiceStart();
            RecvMsg(szBuf);
            SendMsg(szReplyMsg);
            ServiceStop();
        }
        ShutdownService();
        return 0;
    }

    上述为Replyhi()方法中的源码,可以看见其中调用了如下六个方法: 

     InitializeService();
     ServiceStart();
     RecvMsg();
     SendMsg();
     ServiceStop();
     ShutdownService();
    继续进入这些方法:
    syswrapper.h
    #define
    PrepareSocket(addr,port) int sockfd = -1; struct sockaddr_in serveraddr; struct sockaddr_in clientaddr; socklen_t addr_len = sizeof(struct sockaddr); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(port); serveraddr.sin_addr.s_addr = inet_addr(addr); memset(&serveraddr.sin_zero, 0, 8); sockfd = socket(PF_INET,SOCK_STREAM,0); #define InitServer() int ret = bind( sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)); if(ret == -1) { fprintf(stderr,"Bind Error,%s:%d ", __FILE__,__LINE__); close(sockfd); return -1; } listen(sockfd,MAX_CONNECT_QUEUE); #define InitClient() int ret = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)); if(ret == -1) { fprintf(stderr,"Connect Error,%s:%d ", __FILE__,__LINE__); return -1; } /* public macro */ #define InitializeService() PrepareSocket(IP_ADDR,PORT); InitServer(); #define ShutdownService() close(sockfd); #define OpenRemoteService() PrepareSocket(IP_ADDR,PORT); InitClient(); int newfd = sockfd; #define CloseRemoteService() close(sockfd); #define ServiceStart() int newfd = accept( sockfd, (struct sockaddr *)&clientaddr, &addr_len); if(newfd == -1) { fprintf(stderr,"Accept Error,%s:%d ", __FILE__,__LINE__); } #define ServiceStop() close(newfd); #define RecvMsg(buf) ret = recv(newfd,buf,MAX_BUF_LEN,0); if(ret > 0) { printf("recv "%s" from %s:%d ", buf, (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); } #define SendMsg(buf) ret = send(newfd,buf,strlen(buf),0); if(ret > 0) { printf("send "hi" to %s:%d ", (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); }

    在syswrapper.h文件中我们找到了上述方法的宏定义,其中重点关注在InitializeService中调用的bind方法、listen方法、以及在ServiceStart中调用的acccept方法,

    让我们反编译init文件到其中去找调用过程

     在反编译的代码中,<libc_setup_tls>中,陷入到了内核态,现在通过gdb断点看看在内核态中的运行

     可以看见代码首先调用了__sys_socket方法,,并在其中调用了sock_create方法和sock_map_fd方法,并创建了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_bind方法

    在__sys_bind方法中,首先调用了security_socket_bind()方法,security_socket_bind方法中调用了call_int_hook()方法,保证了bind的方法的去安全性,然后再调用了Socket结构体中的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;
    }
    同样,再bind函数之后,调用了__sys_listen方法,__sys_listen方法和__sys_bind方法中同样,先进行安全检查,再安全检查通过之后调用socket结构体中listen指针指向的方法

     但是socket结构体中的bind指针和listen指针指向什么方法呢?通过·gdb的单步调试,我们得到如下结果:

     在安全检查之后,listen调用了位于net/ipv4/af_inet中的inet_listen方法,

    int inet_listen(struct socket *sock, int backlog)
    {
        struct sock *sk = sock->sk;
        unsigned char old_state;
        int err, tcp_fastopen;
    
        lock_sock(sk);
    
        err = -EINVAL;
        if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
            goto out;
    
        old_state = sk->sk_state;
        if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
            goto out;
    
        sk->sk_max_ack_backlog = backlog;
        /* Really, if the socket is already in listen state
         * we can only allow the backlog to be adjusted.
         */
        if (old_state != TCP_LISTEN) {
            /* Enable TFO w/o requiring TCP_FASTOPEN socket option.
             * Note that only TCP sockets (SOCK_STREAM) will reach here.
             * Also fastopen backlog may already been set via the option
             * because the socket was in TCP_LISTEN state previously but
             * was shutdown() rather than close().
             */
            tcp_fastopen = sock_net(sk)->ipv4.sysctl_tcp_fastopen;
            if ((tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) &&
                (tcp_fastopen & TFO_SERVER_ENABLE) &&
                !inet_csk(sk)->icsk_accept_queue.fastopenq.max_qlen) {
                fastopen_queue_tune(sk, backlog);
                tcp_fastopen_init_key_once(sock_net(sk));
            }
    
            err = inet_csk_listen_start(sk, backlog);
            if (err)
                goto out;
            tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_LISTEN_CB, 0, NULL);
        }
        err = 0;
    
    out:
        release_sock(sk);
        return err;
    }

    在inet_listen方法中对又调用了tcp_call_bpf()方法,通过操作结构体变量进行操作,同理bind方法中,也是同样的调用方式。上述展示了listen()函数从用户态调用底层的全过程,其他socket函数采用的同样的调用方式,在这里就不一一详述了。

  • 相关阅读:
    快速排序
    NDK获取android下sd card id
    NDK 获取android的imei和serial number
    要养成的c++代码编写风格:
    google代码风格(转)
    把ORM封装成一个类(linq to entity)
    【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK SampleSnake详解
    鼠标右键屏蔽
    asp.net生成静态页面
    visual studio快捷键大全
  • 原文地址:https://www.cnblogs.com/AmosYang6814/p/12070575.html
Copyright © 2020-2023  润新知