说明:该文章中部分代码未能完全理解透彻,可能对您造成误解,请慎读;

并建议您先阅读本博另外一篇文章:<Linux TCP套接字选项 之 SO_REUSEADDR && SO_REUSEPORT>

另:该文章将会持续更新改进;

TCP的接口绑定通过函数inet_csk_get_port函数执行,其中包含了未设置端口号自动分配的情况,设置了地址重用标记(SO_REUSEADDR)和设置了端口重用(SO_REUSEPORT)选项的处理;检查成功则控制块节点加入到绑定接口hash中对应端口的链表中;

 /* Obtain a reference to a local port for the given sock,
* if snum is zero it means select any available local port.
* We try to allocate an odd port (and leave even ports for connect())
*/
/* 绑定端口bind */
/* 下面的重用地址表示SO_REUSEADDR,重用端口表示SO_REUSEPORT */
int inet_csk_get_port(struct sock *sk, unsigned short snum)
{
bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN;
struct inet_hashinfo *hinfo = sk->sk_prot->h.hashinfo;
int ret = , port = snum;
struct inet_bind_hashbucket *head;
struct net *net = sock_net(sk);
struct inet_bind_bucket *tb = NULL;
kuid_t uid = sock_i_uid(sk); /* 未设定端口,自动绑定 */
if (!port) {
/* 返回端口所在的桶节点 */
head = inet_csk_find_open_port(sk, &tb, &port);
/* 未找到桶节点 */
if (!head)
return ret;
/* 没有相同节点,创建节点 */
if (!tb)
goto tb_not_found; /* 有相同节点,成功 */
goto success;
} /* 设置了端口,找到端口hash桶节点 */
head = &hinfo->bhash[inet_bhashfn(net, port,
hinfo->bhash_size)];
spin_lock_bh(&head->lock); /* 遍历该桶节点下面的所有端口 */
inet_bind_bucket_for_each(tb, &head->chain)
/* 找到相同端口 */
if (net_eq(ib_net(tb), net) && tb->port == port)
goto tb_found;
tb_not_found:
/* 创建绑定节点 */
tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
net, head, port);
if (!tb)
goto fail_unlock;
tb_found:
/* 节点上有控制块 */
if (!hlist_empty(&tb->owners)) {
/* 强制绑定,成功 */
if (sk->sk_reuse == SK_FORCE_REUSE)
goto success; /* 快速的比对 */
/* 已绑定的开启重用&&新请求绑定的开启了地址重用|| 端口重用检查通过,成功 */
/* 先检查地址重用,未通过则检查端口重用 */
if ((tb->fastreuse > && reuse) ||
sk_reuseport_match(tb, sk))
goto success; /* 地址重用和端口重用fast检查都失败 */ /* 走更精确的比对 */
/* 注意,在指定端口的情况下,不需要进行严格检查,并且可以重用端口 */
if (inet_csk_bind_conflict(sk, tb, true, true))
goto fail_unlock;
}
success:
/* 该节点有控制块共用 */
if (!hlist_empty(&tb->owners)) {
/* 设置地址重用标志 */
tb->fastreuse = reuse; /* 如果开启端口重用 */
if (sk->sk_reuseport) {
tb->fastreuseport = FASTREUSEPORT_ANY;
tb->fastuid = uid;
tb->fast_rcv_saddr = sk->sk_rcv_saddr;
tb->fast_ipv6_only = ipv6_only_sock(sk);
#if IS_ENABLED(CONFIG_IPV6)
tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
#endif
}
/* 未开启端口重用 */
else {
tb->fastreuseport = ;
}
}
/* 该端口节点还没有其他控制块 */
else {
/* 未开启地址重用,置0 */
if (!reuse)
tb->fastreuse = ; /* 开启了端口重用 */
if (sk->sk_reuseport) {
/* We didn't match or we don't have fastreuseport set on
* the tb, but we have sk_reuseport set on this socket
* and we know that there are no bind conflicts with
* this socket in this tb, so reset our tb's reuseport
* settings so that any subsequent sockets that match
* our current socket will be put on the fast path.
*
* If we reset we need to set FASTREUSEPORT_STRICT so we
* do extra checking for all subsequent sk_reuseport
* socks.
*/
if (!sk_reuseport_match(tb, sk)) {
tb->fastreuseport = FASTREUSEPORT_STRICT;
tb->fastuid = uid;
tb->fast_rcv_saddr = sk->sk_rcv_saddr;
tb->fast_ipv6_only = ipv6_only_sock(sk);
#if IS_ENABLED(CONFIG_IPV6)
tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
#endif
}
}
/* 未开启端口重用 */
else {
tb->fastreuseport = ;
}
} /* 添加到绑定hash */
if (!inet_csk(sk)->icsk_bind_hash)
inet_bind_hash(sk, tb, port);
WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);
ret = ; fail_unlock:
spin_unlock_bh(&head->lock);
return ret;
}

