Skip to content

Commit

Permalink
Support forwarding bungeecord plugin channel to the ENTRY server. Bun…
Browse files Browse the repository at this point in the history
…geecord PROXIES can now support direct connection from client.

Version 2.0.0
  • Loading branch information
ARTI5T committed Jul 17, 2023
1 parent af9dee4 commit ffeb5e9
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 30 deletions.
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,8 @@ sourceSets.main.java.srcDir(generateTemplates.map { it.outputs })

rootProject.idea.project.settings.taskTriggers.afterSync generateTemplates
project.eclipse.synchronizationTasks(generateTemplates)

tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.velocitypowered.proxy.connection.backend;

import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.util.ByteBufDataInput;
import work.art1st.proxiedproxy.PPlugin;

public class VBungeeCordMessageResponder extends BungeeCordMessageResponder {
private final VelocityServer proxy;
public VBungeeCordMessageResponder(VelocityServer proxy, ConnectedPlayer player) {
super(proxy, player);
this.proxy = proxy;
}

/* Send the PluginMessage to ENTRY */
@Override
boolean process(PluginMessage message) {
if (PPlugin.getProxyConfig().sendSwitchServerPluginMessageToEntry) {
ByteBufDataInput in = new ByteBufDataInput(message.content());
String subChannel = in.readUTF();
String target;
switch (subChannel) {
case "Connect":
case "ConnectOther":
target = in.readUTF();
if (this.proxy.getServer(target).isEmpty()) {
return false;
}
break;
case "PlayerCount":
case "PlayerList":
target = in.readUTF();
if (!target.equals("ALL") && this.proxy.getServer(target).isEmpty()) {
return false;
}
break;
}
}
return super.process(message);
}
}
4 changes: 4 additions & 0 deletions src/main/java/work/art1st/proxiedproxy/EventHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public void handleLoginPluginMessageResponse(PLoginPluginMessageResponseEvent ev
String response = event.getMessage();

ForwardingParser parser = event.getForwardingParser();
if (parser == null) {
loginInboundConnection.disconnect(Component.text("Invalid login packet: Can not parse profile data."));
return;
}
/* Async callback function that stores forwarded player info into cache
* The callback is always invoked before the next event (GameProfileRequestEvent). */
PPlugin.debugOutput(response);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/work/art1st/proxiedproxy/PPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private static boolean doInitialize() throws IOException {
break;
case PROXY:
proxyConfig.allowClientConnection = configFile.getOrElse("proxy.allow-client-connection", false);
proxyConfig.sendSwitchServerPluginMessageToEntry = configFile.getOrElse("proxy.send-switch-server-plugin-message-to-entry", true);
try {
proxyConfig.skinServiceBackendVerifier = new SkinServiceBackendVerifier(
configFile.getOrElse("proxy.skin-service-backend.allowed", new ArrayList<>()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ public class ProxyConfig {
.expireAfterAccess(FORWARDING_PACKET_TIMEOUT + 5, TimeUnit.SECONDS)
.build();
public SkinServiceBackendVerifier skinServiceBackendVerifier;
public boolean sendSwitchServerPluginMessageToEntry;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package work.art1st.proxiedproxy.platform.bungeecord;

import lombok.SneakyThrows;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import work.art1st.proxiedproxy.PPlugin;
import work.art1st.proxiedproxy.platform.bungeecord.connection.BDownstreamBridge;
import work.art1st.proxiedproxy.platform.bungeecord.event.BPostLoginEvent;
import work.art1st.proxiedproxy.platform.bungeecord.event.BPreLoginEvent;
import work.art1st.proxiedproxy.util.ReflectUtil;

public class BungeeEventListener implements Listener {
@SneakyThrows
Expand All @@ -20,4 +27,13 @@ public void onPreLogin(PreLoginEvent event) {
public void onPostLogin(PostLoginEvent event) {
PPlugin.getEventHandler().handlePostLogin(new BPostLoginEvent(event));
}

@SneakyThrows
@EventHandler
public void onServerSwitchEvent(ServerSwitchEvent event) {
UserConnection userConnection = (UserConnection) event.getPlayer();
ChannelWrapper ch = ReflectUtil.getDeclaredFieldValue(userConnection, "ch");
BungeeCord bungee = ReflectUtil.getDeclaredFieldValue(userConnection, "bungee");
ch.getHandle().pipeline().get(HandlerBoss.class).setHandler(new BDownstreamBridge(bungee, userConnection, userConnection.getServer()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package work.art1st.proxiedproxy.platform.bungeecord.connection;

import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.connection.DownstreamBridge;
import net.md_5.bungee.protocol.packet.PluginMessage;
import work.art1st.proxiedproxy.PPlugin;

import java.io.DataInput;

public class BDownstreamBridge extends DownstreamBridge {
private final ProxyServer bungee;
public BDownstreamBridge(ProxyServer bungee, UserConnection con, ServerConnection server) {
super(bungee, con, server);
this.bungee = bungee;
}

/* Send the PluginMessage to ENTRY */
@Override
@SuppressWarnings("checkstyle:avoidnestedblocks")
public void handle(PluginMessage pluginMessage) throws Exception
{
if (PPlugin.getProxyConfig().sendSwitchServerPluginMessageToEntry) {
DataInput in = pluginMessage.getStream();
if (pluginMessage.getTag().equals("BungeeCord")) {
String subChannel = in.readUTF();
String target;
switch (subChannel) {
case "Connect":
case "ConnectOther":
target = in.readUTF();
if (this.bungee.getServerInfo(target) == null) {
return;
}
break;
case "PlayerCount":
case "PlayerList":
target = in.readUTF();
if (!target.equals("ALL") && this.bungee.getServerInfo(target) == null) {
return;
}
break;
}
}
}
super.handle(pluginMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ public final class BLoginInboundConnection implements PLoginInboundConnection {
private final Callback<PreLoginEvent> origEventCallBack;
private PreLoginEvent cb_args_result;
private Throwable cb_args_error;
private final boolean isDirectConnection;
@SneakyThrows
public BLoginInboundConnection(PreLoginEvent preLoginEvent) {
this.handler = (InitialHandler) preLoginEvent.getConnection();
this.ch = ReflectUtil.getDeclaredFieldValue(handler, "ch");
handler = (InitialHandler) preLoginEvent.getConnection();
ch = ReflectUtil.getDeclaredFieldValue(handler, "ch");
Field doneField = AsyncEvent.class.getDeclaredField("done");
ReflectUtil.handleAccessible(doneField);
origEventCallBack = (Callback<PreLoginEvent>) doneField.get(preLoginEvent);
Expand All @@ -34,6 +35,7 @@ public BLoginInboundConnection(PreLoginEvent preLoginEvent) {
cb_args_error = error;
};
doneField.set(preLoginEvent, modifiedCallback);
isDirectConnection = isVHostFromClient(handler.getVirtualHost(), handler.getHandshake().getHost());
}

@Override
Expand All @@ -44,7 +46,7 @@ public void sendLoginPluginMessage(String contents) {

@Override
public boolean isDirectConnection() {
return false;
return isDirectConnection;
}

@SneakyThrows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@

import net.kyori.adventure.text.Component;

import java.net.InetSocketAddress;
import java.util.Locale;

public interface PLoginInboundConnection {
void sendLoginPluginMessage(String message);
boolean isDirectConnection();
/** Should only affect the effect of functions like "getRemoteAddress", not the address of the actual connection. */
void setRemoteAddress(String remoteAddress);
void disconnect(Component reason);
default boolean isVHostFromClient(InetSocketAddress address, String vHostValue) {
String cleanedAddress = address == null ? "" : address.getHostString().toLowerCase(Locale.ROOT);
String origAddress = vHostValue.toLowerCase(Locale.ROOT);
if (!origAddress.isEmpty() && origAddress.charAt(origAddress.length() - 1) == '.') {
origAddress = origAddress.substring(0, origAddress.length() - 1);
}
return !(!cleanedAddress.equals(origAddress)
&& !origAddress.endsWith("\0fml\0")
&& !origAddress.endsWith("\0fml2\0")
&& !origAddress.endsWith("\0fml3\0")
&& !origAddress.endsWith("\0fml4\0"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.BackendPlaySessionHandler;
import com.velocitypowered.proxy.connection.backend.VBungeeCordMessageResponder;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.LoginInboundConnection;
import com.velocitypowered.proxy.connection.util.ServerListPingHandler;
import lombok.Getter;
Expand Down Expand Up @@ -115,6 +120,19 @@ public void onServerLoginPluginMessage(ServerLoginPluginMessageEvent event) {
}
}

@SneakyThrows
@Subscribe
public void onServerPostConnection(ServerPostConnectEvent event) {
if (event.getPlayer().getCurrentServer().isPresent()) {
VelocityServerConnection serverConnection = (VelocityServerConnection) event.getPlayer().getCurrentServer().get();
MinecraftConnection clientConnection = serverConnection.ensureConnected();
BackendPlaySessionHandler sessionHandler = (BackendPlaySessionHandler) clientConnection.getSessionHandler();
assert sessionHandler != null;
ReflectUtil.setDeclaredFieldValue(sessionHandler, "bungeecordMessageResponder",
new VBungeeCordMessageResponder(sessionHandler.getServer(), serverConnection.getPlayer()));
}
}

@Override
public boolean configCheck() {
if (proxy.getConfiguration() instanceof VelocityConfiguration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,15 @@ public final class VLoginInboundConnection implements PLoginInboundConnection {

public VLoginInboundConnection(LoginInboundConnection connection) {
vConnection = connection;
String cleanedAddress = vConnection.getVirtualHost().map(InetSocketAddress::getHostString).map(str -> str.toLowerCase(Locale.ROOT)).orElse("");
InetSocketAddress address = vConnection.getVirtualHost().orElse(null);
String origAddress;
try {
origAddress = getServerAddressFromConnection(vConnection).toLowerCase(Locale.ROOT);
} catch (NoSuchFieldException | IllegalAccessException e) {
vConnection.disconnect(Component.text("Invalid connection."));
throw new RuntimeException(e);
}
if (!origAddress.isEmpty() && origAddress.charAt(origAddress.length() - 1) == '.') {
origAddress = origAddress.substring(0, origAddress.length() - 1);
}
isDirectConnection = !(!cleanedAddress.equals(origAddress) && !origAddress.endsWith("\0fml\0") && !origAddress.endsWith("\0fml2\0") && !origAddress.endsWith("\0fml3\0") && !origAddress.endsWith("\0fml4\0"));
isDirectConnection = isVHostFromClient(address, origAddress);
}
private static String getServerAddressFromConnection(LoginInboundConnection inbound) throws NoSuchFieldException, IllegalAccessException {
//Field delegateField = ReflectUtil.handleAccessible(inbound.getClass().getDeclaredField("delegate"));
Expand Down
28 changes: 14 additions & 14 deletions src/main/resources/config-entry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ verbose = false

# Contents below are valid for ENTRY servers
[entry]
# Replace /server with your custom command.
server-command-alias = "hub"
# Available options:
# - RSA: Prefered
# - KEY
# Downstream PROXIES should add contents in generated entry.json to their TrustedEntries.json list.
# entry.json is just a generated file for you to conveniently send it to the downstream PROXIES, modifications have no effect.
verification-type = "RSA"
entry-id = ""
pass-through-ping-vhost = true
send-v1-verification = false
# Replace /server with your custom command.
server-command-alias = "hub"
# Available options:
# - RSA: Prefered
# - KEY
# Downstream PROXIES should add contents in generated entry.json to their TrustedEntries.json list.
# entry.json is just a generated file for you to conveniently send it to the downstream PROXIES, modifications have no effect.
verification-type = "RSA"
entry-id = ""
pass-through-ping-vhost = true
send-v1-verification = false

[entry.key]
# Valid if verification-type is set to "KEY"
key = ""
[entry.key]
# Valid if verification-type is set to "KEY"
key = ""
19 changes: 11 additions & 8 deletions src/main/resources/config-proxy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ verbose = false

# Contents below are valid for PROXY servers
[proxy]
# Set to false to disable connection from clients.
allow-client-connection = false
# Set to true to enable clients directly connecting to the server.
allow-client-connection = false
# Set to
send-switch-server-plugin-message-to-entry = true

[proxy.skin-service-backend]
# Both empty: Disabled
# Both not empty: Will work only in whitelist mode
blocked = []
allowed = []
union-query-api = "https://skin.mualliance.ltd/api/union/profile/mapped/byuuid/"
[proxy.skin-service-backend]
# Both empty: Disabled
# Both not empty: Will work only in whitelist mode
# Note: If enabled, the plugin will also check the skin server of directly connected players.
blocked = []
allowed = []
union-query-api = "https://skin.mualliance.ltd/api/union/profile/mapped/byuuid/"

0 comments on commit ffeb5e9

Please sign in to comment.