zoukankan      html  css  js  c++  java
  • 套接字之bind系统调用

    在socket创建成功之后,调用bind函数以完成对指定地址和端口的绑定工作;

    下面详细分析bind相关代码;

     1 /*
     2  *    Bind a name to a socket. Nothing much to do here since it's
     3  *    the protocol's responsibility to handle the local address.
     4  *
     5  *    We move the socket address to kernel space before we call
     6  *    the protocol layer (having also checked the address is ok).
     7  */
     8 
     9 SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
    10 {
    11     struct socket *sock;
    12     struct sockaddr_storage address;
    13     int err, fput_needed;
    14 
    15     /* 获取socket ,fput_need标识是否需要减少文件引用计数*/
    16     sock = sockfd_lookup_light(fd, &err, &fput_needed);
    17     if (sock) {
    18         /* 将用户空间地址复制到内核空间 */
    19         err = move_addr_to_kernel(umyaddr, addrlen, &address);
    20         if (err >= 0) {
    21             /* 安全模块的bind检查 */
    22             err = security_socket_bind(sock,
    23                            (struct sockaddr *)&address,
    24                            addrlen);
    25             if (!err)
    26                 /* 调用socket的bind操作 */
    27                 err = sock->ops->bind(sock,
    28                               (struct sockaddr *)
    29                               &address, addrlen);
    30         }
    31 
    32         /* 根据fput_needed决定是否减少引用计数 */
    33         fput_light(sock->file, fput_needed);
    34     }
    35     return err;
    36 }

    首先根据传入的socket描述符来获取socket结构;

     1 static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
     2 {
     3     /* 获取fd结构 */
     4     struct fd f = fdget(fd);
     5     struct socket *sock;
     6 
     7     *err = -EBADF;
     8     if (f.file) {
     9         /* 从文件的私有数据中获取socket */
    10         sock = sock_from_file(f.file, err);
    11         if (likely(sock)) {
    12             /* 设置是否需要减少引用计数的标志 */
    13             *fput_needed = f.flags;
    14             return sock;
    15         }
    16         fdput(f);
    17     }
    18     return NULL;
    19 }

    之后,将用户空间的地址拷贝到内核空间;

     1 /*
     2  * Support routines.
     3  * Move socket addresses back and forth across the kernel/user
     4  * divide and look after the messy bits.
     5  */
     6 
     7 /**
     8  *    move_addr_to_kernel    -    copy a socket address into kernel space
     9  *    @uaddr: Address in user space
    10  *    @kaddr: Address in kernel space
    11  *    @ulen: Length in user space
    12  *
    13  *    The address is copied into kernel space. If the provided address is
    14  *    too long an error code of -EINVAL is returned. If the copy gives
    15  *    invalid addresses -EFAULT is returned. On a success 0 is returned.
    16  */
    17 
    18 /* 复制socket地址到内核空间 */
    19 int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr)
    20 {
    21     /* 长度检查 */
    22     if (ulen < 0 || ulen > sizeof(struct sockaddr_storage))
    23         return -EINVAL;
    24     if (ulen == 0)
    25         return 0;
    26 
    27     /* 从用户空间拷贝数据 */
    28     if (copy_from_user(kaddr, uaddr, ulen))
    29         return -EFAULT;
    30 
    31     /* 审计信息 */
    32     return audit_sockaddr(ulen, kaddr);
    33 }

    最后,来看最核心的函数,sock->ops->bind调用inet_bind(为什么? 具体可以参考本博套接字调用关系那片文章),inet_bind将会进行一些列检查之后,调用传输层的sk->sk_prot->get_port函数来执行更详细的绑定工作,比如tcp会调用inet_csk_get_port函数;

    /* 地址绑定 */
    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);
        struct net *net = sock_net(sk);
        unsigned short snum;
        int chk_addr_ret;
        u32 tb_id = RT_TABLE_LOCAL;
        int err;
    
        /* If the socket has its own bind function then use it. (RAW) */
        /* 
            如果传输控制块有自己的bind操作则调用,
            目前只有raw实现了自己的bind 
        */
        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))
            goto out;
    
        /* 如果不是AF_INET协议族 */
        if (addr->sin_family != AF_INET) {
            /* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
             * only if s_addr is INADDR_ANY.
             */
            err = -EAFNOSUPPORT;
    
            /* 接受AF_UNSPEC && s_addr=htonl(INADDR_ANY)的情况 */
            if (addr->sin_family != AF_UNSPEC ||
                addr->sin_addr.s_addr != htonl(INADDR_ANY))
                goto out;
        }
    
        tb_id = l3mdev_fib_table_by_index(net, sk->sk_bound_dev_if) ? : tb_id;
        chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id);
    
        /* 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)
         */
        err = -EADDRNOTAVAIL;
    
        /* 合法性检查 */
        if (!net->ipv4.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;
    
        /* 绑定特权端口的权限检查 */
        if (snum && snum < inet_prot_sock(net) &&
            !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
            goto out;
    
        /*      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.
         */
        lock_sock(sk);
    
        /* Check these errors (active socket, double bind). */
        err = -EINVAL;
    
        /* 传输控制块的状态不是CLOSE || 存在本地端口 */
        if (sk->sk_state != TCP_CLOSE || inet->inet_num)
            goto out_release_sock;
    
        /* 设置源地址rcv_addr用作hash查找,saddr用作传输 */
        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. */
    
        /* 
            端口不为0,或者端口为0允许绑定 
            则使用协议的具体获取端口函数绑定端口
        */
        if ((snum || !inet->bind_address_no_port) &&
            sk->sk_prot->get_port(sk, snum)) {
    
            /* 绑定失败 */
            inet->inet_saddr = inet->inet_rcv_saddr = 0;
    
            /* 端口在使用中 */
            err = -EADDRINUSE;
            goto out_release_sock;
        }
    
        /* 传输控制块已经绑定本地地址或端口标志 */
        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;
    }
    EXPORT_SYMBOL(inet_bind);

    TCP bind()系统调用实现部分,请阅读<TCP层bind系统调用的实现分析>

  • 相关阅读:
    数据同步
    闭包的内存泄漏解决办法
    No module named 'MySQLdb'
    pqi 更换pip 国内源
    BZOJ 1934 [Shoi2007]Vote 善意的投票
    BZOJ 2038 [2009国家集训队]小Z的袜子(hose)
    BZOJ 1002 [FJOI2007]轮状病毒
    BZOJ 3442 学习小组
    BZOJ 3261 最大异或和
    BZOJ 4029 [HEOI2015]定价
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/7618371.html
Copyright © 2011-2022 走看看