对于未设置端口号的绑定,系统会在端口号范围内查找一个没有冲突的端口号;

 /*
* Find an open port number for the socket. Returns with the
* inet_bind_hashbucket lock held.
*/
static struct inet_bind_hashbucket *
inet_csk_find_open_port(struct sock *sk, struct inet_bind_bucket **tb_ret, int *port_ret)
{
struct inet_hashinfo *hinfo = sk->sk_prot->h.hashinfo;
int port = ;
struct inet_bind_hashbucket *head;
struct net *net = sock_net(sk);
int i, low, high, attempt_half;
struct inet_bind_bucket *tb;
u32 remaining, offset; /* 地址重用则attempt_half设置为1 */
attempt_half = (sk->sk_reuse == SK_CAN_REUSE) ? : ;
other_half_scan:
/* 获取端口范围区间 */
inet_get_local_port_range(net, &low, &high);
high++; /* [32768, 60999] -> [32768, 61000[ */ /* 端口范围很小,attempt_half设置为0 */
if (high - low < )
attempt_half = ;
/* attempt_half不为0 */
if (attempt_half) { /* 找到一半的位置 */
int half = low + (((high - low) >> ) << ); /* 第一次尝试低一半 */
if (attempt_half == )
high = half;
/* 否则尝试高一半 */
else
low = half;
} /* 地址数 */
remaining = high - low; /* 地址数消除低位 */
if (likely(remaining > ))
remaining &= ~1U; /* 随机一个偏移 */
offset = prandom_u32() % remaining;
/* __inet_hash_connect() favors ports having @low parity
* We do the opposite to not pollute connect() users.
*/
/* 偏移低位置1 ,方便下面分半遍历端口*/
offset |= 1U; other_parity_scan: /* 取一个端口 */
port = low + offset; /* 遍历查找合适端口,先遍历一半端口 */
for (i = ; i < remaining; i += , port += ) {
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);
/* 遍历该链表 */
inet_bind_bucket_for_each(tb, &head->chain)
/* 同一个命名空间&& 端口相同 */
if (net_eq(ib_net(tb), net) && tb->port == port) {
/* 绑定无冲突,成功 */
/* 注意,随机端口,需要进行严谨的检查,并且不能使用端口重用 */
if (!inet_csk_bind_conflict(sk, tb, false, false))
goto success; /* 有冲突,下一个端口 */
goto next_port;
}
/* 没有命名空间和端口相同,成功 */
tb = NULL;
goto success;
next_port:
spin_unlock_bh(&head->lock);
cond_resched();
} /* 遍历另外一半端口 */
offset--;
if (!(offset & ))
goto other_parity_scan; /* 端口均不能用,则尝试高位端口的一半 */
if (attempt_half == ) {
/* OK we now try the upper half of the range */
attempt_half = ;
goto other_half_scan;
}
return NULL; /* 成功返回端口和节点(有端口相同时不为NULL) */
success:
*port_ret = port;
*tb_ret = tb;
return head;
}

