套接字之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系统调用的实现分析>