SO_REUSEADDR和SO_REUSEPORT在不同OS上的表现

BSD 套接字实现是所有套接字实现之母。基本上所有其他系统都在某个时间点(或至少是它的接口)复制了 BSD 套接字实现,然后开始自行发展它。当然,BSD 套接字实现也在同时发展,因此早期复制它的系统缺乏了一些功能。理解 BSD 套接字实现是理解所有其他套接字实现的关键,因此即使您不关心为 BSD 系统编写代码,也应该阅读它。

1、套接字五元组:

TCP/UDP 连接由五元组标识:

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

这些值的任何唯一组合都标识了一个连接。因此,没有两个连接可以具有相同的五个值,否则系统将无法再区分这些连接。使用 socket() 函数创建套接字时设置套接字的协议。源地址和端口由 bind() 函数设置。目标地址和端口由 connect() 函数设置。由于 UDP 是无连接协议,UDP 套接字可以在不连接它们的情况下使用。然而,它允许连接它们,并且在某些情况下对您的代码和一般应用程序设计非常有利。在无连接模式下,第一次通过它们发送数据时未显式绑定的 UDP 套接字通常由系统自动绑定,因为未绑定的 UDP 套接字无法接收任何(回复)数据。未绑定的 TCP 套接字也是如此,它会在连接之前自动绑定。

显式绑定套接字时,注意一下两种情况:

  • 可以将其绑定到端口 0,即“任何端口”。由于套接字不能真正绑定到所有现有端口,因此在这种情况下系统将不得不自己选择一个特定端口(通常来自预定义的、操作系统特定的源端口范围)。
  • 源地址也存在类似的通配符,它​​可以是“任何地址”(IPv4 的情况下为 0.0.0.0,IPv6 的情况下为 ::)。与端口不同,套接字绑定到“任何地址”意味着“所有本地接口的所有源 IP 地址”。如果套接字稍后连接,系统必须选择特定的源 IP 地址,因为套接字无法连接,同时绑定到任何本地 IP 地址。根据目标地址和路由表的内容,系统将选择一个适当的源地址,并将“任何”绑定替换为与所选源 IP 地址的绑定。

默认情况下,不能将两个套接字绑定到相同的源地址和源端口组合。只要源端口不同,源地址其实是无关紧要的。例如:socketA 属于一个 FTP 服务器程序,绑定到 192.168.0.1:21,socketB 属于另一个 FTP 服务器程序,绑定到 10.0.0.1:21,两个绑定都会成功。但是,如果一个套接字绑定到 0.0.0.0:21,意味着绑定了所有的本地地址,在这种情况下,没有其他套接字可以绑定到端口 21,无论它尝试绑定到哪个特定 IP 地址,如0.0.0.0 与所有现有的本地 IP 地址冲突。

到目前为止所说的任何内容对于所有主要操作系统都几乎相同。当地址重用开始发挥作用时,事情开始变得特定于操作系统。我们从 BSD 开始,因为正如我上面所说,它是所有套接字实现的母亲。

2、BSD下的SO_REUSEADDR

如果在绑定之前在套接字上启用了 SO_REUSEADDR,除非与绑定到完全相同的源地址和端口组合的另一个套接字发生冲突,否则是可以成功绑定套接字。

2.1)SO_REUSEADDR 主要改变了在搜索冲突时处理通配符地址(“任何 IP 地址”)的方式

如果没有 SO_REUSEADDR,将 socketA 绑定到 0.0.0.0:21,然后将 socketB 绑定到 192.168.0.1:21 将失败(出现错误 EADDRINUSE),因为 0.0.0.0 表示“任何本地 IP 地址”,因此所有本地 IP 地址都被视为在使用中通过这个套接字,这也包括 192.168.0.1。使用 SO_REUSEADDR 会成功,因为 0.0.0.0 和 192.168.0.1 不是完全相同的地址,一个是所有本地地址的通配符,另一个是非常具体的本地地址。请注意,无论 socketA 和 socketB 的绑定顺序如何,上面的陈述都是正确的;没有 SO_REUSEADDR 它总是会失败,使用 SO_REUSEADDR 它总是会成功。

 说明:如果第一列显示 ON/OFF,则 SO_REUSEADDR 的值与结果无关。

