What I am trying to achieve is binding an IPv6 socket to any address of just one particular device, not system-wide. My intuition is that I could setsockopt()
with SO_BINDTODEVICE
followed by a bind to ::
. It mostly does what I expect it to do. The behaviour is the same in v4.
The sockets bound to an interface with SO_BINDTODEVICE
will only accept connections made to addresses on that interface. That much is expected.
However, I run into errno "Address already in use", if I'm trying to bind to a source port on interface B when there is a socket using the same port but on interface A.
Ex:
- nic A has IPv6 fd00:aaaa::a/64
- nic B has IPv6 fd00:bbbb::b/64
- they do not share networks.
Put shortly (pseudocode):
- process 1 calls
socket(...)
and bindsbind(fd00:aaaa::a/64, 9000)
. - process 2 calls
socket(...)
andsetsockopt(SO_BINDTODEVICE, "B")
- process 2 (continued) calls
bind(::, 9000)
and getsEADDRINUSE
. Why?
How does SO_BINDTODEVICE
really work? Does the determination for "addresses in use" ignore, conservatively, the interface sockets are bound to? Is it a networking stack layering issue?
Example traces:
- I start a listening socket (server) on a specific address:
nc -l fd00:aaaa::a 9000
. Its trace is as follows:
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, ...
- Connecting to it (client) fails if I bind to the port in use by the other interface, even though I've already bound to a different 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)
- However, if I don't specify the port, then all is good when binding to
::
(while the listener still runs):
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) = ...
Note: This is on 3.19.0-68-generic x86_64 . Ubuntu 14.04. In case it makes a difference, for my tests, nicB is a macvlan in bridge mode whose parent is nicA.