Skip to content

Commit ccc6212

Browse files
authored
fix: queue config plugin messages during backend login
1 parent 8409e00 commit ccc6212

4 files changed

Lines changed: 306 additions & 27 deletions

File tree

pkg/edition/java/proxy/session_backend_login.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,12 @@ func (b *backendLoginSessionHandler) handleServerLoginSuccess() {
309309
}
310310

311311
ash := player.ActiveSessionHandler()
312+
if csh, ok := ash.(*clientConfigSessionHandler); ok {
313+
if err = csh.flushQueuedPluginMessagesTo(b.serverConn); err != nil {
314+
fail(err)
315+
return
316+
}
317+
}
312318
csh, ok := ash.(*clientPlaySessionHandler)
313319
if ok {
314320
serverMc.SetAutoReading(false)

pkg/edition/java/proxy/session_backend_play_test.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ func TestBackendPlayRegisterForwardsToPlayer(t *testing.T) {
5555
type testMinecraftConn struct {
5656
writtenPackets []proto.Packet
5757
connType phase.ConnectionType
58+
activeHandler netmc.SessionHandler
59+
writer netmc.Writer
5860
}
5961

6062
func (t *testMinecraftConn) Context() context.Context { return context.Background() }
@@ -70,16 +72,19 @@ func (t *testMinecraftConn) Type() phase.ConnectionType {
7072
return phase.Vanilla
7173
}
7274
func (t *testMinecraftConn) SetType(ct phase.ConnectionType) { t.connType = ct }
73-
func (t *testMinecraftConn) ActiveSessionHandler() netmc.SessionHandler { return nil }
74-
func (t *testMinecraftConn) SetActiveSessionHandler(*state.Registry, netmc.SessionHandler) {
75+
func (t *testMinecraftConn) ActiveSessionHandler() netmc.SessionHandler { return t.activeHandler }
76+
func (t *testMinecraftConn) SetActiveSessionHandler(_ *state.Registry, handler netmc.SessionHandler) {
77+
t.activeHandler = handler
7578
}
7679
func (t *testMinecraftConn) SwitchSessionHandler(*state.Registry) bool { return true }
7780
func (t *testMinecraftConn) AddSessionHandler(*state.Registry, netmc.SessionHandler) {
7881
}
79-
func (t *testMinecraftConn) SetAutoReading(bool) {}
80-
func (t *testMinecraftConn) SetProtocol(proto.Protocol) {}
81-
func (t *testMinecraftConn) SetState(*state.Registry) {}
82-
func (t *testMinecraftConn) SetOutboundState(*state.Registry) {}
82+
func (t *testMinecraftConn) SetAutoReading(bool) {}
83+
func (t *testMinecraftConn) SetProtocol(proto.Protocol) {}
84+
func (t *testMinecraftConn) SetState(*state.Registry) {}
85+
func (t *testMinecraftConn) SetOutboundState(s *state.Registry) {
86+
t.Writer().SetState(s)
87+
}
8388
func (t *testMinecraftConn) SetCompressionThreshold(int) error { return nil }
8489
func (t *testMinecraftConn) EnableEncryption([]byte) error { return nil }
8590
func (t *testMinecraftConn) WritePacket(packet proto.Packet) error {
@@ -94,7 +99,25 @@ func (t *testMinecraftConn) BufferPacket(packet proto.Packet) error {
9499
func (t *testMinecraftConn) BufferPayload([]byte) error { return nil }
95100
func (t *testMinecraftConn) Flush() error { return nil }
96101
func (t *testMinecraftConn) Reader() netmc.Reader { return nil }
97-
func (t *testMinecraftConn) Writer() netmc.Writer { return nil }
98-
func (t *testMinecraftConn) EnablePlayPacketQueue() {}
102+
func (t *testMinecraftConn) Writer() netmc.Writer {
103+
if t.writer == nil {
104+
t.writer = &testWriter{}
105+
}
106+
return t.writer
107+
}
108+
func (t *testMinecraftConn) EnablePlayPacketQueue() {}
99109

100110
var _ netmc.MinecraftConn = (*testMinecraftConn)(nil)
111+
112+
type testWriter struct {
113+
state *state.Registry
114+
}
115+
116+
func (t *testWriter) WritePacket(proto.Packet) (int, error) { return 0, nil }
117+
func (t *testWriter) Write([]byte) (int, error) { return 0, nil }
118+
func (t *testWriter) Flush() error { return nil }
119+
func (t *testWriter) SetProtocol(proto.Protocol) {}
120+
func (t *testWriter) SetState(s *state.Registry) { t.state = s }
121+
func (t *testWriter) SetCompressionThreshold(int) error { return nil }
122+
func (t *testWriter) EnableEncryption([]byte) error { return nil }
123+
func (t *testWriter) Direction() proto.Direction { return proto.ClientBound }

pkg/edition/java/proxy/session_client_config.go

Lines changed: 149 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ package proxy
22

33
import (
44
"bytes"
5+
"fmt"
6+
"sync"
57

8+
"github.com/gammazero/deque"
69
"github.com/go-logr/logr"
710
"github.com/robinbraemer/event"
11+
"go.minekube.com/common/minecraft/component"
812
"go.minekube.com/gate/pkg/edition/java/netmc"
913
"go.minekube.com/gate/pkg/edition/java/proto/packet"
1014
"go.minekube.com/gate/pkg/edition/java/proto/packet/config"
@@ -22,10 +26,18 @@ type clientConfigSessionHandler struct {
2226
player *connectedPlayer
2327
log logr.Logger
2428

25-
brandChannel string
26-
2729
configSwitchDone future.Future[any]
2830

31+
mu struct {
32+
sync.Mutex
33+
pluginMessages deque.Deque[*plugin.Message]
34+
pluginMessagesBytes int
35+
pluginMessagesOverflowed bool
36+
brandChannel string
37+
brandForwardedServer *serverConnection
38+
readyServer *serverConnection
39+
}
40+
2941
nopSessionHandler
3042
}
3143

@@ -80,20 +92,13 @@ func (h *clientConfigSessionHandler) HandlePacket(pc *proto.PacketContext) {
8092

8193
// handleBackendFinishUpdate handles the backend finishing the config stage.
8294
func (h *clientConfigSessionHandler) handleBackendFinishUpdate(serverConn *serverConnection, p *config.FinishedUpdate) *future.Future[any] {
83-
smc, ok := serverConn.ensureConnected()
95+
_, ok := serverConn.ensureConnected()
8496
if !ok {
8597
return nil
8698
}
8799
brand := serverConn.player.ClientBrand()
88-
if brand == "" && h.brandChannel != "" {
89-
buf := new(bytes.Buffer)
90-
_ = util.WriteString(buf, brand)
91-
92-
brandPacket := &plugin.Message{
93-
Channel: h.brandChannel,
94-
Data: buf.Bytes(),
95-
}
96-
_ = smc.WritePacket(brandPacket)
100+
if brand != "" {
101+
h.writeBrandPacketTo(serverConn, brand)
97102
}
98103

99104
if err := h.player.WritePacket(p); err != nil {
@@ -115,23 +120,29 @@ func handleResourcePackResponse(p *packet.ResourcePackResponse, handler resource
115120
}
116121

117122
func (h *clientConfigSessionHandler) handlePluginMessage(p *plugin.Message) {
118-
serverConn := h.player.connectionInFlight()
119-
if serverConn == nil {
120-
return
121-
}
122-
123123
if plugin.McBrand(p) {
124124
brand := plugin.ReadBrandMessage(p.Data)
125-
h.brandChannel = p.Channel
125+
h.player.setClientBrand(brand)
126+
readyServer := h.setBrandChannel(p.Channel)
126127
h.event().FireParallel(&PlayerClientBrandEvent{
127128
player: h.player,
128129
brand: brand,
129130
})
130131
// Client sends `minecraft:brand` packet immediately after Login,
131132
// but at this time the backend server may not be ready
133+
if readyServer != nil {
134+
h.writeBrandPacketTo(readyServer, brand)
135+
}
136+
return
132137
} else if bungeecord.IsBungeeCordMessage(p) {
133138
return
139+
} else if h.enqueuePluginMessage(h.player.connectionInFlightOrConnectedServer(), p) {
140+
return
134141
} else {
142+
serverConn := h.player.connectionInFlightOrConnectedServer()
143+
if serverConn == nil {
144+
return
145+
}
135146
id, ok := h.player.proxy.ChannelRegistrar().FromID(p.Channel)
136147
if !ok {
137148
smc, ok := serverConn.ensureConnected()
@@ -164,8 +175,127 @@ func (h *clientConfigSessionHandler) handlePluginMessage(p *plugin.Message) {
164175
}
165176
}
166177

178+
func (h *clientConfigSessionHandler) setBrandChannel(channel string) *serverConnection {
179+
h.mu.Lock()
180+
defer h.mu.Unlock()
181+
h.mu.brandChannel = channel
182+
return h.mu.readyServer
183+
}
184+
185+
func (h *clientConfigSessionHandler) brandPacket(brand string) *plugin.Message {
186+
h.mu.Lock()
187+
channel := h.mu.brandChannel
188+
h.mu.Unlock()
189+
if channel == "" {
190+
return nil
191+
}
192+
buf := new(bytes.Buffer)
193+
_ = util.WriteString(buf, brand)
194+
return &plugin.Message{
195+
Channel: channel,
196+
Data: buf.Bytes(),
197+
}
198+
}
199+
200+
func (h *clientConfigSessionHandler) writeBrandPacketTo(serverConn *serverConnection, brand string) {
201+
h.mu.Lock()
202+
if h.mu.brandForwardedServer == serverConn {
203+
h.mu.Unlock()
204+
return
205+
}
206+
h.mu.Unlock()
207+
208+
brandPacket := h.brandPacket(brand)
209+
if brandPacket == nil {
210+
return
211+
}
212+
smc, ok := serverConn.ensureConnected()
213+
if !ok {
214+
return
215+
}
216+
if err := smc.WritePacket(brandPacket); err != nil {
217+
return
218+
}
219+
220+
h.mu.Lock()
221+
h.mu.brandForwardedServer = serverConn
222+
h.mu.Unlock()
223+
}
224+
225+
// enqueuePluginMessage returns true when the message was handled by the queue
226+
// path, including overflow rejection. It returns false only once the backend is
227+
// ready for direct config plugin messages.
228+
func (h *clientConfigSessionHandler) enqueuePluginMessage(target *serverConnection, msg *plugin.Message) bool {
229+
h.mu.Lock()
230+
if target != nil && h.mu.readyServer == target {
231+
h.mu.Unlock()
232+
return false
233+
}
234+
if h.mu.pluginMessagesOverflowed {
235+
h.mu.Unlock()
236+
return true
237+
}
238+
newBytes := h.mu.pluginMessagesBytes + len(msg.Data)
239+
newCount := h.mu.pluginMessages.Len() + 1
240+
if newBytes > maxQueuedLoginPluginMessageBytes || newCount > maxQueuedLoginPluginMessages {
241+
h.mu.pluginMessagesOverflowed = true
242+
h.mu.pluginMessages.Clear()
243+
h.mu.pluginMessagesBytes = 0
244+
h.mu.Unlock()
245+
h.log.Info("disconnecting player: pre-backend config plugin message queue exceeded its limits",
246+
"messages", newCount, "bytes", newBytes)
247+
h.player.Disconnect(&component.Text{
248+
Content: "Too many plugin messages were sent before joining a server",
249+
})
250+
return true
251+
}
252+
h.mu.pluginMessages.PushBack(&plugin.Message{
253+
Channel: msg.Channel,
254+
Data: append([]byte(nil), msg.Data...),
255+
})
256+
h.mu.pluginMessagesBytes = newBytes
257+
h.mu.Unlock()
258+
return true
259+
}
260+
261+
func (h *clientConfigSessionHandler) flushQueuedPluginMessagesTo(serverConn *serverConnection) error {
262+
smc, ok := serverConn.ensureConnected()
263+
if !ok {
264+
return netmc.ErrClosedConn
265+
}
266+
267+
h.mu.Lock()
268+
defer h.mu.Unlock()
269+
if h.mu.readyServer == serverConn {
270+
return nil
271+
}
272+
273+
h.mu.pluginMessagesBytes = 0
274+
n := h.mu.pluginMessages.Len()
275+
msgs := make([]*plugin.Message, 0, n)
276+
for h.mu.pluginMessages.Len() != 0 {
277+
msgs = append(msgs, h.mu.pluginMessages.PopFront())
278+
}
279+
for _, pm := range msgs {
280+
if err := smc.BufferPacket(pm); err != nil {
281+
return fmt.Errorf("error buffering queued config plugin message %q: %w", pm.Channel, err)
282+
}
283+
}
284+
if n != 0 {
285+
if err := smc.Flush(); err != nil {
286+
return err
287+
}
288+
}
289+
h.mu.readyServer = serverConn
290+
return nil
291+
}
292+
167293
func (h *clientConfigSessionHandler) handleKnownPacks(p *config.KnownPacks, pc *proto.PacketContext) {
168-
smc, ok := h.player.connectionInFlightOrConnectedServer().ensureConnected()
294+
serverConn := h.player.connectionInFlightOrConnectedServer()
295+
if serverConn == nil {
296+
return
297+
}
298+
smc, ok := serverConn.ensureConnected()
169299
if ok {
170300
_ = smc.WritePacket(p)
171301
}

0 commit comments

Comments
 (0)