我是靠谱客的博主 火星上钢铁侠,这篇文章主要介绍2.2 Bind系统调用,现在分享给大家,希望可以做个参考。

  Socket是用socket系统调用生成的,它指定了地址族(domain)但并没有地址与之关联。Bind系统调用会将一个地址与socket关联。其函数原型为:

复制代码
1
2
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

其中,sockfd为套接字的文件描述符,addr指向存放地址信息的结构体的首地址,addrlen是这个结构体的大小。
    sockaddr结构体的定义:

复制代码
1
2
3
4
5
struct sockaddr { sa_family_t sa_family; char sa_data[14]; }

  sa_family需与socket系统调用中的domain一致;sa_data则根据domain的不同而不同。对于AF_INET则使用 struct sockaddr_in,需要将 struct sockaddr_in指针强制转换为struct sockaddr *作为bind系统调用的第二个参数,addrlen必须设置为sizeof(struct sockaddr_in);AF_INET6则使用 struct sockaddr_in6。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct sockaddr_in6 { unsigned short int sin6_family; /* AF_INET6 */ __be16 sin6_port; /* Transport layer port # */ __be32 sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ __u32 sin6_scope_id; /* scope id (new in RFC2553) */ };
  可见,bind系统调用所指的“地址”是IP地址、端口、地址族的集合。地址族其实在socket系统调用中就已经确定了,故bind系统调用所绑定的真正的地址是IP地址和端口号。
