网络端口号是如何分配的?除了给常用服务保留的Well-known Port numbers之外,给客户端的端口号通常是动态分配的,称为ephemeral port(临时端口),在Linux系统上临时端口号的取值范围是通过这个内核参数定义的:net.ipv4.ip_local_port_range (/proc/sys/net/ipv4/ip_local_port_range),端口号动态分配时并不是从小到大依次选取的,而是按照特定的算法随机分配的。
临时端口号的分配发生在以下两处:
– bind();
– connect()。
bind()通过inet_csk_get_port()获取端口号,利用了net_random()产生的随机数 :
// SLES12 kernel 3.12.69-60.64.32:
0104 int inet_csk_get_port(struct sock *sk, unsigned short snum)
0105 {
0106 struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
0107 struct inet_bind_hashbucket *head;
0108 struct inet_bind_bucket *tb;
0109 int ret, attempts = 5;
0110 struct net *net = sock_net(sk);
0111 int smallest_size = -1, smallest_rover;
0112 kuid_t uid = sock_i_uid(sk);
0113
0114 local_bh_disable();
0115 if (!snum) {
0116 int remaining, rover, low, high;
0117
0118 again:
0119 inet_get_local_port_range(&low, &high);
0120 remaining = (high - low) + 1;
0121 smallest_rover = rover = net_random() % remaining + low;
0122
0123 smallest_size = -1;
0124 do {
0125 if (inet_is_reserved_local_port(rover))
0126 goto next_nolock;
...
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
// SLES12 kernel 3.12.69-60.64.32:
0104intinet_csk_get_port(structsock*sk,unsignedshortsnum)
0105{
0106structinet_hashinfo*hashinfo=sk->sk_prot->h.hashinfo;
0107structinet_bind_hashbucket*head;
0108structinet_bind_bucket*tb;
0109intret,attempts=5;
0110structnet*net=sock_net(sk);
0111intsmallest_size=-1,smallest_rover;
0112kuid_tuid=sock_i_uid(sk);
0113
0114local_bh_disable();
0115if(!snum){
0116intremaining,rover,low,high;
0117
0118again:
0119inet_get_local_port_range(&low,&high);
0120remaining=(high-low)+1;
0121smallest_rover=rover=net_random()%remaining+low;
0122
0123smallest_size=-1;
0124do{
0125if(inet_is_reserved_local_port(rover))
0126gotonext_nolock;
...
connect()通过inet_hash_connect()分配端口号。核心的代码是:
port = low + (i + offset) % remaining;
其中 offset 是随机数。
// SLES12 kernel 3.12.69-60.64.32:
0477 int __inet_hash_connect(struct inet_timewait_death_row *death_row,
0478 struct sock *sk, u32 port_offset,
0479 int (*check_established)(struct inet_timewait_death_row *,
0480 struct sock *, __u16, struct inet_timewait_sock **),
0481 int (*hash)(struct sock *sk, struct inet_timewait_sock *twp))
0482 {
0483 struct inet_hashinfo *hinfo = death_row->hashinfo;
0484 const unsigned short snum = inet_sk(sk)->inet_num;
0485 struct inet_bind_hashbucket *head;
0486 struct inet_bind_bucket *tb;
0487 int ret;
0488 struct net *net = sock_net(sk);
0489 int twrefcnt = 1;
0490
0491 if (!snum) {
0492 int i, remaining, low, high, port;
0493 static u32 hint;
0494 u32 offset = hint + port_offset;
0495 struct inet_timewait_sock *tw = NULL;
0496
0497 inet_get_local_port_range(&low, &high);
0498 remaining = (high - low) + 1;
0499
0500 local_bh_disable();
0501 for (i = 1; i <= remaining; i++) {
0502 port = low + (i + offset) % remaining;
0503 if (inet_is_reserved_local_port(port))
0504 continue;
...
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
// SLES12 kernel 3.12.69-60.64.32:
0477int__inet_hash_connect(structinet_timewait_death_row*death_row,
0478structsock*sk,u32port_offset,
0479int(*check_established)(structinet_timewait_death_row*,
0480structsock*,__u16,structinet_timewait_sock**),
0481int(*hash)(structsock*sk,structinet_timewait_sock*twp))
0482{
0483structinet_hashinfo*hinfo=death_row->hashinfo;
0484constunsignedshortsnum=inet_sk(sk)->inet_num;
0485structinet_bind_hashbucket*head;
0486structinet_bind_bucket*tb;
0487intret;
0488structnet*net=sock_net(sk);
0489inttwrefcnt=1;
0490
0491if(!snum){
0492inti,remaining,low,high,port;
0493staticu32hint;
0494u32offset=hint+port_offset;
0495structinet_timewait_sock*tw=NULL;
0496
0497inet_get_local_port_range(&low,&high);
0498remaining=(high-low)+1;
0499
0500local_bh_disable();
0501for(i=1;i<=remaining;i++){
0502port=low+(i+offset)%remaining;
0503if(inet_is_reserved_local_port(port))
0504continue;
...
为以上代码生成随机数port_offset的函数是:
0391 static inline u32 inet_sk_port_offset(const struct sock *sk)
0392 {
0393 const struct inet_sock *inet = inet_sk(sk);
0394 return secure_ipv4_port_ephemeral(inet->inet_rcv_saddr,
0395 inet->inet_daddr,
0396 inet->inet_dport);
0397 }
1
2
3
4
5
6
7
0391staticinlineu32inet_sk_port_offset(conststructsock*sk)
0392{
0393conststructinet_sock*inet=inet_sk(sk);
0394returnsecure_ipv4_port_ephemeral(inet->inet_rcv_saddr,
0395inet->inet_daddr,
0396inet->inet_dport);
0397}
综上,临时端口号是这样产生的:
生成一个随机数,利用随机数在ip_local_port_range范围内取值,如果取到的值在ip_local_reserved_ports范围内 ,那就再依次取下一个值,直到不在ip_local_reserved_ports范围内为止。
注:
/proc/sys/net/ipv4/ip_local_reserved_ports (net.ipv4.ip_local_reserved_ports) 是应用程序保留的端口号,不会参与内核动态分配。有些软件比如SAP通常会保留大量的端口号,如果导致剩下的临时端口数量太少的话,动态分配的随机算法往往会产生重复的端口号,造成新分配的端口号总是相同的现象。