好的,SO_REUSEADDR 对通配符地址有影响,然而这不是它唯一的效果。还有另一个众所周知的效果,这也是为什么大多数人首先在服务器程序中使用 SO_REUSEADDR 的原因。对于此选项的其他重要用途,我们必须更深入地了解 TCP 协议的工作原理。

2.2)SO_REUSEADDR重用TIME_WAIT连接:

如果 TCP 套接字正在关闭,通常会执行 3 次握手;该序列称为 FIN-ACK。这里的问题是,该序列的最后一个 ACK​​ 可能已经到达另一端,也可能没有到达,只有当它到达时,另一端才会认为套接字已完全关闭。为了防止重复使用地址+端口组合,可能仍然被某些远程对等方认为是打开的,系统在发送最后一个 ACK​​ 后不会立即认为套接字已死,而是将套接字置于通常称为 TIME_WAIT 的状态。它可以处于该状态数分钟(取决于系统的设置)。在大多数系统上,您可以通过启用延迟并将延迟时间设置为 zero1 来绕过该状态,但不能保证这总是可能的,系统将始终支持此请求,即使系统支持它,这也会导致通过重置(RST)关闭套接字(下图),这并不总是一个好主意。要了解有关逗留时间的更多信息,请查看我对此主题的回答

 问题是,系统如何处理处于 TIME_WAIT 状态的套接字?如果未设置 SO_REUSEADDR,则认为处于 TIME_WAIT 状态的套接字仍然绑定到源地址和端口,并且任何将新套接字绑定到相同地址和端口的尝试都将失败,直到套接字真正关闭。所以不要指望你可以在关闭套接字后立即重新绑定它的源地址。在大多数情况下,这将失败。但是,如果为您尝试绑定的套接字设置了 SO_REUSEADDR,则在状态 TIME_WAIT 绑定到相同地址和端口的另一个套接字将被简单地忽略,毕竟它已经“半死”,并且您的套接字可以绑定到完全相同的地址没有任何问题。在这种情况下,另一个套接字可能具有完全相同的地址和端口是没有作用的。请注意,将套接字绑定到与处于 TIME_WAIT 状态的垂死套接字完全相同的地址和端口可能会产生意外的,通常是不希望的副作用,以防另一个套接字仍然“工作”,但这超出了这个答案的范围幸运的是,这些副作用在实践中相当罕见。

总结:

关于 SO_REUSEADDR,您应该了解最后一件事。只要您要绑定的套接字启用了地址重用,上面编写的所有内容都将起作用。另一个套接字(已经绑定或处于 TIME_WAIT 状态的套接字)在绑定时也没有必要设置此标志。决定绑定是成功还是失败的代码只检查输入到 bind() 调用的套接字的 SO_REUSEADDR 标志,对于检查的所有其他套接字,甚至不查看该标志。

3、BSD下的SO_REUSEPORT:

SO_REUSEPORT 是大多数人所期望的 SO_REUSEADDR 。基本上,SO_REUSEPORT 允许您将任意数量的套接字绑定到完全相同的源地址和端口,只要所有先前绑定的套接字在绑定之前也设置了 SO_REUSEPORT。如果绑定到地址和端口的第一个套接字没有设置 SO_REUSEPORT,则任何其他套接字都不能绑定到完全相同的地址和端口,无论这个另一个套接字是否设置了 SO_REUSEPORT,直到第一个套接字释放它的绑定再次。与 SO_REUESADDR 不同,处理 SO_REUSEPORT 的代码不仅会验证当前绑定的套接字是否设置了 SO_REUSEPORT,还会验证地址和端口冲突的套接字在绑定时是否设置了 SO_REUSEPORT。

SO_REUSEPORT 并不会默认设置 SO_REUSEADDR。意味着,如果一个套接字在绑定时没有设置 SO_REUSEPORT,而另一个套接字在绑定到完全相同的地址和端口时即使设置了 SO_REUSEPORT,也会绑定失败。假如一个套接字已经处于 TIME_WAIT 状态,仍然是会绑定失败。为了能够将套接字绑定到与处于 TIME_WAIT 状态的另一个套接字相同的地址和端口,需要在该套接字上设置 SO_REUSEADDR 或在绑定它们之前必须在两个套接字上设置 SO_REUSEPORT。当然,允许在套接字上同时设置 SO_REUSEPORT 和 SO_REUSEADDR。

