bind()系統調用是給套接字分配一個本地協議地址,對於網際協議,協議地址是32位IPv4地址或128位IPv6地址與16位的TCP或UDP端口號的組合。如果沒有通過bind()來指定本地的協議地址,在和遠端通信時,內核會隨機給套接字分配一個IP地址和端口號。bind()系統調用通常是在網絡程序的服務器端調用,而且是必須的。如果TCP服務器不這麼做,讓內核來選擇臨時端口號而不是捆綁眾所周知的端口,客戶端如何發起與服務器的連接?
一、sys_bind()
bind()系統調用對應的內核實現是sys_bind(),其源碼及分析如下:
[cpp]
/*
* 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為索引從當前進程的文件描述符表中
* 找到對應的file實例,然後從file實例的private_data中
* 獲取socket實例。
*/ www.2cto.com
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
/*
* 將用戶空間的地址拷貝到內核空間的緩沖區中。
*/
err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);
if (err >= 0) {
/*
* SELinux相關,不需要關心。
*/
err = security_socket_bind(sock,
(struct sockaddr *)&address,
addrlen);
/*
* 如果是TCP套接字,sock->ops指向的是inet_stream_ops,
* sock->ops是在inet_create()函數中初始化,所以bind接口
* 調用的是inet_bind()函數。
*/
if (!err)
err = sock->ops->bind(sock,
(struct sockaddr *)
&address, addrlen);
}
fput_light(sock->file, fput_needed);
}
return err;
}
sys_bind()的代碼流程如下圖所示:
sys_bind()首先調用sockfd_lookup_light()查找套接字對應的socket實例,如果沒有找到,則返回EBADF錯誤。在進行綁定操作之前,要先將用戶傳入的本地協議地址從用戶空間拷貝到內核緩沖區中,在拷貝過程中會檢查用戶傳入的地址是否正確。如果指定的長度參數小於0或者大於sockaddr_storage的大小,則返回EINVAL錯誤;如果在調用copy_from_user()執行拷貝操作過程中出現錯誤,則返回EFAULT錯誤。在上述的准備工作都完成後,調用inet_bind()函數(即sock->ops->bind指向的函數,參見注釋)來完成綁定操作。
二、inet_bind()
inet_bind()比較簡單,不做過多的分析,注釋的已經很清楚了。代碼及注釋如下所示:
[cpp]
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);
unsigned short snum;
int chk_addr_ret;
int err;
/* If the socket has its own bind function then use it. (RAW) */
/*
* 如果是TCP套接字,sk->sk_prot指向的是tcp_prot,在
* inet_create()中調用的sk_alloc()函數中初始化。由於
* tcp_prot中沒有設置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;
/*
* 判斷傳入的地址類型。
*/
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)
*/
err = -EADDRNOTAVAIL;
/*
* 如果系統不支持綁定本地地址,或者
* 傳入的地址類型有誤,則返回EADDRNOTAVAIL
* 錯誤。
*/
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;
/*
* 如果綁定的端口號小於1024(保留端口號),但是
* 當前用戶沒有CAP_NET_BIND_SERVICE權限,則返回EACCESS錯誤。
*/
if (snum && snum < PROT_SOCK && !capable(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;
/*
* 如果套接字狀態不是TCP_CLOSE(套接字的初始狀態,參見
* sock_init_data()函數),或者已經綁定過,則返回EINVAL錯誤。
*/
if (sk->sk_state != TCP_CLOSE || inet->num)
goto out_release_sock;
inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;
if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
inet->saddr = 0; /* Use device */
/* Make sure we are allowed to bind here. */
/*
* 這裡實際調用的是inet_csk_get_port()函數。
* 檢查要綁定的端口號是否已經使用,如果已經使用,
* 則檢查是否允許復用。如果檢查失敗,則返回
* EADDRINUSE錯誤。
*/
if (sk->sk_prot->get_port(sk, snum)) {
inet->saddr = inet->rcv_saddr = 0;
err = -EADDRINUSE;
goto out_release_sock;
}
/*
* rcv_saddr存儲的是已綁定的本地地址,接收數據時使用。
* 如果已綁定的地址不為0,則設置SOCK_BINDADDR_LOCK標志,
* 表示已綁定本地地址。
*/
if (inet->rcv_saddr)
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
/*
* 如果綁定的端口號不為0,則設置SOCK_BINDPORT_LOCK標志,
* 表示已綁定本地端口號。
*/
if (snum)
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
inet->sport = htons(inet->num);
inet->daddr = 0;
inet->dport = 0;
/*
* 重新初始化目的路由緩存項,如果之前已設置,則
* 調用dst_release()釋放老的路由緩存項。
*/
sk_dst_reset(sk);
err = 0;
out_release_sock:
release_sock(sk);
out:
return err;
}