【问题标题】:What is the meaning of SO_REUSEADDR (setsockopt option) - Linux? [duplicate]SO_REUSEADDR(setsockopt选项)是什么意思-Linux? [复制]
【发布时间】:2011-03-14 21:04:54
【问题描述】:

来自手册页:

SO_REUSEADDR 指定规则 用于验证提供的地址 绑定()应该允许重用本地 地址,如果这是由支持 协议。此选项采用 int 价值。这是一个布尔选项

我应该什么时候使用它?为什么要“重用本地地址”?

【问题讨论】:

    标签: linux sockets port ip-address setsockopt


    【解决方案1】:

    SO_REUSEADDR 允许您的服务器 绑定到 a
    中的地址 TIME_WAIT 状态。

    这个套接字选项告诉内核,即使这个端口很忙(处于 TIME_WAIT 状态),继续并重用它。如果它很忙,但处于另一个状态,您仍然会收到地址已在使用错误。如果您的服务器已关闭,然后在其端口上的套接字仍处于活动状态时立即重新启动,这将很有用。

    来自unixguide.net

    【讨论】:

    • 非常感谢,我也找到了这个但是还是不明白...
    • 假设您打开了一个 TCP 连接。传输数据后,关闭套接字。但实际上,它会被设置为 TIME_WAIT 状态(TIME_WAIT == "可能有些数据还没有传递,或者其他什么,所以我们作为一个谨慎的 TCP 实现等待:) ")一段时间。除非使用 REUSEADDR,否则您无法打开与同一 IP/端口的另一个连接。
    • 一分钱掉了。非常感谢!
    • 问题在于它是一个处于TIME_WAIT 状态的TCP 连接。连接由两个地址标识:远程端口/ip 和本地端口/ip。请注意,当 TCP 服务器未重新启动(始终使用同一个套接字)时,客户端连接一直在不断地循环通过 TIME_WAIT 状态。新客户端不会被阻止连接,因为以前的客户端位于 TIME_WAIT 中。那么为什么仅仅因为服务器重新启动并重新创建了套接字就会出现这种情况呢?记录在案的解释站不住脚。
    • @Kaz 是一个处于TIME_WAIT状态的TCP端口,从它只出现在一端就可以看出。请参阅 RFC 793 中的状态图。另一端可以立即重用其端口,并且(正如您自己所说)创建另一个到同一目标的连接,如果它是客户端,甚至重用相同的源端口。
    【解决方案2】:

    TCP 的主要设计目标是在数据包丢失、数据包重新排序以及(此处为关键)数据包重复的情况下实现可靠​​的数据通信。

    很明显,TCP/IP 网络堆栈在连接建立时如何处理所有这些,但在连接关闭后会发生边缘情况。如果在会话结束时发送的数据包被复制和延迟,这样the 4-way shutdown 数据包在延迟数据包之前到达接收器会发生什么?堆栈尽职尽责地关闭其连接。稍后,延迟的重复数据包出现。堆栈应该做什么?

    更重要的是,如果一个在给定 IP 地址 + TCP 端口组合上打开套接字的程序关闭了它的套接字,然后过了一小段时间,一个程序出现并想要在同一个 IP 地址上侦听并且TCP端口号? (典型情况:一个程序被杀死并迅速重启。)

    有几个选择:

    1. 不允许重复使用该 IP/端口组合至少 2 倍于数据包可以在飞行中的最长时间。在 TCP 中,这通常称为 2×MSL 延迟。有时您还会看到 2×RTT,大致相当。

    这是所有常见 TCP/IP 堆栈的默认行为。 2×MSL 通常在 30 到 120 秒之间,它在netstat 输出中显示为TIME_WAIT 句点。在那之后,堆栈假定由于TTLs 过期而在路由中丢弃了任何恶意数据包,因此套接字离开TIME_WAIT 状态,允许重用该IP/端口组合。

    1. 允许新程序重新绑定到该 IP/端口组合。在具有BSD sockets 接口的堆栈中——基本上所有的 Unix 和类 Unix 系统,以及通过Winsock 的 Windows——你必须在调用 bind() 之前通过设置 SO_REUSEADDR 选项通过setsockopt() 来请求这种行为。

    SO_REUSEADDR 最常设置在网络服务器程序中,因为常见的使用模式是进行配置更改,然后需要重新启动该程序才能使更改生效。如果没有SO_REUSEADDR,如果在您杀死前一个实例时打开了与前一个实例的连接,则重新启动程序的新实例中的bind() 调用将失败。这些连接会将 TCP 端口保持在 TIME_WAIT 状态 30-120 秒,因此您属于上述情况 1。

    设置SO_REUSEADDR 的风险在于它会产生歧义:TCP 数据包标头中的元数据不够独特,以至于堆栈无法可靠地判断数据包是否过时,因此应该丢弃而不是传递给新侦听器的套接字,因为它显然是为已死的侦听器准备的。

    如果您不认为这是真的,那么监听机器的 TCP/IP 堆栈必须与每个连接一起工作才能做出该决定:

    1. 本地 IP: 每个连接不唯一。事实上,我们这里的问题定义是故意重用本地 IP。

    2. 本地 TCP 端口:同上。

    3. 远程 IP:造成歧义的机器可能会重新连接,因此这无助于消除数据包的正确目的地歧义。

    4. 远程端口:在行为良好的网络堆栈中,传出连接的远程端口不会很快重用,但它只有 16 位,所以你有 30-120 秒的时间来强制堆栈要通过几万个选择并重用端口。早在 1960 年代,计算机就能以如此快的速度完成工作。

    如果您对此的回答是远程堆栈应该在其一侧执行类似TIME_WAIT 的操作以禁止ephemeral TCP port 重用,则该解决方案假定远程主机是良性的。恶意行为者可以随意重用该远程端口。

    我想侦听器的堆栈可以选择仅严格禁止来自 TCP 4 元组的连接,以便在 TIME_WAIT 状态期间阻止给定的远程主机与相同的远程临时端口重新连接,但我不是了解具有该特定改进的任何 TCP 堆栈。

    1. 本地和远程 TCP 序列号:这些序列号也不够独特,以至于新的远程程序无法提供相同的值。

    如果我们今天重新设计 TCP,我认为我们会将 TLS 或类似的东西作为非可选功能集成,其效果之一是使这种无意和恶意的连接劫持变得不可能,但是这需要添加大字段(128 位及以上),这在 1981 年根本不实用,当时发布了当前版本的 TCP (RFC 793) 的文档。

    如果没有这种强化,在TIME_WAIT 期间允许重新绑定所产生的歧义意味着您可以 a) 将用于旧侦听器的陈旧数据错误传递到属于新侦听器的套接字,从而破坏侦听器的协议或错误地将陈旧数据注入连接;或者 b) 新侦听器套接字的新数据被错误地分配给旧侦听器的套接字,因此被无意丢弃。

    安全的做法是等待TIME_WAIT 期间结束。

    归根结底,它归结为成本选择:等待TIME_WAIT 期结束或承担不必要的数据丢失或无意数据注入的风险。

    许多服务器程序冒着这种风险,决定最好立即备份服务器,以免错过任何不必要的传入连接。

    这不是一个普遍的选择。许多程序——甚至需要重新启动以应用设置更改的服务器程序——选择不理会SO_REUSEADDR。程序员可能知道这些风险并选择不考虑默认设置,或者他们可能不知道这些问题但正在从明智的默认设置中受益。

    一些网络程序为用户提供配置选项中的选择,从而将责任推给最终用户或系统管理员。

    【讨论】:

    • 很好且很有帮助的答案。 BTW:注意下面的引用man 7 ip一个已经绑定的TCP本地套接字地址在关闭后的一段时间内不可用,除非SO_REUSEADDR标志已经设置。 使用此标志时应小心,因为它会降低 TCP 的可靠性。 如果没有上述说明,则不是很有帮助。
    • 重要的更正:Windows/WinSock 上的 SO_REUSEADDR 并没有像大多数人认为的那样做——实际上它做了一些非常可怕的事情。引用MSDNSO_REUSEADDR 套接字选项允许一个套接字强制绑定到另一个套接字正在使用的端口。 […]
    • 要获得 Windows 上的 Unix SO_REUSEADDR 行为,请改为在套接字上调用 setsockopt(sock, SOL_SOCKET, SO_DONTLINGER, &"\x00\x00\x00\x00", 4);
    • 对于 UDP 套接字,SO_REUSEADDR 用于多播。基本上,多个套接字可以绑定到同一个端口,它们都接收传入的数据报。
    • 您的TIME_WAIT 改进是一个绝妙的主意。
    【解决方案3】:

    当您创建一个套接字时,您并不真正拥有它。操作系统(TCP 堆栈)为您创建它并为您提供一个句柄(文件描述符)来访问它。当您的套接字关闭时,操作系统需要一些时间才能“完全关闭它”,同时它会经历几个状态。正如 cmets 中提到的 EJP,最长的延迟通常来自 TIME_WAIT 状态。这个额外的延迟需要在终止序列的最后处理边缘情况,并确保最后的终止确认要么通过,要么由于超时而让另一端自行重置。 Here you can find 有关此状态的一些额外注意事项。主要考虑如下:

    记住 TCP 保证所有传输的数据都会被传递, 如果可能的话。当你关闭一个套接字时,服务器进入一个 TIME_WAIT 状态,只是为了真正确定所有数据都有 经历了。当一个套接字关闭时,双方同意发送 向对方发送消息,表示他们将不再发送数据。这,它 在我看来已经足够好了,握手完成后, 套接字应该关闭。问题是双重的。首先,没有 确保最后一个 ack 成功传达的方法。 二、网上可能会留下“游走重复”,必须 送达后处理。

    如果您尝试快速创建具有相同 ip:port 对的多个套接字,您会收到“地址已在使用”错误,因为较早的套接字不会被完全释放。使用 SO_REUSEADDR 将消除此错误,因为它将覆盖对任何先前实例的检查。

    【讨论】:

    • 我认为这句话是最重要的,之前没有人提到它——“如果你尝试用相同的 ip:port 对创建多个套接字真的很快,你会得到“地址已经在使用”错误。”值得一票:)
    • IT 不需要时间让操作系统“注意到”它。有一个已定义的 TCP 状态,称为 TIME_WAIT,其中 TCP 协议的规范要求操作系统在最终释放端口之前对其进行维护。此状态晚于相关套接字的关闭。
    • @user207421 此状态在关联套接字关闭后发布 - 在启用SO_LINGER 时也是如此,即在前台徘徊(阻止@987654323 @) 不包括TIME_WAIT?我知道SO_LINGER 为零将完全避免TIME_WAIT(因为它完全避免终止序列并发送RST)。
    • 曾经在 iOS 上使用 SO_REUSEADDR 让我很难受——服务器套接字似乎是打开的,但所有客户端(本地主机)都无法使用 ECONNRESET 连接到它。事实证明,如果 iOS 上的应用程序意外关闭,服务器套接字可以停留很长时间并且仍然存在,并且使用 SO_REUSEADDR 您的新服务器套接字将处于某种“幽灵”状态并断开客户端连接。当我禁用 SO_REUSEADDR 时,我立即注意到了这个问题,因为 listen() 失败了,我可以通过使用带有 listen() 的循环来解决它,直到幽灵套接字消失并创建新套接字。
    • @haelix 没有区别。逗留时间不包括 TIME_WAIT 时间。逗留时间在操作系统实际关闭之前,TIME_WAIT 时间紧随其后。唯一可以改变的方法是设置 SO_LINGER 来重置连接,这会绕过所有关闭后的状态。
    猜你喜欢
    • 1970-01-01
    • 2018-12-28
    • 1970-01-01
    • 2019-09-12
    • 2015-01-22
    • 2012-06-25
    • 1970-01-01
    • 1970-01-01
    • 2020-10-27
    相关资源
    最近更新 更多