gophertunnel icon indicating copy to clipboard operation
gophertunnel copied to clipboard

proposal: expose an API to access batch packets to reduce proxy latency

Open AkmalFairuz opened this issue 8 months ago • 8 comments

Description

Most people use gophertunnel for proxies between the client and server. Typically, a proxy is written like this to redirect packets from the server to the client and vice versa:

pk, err := serverConn.ReadPacket()
if err != nil {
  return
}
clientConn.WritePacket(pk)

As we know, when we call WritePacket(pk), it does not actually send the packet immediately; instead, it's queued/buffered in memory and sent later unless we call Flush() after. By default, gophertunnel will flush queued/buffered packets from memory every 50ms, or we can call it a tick. The time between those ticks, we called WritePacket(). As a result, there is unnecessary latency of up to 50ms due to packet buffering/queueing.

Behind the scenes, when queued/buffered packets are flushed, the packets will be batched into a single-batched packet. That single-batched packet will be compressed & encrypted.

Image

Why not call Flush() after WritePacket(), or reducing the flush rate?

Flushing every single packet or reducing the flush rate would create overhead since each packet must be compressed and encrypted. When small data is compressed using snappy/zlib, it can increase in size instead of decreasing, leading to higher bandwidth usage. Additionally, this adds more overhead on the raknet side (like more packet header).

Proposed Solutions

Add (*minecraft.Conn).ReadPackets([]packet.Packet)

This will read the packets from a single batch packet from the connection.

The proxy will do this:

pks, err := serverConn.ReadPackets()
if err != nil {
  return
}
for _, pk := range pks {
  clientConn.WritePacket(pk)
}
clientConn.Flush()

It will immediately redirect packets from the server to the client without waiting for the flush rate, which can significantly reduce latency by up to 50ms. But there's a problem: when we call Flush(), it will also include another packet from the queue/buffer, but I think that is not a problem for me.

AkmalFairuz avatar Mar 26 '25 12:03 AkmalFairuz

I am not particularly happy with introducing such a concept to the public API, as this complicates the API for really minimal gains. I think a better solution would be to introduce some sort of Proxy/Router type that takes care of this and also resolves other common issues with the current design, such as the need for predialing to obtain resource packs from the target server.

Sandertv avatar Mar 26 '25 14:03 Sandertv

You could also introduce a special flush packet into the stream at batch boundaries. Though you'd have to define it in such a way that the flush packet couldn't be relayed to the client by accident.

dktapps avatar Apr 06 '25 20:04 dktapps

You could also introduce a special flush packet into the stream at batch boundaries. Though you'd have to define it in such a way that the flush packet couldn't be relayed to the client by accident.

You can’t send a special flush packet when you’re proxying to a server you don’t control

AkmalFairuz avatar May 10 '25 04:05 AkmalFairuz

I'm saying gophertunnel itself could do that.

dktapps avatar May 10 '25 10:05 dktapps

@dktapps Would you be able to invite me to the cave, by any chance? Thank you

AkmalFairuz avatar May 11 '25 18:05 AkmalFairuz

Same here! Can I get an invite to the cave? Thank you

NopeNotDark avatar May 11 '25 18:05 NopeNotDark

ohhh @cqdetdev told me about that

cjmustard avatar May 11 '25 18:05 cjmustard

Please immediately stop using our github issues as a chat for personal requests.

Sandertv avatar May 11 '25 19:05 Sandertv