对于服务进程,IP地址(或域名)和端口号必须对外公布,一个端口号就代表了一种服务。如果HTTP使用80端口,FTP使用21,Telnet使用23。这些众所周知的端口号被称为“知名端口”。

  下面来看看bind系统调用在内核是如何工作的。bind系统调用在内核对应的代码为:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1497 SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) 1498 { 1499 struct socket *sock; 1500 struct sockaddr_storage address; 1501 int err, fput_needed; 1502 1503 sock = sockfd_lookup_light(fd, &err, &fput_needed); //通过socket文件符fd找到socket 1504 if (sock) { 1505 err = move_addr_to_kernel(umyaddr, addrlen, &address); //将地址信息由用户态copy到内核 1506 if (err >= 0) { 1507 err = security_socket_bind(sock, //安全检查 1508 (struct sockaddr *)&address, 1509 addrlen); 1510 if (!err) 1511 err = sock->ops->bind(sock, //调用TCP对应的插口函数执行绑定功能 1512 (struct sockaddr *) 1513 &address, addrlen); 1514 } 1515 fput_light(sock->file, fput_needed); 1516 } 1517 return err; 1518 }
  sock->ops指向inet_stream_ops,那么sock->ops->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
460 int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) 461 { 462 struct sockaddr_in *addr = (struct sockaddr_in *)uaddr; 463 struct sock *sk = sock->sk; 464 struct inet_sock *inet = inet_sk(sk); 465 struct net *net = sock_net(sk); 466 unsigned short snum; 467 int chk_addr_ret; 468 int err; ... 476 if (addr_len < sizeof(struct sockaddr_in)) //地址类型必须是struct sockaddr_in 477 goto out; 478 479 if (addr->sin_family != AF_INET) { //地址族必须是AF_INET 480 /* Compatibility games : accept AF_UNSPEC (mapped to AF_INET) 481 * only if s_addr is INADDR_ANY. 482 */ 483 err = -EAFNOSUPPORT; 484 if (addr->sin_family != AF_UNSPEC || 485 addr->sin_addr.s_addr != htonl(INADDR_ANY)) 486 goto out; 487 } ... 507 snum = ntohs(addr->sin_port); ... 522 /* Check these errors (active socket, double bind). */ 523 err = -EINVAL; 524 if (sk->sk_state != TCP_CLOSE || inet->inet_num) //inet->inet_num非0意味着此socket已经被bind过,而重复bind是不允许的。 525 goto out_release_sock; 526 527 inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; ... 531 /* Make sure we are allowed to bind here. */ 532 if (sk->sk_prot->get_port(sk, snum)) { //执行绑定功能 533 inet->inet_saddr = inet->inet_rcv_saddr = 0; 534 err = -EADDRINUSE; 535 goto out_release_sock; 536 } ... 542 inet->inet_sport = htons(inet->inet_num); //将绑定的端口作为源端口号 543 inet->inet_daddr = 0; 544 inet->inet_dport = 0; 545 sk_dst_reset(sk); 546 err = 0; 547 out_release_sock: 548 release_sock(sk); 549 out: 550 return err; 551 }

  sk->sk_prot指向tcp_prot,sk->sk_prot->get_port则指向inet_csk_get_port:

复制代码
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
104 int inet_csk_get_port(struct sock *sk, unsigned short snum) 105 { 106 struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; 107 struct inet_bind_hashbucket *head; 108 struct inet_bind_bucket *tb; 109 int ret, attempts = 5; 110 struct net *net = sock_net(sk); 111 int smallest_size = -1, smallest_rover; 112 kuid_t uid = sock_i_uid(sk); 113 114 local_bh_disable(); 115 if (!snum) { //sport == 0, 内核会自动选择port 116 int remaining, rover, low, high; 117 118 again: 119 inet_get_local_port_range(&low, &high);//获取本地端口范围,这个范围可以用sysctl设置 120 remaining = (high - low) + 1; //获取可用端口数量 121 smallest_rover = rover = net_random() % remaining + low; //随机选取端口,尽量减少冲突 122 123 smallest_size = -1; 124 do { 125 if (inet_is_reserved_local_port(rover)) //判断这个端口是否已经被保留;保留端口可以通过sysctl设置 126 goto next_nolock; //如果已被保留,则选取其它端口 127 head = &hashinfo->bhash[inet_bhashfn(net, rover, 128 hashinfo->bhash_size)]; //查找bind hash表 129 spin_lock(&head->lock); 130 inet_bind_bucket_for_each(tb, &head->chain) 131 if (net_eq(ib_net(tb), net) && tb->port == rover) { 132 if (((tb->fastreuse > 0 && 133 sk->sk_reuse && 134 sk->sk_state != TCP_LISTEN) || 135 (tb->fastreuseport > 0 && 136 sk->sk_reuseport && 137 uid_eq(tb->fastuid, uid))) && 138 (tb->num_owners < smallest_size || smallest_size == -1)) { 139 smallest_size = tb->num_owners; 140 smallest_rover = rover; 141 if (atomic_read(&hashinfo->bsockets) > (high - low) + 1 && 142 !inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) { 143 snum = smallest_rover; 144 goto tb_found; 145 } 146 } 147 if (!inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) { 148 snum = rover; 149 goto tb_found; 150 } 151 goto next; //如果这个端口不可用,查看下一个 152 } 153 break; //如果没有找到这个端口的bind_bucket,即此IP-端口对没有被绑定过,则退出循环,无需查寻其它端口 154 next: 155 spin_unlock(&head->lock); 156 next_nolock: 157 if (++rover > high) 158 rover = low; 159 } while (--remaining > 0); //遍历完所有的port,没有找到能使用的,退出循环 160 161 /* Exhausted local port range during search? It is not 162 * possible for us to be holding one of the bind hash 163 * locks if this test triggers, because if 'remaining' 164 * drops to zero, we broke out of the do/while loop at 165 * the top level, not from the 'break;' statement. 166 */ 167 ret = 1; 168 if (remaining <= 0) { //如果是找遍所有端口后退出循环的 169 if (smallest_size != -1) { //如果曾经找到过可以重用的端口 170 snum = smallest_rover; //之前没有用是因为有冲突,这次用宽松的检查条件再次尝试 171 goto have_snum; 172 } 173 goto fail; 174 } 175 /* OK, here is the one we will use. HEAD is 176 * non-NULL and we hold it's mutex. 177 */ 178 snum = rover; //走到这里,证明是以break的方式跳出循环,tb == NULL为真 179 } else {//sport != 0,直接使用用户设置的端口 180 have_snum: 181 head = &hashinfo->bhash[inet_bhashfn(net, snum, 182 hashinfo->bhash_size)]; 183 spin_lock(&head->lock); 184 inet_bind_bucket_for_each(tb, &head->chain) 185 if (net_eq(ib_net(tb), net) && tb->port == snum) 186 goto tb_found; 187 } 188 tb = NULL; 189 goto tb_not_found; 190 tb_found: 191 if (!hlist_empty(&tb->owners)) { //进入tb_found的都是已经被绑定的端口,其owners队列都不是为空,因为没有被绑定的tb都会被free,故强烈怀疑此判断无用 192 if (sk->sk_reuse == SK_FORCE_REUSE) 193 goto success; 194 195 if (((tb->fastreuse > 0 && 196 sk->sk_reuse && sk->sk_state != TCP_LISTEN) || 197 (tb->fastreuseport > 0 && 198 sk->sk_reuseport && uid_eq(tb->fastuid, uid))) && 199 smallest_size == -1) { 200 goto success; //是用户自己选的端口而且可以重用,则立即使用 201 } else { 202 ret = 1; 203 if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, true)) { //检查是否有冲突 204 if (((sk->sk_reuse && sk->sk_state != TCP_LISTEN) || 205 (tb->fastreuseport > 0 && 206 sk->sk_reuseport && uid_eq(tb->fastuid, uid))) && 207 smallest_size != -1 && --attempts >= 0) { 208 spin_unlock(&head->lock); 209 goto again; //虽然选中的这个端口有冲突,不可用,但此端口并不是用户指定的,而且曾经找到过可以重用的端口,就再试一次吧 210 } 211 212 goto fail_unlock; 213 } 214 } 215 } 216 tb_not_found: //如果该端口没有bind_bucket,则创建一个,然后使socket与其绑定即可 217 ret = 1; 218 if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep, 219 net, head, snum)) == NULL) 220 goto fail_unlock; 221 if (hlist_empty(&tb->owners)) { 222 if (sk->sk_reuse && sk->sk_state != TCP_LISTEN) 223 tb->fastreuse = 1; 224 else 225 tb->fastreuse = 0; 226 if (sk->sk_reuseport) { 227 tb->fastreuseport = 1; 228 tb->fastuid = uid; 229 } else 230 tb->fastreuseport = 0; 231 } else { 232 if (tb->fastreuse && 233 (!sk->sk_reuse || sk->sk_state == TCP_LISTEN)) 234 tb->fastreuse = 0; 235 if (tb->fastreuseport && 236 (!sk->sk_reuseport || !uid_eq(tb->fastuid, uid))) 237 tb->fastreuseport = 0; 238 } 239 success: 240 if (!inet_csk(sk)->icsk_bind_hash) //如果socket还没有绑定端口,则绑定 241 inet_bind_hash(sk, tb, snum); 242 WARN_ON(inet_csk(sk)->icsk_bind_hash != tb); 243 ret = 0; 244 245 fail_unlock: 246 spin_unlock(&head->lock); 247 fail: 248 local_bh_enable(); 249 return ret; 250 }

  代码解析:

131 判断是不是这个端口对应的bind_bucket

132 tb->fastreuse由之前绑定该端口的进程设置,如果之前的所有绑定者都不独占此端口,此值大于0
133 用setsockopt函数设置SO_REUSEADDR选项可以将sk->sk_reuse设置为非0
134 没有调用listen系统调用的话sk_state不会是TCP_LISTEN
135 tb->fastreuseport由之前绑定该端口的进程设置,如果之前的所有绑定者都不独占此端口,此值大于0
136用setsockopt函数设置SO_REUSEPORT选项可以将sk->sk_reuseport设置为非0
137 与上一次在bind_bucket没有任何拥有者时绑定该端口的socket属于同一个用户组,则tb->fastuid与uid相等成立
138 进入这个执行体的目的是在所有使用者超过一个且可以被重用的端口中,挑选一个使用者最少的,来尽可能使各个端口使用者的数量平均一些。
139  tb->num_owners记录端口的引用计数
140 记录使用者最少的端口
141 绑定hash表的socket数量大于可用端口数,则一定有端口被重用
142  这个if判断我觉得没有必要,功能可以用下面的if替代,有这个if与没有的差别仅在于如果在这个if里的bind_conflict失败的话,仍然可以走下面的bind_conflict,即多走了一次bind_conflict;但一次bind_conflict失败马上进行下一次的bind_conflict就会成功吗?应该不会,所以还是觉得这个if判断多余

147查看该端口是否可用,bind_conflict指向inet_csk_bind_conflict或inet6_csk_bind_conflict

  现在就可以总结一下bind系统调用的功能了:绑定一个IP-端口对到一个socket,不允许一个socket重复绑定两次,即不允许修改绑定的地址;在绑定的时候如果IP-端口对没有被绑定,则记录绑定信息,绑定成功;否则检查是否允许重复绑定,如果是则绑定成功,否则失败。绑定成功的话记录IP地址和端口到socket中。那么什么情况下允许重复绑定呢?来看用于检查IP地址绑定冲突的inet_csk_bind_conflict函数:

复制代码
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
56 int inet_csk_bind_conflict(const struct sock *sk, 57 const struct inet_bind_bucket *tb, bool relax) 58 { 59 struct sock *sk2; 60 int reuse = sk->sk_reuse; 61 int reuseport = sk->sk_reuseport; 62 kuid_t uid = sock_i_uid((struct sock *)sk); ... 70 71 sk_for_each_bound(sk2, &tb->owners) { 72 if (sk != sk2 && 73 !inet_v6_ipv6only(sk2) && 74 (!sk->sk_bound_dev_if || 75 !sk2->sk_bound_dev_if || 76 sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) { 77 if ((!reuse || !sk2->sk_reuse || 78 sk2->sk_state == TCP_LISTEN) && 79 (!reuseport || !sk2->sk_reuseport || 80 (sk2->sk_state != TCP_TIME_WAIT && 81 !uid_eq(uid, sock_i_uid(sk2))))) { 82 const __be32 sk2_rcv_saddr = sk_rcv_saddr(sk2); 83 if (!sk2_rcv_saddr || !sk_rcv_saddr(sk) || 84 sk2_rcv_saddr == sk_rcv_saddr(sk)) 85 break; //冲突 86 } 87 if (!relax && reuse && sk2->sk_reuse && 88 sk2->sk_state != TCP_LISTEN) { //进行严格的检查,即使地址可以重用也要查 89 const __be32 sk2_rcv_saddr = sk_rcv_saddr(sk2); 90 91 if (!sk2_rcv_saddr || !sk_rcv_saddr(sk) || 92 sk2_rcv_saddr == sk_rcv_saddr(sk)) 93 break;//冲突 94 } 95 } 96 } 97 return sk2 != NULL; 98 }
  这个函数的基本思路是遍历已经绑定到目标IP的所有socket,依次与要绑定的socket进行对比,只要冲突条件命中一次的就是冲突,都不冲突的才算不冲突。

  先看77-81的命中条件:

  条件1:(!reuse || !sk2->sk_reuse || sk2->sk_state == TCP_LISTEN);这个条件为真意味着,要绑定的socket不允许地址重用,或已绑定的socket不允许地址重用,或已绑定的scoket处于监听状态。

  条件2:(!reuseport || !sk2->sk_reuseport || (sk2->sk_state != TCP_TIME_WAIT && !uid_eq(uid, sock_i_uid(sk2))));这个条件为真意味着,要绑定的socket不允许端口重用,或已绑定的socket不允许端口重用,或已绑定的scoket不处于time_wait状态并且两个socket不属于同一个linux用户。

  条件1和条件2同时为真才能进行83-84行的地址匹配:如果两个socket中的任意一个绑定到了任意IP或它们绑定的IP地址是一样的,就认为是冲突。

  87-93行代码的功能是,即使条件1为假也要执行地址匹配。对应TCP的bind系统调用而言,relax为false,即只要条件2为真,并且地址匹配成功,则宣告绑定失败。

  现在总结一下允许重复绑定的条件:

1、所有绑定到同一端口的socekt的地址都不是任意地址(INADDR_ANY)且都不相同

2、所有绑定到同一端口的socekt都允许端口重用,并且,所有已绑定的socket的状态为time_wait或它们属于同一个linux用户

  上述条件任意一个成立即可。

  注:72-76的条件我不是很明白,就不分析了。

最后

以上就是火星上钢铁侠最近收集整理的关于2.2 Bind系统调用的全部内容,更多相关2.2内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(77)

评论列表共有 0 条评论

立即
投稿
返回
顶部