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.