85

从手册页:

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

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

4

3 回答 3

188

TCP 的主要设计目标是在数据包丢失、数据包重新排序以及——这里的关键——数据包重复的情况下允许可靠的数据通信。

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

更重要的是,如果一个在给定 IP 地址 + TCP 端口组合上打开套接字的程序关闭了它的套接字,然后不久之后,一个程序出现并想要监听相同的 IP 地址和 TCP 端口号,该怎么办? ? (典型情况:一个程序被杀死并迅速重新启动。)

有几个选择:

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

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

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

SO_REUSEADDR最常设置在网络服务器程序中,因为常见的使用模式是进行配置更改,然后需要重新启动该程序以使更改生效。如果没有SO_REUSEADDRbind()重新启动的程序的新实例中的调用将失败,如果在您杀死前一个实例时有连接打开它。这些连接将使 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在其一侧执行类似的操作以禁止临时 TCP 端口重用,那么该解决方案假定远程主机是良性的。恶意行为者可以自由地重用该远程端口。

我想侦听器的堆栈可以选择仅严格禁止来自 TCP 4 元组的连接,以便在TIME_WAIT状态期间阻止给定的远程主机重新连接到相同的远程临时端口,但我不知道任何 TCP 堆栈与那个特别的细化。

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

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

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

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

最终,它归结为成本选择:等待TIME_WAIT一段时间或承担不必要的数据丢失或无意数据注入的风险。

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

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

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

于 2010-07-12T23:18:21.583 回答
40

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

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

来自unixguide.net

于 2010-07-12T15:51:41.310 回答
11

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

请记住,如果可能的话,TCP 保证所有传输的数据都将被传递。当您关闭一个套接字时,服务器会进入 TIME_WAIT 状态,以确保所有数据都已通过。当套接字关闭时,双方通过相互发送消息同意不再发送数据。这对我来说似乎已经足够好了,握手完成后,应该关闭套接字。问题有两个方面。首先,无法确定最后一个 ack 是否已成功传达。二是网上可能存在“游走重复”,送达必须处理。

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

于 2010-07-12T23:33:12.857 回答