使用SO_REUSEADDR和SO_EXCLUSIVEADDRUSE

开发安全的高级网络基础结构是大多数网络应用程序开发人员的首要任务。 但是,尽管在考虑完全安全的解决方案时非常关键,但套接字安全性经常被忽视。 具体而言,套接字安全性涉及绑定到以前由另一个应用程序进程绑定的相同端口的进程。 过去,网络应用程序可能会“劫持”另一个应用程序的端口,这很容易导致“拒绝服务”攻击或数据盗窃。

通常,套接字安全性适用于服务器端进程。 更具体地说,套接字安全性适用于接受连接并接收 IP 数据报流量的任何网络应用程序。 这些应用程序通常绑定到已知端口,并且是恶意网络代码的常见目标。

客户端应用程序不太可能成为此类攻击的目标,不是因为它们不太容易受到攻击,而是因为大多数客户端绑定到“临时”本地端口,而不是静态“服务”端口。 客户端网络应用程序应始终绑定到 (的临时端口,方法是在调用绑定函数) 时,在 name 参数指向的 SOCKADDR 结构中指定端口 0,除非有令人信服的体系结构理由不这样做。 临时本地端口由大于端口 49151 的端口组成。 专用服务的大多数服务器应用程序绑定到小于或等于端口 49151 的已知保留端口。 因此,对于大多数应用程序,客户端和服务器应用程序之间的绑定请求通常不存在冲突。

本部分介绍各种 Microsoft Windows 平台上的默认安全级别,以及特定套接字选项 SO_REUSEADDRSO_EXCLUSIVEADDRUSE 如何影响网络应用程序安全性。 Windows Server 2003 及更高版本上提供了名为增强套接字安全性的其他功能。 这些套接字选项的可用性和增强的套接字安全性因 Microsoft 操作系统版本而异,如下表所示。

平台 SO_REUSEADDR SO_EXCLUSIVEADDRUSE 增强的套接字安全性
Windows 95 可用 不可用 不可用
Windows 98 可用 不可用 不可用
Windows Me 可用 不可用 不可用
Windows NT 4.0 可用 在 Service Pack 4 及更高版本中可用 不可用
Windows 2000 可用 可用 不可用
Windows XP 可用 可用 不可用
Windows Server 2003 可用 可用 可用
Windows Vista 可用 可用 可用
Windows Server 2008 可用 可用 可用
Windows 7 和更新版本 可用 可用 可用

使用 SO_REUSEADDR

SO_REUSEADDR套接字选项允许套接字强行绑定到另一个套接字正在使用的端口。 第二个套接字调用 setsockopt ,其中 optname 参数设置为 SO_REUSEADDRoptval 参数设置为 TRUE ,然后调用与原始套接字相同的端口上的 绑定 。 成功绑定第二个套接字后,绑定到该端口的所有套接字的行为都是不确定的。 例如,如果同一端口上的所有套接字都提供 TCP 服务,则通过该端口传入的任何 TCP 连接请求都不能保证由正确的套接字处理,该行为是不确定的。 恶意程序可以使用 SO_REUSEADDR 强行绑定已用于标准网络协议服务的套接字,以拒绝访问这些服务。 使用此选项不需要任何特殊权限。

如果客户端应用程序在服务器应用程序能够绑定到同一端口之前绑定到某个端口,则可能会导致问题。 如果服务器应用程序使用 SO_REUSEADDR 套接字选项强行绑定到同一端口,则绑定到该端口的所有套接字的行为是不确定的。

此非确定性行为的例外是多播套接字。 如果两个套接字绑定到同一接口和端口,并且是同一多播组的成员,则数据将传递到这两个套接字,而不是任意选择的套接字。

使用 SO_EXCLUSIVEADDRUSE

在引入 SO_EXCLUSIVEADDRUSE 套接字选项之前,网络应用程序开发人员几乎无法阻止恶意程序绑定到网络应用程序在其上绑定自己的套接字的端口。 为了解决此安全问题,Windows 套接字引入了 SO_EXCLUSIVEADDRUSE 套接字选项,该选项在 service Pack 4 (SP4) 及更高版本的 Windows NT 4.0 上可用。

