3

I'm trying to implement a simple proprietary discovery protocol using socat. The discovery is done by sending a UDP broadcast to a well-defined port with a small payload, then listening to "replies" from these devices on my network.

This works if I use bidirectional socat and "responses" go to stdout:

echo -ne "\x00\x01\x00\xF6" | socat -t5 - udp-datagram:255.255.255.255:30718,broadcast |xxd -p

Example responses from a few devices on my local network (each line is a response from a different device):

000000f70020300258366d112c15000062a71b21ff0000000080a3d2ded9
000000f70020300258366d112c15000062a71b21ff0000000080a3a40670
000000f70020300258366d112c15000062a71b21ff0000000080a3b94ca0
000000f70020300258366d112c15000062a71b21ff0000000080a3a4046b

The payloads are what I expect, however what I'm missing is the sender metadata, specifically the sender's IP that I would get if I used ip-pktinfo,fork SYSTEM:. So what I want is to send the initial broadcast from stdin but use SYSTEM to handle the resulting packets coming back.

I've tried a few variations of -u unidirectional mode but I don't seem to receive data on my listener:

# Listener: 
socat -u udp-recvfrom:30222,reuseaddr,ip-pktinfo,fork SYSTEM:./test.sh &

# Broadcast:
echo -ne "\x00\x01\x00\xF6" | socat -u - udp-datagram:255.255.255.255:30222,sourceport=30222,broadcast,reuseaddr

Note from "this end" the source port may be random so I'm explicitly choosing 30222 so my broadcast source port will match my response listener. e.g. if the broadcast comes from port 9987 clients will send their unicast response back to port 9987. If I run the unidirectional broadcast this way, I will occasionally get a response; I have a feeling it depends on how quickly the broadcast process quits.

(I intend to check packet captures next; I'm testing on a remote machine and I have to refresh my memory on tcpdump first.)

References:

2
  • 1
    @A.B sorry I never saw the notification of your response! You're right - I was missin the reuseport option; I think I expected reuseaddr to have the same effect which obv it did not. Thanks so much for your thorough explanation! – thom_nic Dec 10 '20 at 19:36
  • 1
    (deleted wrong comment doh): SO_REUSEADDR helps when using TCP sockets in listening mode to avoid getting "Address already in use" when binding again to the same address/port after having closed it before (when a client was connected): that's to bypass the TIME_WAIT state delay. No effect on UDP afaik. – A.B Dec 10 '20 at 19:46
2

OP mentions ip-pktinfo which is usually a Linux socket option (for IP_PKTINFO). I'll assume Linux below (but see notes at the end for *BSD). This option is actually not needed (also see notes at the end), but could be added for additional information.


The key part is to use the option reuseport which toggles the SO_REUSEPORT socket option:

SO_REUSEPORT (since Linux 3.9)

Permits multiple AF_INET or AF_INET6 sockets to be bound to an identical socket address. This option must be set on each socket (including the first socket) prior to calling bind(2) on the socket. To prevent port hijacking, all of the processes binding to the same address must have the same effective UID. This option can be employed with both TCP and UDP sockets.

Except for this missing option, OP already got all that was needed.

Mostly for other readers' benefit, I'll describe what's done and give a complete example.


If one socat command can't do the work due to limitations, using two socat, splitting the full-duplex communication channel into two simplex channels, will for this case.

One will be used for reading back the answers, the other for sending the broadcast. Again, to workaround socat limitations, the reading socat has to use UDP-RECVFROM with fork and -u (for read-only, so its results are going to stdout, rather than being sent back to the responder) rather than the normally natural choice of UDP-RECV: that's to allow to fork one command per packet and transmit via environment variables the metadata for each packet. Also as OP wrote, a port has to be chosen in advance in order to have them both use the same. The sending socket too must use -u to avoid risking to read back the answer and stealing it to the other dedicated socat command.

Here's an example in a LAN with the local system having 192.0.2.2/24 and the peer system having 192.0.2.3/24.

peer system (responder) named peer:

$ socat udp4-recvfrom:30718,fork system:hostname

local system's term1 (could optionally also use ,ip-pktinfo):

$ socat -u udp4-recvfrom:30222,reuseport,fork system:'cat; printenv|grep -E \"SOCAT_.*(ADDR|PORT)\"'

local system's term2:

$ echo dummyprobe | socat -u - udp-datagram:255.255.255.255:30718,bind=:30222,reuseport,broadcast

response received on term1:

peer
SOCAT_PEERADDR=192.0.2.3
SOCAT_PEERPORT=30718

Notes

  • FreeBSD has a description similar for SO_REUSEPORT (or rather, Linux aligned lately to *BSD):

    SO_REUSEPORT allows completely duplicate bindings by multiple processes if they all set SO_REUSEPORT before binding the port. This option permits multiple instances of a program to each receive UDP/IP multicast or broadcast datagrams destined for the bound port.

  • ip-pktinfo is not needed to retrieve peer's address. It's needed to retrieve the local address and is mostly useful for a multi-homed system so it knows on which of its multiple addresses received a datagram, or else also to know if the received datagram was a broadcast. The peer's address information is always available even without this option. *BSD just requires to replace this option with ip-recvdstaddr,ip-recvif (=> IP_RECVDSTADDR + IP_RECVIF) to receive the same information.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.