即使在使用 IP_FREEBIND 之后 EADDRNOTAVAIL?

EADDRNOTAVAIL even after using IP_FREEBIND?

我的印象是,在 Linux 下,只要设置 IP_FREEBIND 套接字选项,就可以绑定到非本地地址,但这不是我看到的行为:

$ sudo strace -e 'trace=%network' ...
...
socket(AF_INET, SOCK_RAW, IPPROTO_UDP)  = 5
setsockopt(5, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
setsockopt(5, SOL_SOCKET, SO_NO_CHECK, [1], 4) = 0
setsockopt(5, SOL_IP, IP_HDRINCL, [1], 4) = 0
setsockopt(5, SOL_IP, IP_FREEBIND, [1], 4) = 0
bind(5, {sa_family=AF_INET, sin_port=htons(abcd), sin_addr=inet_addr("w.x.y.z")}, 16) = -1 EADDRNOTAVAIL (Cannot assign requested address)
...

我也设置了 ip_nonlocal_bind 设置,只是为了确定,我得到了相同的结果。

$ sysctl net.ipv4.ip_nonlocal_bind
net.ipv4.ip_nonlocal_bind = 1

不幸的是,似乎无法将原始 IP 套接字绑定到非本地、非广播和非多播地址,无论 IP_FREEBIND。因为我在你的 strace 输出中看到 inet_addr("w.x.y.z"),我假设这正是你想要做的并且 w.x.y.z 是一个非本地单播地址,因此你的 bind系统调用失败。

这似乎符合man 7 raw:

A raw socket can be bound to a specific local address using the bind(2) call. If it isn't bound, all packets with the specified IP protocol are received. In addition, a raw socket can be bound to a specific network device using SO_BINDTODEVICE; see socket(7).

确实,查看内核源代码,在raw_bind()中我们可以看到the following check:

    ret = -EADDRNOTAVAIL;
    if (addr->sin_addr.s_addr && chk_addr_ret != RTN_LOCAL &&
        chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST)
        goto out;

另外,请注意 .sin_port 必须是 0。原始套接字的 .sin_port 字段用于 select 一个 sending/receiving IP 协议(​​不是端口,因为我们处于 3 级并且端口不存在)。正如手册所述,从 Linux 2.2 开始,您不能再通过 select 通过 .sin_port 发送协议,发送协议是在创建套接字时设置的。