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

并建议您先阅读本博另外一篇文章:<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. Mysql学习(二)之通过homebrew安装mysql后,为什么在系统偏好设置里没有mysql

    原因 用brew install packagename是用来安装命令行工具的,一般不可能影响到图形界面. mysql官方文档是通过dmg文件安装的: The MySQL Installation P ...

  2. LVS、Nginx及HAProxy

    本文转载自 linkedkeeper.com   当前大多数的互联网系统都使用了服务器集群技术,集群是将相同服务部署在多台服务器上构成一个集群整体对外提供服务,这些集群可以是 Web 应用服务器集群, ...

  3. Python多线程异步任务队列

    原文地址 python的多线程异步常用到queue和threading模块 #!/usr/bin/env python # -*- coding: UTF-8 -*- import logging i ...

  4. zabbix磁盘的自动发现与磁盘指标监控

    由于最近项目上需要对服务器监控进行规范化监控,再磁盘这块有几种方式 1.如果每台设备的磁盘是一样的 比如都有vda,vdb两块磁盘那么可以采用 1.1 每台客户端写脚本,服务端每台设备去加上监控项(- ...

  5. dede_arctype|栏目表

    dede_arctype|栏目表: 字段 类型 整理 属性 Null 默认 额外 id smallint(5) UNSIGNED 是 NULL 栏目ID reid smallint(5) UNSIGN ...

  6. Jenkins+GitHub 项目环境搭建和发布脚本(二)

    Jenkins+gitHub项目搭建配置 项目发布脚本 profilesScript.sh (支持不同环境配置文件) #!/bin/bash ACTIVE=$ JENKINS_PATH=/var/li ...

  7. table 边框问题

    对table设置css样式边框,分为几种情况:1.只对table设置边框2.对td设置边框3.对table和td技巧性设置表格边框4.对table和td设置背景,实现完美表格边框 以下DIVCSS5对 ...

  8. insightface作者提供数据训练解读

    1.下载源码: 开源代码地址:https://github.com/deepinsight/insightface 2.查看作者项目训练要求 (1)训练数据 训练数据使用作者提供并制作好的数据,如下图 ...

  9. (转)AIX中修改主机名 要注意

    smit hostname改名后一个常见的问题是:hostname看到的是新名, uname -n 看到的仍是旧名.没见IBM针对改名有官方的步骤,因此共享下我多年来的一直使用的方法.  1.smit ...

  10. 4.3 jmu-Java-03面向对象-06-继承覆盖综合练习-Person、Student、Employee、Company (20 分)中的一些问题

    1.Employee类的equals 由于题目要求//首先调用父类的equals方法,如果返回true.再比较company与salary.//比较salary属性时,使用DecimalFormat ...