Skip to content

Commit

Permalink
Merge pull request #7 from emortalmc/feat/multi-proxy
Browse files Browse the repository at this point in the history
Feat/multi proxy
  • Loading branch information
ZakShearman authored Sep 9, 2024
2 parents 6cd31b2 + d9e67ba commit 568e018
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 71 deletions.
11 changes: 3 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ FROM --platform=$TARGETPLATFORM azul/zulu-openjdk:21-jre
RUN apt-get update && apt-get install -y wget

# We manually set the Velocity version to avoid bugs
ENV VELOCITY_JAR_URL "https://api.papermc.io/v2/projects/velocity/versions/3.3.0-SNAPSHOT/builds/428/downloads/velocity-3.3.0-SNAPSHOT-428.jar"
#ENV VIA_VERSION_JAR_URL "https://github.com/ViaVersion/ViaVersion/releases/download/4.9.2/ViaVersion-4.9.2.jar"
ENV VELOCITY_JAR_URL "https://api.papermc.io/v2/projects/velocity/versions/3.3.0-SNAPSHOT/builds/430/downloads/velocity-3.3.0-SNAPSHOT-430.jar"

RUN mkdir /app
WORKDIR /app

# Download the Velocity jar
RUN wget -O velocity.jar $VELOCITY_JAR_URL
#COPY run/velocity*.jar velocity.jar
COPY run/velocity.toml .
COPY run/server-icon.png .

