Hello all! I was having some issues when making use of the Eio library, specifically when attempting to create a UDP socket and sending data. As a proof of concept, I went ahead and wrote out the Unix code by hand to try and narrow down the issues:
(* Socket Creation *)
let socket = Unix.socket PF_INET SOCKET_DGRAM 0 in
Unix.setsockopt socket SO_REUSEADDR true;
(* Address Resolution *)
let host_entry = Unix.gethostbyname name in
let entry = Array.get host_entry.h_addr_list 0 in
let addr = Unix.ADDR_INET (entry, port) in
(* Bind socket and address *)
Unix.bind socket addr;
Within this code I get the fatal error Unix.EADDRNOTAVAIL when calling Unix.bind. If Unix.bind is not called the and data is sent/received using Unix.recvfrom and Unix.sendto everything works fine!
Eio binds the socket with the address at some point behind the scenes which also results in this exception! If anyone can help or point me in a direction on this I would be very grateful, thank you.
I don’t have an answer for you: it’s been too long since I worked with low-level networking code, but if I were in your situation, here’s what I’d do:
(1) repro on a machine with syscall tracing, e,g. “strace”, and get a low-level system-call trace.
(2) crack open Stevens (UNIX networking book, basically a Bible 30+ ago) and copy out a program that does something similar, get it working, etc.
(3) That might answer your question; if not, then strace #2 and compare the traces.
With a bit more reading the man page on bind reveals that it is not possible to bind a socket to a non-local address. This would be case closed except eio is binding my socket and address together when I try to make a UDP socket:
Eio.Switch.run @@ fun sw ->
let net = Eio.Stdenv.net env in
let addr = Eio.Net.getaddrinfo_datagram ~service:port net host in
let sock = Eio.Net.datagram_socket ~reuse_addr:true ~reuse_port:tru ~sw net addr in
()
My question would now be, is there a way to create a UDP eio socket and send data to a non-local address?
Just to clarify: your addr is the “listening” address or the “connection” address? If the latter, you shouldn’t be “binding” your socket to it (this is used to set the listening address), you should instead be “connecting” to it, I think (and pass UdpV4 or UdpV6 as last argument to Eio.Net.datagram_socket).
I don’t believe there are any connected datagram sockets available on Unix systems. I think that’s stream sockets, AKA TCP. What you want to do is to bind to get your local endpoint, and then do a sendto() to send a datagram to some other Address.
This is most definitely the point at which I would break out Stevens and look at the very simple examples he provides for each of the basic operations Of tcpip programming. Those can be translated straightforwardly into ocaml code, I remember doing that for some of them back in the day.
“Connecting” UDP sockets is widely supported in Unix systems, see the manual: udp(7) - Linux manual page. It just comes down to setting the default address/port for the datagrams sent.
Ha! Very nice. I have literally never seen that done with UDP. Because anytime you use UDP you use sendto() and don’t bother with connect. Maybe I had seen Long ago in the past that you can connect a UDP socket, but I’d forgotten about it completely.
Thank you this was my issue, connect was to be used here and not bind!!
My original example could be fixed by simply replacing bind with connect:
(* Socket Creation *)
let socket = Unix.socket PF_INET SOCKET_DGRAM 0 in
Unix.setsockopt socket SO_REUSEADDR true;
(* Address Resolution *)
let host_entry = Unix.gethostbyname name in
let entry = Array.get host_entry.h_addr_list 0 in
let addr = Unix.ADDR_INET (entry, port) in
(* Bind socket and address *)
Unix.connect socket addr;
(* Now we can send/recv data *)
The eio example can fixed as follows:
Eio.Switch.run @@ fun sw ->
let net = Eio.Stdenv.net env in
let addr = Eio.Net.getaddrinfo_datagram ~service:port net host |> List.hd in
let sock = Eio.Net.datagram_socket ~sw net `UdpV4 in
(* Now send/recv data using addr *)
Eio.Net.send ~dst:addr sock [data_buffer]
I’ve never seen connect(2) used with UDP, b/c typically each participant has only one UDP socket, which it uses to communicate with all other (often >1) participants. Typically all participants bind(2) their socket to some port.
If you have only one peer, it makes a lot of sense to use connect for UDP. First, you no longer need to systematically pass the peer address to sendto, you can use a plain send syscall. Second, the operating system will automatically drop any packet that were not sent by the peer, so you do not have to filter packets yourselves in user space.
TCP and UDP have fundamentally different properties, especially when it comes to congestion and packet loss. With TCP, if a packet is lost, the receiver will stall the stream; any subsequent packets are put aside, until the sender has sent the missing packet anew, which might take some time. This is great if you are downloading a file, since you want to receive all the packets and you want to receive them in the correct order.
But if you are writing an application for real-time communication, you don’t care about lost packets. Indeed, by the time they would be resent, they would presumably be outdated. Rather than having the call freeze, it is better to experience a video/audio glitch. For such applications, TCP has all the wrong properties; you need to use UDP.
So, the choice between TCP and UDP is not tied to the number of peers. What matters is whether you need data to arrive fast (and corrupted) or correct (and outdated).