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