Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 13, 2026

When a player connects but no backend servers are available, the proxy was closing the TCP connection without sending a disconnect packet. The client received no error message - just a dropped connection.

Root Cause

KickPlayerAsync returned false when the player wasn't in authenticated phases (Play/Configuration/post-LoginSuccess), preventing disconnect packet transmission. The conditional checks were:

// v1_20_2_to_latest example - similar in other versions
if (await player.IsPlayingAsync(cancellationToken))
    await channel.SendPacketAsync(new NbtDisconnectPacket { Reason = reason }, cancellationToken);
else if (await player.IsConfiguringAsync(cancellationToken))
    await channel.SendPacketAsync(new NbtDisconnectPacket { Reason = reason }, cancellationToken);
else if (await player.IsLoggingInAsync(cancellationToken))
    await channel.SendPacketAsync(new JsonDisconnectPacket { Reason = reason }, cancellationToken);
else
    return false;  // ← No packet sent

The IsLoggingInAsync check verified if LoginSuccessPacket was in the registry, which only occurs after successful login - not during early login failure scenarios.

Changes

  • Simplified kick logic: Always attempt sending disconnect packet as fallback instead of returning false
  • Removed redundant helpers: Deleted IsInLoginPhaseAsync methods (checked same registry as IsLoggingInAsync since both serverbound/clientbound Login mappings load simultaneously)
  • Consistent implementation: Applied to all protocol versions (v1_7_2_to_1_12_2, v1_13_to_1_20_1, v1_20_2_to_latest)
// After fix
if (await player.IsPlayingAsync(cancellationToken))
    await channel.SendPacketAsync(new NbtDisconnectPacket { Reason = reason }, cancellationToken);
else if (await player.IsConfiguringAsync(cancellationToken))
    await channel.SendPacketAsync(new NbtDisconnectPacket { Reason = reason }, cancellationToken);
else
    // Fallback: send Login disconnect packet
    await channel.SendPacketAsync(new JsonDisconnectPacket { Reason = reason }, cancellationToken);

return true;

Players now receive "Failed to find a server for you" message before disconnection.

Original prompt

It does kick me without any message. No even a single packet arrived to the client. It just closes TCP connection, but I expect disconnect packet with message.

