2.2 Bind系统调用

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

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

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

           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。

    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系统调用在内核对应的代码为:

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:

 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:

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函数:

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的条件我不是很明白,就不分析了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值