连接端口选择在socket编程中,当客户端使用connect向服务器端发起请求时,如果没有指定本地端口(一般不指定),内核会自动为连接分配一个可用端口。connect如何选择端口??connect端口选择逻辑如下:如果sock已经指定了端口,则使用指定的端口;如果sock没有指定端口,先分配一个端口,获取内核参数设置的本地可用端口范围,默认low:32768-high:60999根据hint和三元组的hash得到一个随机偏移offset从low+offset开始,遍历可用端口范围,判断端口是否可用。每次端口值+2不允许使用用户设置的保留端口,如果端口已经被使用,则不允许使用bind绑定的端口,检查端口是否可复用。1、当ehash表中没有匹配四元组的sock时,端口可以复用。2.当有匹配四元组的sock时,进行TIME_WAIT判断。1、满足以下条件可重复使用。1.匹配连接处于TIME_WAIT状态2.满足TIME_WAIT端口复用条件如果端口没有被使用,内核允许同一个端口向两个不同的服务器发起连接请求源码分析:连接端口选择核心函数inet_hash_connectanalysis:/**为连接操作绑定一个端口并散列它。*/intinet_hash_connect(structinet_timewait_death_row*death_row,structsock*sk){u32port_offset=0;/*if(!inet_sk(sk)->inet_num)if(!inet_sk(sk)->inet_num)/*根据源IP、目的IP和目的端口,使用hash函数计算一个随机数作为初始值端口偏移值*/port_offset=inet_sk_port_offset(sk);返回__inet_hash_connect(death_row,sk,port_offset,__inet_check_established);/*__inet_check_established是回调函数,检查端口是否可用*/}__inet_hash_connect分析:int__inet_hash_connect(structinet_timewait_death_row*death_row,structsock*sk,u32port_offset,int(*check_established)(structinet_timewait_death_row1*ock,*struct__inet_timewait_sock**)){structinet_hashinfo*hinfo=death_row->hashinfo;结构inet_timewait_sock*tw=NULL;结构inet_bind_hashbucket*头;intport=inet_sk(sk)->inet_num;inet_bind_bucket*待定;u32剩余,偏移量;intret,我,低,高;静态u32提示;国际l3mdev;/*sock已经设置了端口*/if(port){head=&hinfo->bhash[inet_bhashfn(net,port,hinfo->bhash_size)];tb=inet_csk(sk)->icsk_bind_hash;spin_lock_bh(&head->lock);如果(sk_head(&tb->owners)==sk&&!sk->sk_bind_node.next){inet_ehash_nolisten(sk,NULL);spin_unlock_bh(&head->lock);返回0;}spin_unlock(&head->lock);/*没有确定的答案...走到已建立的哈希表*/ret=check_established(death_row,sk,port,NULL);local_bh_enable();返还;}/**没有设置端口时sock会走这里,下面是内核自动选择端口的过程*connect自动选择端口会保证ip_local_port_range的低位奇偶校验一致*bind自动选择端口保证是与lowparity相反*//**绑定的VRF(VirtualRoutingandForwarding)设备*内核默认不开启tcp_l3mdev_accept,直接返回0*/l3mdev=inet_sk_bound_l3mdev(sk);/*获取本地可用端口范围,ip_local_port_range是一个可以设置的内核参数,默认为32768-60999*/inet_get_local_port_range(net,&low,&high);高++;/*[32768,60999]->[32768,61000[*//*计算端口范围差值*/remaining=high-low;if(likely(remaining>1))/*确保remaining为偶数,并保证与low的奇偶性一致*/remaining&=~1U;/**根据hint和port_offset计算剩余范围内的一个偏移*hint是一个静态变量,每次+(i+2),i是最后一个可用的端口——最初选择的端口*hint尝试增加每次选择的端口,提高端口命中率*port_offset是根据源地址、目的地址、目的端口散列的随机数*/offset=(hint+port_offset)%remaining;/*在第一遍中,我们尝试@low奇偶校验端口。*inet_csk_get_port()做相反的选择。*//*确保offset为偶数,并保证与low的奇偶校验一致*/offset&=~1U;other_parity_scan:/*选择第一个端口的值*/port=low+offset;/*从第一个端口开始判断端口是否可用。每次port+2时,保证与low的奇偶校验一致*/for(i=0;i=high))port-=remaining;/**排除ip_local_reserved_ports内核参数中设置的保留端口*该参数默认为空,可以自己配置一些服务保留的端口*/if(inet_is_local_reserved_port(net,port))continue;/*根据端口号和命名空间的哈希得到哈希头*/head=&hinfo->bhash[inet_bhashfn(net,port,hinfo->bhash_size)];/*锁定这个标题*/spin_lock_bh(&head->lock);/*不用理会rcv_saddr检查,因为*建立的检查已经足够独特了。*//*从hash得到的链表中找到namespace和端口号对应的bind_bucket*/inet_bind_bucket_for_each(tb,&head->chain){/*比较namespace、端口号和VRF设备(默认不开启)*/if(net_eq(ib_net(tb),net)&&tb->l3mdev==l3mdev&&tb->port==port){/*不允许使用bind创建或使用的端口*bind创建时一个结构,它会让fastreuse和fastreuseport>=0*connect创建一个结构时,这两个值都是-1*/if(tb->fastreuse>=0||tb->fastreuseport>=0)转到下一个端口;WARN_ON(hlist_empty(&tb->owners));/*检测端口是否可以复用*1.ehash表中没有四元组,命名空间匹配sock时可以复用*2.有匹配四元组命名空间的连接时进行TIME_WAIT判断*满足以下条件可以复用*-匹配的连接在TIME_WAIT状态*-满足TIME_WAIT端口复用条件*/if(!check_established(death_row,sk,port,&tw))gotook;转到下一个端口;}}/*没有找到对应的bind_bucket就到这里*表示还没有创建端口的inet_bind_bucket结构,端口必须可用*//*创建端口的inet_bind_bucket结构*/tb=inet_bind_bucket_create(hinfo->bind_bucket_cachep,网络,头,端口,l3mdev);如果(!tb){spin_unlock_bh(&head->lock);返回-ENOMEM;}/*fastreuse和fastreuseport设置为-1*/tb->fastreuse=-1;tb->fastreuseport=-1;转到确定;next_port:spin_unlock_bh(&head->lock);cond_resched();}/*来这里表示没有合适的端口,改变奇偶校验重新选择*/offset++;if((offset&1)&&remaining>1)gotoother_parity_scan;/*改变奇偶校验,仍然没有合适的端口,返回错误Cannotassignrequestedaddress*/返回-EADDRNOTAVAIL;ok:/*保存静态变量的值,下一个相同的三元组会使用新的提示来减少重复判断*/hint+=i+2;/*headlockstillheldandbh'sdisabled*//*添加sock到inet_bind_bucket结构的owner链表中*/inet_bind_hash(sk,tb,port);/*如果sokc没有加入到ehash表中,则将sock加入到ehash表中*/if(sk_unhashed(sk)){inet_sk(sk)->inet_sport=htons(port);inet_ehash_nolisten(sk,(structsock*)tw);}/*提前结束time_wait状态*/if(tw)inet_twsk_bind_unhash(tw,hinfo);spin_unlock(&head->lock);如果(tw)inet_twsk_deschedule_put(tw);local_bh_enable();返回0;**twp){structinet_hashinfo*hinfo=death_row->hashinfo;结构inet_sock*inet=inet_sk(sk);__be32daddr=inet->inet_rcv_saddr;__be32saddr=inet->inet_daddr;intdif=sk->sk_bound_dev_if;结构网*net=sock_net(sk);intsdif=l3mdev_master_ifindex_by_index(net,dif);INET_ADDR_COOKIE(cookie、saddr、daddr);=INET_COMBINED_PORTS(inet->inet_dport,lport);/*根据四元组和命名空间获取ehash表的哈希值*/unsignedinthash=inet_ehashfn(net,daddr,lport,saddr,inet->inet_dport);/*获取指定哈希值的哈希桶*/structinet_ehash_bucket*head=inet_ehash_bucket(hinfo,hash);/*指定哈希桶的锁*/spinlock_t*lock=inet_ehash_lockp(hinfo,hash);袜子结构*sk2;conststructhlist_nulls_node*节点;结构inet_timewait_sock*tw=NULL;/*锁定哈希桶*/spin_lock(lock);/*遍历hashbucket匹配四元组,namespace,绑定相同设备的sock*/sk_nulls_for_each(sk2,node,&head->chain){/*比较hash值*/if(sk2->sk_hash!=hash)继续;/*存在与完全匹配的四个元组、命名空间和绑定设备的连接*/if(likely(INET_MATCH(sk2,net,acookie,saddr,daddr,ports,dif,sdif))){/*连接处于TIME_WAIT状态*/if(sk2->sk_state==TCP_TIME_WAIT){tw=inet_twsk(sk2);/*判断TIME_WAIT端口复用条件是否满足*/if(twsk_unique(sk,sk2,twp))break;}/*如果存在精确匹配的连接,不能被TIME_WAIT重用,就会到这里返回unavailable*/gotonot_unique;}}/*没有匹配到同一个连接,否则time_wait重用会在这里*//*现在必须记录num和sport。否则我们会在哈希表套接字中看到*具有有趣的标识。*/inet->inet_num=lport;inet->inet_sport=htons(lport);sk->sk_hash=散列;WARN_ON(!sk_unhashed(sk));/*将sock添加到ehash哈希桶中*/__sk_nulls_add_node_rcu(sk,&head->chain);/*从ehash桶中删除TIME_WAIT连接*/if(tw){sk_nulls_del_node_init_rcu((structsock*)tw);__NET_INC_STATS(网络,LINUX_MIB_TIMEWAITRECYCLED);}自旋解锁(锁);/*增加端口使用计数*/sock_prot_inuse_add(sock_net(sk),sk->sk_prot,1);/*提前终止time_wait*/if(twp){*twp=tw;}elseif(tw){/*愚蠢。应该改用散列舞……*/inet_twsk_deschedule_put(tw);}/*返回可用*/return0;not_unique:spin_unlock(lock);返回-EADDRNOTAVAIL;}