【问题标题】:MacOS SO_REUSEADDR/SO_REUSEPORT not consistent with Linux?MacOS SO_REUSEADDR/SO_REUSEPORT 与 Linux 不一致?
【发布时间】:2019-01-30 13:20:22
【问题描述】:

考虑这段代码:

#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

#define SERVADDR "::1"
#define PORT 12345

int main() {
    int sd = -1;

    if ((sd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
        fprintf(stderr, "socket() failed: %d", errno);
        exit(1);
    }

    int flag = 1;
    if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1) {
        fprintf(stderr, "Setsockopt %d, SO_REUSEADDR failed with errno %d\n", sd, errno);
        exit(2);
    }
    if(setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag)) == -1) {
        fprintf(stderr, "Setsockopt %d, SO_REUSEPORT failed with errno %d\n", sd, errno);
        exit(3);
    }

    struct sockaddr_in6 addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin6_family = AF_INET6;
    addr.sin6_port = htons(23456);

    if(bind(sd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        fprintf(stderr, "Bind %d failed with errno %d: %s\n", sd, errno, strerror(errno));
        exit(4);
    }

    struct sockaddr_in6 server_addr;
    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin6_family = AF_INET6;
    inet_pton(AF_INET6, SERVADDR, &server_addr.sin6_addr);
    server_addr.sin6_port = htons(PORT);

    if (connect(sd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        fprintf(stderr, "Connect %d failed with errno %d: %s\n", sd, errno, strerror(errno));
        exit(5);
    }

    printf("Seems like it worked this time!\n");
    close(sd);
}

很简单:

  • 创建套接字
  • 设置SO_REUSEADDR
  • 设置SO_REUSEPORT
  • 绑定本地端口23456
  • 在端口12345 上连接到::1

奇怪的是,在 MacOS 上连续运行会导致以下情况:

$ for i in {1..5}; do ./ipv6; done
Seems like it worked this time!
Connect 3 failed with errno 48: Address already in use
Connect 3 failed with errno 48: Address already in use
Connect 3 failed with errno 48: Address already in use
Connect 3 failed with errno 48: Address already in use
$

虽然在 Linux 上运行它似乎工作得很好:

$ for i in {1..5}; do ./ipv6; done
Seems like it worked this time!
Seems like it worked this time!
Seems like it worked this time!
Seems like it worked this time!
Seems like it worked this time!
$

我在端口 12345 上有一个监听器:

$ nc -6 -l -v -p12345 -k

这不仅限于 IPv6,尝试了与 IPv4 相同的事情 - 相同的行为。

谁能解释一下?

我之前认为它在 bind() 中失败,但它在 connect() 中。

编辑#1

根据this - 适用于BSD:

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

所以这是有道理的,为什么这不起作用。如果这怎么可能在 Linux 上实际运行,那有什么没有意义的呢?

我当然希望在 MacOS 上完成这项工作,但我目前觉得这可能是不可能的 - 但我仍然想了解 Linux 是如何做到的。

【问题讨论】:

    标签: c macos sockets raw-sockets


    【解决方案1】:

    是的,Linux 实现与大多数其他操作系统不同。你可以找到详尽的解释here。引用具体部分:

    Linux 3.9 也向 Linux 添加了选项 SO_REUSEPORT。此选项的行为与 BSD 中的选项完全相同,只要所有套接字在绑定之前都设置了此选项,就允许绑定到完全相同的地址和端口号。

    然而,在其他系统上与 SO_REUSEPORT 仍有两个不同之处:

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

    【讨论】:

    • 我通读了整篇文章——内容丰富。它解释了为什么在 BSD 上 connect() 失败(我认为 MacOS 可能由于同样的原因而失败)。我仍然不明白 Linux 是如何做到这一点的,因为 BSD 有一个明确的理由说明为什么这是不可能的。我更新了帖子以反映这一点。
    猜你喜欢
    • 2023-03-03
    • 2013-01-01
    • 1970-01-01
    • 2015-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多