inet_csk_bind_conflict用来判断端口是否冲突;

 static int inet_csk_bind_conflict(const struct sock *sk,
const struct inet_bind_bucket *tb,
bool relax, bool reuseport_ok)
{
struct sock *sk2;
/* 地址重用 */
bool reuse = sk->sk_reuse;
/* 端口重用 */
bool reuseport = !!sk->sk_reuseport && reuseport_ok;
/* 用户id */
kuid_t uid = sock_i_uid((struct sock *)sk); /*
* Unlike other sk lookup places we do not check
* for sk_net here, since _all_ the socks listed
* in tb->owners list belong to the same net - the
* one this bucket belongs to.
*/
/* 遍历所有绑定控制块 */
sk_for_each_bound(sk2, &tb->owners) {
if (sk != sk2 && /* 控制块不同 */
/* 输出报文的网络接口号为0或者相等 */
(!sk->sk_bound_dev_if ||
!sk2->sk_bound_dev_if ||
sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) { /* 或逻辑好多,看着晕啊,看看什么情况下不需要判断吧 */ /*
未启用地址重用 && 未启用端口重用:检查冲突;
启用了地址重用 && 未启用端口重用:状态是LISTEN才检查冲突;
未启用地址重用 && 启用了端口重用:状态不是TIME_WAIT并且不是同一有效用户ID时,检查冲突;
也就是说,假若是TIME_WAIT,则不需要检查;
假如不是TIME_WAIT,但是有效用户ID相同,也不需要检查; 启用了地址重用 && 启用了端口重用:状态是LISTEN时,可能需要检查,需要继续判断端口重用,
这时候只当有效用户ID不相同的时候,才需要检查;
就是说,可以相同用户ID的进程可以同时LISTEN多个相同的地址+端口;
*/ if ((!reuse || !sk2->sk_reuse ||
sk2->sk_state == TCP_LISTEN) &&
(!reuseport || !sk2->sk_reuseport ||
rcu_access_pointer(sk->sk_reuseport_cb) ||
(sk2->sk_state != TCP_TIME_WAIT &&
!uid_eq(uid, sock_i_uid(sk2))))) { /* 地址相同,冲突 */
if (inet_rcv_saddr_equal(sk, sk2, true))
break;
} /* 上面不需要判断的走到这里的情况 */
/* 情况1.新旧绑定都设置了地址重用,状态不是LISTEN ,不满足本条,继续下面2*/
/* 情况2.新旧绑定都设置了端口重用,状态是TIME_WAIT或者用户ID相等 */ /* 上面1情况如果不放宽检查,则检查 */
if (!relax && reuse && sk2->sk_reuse &&
sk2->sk_state != TCP_LISTEN) { /* 地址相同,冲突 */
if (inet_rcv_saddr_equal(sk, sk2, true))
break;
}
}
}
return sk2 != NULL;
}

最新文章

  1. [知识笔记]Java 基本数据类型的大小、取值范围、默认值
  2. JavaScript刷新页面n种方法
  3. 【30集iCore3_ADP出厂源代码(ARM部分)讲解视频】30-3 底层驱动之LED_蜂鸣器
  4. ROS 新手教程 命令汇总
  5. Beego框架使用
  6. 2016年12月21日 星期三 --出埃及记 Exodus 21:16
  7. Maven配置中scope说明
  8. 报课系统APP
  9. iOS 难题解决日志------2层控制器 上面的控制器显示透明
  10. HDU 4704
  11. javascript笔记 面向对象
  12. 实现Linux下的ls -l命令
  13. BigDecimal类对象的使用详解
  14. (四)surging 微服务框架使用系列之网关
  15. SQL修改某个字段中某相同部分(MySQL)
  16. 位移运算 &lt;&lt; &gt;&gt; &gt;&gt;&gt;
  17. 2、LwIP协议栈规范翻译——协议层
  18. solidity return data和revert/require的reason string的获得
  19. Thrift的一些概念
  20. VS2010 发布网站时文件丢失解决办法

热门文章

  1. 常用的TCP/UDP端口
  2. IDEA导入Eclipse 非Maven的Web项目
  3. Mysql学习(一)之简单介绍
  4. luogu P5504 [JSOI2011]柠檬
  5. html2canvas+Canvas2Image分享海报功能踩坑
  6. 神经网络优化算法:梯度下降法、Momentum、RMSprop和Adam
  7. deep_learning_Function_tf.identity()
  8. Chrome安装Axure插件axure-chrome-extension
  9. 个人总结的J2EE目前知道的涵盖面,从大方向入手,多写单元测试,加强基础
  10. JAVA GUI设