I need to create a distributed system comprised of various processes; to simplify matters a bit, let's say they are three: A, B and C. Every process knows the IP and port of the others from/to which to receive/send UDP datagrams. They all use the same UDP port (let's say port 8080).
Assuming that A's IP is 192.168.1.1, B's IP is 192.168.1.2 and C's IP is 192.168.1.3:
- A has two UDP sockets, both bind to 0.0.0.0:8080. The first is connected to 192.168.1.2:8080, the second to 192.168.1.3:8080. It uses the first socket to receive and send datagrams from/to B, and the second socket to receive and send datagrams from/to C.
- B has two UDP sockets, both bind to 0.0.0.0:8080. The first is connected to 192.168.1.1:8080, the second to 192.168.1.3:8080. It uses the first socket to receive and send datagrams from/to A, and the second socket to receive and send datagrams from/to C.
- C has two UDP sockets, both bind to 0.0.0.0:8080. The first is connected to 192.168.1.1:8080, the second to 192.168.1.2:8080. It uses the first socket to receive and send datagrams from/to A, and the second socket to receive and send datagrams from/to B.
The processes communicate with each other and reject datagrams from others.
The communication I want to enable can be described by the following tuples. All rows are different, thus they should describe a valid setup.
Source address | Source port | Dest address | Dest port | Protocol |
---|---|---|---|---|
192.168.1.1 | 8080 | 192.168.1.2 | 8080 | UDP |
192.168.1.1 | 8080 | 192.168.1.3 | 8080 | UDP |
192.168.1.2 | 8080 | 192.168.1.1 | 8080 | UDP |
192.168.1.2 | 8080 | 192.168.1.3 | 8080 | UDP |
192.168.1.3 | 8080 | 192.168.1.1 | 8080 | UDP |
192.168.1.3 | 8080 | 192.168.1.2 | 8080 | UDP |
How can I achieve this in Rust + Tokio?
In Rust, connecting a socket means setting the default destination for send()
and limiting packets that are read via recv
from the specified address. This concept is not specific to Rust: POSIX sockets (such as UDP sockets) behave the same way.
This is what I have in mind:
// A's startup code
let sockforb = UdpSocket::bind("0.0.0.0:8080").await?;
let sockforc = UdpSocket::bind("0.0.0.0:8080").await?;
let b_addr = "192.168.1.2:8080".parse::<SocketAddr>().unwrap();
let c_addr = "192.168.1.3:8080".parse::<SocketAddr>().unwrap();
sockforb.connect(b_addr).await?;
sockforc.connect(c_addr).await?;
The code above would be handy: I have two distinct socket variables, and if I call send
/recv
on them I send/ receive datagrams to/from the desired process.
However, the code produces the following error:
Error: Os { code: 98, kind: AddrInUse, message: "Address already in use" }
As a workaround, I can define one socket variable only and pass multiple addresses to the connect method (the argument has type ToSocketAddrs
). This lets me send/receive datagrams only to/from the designated processes. However, although this solution is free of errors, it is not handy as I would have one single socket variable, in contrast to multiple variables for different processes. My intent is to have different socket variables in order to put them in different structs for each process.
How can I achieve this in Rust + Tokio, possibly with portable (non OS-dependent) code?