Linux 内核网络之 bind 的实现
sys_bind 系统调用是将一个本地地址及传输层的端口和套接口进行关联起来,一般作为客户端进程不用关心它的本地地址和端口是什么,所以也不需要进行绑定,内核会自动为其分配一个本地地址和端口号的。

/*
fd, 进行绑定的套接口文件描述符
umyaddr,进行绑定的地址
addrlen,进行绑定的地址的长度。由于不同协议族的地址描述结构是不一样的,
因此需要标识地址长度。
*/
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err, fput_needed;
//根据fd获取套接口socket指针,并且返回是否需要减少对文件引用计数的标志
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if(sock) {
//将用户空间的地址数据复制到内核空间
err = move_addr_to_kernel(umyaddr, addrlen, address);
if (err >= 0) {
// 安全检查
err = security_socket_bind(sock,
(struct sockaddr *)address,
addrlen);
if (!err)
/*通过套接口层接口,调用bind()接口,在ipv4协议族中,所有类型的bind接口是统一的,
即是inet_bind(), 它将实现对具体传输层接口 bind 的有关调用
*/
err = sock->ops->bind(sock,
(struct sockaddr *)
address, addrlen); // inet_bind()
}
fput_light(sock->file, fput_needed);
}
return err;
}
最终绑定操作为 inet_bind ()。
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) */
/* 若当前套接口在传输层接口上有bind的实现,则直接在调用传输层接口上的bind(),直接进行bind操作即可。
否则进行下面操作。目前只有SOCK_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;
// 获取地址类型
chk_addr_ret = inet_addr_type(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;
if (!sysctl_ip_nonlocal_bind &&
!inet->freebind &&
addr->sin_addr.s_addr != INADDR_ANY &&
chk_addr_ret != RTN_LOCAL &&
chk_addr_ret != RTN_MULTICAST &&
chk_addr_ret != RTN_BROADCAST)
goto out;
//对端口进行合法性校验,并判断是否允许绑定小于1024的特权端口
snum = ntohs(addr->sin_port);
err = -EACCES;
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). */
// 对传输控制块的状态进行检查,这里用TCP_CLOSE,事实上UDP传输控制块也用TCP_CLOSE标识关闭标志
err = -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. */
/*
调用传输层接口上的get_port(),进行具体的传输层的地址绑定。
TCP中对应的函数为 tcp_v4_get_port(), UDP中为 udp_v4_get_port()
*/
if (sk->sk_prot->get_port(sk, snum)) {
inet->saddr = inet->rcv_saddr = 0;
err = -EADDRINUSE;
goto out_release_sock;
}
//标志传输层控制块已经绑定了本地地址和本地端口
if (inet->rcv_saddr)
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
if (snum)
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
//设置本地端口(网络字节序),初始化目的地址、目的端口,
inet->sport = htons(inet->num);//inet->num 也即是 snum, 在sk->sk_prot->get_port中被设置
inet->daddr = 0;
inet->dport = 0;
//由于重新进行了绑定,因此需要清除此传输控制块的路由缓存
sk_dst_reset(sk);
err = 0;
//解锁传输控制块后返回
out_release_sock:
release_sock(sk);
out:
return err;
}
调用传输层接口上的 get_port(), 进行具体的传输层的地址绑定,绑定过程就是在监听的 hash 表中以端口号为索引找到端口号对应的 hash 桶链表,然后创建一个桶节点 inet_bind_bucket,该节点记录这个端口号,把该节点挂到hash 桶链表中,同时把 struct sock 结构挂到该桶节点 inet_bind_bucket 的链表中。之所以桶节点有个挂 struct sock 节点的链表是因为当端口号复用时(多个进程监听同一个端口号时),会有多个 struct sock 节点对应同一个相同端口的桶节点 inet_bind_bucket,因此 inet_bind_bucket 节点下会挂多个 struct sock 节点。

相关推荐
-
第18问:MySQL CPU 高了,怎么办?2025-02-24 10:27:18
-
mysql索引类型 normal, unique, full text
mysql索引类型 normal, unique, full text2025-02-24 10:05:05 -
uwsgi+django+nginx 搭建部分总结2025-02-24 10:03:33
-
使用Docker配置Nginx环境部署Nextcloud2025-02-24 10:02:03
-
Nginx安装和怎么使用2025-02-24 10:00:45