SO_EXCLUSIVEADDRUSE套接字选项只能由 Windows XP 及更早版本上的管理员安全组的成员使用。 本文稍后将讨论 Windows Server 2003 及更高版本上更改此要求的原因。

SO_EXCLUSIVEADDRUSE选项是通过调用 setsockopt 函数来设置的,其中 optname 参数设置为 SO_EXCLUSIVEADDRUSE,并在绑定套接字之前将 optval 参数设置为 TRUE 的布尔值。 设置 选项后,后续 绑定 调用的行为因每个 绑定 调用中指定的网络地址而异。

下表描述了当第二个套接字尝试绑定到之前使用特定套接字选项绑定到第一个套接字绑定到的地址时,Windows XP 及更早版本中发生的行为。

注意

在下表中,“通配符”表示给定协议 (的通配符地址,例如 IPv4 的“0.0.0.0”和“::”表示 IPv6) 。 “Specific”表示分配了接口的特定 IP 地址。 表单元格指示绑定是否成功 (“Success”) 还是 WSAEADDRINUSE 错误 (“INUSE”返回错误; WSAEACCES 错误) 的“ACCESS”。

第一个 绑定 调用 第二个 绑定 调用
默认 SO_REUSEADDR SO_EXCLUSIVEADDRUSE
通配符 特定 通配符 特定 通配符 特定
默认 通配符 INUSE INUSE 成功 成功 INUSE INUSE
特定 INUSE INUSE 成功 成功 INUSE INUSE
SO_REUSEADDR 通配符 INUSE INUSE 成功 成功 INUSE INUSE
特定 INUSE INUSE 成功 成功 INUSE INUSE
SO_EXCLUSIVEADDRUSE 通配符 INUSE INUSE ACCESS ACCESS INUSE INUSE
特定 INUSE INUSE ACCESS ACCESS INUSE INUSE

当两个套接字绑定到同一端口号但在不同的显式接口上时,不存在冲突。 例如,在计算机具有两个 IP 接口的情况下, 10.0.0.1 和 10.99.99.99.99,如果对 绑定 的第一次调用是 10.0.0.1,并且端口设置为 5150,并且 指定了SO_EXCLUSIVEADDRUSE ,则第二次 调用在 10.99.99.99 绑定时,端口也设置为 5150,并且指定了任何选项都将成功。 但是,如果第一个套接字绑定到通配符地址和端口 5150,则通过设置SO_EXCLUSIVEADDRUSE对端口 5150 的任何后续绑定调用都将失败,绑定操作返回WSAEADDRINUSEWSAEACCES

如果对 绑定 的第一次调用设置 SO_REUSEADDR 或根本没有套接字选项,则第二个 绑定 调用将“劫持”端口,应用程序将无法确定两个套接字中的哪一个接收了发送到“共享”端口的特定数据包。

调用 绑定 函数的典型应用程序不会分配绑定套接字供独占使用,除非在调用绑定函数之前在套接字上调用 SO_EXCLUSIVEADDRUSE套接字 选项。 如果客户端应用程序在服务器应用程序绑定到同一端口之前绑定到临时端口或特定端口,则可能会导致问题。 在调用绑定函数之前,服务器应用程序可以通过在套接字上使用 SO_REUSEADDR 套接字选项强行绑定到同一端口,但随后绑定到该端口的所有套接字的行为是不确定的。 如果服务器应用程序尝试使用 SO_EXCLUSIVEADDRUSE 套接字选项独占使用端口,则请求将失败。

相反,设置 SO_EXCLUSIVEADDRUSE 的套接字在套接字关闭后不一定立即重复使用。 例如,如果具有 SO_EXCLUSIVEADDRUSE 集的侦听套接字接受连接,然后随后关闭,另一个套接字 (也具有 SO_EXCLUSIVEADDRUSE) 无法绑定到与第一个套接字相同的端口,直到原始连接变为非活动状态。

