zoukankan      html  css  js  c++  java
  • Socket层实现系列 — bind()的实现(一)

    bind()函数的使用方法很简单,但是它是怎么实现的呢?

    笔者从应用层出发,沿着网络协议栈,分析了bind()的系统调用、Socket层实现,以及它的TCP层实现。

    本文主要内容:bind()的系统调用、bind()的Socket层实现。

    内核版本:3.6

    Author:zhangskd @ csdn blog

    应用层

    int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

    bind() gives the socket sockfd the local address my_addr.

    给socket描述符绑定IP和端口,一般服务器才需要。

    也可交给系统来选择:

    my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口 */

    my_addr.sin_addr.s_addr = INADDR_ANY; /* 自动填入本机的IP地址 */

    #define INADDR_ANY ((unsigned long int) 0x00000000)

    端口号的范围为0 ~ 65535。

    调用bind()时,一般不要把端口号置为小于1024的值,因为1到1023是保留端口号。

    系统调用

    bind()是由glibc提供的,声明位于include/sys/socket.h中,实现位于sysdeps/mach/hurd/bind.c中,

    主要是用来从用户空间进入名为sys_socketcall的系统调用,并传递参数。sys_scoketcall()实际上是

    所有socket函数进入内核空间的共同入口。

    在sys_socketcall()中会调用sys_bind()。

    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
    {
        ...
        switch(call) {
            ...
            case SYS_BIND:
                err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
                break;
            ...
        }
       return err;
    }
    

    经过了socket层的总入口sys_socketcall(),现在进入sys_bind()。

    /*
     * Bind a name to a socket. Nothing much to do here since it's the protocol's responsibility
     * to handle the local address.
     * We move the socket address to kernel space before we call the protocol layer (having also
     * checked the address is ok).
     */
    
    SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
    {
        struct socket *sock;
        struct sockaddr_storage address;
        int err, fput_needed;
    
        /* 通过文件描述符fd,找到对应的socket。
         * 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
         * 然后从file实例的private_data成员中获取socket实例。
         */
        sock = sockfd_lookup_light(fd, &err, &fput_needed);
    
        if (sock) {
            /* 把用户空间的地址复制到内核空间,成功返回0 */
            err = move_addr_to_kernel(umyaddr, addrlen, &address);
    
            if (err >= 0) {
                /* SELInux相关 */
                err = security_socket_bind(sock, (struct sockaddr *)&address, addrlen); 
                if (!err)
                    /* socket层的操作函数集。如果是SOCK_STREAM的话,proto_ops是inet_stream_ops,
                     * 接下来调用的是inet_bind()。
                     */
                    err = sock->ops->bind(sock, (struct sockaddr *)&address, addrlen); 
            }
            fput_light(sock->file, fput_needed);
        }
        return err;
    }
    

    通过文件描述符,找到对应的file结构。

    static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
    {
        struct file *file;
        struct socket *sock;
    
        *err = -EBADF; /* Bad file number */
    
        /* 从当前进程的files_struct中找到网络文件系统中的file指针,并增加它的引用计数 */
        file = fget_light(fd, fput_needed);
    
        if (file) {
            sock = sock_from_file(file, err); /* 通过file找到对应的socket */
            if (sock)
                return sock;
            fput_light(file, *fput_needed); /* 失败的话减少file的引用计数 */
        }
        return NULL;
    }
    

    通过file结构,找到对应的socket结构。

    struct socket *sock_from_file(struct file *file, int *err)
    {
        if (file->f_op == &socket_file_ops) /* 说明此file对应一个socket */
            return file->private_data; /* set in sock_map_fd */
    
        *err = -ENOTSOCK;
        return NULL;
    }
    

    把用户空间的socket地址复制到内核空间,同时检查是否合法,成功返回0。

    int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr)
    {
        if (ulen < 0 || ulen > sizeof(struct sockaddr_storage)) /* socket地址长度是否合法 */
            return -EINVAL;
    
        if (ulen == 0)
            return 0;
    
        if (copy_from_user(kaddr, uaddr, ulen))
            return -EFAULT; /* socket地址是否合法 */
    
        return audit_sockaddr(ulen, kaddr);
    }
    

    socket层

    SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中绑定函数为inet_bind()。

    const struct proto_ops inet_stream_ops = {
        .family = PF_INET,
        .owner = THIS_MODULE,
        ...
        .bind = inet_bind, /* socket层的bind实现 */
        ...
    }
    

    socket层做的主要事情为合法性检查、绑定IP地址,而真正的端口绑定是在TCP层进行的。

    int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
    {
        struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
        struct sock *sk = sock->sk; /* 传输层实例 */
        struct inet_sock *inet = inet_sk(sk); /* INET实例 */
        unsigned short snum; /* 要绑定的端口 */
        int chk_addr_ret; /* IP地址类型 */
        int err;
    
        /* If the socket has its own bind function then use it. (RAW)
         * 用于原始套接字,TCP协议实例tcp_prot不含此函数指针。
         */
        if (sk->sk_prot->bind) {
            err = sk->sk_prot->bind(sk, uaddr, addr_len);
            goto out;
        }
    
        err = -EINVAL;
    
        if (addr_len < sizeof(struct sockaddr_in)) /* socket地址长度错误 */
            goto out;
    
        if (addr->sin_family != AF_INET) { /* 非INET协议族 */
            /* Compatibility games: accept AF_UNSPEC (mapped to AF_INET)
             * only if s_addr is INADDR_ANY.
             */
            err = -EAFNOSUPPORT;
            if (addr->sin_family != AF_UNSPEC || addr->sin_addr.s_addr != htonl(INADDR_ANY))
                goto out;
        }     
     
        /* 在路由中检查IP地址类型,单播、多播还是广播 */
        chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);
     
        /* Not specified by any standard per-se, however it breaks too many applications
         * when removed. It is unfortunate since allowing applications to make a non-local
         * bind solves several problems with systems using dynamic addressing.
         * (ie. your servers still start up even if your ISDN link is temporarily down)
         */
         /* sysctl_ip_nonlocal_bind表示是否允许绑定非本地的IP地址。
         * inet->freebind表示是否允许绑定非主机地址。
         * 这里需要允许绑定非本地地址,除非是发送给自己、多播或广播。
         */
        err = -EADDRNOTAVAIL; /* Cannot assign requested address */
    
        if (! sysctl_ip_nonlocal_bind && ! (inet->freebind || inet->transparent) &&
            addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
            chk_addr_ret != RTN_LOCAL && chk_addr_ret != RTN_MULTICAST &&
            chk_addr_ret != RTN_BROADCAST)
            goto out;
     
        snum = ntohs(addr->sin_port); /* 要绑定的端口 */
    
        err = -EACCES; /* Permission denied */
        /* snum为0表示让系统随机选择一个未使用的端口,因此是合法的。
        * 如要需要绑定的端口为1 ~ 1023,则需要对应的特权。
        */
        if (snum && snum < PORT_SOCK && ! capable(CAP_NET_BIND_SERVICE))
            goto out;
    
        lock_sock(sk);
       
        /* Check these errors (active socket, double bind). 
         * 如果套接字不在初始状态TCP_CLOSE,或者已经绑定端口了,则出错。
         * 一个socket最多可以绑定一个端口,而一个端口则可能被多个socket共用。
         */
        err = -EINVAL;
        if (sk->sk_state != TCP_CLOSE || inet->inet_num)     
            goto out_release_sock;
    
        /* We keep a pair of addresses. rcv_saddr is the one used by hash lookups,
         * and saddr is used for transmit.
         * In the BSD API these are the same except where it would be illegal to use them
         * (multicast/broadcast) in which case the sending device address is used.
         */
        inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; /* 绑定地址 */
    
        if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
            inet->inet_saddr = 0; /* Use device */
        
        /* Make sure we are allowed to bind here.
         * 如果使用的是TCP,则sk_prot为tcp_prot,get_port为inet_csk_get_port()
         * 端口可用的话返回0。
         */
        if (sk->sk_prot->get_port(sk, snum)) {
            inet->inet_saddr = inet->inet_rcv_saddr = 0;
            err = -EADDRINUSE;
            goto out_release_sock;
        }
     
        /* inet_rcv_saddr表示绑定的地址,接收数据时用于查找socket */
        if (inet->inet_rcv_saddr)
            sk->sk_userlocks |= SOCK_BINDADDR_LOCK; /* 表示绑定了本地地址 */
    
        if (snum)
            sk->sk_userlocks |= SOCK_BINDPORT_LOCK; /* 表示绑定了本地端口 */
    
        inet->inet_sport = htons(inet->inet_num); /* 绑定端口 */
        inet->inet_daddr = 0;
        inet->inet_dport = 0;
        sk_dst_reset(sk);
        err = 0;
    
    out_release_sock:
        release_sock(sk);
    
    out:
        return err;
    }
    
    /* Sockets 0 - 1023 can't be bound to unless you are superuser */
    #define PORT_SOCK 1024
    /* Allows binding to TCP/UDP sockets below 1024 */
    #define CAP_NET_BIND_SERVICE 10
    
    


     

  • 相关阅读:
    KETTLE:mongdb与mysql互传数据
    6.azkban的监控
    5.azkaban权限管理
    3.azkaban3.0测试
    2.azkaban3.0安装
    1.编译azkaban
    十六:The YARN Service Registry
    十四:Using CGroups with YARN
    十二:NodeManager
    十 Writing YARN Applications
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333323.html
Copyright © 2011-2022 走看看