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


 

转载于:https://www.cnblogs.com/aiwz/p/6333323.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值