关于 SO_REUSEPORT 没什么可说的,除了它是在 SO_REUSEADDR 之后添加的,这就是为什么在其他系统的许多套接字实现中找不到它的原因,这些系统在添加此选项之前“分叉”了 BSD 代码,并且有在此选项之前,无法将两个套接字绑定到 BSD 中完全相同的套接字地址。

4、常见问题:

4.1)Connect() Returning EADDRINUSE?

大多数人都知道 bind() 可能会因错误 EADDRINUSE 而失败,但是,当您开始尝试地址重用时,您可能会遇到奇怪的情况,即 connect() 也会因该错误而失败。怎么会这样?一个远程地址,毕竟是连接添加到套接字的,怎么可能已经在使用呢?将多个套接字连接到完全相同的远程地址以前从来都不是问题,那么这里出了什么问题呢?

正如我在回复的开头所说,连接是由五个值的元组定义的,还记得吗?而且我还说过,这五个值必须是唯一的,否则系统无法再区分两个连接,对吧?好吧,通过地址重用,您可以将相同协议的两个套接字绑定到相同的源地址和端口。这意味着这五个值中的三个对于这两个套接字已经相同。如果您现在尝试将这两个套接字也连接到相同的目标地址和端口,您将创建两个连接的套接字,它们的元组完全相同。这行不通,至少对于 TCP 连接不起作用(UDP 连接无论如何都不是真正的连接)。如果数据到达两个连接中的任何一个,系统就无法判断数据属于哪个连接。至少每个连接的目标地址或目标端口必须不同,这样系统就可以毫无问题地识别传入数据属于哪个连接。

因此,如果您将相同协议的两个套接字绑定到相同的源地址和端口,并尝试将它们都连接到相同的目标地址和端口,则 connect() 实际上会失败,并显示您尝试连接的第二个套接字的错误 EADDRINUSE,这意味着已经连接了具有五个值的相同元组的套接字。

4.2)Multicast Addresses

大多数人忽略了多播地址存在的事实,但它们确实存在。单播地址用于一对一通信,而多播地址用于一对多通信。大多数人在了解 IPv6 时就知道了多播地址,但多播地址也存在于 IPv4 中,尽管此功能从未在公共 Internet 上广泛使用。

SO_REUSEADDR 对于多播地址的含义会发生变化,因为它允许将多个套接字绑定到完全相同的源多播地址和端口组合。换句话说,对于多播地址,SO_REUSEADDR 的行为与单播地址的 SO_REUSEPORT 完全相同。实际上,对于多播地址,代码对 SO_REUSEADDR 和 SO_REUSEPORT 的处理方式相同,这意味着您可以说 SO_REUSEADDR 对所有多播地址意味着 SO_REUSEPORT,反之亦然。

5、其他OS的表现:

5.1)FreeBSD/OpenBSD/NetBSD

所有这些都是原始 BSD 代码的较晚分支,这就是为什么它们都提供与 BSD 相同的选项,并且它们的行为方式也与 BSD 相同。

5.2)macOS (MacOS X)

macOS 的核心只是一个名为“Darwin”的 BSD 风格的 UNIX,它基于 BSD 代码(BSD 4.3)的一个相当晚的分支,后来甚至Mac OS 10.3 版本与FreeBSD 5(当时最新的)重新同步代码库,以便 Apple 可以获得完全的 POSIX 合规性(macOS 已通过 POSIX 认证)。尽管在其核心中有一个微内核(“Mach”),但内核的其余部分(“XNU”)基本上就是一个 BSD 内核,这就是为什么 macOS 提供与 BSD 相同的选项并且它们的行为方式与 BSD 相同.

iOS / watchOS / tvOS:

iOS 只是一个 macOS 分支,带有略微修改和修剪的内核,在某种程度上剥离了用户空间工具集和略有不同的默认框架集。 watchOS 和 tvOS 是 iOS 的分支,它们被进一步剥离(尤其是 watchOS)。据我所知,它们的行为都与 macOS 完全一样。

5.3)Linux:

1)Linux < 3.9