此问题可能变得复杂,因为即使套接字已关闭,基础传输协议也可能不会终止连接。 即使在应用程序关闭套接字之后,系统也必须传输任何缓冲数据,向对等方发送正常断开连接消息,并等待来自对等方对应的正常断开连接消息。 基础传输协议可能永远不会释放连接;例如,参与原始连接的对等方可能会播发零大小窗口或某种其他形式的“攻击”配置。 在这种情况下,尽管请求关闭客户端连接,但客户端连接仍保持活动状态,因为未确认的数据保留在缓冲区中。

若要避免这种情况,网络应用程序应通过调用设置SD_SEND标志的关闭来确保正常 关闭 ,然后在 recv 循环中等待,直到通过连接返回零个字节。 这可以保证对等方接收所有数据,并同样向对等方确认它已接收所有传输的数据,并避免上述端口重用问题。

可以在套接字上设置SO_LINGER套接字选项,以防止端口转换为“活动”等待状态;但是,不建议这样做,因为它可能会导致意外的影响,例如重置连接。 例如,如果数据由对等方接收,但数据仍然未被其确认,并且本地计算机关闭了套接字并设置了SO_LINGER,则两台计算机之间的连接将重置,并且对等方放弃未确认的数据。 选择适当的延迟时间是困难的,因为较小的超时值通常会导致连接突然中止,而较大的超时值会使系统容易受到拒绝服务攻击, (建立许多连接,并可能停止/阻止应用程序线程) 。 关闭具有非零后滞超时值的套接字还可能导致 对 closesocket 调用受阻。

增强的套接字安全性

Windows Server 2003 版本增加了增强的套接字安全性。 在以前的 Microsoft 服务器操作系统版本中,默认套接字安全性很容易允许进程从不知情的应用程序劫持端口。 在 Windows Server 2003 中,套接字默认不处于可共享状态。 因此,如果应用程序希望允许其他进程重用已绑定套接字的端口,则必须专门启用它。 如果是这种情况,在端口上调用 绑定 的第一个套接字必须在套接字上设置 SO_REUSEADDR 。 当第二次绑定调用由对绑定进行原始调用的同一用户帐户执行时,会发生这种情况的唯一例外。 此异常的存在只是为了提供向后兼容性。

下表描述了当第二个套接字尝试绑定到以前使用特定套接字选项由第一个套接字绑定到的地址时,Windows Server 2003 及更高版本的操作系统中发生的行为。

注意

在下表中,“通配符”表示给定协议 (的通配符地址,例如 IPv4 的“0.0.0.0”,对于 IPv6) 为“::”。 “Specific”表示分配了接口的特定 IP 地址。 表单元格指示绑定是否成功 (“Success”) ,还是 WSAEADDRINUSE 错误 (“INUSE”返回的错误; WSAEACCES 错误) 的“ACCESS”。

另请注意,在此特定表中,这两个 绑定 调用在同一用户帐户下进行。

第一次 绑定 调用 第二次 绑定 调用
默认 SO_REUSEADDR SO_EXCLUSIVEADDRUSE
通配符 特定 通配符 特定 通配符 特定
默认 通配符 INUSE 成功 ACCESS 成功 INUSE 成功
特定 成功 INUSE 成功 ACCESS INUSE INUSE
SO_REUSEADDR 通配符 INUSE 成功 成功 成功 INUSE 成功
特定 成功 INUSE 成功 成功 INUSE INUSE
SO_EXCLUSIVEADDRUSE 通配符 INUSE ACCESS ACCESS ACCESS INUSE ACCESS
特定 成功 INUSE 成功 ACCESS INUSE INUSE

上表中的一些条目值得解释。

例如,如果第一个调用方在特定地址上设置了 SO_EXCLUSIVEADDRUSE ,而第二个调用方尝试在同一端口上使用通配符地址调用 绑定 ,则第二个 绑定 调用将成功。 在此特定情况下,第二个调用方绑定到除第一个调用方绑定到的特定地址之外的所有接口。 请注意,这种情况的相反情况并不成立:如果第一个调用方设置 SO_EXCLUSIVEADDRUSE 并使用通配符标志调用 bind ,则第二个调用方无法使用相同的端口调用 bind

