如何仅绑定到一个网络接口(Linux)的所有地址?
我试图实现的是将IPv6套接字绑定到一个特定设备的任何地址,而不是整个系统。我的直觉是,我可以使用如何仅绑定到一个网络接口(Linux)的所有地址?,linux,networking,ipv6,tcp,sockets,Linux,Networking,Ipv6,Tcp,Sockets,我试图实现的是将IPv6套接字绑定到一个特定设备的任何地址,而不是整个系统。我的直觉是,我可以使用SO\u BINDTODEVICE然后绑定到:。它主要做我期望它做的事情。v4中的行为相同 绑定到具有的接口的套接字,因此_BINDTODEVICE将只接受与该接口上地址的连接。这是人们的期望 但是,如果我试图绑定到接口B上的源端口,而有一个套接字使用接口a上的同一端口,则会遇到errno“Address ready in use” 例: nic A具有IPv6 fd00:aaaa::A/64 n
SO\u BINDTODEVICE
然后绑定到:
。它主要做我期望它做的事情。v4中的行为相同
绑定到具有的接口的套接字,因此_BINDTODEVICE
将只接受与该接口上地址的连接。这是人们的期望
但是,如果我试图绑定到接口B上的源端口,而有一个套接字使用接口a上的同一端口,则会遇到errno“Address ready in use”
例:
- nic A具有IPv6 fd00:aaaa::A/64
- nic B具有IPv6 fd00:bbbb::B/64
- 他们不共享网络
- 进程1调用套接字(…)并绑定
bind(fd00:aaaa::a/649000)
- 进程2调用
和socket(…)
setsockopt(SO_BINDTODEVICE,“B”)
- 进程2(续)调用
并获取bind(:,9000)
。为什么?EADDRINUSE
SO\u BINDTODEVICE
究竟是如何工作的?“正在使用的地址”的确定是否保守地忽略了接口套接字绑定到的地址?这是网络堆栈分层问题吗
示例跟踪:
nc-l fd00:aaaa::a 9000
。其踪迹如下:socket(PF_INET6、SOCK_流、IPPROTO_TCP)=3
setsockopt(3,SOL_套接字,SO_REUSEADDR[1],4)=0
绑定(3{
sa_family=AF_INET6,
sin6_端口=htons(9000),
inet_pton(AF_inet 6,“fd00:aaaa::a”和sin6_addr),
sin6\u flowinfo=0,sin6\u scope\u id=0
}, 28) = 0
听(3,1)=0
接受(3。。。
socket(PF_INET6、SOCK_流、IPPROTO_IP)=3
setsockopt(3,SOL_套接字,SO_BINDTODEVICE,“nicB\0”,5)=0
bind(3,{sa_family=AF_INET6,
sin6_端口=htons(9000),
inet_pton(AF_inet 6,“::”,&sin6_addr),
sin6_flowinfo=0,
sin6\u范围\u id=0
},28)=-1//EADDRINUSE(地址已在使用中)
:
(而侦听器仍在运行)时,一切都很好:socket(PF_INET6、SOCK_流、IPPROTO_IP)=3
setsockopt(3,SOL_套接字,SO_BINDTODEVICE,“nicB\0”,5)=0
绑定(3{
sa_family=AF_INET6,
sin6_端口=htons(0),
inet_pton(AF_inet 6,“::”,&sin6_addr),
sin6\u flowinfo=0,sin6\u scope\u id=0
}, 28) = 0
连接(3{
sa_family=AF_INET6,
sin6_端口=htons(9000),
inet_pton(AF_inet 6,“fd00:aaaa::a”和sin6_addr),
sin6\u flowinfo=0,sin6\u scope\u id=0
}, 28) = ...
注意:这是在3.19.0-68-generic x86_64.Ubuntu 14.04上。如果有区别,在我的测试中,nicB是一个处于桥接模式的MAVLAN,其父节点是nicA。我找到了一个令人满意的解释来解释这个问题 观察结果是,即使程序启动时只有接口“A”具有IP
fd00:aaaa::A/64
,侦听套接字也可以接受通过不同接口传入的连接(如果它们将来要接收该IP)。可以添加和删除IP——服务器进程侦听:
或(v4中的0.0.0.0
)接口接收到新IP时无需重新启动
因此,在某种程度上,进程1的bind(“fd00:aaaa::a/64”,9000)
隐式绑定到所有接口。尽管进程2只需要使用接口B,但进程1已经获得了第一个DIB,因为它在两个接口上都使用端口9000,所以进程2被拒绝
如果我更改程序1,使其也使用so_BINDTODEVICE
(到接口“A”),则两个进程都可以绑定(:,9000)
,而不会出现问题
实验
我已经用一个小小的LD_PRELOAD goop测试了这一点,它在使用setsockopt(…SO_BINDTODEVICE…
调用bind()
之前。如果以下两个TCP侦听器都绑定到不同的接口,那么它们都可以模拟绑定到端口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\u 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);
}
/**
*捆绑钩
*
*在_any_bind()之前使用#SO_BINDTODEVICE调用setsockopt()。
*要绑定到的接口的名称是从中获取的
*环境变量“%u BINDTODEVICE”。
*
*需要根烫发。错误不会发出信号。
*
*编译时使用:
*gcc-Wall-Werror-shared-fPIC-o bind_hook.so-D_GNU_SOURCE bind_hook.c-ldl
*用法示例:
*LD\U PRELOAD=/bind\U hook.so\U BINDTODEVICE=eth0 nc-l 0.0.0 9500
*