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

并建议您先阅读本博另外一篇文章:<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;
}

TCP层bind系统调用的实现分析的更多相关文章

  1. TCP层sendmsg系统调用的实现分析

    概述 sendmsg系统调用在tcp层的实现是tcp_sendmsg函数,该函数完成以下任务:从用户空间读取数据,拷贝到内核skb,将skb加入到发送队列的任务,调用发送函数:函数在执行过程中会锁定控 ...

  2. TCP层recvmsg系统调用的实现分析

    概述 recvmsg系统调用在tcp层的实现是tcp_recvmsg函数,该函数完成从接收队列中读取数据复制到用户空间的任务:函数在执行过程中会锁定控制块,避免软中断在tcp层的影响:函数会涉及从接收 ...

  3. TCP层shutdown系统调用的实现分析

    概述 shutdown系统调用在tcp层会调用两个函数,对于ESTABLISHED状态需要调用tcp_shutdown关闭连接,对于LISTEN和SYN_SENT状态则需要以非阻塞模式调用tcp_di ...

  4. TCP层close系统调用的实现分析

    在调用close系统调用关闭套接字时,如果套接字引用计数已经归零,则需继续向上层调用其close实现,tcp为tcp_close:本文仅介绍tcp部分,前置部分请参考本博关于close系统调用的文章: ...

  5. TCP层accept系统调用的实现分析

    inet_csk_accept函数实现了tcp协议accept操作,其主要完成的功能是,从已经完成三次握手的队列中取控制块,如果没有已经完成的连接,则需要根据阻塞标记来来区分对待,若非阻塞则直接返回, ...

  6. bind系统调用

    /* * Bind a name to a socket. Nothing much to do here since it's * the protocol's responsibility to ...

  7. Socket bind系统调用简要分析

    主要查看linux kernel 源码:Socket.c 以及af_inet.c文件 1.1 bind分析 #include <sys/types.h> /* See NOTES */#i ...

  8. Python Tornado框架(TCP层)

    Tornado在TCP层里的工作机制 上一节是关于应用层的协议 HTTP,它依赖于传输层协议 TCP,例如服务器是如何绑定端口的?HTTP 服务器的 handle_stream 是在什么时候被调用的呢 ...

  9. Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)

    本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函 ...

随机推荐

  1. 第三章 联接查询 T-SQL语言基础

    联接查询 sql server 2008支持四种表运算符----JOIN,APPLY,PIVOT,UNPIVOT. JOIN表运算符是ANSI标准,而APPLY,PIVOT,UNPIVOT是T-SQL ...

  2. 客户端相关知识学习(五)之什么是webView

    webview是什么?作用是什么?和浏览器有什么关系? Android系统中内置了一款高性能 webkit 内核浏览器,在 SDK 中封装为一个叫做 WebView 组件也就是说WebView是一个基 ...

  3. mysql中case when then end的含义和用法

    参见这篇博客:https://blog.csdn.net/rongtaoup/article/details/82183743 case when then else end 语句用于查询满足多种条件 ...

  4. 使用CXF开发WebService程序的总结(七):Spring+CXF+Mybatis+Mysql共同打造的服务端示例

    通过该demo,可以 熟悉下 spring+cxf+maven+mybatis+mysql等常见后端技术整合 1. 在前面的 父工程 ws_parent 中 添加依赖 由于原来的项目是使用的cxf依赖 ...

  5. Hyperledger Fabric(3)通道与组织

    1,通道的结构 通道是Fabric中非常重要的概念(类似微信群?),它实质是由排序节点划分和管理的私有原子广播通道,目的是对通道的信息进行隔离,使得通道外的实体无法访问通道内的信息,从而实现交易的隐私 ...

  6. hadoop安装zookeeper-3.4.12

    在安装hbase的时候,需要安装zookeeper,当然也可以用hbase自己管理的zookeeper,在这里我们独立安装zookeeper-3.4.12. 下载地址:https://mirrors. ...

  7. linux命令详解——vim

    显示行号:命令模式下set nu 定位到指定行: 命令模式下,:n   比如想到第2行,:2 编辑模式下,ngg  比如想到第5行 5gg(或者5G) 打开文件定位到指定行   vim  +n  te ...

  8. Vi编辑器中查找替换

    1 第一个是替换当前行的yesterday为all 第二个是替换全局的 yesterday 为all :s/yesterday/all :s/yesterday/all/g

  9. EfficientNet学习笔记

    EfficientNet是谷歌大脑在2019年提出的,论文地址是:https://arxiv.org/pdf/1905.11946.pdf 这篇文章主要想解决的一个问题是,如何平衡网络的深度.宽度和分 ...

  10. ansible常用模块详解(三)

    1.模块介绍 明确一点:模块的执行就类似是linux命令的一条命令,就单单的是为了执行一条语句,不是批量的操作,批量操作需要用到playbook内类似shell编写脚本进行批量. 1.1 模块的使用方 ...