I was under the impression that under Linux you could bind to a non-local address as long as you set the IP_FREEBIND
socket option, but that's not the behavior I'm seeing:
$ sudo strace -e 'trace=%network' ...
...
socket(AF_INET, SOCK_RAW, IPPROTO_UDP) = 5
setsockopt(5, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
setsockopt(5, SOL_SOCKET, SO_NO_CHECK, [1], 4) = 0
setsockopt(5, SOL_IP, IP_HDRINCL, [1], 4) = 0
setsockopt(5, SOL_IP, IP_FREEBIND, [1], 4) = 0
bind(5, {sa_family=AF_INET, sin_port=htons(abcd), sin_addr=inet_addr("w.x.y.z")}, 16) = -1 EADDRNOTAVAIL (Cannot assign requested address)
...
I also set the ip_nonlocal_bind
setting, just to be certain, and I get the same results.
$ sysctl net.ipv4.ip_nonlocal_bind
net.ipv4.ip_nonlocal_bind = 1
CodePudding user response:
Unfortunately, it seems that it is not possible to bind a raw IP socket to a non-local, non-broadcast and non-multicast address, regargless of IP_FREEBIND
. Since I see inet_addr("w.x.y.z")
in your strace
output, I assume that this is exactly what you're trying to do and w.x.y.z
is a non-local unicast address, thus your bind
syscall fails.
This seems in accordance with man 7 raw
:
A raw socket can be bound to a specific local address using the
bind(2)
call. If it isn't bound, all packets with the specified IP protocol are received. In addition, a raw socket can be bound to a specific network device usingSO_BINDTODEVICE
; seesocket(7)
.
Indeed, looking at the kernel source code, in raw_bind()
we can see the following check:
ret = -EADDRNOTAVAIL;
if (addr->sin_addr.s_addr && chk_addr_ret != RTN_LOCAL &&
chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST)
goto out;
Also, note that .sin_port
must be 0
. The .sin_port
field for raw sockets is used to select a sending/receiving IP protocol (not a port, since we are at level 3 and ports do not exist). As the manual states, from Linux 2.2 onwards you cannot select a sending protocol through .sin_port
anymore, the sending protocol is the one set when creating the socket.