如何仅绑定到一个网络接口(Linux)的所有地址?

如何仅绑定到一个网络接口(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

我试图实现的是将IPv6套接字绑定到一个特定设备的任何地址,而不是整个系统。我的直觉是,我可以使用
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 *