SO_REUSEADDR(setsockopt 选项)的含义是什么 - Linux?
从手册页:
SO_REUSEADDR 指定规则 用于验证提供的地址 to bind() 应该允许重用本地 地址,如果支持的话 协议。该选项需要一个 int 价值。这是一个布尔选项
我什么时候应该使用它?为什么“重用本地地址”会给出?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
TCP 的主要设计目标是在出现数据包丢失、数据包重新排序以及(这里很关键)数据包重复的情况下实现可靠的数据通信。
当连接建立时,TCP/IP 网络堆栈如何处理所有这些问题是相当明显的,但在连接关闭后会发生一种边缘情况。如果在对话结束时发送的数据包被重复并延迟,会发生什么情况,导致 4-关闭数据包在延迟数据包之前到达接收器的方式?堆栈尽职尽责地关闭其连接。然后,延迟的重复数据包出现。堆栈应该做什么?
更重要的是,如果一个在给定 IP 地址 + TCP 端口组合上打开套接字的程序关闭了其套接字,然后不久之后,一个程序出现并想要侦听相同的 IP 地址和 TCP 端口号,那么应该怎么做? (典型情况:程序被终止并快速重新启动。)
有几种选择:
禁止重复使用该 IP/端口组合,时间至少为数据包传输最大时间的 2 倍。在 TCP 中,这通常称为 2×MSL 延迟。有时您还会看到 2×RTT,大致相当。
这是所有常见 TCP/IP 堆栈的默认行为。 2×MSL 通常在 30 到 120 秒之间,它在
netstat
输出中显示为TIME_WAIT
期间。在此之后,堆栈假定由于 在途中所有恶意数据包均已被丢弃>TTL,以便套接字离开TIME_WAIT
状态,从而允许重用该 IP/端口组合。允许新程序重新绑定到该 IP/端口组合。在具有 BSD 套接字 接口的堆栈中 — 基本上所有 Unix 和类 Unix 系统,以及通过 Winsock — 您必须通过设置
SO_REUSEADDR
选项来请求此行为a href="http://linux.die.net/man/2/setsockopt" rel="noreferrer">setsockopt()
在调用bind()
。SO_REUSEADDR
最常在网络服务器程序中设置,因为常见的使用模式是进行配置更改,然后需要重新启动该程序以使更改生效。如果没有SO_REUSEADDR
,重新启动的程序的新实例中的bind()
调用将会失败,如果在您终止前一个实例时存在打开的连接。这些连接将使 TCP 端口保持在 TIME_WAIT 状态 30-120 秒,因此您属于上述情况 1。设置 SO_REUSEADDR 的风险在于它会产生歧义:TCP 数据包标头中的元数据不够唯一,堆栈无法可靠地判断数据包是否过时,因此应该将其丢弃而不是丢弃传递到新侦听器的套接字,因为它显然是针对现已死亡的侦听器的。
如果您不认为这是真的,那么以下是侦听计算机的 TCP/IP 堆栈必须与每个连接一起工作以做出该决定的所有内容:
本地 IP: 每个连接不是唯一的。事实上,我们这里的问题定义表明我们有意重用本地 IP。
本地 TCP 端口:同上。
远程 IP:导致歧义的计算机可能会重新连接,因此这无助于消除数据包正确目的地的歧义。
远程端口:在运行良好的网络堆栈中,传出连接的远程端口不会很快被重用,但它只有 16 位,因此您有 30-120 秒的时间强制重用堆栈通过数万种选择并重用端口。早在 20 世纪 60 年代,计算机就可以那么快地完成工作。
如果您对此的回答是远程堆栈应该在其一侧执行类似
TIME_WAIT
的操作以禁止 临时 TCP 端口 重用,该解决方案假定远程主机是良性的。恶意行为者可以随意重用该远程端口。我认为侦听器堆栈可以选择严格禁止仅来自 TCP 4 元组的连接,以便在 TIME_WAIT 状态期间阻止给定远程主机与同一远程临时端口重新连接,但我不知道有任何 TCP 堆栈具有这种特定的改进。
本地和远程 TCP 序列号:这些序列号也不够独特,新的远程程序无法得出相同的值。
如果我们今天重新设计 TCP,我认为我们会集成 TLS 或类似的东西一项非可选功能,其作用之一是使这种无意和恶意的连接劫持成为不可能。这需要添加大字段(128 位及以上),这在 1981 年根本不切实际,当时 TCP 当前版本的文档 (RFC 793)已发布。
如果没有这样的强化,由于允许在 TIME_WAIT 期间重新绑定而产生的歧义意味着您可以 a) 将用于旧侦听器的陈旧数据错误地传递到属于新侦听器的套接字,从而破坏侦听器的协议或错误地将过时的数据注入连接;或者 b) 新侦听器套接字的新数据被错误地分配给旧侦听器的套接字,从而无意中被丢弃。
安全的做法是等待
TIME_WAIT
期限结束。最终,这取决于成本的选择:等待
TIME_WAIT
期限结束,或者承担不必要的数据丢失或意外数据注入的风险。许多服务器程序都冒着这种风险,认为最好立即恢复服务器,以免错过任何不必要的传入连接。
这不是一个普遍的选择。许多程序(甚至是需要重新启动才能应用设置更改的服务器程序)都会选择保留
SO_REUSEADDR
。程序员可能知道这些风险并选择不理会默认设置,或者他们可能不知道这些问题但正在从明智的默认设置中受益。某些网络程序为用户提供配置选项的选择,从而将责任推卸给最终用户或系统管理员。
TCP's primary design goal is to allow reliable data communication in the face of packet loss, packet reordering, and — key, here — packet duplication.
It's fairly obvious how a TCP/IP network stack deals with all this while the connection is up, but there's an edge case that happens just after the connection closes. What happens if a packet sent right at the end of the conversation is duplicated and delayed, such that the 4-way shutdown packets get to the receiver before the delayed packet? The stack dutifully closes down its connection. Then later, the delayed duplicate packet shows up. What should the stack do?
More importantly, what should it do if a program with open sockets on a given IP address + TCP port combo closes its sockets, and then a brief time later, a program comes along and wants to listen on that same IP address and TCP port number? (Typical case: A program is killed and is quickly restarted.)
There are a couple of choices:
Disallow reuse of that IP/port combo for at least 2 times the maximum time a packet could be in flight. In TCP, this is usually called the 2×MSL delay. You sometimes also see 2×RTT, which is roughly equivalent.
This is the default behavior of all common TCP/IP stacks. 2×MSL is typically between 30 and 120 seconds, and it shows up in
netstat
output as theTIME_WAIT
period. After that time, the stack assumes that any rogue packets have been dropped en route due to expired TTLs, so that socket leaves theTIME_WAIT
state, allowing that IP/port combo to be reused.Allow the new program to re-bind to that IP/port combo. In stacks with BSD sockets interfaces — essentially all Unixes and Unix-like systems, plus Windows via Winsock — you have to ask for this behavior by setting the
SO_REUSEADDR
option viasetsockopt()
before you callbind()
.SO_REUSEADDR
is most commonly set in network server programs, since a common usage pattern is to make a configuration change, then be required to restart that program to make the change take effect. WithoutSO_REUSEADDR
, thebind()
call in the restarted program's new instance will fail if there were connections open to the previous instance when you killed it. Those connections will hold the TCP port in theTIME_WAIT
state for 30-120 seconds, so you fall into case 1 above.The risk in setting
SO_REUSEADDR
is that it creates an ambiguity: the metadata in a TCP packet's headers isn't sufficiently unique that the stack can reliably tell whether the packet is stale and so should be dropped rather than be delivered to the new listener's socket because it was clearly intended for a now-dead listener.If you don't see that that is true, here's all the listening machine's TCP/IP stack has to work with per-connection to make that decision:
Local IP: Not unique per-conn. In fact, our problem definition here says we're reusing the local IP, on purpose.
Local TCP port: Ditto.
Remote IP: The machine causing the ambiguity could re-connect, so that doesn't help disambiguate the packet's proper destination.
Remote port: In well-behaved network stacks, the remote port of an outgoing connection isn't reused quickly, but it's only 16 bits, so you've got 30-120 seconds to force the stack to get through a few tens of thousands of choices and reuse the port. Computers could do work that fast back in the 1960s.
If your answer to that is that the remote stack should do something like
TIME_WAIT
on its side to disallow ephemeral TCP port reuse, that solution assumes that the remote host is benign. A malicious actor is free to reuse that remote port.I suppose the listener's stack could choose to strictly disallow connections from the TCP 4-tuple only, so that during the
TIME_WAIT
state a given remote host is prevented from reconnecting with the same remote ephemeral port, but I'm not aware of any TCP stack with that particular refinement.Local and remote TCP sequence numbers: These are also not sufficiently unique that a new remote program couldn't come up with the same values.
If we were re-designing TCP today, I think we'd integrate TLS or something like it as a non-optional feature, one effect of which is to make this sort of inadvertent and malicious connection hijacking impossible. That requires adding large fields (128 bits and up) which wasn't at all practical back in 1981, when the document for the current version of TCP (RFC 793) was published.
Without such hardening, the ambiguity created by allowing re-binding during
TIME_WAIT
means you can either a) have stale data intended for the old listener be misdelivered to a socket belonging to the new listener, thereby either breaking the listener's protocol or incorrectly injecting stale data into the connection; or b) new data for the new listener's socket mistakenly assigned to the old listener's socket and thus inadvertently dropped.The safe thing to do is wait out the
TIME_WAIT
period.Ultimately, it comes down to a choice of costs: wait out the
TIME_WAIT
period or take on the risk of unwanted data loss or inadvertent data injection.Many server programs take this risk, deciding that it's better to get the server back up immediately so as to not miss any more incoming connections than necessary.
This is not a universal choice. Many programs — even server programs requiring a restart to apply a settings change — choose instead to leave
SO_REUSEADDR
alone. The programmer may know these risks and is choosing to leave the default alone, or they may be ignorant of the issues but are getting the benefit of a wise default.Some network programs offer the user a choice among the configuration options, fobbing the responsibility off on the end user or sysadmin.
此套接字选项告诉内核,即使此端口正忙(处于 TIME_WAIT 状态),仍要继续并重用它。如果它很忙,但处于其他状态,您仍然会收到地址已在使用中的错误。如果您的服务器已关闭,然后立即重新启动,而其端口上的套接字仍处于活动状态,则此功能非常有用。
来自 unixguide.net
This socket option tells the kernel that even if this port is busy (in the TIME_WAIT state), go ahead and reuse it anyway. If it is busy, but with another state, you will still get an address already in use error. It is useful if your server has been shut down, and then restarted right away while sockets are still active on its port.
From unixguide.net
当您创建套接字时,您并不真正拥有它。操作系统(TCP 堆栈)为您创建它,并为您提供一个句柄(文件描述符)来访问它。当您的套接字关闭时,操作系统需要一段时间才能“完全关闭它”,同时它会经历多个状态。正如EJP在评论中提到的,最长的延迟通常来自TIME_WAIT状态。需要这种额外的延迟来处理终止序列最后的边缘情况,并确保最后的终止确认已通过或使另一方因超时而重置自身。 在这里您可以找到有关此状态的一些额外注意事项。主要考虑因素如下:
如果您尝试快速创建具有相同 ip:port 对的多个套接字,则会收到“地址已在使用中”错误,因为较早的套接字尚未完全释放。使用 SO_REUSEADDR 将消除此错误,因为它将覆盖对任何先前实例的检查。
When you create a socket, you don't really own it. The OS (TCP stack) creates it for you and gives you a handle (file descriptor) to access it. When your socket is closed, it take time for the OS to "fully close it" while it goes through several states. As EJP mentioned in the comments, the longest delay is usually from the TIME_WAIT state. This extra delay is required to handle edge cases at the very end of the termination sequence and make sure the last termination acknowledgement either got through or had the other side reset itself because of a timeout. Here you can find some extra considerations about this state. The main considerations are pointed out as follow :
If you try to create multiple sockets with the same ip:port pair really quick, you get the "address already in use" error because the earlier socket will not have been fully released. Using SO_REUSEADDR will get rid of this error as it will override checks for any previous instance.