[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ForwardingSupport.Velocity.Services.ForwardingService event listener
[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ForwardingSupport.Velocity.Services.ForwardingService event listener method OnPhaseChanged
[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ForwardingSupport.Velocity.Services.ForwardingService event listener method OnMessageReceived
[19:38:31 VRB] [Void.Proxy.Plugins.Context.PluginAssemblyLoadContext] Loading System.Collections.Concurrent assembly into Void.Proxy.Plugins.ModsSupport.Forge.dll context
[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ModsSupport.Forge.Services.HandshakeService event listener
[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ModsSupport.Forge.Services.HandshakeService event listener method OnPlayerDisconnect
[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ModsSupport.Forge.Services.HandshakeService event listener method OnServerboundHandshakeBuild
[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ModsSupport.Forge.Services.HandshakeService event listener method OnLoginPluginMessageEvent
[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ModsSupport.Forge.Services.HandshakeService event listener method OnMessageReceived
[19:38:31 VRB] [Void.Proxy.Events.EventService] Registering Void.Proxy.Plugins.ModsSupport.Forge.Services.HandshakeService event listener method OnPhaseChanged
[19:38:31 VRB] [Void.Proxy.Players.PlayerService] Player 10.42.4.0:42521 connecting
[19:38:31 VRB] [Void.Proxy.Links.LinkService] Looking for a server for 10.42.4.0:42521 player
[19:38:31 VRB] [Void.Proxy.Links.LinkService] Connecting 10.42.4.0:42521 player to a args-server-1 server
[19:38:31 VRB] [Void.Proxy.Plugins.Common.Network.Channels.Services.SimpleMinecraftChannelBuilderService] Searching for channel builder for a 10.42.4.0:42521 player
[19:38:31 VRB] [Void.Proxy.Events.EventService] Invoking SearchChannelBuilderEvent event
[19:38:31 VRB] [Void.Proxy.Plugins.Context.PluginAssemblyLoadContext] Loading System.Linq assembly into Void.Proxy.Plugins.ProtocolSupport.Java.v1_7_2_to_1_12_2.dll context
[19:38:31 VRB] [Void.Proxy.Plugins.Context.PluginAssemblyLoadContext] Loading System.Linq assembly into Void.Proxy.Plugins.ProtocolSupport.Java.v1_13_to_1_20_1.dll context
[19:38:31 VRB] [Void.Proxy.Plugins.Context.PluginAssemblyLoadContext] Loading System.Linq assembly into Void.Proxy.Plugins.ProtocolSupport.Java.v1_20_2_to_latest.dll context
[19:38:31 VRB] [Void.Proxy.Events.EventService] Completed invoking SearchChannelBuilderEvent event
[19:38:31 WRN] [Void.Proxy.Plugins.Common.Network.Channels.Services.SimpleMinecraftChannelBuilderService] Channel builder not found for a 10.42.4.0:42521 player
[19:38:31 VRB] [Void.Proxy.Plugins.Common.Network.Channels.Services.SimpleMinecraftChannelBuilderService] Building channel for a 10.42.4.0:42521 player
[19:38:31 VRB] [Void.Proxy.Plugins.Common.Network.Channels.Services.SimpleMinecraftChannelBuilderService] Client 10.42.4.0:42521 is using SimpleMinecraftChannel channel implementation
[19:38:31 VRB] [Void.Proxy.Events.EventService] Invoking ChannelCreatedEvent event
[19:38:31 VRB] [Void.Proxy.Events.EventService] Completed invoking ChannelCreatedEvent event
[19:38:31 VRB] [Void.Proxy.Plugins.Common.Network.Channels.Services.SimpleMinecraftChannelBuilderService] Building channel for a args-server-1 server
[19:38:31 WRN] [Void.Proxy.Links.LinkService] Player 10.42.4.0:42521 cannot connect to a args-server-1 server because it is unavailable: Name or service not known
[19:38:31 VRB] [Void.Proxy.Links.LinkService] Connecting 10.42.4.0:42521 player to a args-server-2 server
[19:38:31 VRB] [Void.Proxy.Plugins.Common.Network.Channels.Services.SimpleMinecraftChannelBuilderService] Building channel for a args-server-2 server
[19:38:31 WRN] [Void.Proxy.Links.LinkService] Player 10.42.4.0:42521 cannot connect to a args-server-2 server because it is unavailable: Name or service not known
[19:38:31 VRB] [Void.Proxy.Players.PlayerService] Kicking player 10.42.4.0:42521
[19:38:31 VRB] [Void.Proxy.Events.EventService] Invoking PlayerKickEvent event
[19:38:31 VRB] [Void.Proxy.Events.EventService] Completed invoking PlayerKickEvent event
[19:38:31 VRB] [Void.Proxy.Events.EventService] Invoking PlayerDisconnectedEvent event
[19:38:31 INF] [Void.Proxy.Players.PlayerService] Player 10.42.4.0:42521 disconnected
[19:38:31 VRB] [Void.Proxy.Events.EventService] Unregistering Void.Proxy.Plugins.ForwardingSupport.Velocity.Services.ForwardingService event listener
[19:38:31 VRB] [Void.Proxy.Events.EventService] Unregistering Void.Proxy...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits January 13, 2026 19:47
Added IsInLoginPhaseAsync helper method to detect when client is in login phase but not yet authenticated. Updated KickPlayerAsync to send disconnect packet even when player hasn't completed login, fixing TCP disconnect without message issue.

Co-authored-by: caunt <5324218+caunt@users.noreply.github.com>
Removed redundant IsInLoginPhaseAsync helper and simplified kick logic to always attempt sending JsonDisconnectPacket as fallback when not in authenticated phases. This ensures clients receive disconnect message even when server connection fails early in login flow.

Co-authored-by: caunt <5324218+caunt@users.noreply.github.com>
Changed to use channel.SendPacketAsync consistently across all code paths in KickPlayerAsync for v1_20_2_to_latest.

Co-authored-by: caunt <5324218+caunt@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix TCP connection closure without disconnect message fix(protocol): send disconnect packet when no servers available during login Jan 13, 2026
Copilot AI requested a review from caunt January 13, 2026 20:18
@caunt caunt closed this Jan 20, 2026
@caunt caunt deleted the copilot/fix-tcp-disconnect-issue branch January 20, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants