I have a daemon I'm working on that listens for UDP broadcast packets and responds also by UDP. When a packet comes in, I'd like to know which IP address (or NIC) the packet came TO so that I can respond with that IP address as the source. (For reasons involving a lot of pain, some users of our system want to connect two NICs on the same machine to the same subnet. We tell them not to, but they insist. I don't need to be reminded how ugly that is.)
There seems to be no way to examine a datagram and find out directly either its destination address or the interface it came in on. Based on a lot of googling, I find that the only way to find out the target of a datagram is to have one listening socket per interface and bind the sockets to their respective interfaces.
First of all, my listening socket is created this way:
s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
To bind the socket, the first thing I tried was this, where nic
is a char*
to the name of an interface:
// Bind to a single interface
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
if (rc != 0) { ... }
This has no effect at all and fails silently. Is the ASCII name (e.g. eth0
) the correct type of name to pass to this call? Why would it fail silently? According to man 7 socket
, "Note that this only works for some socket types, particularly AF_INET sockets. It is not supported for packet sockets (use normal bind(8) there)." I'm not sure what it means by 'packet sockets', but this is an AF_INET socket.
So the next thing I tried was this (based on bind vs SO_BINDTODEVICE socket):
struct sockaddr_ll sock_address;
memset(&sock_address, 0, sizeof(sock_address));
sock_address.sll_family = PF_PACKET;
sock_address.sll_protocol = htons(ETH_P_ALL);
sock_address.sll_ifindex = if_nametoindex(nic);
rc=bind(s, (struct sockaddr*) &sock_address, sizeof(sock_address));
if (rc < 0) { ... }
That fails too, but this time with the error Cannot assign requested address
. I also tried changing the family to AF_INET, but it fails with the same error.
One option remains, which is to bind the sockets to specific IP addresses. I can look up interface addresses and bind to those. Unfortunately, is a bad option, because due to DHCP and hot-plugging ethernet cables, the addresses can change on the fly.
This option may also be bad when it comes to broadcasts and multicasts. I'm concerned that binding to a specific address will mean that I cannot receive broadcasts (which are to an address other than what I bound to). I'm actually going to test this later this evening and update this question.
Questions:
- Is it possible to bind a UDP listening socket specifically to an interface?
- Or alternatively, is there a mechanism I can employ that will inform my program that an interface's address has changed, at the moment that change occurs (as opposed to polling)?
- Is there another kind of listening socket I can create (I do have root privileges) that I can bind to a specific interface, which behaves otherwise identically to UDP (i.e other than raw sockets, where I would basically have to implement UDP myself)? For instance, can I use
AF_PACKET
withSOCK_DGRAM
? I don't understand all the options.
Can anyone help me solve this problem? Thanks!
UPDATE:
Binding to specific IP addresses does not work properly. Specifically, I cannot then receive broadcast packets, which is specifically what I am trying to receive.
UPDATE:
I tried using IP_PKTINFO
and recvmsg
to get more information on packets being received. I can get the receiving interface, the receiving interface address, the target address of the sender, and the address of the sender. Here's an example of a report I get on receipt of one broadcast packet:
Got message from eth0
Peer address 192.168.115.11
Received from interface eth0
Receiving interface address 10.1.2.47
Desination address 10.1.2.47
What's really odd about this is that the address of eth0 is 10.1.2.9, and the address of ech1 is 10.1.2.47. So why in the world is eth0 receiving packets that should be received by eth1? This is definitely a problem.
Note that I enabled net.ipv4.conf.all.arp_filter, although I think that applies only to out-going packets.
I believe you may be approaching the problem from the wrong angle. In the general case, interfaces can have multiple IP address at the same time so knowing which interface you are attached to is not going to give you the IP address (in the general case)
Instead, don't worry too much about the interfaces being used and focus on the IP addresses being used. First get the list all your IP addresses using getifaddrs() and bind one socket to each address.
select() can be used to wait for packets on all your sockets at once. Use the socket that received the packet to determine the destination address of the packet. Also, the socket that received the packet can be used to send a reply which will automatically set the source address appropriately.
You may occasionally need to check for new IP addresses, but you will get an error on a socket if DHCP gives you a new address.
You're passing an illegal value to
setsockopt
.The man page says of
SO_BIND_TO_DEVICE
:strlen
doesn't include the terminating null. You can try:dnsmasq
has this working correctly, and usesThe solution that I found to work is as follows. First of all, we have to change ARP and RP settings. To /etc/sysctl.conf, add the following and reboot (there's also a command to set this dynamically):
The arp filter was necessary to allow responses from eth0 to route over a WAN. The rp filter option was necessary to strictly associate in-coming packets with the NIC they came in on (as opposed to the weak model that associates them with any NIC that matches the subnet). A comment from EJP led me to this critical step.
After that, SO_BINDTODEVICE started working. Each of two sockets was bound to its own NIC, and I could therefore tell which NIC a message came from based on the socket it came from.
Next, I wanted to respond to in-coming datagrams with datagrams whose source address is that of the NIC the original request came from. The answer there is to just look up that NIC's address and bind the out-going socket to that address (using
bind
).(Maybe looking up the NIC's address every time seems like a waste, but it's way more code to get informed when an address changes, and these transactions occur only once every few seconds on a system that doesn't run on battery.)
I know this is an old thread but I did not find the answer I was looking for here.
Binding a raw socket to an interface such that the socket does not see any packets from another interface (including broadcast, IGMP, etc) worked for me by using the info I found here:
https://cs.wikipedia.org/wiki/Raw_socket
The functionality of the BindRawSocketToInterface() function was what I needed.
Hope this helps someone else. Cheers!
You can get the destination address used by the sender via the
IP_RECVDSTADDR
option if your platform supports it, by usingrecvmsg()
. It's rather complicated, described inUnix Network Programming,
volume I, 3rd edition, #22.2, and in the man page.Re your edit, you are up against what is known as the 'weak end system model' of TCP/IP. Basically once a packet arrives the system can choose to deliver it via any appropriate interface listening to the correct port. It's discussed in the TCP/IP RFCs somewhere.