在 Linux 3.9 之前,只有 SO_REUSEADDR 选项存在。此选项的行为通常与 BSD 中的行为相同,但有两个重要例外:

  1. 无论是否设置SO_REUSEADDR,服务端TCP套接字不能绑定到通配符地址,然后再绑定到更具体的地址;你可以做的是可以绑定到同一个端口和两个不同的非通配符地址。在这方面,Linux 比 BSD 更具限制性。
  2. 对于客户端套接字,此选项的行为与 BSD 中的 SO_REUSEPORT 完全相同,只要两者在绑定之前都设置了此标志。允许这样做的原因很简单,重要的是能够将多个套接字完全绑定到不同协议的相同 UDP 套接字地址,并且由于在 3.9 之前没有 SO_REUSEPORT,因此 SO_REUSEADDR 的行为被相应地改变以填充那个差距。在这方面,Linux 的限制比 BSD 少。

2)Linux >= 3.9

Linux 3.9 也向 Linux 添加了选项 SO_REUSEPORT。此选项的行为与 BSD 中的选项完全相同,只要所有套接字在绑定之前都设置了此选项,就允许绑定到完全相同的地址和端口号。然而,在其他系统上与 SO_REUSEPORT 仍有两个不同之处:

  1. 为了防止“端口劫持”,有一个特殊的限制:所有想要共享相同地址和端口组合的套接字必须属于共享相同有效用户 ID 的进程!所以一个用户不能“窃取”另一个用户的端口。这是一些特殊的魔法,可以在一定程度上弥补丢失的 SO_EXCLBIND/SO_EXCLUSIVEADDRUSE 标志。
  2. 此外,内核对 SO_REUSEPORT 套接字执行了一些在其他操作系统中没有的“特殊魔法”:对于 UDP 套接字,它尝试平均分配数据报,对于 TCP 侦听套接字,它尝试分配传入的连接请求(通过调用accept()) 均匀地分布在所有共享相同地址和端口组合的套接字上。因此,一个应用程序可以很容易地在多个子进程中打开同一个端口,然后使用 SO_REUSEPORT 来获得非常便宜的负载平衡。

5.4)Android:

尽管整个 Android 系统与大多数 Linux 发行版有些不同,但其核心是稍微修改过的 Linux 内核,因此适用于 Linux 的所有内容也应该适用于 Android。

5.5)Windows:

Windows 只知道 SO_REUSEADDR 选项,没有 SO_REUSEPORT。在 Windows 中的套接字上设置 SO_REUSEADDR 的行为类似于在 BSD 中的套接字上设置 SO_REUSEPORT 和 SO_REUSEADDR,但有一个例外:

在 Windows 2003 之前,具有 SO_REUSEADDR 的套接字始终可以与已绑定的套接字绑定到完全相同的源地址和端口,即使另一个套接字在绑定时没有设置此选项。这种行为允许应用程序“窃取”另一个应用程序的连接端口。不用说,这具有重大的安全隐患!

微软意识到这一点并添加了另一个重要的套接字选项:SO_EXCLUSIVEADDRUSE。在套接字上设置 SO_EXCLUSIVEADDRUSE 可确保如果绑定成功,则源地址和端口的组合由该套接字独占,并且没有其他套接字可以绑定到它们,即使它设置了 SO_REUSEADDR 也是如此。

此默认行为首先在 Windows 2003 中进行了更改,Microsoft 将其称为“增强的套接字安全性”(所有其他主要操作系统默认行为的有趣名称)。有关更多详细信息,请访问此页面。共有三个表:第一个显示经典行为(在使用兼容模式时仍在使用!),第二个显示当 bind() 调用由同一用户进行时 Windows 2003 及更高版本的行为,第三个是 bind() 调用由不同的用户进行。

5.6)Solaris:

Solaris 是 SunOS 的继任者。 SunOS 最初基于 BSD 的一个分支,SunOS 5 和后来基于 SVR4 的一个分支,但是 SVR4 是 BSD、System V 和 Xenix 的合并,因此在某种程度上 Solaris 算是一个比较早的BSD 分支。因此 Solaris 只知道 SO_REUSEADDR,没有 SO_REUSEPORT。 SO_REUSEADDR 的行为与 BSD 中的行为几乎相同。据我所知,在 Solaris 中无法获得与 SO_REUSEPORT 相同的行为,这意味着无法将两个套接字绑定到完全相同的地址和端口。

