Skip to content

Commit 7f60924

Browse files
Owen1212055kennytv
authored andcommitted
Configuration API (#12301)
This implements a solution that preemptively exits the tick loop if we have already marked the connection as disconnecting. This avoids changing the result of Connection#isConnected in order to avoid other possibly unintentional changes. Fundamentally it should be investigated if closing the connection async is really still needed. This also additionally removes the login disconnecting logic for server stopping, as this was also a possible issue in the config stage but also shouldn't be an issue as connections are closed on server stop very early. Additionally, do not check for isConnecting() on VERIFYING, as that seemed to be an old diff originally trying to resolve this code, however isConnected is not updated at this point so it pretty much was useless.
1 parent dda39a0 commit 7f60924

File tree

48 files changed

+1807
-638
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1807
-638
lines changed

build-data/paper.at

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ public net.minecraft.server.level.ServerPlayer wardenSpawnTracker
104104
public net.minecraft.server.level.ServerPlayer$RespawnPosAngle
105105
public net.minecraft.server.level.ServerPlayerGameMode level
106106
public net.minecraft.server.level.TicketType register(Ljava/lang/String;JZLnet/minecraft/server/level/TicketType$TicketUse;)Lnet/minecraft/server/level/TicketType;
107+
public net.minecraft.server.network.ServerConfigurationPacketListenerImpl clientInformation
108+
public net.minecraft.server.network.ServerConfigurationPacketListenerImpl currentTask
109+
public net.minecraft.server.network.ServerConfigurationPacketListenerImpl finishCurrentTask(Lnet/minecraft/server/network/ConfigurationTask$Type;)V
107110
public net.minecraft.server.network.ServerGamePacketListenerImpl isChatMessageIllegal(Ljava/lang/String;)Z
108111
public net.minecraft.server.network.ServerLoginPacketListenerImpl authenticatedProfile
109112
public net.minecraft.server.network.ServerLoginPacketListenerImpl connection
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.papermc.paper.connection;
2+
3+
import java.util.Map;
4+
5+
import com.destroystokyo.paper.ClientOption;
6+
import org.bukkit.ServerLinks;
7+
8+
/**
9+
* Represents a connection that has properties shared between the GAME and CONFIG stage.
10+
*/
11+
public interface PlayerCommonConnection extends WritablePlayerCookieConnection, ReadablePlayerCookieConnection {
12+
13+
/**
14+
* Sends data to appear in this connection's report logs.
15+
* This is useful for debugging server state that may be causing
16+
* player disconnects.
17+
* <p>
18+
* These are formatted as key - value, where keys are limited to a length of 128 characters,
19+
* values are limited to 4096, and 32 maximum entries can be sent.
20+
*
21+
* @param details report details
22+
*/
23+
void sendReportDetails(Map<String, String> details);
24+
25+
/**
26+
* Sends the given server links to this connection.
27+
*
28+
* @param links links to send
29+
*/
30+
void sendLinks(ServerLinks links);
31+
32+
/**
33+
* Transfers this connection to another server.
34+
*
35+
* @param host host
36+
* @param port port
37+
*/
38+
void transfer(String host, int port);
39+
40+
/**
41+
* @param type client option
42+
* @return the client option value of the player
43+
*/
44+
<T> T getClientOption(ClientOption<T> type);
45+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.papermc.paper.connection;
2+
3+
import com.destroystokyo.paper.ClientOption;
4+
import com.destroystokyo.paper.profile.PlayerProfile;
5+
import net.kyori.adventure.audience.Audience;
6+
7+
public interface PlayerConfigurationConnection extends PlayerCommonConnection {
8+
9+
/**
10+
* Returns the audience representing the player in configuration mode.
11+
* This can be used to interact with the Adventure API during the configuration stage.
12+
* This is guaranteed to be an instance of {@link PlayerConfigurationConnection}
13+
*
14+
* @return the configuring player audience
15+
*/
16+
Audience getAudience();
17+
18+
/**
19+
* Gets the profile for this connection.
20+
*
21+
* @return profile
22+
*/
23+
PlayerProfile getProfile();
24+
25+
/**
26+
* Clears the players chat history and their local chat.
27+
*/
28+
void clearChat();
29+
30+
/**
31+
* Completes the configuration for this player, which will cause this player to reenter the game.
32+
* <p>
33+
* Note, this should be only be called if you are reconfiguring the player.
34+
*/
35+
void completeReconfiguration();
36+
37+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.papermc.paper.connection;
2+
3+
import java.net.InetSocketAddress;
4+
import java.net.SocketAddress;
5+
import net.kyori.adventure.text.Component;
6+
import org.jspecify.annotations.Nullable;
7+
8+
public interface PlayerConnection {
9+
10+
/**
11+
* Disconnects the player connection.
12+
* <p>
13+
* Note that calling this during connection related events may caused undefined behavior.
14+
*
15+
* @param component disconnect reason
16+
*/
17+
void disconnect(Component component);
18+
19+
/**
20+
* Gets if this connection originated from a transferred connection.
21+
* <p>
22+
* Do note that this is sent and stored on the client.
23+
*
24+
* @return is transferred
25+
*/
26+
boolean isTransferred();
27+
28+
/**
29+
* Gets the raw remote address of the connection. This may be a proxy address
30+
* or a Unix domain socket address, depending on how the channel was established.
31+
*
32+
* @return the remote {@link SocketAddress} of the channel
33+
*/
34+
SocketAddress getAddress();
35+
36+
/**
37+
* Gets the real client address of the player. If the connection is behind a proxy,
38+
* this will be the actual player’s IP address extracted from the proxy handshake.
39+
*
40+
* @return the client {@link InetSocketAddress}
41+
*/
42+
InetSocketAddress getClientAddress();
43+
44+
/**
45+
* Returns the virtual host the client is connected to.
46+
*
47+
* <p>The virtual host refers to the hostname/port the client used to
48+
* connect to the server.</p>
49+
*
50+
* @return The client's virtual host, or {@code null} if unknown
51+
*/
52+
@Nullable InetSocketAddress getVirtualHost();
53+
54+
/**
55+
* Gets the socket address of this player's proxy
56+
*
57+
* @return the player's proxy address, null if the server doesn't have Proxy Protocol enabled, or the player didn't connect to an HAProxy instance
58+
*/
59+
@Nullable InetSocketAddress getHAProxyAddress();
60+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.papermc.paper.connection;
2+
3+
import org.bukkit.entity.Player;
4+
5+
public interface PlayerGameConnection extends PlayerCommonConnection {
6+
7+
/**
8+
* Bumps the player to the configuration stage.
9+
* <p>
10+
* This will, by default, cause the player to stay until their connection is released by
11+
* {@link PlayerConfigurationConnection#completeReconfiguration()}
12+
*/
13+
void reenterConfiguration();
14+
15+
/**
16+
* Gets the player that is associated with this game connection.
17+
*
18+
* @return player
19+
*/
20+
Player getPlayer();
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.papermc.paper.connection;
2+
3+
import com.destroystokyo.paper.profile.PlayerProfile;
4+
import org.jspecify.annotations.Nullable;
5+
6+
public interface PlayerLoginConnection extends ReadablePlayerCookieConnection {
7+
8+
/**
9+
* Gets the authenticated profile for this connection.
10+
* This may return null depending on what stage this connection is at.
11+
*
12+
* @return authenticated profile, or null if not present
13+
*/
14+
@Nullable PlayerProfile getAuthenticatedProfile();
15+
16+
/**
17+
* Gets the player profile that this connection is requesting to authenticate as.
18+
*
19+
* @return the unsafe unauthenticated profile, or null if not sent
20+
*/
21+
@Nullable
22+
PlayerProfile getUnsafeProfile();
23+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.papermc.paper.connection;
2+
3+
import java.util.concurrent.CompletableFuture;
4+
import org.bukkit.NamespacedKey;
5+
6+
public interface ReadablePlayerCookieConnection extends PlayerConnection {
7+
8+
/**
9+
* Retrieves a cookie from this connection.
10+
*
11+
* @param key the key identifying the cookie
12+
* @return a {@link CompletableFuture} that will be completed when the
13+
* Cookie response is received or otherwise available. If the cookie is not
14+
* set in the client, the {@link CompletableFuture} will complete with a
15+
* null value.
16+
*/
17+
CompletableFuture<byte[]> retrieveCookie(NamespacedKey key);
18+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.papermc.paper.connection;
2+
3+
import org.bukkit.NamespacedKey;
4+
5+
public interface WritablePlayerCookieConnection extends PlayerConnection {
6+
7+
/**
8+
* Stores a cookie in this player's client.
9+
*
10+
* @param key the key identifying the cookie
11+
* @param value the data to store in the cookie
12+
* @throws IllegalStateException if a cookie cannot be stored at this time
13+
*/
14+
void storeCookie(NamespacedKey key, byte[] value);
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* This package contains events related to player connections, such as joining and leaving the server.
3+
*/
4+
@ApiStatus.Experimental
5+
@NullMarked
6+
package io.papermc.paper.connection;
7+
8+
import org.jetbrains.annotations.ApiStatus;
9+
import org.jspecify.annotations.NullMarked;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.papermc.paper.event.connection;
2+
3+
import io.papermc.paper.connection.PlayerConnection;
4+
import net.kyori.adventure.text.Component;
5+
import org.bukkit.event.Event;
6+
import org.bukkit.event.HandlerList;
7+
import org.jetbrains.annotations.ApiStatus;
8+
import org.jspecify.annotations.Nullable;
9+
10+
/**
11+
* Validates whether a player connection is able to log in.
12+
* <p>
13+
* Called when is attempting to log in for the first time, or is finishing up
14+
* being configured.
15+
*/
16+
public class PlayerConnectionValidateLoginEvent extends Event {
17+
18+
private static final HandlerList HANDLER_LIST = new HandlerList();
19+
20+
private final PlayerConnection connection;
21+
private @Nullable Component kickMessage;
22+
23+
@ApiStatus.Internal
24+
public PlayerConnectionValidateLoginEvent(final PlayerConnection connection, final @Nullable Component kickMessage) {
25+
super(false);
26+
this.connection = connection;
27+
this.kickMessage = kickMessage;
28+
}
29+
30+
/**
31+
* Gets the connection of the player in this event.
32+
* Note, the type of this connection is not guaranteed to be stable across versions.
33+
* Additionally, disconnecting the player through this connection / using any methods that may send packets
34+
* is not supported.
35+
*
36+
* @return connection
37+
*/
38+
public PlayerConnection getConnection() {
39+
return this.connection;
40+
}
41+
42+
/**
43+
* Allows the player to log in.
44+
* This skips any login validation checks.
45+
*/
46+
public void allow() {
47+
this.kickMessage = null;
48+
}
49+
50+
/**
51+
* Disallows the player from logging in, with the given reason
52+
*
53+
* @param message Kick message to display to the user
54+
*/
55+
public void kickMessage(final Component message) {
56+
this.kickMessage = message;
57+
}
58+
59+
/**
60+
* Gets the reason for why a player is not allowed to join the server.
61+
* This will be null in the case that the player is allowed to log in.
62+
*
63+
* @return disallow reason
64+
*/
65+
public @Nullable Component getKickMessage() {
66+
return this.kickMessage;
67+
}
68+
69+
/**
70+
* Gets if the player is allowed to enter the next stage.
71+
*
72+
* @return if allowed
73+
*/
74+
public boolean isAllowed() {
75+
return this.kickMessage == null;
76+
}
77+
78+
@Override
79+
public HandlerList getHandlers() {
80+
return HANDLER_LIST;
81+
}
82+
83+
public static HandlerList getHandlerList() {
84+
return HANDLER_LIST;
85+
}
86+
}

0 commit comments

Comments
 (0)