Expand All @@ -20,13 +20,8 @@ RUN mkdir /app/plugins
WORKDIR /app/plugins
COPY build/libs/*-all.jar velocity-core.jar

RUN #wget -O viaversion.jar $VIA_VERSION_JAR_URL
#COPY run/plugins/viaversion/config.yml viaversion/config.yml

RUN #wget -O viabackwards.jar https://github.com/ViaVersion/ViaBackwards/releases/download/4.9.1/ViaBackwards-4.9.1.jar

# Go back to the base directory for our server
WORKDIR /app

ENTRYPOINT ["java"]
CMD ["-Dlog4j2.debug", "-jar", "/app/velocity.jar"]
CMD ["-jar", "/app/velocity.jar"]
7 changes: 6 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies {
annotationProcessor("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT")
implementation("net.kyori:adventure-text-minimessage:4.14.0")

implementation("dev.emortal.api:common-proto-sdk:44913ed")
implementation("dev.emortal.api:common-proto-sdk:b05808d")
implementation("dev.emortal.api:agones-sdk:1.1.0")
implementation("dev.emortal.api:live-config-parser:8f566b9")
implementation("dev.emortal.api:module-system:1.0.0")
Expand All @@ -40,6 +40,11 @@ dependencies {

implementation("com.github.ben-manes.caffeine:caffeine:3.1.8")

// JWT
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
implementation("io.jsonwebtoken:jjwt-impl:0.12.6")
implementation("io.jsonwebtoken:jjwt-gson:0.12.6")

compileOnly("org.jetbrains:annotations:24.0.1")

testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
Expand Down
14 changes: 9 additions & 5 deletions run/velocity.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Config version. Do not change this
config-version = "2.5"
config-version = "2.7"

# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.
# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565.
bind = "0.0.0.0:25565"

# What should be the MOTD? This gets displayed when the player adds your server to
# their server list. Legacy color codes and JSON are accepted.
motd = "&#09add3A Velocity Server"
# their server list. Only MiniMessage format is accepted.
motd = "<#09add3>A Velocity Server"

# What should we display for the maximum number of players? (Velocity does not support a cap
# on the number of players online.)
Expand Down Expand Up @@ -130,6 +130,10 @@ log-command-executions = false
# and disconnecting from the proxy.
log-player-connections = true

# Allows players transferred from other hosts via the
# Transfer packet (Minecraft 1.20.5) to be received.
accepts-transfers = true

[query]
# Whether to enable responding to GameSpy 4 query responses or not.
enabled = false
Expand All @@ -141,4 +145,4 @@ port = 25565
map = "Velocity"

# Whether plugins should be shown in query response by default or not
show-plugins = false
show-plugins = false
6 changes: 4 additions & 2 deletions src/main/java/dev/emortal/velocity/agones/AgonesListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
import dev.agones.sdk.AgonesSDKProto;
import dev.agones.sdk.SDKGrpc;
Expand Down Expand Up @@ -42,7 +42,7 @@ void onListenerBound(@NotNull ListenerBoundEvent event) {
}

@Subscribe(async = true)
void onLogin(@NotNull LoginEvent event) {
void onLogin(@NotNull PostLoginEvent event) {
UUID id = event.getPlayer().getUniqueId();
AlphaAgonesSDKProto.PlayerID playerId = AlphaAgonesSDKProto.PlayerID.newBuilder().setPlayerID(id.toString()).build();

Expand All @@ -52,6 +52,8 @@ void onLogin(@NotNull LoginEvent event) {

@Subscribe(async = true)
void onDisconnect(@NotNull DisconnectEvent event) {
if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) return;

UUID id = event.getPlayer().getUniqueId();
AlphaAgonesSDKProto.PlayerID playerId = AlphaAgonesSDKProto.PlayerID.newBuilder().setPlayerID(id.toString()).build();

Expand Down
1 change: 1 addition & 0 deletions src/main/java/dev/emortal/velocity/lang/ChatMessages.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public interface ChatMessages {
Args0 GENERIC_ERROR = () -> red("An error occurred");
Args0 ERROR_NO_SKIN = () -> red("You don't have a skin. This doesn't work???");
Args0 ERROR_INVALID_TIME_FORMAT = () -> red("Invalid time format. Must be in ISO-8601 format (yyyy-MM-ddTHH:mm:ss e.g. 2022-01-01T12:00:00)");
Args1<String> ERROR_INVALID_TRANSFER_COOKIE = reason -> red("Invalid transfer cookie (%s)".formatted(reason));
Args1<String> ERROR_INVALID_TIME_FORMAT_ARG = arg -> red("Invalid time format at %s. Must be in ISO-8601 format (yyyy-MM-ddTHH:mm:ss e.g. 2022-01-01T12:00:00)".formatted(arg));
Args1<String> ERROR_TIME_IN_PAST_ARG = arg -> red("Invalid time. %s is in the past".formatted(arg));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ public void handleKickedFromServer(@NotNull KickedFromServerEvent event, @NotNul
continuation.resume();
});

this.matchmaker.queueInitialLobby(event.getPlayer().getUniqueId());
this.matchmaker.loginQueue(event.getPlayer().getUniqueId(), false);
}

private void sendToLobbyServer(@NotNull PlayerChooseInitialServerEvent event, @NotNull Continuation continuation) {
Player player = event.getPlayer();
LOGGER.debug("Queueing initial lobby for '{}'", player.getUsername());

try {
this.matchmaker.queueInitialLobby(player.getUniqueId());
this.matchmaker.loginQueue(player.getUniqueId(), false);
} catch (StatusRuntimeException exception) {
LOGGER.error("Failed to connect '{}' to lobby", player.getUsername(), exception);
event.getPlayer().disconnect(ChatMessages.ERROR_CONNECTING_TO_LOBBY.get());
Expand All @@ -93,6 +93,7 @@ private void sendToLobbyServer(@NotNull PlayerChooseInitialServerEvent event, @N

private void handleMatchCreated(@NotNull MatchCreatedMessage message) {
Match match = message.getMatch();
if (!match.getGameModeId().equals("lobby")) return;

for (Ticket ticket : match.getTicketsList()) {
if (ticket.getAutoTeleport()) continue; // We don't care about auto teleport tickets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public boolean onLoad() {
EmortalEventManager eventManager = super.adapters().eventManager();
EmortalScheduler scheduler = super.adapters().scheduler();

// server list
eventManager.register(new ServerPingListener());
// transfer cookie verification
eventManager.register(new LoginCookieVerification());

// tablist
PlayerTrackerService playerTracker = GrpcStubCollection.getPlayerTrackerService().orElse(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package dev.emortal.velocity.misc.listener;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.velocitypowered.api.event.Continuation;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.player.CookieReceiveEvent;
import dev.emortal.velocity.lang.ChatMessages;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public class LoginCookieVerification {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginCookieVerification.class);

private static final Key COOKIE_NAME = net.kyori.adventure.key.Key.key("emortalmc", "proxy_route_token");

private static final SecretKey SIGNING_KEY;

static {
String signingKey = System.getenv("EDGE_ROUTING_KEY");
SIGNING_KEY = Keys.hmacShaKeyFor(signingKey.getBytes());
}

private final Cache<UUID, Consumer<@Nullable CookieResult>> pendingPlayers = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.evictionListener(this::onEvict)
.build();

@Subscribe
public void onLogin(@NotNull LoginEvent event, Continuation continuation) {
event.getPlayer().requestCookie(COOKIE_NAME);

this.pendingPlayers.put(event.getPlayer().getUniqueId(), result -> {
if (result == null) {
event.getPlayer().disconnect(ChatMessages.ERROR_INVALID_TRANSFER_COOKIE.get(CookieResult.INVALID_JWT.name()));
} else if (!result.isValid()) {
event.getPlayer().disconnect(ChatMessages.ERROR_INVALID_TRANSFER_COOKIE.get(result.name()));
}

continuation.resume();
});
}

@Subscribe
public void onCookieReceived(@NotNull CookieReceiveEvent event) {
System.out.println("Received cookie event " + event.getOriginalKey());
if (!event.getOriginalKey().equals(COOKIE_NAME)) return;

Consumer<CookieResult> consumer = this.pendingPlayers.getIfPresent(event.getPlayer().getUniqueId());
if (consumer == null) {
LOGGER.warn("Received cookie for player '{}' without pending request", event.getPlayer().getUsername());
return;
}

event.setResult(CookieReceiveEvent.ForwardResult.handled());
CookieResult result = this.isCookieValid(event.getPlayer().getUsername(), event.getOriginalData());
consumer.accept(result);
}

private CookieResult isCookieValid(String username, byte[] value) {
if (value == null || value.length == 0) return CookieResult.EMPTY_COOKIE;

String jwt = new String(value, StandardCharsets.UTF_8);

Claims claims;
try {
claims = Jwts.parser().verifyWith(SIGNING_KEY).build()
.parseSignedClaims(jwt)
.getPayload();
} catch (JwtException e) {
LOGGER.warn("Rejected JWT cookie for '{}' '{}'", username, jwt, e);
return CookieResult.INVALID_JWT;
}

if (System.currentTimeMillis() > claims.getExpiration().getTime()) return CookieResult.EXPIRED;
if (!claims.get("proxyId", String.class).equals(System.getenv("HOSTNAME"))) return CookieResult.WRONG_PROXY_ID;
if (!claims.get("username", String.class).equals(username)) return CookieResult.WRONG_USERNAME;

return CookieResult.VALID;
}

private void onEvict(@Nullable UUID playerId, @Nullable Consumer<@Nullable CookieResult> consumer, @NotNull RemovalCause cause) {
if (cause != RemovalCause.EXPIRED) return;
if (playerId == null || consumer == null) return;

consumer.accept(null);
}

private enum CookieResult {
EMPTY_COOKIE(false),
INVALID_JWT(false),
WRONG_PROXY_ID(false),
WRONG_USERNAME(false),
EXPIRED(false),
VALID(true);

private final boolean valid;

CookieResult(boolean valid) {
this.valid = valid;
}

public boolean isValid() {
return this.valid;
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import dev.emortal.velocity.lang.ChatMessages;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
Expand All @@ -19,13 +19,15 @@ public PlayerJoinQuitListener(@NotNull Audience audience) {
}

@Subscribe
public void onJoin(@NotNull LoginEvent event) {
public void onJoin(@NotNull PostLoginEvent event) {
ChatMessages.JOIN.send(this.audience, event.getPlayer().getUsername());
this.audience.playSound(Sound.sound(JOIN_QUIT_SOUND, Sound.Source.MASTER, 1F, 1.2F));
}

@Subscribe
public void onQuit(@NotNull DisconnectEvent event) {
if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) return;

ChatMessages.QUIT.send(this.audience, event.getPlayer().getUsername());
this.audience.playSound(Sound.sound(JOIN_QUIT_SOUND, Sound.Source.MASTER, 1F, 0.5F));
}
Expand Down

0 comments on commit 568e018

Please sign in to comment.