"How to communicate peer-to-peer through NAT firewalls"{target="_blank"}
[Tailscale] has solved a problem very similar to the one I am trying to solve, albeit their solutions rely on a central human authority, and they recommend:
> If you’re reaching for TCP because you want a
> stream‑oriented connection when the NAT traversal is done,
> consider using QUIC instead. It builds on top of UDP,
> so we can focus on UDP for NAT traversal and still have a
> nice stream protocol at the end.
But to interface QUIC to a system capable of handling a massive
number of state machines, going to need something like Tokio,
because we want the thread to service other state machines while
QUIC is stalling the output or waiting for input. Indeed, no
matter what, if we stall in the socket layer rather than the
application layer, which makes life a whole lot easier for the
application programmer, going to need something like Tokio.
On the application side, we have to lock each state machine
when it is active. It can only handle one message at at time.
So the despatch layer has to queue up messages and stash them somewhere,
and if it has too many messages stashed,
it wants to push back on the state machine at the application layer
at the other end of the wire. So the despatch layer at the receiving end
has to from time to time tell the despatch layer at the sending end
"I have `n` bytes in regard to message 'Y', and can receive `m` more.
And when the despatch layer at the other end, which unlike the socket
layer knows which state machine is communicating with which,
has more than that amount of data to send, it then blocks
and locks the state machine at its end in its send operation.
The socket layer does not know about that and does not worry about that.
What it worries about packets getting lost on the wire, and caches
piling up in the middle of the wire.
It adds to each message a send time and a receive time
and if the despatch layer wants to send data faster
than it thinks is suitable, it has to push back on the despatch layer.
Which it does in the same style.
It tells it the connection can handle up to `m` further bytes.
Or we might have two despatch layers, one for sending and one for
receiving, with the send state machine sending events to the receive state
machine, but not vice versa, in which case the socket layer
*can* block the send layer.
# Tokio
Most of this machinery seems like a re-implementation of Tokio-rust,
which is a huge project. I don't wanna learn Tokio-rust, but equally
I don't want to re-invent the wheel.
# Minimal system
Prototype. Limit global bandwidth at the application
state machine level -- they adjust their policy according to how much
data is moving, and they spread the non response outgoing
messages out to a constant rate (constant per counterparty,
and uniformly interleaved.)
Single threaded, hence no state machine locking.
Tweet style limit on the size of messages, hence no fragmentation
and re-assembly issue. Our socket layer becomes trivial - it just
send blobs like a zeromq socket.
If you are trying to download a sackload of data, you request a counterparty to send a certain amount to you at a given rate, he immediately responds (without regard to global bandwidth limits) with the first instalment, and a promise of further instalments at a certain time)
Each instalment records how much has been sent, and when, when the next instalment is coming, and the schedule for further instalments.
If you miss an instalment, you nack it after a delay. If he receives
a nack, he replaces the promised instalments with the missing ones.
The first thing we implement is everyone sharing a list of who they have successfully connected to, in recency order, and everyone keeps everyone else's list, which catastrophically fails to scale, and also how up to date their counter parties are with their own list, so that they do not have
endlessly resend data (unless the counterparty has a catastrophic loss of data, and requests everything from the beginning.)
We assume everyone has an open port, which is sucks intolerably, but once that is working we can handle ports behind firewalls, because we are doing UDP. Knowing who the other guy is connected to, and you are not, you can ask him to initiate a peer connection for the two of you, until you have
enough connections that the keep alive works.
And once everyone can connect to everyone else by their public username, then we can implement bitmessage.