如下函數inet_hash_connect,如果沒有指定綁定的接口,在發起連接的時候,由函數inet_sk_port_offset先選擇一個端口偏移量(port_offset),函數__inet_hash_connect負責綁定端口。
int inet_hash_connect(struct inet_timewait_death_row *death_row, struct sock *sk)
{
u32 port_offset = 0;
if (!inet_sk(sk)->inet_num)
port_offset = inet_sk_port_offset(sk);
return __inet_hash_connect(death_row, sk, port_offset,
__inet_check_established);
}
端口偏移量根據套接口的監聽地址,目的地址和目的端口生成。
static u32 inet_sk_port_offset(const struct sock *sk)
{
const struct inet_sock *inet = inet_sk(sk);
return secure_ipv4_port_ephemeral(inet->inet_rcv_saddr,
inet->inet_daddr, inet->inet_dport);
}
如下函數__inet_hash_connect,如果指定了端口號(port),直接定位到端口號對應的綁定結構體inet_bind_bucket,如果此結構體(tb)的擁有者owners鏈表中的首個套接口等於當前套接口,並且擁有者owners僅有一個(只有當前套接口監聽此端口號),將套接口移入ehash鏈表(TCP客戶端沒有listen鏈表)。
否則,如果tb中有多個套接口,或者tb的擁有者鏈表的首位不是當前套接口,由指針函數check_established檢查此端口號是否被使用,指針函數實際上爲函數__inet_check_established,稍後介紹。
int __inet_hash_connect(struct inet_timewait_death_row *death_row,
struct sock *sk, u32 port_offset,
int (*check_established)(struct inet_timewait_death_row *,
struct sock *, __u16, struct inet_timewait_sock **))
{
struct inet_hashinfo *hinfo = death_row->hashinfo;
struct inet_timewait_sock *tw = NULL;
struct inet_bind_hashbucket *head;
int port = inet_sk(sk)->inet_num;
struct net *net = sock_net(sk);
struct inet_bind_bucket *tb;
static u32 hint;
if (port) {
head = &hinfo->bhash[inet_bhashfn(net, port, hinfo->bhash_size)];
tb = inet_csk(sk)->icsk_bind_hash;
spin_lock_bh(&head->lock);
if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) {
inet_ehash_nolisten(sk, NULL);
spin_unlock_bh(&head->lock);
return 0;
}
spin_unlock(&head->lock);
/* No definite answer... Walk to established hash table */
ret = check_established(death_row, sk, port, NULL);
local_bh_enable();
return ret;
}
以下是沒有指定端口號,進行自動分配的情況。首先確保端口的數量值(remaining = high-low)爲偶數;其次,hint值爲一個靜態變量,表示可能可用的端口號,其記錄了上一次選擇的端口號加上2的結果值。而offset爲以上函數inet_sk_port_offset生成的哈希值。根據以上兩個值(hint和port_offset)生成一個偏移量offset,確保其爲偶數值。在此函數中,首先嚐試端口範圍內與low的奇偶性相同的端口號,offset爲偶數,保證不會改變之後port的奇偶性(加/減偶數,奇偶性不改變)。同理,對於偶數變量remaining,其也不會改變port的奇偶性。
與此不同,在函數inet_csk_find_open_port中,優先選擇與low奇偶性不同的端口號。
l3mdev = inet_sk_bound_l3mdev(sk);
inet_get_local_port_range(net, &low, &high);
high++; /* [32768, 60999] -> [32768, 61000[ */
remaining = high - low;
if (likely(remaining > 1))
remaining &= ~1U;
offset = (hint + port_offset) % remaining;
/* In first pass we try ports of @low parity. inet_csk_get_port() does the opposite choice.
*/
offset &= ~1U;
以下開始端口號遍歷過程,根據端口號和網絡命名空間定位到inet_bind_bucket結構鏈表,遍歷其中的每個tb結構,如果已經設置了地址(fastreuse)或者端口重用(fastreuseport),表明已經有套接口監聽在此端口號上,結束處理,開始遍歷下一個端口號。
否則,使用指針函數check_established檢查端口號是否可用,參見下節對函數__inet_check_established的介紹。
other_parity_scan:
port = low + offset;
for (i = 0; i < remaining; i += 2, port += 2) {
if (unlikely(port >= high))
port -= remaining;
if (inet_is_local_reserved_port(net, port))
continue;
head = &hinfo->bhash[inet_bhashfn(net, port, hinfo->bhash_size)];
spin_lock_bh(&head->lock);
/* Does not bother with rcv_saddr checks, because the established check is already unique enough.
*/
inet_bind_bucket_for_each(tb, &head->chain) {
if (net_eq(ib_net(tb), net) && tb->l3mdev == l3mdev && tb->port == port) {
if (tb->fastreuse >= 0 || tb->fastreuseport >= 0)
goto next_port;
WARN_ON(hlist_empty(&tb->owners));
if (!check_established(death_row, sk, port, &tw))
goto ok;
goto next_port;
}
}
流程走到以下部分,表明端口號對應的inet_bind_bucket結構還沒有創建,端口可用。以下創建tb結構,將其成員fastreuse和fastreuseport設置爲-1,表示客戶端地址/端口號不可重用。
tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep, net, head, port, l3mdev);
if (!tb) {
spin_unlock_bh(&head->lock);
return -ENOMEM;
}
tb->fastreuse = -1;
tb->fastreuseport = -1;
goto ok;
next_port:
spin_unlock_bh(&head->lock);
cond_resched();
}
如果在遍歷所有的與low奇偶性相同的端口號時,沒有找到可用的端口號,以下跳轉回去,在端口範圍內遍歷所有與low的奇偶性不同的端口號。如果所有端口號已經遍歷完成(offset & 1),返回地址不可用的錯誤碼EADDRNOTAVAIL。
offset++;
if ((offset & 1) && remaining > 1)
goto other_parity_scan;
return -EADDRNOTAVAIL;
變量hint記錄下一個可用的與low的奇偶性不同的端口號。最後,將當前套接口添加到連接建立哈希鏈表中。
ok:
hint += i + 2;
/* Head lock still held and bh's disabled */
inet_bind_hash(sk, tb, port);
if (sk_unhashed(sk)) {
inet_sk(sk)->inet_sport = htons(port);
inet_ehash_nolisten(sk, (struct sock *)tw);
}
if (tw)
inet_twsk_bind_unhash(tw, hinfo);
spin_unlock(&head->lock);
if (tw)
inet_twsk_deschedule_put(tw);
local_bh_enable();
return 0;
連接建立鏈表端口檢查
連接狀態套接口鏈表的檢查函數__inet_check_established如下,使用選取的本地端口號lport,與監聽地址,以及目的地址/目的端口號,計算連接建立鏈表的相應哈希值,定位到對應的哈希鏈表。
static int __inet_check_established(struct inet_timewait_death_row *death_row,
struct sock *sk, __u16 lport, struct inet_timewait_sock **twp)
{
struct inet_hashinfo *hinfo = death_row->hashinfo;
struct inet_sock *inet = inet_sk(sk);
__be32 daddr = inet->inet_rcv_saddr;
__be32 saddr = inet->inet_daddr;
int dif = sk->sk_bound_dev_if;
struct net *net = sock_net(sk);
int sdif = l3mdev_master_ifindex_by_index(net, dif);
INET_ADDR_COOKIE(acookie, saddr, daddr);
const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport);
unsigned int hash = inet_ehashfn(net, daddr, lport, saddr, inet->inet_dport);
struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);
遍歷鏈表,如果找到一個源地址/目的地址,源端口/目的端口,綁定接口和網絡命名空間與當前套接口(sk)都相同的套接口(sk2),表明當前選擇的端口號已經被使用,返回錯誤值EADDRNOTAVAIL。但是,有一個例外情況,如果套接口sk2處於TCP_TIME_WAIT狀態,使用函數twsk_unique檢查是否可安全的重用其端口號,PROC文件(/proc/sys/net/ipv4/tcp_tw_reuse)控制是否可重用,默認情況下其值爲0,禁止重用。如果其值爲1,則可進行重用。
具體判斷由函數twsk_unique完成,如果tcp_tw_reuse值爲1,爲安全起見,在重用之前,將修改套接口(sk)的發送序號以及timestamps接收時間戳,以抵禦sk2套接口殘留的報文。
spin_lock(lock);
sk_nulls_for_each(sk2, node, &head->chain) {
if (sk2->sk_hash != hash)
continue;
if (likely(INET_MATCH(sk2, net, acookie,
saddr, daddr, ports, dif, sdif))) {
if (sk2->sk_state == TCP_TIME_WAIT) {
tw = inet_twsk(sk2);
if (twsk_unique(sk, sk2, twp))
break;
}
goto not_unique;
}
}
到這一步已經確認選擇的端口號可以使用,將當前套接口sk添加到連接建立鏈表中。如果tw有值,將tw套接口由鏈表中刪除。
/* Must record num and sport now. Otherwise we will see
* in hash table socket with a funny identity.
*/
inet->inet_num = lport;
inet->inet_sport = htons(lport);
sk->sk_hash = hash;
WARN_ON(!sk_unhashed(sk));
__sk_nulls_add_node_rcu(sk, &head->chain);
if (tw) {
sk_nulls_del_node_init_rcu((struct sock *)tw);
__NET_INC_STATS(net, LINUX_MIB_TIMEWAITRECYCLED);
}
spin_unlock(lock);
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
if (twp) {
*twp = tw;
} else if (tw) {
/* Silly. Should hash-dance instead... */
inet_twsk_deschedule_put(tw);
}
return 0;
not_unique:
spin_unlock(lock);
return -EADDRNOTAVAIL;
內核版本 5.0