网络端口号是如何分配的?除了给常用服务保留的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()产生的随机数 :
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: 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; ... |
connect()通过inet_hash_connect()分配端口号。核心的代码是:
port = low + (i + offset) % remaining;
其中 offset 是随机数。
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: 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; ... |
为以上代码生成随机数port_offset的函数是:
1 2 3 4 5 6 7 |
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 } |
综上,临时端口号是这样产生的:
生成一个随机数,利用随机数在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通常会保留大量的端口号,如果导致剩下的临时端口数量太少的话,动态分配的随机算法往往会产生重复的端口号,造成新分配的端口号总是相同的现象。