在不同的用户帐户下进行套接字绑定调用时,套接字绑定行为会更改。 下表指定了当第二个套接字尝试绑定到使用特定套接字选项和其他用户帐户绑定到之前由第一个套接字绑定到的地址时,Windows Server 2003 及更高版本的操作系统中发生的行为。

第一个 绑定 调用 第二个 绑定 调用
默认 SO_REUSEADDR SO_EXCLUSIVEADDRUSE
通配符 特定 通配符 特定 通配符 特定
默认 通配符 INUSE ACCESS ACCESS ACCESS INUSE ACCESS
特定 成功 INUSE 成功 ACCESS INUSE INUSE
SO_REUSEADDR 通配符 INUSE ACCESS 成功 成功 INUSE ACCESS
特定 成功 INUSE 成功 成功 INUSE INUSE
SO_EXCLUSIVEADDRUSE 通配符 INUSE ACCESS ACCESS ACCESS INUSE ACCESS
特定 成功 INUSE 成功 ACCESS INUSE INUSE

请注意,当 绑定 调用在不同的用户帐户下时,默认行为是不同的。 如果第一个调用方未在套接字上设置任何选项并绑定到通配符地址,则第二个调用方无法设置 SO_REUSEADDR 选项并成功绑定到同一端口。 未设置任何选项的默认行为也会返回错误。

在 Windows Vista 及更高版本上,可以创建同时通过 IPv6 和 IPv4 运行的双堆栈套接字。 将双堆栈套接字绑定到通配符地址时,在 IPv4 和 IPv6 网络堆栈上保留给定端口,如果设置了) ,则与 SO_REUSEADDRSO_EXCLUSIVEADDRUSE (关联的检查。 这些检查必须在两个网络堆栈上成功。 例如,如果双堆栈 TCP 套接字设置 SO_EXCLUSIVEADDRUSE 然后尝试绑定到端口 5000,则以前不能将其他 TCP 套接字绑定到端口 5000 (通配符或特定) 。 在这种情况下,如果 IPv4 TCP 套接字以前绑定到端口 5000 上的环回地址,则双堆栈套接字的 绑定 调用将失败并出现 WSAEACCES

应用程序策略

开发在套接字层上运行的网络应用程序时,请务必考虑所需的套接字安全性类型。 客户端应用程序(连接数据或将数据发送到服务的应用程序)很少需要任何其他步骤,因为它们绑定到随机本地 (临时) 端口。 如果客户端需要特定的本地端口绑定才能正常运行,则必须考虑套接字安全性。

除了多播套接字之外, SO_REUSEADDR 选项在普通应用程序中几乎没有用处,因为多播套接字将数据传送到绑定在同一端口上的所有套接字。 否则,任何设置此套接字选项的应用程序都应重新设计以删除依赖项,因为它非常容易受到“套接字劫持”的影响。 只要 可以使用 SO_REUSEADDR 套接字选项来劫持服务器应用程序中的端口,应用程序就必须被视为不安全。

所有服务器应用程序都必须设置 SO_EXCLUSIVEADDRUSE ,才能获得强大的套接字安全性级别。 它不仅可防止恶意软件劫持端口,还指示是否将另一个应用程序绑定到请求的端口。 例如,如果另一个进程当前绑定到特定 接口上的同 一端口,则通过设置了 SO_EXCLUSIVEADDRUSE 套接字选项的进程对通配符地址的调用将失败。

最后,尽管 Windows Server 2003 中的套接字安全性已得到改进,但应用程序应始终设置 SO_EXCLUSIVEADDRUSE 套接字选项,以确保它绑定到进程请求的所有特定接口。 Windows Server 2003 中的套接字安全性为旧版应用程序增加了更高的安全性级别,但应用程序开发人员在设计产品时仍必须考虑到安全性的各个方面。