MacOS SO_REUSEADDR/SO_REUSEPORT 与 Linux 不一致?

问题描述 投票:0回答:1

考虑这段代码:

#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
  • 连接到
    ::1
    端口
    12345

奇怪的是,在 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

根据SO_REUSEADDR 和 SO_REUSEPORT 有何不同?,这适用于 BSD:

因此,如果您将相同协议的两个套接字绑定到相同的源地址和端口,并尝试将它们都连接到相同的目标地址和端口,

connect()
实际上会失败,您尝试的第二个套接字会出现错误
EADDRINUSE
连接,这意味着已经连接了具有五个值的相同元组的套接字。

所以这是有道理的,为什么它不起作用。如果这怎么可能在 Linux 上实际工作,那有什么不合理的?

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

c macos sockets raw-sockets
1个回答
4
投票

是的,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 来获得非常便宜的负载平衡。
© www.soinside.com 2019 - 2024. All rights reserved.