【发布时间】:2011-03-14 21:04:54
【问题描述】:
来自手册页:
SO_REUSEADDR 指定规则 用于验证提供的地址 绑定()应该允许重用本地 地址,如果这是由支持 协议。此选项采用 int 价值。这是一个布尔选项
我应该什么时候使用它?为什么要“重用本地地址”?
【问题讨论】:
标签: linux sockets port ip-address setsockopt
来自手册页:
SO_REUSEADDR 指定规则 用于验证提供的地址 绑定()应该允许重用本地 地址,如果这是由支持 协议。此选项采用 int 价值。这是一个布尔选项
我应该什么时候使用它?为什么要“重用本地地址”?
【问题讨论】:
标签: linux sockets port ip-address setsockopt
SO_REUSEADDR 允许您的服务器 绑定到 a
中的地址 TIME_WAIT 状态。
这个套接字选项告诉内核,即使这个端口很忙(处于 TIME_WAIT 状态),继续并重用它。如果它很忙,但处于另一个状态,您仍然会收到地址已在使用错误。如果您的服务器已关闭,然后在其端口上的套接字仍处于活动状态时立即重新启动,这将很有用。
【讨论】:
TIME_WAIT 状态的TCP 连接。连接由两个地址标识:远程端口/ip 和本地端口/ip。请注意,当 TCP 服务器未重新启动(始终使用同一个套接字)时,客户端连接一直在不断地循环通过 TIME_WAIT 状态。新客户端不会被阻止连接,因为以前的客户端位于 TIME_WAIT 中。那么为什么仅仅因为服务器重新启动并重新创建了套接字就会出现这种情况呢?记录在案的解释站不住脚。
TCP 的主要设计目标是在数据包丢失、数据包重新排序以及(此处为关键)数据包重复的情况下实现可靠的数据通信。
很明显,TCP/IP 网络堆栈在连接建立时如何处理所有这些,但在连接关闭后会发生边缘情况。如果在会话结束时发送的数据包被复制和延迟,这样the 4-way shutdown 数据包在延迟数据包之前到达接收器会发生什么?堆栈尽职尽责地关闭其连接。稍后,延迟的重复数据包出现。堆栈应该做什么?
更重要的是,如果一个在给定 IP 地址 + TCP 端口组合上打开套接字的程序关闭了它的套接字,然后过了一小段时间,一个程序出现并想要在同一个 IP 地址上侦听并且TCP端口号? (典型情况:一个程序被杀死并迅速重启。)
有几个选择:
这是所有常见 TCP/IP 堆栈的默认行为。 2×MSL 通常在 30 到 120 秒之间,它在netstat 输出中显示为TIME_WAIT 句点。在那之后,堆栈假定由于TTLs 过期而在路由中丢弃了任何恶意数据包,因此套接字离开TIME_WAIT 状态,允许重用该IP/端口组合。
bind() 之前通过设置 SO_REUSEADDR 选项通过setsockopt() 来请求这种行为。
SO_REUSEADDR 最常设置在网络服务器程序中,因为常见的使用模式是进行配置更改,然后需要重新启动该程序才能使更改生效。如果没有SO_REUSEADDR,如果在您杀死前一个实例时打开了与前一个实例的连接,则重新启动程序的新实例中的bind() 调用将失败。这些连接会将 TCP 端口保持在 TIME_WAIT 状态 30-120 秒,因此您属于上述情况 1。
设置SO_REUSEADDR 的风险在于它会产生歧义:TCP 数据包标头中的元数据不够独特,以至于堆栈无法可靠地判断数据包是否过时,因此应该丢弃而不是传递给新侦听器的套接字,因为它显然是为已死的侦听器准备的。
如果您不认为这是真的,那么监听机器的 TCP/IP 堆栈必须与每个连接一起工作才能做出该决定:
本地 IP: 每个连接不唯一。事实上,我们这里的问题定义是故意重用本地 IP。
本地 TCP 端口:同上。
远程 IP:造成歧义的机器可能会重新连接,因此这无助于消除数据包的正确目的地歧义。
远程端口:在行为良好的网络堆栈中,传出连接的远程端口不会很快重用,但它只有 16 位,所以你有 30-120 秒的时间来强制堆栈要通过几万个选择并重用端口。早在 1960 年代,计算机就能以如此快的速度完成工作。
如果您对此的回答是远程堆栈应该在其一侧执行类似TIME_WAIT 的操作以禁止ephemeral TCP port 重用,则该解决方案假定远程主机是良性的。恶意行为者可以随意重用该远程端口。
我想侦听器的堆栈可以选择仅严格禁止来自 TCP 4 元组的连接,以便在 TIME_WAIT 状态期间阻止给定的远程主机与相同的远程临时端口重新连接,但我不是了解具有该特定改进的任何 TCP 堆栈。
如果我们今天重新设计 TCP,我认为我们会将 TLS 或类似的东西作为非可选功能集成,其效果之一是使这种无意和恶意的连接劫持变得不可能,但是这需要添加大字段(128 位及以上),这在 1981 年根本不实用,当时发布了当前版本的 TCP (RFC 793) 的文档。
如果没有这种强化,在TIME_WAIT 期间允许重新绑定所产生的歧义意味着您可以 a) 将用于旧侦听器的陈旧数据错误传递到属于新侦听器的套接字,从而破坏侦听器的协议或错误地将陈旧数据注入连接;或者 b) 新侦听器套接字的新数据被错误地分配给旧侦听器的套接字,因此被无意丢弃。
安全的做法是等待TIME_WAIT 期间结束。
归根结底,它归结为成本选择:等待TIME_WAIT 期结束或承担不必要的数据丢失或无意数据注入的风险。
许多服务器程序冒着这种风险,决定最好立即备份服务器,以免错过任何不必要的传入连接。
这不是一个普遍的选择。许多程序——甚至需要重新启动以应用设置更改的服务器程序——选择不理会SO_REUSEADDR。程序员可能知道这些风险并选择不考虑默认设置,或者他们可能不知道这些问题但正在从明智的默认设置中受益。
一些网络程序为用户提供配置选项中的选择,从而将责任推给最终用户或系统管理员。
【讨论】:
SO_REUSEADDR标志已经设置。 使用此标志时应小心,因为它会降低 TCP 的可靠性。 如果没有上述说明,则不是很有帮助。
setsockopt(sock, SOL_SOCKET, SO_DONTLINGER, &"\x00\x00\x00\x00", 4);。
TIME_WAIT 改进是一个绝妙的主意。
当您创建一个套接字时,您并不真正拥有它。操作系统(TCP 堆栈)为您创建它并为您提供一个句柄(文件描述符)来访问它。当您的套接字关闭时,操作系统需要一些时间才能“完全关闭它”,同时它会经历几个状态。正如 cmets 中提到的 EJP,最长的延迟通常来自 TIME_WAIT 状态。这个额外的延迟需要在终止序列的最后处理边缘情况,并确保最后的终止确认要么通过,要么由于超时而让另一端自行重置。 Here you can find 有关此状态的一些额外注意事项。主要考虑如下:
记住 TCP 保证所有传输的数据都会被传递, 如果可能的话。当你关闭一个套接字时,服务器进入一个 TIME_WAIT 状态,只是为了真正确定所有数据都有 经历了。当一个套接字关闭时,双方同意发送 向对方发送消息,表示他们将不再发送数据。这,它 在我看来已经足够好了,握手完成后, 套接字应该关闭。问题是双重的。首先,没有 确保最后一个 ack 成功传达的方法。 二、网上可能会留下“游走重复”,必须 送达后处理。
如果您尝试快速创建具有相同 ip:port 对的多个套接字,您会收到“地址已在使用”错误,因为较早的套接字不会被完全释放。使用 SO_REUSEADDR 将消除此错误,因为它将覆盖对任何先前实例的检查。
【讨论】:
SO_LINGER 时也是如此,即在前台徘徊(阻止@987654323 @) 不包括TIME_WAIT?我知道SO_LINGER 为零将完全避免TIME_WAIT(因为它完全避免终止序列并发送RST)。