共计 6416 个字符,预计需要花费 17 分钟才能阅读完成。
connect 端口抉择
在 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 端口复用条件
- 如果端口没有被应用, 应用此端口
内核容许同一个端口向两个不同的服务端发动连贯申请
源码剖析:
connect 端口抉择外围函数 inet_hash_connect 剖析:
/*
* Bind a port for a connect operation and hash it.
*/
int inet_hash_connect(struct inet_timewait_death_row *death_row,
struct sock *sk)
{
u32 port_offset = 0;
/* 如果 sk 的本地源端口设置为 0 */
if (!inet_sk(sk)->inet_num)
/* 依据源 IP、目标 IP、目标端口, 用 hash 函数计算出一个随机数, 作为端口的初始偏移值 */
port_offset = inet_sk_port_offset(sk);
return __inet_hash_connect(death_row, sk, port_offset,
__inet_check_established);
/* __inet_check_established 为查看端口是否可用的回调函数 */
}
__inet_hash_connect 剖析:
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;
u32 remaining, offset;
int ret, i, low, high;
static u32 hint;
int 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);
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;
}
/*
* sock 没有设置端口时会走到这里, 以下是内核主动抉择端口的过程
* connect 主动抉择端口会保障和 ip_local_port_range 的 low 的奇偶性保持一致
* bind 主动抉择端口会保障和 low 奇偶性相同 */
/*
* 绑定的 VRF(Virtual Routing and Forwarding) 设施
* 内核默认不开启 tcp_l3mdev_accept, 间接返回 0 */
l3mdev = inet_sk_bound_l3mdev(sk);
/* 获取本地可用端口范畴,ip_local_port_range 是能够设置的内核参数, 默认是 32768-60999 */
inet_get_local_port_range(net, &low, &high);
high++; /* [32768, 60999] -> [32768, 61000[ */
/* 计算端口范畴差值 */
remaining = high - low;
if (likely(remaining > 1))
/* 确保 remaining 为偶数, 保障和 low 的奇偶性保持一致 */
remaining &= ~1U;
/*
* 依据 hint 和 port_offset 计算出一个 remaining 范畴内的偏移量
* hint 是一个动态变量, 每次 +(i+2),i 为上次的可用端口 - 初始抉择端口
* hint 尽量使每次抉择的端口递增, 进步端口命中率
* port_offset 是之前依据源地址, 目标地址, 目标端口 hash 进去的一个随机数 */
offset = (hint + port_offset) % remaining;
/* In first pass we try ports of @low parity.
* inet_csk_get_port() does the opposite choice.
*/
/* 确保 offset 为偶数, 保障和 low 的奇偶性保持一致 */
offset &= ~1U;
other_parity_scan:
/* 抉择第一个 port 的值 */
port = low + offset;
/* 从第一个 port 开始判断端口是否可用, 每次 port+2, 保障和 low 的奇偶性保持一致 */
for (i = 0; i < remaining; i += 2, port += 2) {
/* port 超范围则返回 low 持续查找 */
if (unlikely(port >= 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);
/* Does not bother with rcv_saddr checks, because
* the established check is already unique enough.
*/
/* 从哈希失去的链表中查找对应的命名空间和端口号的 bind_bucket */
inet_bind_bucket_for_each(tb, &head->chain) {/* 比照命名空间, 端口号和 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)
goto next_port;
WARN_ON(hlist_empty(&tb->owners));
/* 查看端口是否可重用
* 1.ehash 表中没有四元组, 命名空间匹配的 sock 时可重用
* 2. 有四元组命名空间匹配的连贯时进行 TIME_WAIT 判断
* 合乎以下条件可重用
* - 匹配的连贯处于 TIME_WAIT 状态
* - 满足 TIME_WAIT 端口复用条件 */
if (!check_established(death_row, sk,
port, &tw))
goto ok;
goto next_port;
}
}
/* 没有找到对应的 bind_bucket 时会走到这里
* 阐明还没有创立端口的 inet_bind_bucket 构造, 端口肯定可用 */
/* 为端口创立 inet_bind_bucket 构造 */
tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
net, head, port, l3mdev);
if (!tb) {spin_unlock_bh(&head->lock);
return -ENOMEM;
}
/* fastreuse 和 fastreuseport 设置为 -1 */
tb->fastreuse = -1;
tb->fastreuseport = -1;
goto ok;
next_port:
spin_unlock_bh(&head->lock);
cond_resched();}
/* 走到这里阐明没有适合端口, 扭转奇偶性再选一次 */
offset++;
if ((offset & 1) && remaining > 1)
goto other_parity_scan;
/* 扭转奇偶性仍然没有适合端口, 返回谬误 Cannot assign requested address */
return -EADDRNOTAVAIL;
ok:
/* 保留动态变量的值, 下个雷同三元组会应用新的 hint, 缩小反复判断 */
hint += i + 2;
/* Head lock still held and bh's disabled */
/* 将 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, (struct sock *)tw);
}
/* 提前结束 time_wait 状态 */
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 剖析:
/* called with local bh disabled */
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);
/* 依据四元组和命名空间失去 ehash 表的哈希值 */
unsigned int hash = inet_ehashfn(net, daddr, lport,
saddr, inet->inet_dport);
/* 获取指定哈希值的哈希桶 */
struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);
/* 指定哈希桶的锁 */
spinlock_t *lock = inet_ehash_lockp(hinfo, hash);
struct sock *sk2;
const struct hlist_nulls_node *node;
struct inet_timewait_sock *tw = NULL;
/* 锁住哈希桶 */
spin_lock(lock);
/* 遍历哈希桶匹配四元组, 命名空间, 绑定设施雷同的 sock */
sk_nulls_for_each(sk2, node, &head->chain) {
/* 比拟 hash 值 */
if (sk2->sk_hash != hash)
continue;
/* 有四元组, 命名空间, 绑定设施齐全匹配的连贯 */
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 复用, 会走到这里, 返回不可用 */
goto not_unique;
}
}
/* 没有匹配到雷同的连贯, 或者 time_wait 重用会走到这里 */
/* 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));
/* 增加 sock 到 ehash 哈希桶中 */
__sk_nulls_add_node_rcu(sk, &head->chain);
/* 从 ehash 哈希桶中删除 TIME_WAIT 连贯 */
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);
/* 提前终止 time_wait */
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;
}
正文完