MacOS SO_REUSEADDR / SO_REUSEPORT与Linux不一致吗?

时间:2018-08-24 05:35:11

标签: c macos sockets raw-sockets

考虑以下代码:

#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

根据this-适用于BSD:

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

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

当然,我当然希望可以在MacOS上进行这项工作,但是我目前可能无法实现-但是我仍然想了解Linux是如何做到的。

1 个答案:

答案 0 :(得分:0)

是的,Linux实现与大多数其他OS不同。您可以找到详尽的说明here。引用特定部分:

  

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

     

但是,在其他系统上与SO_REUSEPORT仍然有两个区别:

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