bind()
内核版本:3.10.0-514.16.1.el7.x86_64
下述源码分析均以tcp socket为背景
1 |
|
- socket文件描述符
- 要绑定的承载地址和端口的结构体 struct sockaddr
- 第二个参数struct sockaddr的长度
该函数负责绑定套接字的地址和端口,按照绑定者身份来分,会存在两种情况
情况1:绑定者为客户端,主动发起请求方,绑定地址和端口成功后,会使用该地址和端口进行发包
一般情况下,客户端的地址和端口都是其自动选择的,不需要绑定动作。
情况2:绑定者为服务端,被动连接接收方,绑定地址和端口成功后,客户端只能向该地址和端口发送连接请求。服务端往往需要绑定地址和端口。如果服务端存在多网卡情况,其只需要绑定服务端口即可,其目的地址就是客户端访问的目的地址。
sys_bind
1 | SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) |
- sockfd_lookup_light 和move_addr_to_kernel分别为根据fd从当前进程取出socket和把参数从用户空间考入地址空间
- bind系统调用最重要函数为sock->ops->bind
- 在TCP协议情况下inet_stream_ops中bind成员函数为inet_bind
- 后续为对此函数的分析
inet_bind
实现较为复杂,现在版本和原始版本相比,支持端口复用了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125int 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;
int err;
/* If the socket has its own bind function then use it. (RAW) */
/*raw socket才会用到,tcp_proc无此函数*/
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;
/*bind地址中协议检查,必须是下面两种情况
* 1.绑定的地址协议为AF_INET
* 2.绑定协议为0(AF_UNSPEC)同时地址也为0
* 否则直接退出inet_bind ,返回地址不支持错误码
*/
if (addr->sin_family != AF_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地址得出地址类型
RTN_LOCAL 本机地址
RTN_MULTICAST 多播
RTN_BROADCAST 广播
RTN_UNICAST
*/
chk_addr_ret = inet_addr_type(net, 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 (!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;
/*
* 要绑定的端口小于1024时候,要求运行该应用程序的为超级权限
* 否则返回并报权限不运行的错误
*/
if (snum && snum < PROT_SOCK &&
!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;
/*bind动作发生在最初状态,其TCP状态是CLOSE且没有绑定过
* 否则直接判别为异常
*/
if (sk->sk_state != TCP_CLOSE || inet->inet_num)
goto out_release_sock;
/*inet_rcv_saddr 用作hash表查找使用
*inet_saddr作为发包源地址
*当为广播和组播时候发送地址为0
*/
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时候该函数负责查询该端口是否被使用,没有被使用返回0,否则返回非0
*如果已经被使用,则退出bind函数,并返回地址和端口已经被使用错误-EADDRINUSE
*sk->sk_prot->get_port= inet_csk_get_port
*/
if (sk->sk_prot->get_port(sk, snum)) {
inet->inet_saddr = inet->inet_rcv_saddr = 0;
err = -EADDRINUSE;
goto out_release_sock;
}
/*
* 更新sk->sk_userlocks标记,表明本地地址和端口已经绑定
*/
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);
- 绑定地址长度和协议检查 长度异常返回-EINVAL 表示参数异常,协议不支持 -EAFNOSUPPORT
- 对绑定地址进行类型检查inet_addr_type,必须是本机地址,组播和广播地址类型 -EADDRNOTAVAIL 否则报地址参数异常
- 如果端口小于1024 ,必须为超级权限ns_capable 否则 err = -EACCES 权限不允许
- sk->sk_prot->get_port = inet_csk_get_port 四层端口检查,看是否被使用
- 更新sk->skuserlocks标记,代表地址和端口已经被绑定
扩展函数:
inet_csk_get_port TCP四层端口检查
inet_addr_type 地址类型判别
ns_capable 超级权限检查
inet_csk_get_port
1 | int inet_csk_get_port(struct sock *sk, unsigned short snum) |
如果端口为0;则自动选取端口选择过程如下:
先在[low,half] or [half,high]中随机选取一个端口,作为循环获取端口的起始端口,开始以下流程
步骤1: 保留端口检查,不满足,端口加1,重试次数减1,继续从步骤1开始
步骤2: 从当前端口映射的hash桶中取出列表头,遍历检查该端口是否被使用
步骤2-1:没有被使用,直接退出循环,tb为NULL,创建tb,跳转到tb_not_found将该端口连同创建的tb加入该hash桶的链表中,sk也被放到tb->owners中管理,结束退出
步骤2-2: 端口被使用了,检查端口使用是否冲突
步骤2-2-1:没有冲突,推出循环,跳转到tb_found,复用检查成功,sk被放到tb->owners中,结束退出
步骤2-2-2:存在冲突,直接端口+1,继续循环查找
步骤3:如果上半部分已经查找完毕,继续[half,high]中选择一个端口,进行步骤1
attempt_half
sk->sk_reuse == SK_CAN_REUSE
取端口范围 [low ,half]
否则
取端口范围 [half,high]
- 该值会影响上述选择端口的流程从上半端还是从下半端选择端口
- 如果sk->sk_reuse被置SK_CAN_REUSE标记则先从下半端开始选择端口
- 否则直接从上半端选择端口
small_size和small_rover
what’s the fuck!!! 疑惑了好久
small_size和small_rover在3.10的版本中根本就没有使用基本用不到
3.10版本的端口查找原则是确定端口查找区间,随机选择端口,只要该端口能复用就直接使用,已经完全去除了优先选择复用端口数较小的端口这一原则了(3.2kernel)
So amazing!这两个变量可以去除了
inet_get_local_port_range
1 | void inet_get_local_port_range(struct net *net, int *low, int *high) |
1 | sysctl -a|grep ip_local_port_range |
- 上述读取端口范围是用户态的ip_local_port_range,默认是3w多以后的,可以调整此参数扩大端口范围
- 上述read_seqbegin这种方式读取数据,是一种顺序锁,适用于读多写少的方式用方式,后续专门处博文研究
tcp端口冲突检查
inet_csk(sk)->icsk_af_ops->bind_conflict1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const struct inet_connection_sock_af_ops ipv4_specific = {
.queue_xmit = ip_queue_xmit,
.send_check = tcp_v4_send_check,
.rebuild_header = inet_sk_rebuild_header,
.sk_rx_dst_set = inet_sk_rx_dst_set,
.conn_request = tcp_v4_conn_request,
.syn_recv_sock = tcp_v4_syn_recv_sock,
.net_header_len = sizeof(struct iphdr),
.setsockopt = ip_setsockopt,
.getsockopt = ip_getsockopt,
.addr2sockaddr = inet_csk_addr2sockaddr,
.sockaddr_len = sizeof(struct sockaddr_in),
.bind_conflict = inet_csk_bind_conflict,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_ip_setsockopt,
.compat_getsockopt = compat_ip_getsockopt,
#endif
.mtu_reduced = tcp_v4_mtu_reduced,
};
static int tcp_v4_init_sock(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
tcp_init_sock(sk);
icsk->icsk_af_ops = &ipv4_specific;
tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific;
return 0;
}
- 从上文得知inet_csk(sk)->icsk_af_ops->bind_conflict 函数是inet_csk_bind_conflict
- af_ops在tcp_v4_init_sock初始化
inet_csk_bind_conflict分析
1 | int inet_csk_bind_conflict(const struct sock *sk, |
在端口自动选择时可以重用端口条件为:
a设备不同
b绑定ip地址不同
c要绑定sock和已绑定sock地址允许重用,且已绑定socket不处于监听状态
d 链上sock和待检查sock开启端口复用且链表上状态为TW
e 链上sock和待检查sock开启端口复用且两个sock的uid相同
关于条件c的补充条件:即使c满足,也需要看relax的值确定,relax为TRUE时可复用,为fase时候不能复用
自动端口时候relax为false,所以条件c消失,仅仅剩下a、b、d、e四个条件