The question is 'who deals with dropped packages'? In TCP, the answer is: 'the protocol'. In UDP the answer is 'the next layer of abstraction' (eg the app or some library).
You can build a 'reliable' protocol on top of UDP, and still not get TCP.
Eg if you want to transfer a large file that you know up front, then TCP's streaming mechanism doesn't make too much sense. You could use something like UDP to send the whole file from A to B in little chunks once, and at the end B can tell A what (numbered) chunks she's missing.
There's no reason to hold off on sending chunk n+1 of the file, just because chunk n hasn't arrived yet.
> There's no reason to hold off on sending chunk n+1 of the file, just because chunk n hasn't arrived yet.
Congestion control comes to mind -- you don't necessarily know what rate the network supports if you don't have a feedback mechanism to let you know when you're sending too fast. Congestion control is one of those things where sure, you can individually cheat and possibly achieve better performance at everyone else's expense, but if everyone does it, then you'll run into congestive collapse.
> You can build a 'reliable' protocol on top of UDP, and still not get TCP.
I agree -- there are reliable protocols running on top of UDP (e.g. QUIC, SCTP) that do not behave exactly like TCP. You don't need an in-order stream in the described use case of bulk file transfer. You certainly don't need head-of-line blocking.
But there are many details and interactions that you and I wouldn't realize or get right on the first try. I would rather not relearn all of those lessons from the past 50+ years.
> But there are many details and interactions that you and I wouldn't realize or get right on the first try. I would rather not relearn all of those lessons from the past 50+ years.
Oh, the model I had in mind was not that everyone should write their network code from scratch all the time, but rather that everything that's higher level than datagrams should be handled by unprivileged library code instead of privileged kernel level code.
If speed is an issue, modern Linux can do wonders with eBPF and io_uring, I guess? I'm taking my inspiration from the exokernel folks who believed that abstractions have no place in the operating system kernel.
interestingly, the exokernel approach is suited to particular cases. for instance, a single application (regardless of whether it's multiple sessions, diversity of RTT, etc). after all, "get the packets into userspace with as little fuss as possible" would be the right goal there.
the unix model is different: it's basically premised on a minicomputer server which would be running a diverse set of independent services where isolation is desired, and where it makes sense for a privileged entity to provide standardized services. services whose API has been both stable and efficient for more than a couple decades.
I think it's kind of like cloud: outsourcing that makes sense at the lower-scale of hosting alternatives. but once you get to a particular scale, you can and should take everything into your own hands, and can expect to obtain some greater efficiency, agility, autonomy.
> the unix model is different: it's basically premised on a minicomputer server which would be running a diverse set of independent services where isolation is desired, [...]
Exokernels provide isolation. Secure multiplexing is actually the only thing they do.
> and where it makes sense for a privileged entity to provide standardized services. services whose API has been both stable and efficient for more than a couple decades.
Yes, standardisation is great. Libraries can do that standardisation. Why do you need standardisation at the kernel level?
That kind of standardisation is eg what we are doing with libc: memcpy has a stable interface, but how it's implemented depends on the underlying hardware; the kernel does not impose an abstraction.
Is there a popular protocol that uses this scheme today? I've often thought that this would be a superior way to transfer/sync data. and have always wondered why it wasn't common.
The closest I can think of is the old FSP protocol, which never really saw wide use. The client would request each individual chunk by offset, and if a chunk got lost, it could re-request it. But that's not quite the same thing.
SACKs have been in TCP for 25-30 years now (Widely adopted as part of New Reno, although RFC 2018 proposed the TCP option and implementation back in 1996).
That said, the typical reason why TCP doesn't send packet N+1 is because its congestion window is full.
There is a related problem known as head-of-line blocking where the application won't receive packet N+1 from the kernel until packet N has been received, as a consequence of TCP delivering that in-order stream of bytes.
Sorry, I didn't literally mean n+1. What I mean is probably better described by 'head of line blocking'.
Basically, when transmitting a file, in principle you could just keep sending n+k, even if the n-th package hasn't been received or has been dropped. No matter how large k is.
You can take your sweet time fixing the missing packages in the middle, as long as the overall file transfer doesn't get delayed.
You can build a 'reliable' protocol on top of UDP, and still not get TCP.
Eg if you want to transfer a large file that you know up front, then TCP's streaming mechanism doesn't make too much sense. You could use something like UDP to send the whole file from A to B in little chunks once, and at the end B can tell A what (numbered) chunks she's missing.
There's no reason to hold off on sending chunk n+1 of the file, just because chunk n hasn't arrived yet.