与 Windows 类似,Solaris 可以选择为套接字提供独占绑定。此选项名为 SO_EXCLBIND。如果在绑定之前在套接字上设置了此选项,则在测试两个套接字的地址冲突时,在另一个套接字上设置 SO_REUSEADDR 无效。例如。如果 socketA 绑定到通配符地址并且 socketB 启用了 SO_REUSEADDR 并且绑定到非通配符地址和与 socketA 相同的端口,则此绑定通常会成功,除非 socketA 启用了 SO_EXCLBIND,在这种情况下,无论 SO_REUSEADDR socketB的标志。

5.7)Other Systems:

如果您的系统未在上面列出,我编写了一个小测试程序,您可以使用它来了解您的系统如何处理这两个选项。构建代码所需的只是一点 POSIX API(用于网络部分)和一个 C99 编译器(实际上,只要它们提供 inttypes.h 和 stdbool.h,大多数非 C99 编译器都可以正常工作;例如 gcc 两者都支持早在提供完整的 C99 支持之前)。 程序需要运行的只是系统中的至少一个接口(本地接口除外)分配了 IP 地址,并设置了使用该接口的默认路由。该程序将收集该 IP 地址并将其用作第二个“特定地址”。 它测试你能想到的所有可能的组合:

  • TCP and UDP protocol
  • Normal sockets, listen (server) sockets, multicast sockets
  • SO_REUSEADDR set on socket1, socket2, or both sockets
  • SO_REUSEPORT set on socket1, socket2, or both sockets
  • All address combinations you can make out of 0.0.0.0 (wildcard), 127.0.0.1 (specific address), and the second specific address found at your primary interface (for multicast it's just 224.1.2.3 in all tests)

并将结果打印在一个漂亮的表格中。它也可以在不知道 SO_REUSEPORT 的系统上工作,在这种情况下,这个选项根本没有经过测试。

程序无法轻易测试的是 SO_REUSEADDR 如何作用于处于 TIME_WAIT 状态的套接字,因为强制并保持套接字处于该状态非常棘手。幸运的是,大多数操作系统在这里似乎只是表现得像 BSD,大多数时候程序员可以简单地忽略该状态的存在。

代码在这里:http://stackoverflow.com/a/14388707/15809, C (gcc) - rextester

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>

#include <errno.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#if defined(__APPLE__) || defined(__bsd__)
#	define HAVE_SA_LEN
#endif

static const uint16_t kTestPort = 23999;

typedef enum {
	SocModeNone = 0,
	SocModeListen,
	SocModeMulticast
} SocketMode;

typedef enum {
	SocReNone = 0,
	SocReAddr1,
	SocReAddr2,
	SocReAddrBoth,
#ifndef SO_REUSEPORT
	SocReLast,
#endif
	SocRePort1,
	SocRePort2,
	SocRePortBoth
#ifdef SO_REUSEPORT
	,
	SocReLast
#endif
} SocketReuse;

static int socketA = -1;
static int socketB = -1;

static
void cleanUp ( ) {
	if (socketA >= 0) close(socketA);
	if (socketB >= 0) close(socketB);
	socketA = -1;
	socketB = -1;
}

static
bool setupUDP ( ) {
	socketA = socket(PF_INET, SOCK_DGRAM, 0);
	socketB = socket(PF_INET, SOCK_DGRAM, 0);
	if (socketA < 0 || socketB < 0) cleanUp();
	return (socketA >= 0);
}

static
bool setupTCP ( ) {
	socketA = socket(PF_INET, SOCK_STREAM, 0);
	socketB = socket(PF_INET, SOCK_STREAM, 0);
	if (socketA < 0 || socketB < 0) cleanUp();
	return (socketA >= 0);
}

static
bool enableSockOpt ( int socket, int option, bool enable ) {
	int yes = (enable ? 1 : 0);
	int err = setsockopt(socket, SOL_SOCKET, option,  &yes, sizeof(yes));
	return !err;
}

static
bool enableReuseAddr ( int socket, bool enable ) {
	return enableSockOpt(socket, SO_REUSEADDR, enable);
}

static
bool enableReusePort ( int socket, bool enable ) {
#ifdef SO_REUSEPORT
	return enableSockOpt(socket, SO_REUSEPORT, enable);
#else
	return true;
#endif
}

static
struct sockaddr_in makeSockaddr (
	const char * localAddr, uint16_t localPort
) {
	struct sockaddr_in addr = { 0 };
	addr.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
	addr.sin_len = sizeof(addr);
#endif
	addr.sin_port = htons((unsigned short)localPort);
	int ok = inet_pton(AF_INET, localAddr, &addr.sin_addr);
	if (!ok) addr.sin_family = AF_UNSPEC;
	return addr;
}

static
bool bindSocket ( int socket, const char * localAddr, uint16_t localPort ) {
	struct sockaddr_in addr = makeSockaddr(localAddr, localPort);
	if (addr.sin_family == AF_UNSPEC) return false;

	int err = bind(socket, (struct sockaddr *)&addr, sizeof(addr));
	return !err;
}

static
bool makeListenSocket ( int socket ) {
	int err = listen(socket, 1);
	return !err;
}


static
bool test (
	SocketMode mode, bool useTCP, SocketReuse reuse,
	const char * localAddress1, const char * localAddress2
) {
	if (useTCP) {
		// TCP cannot be multicast!
		if (mode == SocModeMulticast) { errno = EINVAL; return false; }
		if (!setupTCP()) return false;
	} else {
		// UDP cannot be listen!
		if (mode == SocModeListen) { errno = EINVAL; return false; }
		if (!setupUDP()) return false;
	}

	bool reuseAddr1 = (reuse == SocReAddr1 || reuse == SocReAddrBoth);
	bool reuseAddr2 = (reuse == SocReAddr2 || reuse == SocReAddrBoth);
	bool reusePort1 = (reuse == SocRePort1 || reuse == SocRePortBoth);
	bool reusePort2 = (reuse == SocRePort2 || reuse == SocRePortBoth);

	if (!enableReuseAddr(socketA, reuseAddr1)
		|| !enableReuseAddr(socketB, reuseAddr2)
		|| !enableReusePort(socketA, reusePort1)
		|| !enableReusePort(socketB, reusePort2)
	) {
		cleanUp();
		return false;
	}

	if (!bindSocket(socketA, localAddress1, kTestPort)) {
		cleanUp();
		return false;
	}

	if (mode == SocModeListen) {
		if (!makeListenSocket(socketA)) { cleanUp(); return false; }
	}

	char * modeName = NULL;
	switch (mode) {
		case SocModeNone:      modeName = "(none)   "; break;
		case SocModeListen:    modeName = "Listen   "; break;
		case SocModeMulticast: modeName = "Multicast"; break;
		default: cleanUp(); errno = EINVAL; return false;
	}

	char * reuseName = NULL;
	switch (reuse) {
		case SocReNone:     reuseName = "(none)   "; break;
		case SocReAddr1:    reuseName = "Addr(1)  "; break;
		case SocReAddr2:    reuseName = "Addr(2)  "; break;
		case SocReAddrBoth: reuseName = "Addr(1&2)"; break;
		case SocRePort1:    reuseName = "Port(1)  "; break;
		case SocRePort2:    reuseName = "Port(2)  "; break;
		case SocRePortBoth: reuseName = "Port(1&2)"; break;
		case SocReLast: cleanUp(); errno = EINVAL; return false;
	}

	// INET_ADDRSTRLEN includes terminating \0 in count!
	int padding1 = (int)(INET_ADDRSTRLEN - strlen(localAddress1) - 1);
	int padding2 = (int)(INET_ADDRSTRLEN - strlen(localAddress2) - 1);

	int err = bindSocket(socketB, localAddress2, kTestPort);
	int errNo = (err ? 0 : errno);

	printf(
		"%s  "
		"%s    "
		"%s  "
		"%s%.*s  "
		"%s%.*s  "
		"->  %s%s%s%s\n",
		modeName,
		useTCP  ? "TCP" : "UDP",
		reuseName,
		localAddress1, padding1, "                ",
		localAddress2, padding2, "                ",
		errNo == 0 ? "OK" : "Error!",
		errNo == 0 ? "" : " (",
		errNo == 0 ? "" : strerror(errno),
		errNo == 0 ? "" : ")"
	);
	cleanUp();
	return true;
}

static
void testAndFailOnCriticalError (
	SocketMode mode, bool useTCP, SocketReuse reuse,
	const char * localAddress1, const char * localAddress2
) {
	bool ok = test(
		mode, useTCP, reuse,
		localAddress1, localAddress2
	);
	if (!ok) {
		fprintf(stderr, "Critical error setting up test! (%s)\n",
			strerror(errno)
		);
		exit(EXIT_FAILURE);
	}
}


static
char * copyPrimaryAddress ( ) {
	int so = socket(PF_INET, SOCK_DGRAM, 0);
	if (so < 0) return NULL;

	struct sockaddr_in addr = makeSockaddr("8.8.8.8", 443);
	if (addr.sin_family == AF_UNSPEC) { close(so); return false; }

	int err = connect(so, (struct sockaddr *)&addr, sizeof(addr));
	if (err) { close(so); return NULL; }

	socklen_t len = sizeof(addr);
	err = getsockname(so, (struct sockaddr *)&addr, &len);
	close(so);
	if (err) return NULL;

	// INET_ADDRSTRLEN includes terminating \0 in count!
	char buffer[INET_ADDRSTRLEN] = { 0 };
	const char * res = inet_ntop(
		AF_INET, &addr.sin_addr, buffer, sizeof(buffer)
	);
	if (!res) return NULL;

	// Apparently `strdup()` is not as potrable as one might expect
	size_t resultLength = strlen(res) + 1;
	char * result = malloc(resultLength);
	if (!result) return NULL;

	memcpy(result, res, resultLength);
	return result;
}


int main (
	int argc, const char * argv[]
) {
	const char *const localAddress = "127.0.0.1";
	const char *const wildcardAddress = "0.0.0.0";
	const char *const multicastAddress = "224.1.2.3";

#ifndef SO_REUSEPORT
	printf("WARNING: SO_REUESPORT is not available! "
		"Tests requiring it will just be skipped.\n"
	);
#endif

	printf("Test port is %"PRIu16"...\n", kTestPort);

	char * primaryAddress = copyPrimaryAddress();
	if (!primaryAddress) {
		fprintf(stderr, "Cannot obtain primary interface address!\n");
		return EXIT_FAILURE;
	}
	printf("Primary address: %s...\n", primaryAddress);

	if (strcmp(primaryAddress, localAddress) == 0) {
		fprintf(stderr, "Local address must not be primary address!");
		return EXIT_FAILURE;
	}

	const char *const sourceAddresses[] = {
		wildcardAddress, localAddress, primaryAddress
	};
	size_t addressCount = sizeof(sourceAddresses) / sizeof(sourceAddresses[0]);

	printf(
		"MODE       PROTO  REUSE      "
		"ADDRESS1         ADDRESS2         -> RESULT\n"
	);

	// Test every combinations but multicast
	SocketMode mode;
	for (mode = SocModeNone; mode < SocModeMulticast; mode++) {

		for (int proto = 0; proto < 2; proto++) {
			bool useTCP = (proto == 0);
			// UDP cannot be listen
			if (!useTCP && mode == SocModeListen) continue;

			SocketReuse reuse;
			for (reuse = SocReNone; reuse < SocReLast; reuse++) {

				size_t addr1;
				for (addr1 = 0; addr1 < addressCount; addr1++) {

					size_t addr2;
					for (addr2 = 0; addr2 < addressCount; addr2++) {

						// UDP cannot be listen!
						if (!useTCP && mode == SocModeListen) continue;

						testAndFailOnCriticalError(
							mode, useTCP, reuse,
							sourceAddresses[addr1], sourceAddresses[addr2]
						);
					}
				}
			}
		}
	}

	// Test all multicast combinations
	SocketReuse reuse;
	for (reuse = SocReNone; reuse < SocReLast; reuse++) {

		size_t addr1;
		for (addr1 = 0; addr1 < addressCount; addr1++) {

			size_t addr2;
			for (addr2 = 0; addr2 < addressCount; addr2++) {

				testAndFailOnCriticalError(
					SocModeMulticast, false, reuse,
					multicastAddress, multicastAddress
				);
			}
		}
	}

	return EXIT_SUCCESS;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值