@@ -2,9 +2,13 @@ package proxy
22
33import (
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.
8294func (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
117122func (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+
167293func (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