我想要实现的是将 IPv6 套接字绑定(bind)到任何地址只是一个特定设备,而不是系统范围的。我的直觉是,我可以使用 SO_BINDTODEVICE
来 setsockopt()
,然后绑定(bind)到 ::
。它主要做我期望它做的事。 v4 中的行为相同。
使用 SO_BINDTODEVICE
绑定(bind)到接口(interface)的套接字将只接受与该接口(interface)上的地址建立的连接。这是意料之中的事。
但是,如果我尝试绑定(bind)到接口(interface) B 上的源端口,而接口(interface) A 上有一个使用相同端口的套接字但,我会遇到错误号“地址已在使用中” .
例如:
- 网卡 A 有 IPv6 fd00:aaaa::a/64
- 网卡 B 有 IPv6 fd00:bbbb::b/64
- 他们不共享网络。
简而言之(伪代码):
- 进程 1 调用
socket(...)
并绑定(bind)bind(fd00:aaaa::a/64, 9000)
。 - 进程 2 调用
socket(...)
和setsockopt(SO_BINDTODEVICE, "B")
- 进程 2(续)调用
bind(::, 9000)
并获取EADDRINUSE
。为什么?
SO_BINDTODEVICE
是如何工作的?保守地说,“使用中的地址”的确定是否忽略了接口(interface)套接字所绑定(bind)的?是网络堆栈分层问题吗?
跟踪示例:
- 我在特定地址上启动了一个监听套接字(服务器):
nc -l fd00:aaaa::a 9000
。其踪迹如下:
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {
sa_family=AF_INET6,
sin6_port=htons(9000),
inet_pton(AF_INET6, "fd00:aaaa::a", &sin6_addr),
sin6_flowinfo=0, sin6_scope_id=0
}, 28) = 0
listen(3, 1) = 0
accept(3, ...
- 如果我绑定(bind)到另一个接口(interface)正在使用的端口,即使我已经绑定(bind)到另一个接口(interface),连接到它(客户端)也会失败:
socket(PF_INET6, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_BINDTODEVICE, "nicB\0", 5) = 0
bind(3, {sa_family=AF_INET6,
sin6_port=htons(9000),
inet_pton(AF_INET6, "::", &sin6_addr),
sin6_flowinfo=0,
sin6_scope_id=0
}, 28) = -1 //EADDRINUSE (Address already in use)
- 但是,如果我不指定端口,那么在绑定(bind)到
::
时一切正常(同时监听器仍在运行):
socket(PF_INET6, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_BINDTODEVICE, "nicB\0", 5) = 0
bind(3, {
sa_family=AF_INET6,
sin6_port=htons(0),
inet_pton(AF_INET6, "::", &sin6_addr),
sin6_flowinfo=0, sin6_scope_id=0
}, 28) = 0
connect(3, {
sa_family=AF_INET6,
sin6_port=htons(9000),
inet_pton(AF_INET6, "fd00:aaaa::a", &sin6_addr),
sin6_flowinfo=0, sin6_scope_id=0
}, 28) = ...
注意:这是在 3.19.0-68-generic x86_64 上。 Ubuntu 14.04。如果它有所不同,对于我的测试,nicB 是桥接模式下的 macvlan,其父节点是 nicA。
最佳答案
我找到了这个问题的令人满意的解释。
观察是即使只有接口(interface)“A”有 IP fd00:aaaa::a/64
当程序启动时,监听套接字可以接受来自不同接口(interface)的连接,如果它们将来会收到该 IP。可以添加和删除 IP——当接口(interface)接收到新 IP 时,无需重新启动监听 ::
或(v4 中的 0.0.0.0
)的服务器进程。
因此,在某种程度上,进程 1 的 bind("fd00:aaaa::a/64", 9000)
隐式绑定(bind)到 ALL 接口(interface)。即使进程 2 只需要使用接口(interface) B,进程 1 已经获得了优先权,因为它在两个接口(interface)上都使用了端口 9000,所以进程 2 被拒绝了。
如果我更改程序 1,使其也使用 SO_BINDTODEVICE
(接口(interface)“A”),那么两个进程都可以bind(::, 9000)
而不会出现问题。
实验
我已经用一个小的 LD_PRELOAD goop 测试了这个,它在调用 bind()
之前使用 setsockopt(...SO_BINDTODEVICE...)
。如果以下两个 TCP 监听器分别绑定(bind)到不同的接口(interface),则它们都可以同时绑定(bind)到端口 9000。
# LD_PRELOAD=./bind_hook.so _BINDTODEVICE=eth0 nc -l 0.0.0.0 9000
# LD_PRELOAD=./bind_hook.so _BINDTODEVICE=eth1 nc -l 0.0.0.0 9000
如果两者中只有一个使用 SO_BINDTODEVICE
,则最后一个进程获得 EADDRINUSE
。题中提出的是哪种情况。
我为我的工具包含了 C 代码 (GNU/Linux),以防有人需要类似的东西:
/** * bind_hook.c * * Calls setsockopt() with #SO_BINDTODEVICE before _any_ bind(). * The name of the interface to bind to is obtained from * environment variable `_BINDTODEVICE`. * * Needs root perms. errors are not signalled out. * * Compile with: * gcc -Wall -Werror -shared -fPIC -o bind_hook.so -D_GNU_SOURCE bind_hook.c -ldl * Example usage: * LD_PRELOAD=./bind_hook.so _BINDTODEVICE=eth0 nc -l 0.0.0.0 9500 * * @author: init-js **/ #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <net/if.h> #include <dlfcn.h> #include <errno.h> static char iface[IF_NAMESIZE]; static int (*bind_original)(int, const struct sockaddr*, socklen_t addrlen); int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); __attribute__((constructor)) void ctor() { bind_original = dlsym(RTLD_NEXT, "bind"); char *env_iface = getenv("_BINDTODEVICE"); if (env_iface) { strncpy(iface, env_iface, IF_NAMESIZE - 1); } } /* modified bind() -- call setsockopt first */ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { int _errno; if (iface[0]) { /* preserve errno */ _errno = errno; setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, (void*)iface, IF_NAMESIZE); errno = _errno; } return bind_original(sockfd, addr, addrlen); }
关于linux - 如何绑定(bind)到只有一个网络接口(interface)(Linux)的所有地址?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39536172/